langchain 1.1.0__tar.gz → 1.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {langchain-1.1.0 → langchain-1.1.3}/PKG-INFO +2 -2
- {langchain-1.1.0 → langchain-1.1.3}/langchain/__init__.py +1 -1
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/factory.py +4 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/context_editing.py +1 -1
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/human_in_the_loop.py +9 -7
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/summarization.py +132 -78
- {langchain-1.1.0 → langchain-1.1.3}/langchain/chat_models/base.py +10 -0
- {langchain-1.1.0 → langchain-1.1.3}/pyproject.toml +2 -2
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_summarization.py +124 -132
- langchain-1.1.3/tests/unit_tests/agents/test_agent_name.py +99 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/chat_models/test_chat_models.py +1 -0
- {langchain-1.1.0 → langchain-1.1.3}/uv.lock +37 -6
- {langchain-1.1.0 → langchain-1.1.3}/.gitignore +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/LICENSE +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/Makefile +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/README.md +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/extended_testing_deps.txt +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/_execution.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/_redaction.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/_retry.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/file_search.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/model_call_limit.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/model_fallback.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/model_retry.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/pii.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/shell_tool.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/todo.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/tool_call_limit.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/tool_emulator.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/tool_retry.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/tool_selection.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/middleware/types.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/agents/structured_output.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/chat_models/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/embeddings/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/embeddings/base.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/messages/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/py.typed +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/rate_limiters/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/tools/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/langchain/tools/tool_node.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/scripts/check_imports.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/cassettes/test_inference_to_native_output[False].yaml.gz +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/cassettes/test_inference_to_native_output[True].yaml.gz +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/cassettes/test_inference_to_tool_output[False].yaml.gz +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/cassettes/test_inference_to_tool_output[True].yaml.gz +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/agents/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/agents/middleware/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/agents/middleware/test_shell_tool_integration.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/cache/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/cache/fake_embeddings.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/chat_models/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/chat_models/test_base.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/conftest.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/embeddings/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/embeddings/test_base.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/integration_tests/test_compile.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/__snapshots__/test_middleware_agent.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/__snapshots__/test_middleware_decorators.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/__snapshots__/test_middleware_framework.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/__snapshots__/test_return_direct_graph.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/any_str.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/compose-postgres.yml +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/compose-redis.yml +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/conftest.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/conftest_checkpointer.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/conftest_store.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/memory_assert.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/messages.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/__snapshots__/test_middleware_decorators.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/__snapshots__/test_middleware_diagram.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/__snapshots__/test_middleware_framework.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/__snapshots__/test_decorators.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/__snapshots__/test_diagram.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/__snapshots__/test_framework.ambr +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_composition.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_decorators.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_diagram.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_framework.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_overrides.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_sync_async_wrappers.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_tools.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_wrap_model_call.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/core/test_wrap_tool_call.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_context_editing.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_file_search.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_human_in_the_loop.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_model_call_limit.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_model_fallback.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_model_retry.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_pii.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_shell_execution_policies.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_shell_tool.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_structured_output_retry.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_todo.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_tool_call_limit.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_tool_emulator.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_tool_retry.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/middleware/implementations/test_tool_selection.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/model.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/specifications/responses.json +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/specifications/return_direct.json +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_create_agent_tool_validation.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_injected_runtime_create_agent.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_react_agent.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_response_format.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_response_format_integration.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_responses.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_responses_spec.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_return_direct_graph.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_return_direct_spec.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_state_schema.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/test_system_message.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/agents/utils.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/chat_models/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/conftest.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/embeddings/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/embeddings/test_base.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/embeddings/test_imports.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/stubs.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/test_dependencies.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/test_imports.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/test_pytest_config.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/tools/__init__.py +0 -0
- {langchain-1.1.0 → langchain-1.1.3}/tests/unit_tests/tools/test_imports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: Building applications with LLMs through composability
|
|
5
5
|
Project-URL: Homepage, https://docs.langchain.com/
|
|
6
6
|
Project-URL: Documentation, https://reference.langchain.com/python/langchain/langchain/
|
|
@@ -12,7 +12,7 @@ Project-URL: Reddit, https://www.reddit.com/r/LangChain/
|
|
|
12
12
|
License: MIT
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Python: <4.0.0,>=3.10.0
|
|
15
|
-
Requires-Dist: langchain-core<2.0.0,>=1.1.
|
|
15
|
+
Requires-Dist: langchain-core<2.0.0,>=1.1.2
|
|
16
16
|
Requires-Dist: langgraph<1.1.0,>=1.0.2
|
|
17
17
|
Requires-Dist: pydantic<3.0.0,>=2.7.4
|
|
18
18
|
Provides-Extra: anthropic
|
|
@@ -1100,6 +1100,8 @@ def create_agent( # noqa: PLR0915
|
|
|
1100
1100
|
messages = [request.system_message, *messages]
|
|
1101
1101
|
|
|
1102
1102
|
output = model_.invoke(messages)
|
|
1103
|
+
if name:
|
|
1104
|
+
output.name = name
|
|
1103
1105
|
|
|
1104
1106
|
# Handle model output to get messages and structured_response
|
|
1105
1107
|
handled_output = _handle_model_output(output, effective_response_format)
|
|
@@ -1153,6 +1155,8 @@ def create_agent( # noqa: PLR0915
|
|
|
1153
1155
|
messages = [request.system_message, *messages]
|
|
1154
1156
|
|
|
1155
1157
|
output = await model_.ainvoke(messages)
|
|
1158
|
+
if name:
|
|
1159
|
+
output.name = name
|
|
1156
1160
|
|
|
1157
1161
|
# Handle model output to get messages and structured_response
|
|
1158
1162
|
handled_output = _handle_model_output(output, effective_response_format)
|
|
@@ -189,7 +189,7 @@ class ContextEditingMiddleware(AgentMiddleware):
|
|
|
189
189
|
configured thresholds.
|
|
190
190
|
|
|
191
191
|
Currently the `ClearToolUsesEdit` strategy is supported, aligning with Anthropic's
|
|
192
|
-
`clear_tool_uses_20250919` behavior [(read more)](https://
|
|
192
|
+
`clear_tool_uses_20250919` behavior [(read more)](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool).
|
|
193
193
|
"""
|
|
194
194
|
|
|
195
195
|
edits: list[ContextEdit]
|
|
@@ -7,7 +7,7 @@ from langgraph.runtime import Runtime
|
|
|
7
7
|
from langgraph.types import interrupt
|
|
8
8
|
from typing_extensions import NotRequired, TypedDict
|
|
9
9
|
|
|
10
|
-
from langchain.agents.middleware.types import AgentMiddleware, AgentState
|
|
10
|
+
from langchain.agents.middleware.types import AgentMiddleware, AgentState, ContextT, StateT
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Action(TypedDict):
|
|
@@ -102,7 +102,7 @@ class HITLResponse(TypedDict):
|
|
|
102
102
|
class _DescriptionFactory(Protocol):
|
|
103
103
|
"""Callable that generates a description for a tool call."""
|
|
104
104
|
|
|
105
|
-
def __call__(self, tool_call: ToolCall, state: AgentState, runtime: Runtime) -> str:
|
|
105
|
+
def __call__(self, tool_call: ToolCall, state: AgentState, runtime: Runtime[ContextT]) -> str:
|
|
106
106
|
"""Generate a description for a tool call."""
|
|
107
107
|
...
|
|
108
108
|
|
|
@@ -138,7 +138,7 @@ class InterruptOnConfig(TypedDict):
|
|
|
138
138
|
def format_tool_description(
|
|
139
139
|
tool_call: ToolCall,
|
|
140
140
|
state: AgentState,
|
|
141
|
-
runtime: Runtime
|
|
141
|
+
runtime: Runtime[ContextT]
|
|
142
142
|
) -> str:
|
|
143
143
|
import json
|
|
144
144
|
return (
|
|
@@ -156,7 +156,7 @@ class InterruptOnConfig(TypedDict):
|
|
|
156
156
|
"""JSON schema for the args associated with the action, if edits are allowed."""
|
|
157
157
|
|
|
158
158
|
|
|
159
|
-
class HumanInTheLoopMiddleware(AgentMiddleware):
|
|
159
|
+
class HumanInTheLoopMiddleware(AgentMiddleware[StateT, ContextT]):
|
|
160
160
|
"""Human in the loop middleware."""
|
|
161
161
|
|
|
162
162
|
def __init__(
|
|
@@ -204,7 +204,7 @@ class HumanInTheLoopMiddleware(AgentMiddleware):
|
|
|
204
204
|
tool_call: ToolCall,
|
|
205
205
|
config: InterruptOnConfig,
|
|
206
206
|
state: AgentState,
|
|
207
|
-
runtime: Runtime,
|
|
207
|
+
runtime: Runtime[ContextT],
|
|
208
208
|
) -> tuple[ActionRequest, ReviewConfig]:
|
|
209
209
|
"""Create an ActionRequest and ReviewConfig for a tool call."""
|
|
210
210
|
tool_name = tool_call["name"]
|
|
@@ -277,7 +277,7 @@ class HumanInTheLoopMiddleware(AgentMiddleware):
|
|
|
277
277
|
)
|
|
278
278
|
raise ValueError(msg)
|
|
279
279
|
|
|
280
|
-
def after_model(self, state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
|
|
280
|
+
def after_model(self, state: AgentState, runtime: Runtime[ContextT]) -> dict[str, Any] | None:
|
|
281
281
|
"""Trigger interrupt flows for relevant tool calls after an `AIMessage`."""
|
|
282
282
|
messages = state["messages"]
|
|
283
283
|
if not messages:
|
|
@@ -350,6 +350,8 @@ class HumanInTheLoopMiddleware(AgentMiddleware):
|
|
|
350
350
|
|
|
351
351
|
return {"messages": [last_ai_msg, *artificial_tool_messages]}
|
|
352
352
|
|
|
353
|
-
async def aafter_model(
|
|
353
|
+
async def aafter_model(
|
|
354
|
+
self, state: AgentState, runtime: Runtime[ContextT]
|
|
355
|
+
) -> dict[str, Any] | None:
|
|
354
356
|
"""Async trigger interrupt flows for relevant tool calls after an `AIMessage`."""
|
|
355
357
|
return self.after_model(state, runtime)
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import uuid
|
|
4
4
|
import warnings
|
|
5
5
|
from collections.abc import Callable, Iterable, Mapping
|
|
6
|
+
from functools import partial
|
|
6
7
|
from typing import Any, Literal, cast
|
|
7
8
|
|
|
8
9
|
from langchain_core.messages import (
|
|
9
|
-
AIMessage,
|
|
10
10
|
AnyMessage,
|
|
11
11
|
MessageLikeRepresentation,
|
|
12
12
|
RemoveMessage,
|
|
@@ -55,13 +55,76 @@ Messages to summarize:
|
|
|
55
55
|
_DEFAULT_MESSAGES_TO_KEEP = 20
|
|
56
56
|
_DEFAULT_TRIM_TOKEN_LIMIT = 4000
|
|
57
57
|
_DEFAULT_FALLBACK_MESSAGE_COUNT = 15
|
|
58
|
-
_SEARCH_RANGE_FOR_TOOL_PAIRS = 5
|
|
59
58
|
|
|
60
59
|
ContextFraction = tuple[Literal["fraction"], float]
|
|
60
|
+
"""Fraction of model's maximum input tokens.
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
To specify 50% of the model's max input tokens:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
("fraction", 0.5)
|
|
67
|
+
```
|
|
68
|
+
"""
|
|
69
|
+
|
|
61
70
|
ContextTokens = tuple[Literal["tokens"], int]
|
|
71
|
+
"""Absolute number of tokens.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
To specify 3000 tokens:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
("tokens", 3000)
|
|
78
|
+
```
|
|
79
|
+
"""
|
|
80
|
+
|
|
62
81
|
ContextMessages = tuple[Literal["messages"], int]
|
|
82
|
+
"""Absolute number of messages.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
To specify 50 messages:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
("messages", 50)
|
|
89
|
+
```
|
|
90
|
+
"""
|
|
63
91
|
|
|
64
92
|
ContextSize = ContextFraction | ContextTokens | ContextMessages
|
|
93
|
+
"""Union type for context size specifications.
|
|
94
|
+
|
|
95
|
+
Can be either:
|
|
96
|
+
|
|
97
|
+
- [`ContextFraction`][langchain.agents.middleware.summarization.ContextFraction]: A
|
|
98
|
+
fraction of the model's maximum input tokens.
|
|
99
|
+
- [`ContextTokens`][langchain.agents.middleware.summarization.ContextTokens]: An absolute
|
|
100
|
+
number of tokens.
|
|
101
|
+
- [`ContextMessages`][langchain.agents.middleware.summarization.ContextMessages]: An
|
|
102
|
+
absolute number of messages.
|
|
103
|
+
|
|
104
|
+
Depending on use with `trigger` or `keep` parameters, this type indicates either
|
|
105
|
+
when to trigger summarization or how much context to retain.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
```python
|
|
109
|
+
# ContextFraction
|
|
110
|
+
context_size: ContextSize = ("fraction", 0.5)
|
|
111
|
+
|
|
112
|
+
# ContextTokens
|
|
113
|
+
context_size: ContextSize = ("tokens", 3000)
|
|
114
|
+
|
|
115
|
+
# ContextMessages
|
|
116
|
+
context_size: ContextSize = ("messages", 50)
|
|
117
|
+
```
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _get_approximate_token_counter(model: BaseChatModel) -> TokenCounter:
|
|
122
|
+
"""Tune parameters of approximate token counter based on model type."""
|
|
123
|
+
if model._llm_type == "anthropic-chat":
|
|
124
|
+
# 3.3 was estimated in an offline experiment, comparing with Claude's token-counting
|
|
125
|
+
# API: https://platform.claude.com/docs/en/build-with-claude/token-counting
|
|
126
|
+
return partial(count_tokens_approximately, chars_per_token=3.3)
|
|
127
|
+
return count_tokens_approximately
|
|
65
128
|
|
|
66
129
|
|
|
67
130
|
class SummarizationMiddleware(AgentMiddleware):
|
|
@@ -89,19 +152,48 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
89
152
|
model: The language model to use for generating summaries.
|
|
90
153
|
trigger: One or more thresholds that trigger summarization.
|
|
91
154
|
|
|
92
|
-
Provide a single
|
|
93
|
-
summarization
|
|
155
|
+
Provide a single
|
|
156
|
+
[`ContextSize`][langchain.agents.middleware.summarization.ContextSize]
|
|
157
|
+
tuple or a list of tuples, in which case summarization runs when any
|
|
158
|
+
threshold is met.
|
|
159
|
+
|
|
160
|
+
!!! example
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Trigger summarization when 50 messages is reached
|
|
164
|
+
("messages", 50)
|
|
165
|
+
|
|
166
|
+
# Trigger summarization when 3000 tokens is reached
|
|
167
|
+
("tokens", 3000)
|
|
168
|
+
|
|
169
|
+
# Trigger summarization either when 80% of model's max input tokens
|
|
170
|
+
# is reached or when 100 messages is reached (whichever comes first)
|
|
171
|
+
[("fraction", 0.8), ("messages", 100)]
|
|
172
|
+
```
|
|
94
173
|
|
|
95
|
-
|
|
96
|
-
|
|
174
|
+
See [`ContextSize`][langchain.agents.middleware.summarization.ContextSize]
|
|
175
|
+
for more details.
|
|
97
176
|
keep: Context retention policy applied after summarization.
|
|
98
177
|
|
|
99
|
-
Provide a `ContextSize`
|
|
178
|
+
Provide a [`ContextSize`][langchain.agents.middleware.summarization.ContextSize]
|
|
179
|
+
tuple to specify how much history to preserve.
|
|
100
180
|
|
|
101
|
-
Defaults to keeping the most recent 20 messages.
|
|
181
|
+
Defaults to keeping the most recent `20` messages.
|
|
102
182
|
|
|
103
|
-
|
|
104
|
-
|
|
183
|
+
Does not support multiple values like `trigger`.
|
|
184
|
+
|
|
185
|
+
!!! example
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
# Keep the most recent 20 messages
|
|
189
|
+
("messages", 20)
|
|
190
|
+
|
|
191
|
+
# Keep the most recent 3000 tokens
|
|
192
|
+
("tokens", 3000)
|
|
193
|
+
|
|
194
|
+
# Keep the most recent 30% of the model's max input tokens
|
|
195
|
+
("fraction", 0.3)
|
|
196
|
+
```
|
|
105
197
|
token_counter: Function to count tokens in messages.
|
|
106
198
|
summary_prompt: Prompt template for generating summaries.
|
|
107
199
|
trim_tokens_to_summarize: Maximum tokens to keep when preparing messages for
|
|
@@ -150,7 +242,10 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
150
242
|
self._trigger_conditions = trigger_conditions
|
|
151
243
|
|
|
152
244
|
self.keep = self._validate_context_size(keep, "keep")
|
|
153
|
-
|
|
245
|
+
if token_counter is count_tokens_approximately:
|
|
246
|
+
self.token_counter = _get_approximate_token_counter(self.model)
|
|
247
|
+
else:
|
|
248
|
+
self.token_counter = token_counter
|
|
154
249
|
self.summary_prompt = summary_prompt
|
|
155
250
|
self.trim_tokens_to_summarize = trim_tokens_to_summarize
|
|
156
251
|
|
|
@@ -300,11 +395,8 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
300
395
|
return 0
|
|
301
396
|
cutoff_candidate = len(messages) - 1
|
|
302
397
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return i
|
|
306
|
-
|
|
307
|
-
return 0
|
|
398
|
+
# Advance past any ToolMessages to avoid splitting AI/Tool pairs
|
|
399
|
+
return self._find_safe_cutoff_point(messages, cutoff_candidate)
|
|
308
400
|
|
|
309
401
|
def _get_profile_limits(self) -> int | None:
|
|
310
402
|
"""Retrieve max input token limit from the model profile."""
|
|
@@ -366,67 +458,26 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
366
458
|
|
|
367
459
|
Returns the index where messages can be safely cut without separating
|
|
368
460
|
related AI and Tool messages. Returns `0` if no safe cutoff is found.
|
|
461
|
+
|
|
462
|
+
This is aggressive with summarization - if the target cutoff lands in the
|
|
463
|
+
middle of tool messages, we advance past all of them (summarizing more).
|
|
369
464
|
"""
|
|
370
465
|
if len(messages) <= messages_to_keep:
|
|
371
466
|
return 0
|
|
372
467
|
|
|
373
468
|
target_cutoff = len(messages) - messages_to_keep
|
|
469
|
+
return self._find_safe_cutoff_point(messages, target_cutoff)
|
|
374
470
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return i
|
|
378
|
-
|
|
379
|
-
return 0
|
|
380
|
-
|
|
381
|
-
def _is_safe_cutoff_point(self, messages: list[AnyMessage], cutoff_index: int) -> bool:
|
|
382
|
-
"""Check if cutting at index would separate AI/Tool message pairs."""
|
|
383
|
-
if cutoff_index >= len(messages):
|
|
384
|
-
return True
|
|
385
|
-
|
|
386
|
-
search_start = max(0, cutoff_index - _SEARCH_RANGE_FOR_TOOL_PAIRS)
|
|
387
|
-
search_end = min(len(messages), cutoff_index + _SEARCH_RANGE_FOR_TOOL_PAIRS)
|
|
471
|
+
def _find_safe_cutoff_point(self, messages: list[AnyMessage], cutoff_index: int) -> int:
|
|
472
|
+
"""Find a safe cutoff point that doesn't split AI/Tool message pairs.
|
|
388
473
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
return True
|
|
398
|
-
|
|
399
|
-
def _has_tool_calls(self, message: AnyMessage) -> bool:
|
|
400
|
-
"""Check if message is an AI message with tool calls."""
|
|
401
|
-
return (
|
|
402
|
-
isinstance(message, AIMessage) and hasattr(message, "tool_calls") and message.tool_calls # type: ignore[return-value]
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
def _extract_tool_call_ids(self, ai_message: AIMessage) -> set[str]:
|
|
406
|
-
"""Extract tool call IDs from an AI message."""
|
|
407
|
-
tool_call_ids = set()
|
|
408
|
-
for tc in ai_message.tool_calls:
|
|
409
|
-
call_id = tc.get("id") if isinstance(tc, dict) else getattr(tc, "id", None)
|
|
410
|
-
if call_id is not None:
|
|
411
|
-
tool_call_ids.add(call_id)
|
|
412
|
-
return tool_call_ids
|
|
413
|
-
|
|
414
|
-
def _cutoff_separates_tool_pair(
|
|
415
|
-
self,
|
|
416
|
-
messages: list[AnyMessage],
|
|
417
|
-
ai_message_index: int,
|
|
418
|
-
cutoff_index: int,
|
|
419
|
-
tool_call_ids: set[str],
|
|
420
|
-
) -> bool:
|
|
421
|
-
"""Check if cutoff separates an AI message from its corresponding tool messages."""
|
|
422
|
-
for j in range(ai_message_index + 1, len(messages)):
|
|
423
|
-
message = messages[j]
|
|
424
|
-
if isinstance(message, ToolMessage) and message.tool_call_id in tool_call_ids:
|
|
425
|
-
ai_before_cutoff = ai_message_index < cutoff_index
|
|
426
|
-
tool_before_cutoff = j < cutoff_index
|
|
427
|
-
if ai_before_cutoff != tool_before_cutoff:
|
|
428
|
-
return True
|
|
429
|
-
return False
|
|
474
|
+
If the message at cutoff_index is a ToolMessage, advance until we find
|
|
475
|
+
a non-ToolMessage. This ensures we never cut in the middle of parallel
|
|
476
|
+
tool call responses.
|
|
477
|
+
"""
|
|
478
|
+
while cutoff_index < len(messages) and isinstance(messages[cutoff_index], ToolMessage):
|
|
479
|
+
cutoff_index += 1
|
|
480
|
+
return cutoff_index
|
|
430
481
|
|
|
431
482
|
def _create_summary(self, messages_to_summarize: list[AnyMessage]) -> str:
|
|
432
483
|
"""Generate summary for the given messages."""
|
|
@@ -465,14 +516,17 @@ class SummarizationMiddleware(AgentMiddleware):
|
|
|
465
516
|
try:
|
|
466
517
|
if self.trim_tokens_to_summarize is None:
|
|
467
518
|
return messages
|
|
468
|
-
return
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
519
|
+
return cast(
|
|
520
|
+
"list[AnyMessage]",
|
|
521
|
+
trim_messages(
|
|
522
|
+
messages,
|
|
523
|
+
max_tokens=self.trim_tokens_to_summarize,
|
|
524
|
+
token_counter=self.token_counter,
|
|
525
|
+
start_on="human",
|
|
526
|
+
strategy="last",
|
|
527
|
+
allow_partial=True,
|
|
528
|
+
include_system=True,
|
|
529
|
+
),
|
|
476
530
|
)
|
|
477
531
|
except Exception: # noqa: BLE001
|
|
478
532
|
return messages[-_DEFAULT_FALLBACK_MESSAGE_COUNT:]
|
|
@@ -102,6 +102,7 @@ def init_chat_model(
|
|
|
102
102
|
- `deepseek...` -> `deepseek`
|
|
103
103
|
- `grok...` -> `xai`
|
|
104
104
|
- `sonar...` -> `perplexity`
|
|
105
|
+
- `solar...` -> `upstage`
|
|
105
106
|
model_provider: The model provider if not specified as part of the model arg
|
|
106
107
|
(see above).
|
|
107
108
|
|
|
@@ -129,6 +130,7 @@ def init_chat_model(
|
|
|
129
130
|
- `nvidia` -> [`langchain-nvidia-ai-endpoints`](https://docs.langchain.com/oss/python/integrations/providers/nvidia)
|
|
130
131
|
- `xai` -> [`langchain-xai`](https://docs.langchain.com/oss/python/integrations/providers/xai)
|
|
131
132
|
- `perplexity` -> [`langchain-perplexity`](https://docs.langchain.com/oss/python/integrations/providers/perplexity)
|
|
133
|
+
- `upstage` -> [`langchain-upstage`](https://docs.langchain.com/oss/python/integrations/providers/upstage)
|
|
132
134
|
|
|
133
135
|
configurable_fields: Which model parameters are configurable at runtime:
|
|
134
136
|
|
|
@@ -449,6 +451,11 @@ def _init_chat_model_helper(
|
|
|
449
451
|
from langchain_perplexity import ChatPerplexity
|
|
450
452
|
|
|
451
453
|
return ChatPerplexity(model=model, **kwargs)
|
|
454
|
+
if model_provider == "upstage":
|
|
455
|
+
_check_pkg("langchain_upstage")
|
|
456
|
+
from langchain_upstage import ChatUpstage
|
|
457
|
+
|
|
458
|
+
return ChatUpstage(model=model, **kwargs)
|
|
452
459
|
supported = ", ".join(_SUPPORTED_PROVIDERS)
|
|
453
460
|
msg = f"Unsupported {model_provider=}.\n\nSupported model providers are: {supported}"
|
|
454
461
|
raise ValueError(msg)
|
|
@@ -475,6 +482,7 @@ _SUPPORTED_PROVIDERS = {
|
|
|
475
482
|
"ibm",
|
|
476
483
|
"xai",
|
|
477
484
|
"perplexity",
|
|
485
|
+
"upstage",
|
|
478
486
|
}
|
|
479
487
|
|
|
480
488
|
|
|
@@ -499,6 +507,8 @@ def _attempt_infer_model_provider(model_name: str) -> str | None:
|
|
|
499
507
|
return "xai"
|
|
500
508
|
if model_name.startswith("sonar"):
|
|
501
509
|
return "perplexity"
|
|
510
|
+
if model_name.startswith("solar"):
|
|
511
|
+
return "upstage"
|
|
502
512
|
return None
|
|
503
513
|
|
|
504
514
|
|
|
@@ -9,10 +9,10 @@ license = { text = "MIT" }
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = []
|
|
11
11
|
|
|
12
|
-
version = "1.1.
|
|
12
|
+
version = "1.1.3"
|
|
13
13
|
requires-python = ">=3.10.0,<4.0.0"
|
|
14
14
|
dependencies = [
|
|
15
|
-
"langchain-core>=1.1.
|
|
15
|
+
"langchain-core>=1.1.2,<2.0.0",
|
|
16
16
|
"langgraph>=1.0.2,<1.1.0",
|
|
17
17
|
"pydantic>=2.7.4,<3.0.0",
|
|
18
18
|
]
|