Skip to content

Commit 8f16130

Browse files
Alexx999amitksingh1490forge-code-agent
authored
Implement per-model reasoning fixups for Anthropic models (#3067)
Co-authored-by: Amit Singh <amitksingh1490@gmail.com> Co-authored-by: ForgeCode <noreply@forgecode.dev>
1 parent fa1fd37 commit 8f16130

8 files changed

Lines changed: 1156 additions & 144 deletions

File tree

crates/forge_app/src/dto/anthropic/request.rs

Lines changed: 256 additions & 90 deletions
Large diffs are not rendered by default.

crates/forge_app/src/dto/anthropic/transforms/reasoning_transform.rs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ pub struct ReasoningTransform;
55
impl Transformer for ReasoningTransform {
66
type Value = Context;
77
fn transform(&mut self, mut context: Self::Value) -> Self::Value {
8-
if let Some(reasoning) = context.reasoning.as_ref()
9-
&& reasoning.enabled.unwrap_or(false)
10-
{
11-
// if reasoning is enabled then we've to drop top_k and top_p
8+
// Must stay in lockstep with the Anthropic request builder, which gates
9+
// on the same predicate — otherwise `thinking`/`output_config` ship
10+
// alongside sampling params that Anthropic rejects.
11+
if context.is_reasoning_supported() {
1212
context.top_k = None;
1313
context.top_p = None;
1414
}
@@ -85,4 +85,51 @@ mod tests {
8585

8686
assert_eq!(actual, expected);
8787
}
88+
89+
#[test]
90+
fn test_enabled_none_with_effort_still_strips_top_k_and_top_p() {
91+
// `enabled: None` + effort is treated as reasoning-on (domain rule).
92+
let fixture = create_context_fixture().reasoning(ReasoningConfig {
93+
enabled: None,
94+
max_tokens: None,
95+
effort: Some(forge_domain::Effort::High),
96+
exclude: None,
97+
});
98+
let mut transformer = ReasoningTransform;
99+
let actual = transformer.transform(fixture);
100+
101+
assert_eq!(actual.top_k, None);
102+
assert_eq!(actual.top_p, None);
103+
}
104+
105+
#[test]
106+
fn test_enabled_none_with_positive_max_tokens_still_strips_top_k_and_top_p() {
107+
let fixture = create_context_fixture().reasoning(ReasoningConfig {
108+
enabled: None,
109+
max_tokens: Some(8000),
110+
effort: None,
111+
exclude: None,
112+
});
113+
let mut transformer = ReasoningTransform;
114+
let actual = transformer.transform(fixture);
115+
116+
assert_eq!(actual.top_k, None);
117+
assert_eq!(actual.top_p, None);
118+
}
119+
120+
#[test]
121+
fn test_enabled_none_with_zero_max_tokens_preserves_top_k_and_top_p() {
122+
// Matches `is_reasoning_supported`: max_tokens == 0 is treated as off.
123+
let fixture = create_context_fixture().reasoning(ReasoningConfig {
124+
enabled: None,
125+
max_tokens: Some(0),
126+
effort: None,
127+
exclude: None,
128+
});
129+
let mut transformer = ReasoningTransform;
130+
let actual = transformer.transform(fixture.clone());
131+
132+
assert_eq!(actual.top_k, fixture.top_k);
133+
assert_eq!(actual.top_p, fixture.top_p);
134+
}
88135
}

crates/forge_app/src/orch.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use tokio::sync::Notify;
1111
use tracing::warn;
1212

1313
use crate::agent::AgentService;
14+
use crate::transformers::ModelSpecificReasoning;
1415
use crate::{EnvironmentInfra, TemplateEngine};
1516

1617
#[derive(Clone, Setters)]
@@ -208,7 +209,12 @@ impl<S: AgentService + EnvironmentInfra<Config = forge_config::ForgeConfig>> Orc
208209
.pipe(DropReasoningDetails.when(|_| !reasoning_supported))
209210
// Strip all reasoning from messages when the model has changed (signatures are
210211
// model-specific and invalid across models). No-op when model is unchanged.
211-
.pipe(ReasoningNormalizer::new(model_id.clone()));
212+
.pipe(ReasoningNormalizer::new(model_id.clone()))
213+
// Normalize Anthropic reasoning knobs per model family before provider conversion.
214+
.pipe(
215+
ModelSpecificReasoning::new(model_id.as_str())
216+
.when(|_| model_id.as_str().to_lowercase().contains("claude")),
217+
);
212218
let response = self
213219
.services
214220
.chat_agent(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
mod compaction;
22
mod dedupe_role;
33
mod drop_role;
4+
mod model_specific_reasoning;
45
mod strip_working_dir;
56
mod trim_context_summary;
67

78
pub use compaction::SummaryTransformer;
9+
pub(crate) use model_specific_reasoning::ModelSpecificReasoning;

0 commit comments

Comments
 (0)