From 3867d52689769e4884fb39ccff176cbf7a60806b Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Sat, 16 May 2026 07:32:20 +0200 Subject: [PATCH] add generation params: top_p, top_k, min_p, top_a, repetition_penalty, max_completion_tokens, response_format, reasoning_effort, include_reasoning, stop, verbosity --- pkg/agent/core.go | 20 ++++++++++++--- pkg/backend/anthropic.go | 6 +++++ pkg/backend/backend.go | 21 ++++++++++++---- pkg/backend/ollama.go | 46 ++++++++++++++++++++++++++++++---- pkg/backend/openai.go | 54 ++++++++++++++++++++++++++-------------- pkg/config/config.go | 30 +++++++++++++++------- 6 files changed, 136 insertions(+), 41 deletions(-) diff --git a/pkg/agent/core.go b/pkg/agent/core.go index f1eb234..f84aa8e 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -78,10 +78,22 @@ func BuildAgentEnv(cfg *config.Config, d tools.Dispatcher, cwd string) AgentEnv preamble = resolved } genParams = backend.GenerationParams{ - MaxTokens: cfg.MaxTokens, - Temperature: cfg.Temperature, - FrequencyPenalty: cfg.FrequencyPenalty, - PresencePenalty: cfg.PresencePenalty, + MaxTokens: cfg.MaxTokens, + MaxCompletionTokens: cfg.MaxCompletionTokens, + Temperature: cfg.Temperature, + TopP: cfg.TopP, + TopK: cfg.TopK, + MinP: cfg.MinP, + TopA: cfg.TopA, + FrequencyPenalty: cfg.FrequencyPenalty, + PresencePenalty: cfg.PresencePenalty, + RepetitionPenalty: cfg.RepetitionPenalty, + ThinkingBudget: cfg.Reasoning, + ReasoningEffort: cfg.ReasoningEffort, + IncludeReasoning: cfg.IncludeReasoning, + ResponseFormat: cfg.ResponseFormat, + Stop: cfg.Stop, + Verbosity: cfg.Verbosity, } maxSteps = cfg.MaxSteps if len(cfg.TrustedTools) > 0 { diff --git a/pkg/backend/anthropic.go b/pkg/backend/anthropic.go index 1ce2115..67f4adb 100644 --- a/pkg/backend/anthropic.go +++ b/pkg/backend/anthropic.go @@ -54,6 +54,9 @@ type anthropicRequest struct { Tools []anthropicTool `json:"tools,omitempty"` Stream bool `json:"stream"` Temperature *float64 `json:"temperature,omitempty"` + TopP *float64 `json:"top_p,omitempty"` + TopK *int `json:"top_k,omitempty"` + StopSeqs []string `json:"stop_sequences,omitempty"` Thinking *anthropicThinking `json:"thinking,omitempty"` } @@ -113,6 +116,9 @@ func (b *AnthropicBackend) ChatStream(ctx context.Context, messages []Message, t Messages: wireMessages, Stream: true, Temperature: params.Temperature, + TopP: params.TopP, + TopK: params.TopK, + StopSeqs: params.Stop, } if params.ThinkingBudget > 0 { areq.Thinking = &anthropicThinking{Type: "enabled", BudgetTokens: params.ThinkingBudget} diff --git a/pkg/backend/backend.go b/pkg/backend/backend.go index b53bdcb..26c7d30 100644 --- a/pkg/backend/backend.go +++ b/pkg/backend/backend.go @@ -105,11 +105,22 @@ func (e *ToolUnsupportedError) Error() string { return e.Message } // GenerationParams controls sampling behaviour for a single ChatStream call. // Zero values mean "use the API default". type GenerationParams struct { - MaxTokens int // 0 = no limit - Temperature *float64 // nil = API default - FrequencyPenalty *float64 // nil = API default - PresencePenalty *float64 // nil = API default - ThinkingBudget int // Anthropic extended thinking token budget; 0 = disabled + MaxTokens int // 0 = no limit + MaxCompletionTokens int // OpenAI o-series; 0 = use MaxTokens + Temperature *float64 // nil = API default + TopP *float64 // nil = API default + TopK *int // nil = API default (Anthropic, Ollama) + MinP *float64 // nil = API default (Ollama) + TopA *float64 // nil = API default (Ollama) + FrequencyPenalty *float64 // nil = API default + PresencePenalty *float64 // nil = API default + RepetitionPenalty *float64 // nil = API default (Ollama) + ThinkingBudget int // Anthropic extended thinking token budget; 0 = disabled + ReasoningEffort string // OpenAI o-series: "low", "medium", "high" + IncludeReasoning *bool // include reasoning in response (OpenRouter) + ResponseFormat string // "json_object", "text", etc. + Stop []string // stop sequences + Verbosity string // internal: controls output detail level } // Backend is the interface all LLM providers must implement. diff --git a/pkg/backend/ollama.go b/pkg/backend/ollama.go index 9f6d623..e79eff1 100644 --- a/pkg/backend/ollama.go +++ b/pkg/backend/ollama.go @@ -134,10 +134,12 @@ type ollamaToolFunction struct { } type ollamaChatRequest struct { - Model string `json:"model"` - Messages []ollamaMessage `json:"messages"` - Tools []ollamaTool `json:"tools,omitempty"` - Stream bool `json:"stream"` + Model string `json:"model"` + Messages []ollamaMessage `json:"messages"` + Tools []ollamaTool `json:"tools,omitempty"` + Stream bool `json:"stream"` + Options map[string]interface{} `json:"options,omitempty"` + Stop []string `json:"stop,omitempty"` } type ollamaChatResponse struct { @@ -150,7 +152,7 @@ type ollamaChatResponse struct { // -- implementation -- -func (b *OllamaBackend) ChatStream(ctx context.Context, messages []Message, tools []Tool, _ GenerationParams) (<-chan StreamEvent, error) { +func (b *OllamaBackend) ChatStream(ctx context.Context, messages []Message, tools []Tool, params GenerationParams) (<-chan StreamEvent, error) { model := b.model wireMessages := make([]ollamaMessage, len(messages)) for i, m := range messages { @@ -174,11 +176,45 @@ func (b *OllamaBackend) ChatStream(ctx context.Context, messages []Message, tool }) } + opts := make(map[string]interface{}) + if params.Temperature != nil { + opts["temperature"] = *params.Temperature + } + if params.TopP != nil { + opts["top_p"] = *params.TopP + } + if params.TopK != nil { + opts["top_k"] = *params.TopK + } + if params.MinP != nil { + opts["min_p"] = *params.MinP + } + if params.TopA != nil { + opts["top_a"] = *params.TopA + } + if params.RepetitionPenalty != nil { + opts["repeat_penalty"] = *params.RepetitionPenalty + } + if params.FrequencyPenalty != nil { + opts["frequency_penalty"] = *params.FrequencyPenalty + } + if params.PresencePenalty != nil { + opts["presence_penalty"] = *params.PresencePenalty + } + if params.MaxTokens > 0 { + opts["num_predict"] = params.MaxTokens + } + if len(opts) == 0 { + opts = nil + } + data, _ := json.Marshal(ollamaChatRequest{ Model: model, Messages: wireMessages, Tools: wireTools, Stream: true, + Options: opts, + Stop: params.Stop, }) httpReq, _ := http.NewRequestWithContext(ctx, http.MethodPost, b.baseURL.JoinPath("/api/chat").String(), bytes.NewReader(data)) diff --git a/pkg/backend/openai.go b/pkg/backend/openai.go index 4158050..f58aee7 100644 --- a/pkg/backend/openai.go +++ b/pkg/backend/openai.go @@ -170,15 +170,25 @@ type openAIStreamOptions struct { } type openAIChatRequest struct { - Model string `json:"model"` - Messages []openAIMessage `json:"messages"` - Tools []openAITool `json:"tools,omitempty"` - Stream bool `json:"stream"` - StreamOptions *openAIStreamOptions `json:"stream_options,omitempty"` - MaxTokens int `json:"max_tokens,omitempty"` - Temperature *float64 `json:"temperature,omitempty"` - FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` - PresencePenalty *float64 `json:"presence_penalty,omitempty"` + Model string `json:"model"` + Messages []openAIMessage `json:"messages"` + Tools []openAITool `json:"tools,omitempty"` + Stream bool `json:"stream"` + StreamOptions *openAIStreamOptions `json:"stream_options,omitempty"` + MaxTokens int `json:"max_tokens,omitempty"` + MaxCompletionTokens int `json:"max_completion_tokens,omitempty"` + Temperature *float64 `json:"temperature,omitempty"` + TopP *float64 `json:"top_p,omitempty"` + FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` + PresencePenalty *float64 `json:"presence_penalty,omitempty"` + Stop []string `json:"stop,omitempty"` + ResponseFormat *openAIResponseFmt `json:"response_format,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` + IncludeReasoning *bool `json:"include_reasoning,omitempty"` +} + +type openAIResponseFmt struct { + Type string `json:"type"` } type openAIUsage struct { @@ -462,15 +472,23 @@ func (b *OpenAIBackend) ChatStream(ctx context.Context, messages []Message, tool } } req := openAIChatRequest{ - Model: b.model, - Messages: wireMessages, - Tools: wireTools, - Stream: true, - StreamOptions: &openAIStreamOptions{IncludeUsage: true}, - MaxTokens: params.MaxTokens, - Temperature: params.Temperature, - FrequencyPenalty: params.FrequencyPenalty, - PresencePenalty: params.PresencePenalty, + Model: b.model, + Messages: wireMessages, + Tools: wireTools, + Stream: true, + StreamOptions: &openAIStreamOptions{IncludeUsage: true}, + MaxTokens: params.MaxTokens, + MaxCompletionTokens: params.MaxCompletionTokens, + Temperature: params.Temperature, + TopP: params.TopP, + FrequencyPenalty: params.FrequencyPenalty, + PresencePenalty: params.PresencePenalty, + Stop: params.Stop, + ReasoningEffort: params.ReasoningEffort, + IncludeReasoning: params.IncludeReasoning, + } + if params.ResponseFormat != "" { + req.ResponseFormat = &openAIResponseFmt{Type: params.ResponseFormat} } data, _ := json.Marshal(req) diff --git a/pkg/config/config.go b/pkg/config/config.go index e77a48a..f7d4646 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -59,16 +59,28 @@ func (p Prompt) MarshalJSON() ([]byte, error) { } type Config struct { - Hooks map[string]HookCmds `json:"hooks,omitempty"` - Prompt Prompt `json:"prompt,omitempty"` - Tools *bool `json:"tools,omitempty"` - TrustedTools []string `json:"trustedTools,omitempty"` - MaxTokens int `json:"maxTokens,omitempty"` + Hooks map[string]HookCmds `json:"hooks,omitempty"` + Prompt Prompt `json:"prompt,omitempty"` + Tools *bool `json:"tools,omitempty"` + TrustedTools []string `json:"trustedTools,omitempty"` + MaxTokens int `json:"maxTokens,omitempty"` + MaxCompletionTokens int `json:"maxCompletionTokens,omitempty"` // MaxSteps caps the number of tool-call rounds per turn. 0 means unlimited. - MaxSteps int `json:"maxSteps,omitempty"` - Temperature *float64 `json:"temperature,omitempty"` - FrequencyPenalty *float64 `json:"frequencyPenalty,omitempty"` - PresencePenalty *float64 `json:"presencePenalty,omitempty"` + MaxSteps int `json:"maxSteps,omitempty"` + Temperature *float64 `json:"temperature,omitempty"` + TopP *float64 `json:"topP,omitempty"` + TopK *int `json:"topK,omitempty"` + MinP *float64 `json:"minP,omitempty"` + TopA *float64 `json:"topA,omitempty"` + FrequencyPenalty *float64 `json:"frequencyPenalty,omitempty"` + PresencePenalty *float64 `json:"presencePenalty,omitempty"` + RepetitionPenalty *float64 `json:"repetitionPenalty,omitempty"` + Reasoning int `json:"reasoning,omitempty"` + ReasoningEffort string `json:"reasoningEffort,omitempty"` + IncludeReasoning *bool `json:"includeReasoning,omitempty"` + ResponseFormat string `json:"responseFormat,omitempty"` + Stop []string `json:"stop,omitempty"` + Verbosity string `json:"verbosity,omitempty"` } // Load parses a Config from r.