feat: add reasoning_effort support for Codex provider
Allow controlling OpenAI reasoning effort via the agents.defaults.reasoning_effort config field or the PICOCLAW_AGENTS_DEFAULTS_REASONING_EFFORT env var. The value is passed through the options map and applied to the Responses API ReasoningParam when using the Codex provider. Non-Codex providers ignore the option. Valid levels: none, minimal, low, medium, high, xhigh.
This commit is contained in:
parent
e8afd31b28
commit
22f26d5710
5 changed files with 100 additions and 19 deletions
|
|
@ -29,6 +29,7 @@ type AgentInstance struct {
|
|||
Subagents *config.SubagentsConfig
|
||||
SkillsFilter []string
|
||||
Candidates []providers.FallbackCandidate
|
||||
ReasoningEffort string
|
||||
}
|
||||
|
||||
// NewAgentInstance creates an agent instance from config.
|
||||
|
|
@ -98,6 +99,7 @@ func NewAgentInstance(
|
|||
Subagents: subagents,
|
||||
SkillsFilter: skillsFilter,
|
||||
Candidates: candidates,
|
||||
ReasoningEffort: defaults.ReasoningEffort,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -472,6 +472,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance,
|
|||
"tools_count": len(providerToolDefs),
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"reasoning_effort": agent.ReasoningEffort,
|
||||
"system_prompt_len": len(messages[0].Content),
|
||||
})
|
||||
|
||||
|
|
@ -494,6 +495,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance,
|
|||
return agent.Provider.Chat(ctx, messages, providerToolDefs, model, map[string]interface{}{
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"reasoning_effort": agent.ReasoningEffort,
|
||||
})
|
||||
},
|
||||
)
|
||||
|
|
@ -510,6 +512,7 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, agent *AgentInstance,
|
|||
return agent.Provider.Chat(ctx, messages, providerToolDefs, agent.Model, map[string]interface{}{
|
||||
"max_tokens": 8192,
|
||||
"temperature": 0.7,
|
||||
"reasoning_effort": agent.ReasoningEffort,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,7 @@ type AgentDefaults struct {
|
|||
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||||
Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||
ReasoningEffort string `json:"reasoning_effort,omitempty" env:"PICOCLAW_AGENTS_DEFAULTS_REASONING_EFFORT"`
|
||||
}
|
||||
|
||||
type ChannelsConfig struct {
|
||||
|
|
|
|||
|
|
@ -268,6 +268,12 @@ func buildCodexParams(messages []Message, tools []ToolDefinition, model string,
|
|||
params.Instructions = openai.Opt(defaultCodexInstructions)
|
||||
}
|
||||
|
||||
if re, ok := options["reasoning_effort"].(string); ok && re != "" {
|
||||
params.Reasoning = responses.ReasoningParam{
|
||||
Effort: responses.ReasoningEffort(re),
|
||||
}
|
||||
}
|
||||
|
||||
if len(tools) > 0 || enableWebSearch {
|
||||
params.Tools = translateToolsForCodex(tools, enableWebSearch)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,75 @@ func TestBuildCodexParams_StoreIsFalse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBuildCodexParams_NilOptions(t *testing.T) {
|
||||
// Passing nil options should not panic.
|
||||
params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "gpt-4o", nil, false)
|
||||
if params.Model != "gpt-4o" {
|
||||
t.Errorf("Model = %q, want %q", params.Model, "gpt-4o")
|
||||
}
|
||||
if params.Reasoning.Effort != "" {
|
||||
t.Errorf("Reasoning.Effort = %q, want empty", params.Reasoning.Effort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCodexParams_MissingReasoningEffortKey(t *testing.T) {
|
||||
// Options map without reasoning_effort should produce zero-value Reasoning.
|
||||
params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "o3", map[string]interface{}{
|
||||
"max_tokens": 4096,
|
||||
}, false)
|
||||
if params.Reasoning.Effort != "" {
|
||||
t.Errorf("Reasoning.Effort = %q, want empty", params.Reasoning.Effort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCodexParams_ReasoningEffort(t *testing.T) {
|
||||
params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "o3", map[string]interface{}{
|
||||
"reasoning_effort": "medium",
|
||||
}, false)
|
||||
if params.Reasoning.Effort != responses.ReasoningEffortMedium {
|
||||
t.Errorf("Reasoning.Effort = %q, want %q", params.Reasoning.Effort, responses.ReasoningEffortMedium)
|
||||
}
|
||||
|
||||
// Verify the JSON serialization includes the reasoning field.
|
||||
b, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal: %v", err)
|
||||
}
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
reasoning, ok := raw["reasoning"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("reasoning field missing or wrong type in JSON: %s", string(b))
|
||||
}
|
||||
if reasoning["effort"] != "medium" {
|
||||
t.Errorf("JSON reasoning.effort = %v, want %q", reasoning["effort"], "medium")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCodexParams_ReasoningEffortEmpty(t *testing.T) {
|
||||
params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "o3", map[string]interface{}{
|
||||
"reasoning_effort": "",
|
||||
}, false)
|
||||
if params.Reasoning.Effort != "" {
|
||||
t.Errorf("Reasoning.Effort = %q, want empty", params.Reasoning.Effort)
|
||||
}
|
||||
|
||||
// When empty, reasoning should not appear in JSON.
|
||||
b, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal: %v", err)
|
||||
}
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
t.Fatalf("json.Unmarshal: %v", err)
|
||||
}
|
||||
if _, ok := raw["reasoning"]; ok {
|
||||
t.Errorf("reasoning field should be absent in JSON when empty, got: %s", string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCodexParams_DefaultWebSearchEnabled(t *testing.T) {
|
||||
params := buildCodexParams([]Message{{Role: "user", Content: "Hi"}}, nil, "gpt-4o", map[string]interface{}{}, true)
|
||||
if len(params.Tools) != 1 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue