mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-09-05 09:55:42 +00:00
feat(bedrock): harden adaptive thinking + add Opus 4.7/4.8 (refs #288)
Makes adaptive-thinking models usable safely and adds Opus 4.7/4.8 to the catalog so they are selectable and configurable. - middleware: strip temperature/top_p/top_k and set thinking.display=summarized when rewriting to adaptive. Opus 4.7+ reject sampling params (AWS: "no longer supported", 400) and default display to "omitted" (empty reasoning text). - models.yml: add us.anthropic.claude-opus-4-7 and us.anthropic.claude-opus-4-8, plus a ModelReasoningInfo capability descriptor (mode + allowed efforts) on the adaptive models (4.6/4.7/4.8/sonnet-4.6). - provider backstop: force adaptive for adaptive-only models (Opus 4.7/4.8) regardless of agent config, so selecting them cannot 400 on budget thinking. The capability descriptor is the single source of truth (no model-name regex). Verified: unit tests cover the body rewrite (sampling strip + display); a live Bedrock run of the adaptive path on us.anthropic.claude-opus-4-6-v1 passed 21/23 (the 2 failures were 429 throttling, not the mechanism). Opus 4.7/4.8 could not be exercised live -- this AWS account lacks model access (403), not a code issue; their correctness rests on the AWS docs + the 4.6 live run + unit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ca9283b93e
commit
7b01517bcd
@@ -90,11 +90,21 @@ func rewriteAdaptiveThinkingBody(body []byte, effort string) ([]byte, error) {
|
||||
}
|
||||
|
||||
thinking["type"] = "adaptive"
|
||||
// Opus 4.7/4.8 default thinking.display to "omitted" (empty reasoning text);
|
||||
// force "summarized" so reasoning stays visible in the UI.
|
||||
thinking["display"] = "summarized"
|
||||
delete(thinking, "budget_tokens")
|
||||
fields["output_config"] = map[string]any{
|
||||
"effort": effort,
|
||||
}
|
||||
|
||||
// Adaptive Claude models reject sampling params (Opus 4.7+ return 400 for any
|
||||
// temperature/top_p/top_k); drop them from the Converse inferenceConfig.
|
||||
if inferenceConfig, ok := payload["inferenceConfig"].(map[string]any); ok {
|
||||
delete(inferenceConfig, "temperature")
|
||||
delete(inferenceConfig, "topP")
|
||||
}
|
||||
|
||||
updatedBody, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("encode Bedrock request body: %w", err)
|
||||
@@ -108,18 +118,46 @@ func (p *bedrockProvider) prepareCallOptions(
|
||||
opt pconfig.ProviderOptionsType,
|
||||
options []llms.CallOption,
|
||||
) (context.Context, []llms.CallOption) {
|
||||
reasoning, ok := p.reasoningConfigForType(opt)
|
||||
if !ok || reasoning.EffectiveMode() != pconfig.ReasoningModeAdaptive {
|
||||
if !p.usesAdaptiveThinking(opt) {
|
||||
return ctx, options
|
||||
}
|
||||
|
||||
effort := string(reasoning.Effort)
|
||||
ctx = withAdaptiveThinkingEffort(ctx, effort)
|
||||
reasoning, _ := p.reasoningConfigForType(opt)
|
||||
ctx = withAdaptiveThinkingEffort(ctx, string(reasoning.Effort))
|
||||
options = append(options, llms.WithReasoning(llms.ReasoningHigh, 0))
|
||||
|
||||
return ctx, options
|
||||
}
|
||||
|
||||
// usesAdaptiveThinking reports whether this agent's call must use adaptive thinking:
|
||||
// either the agent config selected it, or the agent's model only supports adaptive
|
||||
// (e.g. Opus 4.7/4.8, where budget thinking returns a 400).
|
||||
func (p *bedrockProvider) usesAdaptiveThinking(opt pconfig.ProviderOptionsType) bool {
|
||||
if p.modelReasoningMode(opt) == pconfig.ModelReasoningAdaptiveOnly {
|
||||
return true
|
||||
}
|
||||
|
||||
reasoning, ok := p.reasoningConfigForType(opt)
|
||||
return ok && reasoning.EffectiveMode() == pconfig.ReasoningModeAdaptive
|
||||
}
|
||||
|
||||
// modelReasoningMode returns the reasoning capability declared in models.yml for
|
||||
// the model assigned to this agent, or ModelReasoningNone if unknown.
|
||||
func (p *bedrockProvider) modelReasoningMode(opt pconfig.ProviderOptionsType) pconfig.ModelReasoningMode {
|
||||
agentConfig := p.agentConfigForType(opt)
|
||||
if agentConfig == nil || agentConfig.Model == "" {
|
||||
return pconfig.ModelReasoningNone
|
||||
}
|
||||
|
||||
for _, m := range p.models {
|
||||
if m.Name == agentConfig.Model && m.Reasoning != nil {
|
||||
return m.Reasoning.Mode
|
||||
}
|
||||
}
|
||||
|
||||
return pconfig.ModelReasoningNone
|
||||
}
|
||||
|
||||
func (p *bedrockProvider) reasoningConfigForType(opt pconfig.ProviderOptionsType) (pconfig.ReasoningConfig, bool) {
|
||||
agentConfig := p.agentConfigForType(opt)
|
||||
if agentConfig == nil || agentConfig.Reasoning.IsZero() {
|
||||
|
||||
@@ -44,3 +44,29 @@ func TestRewriteAdaptiveThinkingBodyWithoutThinking(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.JSONEq(t, string(body), string(updatedBody))
|
||||
}
|
||||
|
||||
func TestRewriteAdaptiveThinkingBodyStripsSamplingAndSetsDisplay(t *testing.T) {
|
||||
body := []byte(`{
|
||||
"additionalModelRequestFields": {
|
||||
"thinking": {"type": "enabled", "budget_tokens": 4096}
|
||||
},
|
||||
"inferenceConfig": {
|
||||
"maxTokens": 16384,
|
||||
"temperature": 1.0,
|
||||
"topP": 0.95
|
||||
}
|
||||
}`)
|
||||
|
||||
updatedBody, err := rewriteAdaptiveThinkingBody(body, "high")
|
||||
require.NoError(t, err)
|
||||
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal(updatedBody, &payload))
|
||||
|
||||
thinking := payload["additionalModelRequestFields"].(map[string]any)["thinking"].(map[string]any)
|
||||
assert.Equal(t, "adaptive", thinking["type"])
|
||||
assert.Equal(t, "summarized", thinking["display"])
|
||||
|
||||
// Opus 4.7+ reject sampling params; only maxTokens must survive in inferenceConfig.
|
||||
assert.Equal(t, map[string]any{"maxTokens": float64(16384)}, payload["inferenceConfig"])
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
- name: us.anthropic.claude-opus-4-7
|
||||
description: Most capable Opus model for advanced software engineering, long-running agentic tasks, professional work, and rigorous security analysis with adaptive thinking
|
||||
thinking: true
|
||||
reasoning:
|
||||
mode: adaptive-only
|
||||
efforts: [low, medium, high, xhigh, max]
|
||||
release_date: 2026-04-16
|
||||
price:
|
||||
input: 5.0
|
||||
@@ -50,10 +53,26 @@
|
||||
cache_read: 0.5
|
||||
cache_write: 6.25
|
||||
|
||||
- name: us.anthropic.claude-opus-4-8
|
||||
description: Anthropic Opus 4.8 - flagship model for coding, agents, and deep reasoning in enterprise security workflows, with adaptive thinking only
|
||||
thinking: true
|
||||
reasoning:
|
||||
mode: adaptive-only
|
||||
efforts: [low, medium, high, xhigh, max]
|
||||
release_date: 2026-05-28
|
||||
price:
|
||||
input: 5.0
|
||||
output: 25.0
|
||||
cache_read: 0.5
|
||||
cache_write: 6.25
|
||||
|
||||
# Anthropic Claude 4.6 Series - Latest generation with world-class coding and agentic capabilities
|
||||
- name: us.anthropic.claude-opus-4-6-v1
|
||||
description: World's best model for coding, enterprise agents, and professional work with industry-leading reliability for agentic workflows and security analysis
|
||||
thinking: true
|
||||
reasoning:
|
||||
mode: adaptive
|
||||
efforts: [low, medium, high, max]
|
||||
release_date: 2026-02-05
|
||||
price:
|
||||
input: 5.0
|
||||
@@ -64,6 +83,9 @@
|
||||
- name: us.anthropic.claude-sonnet-4-6
|
||||
description: Frontier intelligence at scale built for coding, agents, and enterprise workflows with sustained reasoning and adaptive decision-making
|
||||
thinking: true
|
||||
reasoning:
|
||||
mode: adaptive
|
||||
efforts: [low, medium, high, max]
|
||||
release_date: 2026-02-17
|
||||
price:
|
||||
input: 3.0
|
||||
|
||||
@@ -169,11 +169,31 @@ var AllAgentTypes = []ProviderOptionsType{
|
||||
}
|
||||
|
||||
type ModelConfig struct {
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
ReleaseDate *time.Time `json:"release_date,omitempty" yaml:"release_date,omitempty"`
|
||||
Thinking *bool `json:"thinking,omitempty" yaml:"thinking,omitempty"`
|
||||
Price *PriceInfo `json:"price,omitempty" yaml:"price,omitempty"`
|
||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||
ReleaseDate *time.Time `json:"release_date,omitempty" yaml:"release_date,omitempty"`
|
||||
Thinking *bool `json:"thinking,omitempty" yaml:"thinking,omitempty"`
|
||||
Reasoning *ModelReasoningInfo `json:"reasoning,omitempty" yaml:"reasoning,omitempty"`
|
||||
Price *PriceInfo `json:"price,omitempty" yaml:"price,omitempty"`
|
||||
}
|
||||
|
||||
// ModelReasoningMode declares a model's reasoning capability. It is distinct from
|
||||
// the per-agent ReasoningConfig.Mode (the chosen mode): it is the source of truth
|
||||
// that lets a provider force the correct mode for models that require it.
|
||||
type ModelReasoningMode string
|
||||
|
||||
const (
|
||||
ModelReasoningNone ModelReasoningMode = "" // no extended thinking
|
||||
ModelReasoningBudget ModelReasoningMode = "budget" // budget thinking only (older Claude)
|
||||
ModelReasoningAdaptive ModelReasoningMode = "adaptive" // budget or adaptive (Opus 4.6, Sonnet 4.6)
|
||||
ModelReasoningAdaptiveOnly ModelReasoningMode = "adaptive-only" // adaptive required; budget returns 400 (Opus 4.7/4.8)
|
||||
)
|
||||
|
||||
// ModelReasoningInfo describes how a model supports extended thinking, so the
|
||||
// provider and UI can pick a valid mode/effort without hardcoding model names.
|
||||
type ModelReasoningInfo struct {
|
||||
Mode ModelReasoningMode `json:"mode,omitempty" yaml:"mode,omitempty"`
|
||||
Efforts []llms.ReasoningEffort `json:"efforts,omitempty" yaml:"efforts,omitempty"`
|
||||
}
|
||||
|
||||
type ModelsConfig []ModelConfig
|
||||
|
||||
Reference in New Issue
Block a user