langchain-dev-utils 1.1.11__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.
Files changed (54) hide show
  1. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/PKG-INFO +1 -1
  2. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/pyproject.toml +1 -1
  3. langchain_dev_utils-1.1.12/src/langchain_dev_utils/__init__.py +1 -0
  4. langchain_dev_utils-1.1.12/src/langchain_dev_utils/agents/middleware/plan.py +360 -0
  5. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/wrap.py +1 -1
  6. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/adapters/openai_compatible.py +5 -2
  7. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/base.py +4 -0
  8. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/types.py +1 -0
  9. langchain_dev_utils-1.1.11/src/langchain_dev_utils/__init__.py +0 -1
  10. langchain_dev_utils-1.1.11/src/langchain_dev_utils/agents/middleware/plan.py +0 -458
  11. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/.gitignore +0 -0
  12. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/.python-version +0 -0
  13. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/.vscode/settings.json +0 -0
  14. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/LICENSE +0 -0
  15. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/README.md +0 -0
  16. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/README_cn.md +0 -0
  17. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/__init__.py +0 -0
  18. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/factory.py +0 -0
  19. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/file_system.py +0 -0
  20. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/__init__.py +0 -0
  21. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/model_fallback.py +0 -0
  22. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/model_router.py +0 -0
  23. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/summarization.py +0 -0
  24. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/tool_emulator.py +0 -0
  25. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/middleware/tool_selection.py +0 -0
  26. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/agents/plan.py +0 -0
  27. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/__init__.py +0 -0
  28. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/chat_models/adapters/__init__.py +0 -0
  29. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/embeddings/__init__.py +0 -0
  30. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/embeddings/base.py +0 -0
  31. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/message_convert/__init__.py +0 -0
  32. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/message_convert/content.py +0 -0
  33. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/message_convert/format.py +0 -0
  34. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/__init__.py +0 -0
  35. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/parallel.py +0 -0
  36. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/sequential.py +0 -0
  37. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/pipeline/types.py +0 -0
  38. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/py.typed +0 -0
  39. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/tool_calling/__init__.py +0 -0
  40. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/tool_calling/human_in_the_loop.py +0 -0
  41. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/src/langchain_dev_utils/tool_calling/utils.py +0 -0
  42. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_agent.py +0 -0
  43. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_chat_models.py +0 -0
  44. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_human_in_the_loop.py +0 -0
  45. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_load_embbeding.py +0 -0
  46. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_load_model.py +0 -0
  47. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_messages.py +0 -0
  48. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_model_tool_emulator.py +0 -0
  49. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_pipline.py +0 -0
  50. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_plan_middleware.py +0 -0
  51. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_router_model.py +0 -0
  52. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_tool_calling.py +0 -0
  53. {langchain_dev_utils-1.1.11 → langchain_dev_utils-1.1.12}/tests/test_wrap_agent.py +0 -0
  54. {langchain_dev_utils-1.1.11 → 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.11
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "langchain-dev-utils"
3
- version = "1.1.11"
3
+ version = "1.1.12"
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" }]
@@ -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)
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from typing import Any, Awaitable, Optional, Callable, cast
2
+ from typing import Any, Awaitable, Callable, Optional, cast
3
3
 
4
4
  from langchain.tools import ToolRuntime
5
5
  from langchain_core.messages import AnyMessage, HumanMessage
@@ -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,4 +1,5 @@
1
1
  from typing import Literal, Union
2
+
2
3
  from langchain_core.language_models.chat_models import BaseChatModel
3
4
 
4
5
 
@@ -1 +0,0 @@
1
- __version__ = "1.1.11"
@@ -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)