mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-09 09:19:54 +00:00
dynamic iterations
This commit is contained in:
11
chat.go
11
chat.go
@@ -42,6 +42,7 @@ type Request struct {
|
|||||||
Prompt string `json:"prompt"`
|
Prompt string `json:"prompt"`
|
||||||
Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
Temperature float64 `json:"temperature"`
|
Temperature float64 `json:"temperature"`
|
||||||
|
Iterations int64 `json:"iterations"`
|
||||||
JSON bool `json:"json"`
|
JSON bool `json:"json"`
|
||||||
Search bool `json:"search"`
|
Search bool `json:"search"`
|
||||||
Reasoning Reasoning `json:"reasoning"`
|
Reasoning Reasoning `json:"reasoning"`
|
||||||
@@ -92,6 +93,10 @@ func (r *Request) Parse() (*openrouter.ChatCompletionRequest, error) {
|
|||||||
|
|
||||||
request.Model = r.Model
|
request.Model = r.Model
|
||||||
|
|
||||||
|
if r.Iterations < 1 || r.Iterations > 50 {
|
||||||
|
return nil, fmt.Errorf("invalid iterations (1-50): %d", r.Iterations)
|
||||||
|
}
|
||||||
|
|
||||||
if r.Temperature < 0 || r.Temperature > 2 {
|
if r.Temperature < 0 || r.Temperature > 2 {
|
||||||
return nil, fmt.Errorf("invalid temperature (0-2): %f", r.Temperature)
|
return nil, fmt.Errorf("invalid temperature (0-2): %f", r.Temperature)
|
||||||
}
|
}
|
||||||
@@ -253,10 +258,10 @@ func HandleChat(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
for iteration := range env.Settings.MaxIterations {
|
for iteration := range raw.Iterations {
|
||||||
debug("iteration %d of %d", iteration+1, env.Settings.MaxIterations)
|
debug("iteration %d of %d", iteration+1, raw.Iterations)
|
||||||
|
|
||||||
if iteration == env.Settings.MaxIterations-1 {
|
if iteration == raw.Iterations-1 {
|
||||||
debug("no more tool calls")
|
debug("no more tool calls")
|
||||||
|
|
||||||
request.Tools = nil
|
request.Tools = nil
|
||||||
|
12
env.go
12
env.go
@@ -18,8 +18,7 @@ type EnvTokens struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EnvSettings struct {
|
type EnvSettings struct {
|
||||||
CleanContent bool `json:"cleanup"`
|
CleanContent bool `json:"cleanup"`
|
||||||
MaxIterations uint `json:"iterations"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnvUser struct {
|
type EnvUser struct {
|
||||||
@@ -44,8 +43,7 @@ type Environment struct {
|
|||||||
var env = Environment{
|
var env = Environment{
|
||||||
// defaults
|
// defaults
|
||||||
Settings: EnvSettings{
|
Settings: EnvSettings{
|
||||||
CleanContent: true,
|
CleanContent: true,
|
||||||
MaxIterations: 3,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +65,6 @@ func (e *Environment) Init() error {
|
|||||||
log.Warning("Debug mode enabled")
|
log.Warning("Debug mode enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// check max iterations
|
|
||||||
e.Settings.MaxIterations = max(e.Settings.MaxIterations, 1)
|
|
||||||
|
|
||||||
// check if server secret is set
|
// check if server secret is set
|
||||||
if e.Tokens.Secret == "" {
|
if e.Tokens.Secret == "" {
|
||||||
log.Warning("Missing tokens.secret, generating new...")
|
log.Warning("Missing tokens.secret, generating new...")
|
||||||
@@ -125,8 +120,7 @@ func (e *Environment) Store() error {
|
|||||||
"$.tokens.openrouter": {yaml.HeadComment(" openrouter.ai api token (required)")},
|
"$.tokens.openrouter": {yaml.HeadComment(" openrouter.ai api token (required)")},
|
||||||
"$.tokens.exa": {yaml.HeadComment(" exa search api token (optional; used by search tools)")},
|
"$.tokens.exa": {yaml.HeadComment(" exa search api token (optional; used by search tools)")},
|
||||||
|
|
||||||
"$.settings.cleanup": {yaml.HeadComment(" normalize unicode in assistant output (optional; default: true)")},
|
"$.settings.cleanup": {yaml.HeadComment(" normalize unicode in assistant output (optional; default: true)")},
|
||||||
"$.settings.iterations": {yaml.HeadComment(" max model turns per request (optional; default: 3)")},
|
|
||||||
|
|
||||||
"$.authentication.enabled": {yaml.HeadComment(" require login with username and password")},
|
"$.authentication.enabled": {yaml.HeadComment(" require login with username and password")},
|
||||||
"$.authentication.users": {yaml.HeadComment(" list of users with bcrypt password hashes")},
|
"$.authentication.users": {yaml.HeadComment(" list of users with bcrypt password hashes")},
|
||||||
|
@@ -12,8 +12,6 @@ tokens:
|
|||||||
settings:
|
settings:
|
||||||
# normalize unicode in assistant output (optional; default: true)
|
# normalize unicode in assistant output (optional; default: true)
|
||||||
cleanup: true
|
cleanup: true
|
||||||
# max model turns per request (optional; default: 3)
|
|
||||||
iterations: 3
|
|
||||||
|
|
||||||
authentication:
|
authentication:
|
||||||
# require login with username and password
|
# require login with username and password
|
||||||
|
4
main.go
4
main.go
@@ -64,8 +64,10 @@ func cache(next http.Handler) http.Handler {
|
|||||||
path := strings.ToLower(r.URL.Path)
|
path := strings.ToLower(r.URL.Path)
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
|
|
||||||
if ext == ".svg" || ext == ".ttf" || strings.HasSuffix(path, ".min.js") || strings.HasSuffix(path, ".min.css") {
|
if ext == ".png" || ext == ".svg" || ext == ".ttf" || strings.HasSuffix(path, ".min.js") || strings.HasSuffix(path, ".min.css") {
|
||||||
w.Header().Set("Cache-Control", "public, max-age=3024000, immutable")
|
w.Header().Set("Cache-Control", "public, max-age=3024000, immutable")
|
||||||
|
} else if env.Debug {
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
@@ -532,7 +532,7 @@ body:not(.loading) #loading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.statistics .ttft::before {
|
.statistics .ttft::before {
|
||||||
background-image: url(icons/ttft.svg);
|
background-image: url(icons/time.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statistics .tps::before {
|
.statistics .tps::before {
|
||||||
@@ -789,13 +789,18 @@ input.invalid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#reasoning-tokens,
|
#reasoning-tokens,
|
||||||
#temperature {
|
#temperature,
|
||||||
|
#iterations {
|
||||||
appearance: textfield;
|
appearance: textfield;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
padding: 2px 4px;
|
padding: 2px 4px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#iterations {
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
label[for="role"] {
|
label[for="role"] {
|
||||||
background-image: url(icons/user.svg);
|
background-image: url(icons/user.svg);
|
||||||
}
|
}
|
||||||
@@ -812,6 +817,10 @@ label[for="temperature"] {
|
|||||||
background-image: url(icons/temperature.svg);
|
background-image: url(icons/temperature.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label[for="iterations"] {
|
||||||
|
background-image: url(icons/time.svg);
|
||||||
|
}
|
||||||
|
|
||||||
label[for="reasoning-effort"] {
|
label[for="reasoning-effort"] {
|
||||||
background-image: url(icons/reasoning.svg);
|
background-image: url(icons/reasoning.svg);
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 679 B |
@@ -58,6 +58,10 @@
|
|||||||
<label for="temperature" title="Temperature (0 - 2)"></label>
|
<label for="temperature" title="Temperature (0 - 2)"></label>
|
||||||
<input id="temperature" type="number" min="0" max="2" step="0.05" value="0.85" />
|
<input id="temperature" type="number" min="0" max="2" step="0.05" value="0.85" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label for="iterations" title="Maximum number of iterations (turns) per response"></label>
|
||||||
|
<input id="iterations" type="number" min="1" max="50" value="3" />
|
||||||
|
</div>
|
||||||
<div class="option none">
|
<div class="option none">
|
||||||
<label for="reasoning-effort" title="Reasoning Effort"></label>
|
<label for="reasoning-effort" title="Reasoning Effort"></label>
|
||||||
<select id="reasoning-effort">
|
<select id="reasoning-effort">
|
||||||
@@ -69,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="option none">
|
<div class="option none">
|
||||||
<label for="reasoning-tokens" title="Maximum amount of reasoning tokens"></label>
|
<label for="reasoning-tokens" title="Maximum amount of reasoning tokens"></label>
|
||||||
<input id="reasoning-tokens" type="number" min="2" max="1" step="0.05" value="0.85" />
|
<input id="reasoning-tokens" type="number" min="2" max="1048576" value="1024" />
|
||||||
</div>
|
</div>
|
||||||
<div class="option group none">
|
<div class="option group none">
|
||||||
<button id="json" title="Turn on structured json output"></button>
|
<button id="json" title="Turn on structured json output"></button>
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
$model = document.getElementById("model"),
|
$model = document.getElementById("model"),
|
||||||
$prompt = document.getElementById("prompt"),
|
$prompt = document.getElementById("prompt"),
|
||||||
$temperature = document.getElementById("temperature"),
|
$temperature = document.getElementById("temperature"),
|
||||||
|
$iterations = document.getElementById("iterations"),
|
||||||
$reasoningEffort = document.getElementById("reasoning-effort"),
|
$reasoningEffort = document.getElementById("reasoning-effort"),
|
||||||
$reasoningTokens = document.getElementById("reasoning-tokens"),
|
$reasoningTokens = document.getElementById("reasoning-tokens"),
|
||||||
$json = document.getElementById("json"),
|
$json = document.getElementById("json"),
|
||||||
@@ -816,20 +817,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$temperature.value) {
|
if (!$temperature.value) {
|
||||||
$temperature.value = 0.85;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const temperature = parseFloat($temperature.value);
|
let temperature = parseFloat($temperature.value);
|
||||||
|
|
||||||
if (Number.isNaN(temperature) || temperature < 0 || temperature > 2) {
|
if (Number.isNaN(temperature) || temperature < 0 || temperature > 2) {
|
||||||
return;
|
temperature = 0.85;
|
||||||
|
|
||||||
|
$temperature.value = temperature;
|
||||||
}
|
}
|
||||||
|
|
||||||
const effort = $reasoningEffort.value,
|
let iterations = parseInt($iterations.value);
|
||||||
tokens = parseInt($reasoningTokens.value);
|
|
||||||
|
if (Number.isNaN(iterations) || iterations < 1 || iterations > 50) {
|
||||||
|
iterations = 3;
|
||||||
|
|
||||||
|
$iterations.value = iterations;
|
||||||
|
}
|
||||||
|
|
||||||
|
const effort = $reasoningEffort.value;
|
||||||
|
|
||||||
|
let tokens = parseInt($reasoningTokens.value);
|
||||||
|
|
||||||
if (!effort && (Number.isNaN(tokens) || tokens <= 0 || tokens > 1024 * 1024)) {
|
if (!effort && (Number.isNaN(tokens) || tokens <= 0 || tokens > 1024 * 1024)) {
|
||||||
return;
|
tokens = 1024;
|
||||||
|
|
||||||
|
$reasoningTokens.value = tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
pushMessage();
|
pushMessage();
|
||||||
@@ -842,6 +855,7 @@
|
|||||||
prompt: $prompt.value,
|
prompt: $prompt.value,
|
||||||
model: $model.value,
|
model: $model.value,
|
||||||
temperature: temperature,
|
temperature: temperature,
|
||||||
|
iterations: iterations,
|
||||||
reasoning: {
|
reasoning: {
|
||||||
effort: effort,
|
effort: effort,
|
||||||
tokens: tokens || 0,
|
tokens: tokens || 0,
|
||||||
|
Reference in New Issue
Block a user