langchain-dev-utils 1.3.5__tar.gz → 1.3.7__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_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/PKG-INFO +1 -1
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/pyproject.toml +1 -1
- langchain_dev_utils-1.3.7/src/langchain_dev_utils/__init__.py +1 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/_utils.py +6 -1
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/handoffs.py +30 -20
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/model_router.py +9 -12
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/plan.py +13 -18
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/wrap.py +99 -49
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_handoffs_middleware.py +51 -4
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_load_model.py +0 -11
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_wrap_agent.py +12 -8
- langchain_dev_utils-1.3.5/src/langchain_dev_utils/__init__.py +0 -1
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/.gitignore +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/.python-version +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/.vscode/settings.json +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/LICENSE +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/README.md +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/README_cn.md +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/factory.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/file_system.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/format_prompt.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/model_fallback.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/summarization.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/tool_call_repair.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/tool_emulator.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/middleware/tool_selection.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/plan.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/adapters/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/adapters/create_utils.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/adapters/openai_compatible.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/adapters/register_profiles.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/base.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/types.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/embeddings/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/embeddings/adapters/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/embeddings/adapters/create_utils.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/embeddings/adapters/openai_compatible.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/embeddings/base.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/message_convert/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/message_convert/content.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/message_convert/format.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/parallel.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/sequential.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/types.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/py.typed +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/tool_calling/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/tool_calling/human_in_the_loop.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/tool_calling/utils.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_agent.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_chat_models.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_embedding.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_human_in_the_loop.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_load_embbeding.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_messages.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_model_tool_emulator.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_pipline.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_plan_middleware.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_router_model.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_tool_call_repair.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/test_tool_calling.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/utils/__init__.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/tests/utils/register.py +0 -0
- {langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-dev-utils
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.7
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.7"
|
|
@@ -97,6 +97,11 @@ def _validate_model_cls_name(model_cls_name: str) -> None:
|
|
|
97
97
|
f"model_cls_name should start with an uppercase letter (PEP 8). Received: {model_cls_name}"
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
+
if len(model_cls_name) > 30:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"model_cls_name must be 30 characters or fewer. Received: {model_cls_name}"
|
|
103
|
+
)
|
|
104
|
+
|
|
100
105
|
|
|
101
106
|
def _validate_provider_name(provider_name: str) -> None:
|
|
102
107
|
"""Validate provider name follows Python naming conventions.
|
|
@@ -112,7 +117,7 @@ def _validate_provider_name(provider_name: str) -> None:
|
|
|
112
117
|
|
|
113
118
|
if not provider_name[0].isalnum():
|
|
114
119
|
raise ValueError(
|
|
115
|
-
f"provider_name must start with a letter. Received: {provider_name}"
|
|
120
|
+
f"provider_name must start with a letter or number. Received: {provider_name}"
|
|
116
121
|
)
|
|
117
122
|
|
|
118
123
|
if not all(c.isalnum() or c == "_" for c in provider_name):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, Awaitable, Callable, Literal
|
|
1
|
+
from typing import Any, Awaitable, Callable, Literal, cast
|
|
2
2
|
|
|
3
3
|
from langchain.agents import AgentState
|
|
4
4
|
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
|
|
@@ -128,6 +128,7 @@ class HandoffAgentMiddleware(AgentMiddleware):
|
|
|
128
128
|
Args:
|
|
129
129
|
agents_config (dict[str, AgentConfig]): A dictionary of agent configurations.
|
|
130
130
|
custom_handoffs_tool_descriptions (Optional[dict[str, str]]): A dictionary of custom tool descriptions for handoffs tools. Defaults to None.
|
|
131
|
+
handoffs_tool_overrides (Optional[dict[str, BaseTool]]): A dictionary of handoffs tools to override. Defaults to None.
|
|
131
132
|
|
|
132
133
|
Examples:
|
|
133
134
|
```python
|
|
@@ -142,6 +143,7 @@ class HandoffAgentMiddleware(AgentMiddleware):
|
|
|
142
143
|
self,
|
|
143
144
|
agents_config: dict[str, AgentConfig],
|
|
144
145
|
custom_handoffs_tool_descriptions: Optional[dict[str, str]] = None,
|
|
146
|
+
handoffs_tool_overrides: Optional[dict[str, BaseTool]] = None,
|
|
145
147
|
) -> None:
|
|
146
148
|
default_agent_name = _get_default_active_agent(agents_config)
|
|
147
149
|
if default_agent_name is None:
|
|
@@ -152,13 +154,23 @@ class HandoffAgentMiddleware(AgentMiddleware):
|
|
|
152
154
|
if custom_handoffs_tool_descriptions is None:
|
|
153
155
|
custom_handoffs_tool_descriptions = {}
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
if handoffs_tool_overrides is None:
|
|
158
|
+
handoffs_tool_overrides = {}
|
|
159
|
+
|
|
160
|
+
handoffs_tools = []
|
|
161
|
+
for agent_name in agents_config.keys():
|
|
162
|
+
if not handoffs_tool_overrides.get(agent_name):
|
|
163
|
+
handoffs_tools.append(
|
|
164
|
+
_create_handoffs_tool(
|
|
165
|
+
agent_name,
|
|
166
|
+
custom_handoffs_tool_descriptions.get(agent_name),
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
handoffs_tools.append(
|
|
171
|
+
cast(BaseTool, handoffs_tool_overrides.get(agent_name))
|
|
172
|
+
)
|
|
173
|
+
|
|
162
174
|
self.default_agent_name = default_agent_name
|
|
163
175
|
self.agents_config = _transform_agent_config(
|
|
164
176
|
agents_config,
|
|
@@ -166,7 +178,7 @@ class HandoffAgentMiddleware(AgentMiddleware):
|
|
|
166
178
|
)
|
|
167
179
|
self.tools = handoffs_tools
|
|
168
180
|
|
|
169
|
-
def
|
|
181
|
+
def _get_override_request(self, request: ModelRequest) -> ModelRequest:
|
|
170
182
|
active_agent_name = request.state.get("active_agent", self.default_agent_name)
|
|
171
183
|
|
|
172
184
|
_config = self.agents_config[active_agent_name]
|
|
@@ -181,24 +193,22 @@ class HandoffAgentMiddleware(AgentMiddleware):
|
|
|
181
193
|
params["system_prompt"] = _config.get("prompt")
|
|
182
194
|
if _config.get("tools"):
|
|
183
195
|
params["tools"] = _config.get("tools")
|
|
184
|
-
|
|
196
|
+
|
|
197
|
+
if params:
|
|
198
|
+
return request.override(**params)
|
|
199
|
+
else:
|
|
200
|
+
return request
|
|
185
201
|
|
|
186
202
|
def wrap_model_call(
|
|
187
203
|
self, request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]
|
|
188
204
|
) -> ModelCallResult:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return handler(request.override(**override_kwargs))
|
|
192
|
-
else:
|
|
193
|
-
return handler(request)
|
|
205
|
+
override_request = self._get_override_request(request)
|
|
206
|
+
return handler(override_request)
|
|
194
207
|
|
|
195
208
|
async def awrap_model_call(
|
|
196
209
|
self,
|
|
197
210
|
request: ModelRequest,
|
|
198
211
|
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
199
212
|
) -> ModelCallResult:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return await handler(request.override(**override_kwargs))
|
|
203
|
-
else:
|
|
204
|
-
return await handler(request)
|
|
213
|
+
override_request = self._get_override_request(request)
|
|
214
|
+
return await handler(override_request)
|
|
@@ -150,7 +150,7 @@ class ModelRouterMiddleware(AgentMiddleware):
|
|
|
150
150
|
model_name = await self._aselect_model(state["messages"])
|
|
151
151
|
return {"router_model_selection": model_name}
|
|
152
152
|
|
|
153
|
-
def
|
|
153
|
+
def _get_override_request(self, request: ModelRequest) -> ModelRequest:
|
|
154
154
|
model_dict = {
|
|
155
155
|
item["model_name"]: {
|
|
156
156
|
"tools": item.get("tools", None),
|
|
@@ -180,24 +180,21 @@ class ModelRouterMiddleware(AgentMiddleware):
|
|
|
180
180
|
content=model_values["system_prompt"]
|
|
181
181
|
)
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
if override_kwargs:
|
|
184
|
+
return request.override(**override_kwargs)
|
|
185
|
+
else:
|
|
186
|
+
return request
|
|
184
187
|
|
|
185
188
|
def wrap_model_call(
|
|
186
189
|
self, request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]
|
|
187
190
|
) -> ModelCallResult:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return handler(request.override(**override_kwargs))
|
|
191
|
-
else:
|
|
192
|
-
return handler(request)
|
|
191
|
+
override_request = self._get_override_request(request)
|
|
192
|
+
return handler(override_request)
|
|
193
193
|
|
|
194
194
|
async def awrap_model_call(
|
|
195
195
|
self,
|
|
196
196
|
request: ModelRequest,
|
|
197
197
|
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
198
198
|
) -> ModelCallResult:
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return await handler(request.override(**override_kwargs))
|
|
202
|
-
else:
|
|
203
|
-
return await handler(request)
|
|
199
|
+
override_request = self._get_override_request(request)
|
|
200
|
+
return await handler(override_request)
|
|
@@ -335,12 +335,8 @@ class PlanMiddleware(AgentMiddleware):
|
|
|
335
335
|
self.system_prompt = system_prompt
|
|
336
336
|
self.tools = tools
|
|
337
337
|
|
|
338
|
-
def
|
|
339
|
-
|
|
340
|
-
request: ModelRequest,
|
|
341
|
-
handler: Callable[[ModelRequest], ModelResponse],
|
|
342
|
-
) -> ModelCallResult:
|
|
343
|
-
"""Update the system message to include the plan system prompt."""
|
|
338
|
+
def _get_override_request(self, request: ModelRequest) -> ModelRequest:
|
|
339
|
+
"""Add the plan system prompt to the system message."""
|
|
344
340
|
if request.system_message is not None:
|
|
345
341
|
new_system_content = [
|
|
346
342
|
*request.system_message.content_blocks,
|
|
@@ -351,7 +347,15 @@ class PlanMiddleware(AgentMiddleware):
|
|
|
351
347
|
new_system_message = SystemMessage(
|
|
352
348
|
content=cast("list[str | dict[str, str]]", new_system_content)
|
|
353
349
|
)
|
|
354
|
-
return
|
|
350
|
+
return request.override(system_message=new_system_message)
|
|
351
|
+
|
|
352
|
+
def wrap_model_call(
|
|
353
|
+
self,
|
|
354
|
+
request: ModelRequest,
|
|
355
|
+
handler: Callable[[ModelRequest], ModelResponse],
|
|
356
|
+
) -> ModelCallResult:
|
|
357
|
+
override_request = self._get_override_request(request)
|
|
358
|
+
return handler(override_request)
|
|
355
359
|
|
|
356
360
|
async def awrap_model_call(
|
|
357
361
|
self,
|
|
@@ -359,14 +363,5 @@ class PlanMiddleware(AgentMiddleware):
|
|
|
359
363
|
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
360
364
|
) -> ModelCallResult:
|
|
361
365
|
"""Update the system message to include the plan system prompt."""
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
*request.system_message.content_blocks,
|
|
365
|
-
{"type": "text", "text": f"\n\n{self.system_prompt}"},
|
|
366
|
-
]
|
|
367
|
-
else:
|
|
368
|
-
new_system_content = [{"type": "text", "text": self.system_prompt}]
|
|
369
|
-
new_system_message = SystemMessage(
|
|
370
|
-
content=cast("list[str | dict[str, str]]", new_system_content)
|
|
371
|
-
)
|
|
372
|
-
return await handler(request.override(system_message=new_system_message))
|
|
366
|
+
override_request = self._get_override_request(request)
|
|
367
|
+
return await handler(override_request)
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/wrap.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Any, Awaitable, Callable, Optional
|
|
2
|
+
from typing import Any, Awaitable, Callable, Optional
|
|
3
3
|
|
|
4
4
|
from langchain.tools import ToolRuntime
|
|
5
|
-
from langchain_core.messages import
|
|
5
|
+
from langchain_core.messages import HumanMessage
|
|
6
6
|
from langchain_core.tools import BaseTool, StructuredTool
|
|
7
7
|
from langgraph.graph.state import CompiledStateGraph
|
|
8
8
|
|
|
@@ -14,9 +14,9 @@ def _process_input(request: str, runtime: ToolRuntime) -> str:
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _process_output(
|
|
17
|
-
request: str, response:
|
|
17
|
+
request: str, response: dict[str, Any], runtime: ToolRuntime
|
|
18
18
|
) -> Any:
|
|
19
|
-
return response[-1].content
|
|
19
|
+
return response["messages"][-1].content
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def wrap_agent_as_tool(
|
|
@@ -25,17 +25,17 @@ def wrap_agent_as_tool(
|
|
|
25
25
|
tool_description: Optional[str] = None,
|
|
26
26
|
pre_input_hooks: Optional[
|
|
27
27
|
tuple[
|
|
28
|
-
Callable[[str, ToolRuntime], str],
|
|
29
|
-
Callable[[str, ToolRuntime], Awaitable[str]],
|
|
28
|
+
Callable[[str, ToolRuntime], str | dict[str, Any]],
|
|
29
|
+
Callable[[str, ToolRuntime], Awaitable[str | dict[str, Any]]],
|
|
30
30
|
]
|
|
31
|
-
| Callable[[str, ToolRuntime], str]
|
|
31
|
+
| Callable[[str, ToolRuntime], str | dict[str, Any]]
|
|
32
32
|
] = None,
|
|
33
33
|
post_output_hooks: Optional[
|
|
34
34
|
tuple[
|
|
35
|
-
Callable[[str,
|
|
36
|
-
Callable[[str,
|
|
35
|
+
Callable[[str, dict[str, Any], ToolRuntime], Any],
|
|
36
|
+
Callable[[str, dict[str, Any], ToolRuntime], Awaitable[Any]],
|
|
37
37
|
]
|
|
38
|
-
| Callable[[str,
|
|
38
|
+
| Callable[[str, dict[str, Any], ToolRuntime], Any]
|
|
39
39
|
] = None,
|
|
40
40
|
) -> BaseTool:
|
|
41
41
|
"""Wraps an agent as a tool
|
|
@@ -91,33 +91,58 @@ def wrap_agent_as_tool(
|
|
|
91
91
|
def call_agent(
|
|
92
92
|
request: str,
|
|
93
93
|
runtime: ToolRuntime,
|
|
94
|
-
)
|
|
95
|
-
|
|
94
|
+
):
|
|
95
|
+
_processed_input = process_input(request, runtime) if process_input else request
|
|
96
|
+
if isinstance(_processed_input, str):
|
|
97
|
+
agent_input = {"messages": [HumanMessage(content=_processed_input)]}
|
|
98
|
+
elif isinstance(_processed_input, dict):
|
|
99
|
+
if "messages" not in _processed_input:
|
|
100
|
+
raise ValueError("Agent input must contain 'messages' key")
|
|
101
|
+
agent_input = _processed_input
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError("Pre Hooks must return a string or a dict")
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
response = agent.invoke({"messages": messages})
|
|
105
|
+
response = agent.invoke(agent_input)
|
|
99
106
|
|
|
100
|
-
response =
|
|
107
|
+
response = (
|
|
108
|
+
process_output(request, response, runtime)
|
|
109
|
+
if process_output
|
|
110
|
+
else response["messages"][-1].content
|
|
111
|
+
)
|
|
101
112
|
return response
|
|
102
113
|
|
|
103
114
|
async def acall_agent(
|
|
104
115
|
request: str,
|
|
105
116
|
runtime: ToolRuntime,
|
|
106
|
-
)
|
|
117
|
+
):
|
|
107
118
|
if asyncio.iscoroutinefunction(process_input_async):
|
|
108
|
-
|
|
119
|
+
_processed_input = await process_input_async(request, runtime)
|
|
120
|
+
else:
|
|
121
|
+
_processed_input = (
|
|
122
|
+
process_input_async(request, runtime)
|
|
123
|
+
if process_input_async
|
|
124
|
+
else request
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if isinstance(_processed_input, str):
|
|
128
|
+
agent_input = {"messages": [HumanMessage(content=_processed_input)]}
|
|
129
|
+
elif isinstance(_processed_input, dict):
|
|
130
|
+
if "messages" not in _processed_input:
|
|
131
|
+
raise ValueError("Agent input must contain 'messages' key")
|
|
132
|
+
agent_input = _processed_input
|
|
109
133
|
else:
|
|
110
|
-
|
|
134
|
+
raise ValueError("Pre Hooks must return a string or a dict")
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
response = await agent.ainvoke({"messages": messages})
|
|
136
|
+
response = await agent.ainvoke(agent_input)
|
|
114
137
|
|
|
115
138
|
if asyncio.iscoroutinefunction(process_output_async):
|
|
116
|
-
response = await process_output_async(
|
|
117
|
-
request, response["messages"], runtime
|
|
118
|
-
)
|
|
139
|
+
response = await process_output_async(request, response, runtime)
|
|
119
140
|
else:
|
|
120
|
-
response =
|
|
141
|
+
response = (
|
|
142
|
+
process_output(request, response, runtime)
|
|
143
|
+
if process_output
|
|
144
|
+
else response["messages"][-1].content
|
|
145
|
+
)
|
|
121
146
|
|
|
122
147
|
return response
|
|
123
148
|
|
|
@@ -143,17 +168,17 @@ def wrap_all_agents_as_tool(
|
|
|
143
168
|
tool_description: Optional[str] = None,
|
|
144
169
|
pre_input_hooks: Optional[
|
|
145
170
|
tuple[
|
|
146
|
-
Callable[[str, ToolRuntime], str],
|
|
147
|
-
Callable[[str, ToolRuntime], Awaitable[str]],
|
|
171
|
+
Callable[[str, ToolRuntime], str | dict[str, Any]],
|
|
172
|
+
Callable[[str, ToolRuntime], Awaitable[str | dict[str, Any]]],
|
|
148
173
|
]
|
|
149
|
-
| Callable[[str, ToolRuntime], str]
|
|
174
|
+
| Callable[[str, ToolRuntime], str | dict[str, Any]]
|
|
150
175
|
] = None,
|
|
151
176
|
post_output_hooks: Optional[
|
|
152
177
|
tuple[
|
|
153
|
-
Callable[[str,
|
|
154
|
-
Callable[[str,
|
|
178
|
+
Callable[[str, dict[str, Any], ToolRuntime], Any],
|
|
179
|
+
Callable[[str, dict[str, Any], ToolRuntime], Awaitable[Any]],
|
|
155
180
|
]
|
|
156
|
-
| Callable[[str,
|
|
181
|
+
| Callable[[str, dict[str, Any], ToolRuntime], Any]
|
|
157
182
|
] = None,
|
|
158
183
|
) -> BaseTool:
|
|
159
184
|
"""Wraps all agents as single tool
|
|
@@ -219,42 +244,67 @@ def wrap_all_agents_as_tool(
|
|
|
219
244
|
agent_name: str,
|
|
220
245
|
description: str,
|
|
221
246
|
runtime: ToolRuntime,
|
|
222
|
-
)
|
|
223
|
-
task_description = (
|
|
224
|
-
process_input(description, runtime) if process_input else description
|
|
225
|
-
)
|
|
226
|
-
|
|
247
|
+
):
|
|
227
248
|
if agent_name not in agents_map:
|
|
228
249
|
raise ValueError(f"Agent {agent_name} not found")
|
|
229
250
|
|
|
230
|
-
|
|
231
|
-
|
|
251
|
+
_processed_input = (
|
|
252
|
+
process_input(description, runtime) if process_input else description
|
|
253
|
+
)
|
|
254
|
+
if isinstance(_processed_input, str):
|
|
255
|
+
agent_input = {"messages": [HumanMessage(content=_processed_input)]}
|
|
256
|
+
elif isinstance(_processed_input, dict):
|
|
257
|
+
if "messages" not in _processed_input:
|
|
258
|
+
raise ValueError("Agent input must contain 'messages' key")
|
|
259
|
+
agent_input = _processed_input
|
|
260
|
+
else:
|
|
261
|
+
raise ValueError("Pre Hooks must return str or dict")
|
|
262
|
+
|
|
263
|
+
response = agent.invoke(agent_input)
|
|
232
264
|
|
|
233
|
-
response =
|
|
265
|
+
response = (
|
|
266
|
+
process_output(description, response, runtime)
|
|
267
|
+
if process_output
|
|
268
|
+
else response["messages"][-1].content
|
|
269
|
+
)
|
|
234
270
|
return response
|
|
235
271
|
|
|
236
272
|
async def acall_agent(
|
|
237
273
|
agent_name: str,
|
|
238
274
|
description: str,
|
|
239
275
|
runtime: ToolRuntime,
|
|
240
|
-
)
|
|
276
|
+
):
|
|
277
|
+
if agent_name not in agents_map:
|
|
278
|
+
raise ValueError(f"Agent {agent_name} not found")
|
|
279
|
+
|
|
241
280
|
if asyncio.iscoroutinefunction(process_input_async):
|
|
242
|
-
|
|
281
|
+
_processed_input = await process_input_async(description, runtime)
|
|
243
282
|
else:
|
|
244
|
-
|
|
283
|
+
_processed_input = (
|
|
284
|
+
process_input_async(description, runtime)
|
|
285
|
+
if process_input_async
|
|
286
|
+
else description
|
|
287
|
+
)
|
|
245
288
|
|
|
246
|
-
if
|
|
247
|
-
|
|
289
|
+
if isinstance(_processed_input, str):
|
|
290
|
+
agent_input = {"messages": [HumanMessage(content=_processed_input)]}
|
|
291
|
+
elif isinstance(_processed_input, dict):
|
|
292
|
+
if "messages" not in _processed_input:
|
|
293
|
+
raise ValueError("Agent input must contain 'messages' key")
|
|
294
|
+
agent_input = _processed_input
|
|
295
|
+
else:
|
|
296
|
+
raise ValueError("Pre Hooks must return str or dict")
|
|
248
297
|
|
|
249
|
-
|
|
250
|
-
response = await agents_map[agent_name].ainvoke({"messages": messages})
|
|
298
|
+
response = await agents_map[agent_name].ainvoke(agent_input)
|
|
251
299
|
|
|
252
300
|
if asyncio.iscoroutinefunction(process_output_async):
|
|
253
|
-
response = await process_output_async(
|
|
254
|
-
task_description, response["messages"], runtime
|
|
255
|
-
)
|
|
301
|
+
response = await process_output_async(description, response, runtime)
|
|
256
302
|
else:
|
|
257
|
-
response =
|
|
303
|
+
response = (
|
|
304
|
+
process_output(description, response, runtime)
|
|
305
|
+
if process_output
|
|
306
|
+
else response["messages"][-1].content
|
|
307
|
+
)
|
|
258
308
|
|
|
259
309
|
return response
|
|
260
310
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
|
|
3
|
-
from langchain.tools import tool
|
|
3
|
+
from langchain.tools import BaseTool, ToolRuntime, tool
|
|
4
4
|
from langchain_core.messages import HumanMessage, ToolMessage
|
|
5
5
|
from langgraph.checkpoint.memory import InMemorySaver
|
|
6
|
+
from langgraph.types import Command
|
|
6
7
|
|
|
7
8
|
from langchain_dev_utils.agents import create_agent
|
|
8
9
|
from langchain_dev_utils.agents.middleware import (
|
|
@@ -24,6 +25,22 @@ def run_code(code: str) -> str:
|
|
|
24
25
|
return "Running code successfully"
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
@tool
|
|
29
|
+
def transfer_to_coding_agent(runtime: ToolRuntime) -> Command:
|
|
30
|
+
"""This tool help you transfer to the coding agent."""
|
|
31
|
+
return Command(
|
|
32
|
+
update={
|
|
33
|
+
"messages": [
|
|
34
|
+
ToolMessage(
|
|
35
|
+
content="Successfully transferred to coding agent",
|
|
36
|
+
tool_call_id=runtime.tool_call_id,
|
|
37
|
+
)
|
|
38
|
+
],
|
|
39
|
+
"active_agent": "code_agent",
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
27
44
|
agents_config: dict[str, AgentConfig] = {
|
|
28
45
|
"time_agent": {
|
|
29
46
|
"model": "zai:glm-4.5",
|
|
@@ -56,14 +73,24 @@ agents_config: dict[str, AgentConfig] = {
|
|
|
56
73
|
custom_tool_descriptions: dict[str, str] = {
|
|
57
74
|
"time_agent": "transfer to the time agent to answer time-related questions",
|
|
58
75
|
"talk_agent": "transfer to the talk agent to answer user questions",
|
|
59
|
-
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
handoffs_tool_map: dict[str, BaseTool] = {
|
|
80
|
+
"code_agent": transfer_to_coding_agent,
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
|
|
63
84
|
def test_handoffs_middleware():
|
|
64
85
|
agent = create_agent(
|
|
65
86
|
model="dashscope:qwen3-max",
|
|
66
|
-
middleware=[
|
|
87
|
+
middleware=[
|
|
88
|
+
HandoffAgentMiddleware(
|
|
89
|
+
agents_config=agents_config,
|
|
90
|
+
custom_handoffs_tool_descriptions=custom_tool_descriptions,
|
|
91
|
+
handoffs_tool_overrides=handoffs_tool_map,
|
|
92
|
+
)
|
|
93
|
+
],
|
|
67
94
|
tools=[
|
|
68
95
|
get_current_time,
|
|
69
96
|
run_code,
|
|
@@ -95,13 +122,26 @@ def test_handoffs_middleware():
|
|
|
95
122
|
== "qwen3-coder-plus"
|
|
96
123
|
)
|
|
97
124
|
assert isinstance(response["messages"][-2], ToolMessage)
|
|
125
|
+
assert any(
|
|
126
|
+
message
|
|
127
|
+
for message in response["messages"]
|
|
128
|
+
if isinstance(message, ToolMessage)
|
|
129
|
+
and "Successfully transferred to coding agent" in message.content
|
|
130
|
+
and message.name == "transfer_to_coding_agent"
|
|
131
|
+
)
|
|
98
132
|
assert "active_agent" in response and response["active_agent"] == "code_agent"
|
|
99
133
|
|
|
100
134
|
|
|
101
135
|
async def test_handoffs_middleware_async():
|
|
102
136
|
agent = create_agent(
|
|
103
137
|
model="dashscope:qwen3-max",
|
|
104
|
-
middleware=[
|
|
138
|
+
middleware=[
|
|
139
|
+
HandoffAgentMiddleware(
|
|
140
|
+
agents_config=agents_config,
|
|
141
|
+
custom_handoffs_tool_descriptions=custom_tool_descriptions,
|
|
142
|
+
handoffs_tool_overrides=handoffs_tool_map,
|
|
143
|
+
)
|
|
144
|
+
],
|
|
105
145
|
tools=[
|
|
106
146
|
get_current_time,
|
|
107
147
|
run_code,
|
|
@@ -133,4 +173,11 @@ async def test_handoffs_middleware_async():
|
|
|
133
173
|
== "qwen3-coder-plus"
|
|
134
174
|
)
|
|
135
175
|
assert isinstance(response["messages"][-2], ToolMessage)
|
|
176
|
+
assert any(
|
|
177
|
+
message
|
|
178
|
+
for message in response["messages"]
|
|
179
|
+
if isinstance(message, ToolMessage)
|
|
180
|
+
and "Successfully transferred to coding agent" in message.content
|
|
181
|
+
and message.name == "transfer_to_coding_agent"
|
|
182
|
+
)
|
|
136
183
|
assert "active_agent" in response and response["active_agent"] == "code_agent"
|
|
@@ -76,17 +76,6 @@ async def test_model_tool_calling_async(
|
|
|
76
76
|
assert hasattr(response, "tool_calls") and len(response.tool_calls) == 1
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def test_model_with_reasoning(reasoning_model: BaseChatModel):
|
|
80
|
-
response = reasoning_model.invoke("hello?")
|
|
81
|
-
assert response.additional_kwargs.get("reasoning_content")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@pytest.mark.asyncio
|
|
85
|
-
async def test_model_with_reasoning_async(reasoning_model: BaseChatModel):
|
|
86
|
-
response = await reasoning_model.ainvoke("hello?")
|
|
87
|
-
assert response.additional_kwargs.get("reasoning_content")
|
|
88
|
-
|
|
89
|
-
|
|
90
79
|
def test_model_profile():
|
|
91
80
|
model = load_chat_model("dashscope:qwen-flash")
|
|
92
81
|
assert model.profile == ALI_PROFILES["qwen-flash"]
|
|
@@ -31,18 +31,22 @@ async def process_input_async(request: str, runtime: ToolRuntime) -> str:
|
|
|
31
31
|
return "<task_description>" + request + "</task_description>"
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def process_output(request: str,
|
|
35
|
-
|
|
36
|
-
assert
|
|
37
|
-
|
|
34
|
+
def process_output(request: str, response: dict[str, Any], runtime: ToolRuntime) -> str:
|
|
35
|
+
human_message = response["messages"][0]
|
|
36
|
+
assert human_message.content.startswith(
|
|
37
|
+
"<task_description>"
|
|
38
|
+
) and human_message.content.endswith("</task_description>")
|
|
39
|
+
return "<task_response>" + response["messages"][-1].content + "</task_response>"
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
async def process_output_async(
|
|
41
|
-
request: str,
|
|
43
|
+
request: str, response: dict[str, Any], runtime: ToolRuntime
|
|
42
44
|
) -> str:
|
|
43
|
-
|
|
44
|
-
assert
|
|
45
|
-
|
|
45
|
+
human_message = response["messages"][0]
|
|
46
|
+
assert human_message.content.startswith(
|
|
47
|
+
"<task_description>"
|
|
48
|
+
) and human_message.content.endswith("</task_description>")
|
|
49
|
+
return "<task_response>" + response["messages"][-1].content + "</task_response>"
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def test_wrap_agent():
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.3.5"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/__init__.py
RENAMED
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/agents/plan.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/base.py
RENAMED
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/chat_models/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/embeddings/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/__init__.py
RENAMED
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/parallel.py
RENAMED
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.3.5 → langchain_dev_utils-1.3.7}/src/langchain_dev_utils/pipeline/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|