Skip to content

Commit 88f9755

Browse files
committed
Add seed, top-K, repeat penalty, and stop sequence generation parameters
1 parent 1a14af8 commit 88f9755

11 files changed

Lines changed: 242 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Beyond chat, Modelforge includes an **agentic mode** — the model can read/writ
3333

3434
**Organization**
3535
- **Projects** — group related chats under shared instructions and default model parameters.
36-
- **Per-session and per-project overrides** — pin a specific prompt, model, context length, or temperature to a single chat or an entire project, falling back to sane defaults.
36+
- **Per-session and per-project overrides** — pin a specific prompt, model, temperature, seed, top-K/top-P, repeat penalty, context length, GPU offload, or stop sequences to a single chat or an entire project, falling back to sane defaults. Provider-specific parameters (e.g. seed isn't supported by Claude, top-K isn't supported by ChatGPT) are automatically disabled when they don't apply to the selected model.
3737
- **Prompt library** — save and reuse system prompts across chats.
3838
- **Command palette** (`Ctrl/Cmd+K`) — jump between chats, projects, and settings without touching the mouse.
3939

‎app/src/ollama-manager.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ export async function chat(
232232
presence_penalty: options?.presencePenalty,
233233
num_ctx: options?.contextLength,
234234
num_gpu: options?.gpuLayers,
235+
seed: options?.seed,
236+
top_k: options?.topK,
237+
repeat_penalty: options?.repeatPenalty,
238+
stop: options?.stop,
235239
},
236240
}),
237241
signal,

‎app/src/providers/anthropic.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export const chat: ChatFn = async (apiKey, model, messages, options, onToken, si
6363
stream: true,
6464
temperature: options?.temperature ?? 0.7,
6565
top_p: options?.topP,
66+
top_k: options?.topK,
67+
stop_sequences: options?.stop && options.stop.length > 0 ? options.stop : undefined,
6668
...(tools && tools.length > 0 ? { tools: toAnthropicTools(tools) } : {}),
6769
}),
6870
signal,

‎app/src/providers/openai.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export const chat: ChatFn = async (apiKey, model, messages, options, onToken, si
5757
max_tokens: options?.maxTokens,
5858
frequency_penalty: options?.frequencyPenalty,
5959
presence_penalty: options?.presencePenalty,
60+
seed: options?.seed,
61+
stop: options?.stop && options.stop.length > 0 ? options.stop : undefined,
6062
...(tools && tools.length > 0 ? { tools: toOpenAiTools(tools) } : {}),
6163
}),
6264
signal,

‎app/src/providers/types.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ export interface ChatOptions {
6161
// number = offload that many layers (useful for tuning multi-GPU setups
6262
// or freeing VRAM for something else running alongside Ollama).
6363
gpuLayers?: number;
64+
// Ollama + OpenAI only (Anthropic has no reproducibility param). Same
65+
// seed + same prompt should produce the same output, useful for testing.
66+
seed?: number;
67+
// Ollama + Anthropic only (OpenAI doesn't expose top-k sampling).
68+
topK?: number;
69+
// Ollama-only: penalizes tokens that already appeared recently, distinct
70+
// from (and in addition to) the OpenAI-style frequency/presence penalties.
71+
repeatPenalty?: number;
72+
// All providers: stop generation as soon as any of these strings appears.
73+
stop?: string[];
6474
}
6575

6676
export type ChatFn = (

‎app/src/settings-store.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export interface AppSettings {
1919
contextLength: number;
2020
// undefined = auto (let Ollama decide how many layers to offload to GPU).
2121
gpuLayers?: number;
22+
seed?: number;
23+
topK?: number;
24+
repeatPenalty?: number;
25+
stop?: string[];
2226
systemPrompt: string;
2327
promptPresets: PromptPreset[];
2428
theme: "light" | "dark" | "system";

‎frontend/src/components/layout.tsx‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,64 @@ function ProjectGroup({
313313
className="h-8 text-xs"
314314
/>
315315
</div>
316+
<div className="flex flex-col gap-1">
317+
<label className="text-xs text-muted-foreground">{t.seed}</label>
318+
<Input
319+
type="number"
320+
step={1}
321+
placeholder={t.seedRandom}
322+
title={t.seedHelp}
323+
value={params.seed ?? ""}
324+
onChange={(e) => updateParam({ seed: e.target.value === "" ? undefined : Number(e.target.value) })}
325+
aria-label={t.seed}
326+
className="h-8 text-xs"
327+
/>
328+
</div>
329+
<div className="flex flex-col gap-1">
330+
<label className="text-xs text-muted-foreground">{t.topK}</label>
331+
<Input
332+
type="number"
333+
min={1}
334+
step={1}
335+
title={t.topKHelp}
336+
value={params.topK ?? ""}
337+
onChange={(e) => updateParam({ topK: e.target.value === "" ? undefined : Number(e.target.value) })}
338+
aria-label={t.topK}
339+
className="h-8 text-xs"
340+
/>
341+
</div>
342+
<div className="flex flex-col gap-1">
343+
<label className="text-xs text-muted-foreground">{t.repeatPenalty}</label>
344+
<Input
345+
type="number"
346+
min={0}
347+
step={0.05}
348+
title={t.repeatPenaltyHelp}
349+
value={params.repeatPenalty ?? ""}
350+
onChange={(e) =>
351+
updateParam({ repeatPenalty: e.target.value === "" ? undefined : Number(e.target.value) })
352+
}
353+
aria-label={t.repeatPenalty}
354+
className="h-8 text-xs"
355+
/>
356+
</div>
357+
</div>
358+
<div className="flex flex-col gap-1">
359+
<label className="text-xs text-muted-foreground">{t.stopSequences}</label>
360+
<Input
361+
placeholder={t.stopSequencesPlaceholder}
362+
value={(params.stop ?? []).join(", ")}
363+
onChange={(e) =>
364+
updateParam({
365+
stop: e.target.value
366+
.split(",")
367+
.map((s) => s.trim())
368+
.filter(Boolean),
369+
})
370+
}
371+
aria-label={t.stopSequences}
372+
className="h-8 text-xs"
373+
/>
316374
</div>
317375
<p className="text-xs text-muted-foreground">
318376
Blank fields fall back to the global default. {t.penaltyClaudeNote}

‎frontend/src/lib/translations.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ export interface Dictionary {
5252
gpuLayers: string;
5353
gpuLayersAuto: string;
5454
gpuLayersHelp: string;
55+
seed: string;
56+
seedRandom: string;
57+
seedHelp: string;
58+
topK: string;
59+
topKHelp: string;
60+
repeatPenalty: string;
61+
repeatPenaltyHelp: string;
62+
stopSequences: string;
63+
stopSequencesHelp: string;
64+
stopSequencesPlaceholder: string;
5565
contextLengthOllamaOnly: string;
5666
frequencyPenalty: string;
5767
presencePenalty: string;
@@ -142,6 +152,16 @@ export const en: Dictionary = {
142152
gpuLayers: "GPU layers",
143153
gpuLayersAuto: "Auto",
144154
gpuLayersHelp: "How many model layers to offload to the GPU (Ollama's num_gpu). Leave blank to let Ollama decide automatically; 0 forces CPU-only.",
155+
seed: "Seed",
156+
seedRandom: "Random",
157+
seedHelp: "A fixed seed makes output reproducible — same seed and prompt should give the same response. Leave blank for a random seed each time. Not supported by Claude.",
158+
topK: "Top K",
159+
topKHelp: "Limits sampling to the K most likely next tokens. Lower values are more focused/deterministic. Not supported by ChatGPT.",
160+
repeatPenalty: "Repeat penalty",
161+
repeatPenaltyHelp: "Penalizes tokens that already appeared recently, reducing repetition. 1.0 = no penalty. Ollama only.",
162+
stopSequences: "Stop sequences",
163+
stopSequencesHelp: "Generation stops as soon as any of these strings appears in the output. Comma-separated.",
164+
stopSequencesPlaceholder: "e.g. \\n\\nUser:, ###, <|end|>",
145165
contextLengthOllamaOnly: " (Ollama only)",
146166
frequencyPenalty: "Frequency penalty",
147167
presencePenalty: "Presence penalty",
@@ -234,6 +254,16 @@ export const tr: Dictionary = {
234254
gpuLayers: "GPU katmanları",
235255
gpuLayersAuto: "Otomatik",
236256
gpuLayersHelp: "GPU'ya kaç model katmanının aktarılacağı (Ollama'nın num_gpu ayarı). Ollama'nın otomatik karar vermesi için boş bırakın; 0 sadece CPU kullanımını zorlar.",
257+
seed: "Seed",
258+
seedRandom: "Rastgele",
259+
seedHelp: "Sabit bir seed, çıktıyı tekrarlanabilir kılar — aynı seed ve istemle aynı yanıt alınır. Her seferinde rastgele bir seed için boş bırakın. Claude tarafından desteklenmez.",
260+
topK: "Top K",
261+
topKHelp: "Örneklemeyi en olası K sonraki token ile sınırlar. Düşük değerler daha odaklı/deterministik sonuç verir. ChatGPT tarafından desteklenmez.",
262+
repeatPenalty: "Tekrar cezası",
263+
repeatPenaltyHelp: "Yakın zamanda geçmiş olan token'ları cezalandırarak tekrarı azaltır. 1.0 = ceza yok. Sadece Ollama.",
264+
stopSequences: "Durdurma dizileri",
265+
stopSequencesHelp: "Çıktıda bu dizilerden herhangi biri göründüğünde üretim durur. Virgülle ayırın.",
266+
stopSequencesPlaceholder: "örn. \\n\\nUser:, ###, <|end|>",
237267
contextLengthOllamaOnly: " (yalnızca Ollama)",
238268
frequencyPenalty: "Sıklık cezası",
239269
presencePenalty: "Varlık cezası",

‎frontend/src/pages/Chat.tsx‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,10 @@ export default function Chat() {
507507
presencePenalty: params.presencePenalty ?? project?.params?.presencePenalty ?? settings?.presencePenalty,
508508
contextLength: params.contextLength ?? project?.params?.contextLength ?? settings?.contextLength,
509509
gpuLayers: params.gpuLayers ?? project?.params?.gpuLayers ?? settings?.gpuLayers,
510+
seed: params.seed ?? project?.params?.seed ?? settings?.seed,
511+
topK: params.topK ?? project?.params?.topK ?? settings?.topK,
512+
repeatPenalty: params.repeatPenalty ?? project?.params?.repeatPenalty ?? settings?.repeatPenalty,
513+
stop: params.stop ?? project?.params?.stop ?? settings?.stop,
510514
};
511515
}
512516

@@ -1070,6 +1074,72 @@ export default function Chat() {
10701074
className="h-8 text-xs"
10711075
/>
10721076
</div>
1077+
<div className="flex flex-col gap-1">
1078+
<label className="text-xs text-muted-foreground">
1079+
{t.seed}
1080+
{parsedModel?.provider === "anthropic" ? " (not supported by Claude)" : ""}
1081+
</label>
1082+
<Input
1083+
type="number"
1084+
step={1}
1085+
placeholder={t.seedRandom}
1086+
title={t.seedHelp}
1087+
value={params.seed ?? currentProject?.params?.seed ?? settings?.seed ?? ""}
1088+
onChange={(e) => updateParam({ seed: e.target.value === "" ? undefined : Number(e.target.value) })}
1089+
disabled={parsedModel?.provider === "anthropic"}
1090+
className="h-8 text-xs"
1091+
/>
1092+
</div>
1093+
<div className="flex flex-col gap-1">
1094+
<label className="text-xs text-muted-foreground">
1095+
{t.topK}
1096+
{parsedModel?.provider === "openai" ? " (not supported by ChatGPT)" : ""}
1097+
</label>
1098+
<Input
1099+
type="number"
1100+
min={1}
1101+
step={1}
1102+
title={t.topKHelp}
1103+
value={params.topK ?? currentProject?.params?.topK ?? settings?.topK ?? ""}
1104+
onChange={(e) => updateParam({ topK: e.target.value === "" ? undefined : Number(e.target.value) })}
1105+
disabled={parsedModel?.provider === "openai"}
1106+
className="h-8 text-xs"
1107+
/>
1108+
</div>
1109+
<div className="flex flex-col gap-1">
1110+
<label className="text-xs text-muted-foreground">
1111+
{t.repeatPenalty}
1112+
{parsedModel?.provider !== "ollama" ? " (Ollama only)" : ""}
1113+
</label>
1114+
<Input
1115+
type="number"
1116+
min={0}
1117+
step={0.05}
1118+
title={t.repeatPenaltyHelp}
1119+
value={params.repeatPenalty ?? currentProject?.params?.repeatPenalty ?? settings?.repeatPenalty ?? ""}
1120+
onChange={(e) =>
1121+
updateParam({ repeatPenalty: e.target.value === "" ? undefined : Number(e.target.value) })
1122+
}
1123+
disabled={parsedModel?.provider !== "ollama"}
1124+
className="h-8 text-xs"
1125+
/>
1126+
</div>
1127+
</div>
1128+
<div className="mt-3 flex flex-col gap-1">
1129+
<label className="text-xs text-muted-foreground">{t.stopSequences}</label>
1130+
<Input
1131+
placeholder={t.stopSequencesPlaceholder}
1132+
value={(params.stop ?? currentProject?.params?.stop ?? settings?.stop ?? []).join(", ")}
1133+
onChange={(e) =>
1134+
updateParam({
1135+
stop: e.target.value
1136+
.split(",")
1137+
.map((s) => s.trim())
1138+
.filter(Boolean),
1139+
})
1140+
}
1141+
className="h-8 text-xs"
1142+
/>
10731143
</div>
10741144
</PopoverContent>
10751145
</Popover>

0 commit comments

Comments
 (0)