fix(anthropic): use model capability flag for response_format routing + eager_input_streaming + citation offsets

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Krrish Dholakia 2026-07-01 14:35:18 +00:00
parent 13b590c8ec
commit 16b47d8491
3 changed files with 117 additions and 19 deletions

View file

@ -790,6 +790,26 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
raise ValueError("defer_loading must be a boolean")
returned_tool["defer_loading"] = _defer_loading_function # type: ignore[typeddict-item]
## check if eager_input_streaming is set in the tool
_eager_input_streaming = tool.get("eager_input_streaming", None)
_eager_input_streaming_function = tool.get("function", {}).get("eager_input_streaming", None)
if returned_tool is not None:
tool_type = returned_tool.get("type", "")
if tool_type not in (
"tool_search_tool_regex_20251119",
"tool_search_tool_bm25_20251119",
"computer_20241022",
"computer_20250124",
):
if _eager_input_streaming is not None:
if not isinstance(_eager_input_streaming, bool):
raise ValueError("eager_input_streaming must be a boolean")
returned_tool["eager_input_streaming"] = _eager_input_streaming # type: ignore[typeddict-item]
elif _eager_input_streaming_function is not None:
if not isinstance(_eager_input_streaming_function, bool):
raise ValueError("eager_input_streaming must be a boolean")
returned_tool["eager_input_streaming"] = _eager_input_streaming_function # type: ignore[typeddict-item]
## check if allowed_callers is set in the tool
_allowed_callers = tool.get("allowed_callers", None)
_allowed_callers_function = tool.get("function", {}).get("allowed_callers", None)
@ -1411,25 +1431,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
output_key=param,
)
elif param == "response_format" and isinstance(value, dict):
if any(
substring in model
for substring in {
"sonnet-4.5",
"sonnet-4-5",
"opus-4.1",
"opus-4-1",
"opus-4.5",
"opus-4-5",
"opus-4.6",
"opus-4-6",
"opus-4.7",
"opus-4-7",
"sonnet-4.6",
"sonnet-4-6",
"sonnet_4.6",
"sonnet_4_6",
}
):
if self._supports_model_capability(model, "supports_output_config"):
_output_format = self.map_response_format_to_anthropic_output_format(value)
if _output_format is not None:
optional_params["output_format"] = _output_format
@ -1980,6 +1982,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
tool_results: Optional[List[Any]] = None
compaction_blocks: Optional[List[Any]] = None
for idx, content in enumerate(completion_response["content"]):
text_start = len(text_content)
if content["type"] == "text":
text_content += content["text"]
## TOOL CALLING
@ -2034,6 +2037,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
{
**citation,
"supported_text": content.get("text", ""),
"supported_text_start_char_index": text_start,
"supported_text_end_char_index": len(text_content),
}
for citation in content["citations"]
]

View file

@ -50,6 +50,7 @@ class AnthropicMessagesTool(TypedDict, total=False):
type: Literal["custom"]
cache_control: Optional[Union[dict, ChatCompletionCachedContent]]
defer_loading: bool
eager_input_streaming: bool
allowed_callers: Optional[List[str]]
input_examples: Optional[List[Dict[str, Any]]]

View file

@ -267,6 +267,8 @@ def test_extract_response_content_with_citations():
"start_char_index": 0,
"end_char_index": 20,
"supported_text": "the grass is green",
"supported_text_start_char_index": 28,
"supported_text_end_char_index": 46,
},
],
[
@ -278,6 +280,8 @@ def test_extract_response_content_with_citations():
"start_char_index": 20,
"end_char_index": 36,
"supported_text": "the sky is blue",
"supported_text_start_char_index": 51,
"supported_text_end_char_index": 66,
},
],
]
@ -5733,3 +5737,91 @@ def test_top_k_forwarded_at_transform_on_models_that_accept_it():
)
assert result["top_k"] == 40
def test_opus_4_8_uses_native_structured_output():
"""claude-opus-4-8 has supports_output_config=true in the model map,
so response_format should route to the native output_format path
rather than the tool-call emulation path"""
config = AnthropicConfig()
response_format = {
"type": "json_schema",
"json_schema": {
"name": "test_schema",
"schema": {
"type": "object",
"properties": {"answer": {"type": "string"}},
"required": ["answer"],
"additionalProperties": False,
},
},
}
optional_params = config.map_openai_params(
non_default_params={"response_format": response_format},
optional_params={},
model="claude-opus-4-8",
drop_params=False,
)
assert "output_format" in optional_params
assert optional_params["output_format"]["type"] == "json_schema"
assert "tools" not in optional_params
assert "tool_choice" not in optional_params
assert optional_params.get("json_mode") is True
def test_map_tool_helper_eager_input_streaming_passthrough():
"""eager_input_streaming on a custom tool must survive the mapping"""
config = AnthropicConfig()
tool = {
"type": "function",
"function": {
"name": "my_tool",
"description": "a tool",
"parameters": {"type": "object", "properties": {}},
"eager_input_streaming": True,
},
}
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result.get("eager_input_streaming") is True
def test_map_tool_helper_eager_input_streaming_top_level():
"""eager_input_streaming set at top level (not nested in function)"""
config = AnthropicConfig()
tool = {
"type": "function",
"eager_input_streaming": True,
"function": {
"name": "my_tool",
"description": "a tool",
"parameters": {"type": "object", "properties": {}},
},
}
result, _ = config._map_tool_helper(tool)
assert result is not None
assert result.get("eager_input_streaming") is True
def test_map_tool_helper_eager_input_streaming_rejects_non_bool():
"""eager_input_streaming must be a boolean"""
config = AnthropicConfig()
tool = {
"type": "function",
"function": {
"name": "my_tool",
"parameters": {"type": "object", "properties": {}},
"eager_input_streaming": "yes",
},
}
with pytest.raises(ValueError, match="eager_input_streaming must be a boolean"):
config._map_tool_helper(tool)