langchain 1.0.7__tar.gz → 1.1.0__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.0.7 → langchain-1.1.0}/.gitignore +3 -0
- {langchain-1.0.7 → langchain-1.1.0}/Makefile +12 -2
- {langchain-1.0.7 → langchain-1.1.0}/PKG-INFO +3 -5
- {langchain-1.0.7 → langchain-1.1.0}/README.md +1 -1
- {langchain-1.0.7 → langchain-1.1.0}/langchain/__init__.py +1 -1
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/factory.py +50 -16
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/__init__.py +2 -0
- langchain-1.1.0/langchain/agents/middleware/_retry.py +123 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/context_editing.py +9 -11
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/file_search.py +2 -2
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/human_in_the_loop.py +38 -42
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/model_call_limit.py +1 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/model_fallback.py +2 -4
- langchain-1.1.0/langchain/agents/middleware/model_retry.py +300 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/shell_tool.py +1 -1
- langchain-1.1.0/langchain/agents/middleware/summarization.py +478 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/todo.py +24 -18
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/tool_call_limit.py +36 -27
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/tool_emulator.py +29 -21
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/tool_retry.py +129 -133
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/tool_selection.py +18 -15
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/types.py +487 -259
- {langchain-1.0.7 → langchain-1.1.0}/langchain/chat_models/base.py +1 -1
- {langchain-1.0.7 → langchain-1.1.0}/pyproject.toml +2 -3
- langchain-1.1.0/tests/cassettes/test_inference_to_native_output[False].yaml.gz +0 -0
- langchain-1.1.0/tests/cassettes/test_inference_to_native_output[True].yaml.gz +0 -0
- langchain-1.1.0/tests/cassettes/test_inference_to_tool_output[False].yaml.gz +0 -0
- langchain-1.1.0/tests/cassettes/test_inference_to_tool_output[True].yaml.gz +0 -0
- langchain-1.1.0/tests/unit_tests/agents/__snapshots__/test_middleware_framework.ambr +212 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/__snapshots__/test_middleware_decorators.ambr +95 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/__snapshots__/test_middleware_diagram.ambr +289 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/__snapshots__/test_middleware_framework.ambr +212 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/core/__snapshots__/test_decorators.ambr +95 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/core/__snapshots__/test_diagram.ambr +289 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/core/__snapshots__/test_framework.ambr +212 -0
- langchain-1.0.7/tests/unit_tests/agents/test_handler_composition.py → langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_composition.py +1 -3
- langchain-1.0.7/tests/unit_tests/agents/test_middleware_decorators.py → langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_decorators.py +3 -5
- langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_diagram.py +193 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_framework.py +1051 -0
- langchain-1.0.7/tests/unit_tests/agents/test_sync_async_tool_wrapper_composition.py → langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_sync_async_wrappers.py +1 -1
- langchain-1.0.7/tests/unit_tests/agents/test_middleware_tools.py → langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_tools.py +11 -9
- langchain-1.0.7/tests/unit_tests/agents/middleware/test_wrap_model_call_middleware.py → langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_wrap_model_call.py +549 -72
- langchain-1.0.7/tests/unit_tests/agents/middleware/test_wrap_tool_call_decorator.py → langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_wrap_tool_call.py +1 -1
- langchain-1.0.7/tests/unit_tests/agents/test_context_editing_middleware.py → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_context_editing.py +76 -28
- {langchain-1.0.7/tests/unit_tests/agents/middleware → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/test_file_search.py +105 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_human_in_the_loop.py +737 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_model_call_limit.py +224 -0
- langchain-1.0.7/tests/unit_tests/agents/test_model_fallback_middleware.py → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_model_fallback.py +145 -9
- langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_model_retry.py +689 -0
- langchain-1.0.7/tests/unit_tests/agents/test_pii_middleware.py → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_pii.py +1 -1
- langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_summarization.py +894 -0
- langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_todo.py +516 -0
- {langchain-1.0.7/tests/unit_tests/agents → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/test_tool_call_limit.py +183 -1
- {langchain-1.0.7/tests/unit_tests/agents/middleware → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/test_tool_retry.py +143 -32
- langchain-1.0.7/tests/unit_tests/agents/middleware/test_llm_tool_selection.py → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations/test_tool_selection.py +17 -8
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/model.py +2 -3
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_injected_runtime_create_agent.py +244 -1
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_response_format.py +2 -3
- langchain-1.1.0/tests/unit_tests/agents/test_response_format_integration.py +142 -0
- langchain-1.1.0/tests/unit_tests/agents/test_system_message.py +1013 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/conftest.py +44 -0
- langchain-1.1.0/tests/unit_tests/embeddings/__init__.py +0 -0
- langchain-1.1.0/tests/unit_tests/tools/__init__.py +0 -0
- langchain-1.1.0/uv.lock +5408 -0
- langchain-1.0.7/langchain/agents/middleware/summarization.py +0 -249
- langchain-1.0.7/tests/integration_tests/agents/test_response_format.py +0 -79
- langchain-1.0.7/tests/unit_tests/agents/middleware/test_before_after_agent.py +0 -286
- langchain-1.0.7/tests/unit_tests/agents/middleware/test_shell_tool.py +0 -175
- langchain-1.0.7/tests/unit_tests/agents/middleware/test_wrap_model_call_decorator.py +0 -355
- langchain-1.0.7/tests/unit_tests/agents/test_middleware_agent.py +0 -2600
- langchain-1.0.7/tests/unit_tests/agents/test_on_tool_call_middleware.py +0 -828
- langchain-1.0.7/tests/unit_tests/agents/test_parallel_tool_call_limits.py +0 -192
- langchain-1.0.7/tests/unit_tests/agents/test_todo_middleware.py +0 -172
- langchain-1.0.7/uv.lock +0 -5129
- {langchain-1.0.7 → langchain-1.1.0}/LICENSE +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/extended_testing_deps.txt +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/_execution.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/_redaction.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/middleware/pii.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/agents/structured_output.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/chat_models/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/embeddings/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/embeddings/base.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/messages/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/py.typed +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/rate_limiters/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/tools/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/langchain/tools/tool_node.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/scripts/check_imports.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/agents/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/agents/middleware/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/agents/middleware/test_shell_tool_integration.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/cache/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/cache/fake_embeddings.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/chat_models/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/chat_models/test_base.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/conftest.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/embeddings/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/embeddings/test_base.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/integration_tests/test_compile.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/__snapshots__/test_middleware_agent.ambr +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/__snapshots__/test_middleware_decorators.ambr +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/__snapshots__/test_return_direct_graph.ambr +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/any_str.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/compose-postgres.yml +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/compose-redis.yml +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/conftest.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/conftest_checkpointer.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/conftest_store.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/memory_assert.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/messages.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/middleware/__init__.py +0 -0
- {langchain-1.0.7/tests/unit_tests/chat_models → langchain-1.1.0/tests/unit_tests/agents/middleware/core}/__init__.py +0 -0
- /langchain-1.0.7/tests/unit_tests/agents/middleware/test_override_methods.py → /langchain-1.1.0/tests/unit_tests/agents/middleware/core/test_overrides.py +0 -0
- {langchain-1.0.7/tests/unit_tests/embeddings → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/__init__.py +0 -0
- {langchain-1.0.7/tests/unit_tests/agents/middleware → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/test_shell_execution_policies.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/middleware/implementations/test_shell_tool.py +0 -0
- {langchain-1.0.7/tests/unit_tests/agents/middleware → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/test_structured_output_retry.py +0 -0
- {langchain-1.0.7/tests/unit_tests/agents/middleware → langchain-1.1.0/tests/unit_tests/agents/middleware/implementations}/test_tool_emulator.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/specifications/responses.json +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/specifications/return_direct.json +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_create_agent_tool_validation.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_react_agent.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_responses.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_responses_spec.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_return_direct_graph.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_return_direct_spec.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/test_state_schema.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/agents/utils.py +0 -0
- {langchain-1.0.7/tests/unit_tests/tools → langchain-1.1.0/tests/unit_tests/chat_models}/__init__.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/chat_models/test_chat_models.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/embeddings/test_base.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/embeddings/test_imports.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/stubs.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/test_dependencies.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/test_imports.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/test_pytest_config.py +0 -0
- {langchain-1.0.7 → langchain-1.1.0}/tests/unit_tests/tools/test_imports.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.PHONY: all start_services stop_services coverage test test_fast extended_tests test_watch test_watch_extended integration_tests check_imports lint format lint_diff format_diff lint_package lint_tests help
|
|
1
|
+
.PHONY: all start_services stop_services coverage coverage_agents test test_fast extended_tests test_watch test_watch_extended integration_tests check_imports lint format lint_diff format_diff lint_package lint_tests help
|
|
2
2
|
|
|
3
3
|
# Default target executed when no arguments are given to make.
|
|
4
4
|
all: help
|
|
@@ -27,8 +27,17 @@ coverage:
|
|
|
27
27
|
--cov-report term-missing:skip-covered \
|
|
28
28
|
$(TEST_FILE)
|
|
29
29
|
|
|
30
|
+
# Run middleware and agent tests with coverage report.
|
|
31
|
+
coverage_agents:
|
|
32
|
+
uv run --group test pytest \
|
|
33
|
+
tests/unit_tests/agents/middleware/ \
|
|
34
|
+
tests/unit_tests/agents/test_*.py \
|
|
35
|
+
--cov=langchain.agents \
|
|
36
|
+
--cov-report=term-missing \
|
|
37
|
+
--cov-report=html:htmlcov \
|
|
38
|
+
|
|
30
39
|
test:
|
|
31
|
-
make start_services && LANGGRAPH_TEST_FAST=0 uv run --no-sync --active --group test pytest -n auto --disable-socket --allow-unix-socket $(TEST_FILE) --cov-report term-missing:skip-covered; \
|
|
40
|
+
make start_services && LANGGRAPH_TEST_FAST=0 uv run --no-sync --active --group test pytest -n auto --disable-socket --allow-unix-socket $(TEST_FILE) --cov-report term-missing:skip-covered --snapshot-update; \
|
|
32
41
|
EXIT_CODE=$$?; \
|
|
33
42
|
make stop_services; \
|
|
34
43
|
exit $$EXIT_CODE
|
|
@@ -93,6 +102,7 @@ help:
|
|
|
93
102
|
@echo 'lint - run linters'
|
|
94
103
|
@echo '-- TESTS --'
|
|
95
104
|
@echo 'coverage - run unit tests and generate coverage report'
|
|
105
|
+
@echo 'coverage_agents - run middleware and agent tests with coverage report'
|
|
96
106
|
@echo 'test - run unit tests with all services'
|
|
97
107
|
@echo 'test_fast - run unit tests with in-memory services only'
|
|
98
108
|
@echo 'tests - run unit tests (alias for "make test")'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
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.0
|
|
15
|
+
Requires-Dist: langchain-core<2.0.0,>=1.1.0
|
|
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
|
|
@@ -37,8 +37,6 @@ Provides-Extra: huggingface
|
|
|
37
37
|
Requires-Dist: langchain-huggingface; extra == 'huggingface'
|
|
38
38
|
Provides-Extra: mistralai
|
|
39
39
|
Requires-Dist: langchain-mistralai; extra == 'mistralai'
|
|
40
|
-
Provides-Extra: model-profiles
|
|
41
|
-
Requires-Dist: langchain-model-profiles; extra == 'model-profiles'
|
|
42
40
|
Provides-Extra: ollama
|
|
43
41
|
Requires-Dist: langchain-ollama; extra == 'ollama'
|
|
44
42
|
Provides-Extra: openai
|
|
@@ -79,7 +77,7 @@ LangChain [agents](https://docs.langchain.com/oss/python/langchain/agents) are b
|
|
|
79
77
|
|
|
80
78
|
## 📖 Documentation
|
|
81
79
|
|
|
82
|
-
For full documentation, see the [API reference](https://reference.langchain.com/python/langchain/langchain/).
|
|
80
|
+
For full documentation, see the [API reference](https://reference.langchain.com/python/langchain/langchain/). For conceptual guides, tutorials, and examples on using LangChain, see the [LangChain Docs](https://docs.langchain.com/oss/python/langchain/overview).
|
|
83
81
|
|
|
84
82
|
## 📕 Releases & Versioning
|
|
85
83
|
|
|
@@ -26,7 +26,7 @@ LangChain [agents](https://docs.langchain.com/oss/python/langchain/agents) are b
|
|
|
26
26
|
|
|
27
27
|
## 📖 Documentation
|
|
28
28
|
|
|
29
|
-
For full documentation, see the [API reference](https://reference.langchain.com/python/langchain/langchain/).
|
|
29
|
+
For full documentation, see the [API reference](https://reference.langchain.com/python/langchain/langchain/). For conceptual guides, tutorials, and examples on using LangChain, see the [LangChain Docs](https://docs.langchain.com/oss/python/langchain/overview).
|
|
30
30
|
|
|
31
31
|
## 📕 Releases & Versioning
|
|
32
32
|
|
|
@@ -63,6 +63,18 @@ if TYPE_CHECKING:
|
|
|
63
63
|
|
|
64
64
|
STRUCTURED_OUTPUT_ERROR_TEMPLATE = "Error: {error}\n Please fix your mistakes."
|
|
65
65
|
|
|
66
|
+
FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT = [
|
|
67
|
+
# if model profile data are not available, these models are assumed to support
|
|
68
|
+
# structured output
|
|
69
|
+
"grok",
|
|
70
|
+
"gpt-5",
|
|
71
|
+
"gpt-4.1",
|
|
72
|
+
"gpt-4o",
|
|
73
|
+
"gpt-oss",
|
|
74
|
+
"o3-pro",
|
|
75
|
+
"o3-mini",
|
|
76
|
+
]
|
|
77
|
+
|
|
66
78
|
|
|
67
79
|
def _normalize_to_model_response(result: ModelResponse | AIMessage) -> ModelResponse:
|
|
68
80
|
"""Normalize middleware return value to ModelResponse."""
|
|
@@ -349,11 +361,13 @@ def _get_can_jump_to(middleware: AgentMiddleware[Any, Any], hook_name: str) -> l
|
|
|
349
361
|
return []
|
|
350
362
|
|
|
351
363
|
|
|
352
|
-
def _supports_provider_strategy(model: str | BaseChatModel) -> bool:
|
|
364
|
+
def _supports_provider_strategy(model: str | BaseChatModel, tools: list | None = None) -> bool:
|
|
353
365
|
"""Check if a model supports provider-specific structured output.
|
|
354
366
|
|
|
355
367
|
Args:
|
|
356
368
|
model: Model name string or `BaseChatModel` instance.
|
|
369
|
+
tools: Optional list of tools provided to the agent. Needed because some models
|
|
370
|
+
don't support structured output together with tool calling.
|
|
357
371
|
|
|
358
372
|
Returns:
|
|
359
373
|
`True` if the model supports provider-specific structured output, `False` otherwise.
|
|
@@ -362,11 +376,23 @@ def _supports_provider_strategy(model: str | BaseChatModel) -> bool:
|
|
|
362
376
|
if isinstance(model, str):
|
|
363
377
|
model_name = model
|
|
364
378
|
elif isinstance(model, BaseChatModel):
|
|
365
|
-
model_name =
|
|
379
|
+
model_name = (
|
|
380
|
+
getattr(model, "model_name", None)
|
|
381
|
+
or getattr(model, "model", None)
|
|
382
|
+
or getattr(model, "model_id", "")
|
|
383
|
+
)
|
|
384
|
+
model_profile = model.profile
|
|
385
|
+
if (
|
|
386
|
+
model_profile is not None
|
|
387
|
+
and model_profile.get("structured_output")
|
|
388
|
+
# We make an exception for Gemini models, which currently do not support
|
|
389
|
+
# simultaneous tool use with structured output
|
|
390
|
+
and not (tools and isinstance(model_name, str) and "gemini" in model_name.lower())
|
|
391
|
+
):
|
|
392
|
+
return True
|
|
366
393
|
|
|
367
394
|
return (
|
|
368
|
-
|
|
369
|
-
or any(part in model_name for part in ["gpt-5", "gpt-4.1", "gpt-oss", "o3-pro", "o3-mini"])
|
|
395
|
+
any(part in model_name.lower() for part in FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT)
|
|
370
396
|
if model_name
|
|
371
397
|
else False
|
|
372
398
|
)
|
|
@@ -516,7 +542,7 @@ def create_agent( # noqa: PLR0915
|
|
|
516
542
|
model: str | BaseChatModel,
|
|
517
543
|
tools: Sequence[BaseTool | Callable | dict[str, Any]] | None = None,
|
|
518
544
|
*,
|
|
519
|
-
system_prompt: str | None = None,
|
|
545
|
+
system_prompt: str | SystemMessage | None = None,
|
|
520
546
|
middleware: Sequence[AgentMiddleware[StateT_co, ContextT]] = (),
|
|
521
547
|
response_format: ResponseFormat[ResponseT] | type[ResponseT] | None = None,
|
|
522
548
|
state_schema: type[AgentState[ResponseT]] | None = None,
|
|
@@ -562,9 +588,9 @@ def create_agent( # noqa: PLR0915
|
|
|
562
588
|
docs for more information.
|
|
563
589
|
system_prompt: An optional system prompt for the LLM.
|
|
564
590
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
beginning of the message list.
|
|
591
|
+
Can be a `str` (which will be converted to a `SystemMessage`) or a
|
|
592
|
+
`SystemMessage` instance directly. The system message is added to the
|
|
593
|
+
beginning of the message list when calling the model.
|
|
568
594
|
middleware: A sequence of middleware instances to apply to the agent.
|
|
569
595
|
|
|
570
596
|
Middleware can intercept and modify agent behavior at various stages.
|
|
@@ -659,6 +685,14 @@ def create_agent( # noqa: PLR0915
|
|
|
659
685
|
if isinstance(model, str):
|
|
660
686
|
model = init_chat_model(model)
|
|
661
687
|
|
|
688
|
+
# Convert system_prompt to SystemMessage if needed
|
|
689
|
+
system_message: SystemMessage | None = None
|
|
690
|
+
if system_prompt is not None:
|
|
691
|
+
if isinstance(system_prompt, SystemMessage):
|
|
692
|
+
system_message = system_prompt
|
|
693
|
+
else:
|
|
694
|
+
system_message = SystemMessage(content=system_prompt)
|
|
695
|
+
|
|
662
696
|
# Handle tools being None or empty
|
|
663
697
|
if tools is None:
|
|
664
698
|
tools = []
|
|
@@ -988,7 +1022,7 @@ def create_agent( # noqa: PLR0915
|
|
|
988
1022
|
effective_response_format: ResponseFormat | None
|
|
989
1023
|
if isinstance(request.response_format, AutoStrategy):
|
|
990
1024
|
# User provided raw schema via AutoStrategy - auto-detect best strategy based on model
|
|
991
|
-
if _supports_provider_strategy(request.model):
|
|
1025
|
+
if _supports_provider_strategy(request.model, tools=request.tools):
|
|
992
1026
|
# Model supports provider strategy - use it
|
|
993
1027
|
effective_response_format = ProviderStrategy(schema=request.response_format.schema)
|
|
994
1028
|
else:
|
|
@@ -1009,7 +1043,7 @@ def create_agent( # noqa: PLR0915
|
|
|
1009
1043
|
|
|
1010
1044
|
# Bind model based on effective response format
|
|
1011
1045
|
if isinstance(effective_response_format, ProviderStrategy):
|
|
1012
|
-
# Use
|
|
1046
|
+
# (Backward compatibility) Use OpenAI format structured output
|
|
1013
1047
|
kwargs = effective_response_format.to_model_kwargs()
|
|
1014
1048
|
return (
|
|
1015
1049
|
request.model.bind_tools(
|
|
@@ -1062,8 +1096,8 @@ def create_agent( # noqa: PLR0915
|
|
|
1062
1096
|
# Get the bound model (with auto-detection if needed)
|
|
1063
1097
|
model_, effective_response_format = _get_bound_model(request)
|
|
1064
1098
|
messages = request.messages
|
|
1065
|
-
if request.
|
|
1066
|
-
messages = [
|
|
1099
|
+
if request.system_message:
|
|
1100
|
+
messages = [request.system_message, *messages]
|
|
1067
1101
|
|
|
1068
1102
|
output = model_.invoke(messages)
|
|
1069
1103
|
|
|
@@ -1082,7 +1116,7 @@ def create_agent( # noqa: PLR0915
|
|
|
1082
1116
|
request = ModelRequest(
|
|
1083
1117
|
model=model,
|
|
1084
1118
|
tools=default_tools,
|
|
1085
|
-
|
|
1119
|
+
system_message=system_message,
|
|
1086
1120
|
response_format=initial_response_format,
|
|
1087
1121
|
messages=state["messages"],
|
|
1088
1122
|
tool_choice=None,
|
|
@@ -1115,8 +1149,8 @@ def create_agent( # noqa: PLR0915
|
|
|
1115
1149
|
# Get the bound model (with auto-detection if needed)
|
|
1116
1150
|
model_, effective_response_format = _get_bound_model(request)
|
|
1117
1151
|
messages = request.messages
|
|
1118
|
-
if request.
|
|
1119
|
-
messages = [
|
|
1152
|
+
if request.system_message:
|
|
1153
|
+
messages = [request.system_message, *messages]
|
|
1120
1154
|
|
|
1121
1155
|
output = await model_.ainvoke(messages)
|
|
1122
1156
|
|
|
@@ -1135,7 +1169,7 @@ def create_agent( # noqa: PLR0915
|
|
|
1135
1169
|
request = ModelRequest(
|
|
1136
1170
|
model=model,
|
|
1137
1171
|
tools=default_tools,
|
|
1138
|
-
|
|
1172
|
+
system_message=system_message,
|
|
1139
1173
|
response_format=initial_response_format,
|
|
1140
1174
|
messages=state["messages"],
|
|
1141
1175
|
tool_choice=None,
|
|
@@ -11,6 +11,7 @@ from .human_in_the_loop import (
|
|
|
11
11
|
)
|
|
12
12
|
from .model_call_limit import ModelCallLimitMiddleware
|
|
13
13
|
from .model_fallback import ModelFallbackMiddleware
|
|
14
|
+
from .model_retry import ModelRetryMiddleware
|
|
14
15
|
from .pii import PIIDetectionError, PIIMiddleware
|
|
15
16
|
from .shell_tool import (
|
|
16
17
|
CodexSandboxExecutionPolicy,
|
|
@@ -57,6 +58,7 @@ __all__ = [
|
|
|
57
58
|
"ModelFallbackMiddleware",
|
|
58
59
|
"ModelRequest",
|
|
59
60
|
"ModelResponse",
|
|
61
|
+
"ModelRetryMiddleware",
|
|
60
62
|
"PIIDetectionError",
|
|
61
63
|
"PIIMiddleware",
|
|
62
64
|
"RedactionRule",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Shared retry utilities for agent middleware.
|
|
2
|
+
|
|
3
|
+
This module contains common constants, utilities, and logic used by both
|
|
4
|
+
model and tool retry middleware implementations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import random
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
# Type aliases
|
|
14
|
+
RetryOn = tuple[type[Exception], ...] | Callable[[Exception], bool]
|
|
15
|
+
"""Type for specifying which exceptions to retry on.
|
|
16
|
+
|
|
17
|
+
Can be either:
|
|
18
|
+
- A tuple of exception types to retry on (based on `isinstance` checks)
|
|
19
|
+
- A callable that takes an exception and returns `True` if it should be retried
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
OnFailure = Literal["error", "continue"] | Callable[[Exception], str]
|
|
23
|
+
"""Type for specifying failure handling behavior.
|
|
24
|
+
|
|
25
|
+
Can be either:
|
|
26
|
+
- A literal action string (`'error'` or `'continue'`)
|
|
27
|
+
- `'error'`: Re-raise the exception, stopping agent execution.
|
|
28
|
+
- `'continue'`: Inject a message with the error details, allowing the agent to continue.
|
|
29
|
+
For tool retries, a `ToolMessage` with the error details will be injected.
|
|
30
|
+
For model retries, an `AIMessage` with the error details will be returned.
|
|
31
|
+
- A callable that takes an exception and returns a string for error message content
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def validate_retry_params(
|
|
36
|
+
max_retries: int,
|
|
37
|
+
initial_delay: float,
|
|
38
|
+
max_delay: float,
|
|
39
|
+
backoff_factor: float,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Validate retry parameters.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
max_retries: Maximum number of retry attempts.
|
|
45
|
+
initial_delay: Initial delay in seconds before first retry.
|
|
46
|
+
max_delay: Maximum delay in seconds between retries.
|
|
47
|
+
backoff_factor: Multiplier for exponential backoff.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If any parameter is invalid (negative values).
|
|
51
|
+
"""
|
|
52
|
+
if max_retries < 0:
|
|
53
|
+
msg = "max_retries must be >= 0"
|
|
54
|
+
raise ValueError(msg)
|
|
55
|
+
if initial_delay < 0:
|
|
56
|
+
msg = "initial_delay must be >= 0"
|
|
57
|
+
raise ValueError(msg)
|
|
58
|
+
if max_delay < 0:
|
|
59
|
+
msg = "max_delay must be >= 0"
|
|
60
|
+
raise ValueError(msg)
|
|
61
|
+
if backoff_factor < 0:
|
|
62
|
+
msg = "backoff_factor must be >= 0"
|
|
63
|
+
raise ValueError(msg)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def should_retry_exception(
|
|
67
|
+
exc: Exception,
|
|
68
|
+
retry_on: RetryOn,
|
|
69
|
+
) -> bool:
|
|
70
|
+
"""Check if an exception should trigger a retry.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
exc: The exception that occurred.
|
|
74
|
+
retry_on: Either a tuple of exception types to retry on, or a callable
|
|
75
|
+
that takes an exception and returns `True` if it should be retried.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
`True` if the exception should be retried, `False` otherwise.
|
|
79
|
+
"""
|
|
80
|
+
if callable(retry_on):
|
|
81
|
+
return retry_on(exc)
|
|
82
|
+
return isinstance(exc, retry_on)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def calculate_delay(
|
|
86
|
+
retry_number: int,
|
|
87
|
+
*,
|
|
88
|
+
backoff_factor: float,
|
|
89
|
+
initial_delay: float,
|
|
90
|
+
max_delay: float,
|
|
91
|
+
jitter: bool,
|
|
92
|
+
) -> float:
|
|
93
|
+
"""Calculate delay for a retry attempt with exponential backoff and optional jitter.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
retry_number: The retry attempt number (0-indexed).
|
|
97
|
+
backoff_factor: Multiplier for exponential backoff.
|
|
98
|
+
|
|
99
|
+
Set to `0.0` for constant delay.
|
|
100
|
+
initial_delay: Initial delay in seconds before first retry.
|
|
101
|
+
max_delay: Maximum delay in seconds between retries.
|
|
102
|
+
|
|
103
|
+
Caps exponential backoff growth.
|
|
104
|
+
jitter: Whether to add random jitter to delay to avoid thundering herd.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Delay in seconds before next retry.
|
|
108
|
+
"""
|
|
109
|
+
if backoff_factor == 0.0:
|
|
110
|
+
delay = initial_delay
|
|
111
|
+
else:
|
|
112
|
+
delay = initial_delay * (backoff_factor**retry_number)
|
|
113
|
+
|
|
114
|
+
# Cap at max_delay
|
|
115
|
+
delay = min(delay, max_delay)
|
|
116
|
+
|
|
117
|
+
if jitter and delay > 0:
|
|
118
|
+
jitter_amount = delay * 0.25 # ±25% jitter
|
|
119
|
+
delay = delay + random.uniform(-jitter_amount, jitter_amount) # noqa: S311
|
|
120
|
+
# Ensure delay is not negative after jitter
|
|
121
|
+
delay = max(0, delay)
|
|
122
|
+
|
|
123
|
+
return delay
|
|
@@ -10,6 +10,7 @@ chat model.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from collections.abc import Awaitable, Callable, Iterable, Sequence
|
|
13
|
+
from copy import deepcopy
|
|
13
14
|
from dataclasses import dataclass
|
|
14
15
|
from typing import Literal
|
|
15
16
|
|
|
@@ -17,7 +18,6 @@ from langchain_core.messages import (
|
|
|
17
18
|
AIMessage,
|
|
18
19
|
AnyMessage,
|
|
19
20
|
BaseMessage,
|
|
20
|
-
SystemMessage,
|
|
21
21
|
ToolMessage,
|
|
22
22
|
)
|
|
23
23
|
from langchain_core.messages.utils import count_tokens_approximately
|
|
@@ -229,19 +229,18 @@ class ContextEditingMiddleware(AgentMiddleware):
|
|
|
229
229
|
def count_tokens(messages: Sequence[BaseMessage]) -> int:
|
|
230
230
|
return count_tokens_approximately(messages)
|
|
231
231
|
else:
|
|
232
|
-
system_msg =
|
|
233
|
-
[SystemMessage(content=request.system_prompt)] if request.system_prompt else []
|
|
234
|
-
)
|
|
232
|
+
system_msg = [request.system_message] if request.system_message else []
|
|
235
233
|
|
|
236
234
|
def count_tokens(messages: Sequence[BaseMessage]) -> int:
|
|
237
235
|
return request.model.get_num_tokens_from_messages(
|
|
238
236
|
system_msg + list(messages), request.tools
|
|
239
237
|
)
|
|
240
238
|
|
|
239
|
+
edited_messages = deepcopy(list(request.messages))
|
|
241
240
|
for edit in self.edits:
|
|
242
|
-
edit.apply(
|
|
241
|
+
edit.apply(edited_messages, count_tokens=count_tokens)
|
|
243
242
|
|
|
244
|
-
return handler(request)
|
|
243
|
+
return handler(request.override(messages=edited_messages))
|
|
245
244
|
|
|
246
245
|
async def awrap_model_call(
|
|
247
246
|
self,
|
|
@@ -257,19 +256,18 @@ class ContextEditingMiddleware(AgentMiddleware):
|
|
|
257
256
|
def count_tokens(messages: Sequence[BaseMessage]) -> int:
|
|
258
257
|
return count_tokens_approximately(messages)
|
|
259
258
|
else:
|
|
260
|
-
system_msg =
|
|
261
|
-
[SystemMessage(content=request.system_prompt)] if request.system_prompt else []
|
|
262
|
-
)
|
|
259
|
+
system_msg = [request.system_message] if request.system_message else []
|
|
263
260
|
|
|
264
261
|
def count_tokens(messages: Sequence[BaseMessage]) -> int:
|
|
265
262
|
return request.model.get_num_tokens_from_messages(
|
|
266
263
|
system_msg + list(messages), request.tools
|
|
267
264
|
)
|
|
268
265
|
|
|
266
|
+
edited_messages = deepcopy(list(request.messages))
|
|
269
267
|
for edit in self.edits:
|
|
270
|
-
edit.apply(
|
|
268
|
+
edit.apply(edited_messages, count_tokens=count_tokens)
|
|
271
269
|
|
|
272
|
-
return await handler(request)
|
|
270
|
+
return await handler(request.override(messages=edited_messages))
|
|
273
271
|
|
|
274
272
|
|
|
275
273
|
__all__ = [
|
|
@@ -120,9 +120,9 @@ class FilesystemFileSearchMiddleware(AgentMiddleware):
|
|
|
120
120
|
|
|
121
121
|
Args:
|
|
122
122
|
root_path: Root directory to search.
|
|
123
|
-
use_ripgrep: Whether to use ripgrep for search.
|
|
123
|
+
use_ripgrep: Whether to use `ripgrep` for search.
|
|
124
124
|
|
|
125
|
-
Falls back to Python if ripgrep unavailable.
|
|
125
|
+
Falls back to Python if `ripgrep` unavailable.
|
|
126
126
|
max_file_size_mb: Maximum file size to search in MB.
|
|
127
127
|
"""
|
|
128
128
|
self.root_path = Path(root_path).resolve()
|
|
@@ -287,36 +287,23 @@ class HumanInTheLoopMiddleware(AgentMiddleware):
|
|
|
287
287
|
if not last_ai_msg or not last_ai_msg.tool_calls:
|
|
288
288
|
return None
|
|
289
289
|
|
|
290
|
-
#
|
|
291
|
-
interrupt_tool_calls: list[ToolCall] = []
|
|
292
|
-
auto_approved_tool_calls = []
|
|
293
|
-
|
|
294
|
-
for tool_call in last_ai_msg.tool_calls:
|
|
295
|
-
interrupt_tool_calls.append(tool_call) if tool_call[
|
|
296
|
-
"name"
|
|
297
|
-
] in self.interrupt_on else auto_approved_tool_calls.append(tool_call)
|
|
298
|
-
|
|
299
|
-
# If no interrupts needed, return early
|
|
300
|
-
if not interrupt_tool_calls:
|
|
301
|
-
return None
|
|
302
|
-
|
|
303
|
-
# Process all tool calls that require interrupts
|
|
304
|
-
revised_tool_calls: list[ToolCall] = auto_approved_tool_calls.copy()
|
|
305
|
-
artificial_tool_messages: list[ToolMessage] = []
|
|
306
|
-
|
|
307
|
-
# Create action requests and review configs for all tools that need approval
|
|
290
|
+
# Create action requests and review configs for tools that need approval
|
|
308
291
|
action_requests: list[ActionRequest] = []
|
|
309
292
|
review_configs: list[ReviewConfig] = []
|
|
293
|
+
interrupt_indices: list[int] = []
|
|
310
294
|
|
|
311
|
-
for tool_call in
|
|
312
|
-
config
|
|
295
|
+
for idx, tool_call in enumerate(last_ai_msg.tool_calls):
|
|
296
|
+
if (config := self.interrupt_on.get(tool_call["name"])) is not None:
|
|
297
|
+
action_request, review_config = self._create_action_and_config(
|
|
298
|
+
tool_call, config, state, runtime
|
|
299
|
+
)
|
|
300
|
+
action_requests.append(action_request)
|
|
301
|
+
review_configs.append(review_config)
|
|
302
|
+
interrupt_indices.append(idx)
|
|
313
303
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
action_requests.append(action_request)
|
|
319
|
-
review_configs.append(review_config)
|
|
304
|
+
# If no interrupts needed, return early
|
|
305
|
+
if not action_requests:
|
|
306
|
+
return None
|
|
320
307
|
|
|
321
308
|
# Create single HITLRequest with all actions and configs
|
|
322
309
|
hitl_request = HITLRequest(
|
|
@@ -325,29 +312,38 @@ class HumanInTheLoopMiddleware(AgentMiddleware):
|
|
|
325
312
|
)
|
|
326
313
|
|
|
327
314
|
# Send interrupt and get response
|
|
328
|
-
|
|
329
|
-
decisions = hitl_response["decisions"]
|
|
315
|
+
decisions = interrupt(hitl_request)["decisions"]
|
|
330
316
|
|
|
331
317
|
# Validate that the number of decisions matches the number of interrupt tool calls
|
|
332
|
-
if (decisions_len := len(decisions)) != (
|
|
333
|
-
interrupt_tool_calls_len := len(interrupt_tool_calls)
|
|
334
|
-
):
|
|
318
|
+
if (decisions_len := len(decisions)) != (interrupt_count := len(interrupt_indices)):
|
|
335
319
|
msg = (
|
|
336
320
|
f"Number of human decisions ({decisions_len}) does not match "
|
|
337
|
-
f"number of hanging tool calls ({
|
|
321
|
+
f"number of hanging tool calls ({interrupt_count})."
|
|
338
322
|
)
|
|
339
323
|
raise ValueError(msg)
|
|
340
324
|
|
|
341
|
-
# Process
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
325
|
+
# Process decisions and rebuild tool calls in original order
|
|
326
|
+
revised_tool_calls: list[ToolCall] = []
|
|
327
|
+
artificial_tool_messages: list[ToolMessage] = []
|
|
328
|
+
decision_idx = 0
|
|
329
|
+
|
|
330
|
+
for idx, tool_call in enumerate(last_ai_msg.tool_calls):
|
|
331
|
+
if idx in interrupt_indices:
|
|
332
|
+
# This was an interrupt tool call - process the decision
|
|
333
|
+
config = self.interrupt_on[tool_call["name"]]
|
|
334
|
+
decision = decisions[decision_idx]
|
|
335
|
+
decision_idx += 1
|
|
336
|
+
|
|
337
|
+
revised_tool_call, tool_message = self._process_decision(
|
|
338
|
+
decision, tool_call, config
|
|
339
|
+
)
|
|
340
|
+
if revised_tool_call is not None:
|
|
341
|
+
revised_tool_calls.append(revised_tool_call)
|
|
342
|
+
if tool_message:
|
|
343
|
+
artificial_tool_messages.append(tool_message)
|
|
344
|
+
else:
|
|
345
|
+
# This was auto-approved - keep original
|
|
346
|
+
revised_tool_calls.append(tool_call)
|
|
351
347
|
|
|
352
348
|
# Update the AI message to only include approved tool calls
|
|
353
349
|
last_ai_msg.tool_calls = revised_tool_calls
|
|
@@ -133,6 +133,7 @@ class ModelCallLimitMiddleware(AgentMiddleware[ModelCallLimitState, Any]):
|
|
|
133
133
|
|
|
134
134
|
`None` means no limit.
|
|
135
135
|
exit_behavior: What to do when limits are exceeded.
|
|
136
|
+
|
|
136
137
|
- `'end'`: Jump to the end of the agent execution and
|
|
137
138
|
inject an artificial AI message indicating that the limit was
|
|
138
139
|
exceeded.
|
|
@@ -92,9 +92,8 @@ class ModelFallbackMiddleware(AgentMiddleware):
|
|
|
92
92
|
|
|
93
93
|
# Try fallback models
|
|
94
94
|
for fallback_model in self.models:
|
|
95
|
-
request.model = fallback_model
|
|
96
95
|
try:
|
|
97
|
-
return handler(request)
|
|
96
|
+
return handler(request.override(model=fallback_model))
|
|
98
97
|
except Exception as e: # noqa: BLE001
|
|
99
98
|
last_exception = e
|
|
100
99
|
continue
|
|
@@ -127,9 +126,8 @@ class ModelFallbackMiddleware(AgentMiddleware):
|
|
|
127
126
|
|
|
128
127
|
# Try fallback models
|
|
129
128
|
for fallback_model in self.models:
|
|
130
|
-
request.model = fallback_model
|
|
131
129
|
try:
|
|
132
|
-
return await handler(request)
|
|
130
|
+
return await handler(request.override(model=fallback_model))
|
|
133
131
|
except Exception as e: # noqa: BLE001
|
|
134
132
|
last_exception = e
|
|
135
133
|
continue
|