mirror of
https://github.com/hwchase17/langchain.git
synced 2026-02-21 14:43:07 +00:00
fix(anthropic): support automatic compaction (Opus 4.6) (#35034)
This commit is contained in:
@@ -848,6 +848,9 @@ class ChatAnthropic(BaseChatModel):
|
||||
"""Parameters for Claude reasoning,
|
||||
|
||||
e.g., `#!python {"type": "enabled", "budget_tokens": 10_000}`
|
||||
|
||||
For Claude Opus 4.6, `budget_tokens` is deprecated in favor of
|
||||
`#!python {"type": "adaptive"}`
|
||||
"""
|
||||
|
||||
effort: Literal["high", "medium", "low"] | None = None
|
||||
@@ -896,6 +899,12 @@ class ChatAnthropic(BaseChatModel):
|
||||
invocations.
|
||||
"""
|
||||
|
||||
inference_geo: str | None = None
|
||||
"""Controls where model inference runs. See Anthropic's
|
||||
[data residency](https://platform.claude.com/docs/en/build-with-claude/data-residency)
|
||||
docs for more information.
|
||||
"""
|
||||
|
||||
@property
|
||||
def _llm_type(self) -> str:
|
||||
"""Return type of chat model."""
|
||||
@@ -1122,6 +1131,8 @@ class ChatAnthropic(BaseChatModel):
|
||||
}
|
||||
if self.thinking is not None:
|
||||
payload["thinking"] = self.thinking
|
||||
if self.inference_geo is not None:
|
||||
payload["inference_geo"] = self.inference_geo
|
||||
|
||||
# Handle output_config and effort parameter
|
||||
# Priority: self.effort > payload output_config
|
||||
@@ -1273,6 +1284,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
not _tools_in_params(payload)
|
||||
and not _documents_in_params(payload)
|
||||
and not _thinking_in_params(payload)
|
||||
and not _compact_in_params(payload)
|
||||
)
|
||||
block_start_event = None
|
||||
for event in stream:
|
||||
@@ -1309,6 +1321,7 @@ class ChatAnthropic(BaseChatModel):
|
||||
not _tools_in_params(payload)
|
||||
and not _documents_in_params(payload)
|
||||
and not _thinking_in_params(payload)
|
||||
and not _compact_in_params(payload)
|
||||
)
|
||||
block_start_event = None
|
||||
async for event in stream:
|
||||
@@ -1870,6 +1883,12 @@ def _documents_in_params(params: dict) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _compact_in_params(params: dict) -> bool:
|
||||
edits = params.get("context_management", {}).get("edits") or []
|
||||
|
||||
return any("compact" in (edit.get("type") or "") for edit in edits)
|
||||
|
||||
|
||||
class _AnthropicToolUse(TypedDict):
|
||||
type: Literal["tool_use"]
|
||||
name: str
|
||||
@@ -2049,6 +2068,13 @@ def _make_message_chunk_from_anthropic_event(
|
||||
tool_call_chunks=tool_call_chunks,
|
||||
)
|
||||
|
||||
# Compaction block
|
||||
elif event.delta.type == "compaction_delta":
|
||||
content_block = event.delta.model_dump()
|
||||
content_block["index"] = event.index
|
||||
content_block["type"] = "compaction"
|
||||
message_chunk = AIMessageChunk(content=[content_block])
|
||||
|
||||
# Process final usage metadata and completion info
|
||||
elif event.type == "message_delta" and stream_usage:
|
||||
usage_metadata = _create_usage_metadata(event.usage)
|
||||
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
version = "1.3.1"
|
||||
requires-python = ">=3.10.0,<4.0.0"
|
||||
dependencies = [
|
||||
"anthropic>=0.75.0,<1.0.0",
|
||||
"anthropic>=0.78.0,<1.0.0",
|
||||
"langchain-core>=1.2.6,<2.0.0",
|
||||
"pydantic>=2.7.4,<3.0.0",
|
||||
]
|
||||
|
||||
BIN
libs/partners/anthropic/tests/cassettes/test_compaction.yaml.gz
Normal file
BIN
libs/partners/anthropic/tests/cassettes/test_compaction.yaml.gz
Normal file
Binary file not shown.
@@ -2409,3 +2409,112 @@ def test_fine_grained_tool_streaming() -> None:
|
||||
assert write_doc_block is not None
|
||||
assert write_doc_block["name"] == "write_document"
|
||||
assert "args" in write_doc_block
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_compaction() -> None:
|
||||
"""Test the compation beta feature."""
|
||||
llm = ChatAnthropic(
|
||||
model="claude-opus-4-6", # type: ignore[call-arg]
|
||||
betas=["compact-2026-01-12"],
|
||||
max_tokens=4096,
|
||||
context_management={
|
||||
"edits": [
|
||||
{
|
||||
"type": "compact_20260112",
|
||||
"trigger": {"type": "input_tokens", "value": 50000},
|
||||
"pause_after_compaction": True,
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
input_message = {
|
||||
"role": "user",
|
||||
"content": f"Generate a one-sentence summary of this:\n\n{'a' * 100000}",
|
||||
}
|
||||
messages: list = [input_message]
|
||||
|
||||
first_response = llm.invoke(messages)
|
||||
messages.append(first_response)
|
||||
|
||||
second_message = {
|
||||
"role": "user",
|
||||
"content": f"Generate a one-sentence summary of this:\n\n{'b' * 100000}",
|
||||
}
|
||||
messages.append(second_message)
|
||||
|
||||
second_response = llm.invoke(messages)
|
||||
messages.append(second_response)
|
||||
|
||||
content_blocks = second_response.content_blocks
|
||||
compaction_block = next(
|
||||
(block for block in content_blocks if block["type"] == "non_standard"),
|
||||
None,
|
||||
)
|
||||
assert compaction_block
|
||||
assert compaction_block["value"].get("type") == "compaction"
|
||||
|
||||
third_message = {
|
||||
"role": "user",
|
||||
"content": "What are we talking about?",
|
||||
}
|
||||
messages.append(third_message)
|
||||
third_response = llm.invoke(messages)
|
||||
content_blocks = third_response.content_blocks
|
||||
assert [block["type"] for block in content_blocks] == ["text"]
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_compaction_streaming() -> None:
|
||||
"""Test the compation beta feature."""
|
||||
llm = ChatAnthropic(
|
||||
model="claude-opus-4-6", # type: ignore[call-arg]
|
||||
betas=["compact-2026-01-12"],
|
||||
max_tokens=4096,
|
||||
context_management={
|
||||
"edits": [
|
||||
{
|
||||
"type": "compact_20260112",
|
||||
"trigger": {"type": "input_tokens", "value": 50000},
|
||||
"pause_after_compaction": False,
|
||||
}
|
||||
]
|
||||
},
|
||||
streaming=True,
|
||||
)
|
||||
|
||||
input_message = {
|
||||
"role": "user",
|
||||
"content": f"Generate a one-sentence summary of this:\n\n{'a' * 100000}",
|
||||
}
|
||||
messages: list = [input_message]
|
||||
|
||||
first_response = llm.invoke(messages)
|
||||
messages.append(first_response)
|
||||
|
||||
second_message = {
|
||||
"role": "user",
|
||||
"content": f"Generate a one-sentence summary of this:\n\n{'b' * 100000}",
|
||||
}
|
||||
messages.append(second_message)
|
||||
|
||||
second_response = llm.invoke(messages)
|
||||
messages.append(second_response)
|
||||
|
||||
content_blocks = second_response.content_blocks
|
||||
compaction_block = next(
|
||||
(block for block in content_blocks if block["type"] == "non_standard"),
|
||||
None,
|
||||
)
|
||||
assert compaction_block
|
||||
assert compaction_block["value"].get("type") == "compaction"
|
||||
|
||||
third_message = {
|
||||
"role": "user",
|
||||
"content": "What are we talking about?",
|
||||
}
|
||||
messages.append(third_message)
|
||||
third_response = llm.invoke(messages)
|
||||
content_blocks = third_response.content_blocks
|
||||
assert [block["type"] for block in content_blocks] == ["text"]
|
||||
|
||||
@@ -1564,6 +1564,13 @@ def test_context_management_in_payload() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_inference_geo_in_payload() -> None:
|
||||
llm = ChatAnthropic(model=MODEL_NAME, inference_geo="us")
|
||||
input_message = HumanMessage("Hello, world!")
|
||||
payload = llm._get_request_payload([input_message])
|
||||
assert payload["inference_geo"] == "us"
|
||||
|
||||
|
||||
def test_anthropic_model_params() -> None:
|
||||
llm = ChatAnthropic(model=MODEL_NAME)
|
||||
|
||||
|
||||
16
libs/partners/anthropic/uv.lock
generated
16
libs/partners/anthropic/uv.lock
generated
@@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.10.0, <4.0.0"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13' and platform_python_implementation == 'PyPy'",
|
||||
@@ -24,7 +24,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.75.0"
|
||||
version = "0.78.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -36,9 +36,9 @@ dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/1f/08e95f4b7e2d35205ae5dcbb4ae97e7d477fc521c275c02609e2931ece2d/anthropic-0.75.0.tar.gz", hash = "sha256:e8607422f4ab616db2ea5baacc215dd5f028da99ce2f022e33c7c535b29f3dfb", size = 439565, upload-time = "2025-11-24T20:41:45.28Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ec/51/32849a48f9b1cfe80a508fd269b20bd8f0b1357c70ba092890fde5a6a10b/anthropic-0.78.0.tar.gz", hash = "sha256:55fd978ab9b049c61857463f4c4e9e092b24f892519c6d8078cee1713d8af06e", size = 509136, upload-time = "2026-02-05T17:52:04.986Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/1c/1cd02b7ae64302a6e06724bf80a96401d5313708651d277b1458504a1730/anthropic-0.75.0-py3-none-any.whl", hash = "sha256:ea8317271b6c15d80225a9f3c670152746e88805a7a61e14d4a374577164965b", size = 388164, upload-time = "2025-11-24T20:41:43.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/03/2f50931a942e5e13f80e24d83406714672c57964be593fc046d81369335b/anthropic-0.78.0-py3-none-any.whl", hash = "sha256:2a9887d2e99d1b0f9fe08857a1e9fe5d2d4030455dbf9ac65aab052e2efaeac4", size = 405485, upload-time = "2026-02-05T17:52:03.674Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -498,7 +498,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain"
|
||||
version = "1.2.7"
|
||||
version = "1.2.9"
|
||||
source = { editable = "../../langchain_v1" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
@@ -607,7 +607,7 @@ typing = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anthropic", specifier = ">=0.75.0,<1.0.0" },
|
||||
{ name = "anthropic", specifier = ">=0.78.0,<1.0.0" },
|
||||
{ name = "langchain-core", editable = "../../core" },
|
||||
{ name = "pydantic", specifier = ">=2.7.4,<3.0.0" },
|
||||
]
|
||||
@@ -646,7 +646,7 @@ typing = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "1.2.7"
|
||||
version = "1.2.9"
|
||||
source = { editable = "../../core" }
|
||||
dependencies = [
|
||||
{ name = "jsonpatch" },
|
||||
@@ -706,7 +706,7 @@ typing = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain-tests"
|
||||
version = "1.1.2"
|
||||
version = "1.1.4"
|
||||
source = { editable = "../../standard-tests" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
|
||||
Reference in New Issue
Block a user