langchain-agentx-python 2.1.4__py3-none-any.whl → 2.1.6__py3-none-any.whl
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_agentx/__init__.py +1 -1
- langchain_agentx/loop/__init__.py +2 -1
- langchain_agentx/loop/graph/builtin_loop_control.py +60 -20
- langchain_agentx/loop/graph/runtime_tools_node.py +64 -18
- langchain_agentx/loop/loop_abort.py +34 -3
- langchain_agentx/loop/model/model_node.py +78 -30
- langchain_agentx/loop/subagent/__init__.py +6 -0
- langchain_agentx/loop/subagent/async_runner.py +149 -72
- langchain_agentx/loop/subagent/orchestrator.py +84 -8
- langchain_agentx/loop/subagent/runner.py +119 -22
- langchain_agentx/tools/read/models.py +2 -1
- langchain_agentx/tools/read/tool.py +2 -6
- {langchain_agentx_python-2.1.4.dist-info → langchain_agentx_python-2.1.6.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.1.4.dist-info → langchain_agentx_python-2.1.6.dist-info}/RECORD +17 -17
- {langchain_agentx_python-2.1.4.dist-info → langchain_agentx_python-2.1.6.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.1.4.dist-info → langchain_agentx_python-2.1.6.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.1.4.dist-info → langchain_agentx_python-2.1.6.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -35,13 +35,14 @@ for chunk in agent.stream({"messages": [{"role": "user", "content": "Hello"}]}):
|
|
|
35
35
|
from .graph.factory import (
|
|
36
36
|
create_loop_agent,
|
|
37
37
|
)
|
|
38
|
-
from .loop_abort import LoopAbortSignal
|
|
38
|
+
from .loop_abort import LoopAbortSignal, raise_if_cancel_requested
|
|
39
39
|
from .config import build_agent_config
|
|
40
40
|
|
|
41
41
|
__all__ = [
|
|
42
42
|
# 主要接口
|
|
43
43
|
"create_loop_agent",
|
|
44
44
|
"LoopAbortSignal",
|
|
45
|
+
"raise_if_cancel_requested",
|
|
45
46
|
# 配置助手
|
|
46
47
|
"build_agent_config",
|
|
47
48
|
]
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
内置 loop 控制:捕获 ``LoopAbortSignal``,写入 ``abort_terminal_reason``(及工具侧合成 ToolMessage)。
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
由 ModelNode / RuntimeToolsNode 在 invoke 路径最外层调用;handler 前读
|
|
5
|
+
``raise_if_cancel_requested``(Doc-35 P1 / D-ABORT-1)。用户侧抛出 ``LoopAbortSignal``
|
|
6
|
+
即可,无需手写 Command。
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
11
|
+
import asyncio
|
|
10
12
|
import copy
|
|
11
13
|
import logging
|
|
12
14
|
from typing import Any
|
|
@@ -21,7 +23,7 @@ from ..exit.reason_codes import (
|
|
|
21
23
|
TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
22
24
|
TERMINAL_REASON_HOOK_STOPPED,
|
|
23
25
|
)
|
|
24
|
-
from ..loop_abort import LoopAbortSignal
|
|
26
|
+
from ..loop_abort import LoopAbortSignal, raise_if_cancel_requested
|
|
25
27
|
from ..model.orphan_tool_results import synthetic_tool_messages_for_orphan_tool_calls
|
|
26
28
|
from ...tool_runtime.classifier_denial_limit import ClassifierDenialLimitAbort
|
|
27
29
|
from ...tool_runtime.permission_context import clear_auto_approved_window
|
|
@@ -50,46 +52,51 @@ def _session_store_from_request(request: Any) -> Any | None:
|
|
|
50
52
|
return None
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
def _model_abort_response(sig: LoopAbortSignal) -> ExtendedModelResponse:
|
|
56
|
+
ai = AIMessage(
|
|
57
|
+
content=_abort_model_content(sig.terminal_reason, sig.detail),
|
|
58
|
+
response_metadata={"finish_reason": "stop"},
|
|
59
|
+
)
|
|
60
|
+
return ExtendedModelResponse(
|
|
61
|
+
model_response=ModelResponse(result=[ai], structured_response=None),
|
|
62
|
+
command=Command(update={"abort_terminal_reason": sig.terminal_reason}),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
53
66
|
class _LoopBuiltinModelControl:
|
|
54
|
-
"""
|
|
67
|
+
"""内部单例:协作读停令 + 捕获模型侧的 ``LoopAbortSignal``。"""
|
|
55
68
|
|
|
56
69
|
def wrap_model_call(self, request: Any, handler: Any) -> Any:
|
|
57
70
|
clear_auto_approved_window(_session_store_from_request(request))
|
|
58
71
|
try:
|
|
72
|
+
raise_if_cancel_requested(phase="model")
|
|
59
73
|
return handler(request)
|
|
74
|
+
except asyncio.CancelledError:
|
|
75
|
+
raise_if_cancel_requested(phase="model")
|
|
76
|
+
raise
|
|
60
77
|
except LoopAbortSignal as sig:
|
|
61
78
|
if sig.terminal_reason not in (
|
|
62
79
|
TERMINAL_REASON_ABORTED_STREAMING,
|
|
63
80
|
TERMINAL_REASON_HOOK_STOPPED,
|
|
64
81
|
):
|
|
65
82
|
raise
|
|
66
|
-
|
|
67
|
-
content=_abort_model_content(sig.terminal_reason, sig.detail),
|
|
68
|
-
response_metadata={"finish_reason": "stop"},
|
|
69
|
-
)
|
|
70
|
-
return ExtendedModelResponse(
|
|
71
|
-
model_response=ModelResponse(result=[ai], structured_response=None),
|
|
72
|
-
command=Command(update={"abort_terminal_reason": sig.terminal_reason}),
|
|
73
|
-
)
|
|
83
|
+
return _model_abort_response(sig)
|
|
74
84
|
|
|
75
85
|
async def awrap_model_call(self, request: Any, handler: Any) -> Any:
|
|
76
86
|
clear_auto_approved_window(_session_store_from_request(request))
|
|
77
87
|
try:
|
|
88
|
+
raise_if_cancel_requested(phase="model")
|
|
78
89
|
return await handler(request)
|
|
90
|
+
except asyncio.CancelledError:
|
|
91
|
+
raise_if_cancel_requested(phase="model")
|
|
92
|
+
raise
|
|
79
93
|
except LoopAbortSignal as sig:
|
|
80
94
|
if sig.terminal_reason not in (
|
|
81
95
|
TERMINAL_REASON_ABORTED_STREAMING,
|
|
82
96
|
TERMINAL_REASON_HOOK_STOPPED,
|
|
83
97
|
):
|
|
84
98
|
raise
|
|
85
|
-
|
|
86
|
-
content=_abort_model_content(sig.terminal_reason, sig.detail),
|
|
87
|
-
response_metadata={"finish_reason": "stop"},
|
|
88
|
-
)
|
|
89
|
-
return ExtendedModelResponse(
|
|
90
|
-
model_response=ModelResponse(result=[ai], structured_response=None),
|
|
91
|
-
command=Command(update={"abort_terminal_reason": sig.terminal_reason}),
|
|
92
|
-
)
|
|
99
|
+
return _model_abort_response(sig)
|
|
93
100
|
|
|
94
101
|
|
|
95
102
|
_LOOP_BUILTIN_MODEL_SINGLETON = _LoopBuiltinModelControl()
|
|
@@ -173,8 +180,12 @@ def _tool_abort_command(
|
|
|
173
180
|
|
|
174
181
|
def builtin_sync_tool_wrapper(request: Any, handler: Any) -> Any:
|
|
175
182
|
try:
|
|
183
|
+
raise_if_cancel_requested(phase="tools")
|
|
176
184
|
result = handler(_with_injected_agent_tool_model(request))
|
|
177
185
|
return apply_tool_message_status_to_result(result)
|
|
186
|
+
except asyncio.CancelledError:
|
|
187
|
+
raise_if_cancel_requested(phase="tools")
|
|
188
|
+
raise
|
|
178
189
|
except ClassifierDenialLimitAbort as exc:
|
|
179
190
|
return _tool_abort_command(
|
|
180
191
|
request,
|
|
@@ -192,8 +203,12 @@ def builtin_sync_tool_wrapper(request: Any, handler: Any) -> Any:
|
|
|
192
203
|
|
|
193
204
|
async def builtin_async_tool_wrapper(request: Any, handler: Any) -> Any:
|
|
194
205
|
try:
|
|
206
|
+
raise_if_cancel_requested(phase="tools")
|
|
195
207
|
result = await handler(_with_injected_agent_tool_model(request))
|
|
196
208
|
return apply_tool_message_status_to_result(result)
|
|
209
|
+
except asyncio.CancelledError:
|
|
210
|
+
raise_if_cancel_requested(phase="tools")
|
|
211
|
+
raise
|
|
197
212
|
except ClassifierDenialLimitAbort as exc:
|
|
198
213
|
return _tool_abort_command(
|
|
199
214
|
request,
|
|
@@ -209,8 +224,33 @@ async def builtin_async_tool_wrapper(request: Any, handler: Any) -> Any:
|
|
|
209
224
|
)
|
|
210
225
|
|
|
211
226
|
|
|
227
|
+
def tools_batch_abort_command(
|
|
228
|
+
tool_calls: list[dict[str, Any]],
|
|
229
|
+
*,
|
|
230
|
+
terminal_reason: str = TERMINAL_REASON_ABORTED_TOOLS,
|
|
231
|
+
content_template: str | None = None,
|
|
232
|
+
) -> Command:
|
|
233
|
+
"""tools 节点批次入口 abort:合成 orphan ToolMessage + abort_terminal_reason。"""
|
|
234
|
+
synth = (
|
|
235
|
+
synthetic_tool_messages_for_orphan_tool_calls(
|
|
236
|
+
pending_tool_calls=list(tool_calls),
|
|
237
|
+
reason="user_interrupted",
|
|
238
|
+
content_template=content_template,
|
|
239
|
+
)
|
|
240
|
+
if tool_calls
|
|
241
|
+
else []
|
|
242
|
+
)
|
|
243
|
+
return Command(
|
|
244
|
+
update={
|
|
245
|
+
"abort_terminal_reason": terminal_reason,
|
|
246
|
+
"messages": synth,
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
212
251
|
__all__ = [
|
|
213
252
|
"builtin_async_tool_wrapper",
|
|
214
253
|
"builtin_sync_tool_wrapper",
|
|
215
254
|
"get_loop_builtin_model_middleware",
|
|
255
|
+
"tools_batch_abort_command",
|
|
216
256
|
]
|
|
@@ -9,17 +9,21 @@ loop/graph/runtime_tools_node.py — 新 tools 执行 graph bridge
|
|
|
9
9
|
|
|
10
10
|
当前裁剪范围:
|
|
11
11
|
Phase 4 readonly_parallel 由 engine 对 safe_group 受控并发;exclusive 仍串行。
|
|
12
|
+
P1:批次入口读 cancel_requested → LoopAbortSignal 单轨(D-ABORT-1)。
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
from __future__ import annotations
|
|
15
16
|
|
|
17
|
+
import asyncio
|
|
16
18
|
from typing import Any, MutableMapping
|
|
17
19
|
|
|
18
20
|
from langchain_core.messages import AIMessage, ToolMessage
|
|
19
21
|
from langchain_core.runnables import Runnable, RunnableConfig
|
|
20
22
|
from langchain_core.tools import BaseTool
|
|
23
|
+
from langgraph.types import Command
|
|
21
24
|
|
|
22
25
|
from ...tool_runtime.adapter import LangChainAdapter
|
|
26
|
+
from ...tool_runtime.classifier_denial_limit import ClassifierDenialLimitAbort
|
|
23
27
|
from ...tool_runtime.execution import (
|
|
24
28
|
EngineConfig,
|
|
25
29
|
EnginePolicy,
|
|
@@ -29,6 +33,12 @@ from ...tool_runtime.execution import (
|
|
|
29
33
|
)
|
|
30
34
|
from ...tool_runtime.pipeline import ToolExecutorPipeline
|
|
31
35
|
from ...tool_runtime.registry import RuntimeToolRegistry
|
|
36
|
+
from ..exit.reason_codes import (
|
|
37
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
38
|
+
TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
39
|
+
)
|
|
40
|
+
from ..loop_abort import LoopAbortSignal, raise_if_cancel_requested
|
|
41
|
+
from .builtin_loop_control import tools_batch_abort_command
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
class RuntimeToolsNode(Runnable):
|
|
@@ -87,35 +97,71 @@ class RuntimeToolsNode(Runnable):
|
|
|
87
97
|
self,
|
|
88
98
|
state: MutableMapping[str, Any],
|
|
89
99
|
config: RunnableConfig | None,
|
|
90
|
-
) -> dict[str, Any]:
|
|
100
|
+
) -> dict[str, Any] | Command:
|
|
91
101
|
_ai_message, tool_calls = _extract_tool_calls(state)
|
|
92
102
|
if not tool_calls:
|
|
93
103
|
return {}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
batches=
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
try:
|
|
105
|
+
raise_if_cancel_requested(phase="tools")
|
|
106
|
+
batches = self._planner.plan(tool_calls)
|
|
107
|
+
records = self._engine.execute_turn(
|
|
108
|
+
batches=batches,
|
|
109
|
+
state=state,
|
|
110
|
+
config=config,
|
|
111
|
+
)
|
|
112
|
+
tool_messages = self._message_adapter.records_to_graph_messages(records)
|
|
113
|
+
return {"messages": tool_messages}
|
|
114
|
+
except asyncio.CancelledError:
|
|
115
|
+
raise_if_cancel_requested(phase="tools")
|
|
116
|
+
raise
|
|
117
|
+
except ClassifierDenialLimitAbort as exc:
|
|
118
|
+
return tools_batch_abort_command(
|
|
119
|
+
tool_calls,
|
|
120
|
+
terminal_reason=TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
121
|
+
content_template=exc.message,
|
|
122
|
+
)
|
|
123
|
+
except LoopAbortSignal as sig:
|
|
124
|
+
if sig.terminal_reason != TERMINAL_REASON_ABORTED_TOOLS:
|
|
125
|
+
raise
|
|
126
|
+
return tools_batch_abort_command(
|
|
127
|
+
tool_calls,
|
|
128
|
+
terminal_reason=TERMINAL_REASON_ABORTED_TOOLS,
|
|
129
|
+
)
|
|
102
130
|
|
|
103
131
|
async def _arun(
|
|
104
132
|
self,
|
|
105
133
|
state: MutableMapping[str, Any],
|
|
106
134
|
config: RunnableConfig | None,
|
|
107
|
-
) -> dict[str, Any]:
|
|
135
|
+
) -> dict[str, Any] | Command:
|
|
108
136
|
_ai_message, tool_calls = _extract_tool_calls(state)
|
|
109
137
|
if not tool_calls:
|
|
110
138
|
return {}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
batches=
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
try:
|
|
140
|
+
raise_if_cancel_requested(phase="tools")
|
|
141
|
+
batches = self._planner.plan(tool_calls)
|
|
142
|
+
records = await self._engine.aexecute_turn(
|
|
143
|
+
batches=batches,
|
|
144
|
+
state=state,
|
|
145
|
+
config=config,
|
|
146
|
+
)
|
|
147
|
+
tool_messages = self._message_adapter.records_to_graph_messages(records)
|
|
148
|
+
return {"messages": tool_messages}
|
|
149
|
+
except asyncio.CancelledError:
|
|
150
|
+
raise_if_cancel_requested(phase="tools")
|
|
151
|
+
raise
|
|
152
|
+
except ClassifierDenialLimitAbort as exc:
|
|
153
|
+
return tools_batch_abort_command(
|
|
154
|
+
tool_calls,
|
|
155
|
+
terminal_reason=TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
156
|
+
content_template=exc.message,
|
|
157
|
+
)
|
|
158
|
+
except LoopAbortSignal as sig:
|
|
159
|
+
if sig.terminal_reason != TERMINAL_REASON_ABORTED_TOOLS:
|
|
160
|
+
raise
|
|
161
|
+
return tools_batch_abort_command(
|
|
162
|
+
tool_calls,
|
|
163
|
+
terminal_reason=TERMINAL_REASON_ABORTED_TOOLS,
|
|
164
|
+
)
|
|
119
165
|
|
|
120
166
|
|
|
121
167
|
def _extract_tool_calls(
|
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
"""流式 / 工具 / hook 中断信号:由业务 middleware 或工具抛出,由内置 wrapper 转为 state 契约。
|
|
1
|
+
"""流式 / 工具 / hook 中断信号:由业务 middleware 或工具抛出,由内置 wrapper 转为 state 契约。
|
|
2
|
+
|
|
3
|
+
P1(Doc-35 D-ABORT-1):``raise_if_cancel_requested`` 读 ``RunContext.cancel_requested``,
|
|
4
|
+
真则抛既有 ``LoopAbortSignal``(单轨);禁止平行 Abort 异常族。
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
5
11
|
from .exit.reason_codes import (
|
|
6
12
|
TERMINAL_REASON_ABORTED_STREAMING,
|
|
7
13
|
TERMINAL_REASON_ABORTED_TOOLS,
|
|
8
14
|
TERMINAL_REASON_HOOK_STOPPED,
|
|
9
15
|
)
|
|
10
16
|
|
|
17
|
+
CancelAbortPhase = Literal["model", "tools"]
|
|
18
|
+
|
|
11
19
|
|
|
12
20
|
class LoopAbortSignal(Exception):
|
|
13
21
|
"""请求以 CC 式 abort 终局结束(写入 ``abort_terminal_reason``)。
|
|
14
22
|
|
|
15
23
|
- ``aborted_streaming`` / ``hook_stopped``:在 ``wrap_model_call`` / ``awrap_model_call`` 中抛出,
|
|
16
|
-
由 ``
|
|
24
|
+
由 ``_LoopBuiltinModelControl`` 捕获并合成 AIMessage + Command。
|
|
17
25
|
- ``aborted_tools``:在工具执行或 ``wrap_tool_call`` 中抛出,由内置 tool wrapper 捕获并
|
|
18
26
|
注入合成 ``ToolMessage`` + ``abort_terminal_reason``。
|
|
19
27
|
"""
|
|
@@ -33,4 +41,27 @@ class LoopAbortSignal(Exception):
|
|
|
33
41
|
super().__init__(detail or terminal_reason)
|
|
34
42
|
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
def raise_if_cancel_requested(*, phase: CancelAbortPhase) -> None:
|
|
45
|
+
"""若当前 ``RunContext.cancel_requested`` 为真,抛既有 ``LoopAbortSignal``。
|
|
46
|
+
|
|
47
|
+
对齐 CC ``query.ts`` 读 ``signal.aborted``;SDK 原语为 bool(D-SEM-1),
|
|
48
|
+
退出语义仅走 ``LoopAbortSignal`` → ``builtin_loop_control``(D-ABORT-1)。
|
|
49
|
+
无 RunContext 时 no-op。
|
|
50
|
+
"""
|
|
51
|
+
from .run_context import get_run_context
|
|
52
|
+
|
|
53
|
+
ctx = get_run_context()
|
|
54
|
+
if ctx is None or not ctx.cancel_requested:
|
|
55
|
+
return
|
|
56
|
+
if phase == "tools":
|
|
57
|
+
raise LoopAbortSignal(
|
|
58
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
59
|
+
detail="cancel_requested",
|
|
60
|
+
)
|
|
61
|
+
raise LoopAbortSignal(
|
|
62
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
63
|
+
detail="cancel_requested",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = ["CancelAbortPhase", "LoopAbortSignal", "raise_if_cancel_requested"]
|
|
@@ -338,55 +338,103 @@ class ModelNode(Runnable):
|
|
|
338
338
|
|
|
339
339
|
def _run(self, state: dict, config: RunnableConfig) -> dict:
|
|
340
340
|
"""核心同步执行逻辑。"""
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
from langchain.agents.middleware.types import ExtendedModelResponse
|
|
342
|
+
from langgraph.types import Command
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
name=self._name,
|
|
347
|
-
initial_response_format=self._initial_response_format,
|
|
348
|
-
structured_output_tools=self._structured_output_tools,
|
|
349
|
-
compaction_settings=self._compaction_settings,
|
|
350
|
-
query_source=state.get("query_source")
|
|
351
|
-
if isinstance(state.get("query_source"), str)
|
|
352
|
-
else None,
|
|
344
|
+
from langchain_agentx.loop.graph.builtin_loop_control import (
|
|
345
|
+
get_loop_builtin_model_middleware,
|
|
353
346
|
)
|
|
354
|
-
|
|
355
|
-
|
|
347
|
+
|
|
348
|
+
runtime = config.get("configurable", {}).get("__pregel_runtime")
|
|
349
|
+
request = self._build_request(state, runtime, config=config)
|
|
350
|
+
mw = get_loop_builtin_model_middleware()
|
|
351
|
+
|
|
352
|
+
def _handler(req: Any) -> Any:
|
|
353
|
+
synth = _model_response_if_context_token_blocked(
|
|
354
|
+
state_messages=state.get("messages", []),
|
|
355
|
+
name=self._name,
|
|
356
|
+
initial_response_format=self._initial_response_format,
|
|
357
|
+
structured_output_tools=self._structured_output_tools,
|
|
358
|
+
compaction_settings=self._compaction_settings,
|
|
359
|
+
query_source=state.get("query_source")
|
|
360
|
+
if isinstance(state.get("query_source"), str)
|
|
361
|
+
else None,
|
|
362
|
+
)
|
|
363
|
+
if synth is not None:
|
|
364
|
+
return synth
|
|
365
|
+
model_response, _ = self._run_sync(req, state, config=config)
|
|
366
|
+
return model_response
|
|
367
|
+
|
|
368
|
+
wrapped = mw.wrap_model_call(request, _handler)
|
|
369
|
+
abort_reason: str | None = None
|
|
370
|
+
if isinstance(wrapped, ExtendedModelResponse):
|
|
371
|
+
model_response = wrapped.model_response
|
|
372
|
+
cmd = wrapped.command
|
|
373
|
+
if isinstance(cmd, Command) and isinstance(cmd.update, dict):
|
|
374
|
+
reason = cmd.update.get("abort_terminal_reason")
|
|
375
|
+
if isinstance(reason, str):
|
|
376
|
+
abort_reason = reason
|
|
356
377
|
else:
|
|
357
|
-
model_response
|
|
378
|
+
model_response = wrapped
|
|
358
379
|
|
|
359
|
-
|
|
380
|
+
updates = self._build_state_update(
|
|
360
381
|
state=state,
|
|
361
382
|
model_response=model_response,
|
|
362
383
|
mode="sync",
|
|
363
384
|
)
|
|
385
|
+
if abort_reason:
|
|
386
|
+
return list(updates) + [Command(update={"abort_terminal_reason": abort_reason})]
|
|
387
|
+
return updates
|
|
364
388
|
|
|
365
389
|
async def _arun(self, state: dict, config: RunnableConfig) -> dict:
|
|
366
390
|
"""核心异步执行逻辑。"""
|
|
367
|
-
|
|
368
|
-
|
|
391
|
+
from langchain.agents.middleware.types import ExtendedModelResponse
|
|
392
|
+
from langgraph.types import Command
|
|
369
393
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
name=self._name,
|
|
373
|
-
initial_response_format=self._initial_response_format,
|
|
374
|
-
structured_output_tools=self._structured_output_tools,
|
|
375
|
-
compaction_settings=self._compaction_settings,
|
|
376
|
-
query_source=state.get("query_source")
|
|
377
|
-
if isinstance(state.get("query_source"), str)
|
|
378
|
-
else None,
|
|
394
|
+
from langchain_agentx.loop.graph.builtin_loop_control import (
|
|
395
|
+
get_loop_builtin_model_middleware,
|
|
379
396
|
)
|
|
380
|
-
|
|
381
|
-
|
|
397
|
+
|
|
398
|
+
runtime = config.get("configurable", {}).get("__pregel_runtime")
|
|
399
|
+
request = self._build_request(state, runtime, config=config)
|
|
400
|
+
mw = get_loop_builtin_model_middleware()
|
|
401
|
+
|
|
402
|
+
async def _handler(req: Any) -> Any:
|
|
403
|
+
synth = _model_response_if_context_token_blocked(
|
|
404
|
+
state_messages=state.get("messages", []),
|
|
405
|
+
name=self._name,
|
|
406
|
+
initial_response_format=self._initial_response_format,
|
|
407
|
+
structured_output_tools=self._structured_output_tools,
|
|
408
|
+
compaction_settings=self._compaction_settings,
|
|
409
|
+
query_source=state.get("query_source")
|
|
410
|
+
if isinstance(state.get("query_source"), str)
|
|
411
|
+
else None,
|
|
412
|
+
)
|
|
413
|
+
if synth is not None:
|
|
414
|
+
return synth
|
|
415
|
+
model_response, _ = await self._arun_async(req, state, config=config)
|
|
416
|
+
return model_response
|
|
417
|
+
|
|
418
|
+
wrapped = await mw.awrap_model_call(request, _handler)
|
|
419
|
+
abort_reason: str | None = None
|
|
420
|
+
if isinstance(wrapped, ExtendedModelResponse):
|
|
421
|
+
model_response = wrapped.model_response
|
|
422
|
+
cmd = wrapped.command
|
|
423
|
+
if isinstance(cmd, Command) and isinstance(cmd.update, dict):
|
|
424
|
+
reason = cmd.update.get("abort_terminal_reason")
|
|
425
|
+
if isinstance(reason, str):
|
|
426
|
+
abort_reason = reason
|
|
382
427
|
else:
|
|
383
|
-
model_response
|
|
428
|
+
model_response = wrapped
|
|
384
429
|
|
|
385
|
-
|
|
430
|
+
updates = self._build_state_update(
|
|
386
431
|
state=state,
|
|
387
432
|
model_response=model_response,
|
|
388
433
|
mode="async",
|
|
389
434
|
)
|
|
435
|
+
if abort_reason:
|
|
436
|
+
return list(updates) + [Command(update={"abort_terminal_reason": abort_reason})]
|
|
437
|
+
return updates
|
|
390
438
|
|
|
391
439
|
# ===== 请求构建 =====
|
|
392
440
|
|
|
@@ -28,7 +28,10 @@ from langchain_agentx.loop.subagent.forked_runner import (
|
|
|
28
28
|
)
|
|
29
29
|
from langchain_agentx.loop.subagent.async_runner import (
|
|
30
30
|
BackgroundAgentHandle,
|
|
31
|
+
BackgroundAgentLoopRequiredError,
|
|
31
32
|
get_background_agent_handle,
|
|
33
|
+
kill_all_background_agents,
|
|
34
|
+
kill_background_agent,
|
|
32
35
|
register_background_agent,
|
|
33
36
|
run_async_agent_lifecycle,
|
|
34
37
|
)
|
|
@@ -67,7 +70,10 @@ __all__ = [
|
|
|
67
70
|
"_run_coroutine_sync",
|
|
68
71
|
"register_background_agent",
|
|
69
72
|
"get_background_agent_handle",
|
|
73
|
+
"kill_background_agent",
|
|
74
|
+
"kill_all_background_agents",
|
|
70
75
|
"BackgroundAgentHandle",
|
|
76
|
+
"BackgroundAgentLoopRequiredError",
|
|
71
77
|
"run_async_agent_lifecycle",
|
|
72
78
|
"JsonlTranscriptStore",
|
|
73
79
|
"SubagentTranscriptManager",
|