langchain-dev-utils 1.4.0__tar.gz → 1.4.2__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.
Files changed (72) hide show
  1. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/.vscode/settings.json +2 -1
  2. langchain_dev_utils-1.4.2/AGENTS.md +173 -0
  3. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/PKG-INFO +2 -1
  4. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/pyproject.toml +16 -4
  5. langchain_dev_utils-1.4.2/src/langchain_dev_utils/__init__.py +1 -0
  6. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/_utils.py +41 -2
  7. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/__init__.py +5 -3
  8. langchain_dev_utils-1.4.2/src/langchain_dev_utils/agents/middleware/format_prompt.py +156 -0
  9. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/handoffs.py +18 -9
  10. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/model_router.py +9 -1
  11. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/tool_call_repair.py +3 -0
  12. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/wrap.py +1 -1
  13. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/adapters/openai_compatible.py +7 -3
  14. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_embedding.py +4 -4
  15. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_handoffs_middleware.py +1 -7
  16. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_load_embbeding.py +0 -1
  17. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/utils/register.py +0 -4
  18. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/uv.lock +79 -1
  19. langchain_dev_utils-1.4.0/src/langchain_dev_utils/__init__.py +0 -1
  20. langchain_dev_utils-1.4.0/src/langchain_dev_utils/agents/middleware/format_prompt.py +0 -66
  21. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/.gitignore +0 -0
  22. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/.python-version +0 -0
  23. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/LICENSE +0 -0
  24. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/README.md +0 -0
  25. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/README_cn.md +0 -0
  26. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/__init__.py +0 -0
  27. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/factory.py +0 -0
  28. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/model_fallback.py +0 -0
  29. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/plan.py +0 -0
  30. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/summarization.py +0 -0
  31. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/tool_emulator.py +0 -0
  32. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/agents/middleware/tool_selection.py +0 -0
  33. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/__init__.py +0 -0
  34. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/adapters/__init__.py +0 -0
  35. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/adapters/create_utils.py +0 -0
  36. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/adapters/register_profiles.py +0 -0
  37. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/base.py +0 -0
  38. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/chat_models/types.py +0 -0
  39. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/embeddings/__init__.py +0 -0
  40. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/embeddings/adapters/__init__.py +0 -0
  41. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/embeddings/adapters/create_utils.py +0 -0
  42. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/embeddings/adapters/openai_compatible.py +0 -0
  43. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/embeddings/base.py +0 -0
  44. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/graph/__init__.py +0 -0
  45. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/graph/parallel.py +0 -0
  46. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/graph/sequential.py +0 -0
  47. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/graph/types.py +0 -0
  48. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/message_convert/__init__.py +0 -0
  49. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/message_convert/content.py +0 -0
  50. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/message_convert/format.py +0 -0
  51. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/pipeline/__init__.py +0 -0
  52. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/pipeline/parallel.py +0 -0
  53. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/pipeline/sequential.py +0 -0
  54. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/pipeline/types.py +0 -0
  55. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/py.typed +0 -0
  56. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/tool_calling/__init__.py +0 -0
  57. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/tool_calling/human_in_the_loop.py +0 -0
  58. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/src/langchain_dev_utils/tool_calling/utils.py +0 -0
  59. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/__init__.py +0 -0
  60. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_agent.py +0 -0
  61. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_chat_models.py +0 -0
  62. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_graph.py +0 -0
  63. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_human_in_the_loop.py +0 -0
  64. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_load_model.py +0 -0
  65. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_messages.py +0 -0
  66. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_model_tool_emulator.py +0 -0
  67. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_plan_middleware.py +0 -0
  68. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_router_model.py +0 -0
  69. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_tool_call_repair.py +0 -0
  70. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_tool_calling.py +0 -0
  71. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/test_wrap_agent.py +0 -0
  72. {langchain_dev_utils-1.4.0 → langchain_dev_utils-1.4.2}/tests/utils/__init__.py +0 -0
@@ -3,5 +3,6 @@
3
3
  "python.testing.unittestEnabled": false,
4
4
  "python.testing.pytestEnabled": true,
5
5
  "search.useIgnoreFiles": true,
6
- "python.analysis.typeCheckingMode": "off"
6
+ "python.analysis.typeCheckingMode": "off",
7
+ "python.languageServer": "None"
7
8
  }
@@ -0,0 +1,173 @@
1
+ # Agentic Coding Guidelines for langchain-dev-utils
2
+
3
+ This file provides guidelines for AI agents working in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ A Python utility library for LangChain and LangGraph development. Uses hatchling build system, uv for package management, ruff for linting, and pytest for testing.
8
+
9
+ ---
10
+
11
+ ## Build/Lint/Test Commands
12
+
13
+ ### Package Management (uv)
14
+ ```bash
15
+ # Install dependencies
16
+ uv sync
17
+
18
+ # Install with optional dependencies
19
+ uv sync --extra standard
20
+
21
+ # Install dev dependencies
22
+ uv sync --group dev
23
+
24
+ # Install test dependencies
25
+ uv sync --group tests
26
+ ```
27
+
28
+ ### Linting & Formatting (ruff)
29
+ ```bash
30
+ # Check linting
31
+ uv run ruff check .
32
+
33
+ # Check specific file
34
+ uv run ruff check src/langchain_dev_utils/path/to/file.py
35
+
36
+ # Fix auto-fixable issues
37
+ uv run ruff check --fix .
38
+
39
+ # Format code
40
+ uv run ruff format .
41
+
42
+ # Format specific file
43
+ uv run ruff format src/langchain_dev_utils/path/to/file.py
44
+ ```
45
+
46
+ ### Testing (pytest)
47
+ ```bash
48
+ # Run all tests
49
+ uv run pytest
50
+
51
+ # Run with verbose output
52
+ uv run pytest -v
53
+
54
+ # Run specific test file
55
+ uv run pytest tests/test_agent.py
56
+
57
+ # Run specific test function
58
+ uv run pytest tests/test_agent.py::test_prebuilt_agent
59
+
60
+ # Run specific test class
61
+ uv run pytest tests/test_chat_models.py::TestImageProcessing
62
+
63
+ # Run with asyncio support (automatically configured)
64
+ uv run pytest -s
65
+
66
+ # Run tests matching pattern
67
+ uv run pytest -k "test_load"
68
+ ```
69
+
70
+ ### Build
71
+ ```bash
72
+ # Build package
73
+ uv build
74
+
75
+ # Build wheel only
76
+ uv build --wheel
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Code Style Guidelines
82
+
83
+ ### Imports
84
+ - Use absolute imports for external packages
85
+ - Use relative imports within the package (e.g., `from ..chat_models import ...`)
86
+ - Group imports: stdlib → third-party → local
87
+ - Ruff handles import sorting automatically
88
+
89
+ ### Type Hints
90
+ - Use type hints for all function parameters and return types
91
+ - Use `Optional[Type]` or `Type | None` for nullable types (both acceptable)
92
+ - Use `Any` sparingly and only when necessary
93
+ - Use `Sequence`, `Mapping` for generic collections
94
+ - Use generics with TypeVars where appropriate
95
+
96
+ ### Naming Conventions
97
+ - `snake_case` for functions, methods, variables
98
+ - `PascalCase` for classes
99
+ - `UPPER_CASE` for constants
100
+ - `_leading_underscore` for private/internal functions
101
+ - Leading double underscore for name mangling when needed
102
+
103
+ ### Docstrings
104
+ - Use Google-style docstrings
105
+ - Include Args, Returns, Raises sections for public functions
106
+ - Include Examples section for complex functions
107
+ - Keep docstrings under 100 characters per line when possible
108
+
109
+ ### Code Structure
110
+ - Maximum line length: 88 characters (ruff enforces, E501 ignored)
111
+ - Use trailing commas in multi-line structures
112
+ - Two blank lines between top-level functions/classes
113
+ - One blank line between methods
114
+
115
+ ### Error Handling
116
+ - Use specific exception types, not bare `except:`
117
+ - Provide descriptive error messages with f-strings
118
+ - Use `raise ValueError(msg)` pattern with descriptive messages
119
+ - Avoid bare `raise` statements
120
+
121
+ ### Async Code
122
+ - Use `pytest.mark.asyncio` for async test functions
123
+ - Use `async`/`await` consistently
124
+ - Prefer `asyncio` primitives from standard library
125
+
126
+ ### Ruff Configuration
127
+ - Enabled rules: E, F, I, PGH003, T201
128
+ - Import sorting (I) is enforced
129
+ - No print statements in production code (T201)
130
+
131
+ ---
132
+
133
+ ## Testing Guidelines
134
+
135
+ - Tests live in `tests/` directory
136
+ - Test files named `test_*.py`
137
+ - Test functions named `test_*`
138
+ - Use pytest fixtures for setup/teardown
139
+ - Use `pytest.mark.asyncio` for async tests
140
+ - Mock external API calls in unit tests
141
+ - Integration tests use actual APIs (marked implicitly by test names)
142
+ - Tests use `langchain-tests` for standard integration test suites
143
+
144
+ ---
145
+
146
+ ## Project Structure
147
+
148
+ ```
149
+ langchain-dev-utils/
150
+ ├── src/langchain_dev_utils/ # Source code
151
+ │ ├── agents/ # Agent utilities
152
+ │ ├── chat_models/ # Chat model utilities
153
+ │ ├── embeddings/ # Embedding utilities
154
+ │ ├── graph/ # Graph utilities
155
+ │ ├── message_convert/ # Message conversion
156
+ │ ├── pipeline/ # Pipeline utilities (deprecated since v1.4.0, will be removed in v1.5.0)
157
+ │ └── tool_calling/ # Tool calling utilities
158
+ ├── tests/ # Test files
159
+ ├── docs/ # Documentation
160
+ ├── pyproject.toml # Project configuration
161
+ └── uv.lock # Dependency lock file
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Dependencies
167
+
168
+ - Core: langchain, langchain-core, langgraph
169
+ - Optional: jinja2, json-repair, langchain-openai
170
+ - Dev: ruff, dashscope, langchain-model-profiles
171
+ - Test: python-dotenv, langchain-tests, langchain-deepseek, langchain-qwq, langchain-ollama, langchain-community
172
+
173
+ Python version: >=3.11
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-dev-utils
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: A practical utility library for LangChain and LangGraph development
5
5
  Project-URL: Source Code, https://github.com/TBice123123/langchain-dev-utils
6
6
  Project-URL: repository, https://github.com/TBice123123/langchain-dev-utils
@@ -12,6 +12,7 @@ Requires-Dist: langchain-core>=1.2.5
12
12
  Requires-Dist: langchain>=1.2.0
13
13
  Requires-Dist: langgraph>=1.0.0
14
14
  Provides-Extra: standard
15
+ Requires-Dist: jinja2>=3.1.6; extra == 'standard'
15
16
  Requires-Dist: json-repair>=0.53.1; extra == 'standard'
16
17
  Requires-Dist: langchain-openai; extra == 'standard'
17
18
  Description-Content-Type: text/markdown
@@ -1,11 +1,15 @@
1
1
  [project]
2
2
  name = "langchain-dev-utils"
3
- version = "1.4.0"
3
+ version = "1.4.2"
4
4
  description = "A practical utility library for LangChain and LangGraph development"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "tiebingice", email = "tiebingice123@outlook.com" }]
7
7
  requires-python = ">=3.11"
8
- dependencies = ["langchain>=1.2.0", "langchain-core>=1.2.5", "langgraph>=1.0.0"]
8
+ dependencies = [
9
+ "langchain>=1.2.0",
10
+ "langchain-core>=1.2.5",
11
+ "langgraph>=1.0.0",
12
+ ]
9
13
 
10
14
  [project.urls]
11
15
  "Source Code" = "https://github.com/TBice123123/langchain-dev-utils"
@@ -14,7 +18,11 @@ documentation = "https://tbice123123.github.io/langchain-dev-utils"
14
18
 
15
19
 
16
20
  [project.optional-dependencies]
17
- standard = ["json-repair>=0.53.1", "langchain-openai"]
21
+ standard = [
22
+ "jinja2>=3.1.6",
23
+ "json-repair>=0.53.1",
24
+ "langchain-openai",
25
+ ]
18
26
 
19
27
  [build-system]
20
28
  requires = ["hatchling"]
@@ -30,7 +38,11 @@ python_files = ["test_*.py"]
30
38
  python_functions = ["test_*"]
31
39
 
32
40
  [dependency-groups]
33
- dev = ["langchain-model-profiles>=0.0.5", "ruff>=0.14.5"]
41
+ dev = [
42
+ "dashscope>=1.25.9",
43
+ "langchain-model-profiles>=0.0.5",
44
+ "ruff>=0.14.5",
45
+ ]
34
46
  docs = [
35
47
  "jupyter>=1.1.1",
36
48
  "mkdocs-material>=9.7.0",
@@ -0,0 +1 @@
1
+ __version__ = "1.4.2"
@@ -1,11 +1,48 @@
1
1
  from importlib import util
2
2
  from typing import Literal, Optional, cast
3
3
 
4
+ from langchain_core.tools import BaseTool
4
5
  from langgraph.graph import StateGraph
5
6
  from langgraph.graph.state import StateNode
6
7
  from pydantic import BaseModel
7
8
 
8
9
 
10
+ def _duplicate_tools(tools: list[BaseTool]) -> list[BaseTool]:
11
+ """Duplicate tools with the same name.
12
+
13
+ Args:
14
+ tools (list[BaseTool]): The list of tools.
15
+
16
+ Returns:
17
+ list[BaseTool]: The duplicated tools.
18
+ """
19
+ tool_name_set = set()
20
+ duplicated_tools = []
21
+ for tool_obj in tools:
22
+ if tool_obj.name not in tool_name_set:
23
+ duplicated_tools.append(tool_obj)
24
+ tool_name_set.add(tool_obj.name)
25
+ return duplicated_tools
26
+
27
+
28
+ def _merge_tools(tools: list[BaseTool]) -> list[BaseTool]:
29
+ """Merge tools with the same name.
30
+
31
+ Args:
32
+ tools (list[BaseTool]): The list of tools.
33
+
34
+ Returns:
35
+ list[BaseTool]: The merged tools.
36
+ """
37
+ tool_name_set = set()
38
+ merged_tools = []
39
+ for tool_obj in tools:
40
+ if tool_obj.name not in tool_name_set:
41
+ merged_tools.append(tool_obj)
42
+ tool_name_set.add(tool_obj.name)
43
+ return merged_tools
44
+
45
+
9
46
  def _transform_node_to_tuple(
10
47
  node: StateNode | tuple[str, StateNode],
11
48
  ) -> tuple[str, StateNode]:
@@ -23,13 +60,15 @@ def _transform_node_to_tuple(
23
60
 
24
61
 
25
62
  def _check_pkg_install(
26
- pkg: Literal["langchain_openai", "json_repair"],
63
+ pkg: Literal["langchain_openai", "json_repair", "jinja2"],
27
64
  ) -> None:
28
65
  if not util.find_spec(pkg):
29
66
  if pkg == "langchain_openai":
30
67
  msg = "Please install langchain_dev_utils[standard],when use 'openai-compatible'"
31
- else:
68
+ elif pkg == "json_repair":
32
69
  msg = "Please install langchain_dev_utils[standard] to use ToolCallRepairMiddleware."
70
+ else:
71
+ msg = "Please install langchain_dev_utils[standard] to use FormatPromptMiddleware."
33
72
  raise ImportError(msg)
34
73
 
35
74
 
@@ -1,10 +1,10 @@
1
- from .format_prompt import format_prompt
1
+ from .format_prompt import FormatPromptMiddleware, format_prompt
2
2
  from .handoffs import HandoffAgentMiddleware
3
3
  from .model_fallback import ModelFallbackMiddleware
4
4
  from .model_router import ModelRouterMiddleware
5
5
  from .plan import PlanMiddleware
6
6
  from .summarization import SummarizationMiddleware
7
- from .tool_call_repair import ToolCallRepairMiddleware
7
+ from .tool_call_repair import ToolCallRepairMiddleware, tool_call_repair
8
8
  from .tool_emulator import LLMToolEmulator
9
9
  from .tool_selection import LLMToolSelectorMiddleware
10
10
 
@@ -16,6 +16,8 @@ __all__ = [
16
16
  "LLMToolEmulator",
17
17
  "ModelRouterMiddleware",
18
18
  "ToolCallRepairMiddleware",
19
- "format_prompt",
19
+ "FormatPromptMiddleware",
20
20
  "HandoffAgentMiddleware",
21
+ "tool_call_repair",
22
+ "format_prompt",
21
23
  ]
@@ -0,0 +1,156 @@
1
+ from typing import Awaitable, Callable, Literal
2
+
3
+ from langchain.agents.middleware import ModelRequest
4
+ from langchain.agents.middleware.types import (
5
+ AgentMiddleware,
6
+ ModelCallResult,
7
+ ModelResponse,
8
+ )
9
+ from langchain_core.messages import SystemMessage
10
+ from langchain_core.prompts.string import get_template_variables
11
+
12
+ from langchain_dev_utils._utils import _check_pkg_install
13
+
14
+
15
+ class FormatPromptMiddleware(AgentMiddleware):
16
+ """Format the system prompt with variables from state and context.
17
+
18
+ This middleware function extracts template variables from the system prompt
19
+ and populates them with values from the agent's state and runtime context.
20
+ Variables are first resolved from the state, then from the context if not found.
21
+
22
+ Args:
23
+ template_format: The format of the template. Defaults to "f-string".
24
+
25
+ Example:
26
+
27
+ # Use format_prompt middleware instance rather than FormatPromptMiddleware class (Recommended)
28
+
29
+ ```python
30
+ from langchain_dev_utils.agents.middleware import format_prompt
31
+ from langchain.agents import create_agent
32
+ from langchain_core.messages import HumanMessage
33
+ from dataclasses import dataclass
34
+
35
+ @dataclass
36
+ class Context:
37
+ name: str
38
+ user: str
39
+
40
+ agent = create_agent(
41
+ model=model,
42
+ tools=tools,
43
+ system_prompt="You are a helpful assistant. Your name is {name}. Your user is {user}.",
44
+ middleware=[format_prompt],
45
+ context_schema=Context,
46
+ )
47
+ agent.invoke(
48
+ {
49
+ "messages": [HumanMessage(content="Hello")],
50
+ },
51
+ context=Context(name="assistant", user="Tom"),
52
+ )
53
+ ```
54
+
55
+ # Use FormatPromptMiddleware class(Use when template_format is jinja2)
56
+
57
+ ```python
58
+ from langchain_dev_utils.agents.middleware import FormatPromptMiddleware
59
+ from langchain.agents import create_agent
60
+ from langchain_core.messages import HumanMessage
61
+ from dataclasses import dataclass
62
+
63
+ @dataclass
64
+ class Context:
65
+ name: str
66
+ user: str
67
+
68
+ agent = create_agent(
69
+ model=model,
70
+ tools=tools,
71
+ system_prompt="You are a helpful assistant. Your name is {{ name }}. Your user is {{ user }}.",
72
+ middleware=[FormatPromptMiddleware(template_format="jinja2")],
73
+ context_schema=Context,
74
+ )
75
+ agent.invoke(
76
+ {
77
+ "messages": [HumanMessage(content="Hello")],
78
+ },
79
+ context=Context(name="assistant", user="Tom"),
80
+ )
81
+ ```
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ *,
87
+ template_format: Literal["f-string", "jinja2"] = "f-string",
88
+ ) -> None:
89
+ super().__init__()
90
+
91
+ self.template_format = template_format
92
+
93
+ if template_format == "jinja2":
94
+ _check_pkg_install("jinja2")
95
+
96
+ def _format_prompt(self, request: ModelRequest) -> str:
97
+ """Add the plan system prompt to the system message."""
98
+ system_msg = request.system_message
99
+ if system_msg is None:
100
+ raise ValueError(
101
+ "system_message must be provided,while use format_prompt in middleware."
102
+ )
103
+
104
+ system_prompt = "\n".join(
105
+ [content.get("text", "") for content in system_msg.content_blocks]
106
+ )
107
+ variables = get_template_variables(system_prompt, self.template_format)
108
+
109
+ format_params = {}
110
+
111
+ state = request.state
112
+ for key in variables:
113
+ if var := state.get(key, None):
114
+ format_params[key] = var
115
+
116
+ other_var_keys = set(variables) - set(format_params.keys())
117
+
118
+ if other_var_keys:
119
+ context = request.runtime.context
120
+ if context is not None:
121
+ for key in other_var_keys:
122
+ if var := getattr(context, key, None):
123
+ format_params[key] = var
124
+
125
+ if self.template_format == "jinja2":
126
+ from jinja2 import Template
127
+
128
+ template = Template(system_prompt)
129
+ return template.render(**format_params)
130
+ else:
131
+ return system_prompt.format(**format_params)
132
+
133
+ def wrap_model_call(
134
+ self,
135
+ request: ModelRequest,
136
+ handler: Callable[[ModelRequest], ModelResponse],
137
+ ) -> ModelCallResult:
138
+ """Update the system prompt with variables from state and context."""
139
+ prompt = self._format_prompt(request)
140
+ request = request.override(system_message=SystemMessage(content=prompt))
141
+ return handler(request)
142
+
143
+ async def awrap_model_call(
144
+ self,
145
+ request: ModelRequest,
146
+ handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
147
+ ) -> ModelCallResult:
148
+ """Update the system prompt with variables from state and context."""
149
+ prompt = self._format_prompt(request)
150
+ override_request = request.override(
151
+ system_message=SystemMessage(content=prompt)
152
+ )
153
+ return await handler(override_request)
154
+
155
+
156
+ format_prompt = FormatPromptMiddleware()
@@ -1,4 +1,4 @@
1
- from typing import Any, Awaitable, Callable, Literal, cast
1
+ from typing import Awaitable, Callable, Literal, cast
2
2
 
3
3
  from langchain.agents import AgentState
4
4
  from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
@@ -9,6 +9,7 @@ from langchain_core.messages import SystemMessage, ToolMessage
9
9
  from langgraph.types import Command
10
10
  from typing_extensions import NotRequired, Optional, TypedDict
11
11
 
12
+ from langchain_dev_utils._utils import _duplicate_tools, _merge_tools
12
13
  from langchain_dev_utils.chat_models import load_chat_model
13
14
 
14
15
 
@@ -19,7 +20,7 @@ class MultiAgentState(AgentState):
19
20
  class AgentConfig(TypedDict):
20
21
  model: NotRequired[str | BaseChatModel]
21
22
  prompt: str | SystemMessage
22
- tools: NotRequired[list[BaseTool | dict[str, Any]]]
23
+ tools: NotRequired[list[BaseTool]]
23
24
  default: NotRequired[bool]
24
25
  handoffs: list[str] | Literal["all"]
25
26
 
@@ -176,7 +177,16 @@ class HandoffAgentMiddleware(AgentMiddleware):
176
177
  agents_config,
177
178
  handoffs_tools,
178
179
  )
179
- self.tools = handoffs_tools
180
+
181
+ all_tools = [
182
+ *handoffs_tools,
183
+ ]
184
+
185
+ for agent_name in agents_config:
186
+ tools = agents_config.get(agent_name, {}).get("tools", [])
187
+ all_tools.extend(tools)
188
+
189
+ self.tools = _merge_tools(all_tools)
180
190
 
181
191
  def _get_override_request(self, request: ModelRequest) -> ModelRequest:
182
192
  active_agent_name = request.state.get("active_agent", self.default_agent_name)
@@ -184,15 +194,14 @@ class HandoffAgentMiddleware(AgentMiddleware):
184
194
  _config = self.agents_config[active_agent_name]
185
195
 
186
196
  params = {}
187
- if _config.get("model"):
188
- model = _config.get("model")
197
+ if model := _config.get("model"):
189
198
  if isinstance(model, str):
190
199
  model = load_chat_model(model)
191
200
  params["model"] = model
192
- if _config.get("prompt"):
193
- params["system_prompt"] = _config.get("prompt")
194
- if _config.get("tools"):
195
- params["tools"] = _config.get("tools")
201
+ if prompt := _config.get("prompt"):
202
+ params["system_prompt"] = prompt
203
+ if tools := _config.get("tools"):
204
+ params["tools"] = _duplicate_tools(tools)
196
205
 
197
206
  if params:
198
207
  return request.override(**params)
@@ -10,6 +10,7 @@ from langgraph.runtime import Runtime
10
10
  from pydantic import BaseModel, Field
11
11
  from typing_extensions import TypedDict
12
12
 
13
+ from langchain_dev_utils._utils import _duplicate_tools, _merge_tools
13
14
  from langchain_dev_utils.chat_models import load_chat_model
14
15
  from langchain_dev_utils.message_convert import format_sequence
15
16
 
@@ -120,6 +121,13 @@ class ModelRouterMiddleware(AgentMiddleware):
120
121
 
121
122
  self.router_prompt = router_prompt
122
123
 
124
+ all_tools = []
125
+ for model in model_list:
126
+ if tools := model.get("tools"):
127
+ all_tools.extend(tools)
128
+
129
+ self.tools = _merge_tools(all_tools)
130
+
123
131
  def _select_model(self, messages: list[AnyMessage]):
124
132
  response = cast(
125
133
  SelectModel,
@@ -174,7 +182,7 @@ class ModelRouterMiddleware(AgentMiddleware):
174
182
  model = load_chat_model(select_model_name)
175
183
  override_kwargs["model"] = model
176
184
  if model_values["tools"] is not None:
177
- override_kwargs["tools"] = model_values["tools"]
185
+ override_kwargs["tools"] = _duplicate_tools(model_values["tools"])
178
186
  if model_values["system_prompt"] is not None:
179
187
  override_kwargs["system_message"] = SystemMessage(
180
188
  content=model_values["system_prompt"]
@@ -94,3 +94,6 @@ class ToolCallRepairMiddleware(AgentMiddleware):
94
94
  result=results,
95
95
  structured_response=response.structured_response,
96
96
  )
97
+
98
+
99
+ tool_call_repair = ToolCallRepairMiddleware()
@@ -17,7 +17,7 @@ def _process_input(request: str, runtime: ToolRuntime) -> str:
17
17
  def _process_output(
18
18
  request: str, response: dict[str, Any], runtime: ToolRuntime
19
19
  ) -> Any:
20
- return response["messages"][-1].content
20
+ return response["messages"][-1].text
21
21
 
22
22
 
23
23
  def get_subagent_name(runtime: ToolRuntime) -> str:
@@ -261,6 +261,8 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
261
261
  payload_messages.append(_convert_message_to_dict(m))
262
262
 
263
263
  payload["messages"] = payload_messages
264
+ if "tools" in payload and len(payload["tools"]) == 0:
265
+ payload.pop("tools")
264
266
  return payload
265
267
 
266
268
  @model_validator(mode="after")
@@ -302,6 +304,8 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
302
304
  ModelProfile,
303
305
  _get_profile_by_provider_and_model(self._provider, self.model_name),
304
306
  )
307
+ if "json_schema" in self.supported_response_format:
308
+ self.profile.update({"structured_output": True})
305
309
  return self
306
310
 
307
311
  def _create_chat_result(
@@ -347,9 +351,9 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
347
351
  if isinstance(model_extra, dict) and (
348
352
  reasoning := model_extra.get("reasoning")
349
353
  ):
350
- rtn.generations[0].message.additional_kwargs["reasoning_content"] = (
351
- reasoning
352
- )
354
+ rtn.generations[0].message.additional_kwargs[
355
+ "reasoning_content"
356
+ ] = reasoning
353
357
 
354
358
  return rtn
355
359
 
@@ -5,8 +5,8 @@ from langchain_tests.integration_tests.embeddings import EmbeddingsIntegrationTe
5
5
 
6
6
  from langchain_dev_utils.embeddings.adapters import create_openai_compatible_embedding
7
7
 
8
- SiliconFlowEmbeddings = create_openai_compatible_embedding(
9
- "siliconflow", embedding_model_cls_name="SiliconFlowEmbeddings"
8
+ ZAIEmbeddings = create_openai_compatible_embedding(
9
+ "zai", embedding_model_cls_name="ZAIEmbeddings"
10
10
  )
11
11
 
12
12
 
@@ -14,9 +14,9 @@ class TestStandard(EmbeddingsIntegrationTests):
14
14
  @property
15
15
  def embeddings_class(self) -> type[Embeddings]:
16
16
  """Embeddings class."""
17
- return cast("type[Embeddings]", SiliconFlowEmbeddings)
17
+ return cast("type[Embeddings]", ZAIEmbeddings)
18
18
 
19
19
  @property
20
20
  def embedding_model_params(self) -> dict[str, Any]:
21
21
  """Embeddings model parameters."""
22
- return {"model": "BAAI/bge-m3"}
22
+ return {"model": "embedding-3"}
@@ -6,9 +6,7 @@ from langgraph.checkpoint.memory import InMemorySaver
6
6
  from langgraph.types import Command
7
7
 
8
8
  from langchain_dev_utils.agents import create_agent
9
- from langchain_dev_utils.agents.middleware import (
10
- HandoffAgentMiddleware,
11
- )
9
+ from langchain_dev_utils.agents.middleware import HandoffAgentMiddleware
12
10
  from langchain_dev_utils.agents.middleware.handoffs import AgentConfig
13
11
  from langchain_dev_utils.chat_models import load_chat_model
14
12
 
@@ -91,10 +89,6 @@ def test_handoffs_middleware():
91
89
  handoffs_tool_overrides=handoffs_tool_map,
92
90
  )
93
91
  ],
94
- tools=[
95
- get_current_time,
96
- run_code,
97
- ],
98
92
  checkpointer=InMemorySaver(),
99
93
  )
100
94
 
@@ -7,7 +7,6 @@ from langchain_dev_utils.embeddings import load_embeddings
7
7
  @pytest.fixture(
8
8
  params=[
9
9
  "dashscope:text-embedding-v4",
10
- "siliconflow:BAAI/bge-m3",
11
10
  "ollama:bge-m3:latest",
12
11
  ]
13
12
  )
@@ -27,10 +27,6 @@ def register_all_model_providers():
27
27
  def register_all_embeddings_providers():
28
28
  batch_register_embeddings_provider(
29
29
  [
30
- {
31
- "provider_name": "siliconflow",
32
- "embeddings_model": "openai-compatible",
33
- },
34
30
  {"provider_name": "dashscope", "embeddings_model": DashScopeEmbeddings},
35
31
  ]
36
32
  )
@@ -483,6 +483,80 @@ wheels = [
483
483
  { url = "https://mirrors.ustc.edu.cn/pypi/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" },
484
484
  ]
485
485
 
486
+ [[package]]
487
+ name = "cryptography"
488
+ version = "46.0.4"
489
+ source = { registry = "https://pypi.mirrors.ustc.edu.cn/simple/" }
490
+ dependencies = [
491
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
492
+ ]
493
+ sdist = { url = "https://mirrors.ustc.edu.cn/pypi/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" }
494
+ wheels = [
495
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" },
496
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" },
497
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" },
498
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" },
499
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" },
500
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" },
501
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" },
502
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" },
503
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" },
504
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" },
505
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" },
506
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" },
507
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" },
508
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" },
509
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" },
510
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" },
511
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" },
512
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" },
513
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" },
514
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" },
515
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" },
516
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" },
517
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" },
518
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" },
519
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" },
520
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" },
521
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" },
522
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" },
523
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" },
524
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" },
525
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" },
526
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" },
527
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" },
528
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" },
529
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" },
530
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" },
531
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" },
532
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" },
533
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" },
534
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" },
535
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" },
536
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" },
537
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" },
538
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" },
539
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" },
540
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" },
541
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" },
542
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" },
543
+ ]
544
+
545
+ [[package]]
546
+ name = "dashscope"
547
+ version = "1.25.9"
548
+ source = { registry = "https://pypi.mirrors.ustc.edu.cn/simple/" }
549
+ dependencies = [
550
+ { name = "aiohttp" },
551
+ { name = "certifi" },
552
+ { name = "cryptography" },
553
+ { name = "requests" },
554
+ { name = "websocket-client" },
555
+ ]
556
+ wheels = [
557
+ { url = "https://mirrors.ustc.edu.cn/pypi/packages/03/bf/503587663b909427c1906b3b75fc2982bf9e42161d8b687f6e38ad12d042/dashscope-1.25.9-py3-none-any.whl", hash = "sha256:03b587bcb58a2f0a76fa5102925c16609b50af176198af0aeb0fd85aa44d6cfe", size = 1335755, upload-time = "2026-01-21T06:58:14.496Z" },
558
+ ]
559
+
486
560
  [[package]]
487
561
  name = "dataclasses-json"
488
562
  version = "0.6.7"
@@ -1365,7 +1439,7 @@ wheels = [
1365
1439
 
1366
1440
  [[package]]
1367
1441
  name = "langchain-dev-utils"
1368
- version = "1.3.7"
1442
+ version = "1.4.1"
1369
1443
  source = { editable = "." }
1370
1444
  dependencies = [
1371
1445
  { name = "langchain" },
@@ -1375,12 +1449,14 @@ dependencies = [
1375
1449
 
1376
1450
  [package.optional-dependencies]
1377
1451
  standard = [
1452
+ { name = "jinja2" },
1378
1453
  { name = "json-repair" },
1379
1454
  { name = "langchain-openai" },
1380
1455
  ]
1381
1456
 
1382
1457
  [package.dev-dependencies]
1383
1458
  dev = [
1459
+ { name = "dashscope" },
1384
1460
  { name = "langchain-model-profiles" },
1385
1461
  { name = "ruff" },
1386
1462
  ]
@@ -1400,6 +1476,7 @@ tests = [
1400
1476
 
1401
1477
  [package.metadata]
1402
1478
  requires-dist = [
1479
+ { name = "jinja2", marker = "extra == 'standard'", specifier = ">=3.1.6" },
1403
1480
  { name = "json-repair", marker = "extra == 'standard'", specifier = ">=0.53.1" },
1404
1481
  { name = "langchain", specifier = ">=1.2.0" },
1405
1482
  { name = "langchain-core", specifier = ">=1.2.5" },
@@ -1410,6 +1487,7 @@ provides-extras = ["standard"]
1410
1487
 
1411
1488
  [package.metadata.requires-dev]
1412
1489
  dev = [
1490
+ { name = "dashscope", specifier = ">=1.25.9" },
1413
1491
  { name = "langchain-model-profiles", specifier = ">=0.0.5" },
1414
1492
  { name = "ruff", specifier = ">=0.14.5" },
1415
1493
  ]
@@ -1 +0,0 @@
1
- __version__ = "1.4.0"
@@ -1,66 +0,0 @@
1
- from langchain.agents.middleware import ModelRequest, dynamic_prompt
2
- from langchain_core.prompts.string import get_template_variables
3
-
4
-
5
- @dynamic_prompt
6
- def format_prompt(request: ModelRequest) -> str:
7
- """Format the system prompt with variables from state and context.
8
-
9
- This middleware function extracts template variables from the system prompt
10
- and populates them with values from the agent's state and runtime context.
11
- Variables are first resolved from the state, then from the context if not found.
12
-
13
- Example:
14
- >>> from langchain_dev_utils.agents.middleware import format_prompt
15
- >>> from langchain.agents import create_agent
16
- >>> from langchain_core.messages import HumanMessage
17
- >>> from dataclasses import dataclass
18
- >>>
19
- >>> @dataclass
20
- ... class Context:
21
- ... name: str
22
- ... user: str
23
- >>>
24
- >>> agent=create_agent(
25
- ... model=model,
26
- ... tools=tools,
27
- ... system_prompt="You are a helpful assistant. Your name is {name}. Your user is {user}.",
28
- ... middleware=[format_prompt],
29
- ... context_schema=Context,
30
- ... )
31
- >>> agent.invoke(
32
- ... {
33
- ... "messages": [HumanMessage(content="Hello")],
34
- ... },
35
- ... context=Context(name="assistant", user="Tom"),
36
- ... )
37
-
38
- """
39
- system_msg = request.system_message
40
- if system_msg is None:
41
- raise ValueError(
42
- "system_message must be provided,while use format_prompt in middleware."
43
- )
44
-
45
- system_prompt = "\n".join(
46
- [content.get("text", "") for content in system_msg.content_blocks]
47
- )
48
- variables = get_template_variables(system_prompt, "f-string")
49
-
50
- format_params = {}
51
-
52
- state = request.state
53
- for key in variables:
54
- if var := state.get(key, None):
55
- format_params[key] = var
56
-
57
- other_var_keys = set(variables) - set(format_params.keys())
58
-
59
- if other_var_keys:
60
- context = request.runtime.context
61
- if context is not None:
62
- for key in other_var_keys:
63
- if var := getattr(context, key, None):
64
- format_params[key] = var
65
-
66
- return system_prompt.format(**format_params)