Skip to content

Commit b7091d3

Browse files
authored
feat(anthropic): auto append relevant beta headers (#34113)
1 parent 7a29522 commit b7091d3

File tree

3 files changed

+392
-37
lines changed

3 files changed

+392
-37
lines changed

libs/partners/anthropic/langchain_anthropic/chat_models.py

Lines changed: 110 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ class AnthropicTool(TypedDict):
121121
cache_control: NotRequired[dict[str, str]]
122122

123123

124+
# Some tool types require specific beta headers to be enabled
125+
# Mapping of tool type patterns to required beta headers
126+
_TOOL_TYPE_TO_BETA: dict[str, str] = {
127+
"web_fetch_20250910": "web-fetch-2025-09-10",
128+
"code_execution_20250522": "code-execution-2025-05-22",
129+
"code_execution_20250825": "code-execution-2025-08-25",
130+
"memory_20250818": "context-management-2025-06-27",
131+
}
132+
133+
124134
def _is_builtin_tool(tool: Any) -> bool:
125135
"""Check if a tool is a built-in Anthropic tool.
126136
@@ -1393,12 +1403,11 @@ class Joke(BaseModel):
13931403
13941404
??? example "Web fetch (beta)"
13951405
1396-
```python hl_lines="5 8-12"
1406+
```python hl_lines="7-11"
13971407
from langchain_anthropic import ChatAnthropic
13981408
13991409
model = ChatAnthropic(
14001410
model="claude-3-5-haiku-20241022",
1401-
betas=["web-fetch-2025-09-10"], # Enable web fetch beta
14021411
)
14031412
14041413
tool = {
@@ -1411,16 +1420,19 @@ class Joke(BaseModel):
14111420
response = model_with_tools.invoke("Please analyze the content at https://example.com/article")
14121421
```
14131422
1423+
!!! note "Automatic beta header"
1424+
1425+
The required `web-fetch-2025-09-10` beta header is automatically
1426+
appended to the request when using the `web_fetch_20250910` tool type.
1427+
You don't need to manually specify it in the `betas` parameter.
1428+
14141429
See the [Claude docs](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool)
14151430
for more info.
14161431
14171432
??? example "Code execution"
14181433
1419-
```python hl_lines="3 6-9"
1420-
model = ChatAnthropic(
1421-
model="claude-sonnet-4-5-20250929",
1422-
betas=["code-execution-2025-05-22"], # Enable code execution beta
1423-
)
1434+
```python hl_lines="3-6"
1435+
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
14241436
14251437
tool = {
14261438
"type": "code_execution_20250522",
@@ -1433,18 +1445,21 @@ class Joke(BaseModel):
14331445
)
14341446
```
14351447
1448+
!!! note "Automatic beta header"
1449+
1450+
The required `code-execution-2025-05-22` beta header is automatically
1451+
appended to the request when using the `code_execution_20250522` tool
1452+
type. You don't need to manually specify it in the `betas` parameter.
1453+
14361454
See the [Claude docs](https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool)
14371455
for more info.
14381456
14391457
??? example "Memory tool"
14401458
1441-
```python hl_lines="5 8-11"
1459+
```python hl_lines="5-8"
14421460
from langchain_anthropic import ChatAnthropic
14431461
1444-
model = ChatAnthropic(
1445-
model="claude-sonnet-4-5-20250929",
1446-
betas=["context-management-2025-06-27"], # Enable context management beta
1447-
)
1462+
model = ChatAnthropic(model="claude-sonnet-4-5-20250929")
14481463
14491464
tool = {
14501465
"type": "memory_20250818",
@@ -1455,6 +1470,12 @@ class Joke(BaseModel):
14551470
response = model_with_tools.invoke("What are my interests?")
14561471
```
14571472
1473+
!!! note "Automatic beta header"
1474+
1475+
The required `context-management-2025-06-27` beta header is automatically
1476+
appended to the request when using the `memory_20250818` tool type.
1477+
You don't need to manually specify it in the `betas` parameter.
1478+
14581479
See the [Claude docs](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool)
14591480
for more info.
14601481
@@ -1592,6 +1613,12 @@ class Joke(BaseModel):
15921613
15931614
Example: `#!python betas=["mcp-client-2025-04-04"]`
15941615
"""
1616+
# Can also be passed in w/ model_kwargs, but having it as a param makes better devx
1617+
#
1618+
# Precedence order:
1619+
# 1. Call-time kwargs (e.g., llm.invoke(..., betas=[...]))
1620+
# 2. model_kwargs (e.g., ChatAnthropic(model_kwargs={"betas": [...]}))
1621+
# 3. Direct parameter (e.g., ChatAnthropic(betas=[...]))
15951622

15961623
model_kwargs: dict[str, Any] = Field(default_factory=dict)
15971624

@@ -1842,21 +1869,74 @@ def _get_request_payload(
18421869
payload["thinking"] = self.thinking
18431870

18441871
if "response_format" in payload:
1872+
# response_format present when using agents.create_agent's ProviderStrategy
1873+
# ---
1874+
# ProviderStrategy converts to OpenAI-style format, which passes kwargs to
1875+
# ChatAnthropic, ending up in our payload
18451876
response_format = payload.pop("response_format")
18461877
if (
18471878
isinstance(response_format, dict)
18481879
and response_format.get("type") == "json_schema"
18491880
and "schema" in response_format.get("json_schema", {})
18501881
):
1851-
# compat with langchain.agents.create_agent response_format, which is
1852-
# an approximation of OpenAI format
18531882
response_format = cast(dict, response_format["json_schema"]["schema"])
1883+
# Convert OpenAI-style response_format to Anthropic's output_format
18541884
payload["output_format"] = _convert_to_anthropic_output_format(
18551885
response_format
18561886
)
18571887

1858-
if "output_format" in payload and not payload["betas"]:
1859-
payload["betas"] = ["structured-outputs-2025-11-13"]
1888+
if "output_format" in payload:
1889+
# Native structured output requires the structured outputs beta
1890+
if payload["betas"]:
1891+
if "structured-outputs-2025-11-13" not in payload["betas"]:
1892+
# Merge with existing betas
1893+
payload["betas"] = [
1894+
*payload["betas"],
1895+
"structured-outputs-2025-11-13",
1896+
]
1897+
else:
1898+
payload["betas"] = ["structured-outputs-2025-11-13"]
1899+
1900+
# Check if any tools have strict mode enabled
1901+
if "tools" in payload and isinstance(payload["tools"], list):
1902+
has_strict_tool = any(
1903+
isinstance(tool, dict) and tool.get("strict") is True
1904+
for tool in payload["tools"]
1905+
)
1906+
if has_strict_tool:
1907+
# Strict tool use requires the structured outputs beta
1908+
if payload["betas"]:
1909+
if "structured-outputs-2025-11-13" not in payload["betas"]:
1910+
# Merge with existing betas
1911+
payload["betas"] = [
1912+
*payload["betas"],
1913+
"structured-outputs-2025-11-13",
1914+
]
1915+
else:
1916+
payload["betas"] = ["structured-outputs-2025-11-13"]
1917+
1918+
# Auto-append required betas for specific tool types
1919+
for tool in payload["tools"]:
1920+
if isinstance(tool, dict) and "type" in tool:
1921+
tool_type = tool["type"]
1922+
if tool_type in _TOOL_TYPE_TO_BETA:
1923+
required_beta = _TOOL_TYPE_TO_BETA[tool_type]
1924+
if payload["betas"]:
1925+
# Append to existing betas if not already present
1926+
if required_beta not in payload["betas"]:
1927+
payload["betas"] = [*payload["betas"], required_beta]
1928+
else:
1929+
payload["betas"] = [required_beta]
1930+
1931+
# Auto-append required beta for mcp_servers
1932+
if payload.get("mcp_servers"):
1933+
required_beta = "mcp-client-2025-11-20"
1934+
if payload["betas"]:
1935+
# Append to existing betas if not already present
1936+
if required_beta not in payload["betas"]:
1937+
payload["betas"] = [*payload["betas"], required_beta]
1938+
else:
1939+
payload["betas"] = [required_beta]
18601940

18611941
return {k: v for k, v in payload.items() if v is not None}
18621942

@@ -2300,17 +2380,13 @@ class GetPrice(BaseModel):
23002380
- Claude Sonnet 4.5 or Opus 4.1
23012381
- `langchain-anthropic>=1.1.0`
23022382
2303-
To enable strict tool use:
2304-
2305-
1. Specify the `structured-outputs-2025-11-13` beta header
2306-
2. Specify `strict=True` when calling `bind_tools`
2383+
To enable strict tool use, specify `strict=True` when calling `bind_tools`.
23072384
2308-
```python hl_lines="5 12"
2385+
```python hl_lines="11"
23092386
from langchain_anthropic import ChatAnthropic
23102387
23112388
model = ChatAnthropic(
23122389
model="claude-sonnet-4-5",
2313-
betas=["structured-outputs-2025-11-13"],
23142390
)
23152391
23162392
def get_weather(location: str) -> str:
@@ -2320,6 +2396,12 @@ def get_weather(location: str) -> str:
23202396
model_with_tools = model.bind_tools([get_weather], strict=True)
23212397
```
23222398
2399+
!!! note "Automatic beta header"
2400+
2401+
The required `structured-outputs-2025-11-13` beta header is
2402+
automatically appended to the request when using `strict=True`, so you
2403+
don't need to manually specify it in the `betas` parameter.
2404+
23232405
See LangChain [docs](https://docs.langchain.com/oss/python/integrations/chat/anthropic#strict-tool-use)
23242406
for more detail.
23252407
""" # noqa: E501
@@ -2513,19 +2595,15 @@ class AnswerWithJustification(BaseModel):
25132595
- Claude Sonnet 4.5 or Opus 4.1
25142596
- `langchain-anthropic>=1.1.0`
25152597
2516-
To enable native structured output:
2598+
To enable native structured output, specify `method="json_schema"` when
2599+
calling `with_structured_output`. (Under the hood, LangChain will
2600+
append the required `structured-outputs-2025-11-13` beta header)
25172601
2518-
1. Specify the `structured-outputs-2025-11-13` beta header
2519-
2. Specify `method="json_schema"` when calling `with_structured_output`
2520-
2521-
```python hl_lines="6 16"
2602+
```python hl_lines="13"
25222603
from langchain_anthropic import ChatAnthropic
25232604
from pydantic import BaseModel, Field
25242605
2525-
model = ChatAnthropic(
2526-
model="claude-sonnet-4-5",
2527-
betas=["structured-outputs-2025-11-13"],
2528-
)
2606+
model = ChatAnthropic(model="claude-sonnet-4-5")
25292607
25302608
class Movie(BaseModel):
25312609
\"\"\"A movie with details.\"\"\"
@@ -2713,8 +2791,7 @@ def convert_to_anthropic_tool(
27132791
27142792
!!! note
27152793
2716-
Requires Claude Sonnet 4.5 or Opus 4.1 and the
2717-
`structured-outputs-2025-11-13` beta header.
2794+
Requires Claude Sonnet 4.5 or Opus 4.1.
27182795
27192796
Returns:
27202797
An Anthropic tool definition dict.

0 commit comments

Comments
 (0)