langchain-dev-utils 1.1.10__tar.gz → 1.1.12__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.1.10 → langchain_dev_utils-1.1.12}/PKG-INFO +3 -3
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/README.md +2 -2
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/pyproject.toml +1 -1
- langchain_dev_utils-1.1.12/src/langchain_dev_utils/__init__.py +1 -0
- langchain_dev_utils-1.1.12/src/langchain_dev_utils/agents/middleware/plan.py +360 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/wrap.py +1 -1
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/adapters/openai_compatible.py +5 -2
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/base.py +4 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/types.py +1 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/message_convert/content.py +49 -45
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/tool_calling/utils.py +19 -2
- langchain_dev_utils-1.1.10/src/langchain_dev_utils/__init__.py +0 -1
- langchain_dev_utils-1.1.10/src/langchain_dev_utils/agents/middleware/plan.py +0 -458
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/.gitignore +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/.python-version +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/.vscode/settings.json +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/LICENSE +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/README_cn.md +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/factory.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/file_system.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/model_fallback.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/model_router.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/summarization.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/tool_emulator.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/tool_selection.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/plan.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/adapters/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/embeddings/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/embeddings/base.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/message_convert/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/message_convert/format.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/parallel.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/sequential.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/types.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/py.typed +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/tool_calling/__init__.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/tool_calling/human_in_the_loop.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_agent.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_chat_models.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_human_in_the_loop.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_load_embbeding.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_load_model.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_messages.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_model_tool_emulator.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_pipline.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_plan_middleware.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_router_model.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_tool_calling.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/tests/test_wrap_agent.py +0 -0
- {langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-dev-utils
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.12
|
|
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
|
|
@@ -31,7 +31,7 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
[](https://pepy.tech/project/langchain-dev-utils)
|
|
32
32
|
[](https://tbice123123.github.io/langchain-dev-utils-docs/zh/)
|
|
33
33
|
|
|
34
|
-
> This is the
|
|
34
|
+
> This is the English version. For the Chinese version, please visit [Chinese Documentation](https://github.com/TBice123123/langchain-dev-utils/blob/master/README_cn.md)
|
|
35
35
|
|
|
36
36
|
**langchain-dev-utils** is a utility library focused on enhancing the development experience with LangChain and LangGraph. It provides a series of out-of-the-box utility functions that can both reduce repetitive code writing and improve code consistency and readability. By simplifying development workflows, this library helps you prototype faster, iterate more smoothly, and create clearer, more reliable LLM-based AI applications.
|
|
37
37
|
|
|
@@ -133,7 +133,7 @@ print(emb)
|
|
|
133
133
|
|
|
134
134
|
Includes the following features:
|
|
135
135
|
|
|
136
|
-
- Merge
|
|
136
|
+
- Merge reasoning content into the final response
|
|
137
137
|
- Stream content merging
|
|
138
138
|
- Content formatting tools
|
|
139
139
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
[](https://pepy.tech/project/langchain-dev-utils)
|
|
16
16
|
[](https://tbice123123.github.io/langchain-dev-utils-docs/zh/)
|
|
17
17
|
|
|
18
|
-
> This is the
|
|
18
|
+
> This is the English version. For the Chinese version, please visit [Chinese Documentation](https://github.com/TBice123123/langchain-dev-utils/blob/master/README_cn.md)
|
|
19
19
|
|
|
20
20
|
**langchain-dev-utils** is a utility library focused on enhancing the development experience with LangChain and LangGraph. It provides a series of out-of-the-box utility functions that can both reduce repetitive code writing and improve code consistency and readability. By simplifying development workflows, this library helps you prototype faster, iterate more smoothly, and create clearer, more reliable LLM-based AI applications.
|
|
21
21
|
|
|
@@ -117,7 +117,7 @@ print(emb)
|
|
|
117
117
|
|
|
118
118
|
Includes the following features:
|
|
119
119
|
|
|
120
|
-
- Merge
|
|
120
|
+
- Merge reasoning content into the final response
|
|
121
121
|
- Stream content merging
|
|
122
122
|
- Content formatting tools
|
|
123
123
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.12"
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Awaitable, Callable, Literal, Optional
|
|
3
|
+
from typing import NotRequired
|
|
4
|
+
|
|
5
|
+
from langchain.agents.middleware import ModelRequest, ModelResponse
|
|
6
|
+
from langchain.agents.middleware.types import (
|
|
7
|
+
AgentMiddleware,
|
|
8
|
+
AgentState,
|
|
9
|
+
ModelCallResult,
|
|
10
|
+
)
|
|
11
|
+
from langchain.tools import BaseTool, ToolRuntime, tool
|
|
12
|
+
from langchain_core.messages import ToolMessage
|
|
13
|
+
from langgraph.types import Command
|
|
14
|
+
from typing_extensions import TypedDict
|
|
15
|
+
|
|
16
|
+
_DEFAULT_WRITE_PLAN_TOOL_DESCRIPTION = """Use this tool to create and manage a structured task list for complex or multi-step work. It helps you stay organized, track progress, and demonstrate to the user that you’re handling tasks systematically.
|
|
17
|
+
|
|
18
|
+
## When to Use This Tool
|
|
19
|
+
Use this tool in the following scenarios:
|
|
20
|
+
|
|
21
|
+
1. **Complex multi-step tasks** — when a task requires three or more distinct steps or actions.
|
|
22
|
+
2. **Non-trivial and complex tasks** — tasks that require careful planning or involve multiple operations.
|
|
23
|
+
3. **User explicitly requests a to-do list** — when the user directly asks you to use the to-do list feature.
|
|
24
|
+
4. **User provides multiple tasks** — when the user supplies a list of items to be done (e.g., numbered or comma-separated).
|
|
25
|
+
5. **The plan needs adjustment based on current execution** — when ongoing progress indicates the plan should be revised.
|
|
26
|
+
|
|
27
|
+
## How to Use This Tool
|
|
28
|
+
1. **When starting a task** — before actually beginning work, invoke this tool with a task list (a list of strings). The first task will automatically be set to `in_progress`, and all others to `pending`.
|
|
29
|
+
2. **When updating the task list** — for example, after completing some tasks, if you find certain tasks are no longer needed, remove them; if new necessary tasks emerge, add them. However, **do not modify** tasks already marked as completed. In such cases, simply call this tool again with the updated task list.
|
|
30
|
+
|
|
31
|
+
## When NOT to Use This Tool
|
|
32
|
+
Avoid using this tool in the following situations:
|
|
33
|
+
1. The task is a **single, straightforward action**.
|
|
34
|
+
2. The task is **too trivial**, and tracking it provides no benefit.
|
|
35
|
+
3. The task can be completed in **fewer than three simple steps**.
|
|
36
|
+
4. The current task list has been fully completed — in this case, use `finish_sub_plan()` to finalize.
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
- **Input**: A parameter named `plan` containing a list of strings representing the tasks (e.g., `["Task 1", "Task 2", "Task 3"]`).
|
|
40
|
+
- **Automatic status assignment**:
|
|
41
|
+
→ First task: `in_progress`
|
|
42
|
+
→ Remaining tasks: `pending`
|
|
43
|
+
- When updating the plan, provide only the **next set of tasks to execute**. For example, if the next phase requires `["Task 4", "Task 5"]`, call this tool with `plan=["Task 4", "Task 5"]`.
|
|
44
|
+
|
|
45
|
+
## Task States
|
|
46
|
+
- `pending`: Ready to start, awaiting execution
|
|
47
|
+
- `in_progress`: Currently being worked on
|
|
48
|
+
- `done`: Completed
|
|
49
|
+
|
|
50
|
+
## Best Practices
|
|
51
|
+
- Break large tasks into clear, actionable steps.
|
|
52
|
+
- Use specific and descriptive task names.
|
|
53
|
+
- Update the plan immediately if priorities shift or blockers arise.
|
|
54
|
+
- Never leave the plan empty — as long as unfinished tasks remain, at least one must be marked `in_progress`.
|
|
55
|
+
- Do not batch completions — mark each task as done immediately after finishing it.
|
|
56
|
+
- Remove irrelevant tasks entirely instead of leaving them in `pending` state.
|
|
57
|
+
|
|
58
|
+
**Remember**: If a task is simple, just do it. This tool is meant to provide structure — not overhead.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_DEFAULT_FINISH_SUB_PLAN_TOOL_DESCRIPTION = """This tool is used to mark the currently in-progress task in an existing task list as completed.
|
|
62
|
+
|
|
63
|
+
## Functionality
|
|
64
|
+
- Marks the current task with status `in_progress` as `done`, and automatically sets the next task (previously `pending`) to `in_progress`.
|
|
65
|
+
|
|
66
|
+
## When to Use
|
|
67
|
+
Use only when you have confirmed that the current task is truly finished.
|
|
68
|
+
|
|
69
|
+
## Example
|
|
70
|
+
Before calling:
|
|
71
|
+
```json
|
|
72
|
+
[
|
|
73
|
+
{"content": "Task 1", "status": "done"},
|
|
74
|
+
{"content": "Task 2", "status": "in_progress"},
|
|
75
|
+
{"content": "Task 3", "status": "pending"}
|
|
76
|
+
]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
After calling `finish_sub_plan()`:
|
|
80
|
+
```json
|
|
81
|
+
[
|
|
82
|
+
{"content": "Task 1", "status": "done"},
|
|
83
|
+
{"content": "Task 2", "status": "done"},
|
|
84
|
+
{"content": "Task 3", "status": "in_progress"}
|
|
85
|
+
]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Note**:
|
|
89
|
+
- This tool is **only** for marking completion — do **not** use it to create or modify plans (use `write_plan` instead).
|
|
90
|
+
- Ensure the task is genuinely complete before invoking this function.
|
|
91
|
+
- No parameters are required — status updates are handled automatically.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
_DEFAULT_READ_PLAN_TOOL_DESCRIPTION = """
|
|
95
|
+
Get all sub-plans with their current status.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class Plan(TypedDict):
|
|
100
|
+
content: str
|
|
101
|
+
status: Literal["pending", "in_progress", "done"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class PlanState(AgentState):
|
|
105
|
+
plan: NotRequired[list[Plan]]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def create_write_plan_tool(
|
|
109
|
+
description: Optional[str] = None,
|
|
110
|
+
message_key: Optional[str] = None,
|
|
111
|
+
) -> BaseTool:
|
|
112
|
+
"""Create a tool for writing initial plan.
|
|
113
|
+
|
|
114
|
+
This function creates a tool that allows agents to write an initial plan
|
|
115
|
+
with a list of plans. The first plan in the plan will be marked as "in_progress"
|
|
116
|
+
and the rest as "pending".
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
description: The description of the tool. Uses default description if not provided.
|
|
120
|
+
message_key: The key of the message to be updated. Defaults to "messages".
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
BaseTool: The tool for writing initial plan.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
Basic usage:
|
|
127
|
+
>>> from langchain_dev_utils.agents.middleware import create_write_plan_tool
|
|
128
|
+
>>> write_plan_tool = create_write_plan_tool()
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
@tool(
|
|
132
|
+
description=description or _DEFAULT_WRITE_PLAN_TOOL_DESCRIPTION,
|
|
133
|
+
)
|
|
134
|
+
def write_plan(plan: list[str], runtime: ToolRuntime):
|
|
135
|
+
msg_key = message_key or "messages"
|
|
136
|
+
return Command(
|
|
137
|
+
update={
|
|
138
|
+
"plan": [
|
|
139
|
+
{
|
|
140
|
+
"content": content,
|
|
141
|
+
"status": "pending" if index > 0 else "in_progress",
|
|
142
|
+
}
|
|
143
|
+
for index, content in enumerate(plan)
|
|
144
|
+
],
|
|
145
|
+
msg_key: [
|
|
146
|
+
ToolMessage(
|
|
147
|
+
content=f"Plan successfully written, please first execute the {plan[0]} sub-plan (no need to change the status to in_process)",
|
|
148
|
+
tool_call_id=runtime.tool_call_id,
|
|
149
|
+
)
|
|
150
|
+
],
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return write_plan
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def create_finish_sub_plan_tool(
|
|
158
|
+
description: Optional[str] = None,
|
|
159
|
+
message_key: Optional[str] = None,
|
|
160
|
+
) -> BaseTool:
|
|
161
|
+
"""Create a tool for finishing sub-plan tasks.
|
|
162
|
+
|
|
163
|
+
This function creates a tool that allows agents to update the status of sub-plans in a plan. Sub-plans can be marked as "done" to track progress.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
description: The description of the tool. Uses default description if not provided.
|
|
167
|
+
message_key: The key of the message to be updated. Defaults to "messages".
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
BaseTool: The tool for finishing sub-plan tasks.
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
Basic usage:
|
|
174
|
+
>>> from langchain_dev_utils.agents.middleware import create_finish_sub_plan_tool
|
|
175
|
+
>>> finish_sub_plan_tool = create_finish_sub_plan_tool()
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
@tool(
|
|
179
|
+
description=description or _DEFAULT_FINISH_SUB_PLAN_TOOL_DESCRIPTION,
|
|
180
|
+
)
|
|
181
|
+
def finish_sub_plan(
|
|
182
|
+
runtime: ToolRuntime,
|
|
183
|
+
):
|
|
184
|
+
msg_key = message_key or "messages"
|
|
185
|
+
plan_list = runtime.state.get("plan", [])
|
|
186
|
+
|
|
187
|
+
sub_finish_plan = ""
|
|
188
|
+
sub_next_plan = ",all sub plan are done"
|
|
189
|
+
for plan in plan_list:
|
|
190
|
+
if plan["status"] == "in_progress":
|
|
191
|
+
plan["status"] = "done"
|
|
192
|
+
sub_finish_plan = f"finish sub plan:**{plan['content']}**"
|
|
193
|
+
|
|
194
|
+
for plan in plan_list:
|
|
195
|
+
if plan["status"] == "pending":
|
|
196
|
+
plan["status"] = "in_progress"
|
|
197
|
+
sub_next_plan = f",next plan:**{plan['content']}**"
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
return Command(
|
|
201
|
+
update={
|
|
202
|
+
"plan": plan_list,
|
|
203
|
+
msg_key: [
|
|
204
|
+
ToolMessage(
|
|
205
|
+
content=sub_finish_plan + sub_next_plan,
|
|
206
|
+
tool_call_id=runtime.tool_call_id,
|
|
207
|
+
)
|
|
208
|
+
],
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return finish_sub_plan
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def create_read_plan_tool(
|
|
216
|
+
description: Optional[str] = None,
|
|
217
|
+
):
|
|
218
|
+
"""Create a tool for reading all sub-plans.
|
|
219
|
+
|
|
220
|
+
This function creates a tool that allows agents to read all sub-plans
|
|
221
|
+
in the current plan with their status information.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
description: The description of the tool. Uses default description if not provided.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
BaseTool: The tool for reading all sub-plans.
|
|
228
|
+
|
|
229
|
+
Example:
|
|
230
|
+
Basic usage:
|
|
231
|
+
>>> from langchain_dev_utils.agents.middleware import create_read_plan_tool
|
|
232
|
+
>>> read_plan_tool = create_read_plan_tool()
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
@tool(
|
|
236
|
+
description=description or _DEFAULT_READ_PLAN_TOOL_DESCRIPTION,
|
|
237
|
+
)
|
|
238
|
+
def read_plan(runtime: ToolRuntime):
|
|
239
|
+
plan_list = runtime.state.get("plan", [])
|
|
240
|
+
return json.dumps(plan_list)
|
|
241
|
+
|
|
242
|
+
return read_plan
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
_PLAN_SYSTEM_PROMPT_NOT_READ_PLAN = """You can manage task plans using two simple tools:
|
|
246
|
+
|
|
247
|
+
1. **write_plan**
|
|
248
|
+
- Use it to break complex tasks (3+ steps) into a clear, actionable list. Only include next steps to execute — the first becomes `"in_progress"`, the rest `"pending"`. Don’t use it for simple tasks (<3 steps).
|
|
249
|
+
2. **finish_sub_plan**
|
|
250
|
+
- Call it **only when the current task is 100% done**. It automatically marks it `"done"` and promotes the next `"pending"` task to `"in_progress"`. No parameters needed. Never use it mid-task or if anything’s incomplete.
|
|
251
|
+
Keep plans lean, update immediately, and never batch completions.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
_PLAN_SYSTEM_PROMPT = """You can manage task plans using three simple tools:
|
|
255
|
+
|
|
256
|
+
## write_plan
|
|
257
|
+
- Use it to break complex tasks (3+ steps) into a clear, actionable list. Only include next steps to execute — the first becomes `"in_progress"`, the rest `"pending"`. Don’t use it for simple tasks (<3 steps).
|
|
258
|
+
|
|
259
|
+
## finish_sub_plan
|
|
260
|
+
- Call it **only when the current task is 100% done**. It automatically marks it `"done"` and promotes the next `"pending"` task to `"in_progress"`. No parameters needed. Never use it mid-task or if anything’s incomplete.
|
|
261
|
+
|
|
262
|
+
## read_plan
|
|
263
|
+
- Retrieve the full current plan list with statuses, especially when you forget which sub-plan you're supposed to execute next.
|
|
264
|
+
- No parameters required—returns a current plan list with statuses.
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class PlanMiddleware(AgentMiddleware):
|
|
269
|
+
"""Middleware that provides plan management capabilities to agents.
|
|
270
|
+
|
|
271
|
+
This middleware adds a `write_plan` and `finish_sub_plan` (and `read_plan` optional) tool that allows agents to create and manage
|
|
272
|
+
structured plan lists for complex multi-step operations. It's designed to help
|
|
273
|
+
agents track progress, organize complex tasks, and provide users with visibility
|
|
274
|
+
into task completion status.
|
|
275
|
+
|
|
276
|
+
The middleware automatically injects system prompts that guide the agent on how to use the plan functionality effectively.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
system_prompt: Custom system prompt to guide the agent on using the plan tool.
|
|
280
|
+
If not provided, uses the default `_PLAN_MIDDLEWARE_SYSTEM_PROMPT`.
|
|
281
|
+
tools: List of tools to be added to the agent. The tools must be created by `create_write_plan_tool`, `create_finish_sub_plan_tool`, and `create_read_plan_tool`(optional).
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
```python
|
|
285
|
+
from langchain_dev_utils.agents.middleware.plan import PlanMiddleware
|
|
286
|
+
from langchain_dev_utils.agents import create_agent
|
|
287
|
+
|
|
288
|
+
agent = create_agent("vllm:qwen3-4b", middleware=[PlanMiddleware()])
|
|
289
|
+
|
|
290
|
+
# Agent now has access to write_plan tool and plan state tracking
|
|
291
|
+
result = await agent.invoke({"messages": [HumanMessage("Help me refactor my codebase")]})
|
|
292
|
+
|
|
293
|
+
print(result["plan"]) # Array of plan items with status tracking
|
|
294
|
+
```
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
state_schema = PlanState
|
|
298
|
+
|
|
299
|
+
def __init__(
|
|
300
|
+
self,
|
|
301
|
+
*,
|
|
302
|
+
system_prompt: Optional[str] = None,
|
|
303
|
+
write_plan_tool_description: Optional[str] = None,
|
|
304
|
+
finish_sub_plan_tool_description: Optional[str] = None,
|
|
305
|
+
read_plan_tool_description: Optional[str] = None,
|
|
306
|
+
use_read_plan_tool: bool = True,
|
|
307
|
+
) -> None:
|
|
308
|
+
super().__init__()
|
|
309
|
+
|
|
310
|
+
write_plan_tool_description = (
|
|
311
|
+
write_plan_tool_description or _DEFAULT_WRITE_PLAN_TOOL_DESCRIPTION
|
|
312
|
+
)
|
|
313
|
+
finish_sub_plan_tool_description = (
|
|
314
|
+
finish_sub_plan_tool_description
|
|
315
|
+
or _DEFAULT_FINISH_SUB_PLAN_TOOL_DESCRIPTION
|
|
316
|
+
)
|
|
317
|
+
read_plan_tool_description = (
|
|
318
|
+
read_plan_tool_description or _DEFAULT_READ_PLAN_TOOL_DESCRIPTION
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
tools = [
|
|
322
|
+
create_write_plan_tool(description=write_plan_tool_description),
|
|
323
|
+
create_finish_sub_plan_tool(description=finish_sub_plan_tool_description),
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
if use_read_plan_tool:
|
|
327
|
+
tools.append(create_read_plan_tool(description=read_plan_tool_description))
|
|
328
|
+
|
|
329
|
+
if system_prompt is None:
|
|
330
|
+
if use_read_plan_tool:
|
|
331
|
+
system_prompt = _PLAN_SYSTEM_PROMPT
|
|
332
|
+
else:
|
|
333
|
+
system_prompt = _PLAN_SYSTEM_PROMPT_NOT_READ_PLAN
|
|
334
|
+
|
|
335
|
+
self.system_prompt = system_prompt
|
|
336
|
+
self.tools = tools
|
|
337
|
+
|
|
338
|
+
def wrap_model_call(
|
|
339
|
+
self,
|
|
340
|
+
request: ModelRequest,
|
|
341
|
+
handler: Callable[[ModelRequest], ModelResponse],
|
|
342
|
+
) -> ModelCallResult:
|
|
343
|
+
request.system_prompt = (
|
|
344
|
+
request.system_prompt + "\n\n" + self.system_prompt
|
|
345
|
+
if request.system_prompt
|
|
346
|
+
else self.system_prompt
|
|
347
|
+
)
|
|
348
|
+
return handler(request)
|
|
349
|
+
|
|
350
|
+
async def awrap_model_call(
|
|
351
|
+
self,
|
|
352
|
+
request: ModelRequest,
|
|
353
|
+
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
354
|
+
) -> ModelCallResult:
|
|
355
|
+
request.system_prompt = (
|
|
356
|
+
request.system_prompt + "\n\n" + self.system_prompt
|
|
357
|
+
if request.system_prompt
|
|
358
|
+
else self.system_prompt
|
|
359
|
+
)
|
|
360
|
+
return await handler(request)
|
|
@@ -126,12 +126,15 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
|
|
|
126
126
|
stop: list[str] | None = None,
|
|
127
127
|
**kwargs: Any,
|
|
128
128
|
) -> dict:
|
|
129
|
+
payload = {**self._default_params, **kwargs}
|
|
130
|
+
|
|
131
|
+
if self._use_responses_api(payload):
|
|
132
|
+
return super()._get_request_payload(input_, stop=stop, **kwargs)
|
|
133
|
+
|
|
129
134
|
messages = self._convert_input(input_).to_messages()
|
|
130
135
|
if stop is not None:
|
|
131
136
|
kwargs["stop"] = stop
|
|
132
137
|
|
|
133
|
-
payload = {**self._default_params, **kwargs}
|
|
134
|
-
|
|
135
138
|
payload_messages = []
|
|
136
139
|
|
|
137
140
|
for m in messages:
|
|
@@ -235,6 +235,10 @@ def load_chat_model(
|
|
|
235
235
|
... )
|
|
236
236
|
>>> model.invoke("Hello, how are you?")
|
|
237
237
|
"""
|
|
238
|
+
if "provider_config" in kwargs:
|
|
239
|
+
raise ValueError(
|
|
240
|
+
"provider_config is not a valid parameter in load_chat_model ,you can only set it when register model provider"
|
|
241
|
+
)
|
|
238
242
|
return _load_chat_model_helper(
|
|
239
243
|
cast(str, model),
|
|
240
244
|
model_provider=model_provider,
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
from functools import reduce
|
|
2
2
|
from typing import AsyncIterator, Iterator, Sequence, Tuple, cast
|
|
3
3
|
|
|
4
|
-
from langchain_core.messages import AIMessage, AIMessageChunk
|
|
4
|
+
from langchain_core.messages import AIMessage, AIMessageChunk
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _get_reasoning_content(model_response: AIMessage | AIMessageChunk) -> str | None:
|
|
8
|
+
reasoning_content = None
|
|
9
|
+
|
|
10
|
+
reasoning_content_block = [
|
|
11
|
+
block for block in model_response.content_blocks if block["type"] == "reasoning"
|
|
12
|
+
]
|
|
13
|
+
if reasoning_content_block:
|
|
14
|
+
reasoning_content = reasoning_content_block[0].get("reasoning")
|
|
15
|
+
|
|
16
|
+
if not reasoning_content:
|
|
17
|
+
reasoning_content = model_response.additional_kwargs.get("reasoning_content")
|
|
18
|
+
|
|
19
|
+
return reasoning_content
|
|
5
20
|
|
|
6
21
|
|
|
7
22
|
def convert_reasoning_content_for_ai_message(
|
|
@@ -33,15 +48,20 @@ def convert_reasoning_content_for_ai_message(
|
|
|
33
48
|
... )
|
|
34
49
|
>>> response.content
|
|
35
50
|
"""
|
|
36
|
-
|
|
37
|
-
|
|
51
|
+
|
|
52
|
+
reasoning_content = _get_reasoning_content(model_response)
|
|
53
|
+
|
|
54
|
+
if reasoning_content:
|
|
55
|
+
model_response.content = (
|
|
56
|
+
f"{think_tag[0]}{reasoning_content}{think_tag[1]}{model_response.content}"
|
|
57
|
+
)
|
|
38
58
|
return model_response
|
|
39
59
|
|
|
40
60
|
|
|
41
61
|
def convert_reasoning_content_for_chunk_iterator(
|
|
42
|
-
model_response: Iterator[
|
|
62
|
+
model_response: Iterator[AIMessageChunk | AIMessage],
|
|
43
63
|
think_tag: Tuple[str, str] = ("<think>", "</think>"),
|
|
44
|
-
) -> Iterator[
|
|
64
|
+
) -> Iterator[AIMessageChunk | AIMessage]:
|
|
45
65
|
"""Convert reasoning content for streaming response chunks.
|
|
46
66
|
|
|
47
67
|
This function processes streaming response chunks and merges reasoning content
|
|
@@ -75,32 +95,24 @@ def convert_reasoning_content_for_chunk_iterator(
|
|
|
75
95
|
isend = True
|
|
76
96
|
|
|
77
97
|
for chunk in model_response:
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
elif (
|
|
90
|
-
isinstance(chunk, AIMessageChunk)
|
|
91
|
-
and "reasoning_content" not in chunk.additional_kwargs
|
|
92
|
-
and chunk.content
|
|
93
|
-
and isend
|
|
94
|
-
):
|
|
95
|
-
chunk.content = f"{think_tag[1]}{chunk.content}"
|
|
96
|
-
isend = False
|
|
98
|
+
if isinstance(chunk, AIMessageChunk):
|
|
99
|
+
reasoning_content = _get_reasoning_content(chunk)
|
|
100
|
+
if reasoning_content:
|
|
101
|
+
if isfirst:
|
|
102
|
+
chunk.content = f"{think_tag[0]}{reasoning_content}"
|
|
103
|
+
isfirst = False
|
|
104
|
+
else:
|
|
105
|
+
chunk.content = reasoning_content
|
|
106
|
+
elif chunk.content and isend and not isfirst:
|
|
107
|
+
chunk.content = f"{think_tag[1]}{chunk.content}"
|
|
108
|
+
isend = False
|
|
97
109
|
yield chunk
|
|
98
110
|
|
|
99
111
|
|
|
100
112
|
async def aconvert_reasoning_content_for_chunk_iterator(
|
|
101
|
-
model_response: AsyncIterator[
|
|
113
|
+
model_response: AsyncIterator[AIMessageChunk | AIMessage],
|
|
102
114
|
think_tag: Tuple[str, str] = ("<think>", "</think>"),
|
|
103
|
-
) -> AsyncIterator[
|
|
115
|
+
) -> AsyncIterator[AIMessageChunk | AIMessage]:
|
|
104
116
|
"""Async convert reasoning content for streaming response chunks.
|
|
105
117
|
|
|
106
118
|
This is the async version of convert_reasoning_content_for_chunk_iterator.
|
|
@@ -133,25 +145,17 @@ async def aconvert_reasoning_content_for_chunk_iterator(
|
|
|
133
145
|
isend = True
|
|
134
146
|
|
|
135
147
|
async for chunk in model_response:
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
elif (
|
|
148
|
-
isinstance(chunk, AIMessageChunk)
|
|
149
|
-
and "reasoning_content" not in chunk.additional_kwargs
|
|
150
|
-
and chunk.content
|
|
151
|
-
and isend
|
|
152
|
-
):
|
|
153
|
-
chunk.content = f"{think_tag[1]}{chunk.content}"
|
|
154
|
-
isend = False
|
|
148
|
+
if isinstance(chunk, AIMessageChunk):
|
|
149
|
+
reasoning_content = _get_reasoning_content(chunk)
|
|
150
|
+
if reasoning_content:
|
|
151
|
+
if isfirst:
|
|
152
|
+
chunk.content = f"{think_tag[0]}{reasoning_content}"
|
|
153
|
+
isfirst = False
|
|
154
|
+
else:
|
|
155
|
+
chunk.content = reasoning_content
|
|
156
|
+
elif chunk.content and isend and not isfirst:
|
|
157
|
+
chunk.content = f"{think_tag[1]}{chunk.content}"
|
|
158
|
+
isend = False
|
|
155
159
|
yield chunk
|
|
156
160
|
|
|
157
161
|
|
|
@@ -22,6 +22,9 @@ def has_tool_calling(message: AIMessage) -> bool:
|
|
|
22
22
|
>>> if has_tool_calling(response):
|
|
23
23
|
... print("Tool calls found in response")
|
|
24
24
|
"""
|
|
25
|
+
|
|
26
|
+
if any([block for block in message.content_blocks if block["type"] == "tool_call"]):
|
|
27
|
+
return True
|
|
25
28
|
if (
|
|
26
29
|
isinstance(message, AIMessage)
|
|
27
30
|
and hasattr(message, "tool_calls")
|
|
@@ -59,6 +62,20 @@ def parse_tool_calling(
|
|
|
59
62
|
... tool_calls = parse_tool_calling(response)
|
|
60
63
|
"""
|
|
61
64
|
|
|
65
|
+
tool_call = None
|
|
66
|
+
|
|
67
|
+
tool_call_blocks = [
|
|
68
|
+
block for block in message.content_blocks if block["type"] == "tool_call"
|
|
69
|
+
]
|
|
70
|
+
if tool_call_blocks:
|
|
71
|
+
tool_call = tool_call_blocks
|
|
72
|
+
|
|
73
|
+
if not tool_call:
|
|
74
|
+
tool_call = message.tool_calls
|
|
75
|
+
|
|
76
|
+
if not tool_call:
|
|
77
|
+
raise ValueError("No tool call found in message")
|
|
78
|
+
|
|
62
79
|
if first_tool_call_only:
|
|
63
|
-
return (
|
|
64
|
-
return [(tool_call["name"], tool_call["args"]) for tool_call in
|
|
80
|
+
return (tool_call[0]["name"], tool_call[0]["args"])
|
|
81
|
+
return [(tool_call["name"], tool_call["args"]) for tool_call in tool_call]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.10"
|
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Awaitable, Callable, Literal, Optional
|
|
3
|
-
from typing import NotRequired
|
|
4
|
-
|
|
5
|
-
from langchain.agents.middleware import ModelRequest, ModelResponse
|
|
6
|
-
from langchain.agents.middleware.types import (
|
|
7
|
-
AgentMiddleware,
|
|
8
|
-
AgentState,
|
|
9
|
-
ModelCallResult,
|
|
10
|
-
)
|
|
11
|
-
from langchain.tools import BaseTool, ToolRuntime, tool
|
|
12
|
-
from langchain_core.messages import ToolMessage
|
|
13
|
-
from langgraph.types import Command
|
|
14
|
-
from typing_extensions import TypedDict
|
|
15
|
-
|
|
16
|
-
_DEFAULT_WRITE_PLAN_TOOL_DESCRIPTION = """
|
|
17
|
-
This tool is used to **create a new plan list ** or **modify an existing plan**.
|
|
18
|
-
|
|
19
|
-
Parameters:
|
|
20
|
-
`plan`: A list of strings representing the plans to be executed. The first plan in the list will automatically be set to the "in_progress" status, and all remaining plans will be set to the "pending" status. Internally, each string is converted into a dictionary containing "content" and "status" keys, where "content" corresponds to each string value in the list, and the "status" is assigned according to the rule: the first plan is "in_progress", and all others are "pending".
|
|
21
|
-
When modifying a plan, only provide the plans that need to be executed next. For example, if the current plan is ['Plan 1', 'Plan 2', 'Plan 3'], and Plan 1 is completed, but you determine that Plan 2 and Plan 3 are no longer suitable and should be replaced with ['Plan 4', 'Plan 5'], then only pass ['Plan 4', 'Plan 5'].
|
|
22
|
-
|
|
23
|
-
## Tool Functionality
|
|
24
|
-
|
|
25
|
-
This tool is used to **create a new plan list** or **modify an existing plan list**.
|
|
26
|
-
|
|
27
|
-
- **Primary Use**: Create a new plan list.
|
|
28
|
-
- **Secondary Use**: Modify the plan when problems are identified with the existing one.
|
|
29
|
-
- **Not For**: Marking plans as completed.
|
|
30
|
-
|
|
31
|
-
## Plan Statuses
|
|
32
|
-
|
|
33
|
-
Use the following statuses to track progress:
|
|
34
|
-
|
|
35
|
-
- `pending`: Plan has not yet started.
|
|
36
|
-
- `in_progress`: Currently being worked on.
|
|
37
|
-
- `done`: Plan has been completed.
|
|
38
|
-
|
|
39
|
-
## Usage Guidelines
|
|
40
|
-
|
|
41
|
-
### Creating a Plan (Primary Use)
|
|
42
|
-
|
|
43
|
-
Call this tool to create a structured plan when dealing with complex tasks:
|
|
44
|
-
|
|
45
|
-
- Complex multi-step tasks (≥3 steps).
|
|
46
|
-
- Non-trivial tasks requiring careful planning.
|
|
47
|
-
- The user explicitly requests a plan list.
|
|
48
|
-
|
|
49
|
-
### Modifying a Plan (Special Cases)
|
|
50
|
-
|
|
51
|
-
Modify an existing plan only under the following circumstances:
|
|
52
|
-
|
|
53
|
-
- The current plan structure is found to be problematic.
|
|
54
|
-
- The plan structure or content needs adjustment.
|
|
55
|
-
- The existing plan cannot be executed effectively.
|
|
56
|
-
|
|
57
|
-
**Important Update Behavior**: When updating a plan, only pass the list of plans that need to be executed next. The tool automatically handles status assignment – the first plan is set to `in_progress`, and the rest are set to `pending`.
|
|
58
|
-
|
|
59
|
-
### Plan Completion
|
|
60
|
-
|
|
61
|
-
**Important Note**: When completing a plan, call the `finish_sub_plan()` function to update the plan status. **Do not use this tool**.
|
|
62
|
-
|
|
63
|
-
## When to Use
|
|
64
|
-
|
|
65
|
-
**Appropriate Scenarios**:
|
|
66
|
-
|
|
67
|
-
- When starting a new complex work session.
|
|
68
|
-
- When needing to reorganize the plan structure.
|
|
69
|
-
- When the current plan needs revision based on new information.
|
|
70
|
-
|
|
71
|
-
**Scenarios to Avoid**:
|
|
72
|
-
|
|
73
|
-
- Simple tasks (<3 steps).
|
|
74
|
-
- When only needing to mark a plan as complete.
|
|
75
|
-
- Purely informational queries or conversation.
|
|
76
|
-
- Trivial tasks that can be completed directly.
|
|
77
|
-
|
|
78
|
-
## Best Practices
|
|
79
|
-
|
|
80
|
-
1. When creating a plan, the first plan is automatically set to `in_progress`.
|
|
81
|
-
2. Ensure sub-plans within the plan are specific and actionable.
|
|
82
|
-
3. Break down complex sub-plans into smaller steps.
|
|
83
|
-
4. Always keep at least one plan in the `in_progress` status unless all plans are completed.
|
|
84
|
-
5. When modifying a plan, only provide the sub-plans that need to be executed next.
|
|
85
|
-
6. Use clear, descriptive names for sub-plans.
|
|
86
|
-
|
|
87
|
-
## Internal Processing Logic
|
|
88
|
-
|
|
89
|
-
The tool automatically converts the input strings into a structured format:
|
|
90
|
-
|
|
91
|
-
- Input: `["Sub-plan 1", "Sub-plan 2", "Sub-plan 3"]`
|
|
92
|
-
- Internal Representation:
|
|
93
|
-
```json
|
|
94
|
-
[
|
|
95
|
-
{"content": "Sub-plan 1", "status": "in_progress"},
|
|
96
|
-
{"content": "Sub-plan 2", "status": "pending"},
|
|
97
|
-
{"content": "Sub-plan 3", "status": "pending"}
|
|
98
|
-
]
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Please remember:
|
|
102
|
-
|
|
103
|
-
- For simple plans, execute them directly; there is no need to call this tool.
|
|
104
|
-
- Use the `finish_sub_plan()` function to mark sub-plans as completed.
|
|
105
|
-
- When modifying a plan, only provide the sub-plans that need to be executed next.
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
_DEFAULT_FINISH_SUB_PLAN_TOOL_DESCRIPTION = """
|
|
109
|
-
This tool is used to mark the completion status of a sub-plan in an existing plan.
|
|
110
|
-
|
|
111
|
-
Functionality:
|
|
112
|
-
- Marks the sub-plan with status 'in_progress' as 'done'
|
|
113
|
-
- Sets the first sub-plan with status 'pending' to 'in_progress' (if one exists)
|
|
114
|
-
|
|
115
|
-
## Tool Purpose
|
|
116
|
-
Specifically designed to **mark the current sub-plan as completed** in an existing plan.
|
|
117
|
-
|
|
118
|
-
### When to Use
|
|
119
|
-
Use only when the current sub-plan is confirmed complete.
|
|
120
|
-
|
|
121
|
-
### Automatic Status Management
|
|
122
|
-
- `in_progress` → `done`
|
|
123
|
-
- First `pending` → `in_progress` (if any)
|
|
124
|
-
|
|
125
|
-
## Usage Example
|
|
126
|
-
Current plan status:
|
|
127
|
-
```json
|
|
128
|
-
[
|
|
129
|
-
{"content": "Research market trends", "status": "done"},
|
|
130
|
-
{"content": "Analyze competitor data", "status": "in_progress"},
|
|
131
|
-
{"content": "Prepare summary report", "status": "pending"}
|
|
132
|
-
]
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
After calling finish_sub_plan():
|
|
136
|
-
```json
|
|
137
|
-
[
|
|
138
|
-
{"content": "Research market trends", "status": "done"},
|
|
139
|
-
{"content": "Analyze competitor data", "status": "done"},
|
|
140
|
-
{"content": "Prepare summary report", "status": "in_progress"}
|
|
141
|
-
]
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Remember:
|
|
145
|
-
- Only for marking completion—do not use to create or modify plans (use write_plan instead)
|
|
146
|
-
- Ensure the sub-plan is truly complete before calling
|
|
147
|
-
- No parameters needed; status transitions are handled automatically
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
_DEFAULT_READ_PLAN_TOOL_DESCRIPTION = """
|
|
151
|
-
Get all sub-plans with their current status.
|
|
152
|
-
"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class Plan(TypedDict):
|
|
156
|
-
content: str
|
|
157
|
-
status: Literal["pending", "in_progress", "done"]
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
class PlanState(AgentState):
|
|
161
|
-
plan: NotRequired[list[Plan]]
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def create_write_plan_tool(
|
|
165
|
-
description: Optional[str] = None,
|
|
166
|
-
message_key: Optional[str] = None,
|
|
167
|
-
) -> BaseTool:
|
|
168
|
-
"""Create a tool for writing initial plan.
|
|
169
|
-
|
|
170
|
-
This function creates a tool that allows agents to write an initial plan
|
|
171
|
-
with a list of plans. The first plan in the plan will be marked as "in_progress"
|
|
172
|
-
and the rest as "pending".
|
|
173
|
-
|
|
174
|
-
Args:
|
|
175
|
-
description: The description of the tool. Uses default description if not provided.
|
|
176
|
-
message_key: The key of the message to be updated. Defaults to "messages".
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
BaseTool: The tool for writing initial plan.
|
|
180
|
-
|
|
181
|
-
Example:
|
|
182
|
-
Basic usage:
|
|
183
|
-
>>> from langchain_dev_utils.agents.middleware import create_write_plan_tool
|
|
184
|
-
>>> write_plan_tool = create_write_plan_tool()
|
|
185
|
-
"""
|
|
186
|
-
|
|
187
|
-
@tool(
|
|
188
|
-
description=description or _DEFAULT_WRITE_PLAN_TOOL_DESCRIPTION,
|
|
189
|
-
)
|
|
190
|
-
def write_plan(plan: list[str], runtime: ToolRuntime):
|
|
191
|
-
msg_key = message_key or "messages"
|
|
192
|
-
return Command(
|
|
193
|
-
update={
|
|
194
|
-
"plan": [
|
|
195
|
-
{
|
|
196
|
-
"content": content,
|
|
197
|
-
"status": "pending" if index > 0 else "in_progress",
|
|
198
|
-
}
|
|
199
|
-
for index, content in enumerate(plan)
|
|
200
|
-
],
|
|
201
|
-
msg_key: [
|
|
202
|
-
ToolMessage(
|
|
203
|
-
content=f"Plan successfully written, please first execute the {plan[0]} sub-plan (no need to change the status to in_process)",
|
|
204
|
-
tool_call_id=runtime.tool_call_id,
|
|
205
|
-
)
|
|
206
|
-
],
|
|
207
|
-
}
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
return write_plan
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def create_finish_sub_plan_tool(
|
|
214
|
-
description: Optional[str] = None,
|
|
215
|
-
message_key: Optional[str] = None,
|
|
216
|
-
) -> BaseTool:
|
|
217
|
-
"""Create a tool for finishing sub-plan tasks.
|
|
218
|
-
|
|
219
|
-
This function creates a tool that allows agents to update the status of sub-plans in a plan. Sub-plans can be marked as "done" to track progress.
|
|
220
|
-
|
|
221
|
-
Args:
|
|
222
|
-
description: The description of the tool. Uses default description if not provided.
|
|
223
|
-
message_key: The key of the message to be updated. Defaults to "messages".
|
|
224
|
-
|
|
225
|
-
Returns:
|
|
226
|
-
BaseTool: The tool for finishing sub-plan tasks.
|
|
227
|
-
|
|
228
|
-
Example:
|
|
229
|
-
Basic usage:
|
|
230
|
-
>>> from langchain_dev_utils.agents.middleware import create_finish_sub_plan_tool
|
|
231
|
-
>>> finish_sub_plan_tool = create_finish_sub_plan_tool()
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
@tool(
|
|
235
|
-
description=description or _DEFAULT_FINISH_SUB_PLAN_TOOL_DESCRIPTION,
|
|
236
|
-
)
|
|
237
|
-
def finish_sub_plan(
|
|
238
|
-
runtime: ToolRuntime,
|
|
239
|
-
):
|
|
240
|
-
msg_key = message_key or "messages"
|
|
241
|
-
plan_list = runtime.state.get("plan", [])
|
|
242
|
-
|
|
243
|
-
sub_finish_plan = ""
|
|
244
|
-
sub_next_plan = ",all sub plan are done"
|
|
245
|
-
for plan in plan_list:
|
|
246
|
-
if plan["status"] == "in_progress":
|
|
247
|
-
plan["status"] = "done"
|
|
248
|
-
sub_finish_plan = f"finish sub plan:**{plan['content']}**"
|
|
249
|
-
|
|
250
|
-
for plan in plan_list:
|
|
251
|
-
if plan["status"] == "pending":
|
|
252
|
-
plan["status"] = "in_progress"
|
|
253
|
-
sub_next_plan = f",next plan:**{plan['content']}**"
|
|
254
|
-
break
|
|
255
|
-
|
|
256
|
-
return Command(
|
|
257
|
-
update={
|
|
258
|
-
"plan": plan_list,
|
|
259
|
-
msg_key: [
|
|
260
|
-
ToolMessage(
|
|
261
|
-
content=sub_finish_plan + sub_next_plan,
|
|
262
|
-
tool_call_id=runtime.tool_call_id,
|
|
263
|
-
)
|
|
264
|
-
],
|
|
265
|
-
}
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
return finish_sub_plan
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def create_read_plan_tool(
|
|
272
|
-
description: Optional[str] = None,
|
|
273
|
-
):
|
|
274
|
-
"""Create a tool for reading all sub-plans.
|
|
275
|
-
|
|
276
|
-
This function creates a tool that allows agents to read all sub-plans
|
|
277
|
-
in the current plan with their status information.
|
|
278
|
-
|
|
279
|
-
Args:
|
|
280
|
-
description: The description of the tool. Uses default description if not provided.
|
|
281
|
-
|
|
282
|
-
Returns:
|
|
283
|
-
BaseTool: The tool for reading all sub-plans.
|
|
284
|
-
|
|
285
|
-
Example:
|
|
286
|
-
Basic usage:
|
|
287
|
-
>>> from langchain_dev_utils.agents.middleware import create_read_plan_tool
|
|
288
|
-
>>> read_plan_tool = create_read_plan_tool()
|
|
289
|
-
"""
|
|
290
|
-
|
|
291
|
-
@tool(
|
|
292
|
-
description=description or _DEFAULT_READ_PLAN_TOOL_DESCRIPTION,
|
|
293
|
-
)
|
|
294
|
-
def read_plan(runtime: ToolRuntime):
|
|
295
|
-
plan_list = runtime.state.get("plan", [])
|
|
296
|
-
return json.dumps(plan_list)
|
|
297
|
-
|
|
298
|
-
return read_plan
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
_READ_PLAN_SYSTEM_PROMPT = """### 3. read_plan: View Current Plan
|
|
302
|
-
- **Purpose**: Retrieve the full current plan list with statuses, especially when you forget which sub-plan you're supposed to execute next.
|
|
303
|
-
- **No parameters required**—returns a complete snapshot of the active plan.
|
|
304
|
-
"""
|
|
305
|
-
_PLAN_MIDDLEWARE_SYSTEM_PROMPT = """
|
|
306
|
-
You can manage task plans using the following {num} tools:
|
|
307
|
-
|
|
308
|
-
## 1. write_plan: Create or Replace a Plan
|
|
309
|
-
- **Primary Purpose**: Generate a structured execution framework for complex tasks, or completely replace the remaining plan sequence when the current plan is no longer valid.
|
|
310
|
-
- **When to Use**:
|
|
311
|
-
- The task requires 3 or more distinct, actionable steps
|
|
312
|
-
- The user explicitly requests a plan list or "to-do plan"
|
|
313
|
-
- The user provides multiple plans (e.g., numbered list, comma-separated items)
|
|
314
|
-
- Execution reveals fundamental flaws in the current plan, requiring a new direction
|
|
315
|
-
- **Input Format**: A list of plan description strings, e.g., ["Analyze user needs", "Design system architecture", "Implement core module"]
|
|
316
|
-
- **Automatic Status Assignment**:
|
|
317
|
-
- First plan → `"in_progress"`
|
|
318
|
-
- All subsequent plans → `"pending"`
|
|
319
|
-
- **Plan Replacement Rule**: Provide **only the new plans that should be executed next**. Do not include completed, obsolete, or irrelevant plans.
|
|
320
|
-
- **Plan Quality Requirements**:
|
|
321
|
-
- Plans must be specific, actionable, and verifiable
|
|
322
|
-
- Break work into logical phases (chronological or dependency-based)
|
|
323
|
-
- Define clear milestones and deliverable standards
|
|
324
|
-
- Avoid vague, ambiguous, or non-executable descriptions
|
|
325
|
-
- **Do NOT Use When**:
|
|
326
|
-
- The task is simple (<3 steps)
|
|
327
|
-
- The request is conversational, informational, or a one-off query
|
|
328
|
-
- You only need to mark a plan as complete (use `finish_sub_plan` instead)
|
|
329
|
-
|
|
330
|
-
## 2. finish_sub_plan: Mark Current Plan as Complete
|
|
331
|
-
- **Primary Purpose**: Confirm the current `"in_progress"` plan is fully done, mark it as `"done"`, and automatically promote the first `"pending"` plan to `"in_progress"`.
|
|
332
|
-
- **Call Only If ALL Conditions Are Met**:
|
|
333
|
-
- The sub-plan has been **fully executed**
|
|
334
|
-
- All specified requirements have been satisfied
|
|
335
|
-
- There are no unresolved errors, omissions, or blockers
|
|
336
|
-
- The output meets quality standards and has been verified
|
|
337
|
-
- **Automatic Behavior**:
|
|
338
|
-
- No parameters needed—status transitions are handled internally
|
|
339
|
-
- If no `"pending"` plans remain, the plan ends naturally
|
|
340
|
-
- **Never Call If**:
|
|
341
|
-
- The plan is partially complete
|
|
342
|
-
- Known issues or defects remain
|
|
343
|
-
- Execution was blocked due to missing resources or dependencies
|
|
344
|
-
- The result fails to meet expected quality
|
|
345
|
-
|
|
346
|
-
{read_plan_system_prompt}
|
|
347
|
-
|
|
348
|
-
## Plan Status Rules (Only These Three Are Valid)
|
|
349
|
-
- **`"pending"`**: Plan not yet started
|
|
350
|
-
- **`"in_progress"`**: Currently being executed (exactly one allowed at any time)
|
|
351
|
-
- **`"done"`**: Fully completed and verified
|
|
352
|
-
> ⚠️ No other status values (e.g., "completed", "failed", "blocked") are permitted.
|
|
353
|
-
|
|
354
|
-
## General Usage Principles
|
|
355
|
-
1. **Execute simple plans directly**: If a request can be fulfilled in 1–2 steps, do not create a plan—just complete it.
|
|
356
|
-
2. **Decompose thoughtfully**: Break complex work into clear, independent, trackable sub-plans.
|
|
357
|
-
3. **Manage status rigorously**:
|
|
358
|
-
- Always maintain exactly one `"in_progress"` plan while work is ongoing
|
|
359
|
-
- Call `finish_sub_plan` immediately after plan completion—never delay
|
|
360
|
-
4. **Plan modification = full replacement**: Never edit individual plans. To adjust the plan, use `write_plan` with a new list of remaining plans.
|
|
361
|
-
5. **Respect user intent**: If the user explicitly asks for a plan—even for a simpler task—honor the request and create one.
|
|
362
|
-
"""
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
class PlanMiddleware(AgentMiddleware):
|
|
366
|
-
"""Middleware that provides plan management capabilities to agents.
|
|
367
|
-
|
|
368
|
-
This middleware adds a `write_plan` and `finish_sub_plan` (and `read_plan` optional) tool that allows agents to create and manage
|
|
369
|
-
structured plan lists for complex multi-step operations. It's designed to help
|
|
370
|
-
agents track progress, organize complex tasks, and provide users with visibility
|
|
371
|
-
into task completion status.
|
|
372
|
-
|
|
373
|
-
The middleware automatically injects system prompts that guide the agent on how to use the plan functionality effectively.
|
|
374
|
-
|
|
375
|
-
Args:
|
|
376
|
-
system_prompt: Custom system prompt to guide the agent on using the plan tool.
|
|
377
|
-
If not provided, uses the default `_PLAN_MIDDLEWARE_SYSTEM_PROMPT`.
|
|
378
|
-
tools: List of tools to be added to the agent. The tools must be created by `create_write_plan_tool`, `create_finish_sub_plan_tool`, and `create_read_plan_tool`(optional).
|
|
379
|
-
|
|
380
|
-
Example:
|
|
381
|
-
```python
|
|
382
|
-
from langchain_dev_utils.agents.middleware.plan import PlanMiddleware
|
|
383
|
-
from langchain_dev_utils.agents import create_agent
|
|
384
|
-
|
|
385
|
-
agent = create_agent("vllm:qwen3-4b", middleware=[PlanMiddleware()])
|
|
386
|
-
|
|
387
|
-
# Agent now has access to write_plan tool and plan state tracking
|
|
388
|
-
result = await agent.invoke({"messages": [HumanMessage("Help me refactor my codebase")]})
|
|
389
|
-
|
|
390
|
-
print(result["plan"]) # Array of plan items with status tracking
|
|
391
|
-
```
|
|
392
|
-
"""
|
|
393
|
-
|
|
394
|
-
state_schema = PlanState
|
|
395
|
-
|
|
396
|
-
def __init__(
|
|
397
|
-
self,
|
|
398
|
-
*,
|
|
399
|
-
system_prompt: Optional[str] = None,
|
|
400
|
-
tools: Optional[list[BaseTool]] = None,
|
|
401
|
-
) -> None:
|
|
402
|
-
super().__init__()
|
|
403
|
-
|
|
404
|
-
if tools is None:
|
|
405
|
-
tools = [
|
|
406
|
-
create_write_plan_tool(),
|
|
407
|
-
create_finish_sub_plan_tool(),
|
|
408
|
-
create_read_plan_tool(),
|
|
409
|
-
]
|
|
410
|
-
|
|
411
|
-
# Check if required tools exist
|
|
412
|
-
has_write_plan = any(tool_obj.name == "write_plan" for tool_obj in tools)
|
|
413
|
-
has_finish_sub_plan = any(
|
|
414
|
-
tool_obj.name == "finish_sub_plan" for tool_obj in tools
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
if not (has_write_plan and has_finish_sub_plan):
|
|
418
|
-
raise ValueError(
|
|
419
|
-
"PlanMiddleware requires exactly two tools: write_plan and finish_sub_plan."
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
self.tools = tools
|
|
423
|
-
|
|
424
|
-
if system_prompt is None:
|
|
425
|
-
num = len(self.tools)
|
|
426
|
-
read_plan_system = (
|
|
427
|
-
_READ_PLAN_SYSTEM_PROMPT if num == 3 else ""
|
|
428
|
-
) # if read_plan tool is not provided, do not include it in the system prompt
|
|
429
|
-
|
|
430
|
-
system_prompt = _PLAN_MIDDLEWARE_SYSTEM_PROMPT.format(
|
|
431
|
-
num=num, read_plan_system_prompt=read_plan_system
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
self.system_prompt = system_prompt
|
|
435
|
-
|
|
436
|
-
def wrap_model_call(
|
|
437
|
-
self,
|
|
438
|
-
request: ModelRequest,
|
|
439
|
-
handler: Callable[[ModelRequest], ModelResponse],
|
|
440
|
-
) -> ModelCallResult:
|
|
441
|
-
request.system_prompt = (
|
|
442
|
-
request.system_prompt + "\n\n" + self.system_prompt
|
|
443
|
-
if request.system_prompt
|
|
444
|
-
else self.system_prompt
|
|
445
|
-
)
|
|
446
|
-
return handler(request)
|
|
447
|
-
|
|
448
|
-
async def awrap_model_call(
|
|
449
|
-
self,
|
|
450
|
-
request: ModelRequest,
|
|
451
|
-
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
452
|
-
) -> ModelCallResult:
|
|
453
|
-
request.system_prompt = (
|
|
454
|
-
request.system_prompt + "\n\n" + self.system_prompt
|
|
455
|
-
if request.system_prompt
|
|
456
|
-
else self.system_prompt
|
|
457
|
-
)
|
|
458
|
-
return await handler(request)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/__init__.py
RENAMED
|
File without changes
|
{langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/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
|
{langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/plan.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/embeddings/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{langchain_dev_utils-1.1.10 → langchain_dev_utils-1.1.12}/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
|