langchain-agentx-python 2.1.8__py3-none-any.whl → 2.2.0__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 +8 -1
- langchain_agentx/loop/graph/builtin_loop_control.py +21 -5
- langchain_agentx/loop/graph/runtime_tools_node.py +11 -3
- langchain_agentx/loop/loop_abort.py +34 -14
- langchain_agentx/loop/stream/loop_driver.py +68 -0
- langchain_agentx/loop/subagent/runner.py +9 -2
- langchain_agentx/tool_runtime/adapter.py +6 -1
- langchain_agentx/tool_runtime/cancel_projection.py +85 -0
- langchain_agentx/tool_runtime/pipeline.py +6 -1
- langchain_agentx/tools/agent/tool.py +29 -3
- {langchain_agentx_python-2.1.8.dist-info → langchain_agentx_python-2.2.0.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.1.8.dist-info → langchain_agentx_python-2.2.0.dist-info}/RECORD +16 -15
- {langchain_agentx_python-2.1.8.dist-info → langchain_agentx_python-2.2.0.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.1.8.dist-info → langchain_agentx_python-2.2.0.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.1.8.dist-info → langchain_agentx_python-2.2.0.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -35,13 +35,20 @@ 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
|
|
38
|
+
from .loop_abort import (
|
|
39
|
+
LoopAbortSignal,
|
|
40
|
+
abort_signal_for_phase,
|
|
41
|
+
is_cancel_requested,
|
|
42
|
+
raise_if_cancel_requested,
|
|
43
|
+
)
|
|
39
44
|
from .config import build_agent_config
|
|
40
45
|
|
|
41
46
|
__all__ = [
|
|
42
47
|
# 主要接口
|
|
43
48
|
"create_loop_agent",
|
|
44
49
|
"LoopAbortSignal",
|
|
50
|
+
"abort_signal_for_phase",
|
|
51
|
+
"is_cancel_requested",
|
|
45
52
|
"raise_if_cancel_requested",
|
|
46
53
|
# 配置助手
|
|
47
54
|
"build_agent_config",
|
|
@@ -23,7 +23,12 @@ from ..exit.reason_codes import (
|
|
|
23
23
|
TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
24
24
|
TERMINAL_REASON_HOOK_STOPPED,
|
|
25
25
|
)
|
|
26
|
-
from ..loop_abort import
|
|
26
|
+
from ..loop_abort import (
|
|
27
|
+
LoopAbortSignal,
|
|
28
|
+
abort_signal_for_phase,
|
|
29
|
+
is_cancel_requested,
|
|
30
|
+
raise_if_cancel_requested,
|
|
31
|
+
)
|
|
27
32
|
from ..model.orphan_tool_results import synthetic_tool_messages_for_orphan_tool_calls
|
|
28
33
|
from ...tool_runtime.classifier_denial_limit import ClassifierDenialLimitAbort
|
|
29
34
|
from ...tool_runtime.permission_context import clear_auto_approved_window
|
|
@@ -72,7 +77,9 @@ class _LoopBuiltinModelControl:
|
|
|
72
77
|
raise_if_cancel_requested(phase="model")
|
|
73
78
|
return handler(request)
|
|
74
79
|
except asyncio.CancelledError:
|
|
75
|
-
raise_if_cancel_requested
|
|
80
|
+
# D-A′-1:就地 abort;禁止 raise_if_cancel_requested 逃逸
|
|
81
|
+
if is_cancel_requested():
|
|
82
|
+
return _model_abort_response(abort_signal_for_phase("model"))
|
|
76
83
|
raise
|
|
77
84
|
except LoopAbortSignal as sig:
|
|
78
85
|
if sig.terminal_reason not in (
|
|
@@ -88,7 +95,8 @@ class _LoopBuiltinModelControl:
|
|
|
88
95
|
raise_if_cancel_requested(phase="model")
|
|
89
96
|
return await handler(request)
|
|
90
97
|
except asyncio.CancelledError:
|
|
91
|
-
|
|
98
|
+
if is_cancel_requested():
|
|
99
|
+
return _model_abort_response(abort_signal_for_phase("model"))
|
|
92
100
|
raise
|
|
93
101
|
except LoopAbortSignal as sig:
|
|
94
102
|
if sig.terminal_reason not in (
|
|
@@ -184,7 +192,11 @@ def builtin_sync_tool_wrapper(request: Any, handler: Any) -> Any:
|
|
|
184
192
|
result = handler(_with_injected_agent_tool_model(request))
|
|
185
193
|
return apply_tool_message_status_to_result(result)
|
|
186
194
|
except asyncio.CancelledError:
|
|
187
|
-
|
|
195
|
+
if is_cancel_requested():
|
|
196
|
+
return _tool_abort_command(
|
|
197
|
+
request,
|
|
198
|
+
terminal_reason=TERMINAL_REASON_ABORTED_TOOLS,
|
|
199
|
+
)
|
|
188
200
|
raise
|
|
189
201
|
except ClassifierDenialLimitAbort as exc:
|
|
190
202
|
return _tool_abort_command(
|
|
@@ -207,7 +219,11 @@ async def builtin_async_tool_wrapper(request: Any, handler: Any) -> Any:
|
|
|
207
219
|
result = await handler(_with_injected_agent_tool_model(request))
|
|
208
220
|
return apply_tool_message_status_to_result(result)
|
|
209
221
|
except asyncio.CancelledError:
|
|
210
|
-
|
|
222
|
+
if is_cancel_requested():
|
|
223
|
+
return _tool_abort_command(
|
|
224
|
+
request,
|
|
225
|
+
terminal_reason=TERMINAL_REASON_ABORTED_TOOLS,
|
|
226
|
+
)
|
|
211
227
|
raise
|
|
212
228
|
except ClassifierDenialLimitAbort as exc:
|
|
213
229
|
return _tool_abort_command(
|
|
@@ -37,7 +37,7 @@ from ..exit.reason_codes import (
|
|
|
37
37
|
TERMINAL_REASON_ABORTED_TOOLS,
|
|
38
38
|
TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
39
39
|
)
|
|
40
|
-
from ..loop_abort import LoopAbortSignal, raise_if_cancel_requested
|
|
40
|
+
from ..loop_abort import LoopAbortSignal, is_cancel_requested, raise_if_cancel_requested
|
|
41
41
|
from .builtin_loop_control import tools_batch_abort_command
|
|
42
42
|
|
|
43
43
|
|
|
@@ -112,7 +112,11 @@ class RuntimeToolsNode(Runnable):
|
|
|
112
112
|
tool_messages = self._message_adapter.records_to_graph_messages(records)
|
|
113
113
|
return {"messages": tool_messages}
|
|
114
114
|
except asyncio.CancelledError:
|
|
115
|
-
|
|
115
|
+
if is_cancel_requested():
|
|
116
|
+
return tools_batch_abort_command(
|
|
117
|
+
tool_calls,
|
|
118
|
+
terminal_reason=TERMINAL_REASON_ABORTED_TOOLS,
|
|
119
|
+
)
|
|
116
120
|
raise
|
|
117
121
|
except ClassifierDenialLimitAbort as exc:
|
|
118
122
|
return tools_batch_abort_command(
|
|
@@ -147,7 +151,11 @@ class RuntimeToolsNode(Runnable):
|
|
|
147
151
|
tool_messages = self._message_adapter.records_to_graph_messages(records)
|
|
148
152
|
return {"messages": tool_messages}
|
|
149
153
|
except asyncio.CancelledError:
|
|
150
|
-
|
|
154
|
+
if is_cancel_requested():
|
|
155
|
+
return tools_batch_abort_command(
|
|
156
|
+
tool_calls,
|
|
157
|
+
terminal_reason=TERMINAL_REASON_ABORTED_TOOLS,
|
|
158
|
+
)
|
|
151
159
|
raise
|
|
152
160
|
except ClassifierDenialLimitAbort as exc:
|
|
153
161
|
return tools_batch_abort_command(
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
P1(Doc-35 D-ABORT-1):``raise_if_cancel_requested`` 读 ``RunContext.cancel_requested``,
|
|
4
4
|
真则抛既有 ``LoopAbortSignal``(单轨);禁止平行 Abort 异常族。
|
|
5
|
+
|
|
6
|
+
层 A′(2026-07-21):``except CancelledError`` 路径禁止二次 ``raise_if_cancel_requested``
|
|
7
|
+
逃逸;用 ``is_cancel_requested`` + 就地 abort 响应(见 ``abort_signal_for_phase``)。
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
10
|
from __future__ import annotations
|
|
@@ -41,27 +44,44 @@ class LoopAbortSignal(Exception):
|
|
|
41
44
|
super().__init__(detail or terminal_reason)
|
|
42
45
|
|
|
43
46
|
|
|
47
|
+
def is_cancel_requested() -> bool:
|
|
48
|
+
"""当前 RunContext 是否已请求取消(无 context 则为 False)。"""
|
|
49
|
+
from .run_context import get_run_context
|
|
50
|
+
|
|
51
|
+
ctx = get_run_context()
|
|
52
|
+
return ctx is not None and bool(ctx.cancel_requested)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def abort_signal_for_phase(
|
|
56
|
+
phase: CancelAbortPhase,
|
|
57
|
+
*,
|
|
58
|
+
detail: str = "cancel_requested",
|
|
59
|
+
) -> LoopAbortSignal:
|
|
60
|
+
"""构造相位对应的 ``LoopAbortSignal``(不抛出;供 CancelledError 就地收口)。"""
|
|
61
|
+
if phase == "tools":
|
|
62
|
+
return LoopAbortSignal(TERMINAL_REASON_ABORTED_TOOLS, detail=detail)
|
|
63
|
+
return LoopAbortSignal(TERMINAL_REASON_ABORTED_STREAMING, detail=detail)
|
|
64
|
+
|
|
65
|
+
|
|
44
66
|
def raise_if_cancel_requested(*, phase: CancelAbortPhase) -> None:
|
|
45
67
|
"""若当前 ``RunContext.cancel_requested`` 为真,抛既有 ``LoopAbortSignal``。
|
|
46
68
|
|
|
47
69
|
对齐 CC ``query.ts`` 读 ``signal.aborted``;SDK 原语为 bool(D-SEM-1),
|
|
48
70
|
退出语义仅走 ``LoopAbortSignal`` → ``builtin_loop_control``(D-ABORT-1)。
|
|
49
71
|
无 RunContext 时 no-op。
|
|
50
|
-
"""
|
|
51
|
-
from .run_context import get_run_context
|
|
52
72
|
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
**禁止**在 ``except CancelledError`` 内调用本函数再让异常逃逸;该路径应
|
|
74
|
+
``is_cancel_requested`` + 就地 abort 响应(D-A′-1)。
|
|
75
|
+
"""
|
|
76
|
+
if not is_cancel_requested():
|
|
55
77
|
return
|
|
56
|
-
|
|
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
|
-
)
|
|
78
|
+
raise abort_signal_for_phase(phase)
|
|
65
79
|
|
|
66
80
|
|
|
67
|
-
__all__ = [
|
|
81
|
+
__all__ = [
|
|
82
|
+
"CancelAbortPhase",
|
|
83
|
+
"LoopAbortSignal",
|
|
84
|
+
"abort_signal_for_phase",
|
|
85
|
+
"is_cancel_requested",
|
|
86
|
+
"raise_if_cancel_requested",
|
|
87
|
+
]
|
|
@@ -21,8 +21,12 @@ from typing import Any, AsyncIterator, Literal
|
|
|
21
21
|
|
|
22
22
|
from langgraph.errors import GraphRecursionError
|
|
23
23
|
|
|
24
|
+
from langchain_agentx.loop.loop_abort import LoopAbortSignal, is_cancel_requested
|
|
24
25
|
from ..exit.reason_codes import (
|
|
26
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
27
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
25
28
|
TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
|
|
29
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
26
30
|
TERMINAL_REASON_RUNTIME_BUDGET_EXHAUSTED,
|
|
27
31
|
TRANSITION_REACTIVE_COMPACT_RETRY,
|
|
28
32
|
)
|
|
@@ -43,6 +47,14 @@ logger = logging.getLogger(__name__)
|
|
|
43
47
|
# CC `tengu_query_error` 的 SDK trace 等价 event_type
|
|
44
48
|
LOOP_STREAM_DRIVER_ERROR_EVENT = "loop.stream_driver.error"
|
|
45
49
|
|
|
50
|
+
_ABORT_TERMINAL_REASONS = frozenset(
|
|
51
|
+
{
|
|
52
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
53
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
54
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
46
58
|
|
|
47
59
|
class LoopStreamDriver:
|
|
48
60
|
def __init__(self) -> None:
|
|
@@ -123,9 +135,65 @@ class LoopStreamDriver:
|
|
|
123
135
|
self._log_missing_root_chain_end_anomaly()
|
|
124
136
|
except asyncio.CancelledError as exc:
|
|
125
137
|
self.result.final_error = exc
|
|
138
|
+
# 用户 Stop 触发的 Task.cancel:合成 abort closeout(D-A′-3 / I7 区分裸 cancel)
|
|
139
|
+
if is_cancel_requested() and not self.result.saw_finish_surface:
|
|
140
|
+
reason = TERMINAL_REASON_ABORTED_STREAMING
|
|
141
|
+
terminal = build_loop_terminal(
|
|
142
|
+
reason=reason,
|
|
143
|
+
error_kind="aborted",
|
|
144
|
+
step=(self.result.last_loop_snapshot or {}).get("step"),
|
|
145
|
+
turn_count=(self.result.last_loop_snapshot or {}).get("turn_count"),
|
|
146
|
+
provider_finish_reason=(self.result.last_loop_snapshot or {}).get(
|
|
147
|
+
"finish_reason"
|
|
148
|
+
),
|
|
149
|
+
errors=[str(exc) or "cancel_requested"],
|
|
150
|
+
)
|
|
151
|
+
self.result.terminal = terminal
|
|
152
|
+
synthetic_output = self._build_synthetic_graph_output(terminal)
|
|
153
|
+
self.result.graph_output = synthetic_output
|
|
154
|
+
self.result.saw_finish_surface = True
|
|
155
|
+
yield build_synthetic_root_chain_end(graph_output=synthetic_output)
|
|
126
156
|
for repair in self._orphan_tool_result_repairs(closeout_kind="cancelled"):
|
|
127
157
|
yield repair
|
|
128
158
|
raise
|
|
159
|
+
except LoopAbortSignal as exc:
|
|
160
|
+
# D-A′-3:同轨 abort closeout;禁止落入 graph_runtime_error
|
|
161
|
+
self.result.final_error = exc
|
|
162
|
+
reason = (
|
|
163
|
+
exc.terminal_reason
|
|
164
|
+
if exc.terminal_reason in _ABORT_TERMINAL_REASONS
|
|
165
|
+
else TERMINAL_REASON_ABORTED_STREAMING
|
|
166
|
+
)
|
|
167
|
+
logger.info(
|
|
168
|
+
"LoopStreamDriver graph stream aborted: %s",
|
|
169
|
+
exc.detail or reason,
|
|
170
|
+
extra={"loop_stream_driver_abort": {"terminal_reason": reason}},
|
|
171
|
+
)
|
|
172
|
+
for repair in self._orphan_tool_result_repairs(closeout_kind="interrupted"):
|
|
173
|
+
yield repair
|
|
174
|
+
if not self.result.saw_error_visible_surface:
|
|
175
|
+
self.result.saw_error_visible_surface = True
|
|
176
|
+
yield build_synthetic_error_visible_event(
|
|
177
|
+
message=str(exc.detail or reason),
|
|
178
|
+
error_kind="aborted",
|
|
179
|
+
)
|
|
180
|
+
if not self.result.saw_finish_surface:
|
|
181
|
+
terminal = build_loop_terminal(
|
|
182
|
+
reason=reason,
|
|
183
|
+
error_kind="aborted",
|
|
184
|
+
step=(self.result.last_loop_snapshot or {}).get("step"),
|
|
185
|
+
turn_count=(self.result.last_loop_snapshot or {}).get("turn_count"),
|
|
186
|
+
provider_finish_reason=(self.result.last_loop_snapshot or {}).get(
|
|
187
|
+
"finish_reason"
|
|
188
|
+
),
|
|
189
|
+
errors=[str(exc)],
|
|
190
|
+
)
|
|
191
|
+
self.result.terminal = terminal
|
|
192
|
+
synthetic_output = self._build_synthetic_graph_output(terminal)
|
|
193
|
+
self.result.graph_output = synthetic_output
|
|
194
|
+
self.result.saw_finish_surface = True
|
|
195
|
+
yield build_synthetic_root_chain_end(graph_output=synthetic_output)
|
|
196
|
+
raise
|
|
129
197
|
except GraphRecursionError as exc:
|
|
130
198
|
self.result.final_error = exc
|
|
131
199
|
self._log_catch_error(exc)
|
|
@@ -43,7 +43,12 @@ from langchain_agentx.loop.exit.reason_codes import (
|
|
|
43
43
|
TERMINAL_REASON_HOOK_STOPPED,
|
|
44
44
|
)
|
|
45
45
|
from langchain_agentx.loop.exit.terminal import apply_terminal_reason_projection, read_terminal_reason
|
|
46
|
-
from langchain_agentx.loop.loop_abort import
|
|
46
|
+
from langchain_agentx.loop.loop_abort import (
|
|
47
|
+
LoopAbortSignal,
|
|
48
|
+
abort_signal_for_phase,
|
|
49
|
+
is_cancel_requested,
|
|
50
|
+
raise_if_cancel_requested,
|
|
51
|
+
)
|
|
47
52
|
from langchain_agentx.loop.run_context import register_spawned_task
|
|
48
53
|
from langchain_agentx.loop.stream.loop_driver import LoopStreamDriver
|
|
49
54
|
from langchain_agentx.loop.subagent.context import (
|
|
@@ -598,7 +603,9 @@ async def _collect_subagent_events(
|
|
|
598
603
|
child_run_id=child_run_id,
|
|
599
604
|
)
|
|
600
605
|
except asyncio.CancelledError:
|
|
601
|
-
|
|
606
|
+
# D-A′-1:就地转为 LoopAbortSignal 再抛给 body(勿在 except 内 raise_if 逃逸)
|
|
607
|
+
if is_cancel_requested():
|
|
608
|
+
raise abort_signal_for_phase("model") from None
|
|
602
609
|
raise
|
|
603
610
|
except LoopAbortSignal:
|
|
604
611
|
# 上抛给 _run_subagent_body → status=interrupted(勿吞成 completed)
|
|
@@ -653,10 +653,15 @@ class LangChainAdapter:
|
|
|
653
653
|
|
|
654
654
|
@staticmethod
|
|
655
655
|
def _envelope_to_event_artifact_dict(env: ToolResultEnvelope) -> dict[str, Any]:
|
|
656
|
+
from langchain_agentx.tool_runtime.cancel_projection import (
|
|
657
|
+
resolve_artifact_terminal_state,
|
|
658
|
+
)
|
|
659
|
+
|
|
656
660
|
raw = asdict(env)
|
|
657
661
|
raw["schema_version"] = "2"
|
|
658
662
|
result = LangChainAdapter._json_safe_value(raw)
|
|
659
|
-
result
|
|
663
|
+
meta = result.get("meta") if isinstance(result.get("meta"), dict) else {}
|
|
664
|
+
result["terminal_state"] = resolve_artifact_terminal_state(meta=meta)
|
|
660
665
|
# B1/C3 方案 B:blocks/hints/artifacts 同时落 meta.envelope_extensions,
|
|
661
666
|
# CLI Bridge 只消费 meta/display/payload,不扩字段面。
|
|
662
667
|
extensions: dict[str, Any] = {}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tool_runtime/cancel_projection.py — 工具取消投影(层 C · D-TR)
|
|
3
|
+
|
|
4
|
+
职责:判定取消窄条件,解析 artifact ``terminal_state`` 与 model payload 双轨哨兵。
|
|
5
|
+
链路位置:``AgentRuntimeTool.present`` → adapter/pipeline ``envelope → artifact``。
|
|
6
|
+
对照 CC:``CANCEL_MESSAGE`` / ``INTERRUPT_MESSAGE_FOR_TOOL_USE``;设计 D-TR-1…4。
|
|
7
|
+
当前裁剪:不扩 ``ToolResultEnvelope.status``;禁止仅凭散文含 cancel 推断。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Literal, Mapping
|
|
13
|
+
|
|
14
|
+
from langchain_agentx.loop.exit.reason_codes import (
|
|
15
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
16
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
17
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
18
|
+
)
|
|
19
|
+
from langchain_agentx.tool_runtime.messages import (
|
|
20
|
+
CANCEL_MESSAGE,
|
|
21
|
+
INTERRUPT_MESSAGE_FOR_TOOL_USE,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
CANCEL_ERROR_KINDS = frozenset({"aborted", "user_cancelled", "cancelled"})
|
|
25
|
+
|
|
26
|
+
ABORT_LIKE_TERMINAL_REASONS = frozenset(
|
|
27
|
+
{
|
|
28
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
29
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
30
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_cancel_projection(
|
|
36
|
+
*,
|
|
37
|
+
source_status: str | None = None,
|
|
38
|
+
error_kind: str | None = None,
|
|
39
|
+
terminal_reason: str | None = None,
|
|
40
|
+
) -> bool:
|
|
41
|
+
"""D-TR-3:满足其一即视为用户取消投影(窄条件)。"""
|
|
42
|
+
if source_status == "interrupted":
|
|
43
|
+
return True
|
|
44
|
+
if error_kind in CANCEL_ERROR_KINDS:
|
|
45
|
+
return True
|
|
46
|
+
if terminal_reason in ABORT_LIKE_TERMINAL_REASONS:
|
|
47
|
+
return True
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def resolve_cancel_model_payload(*, error_kind: str | None) -> str:
|
|
52
|
+
"""D-TR-4:Stop 轨 ``user_cancelled`` → CANCEL;其余取消 → INTERRUPT_FOR_TOOL_USE。"""
|
|
53
|
+
if error_kind == "user_cancelled":
|
|
54
|
+
return CANCEL_MESSAGE
|
|
55
|
+
return INTERRUPT_MESSAGE_FOR_TOOL_USE
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def resolve_artifact_terminal_state(
|
|
59
|
+
*,
|
|
60
|
+
meta: Mapping[str, Any] | None = None,
|
|
61
|
+
terminal_reason: str | None = None,
|
|
62
|
+
) -> Literal["completed", "cancelled"]:
|
|
63
|
+
"""adapter/pipeline 共用:取消 → ``cancelled``,否则 ``completed``。"""
|
|
64
|
+
m = meta if isinstance(meta, Mapping) else {}
|
|
65
|
+
reason = terminal_reason if terminal_reason is not None else m.get("terminal_reason")
|
|
66
|
+
kind = m.get("error_kind")
|
|
67
|
+
source = m.get("source_status")
|
|
68
|
+
if is_cancel_projection(
|
|
69
|
+
source_status=str(source) if source is not None else None,
|
|
70
|
+
error_kind=str(kind) if kind is not None else None,
|
|
71
|
+
terminal_reason=str(reason) if reason is not None else None,
|
|
72
|
+
):
|
|
73
|
+
return "cancelled"
|
|
74
|
+
return "completed"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__all__ = [
|
|
78
|
+
"ABORT_LIKE_TERMINAL_REASONS",
|
|
79
|
+
"CANCEL_ERROR_KINDS",
|
|
80
|
+
"CANCEL_MESSAGE",
|
|
81
|
+
"INTERRUPT_MESSAGE_FOR_TOOL_USE",
|
|
82
|
+
"is_cancel_projection",
|
|
83
|
+
"resolve_artifact_terminal_state",
|
|
84
|
+
"resolve_cancel_model_payload",
|
|
85
|
+
]
|
|
@@ -684,7 +684,12 @@ class ToolExecutorPipeline:
|
|
|
684
684
|
|
|
685
685
|
artifact = json_safe(asdict(envelope))
|
|
686
686
|
artifact["schema_version"] = "2"
|
|
687
|
-
|
|
687
|
+
from langchain_agentx.tool_runtime.cancel_projection import (
|
|
688
|
+
resolve_artifact_terminal_state,
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
meta = artifact.get("meta") if isinstance(artifact.get("meta"), dict) else {}
|
|
692
|
+
artifact["terminal_state"] = resolve_artifact_terminal_state(meta=meta)
|
|
688
693
|
|
|
689
694
|
# display 可选(CC renderToolResultMessage?);无 Output 跳过;抛错不炸 pipeline
|
|
690
695
|
if (
|
|
@@ -17,6 +17,10 @@ import logging
|
|
|
17
17
|
from typing import Any, Literal
|
|
18
18
|
|
|
19
19
|
from langchain_agentx.tool_runtime.base import RuntimeTool
|
|
20
|
+
from langchain_agentx.tool_runtime.cancel_projection import (
|
|
21
|
+
is_cancel_projection,
|
|
22
|
+
resolve_cancel_model_payload,
|
|
23
|
+
)
|
|
20
24
|
from langchain_agentx.tool_runtime.models import (
|
|
21
25
|
AuthorizationDecision,
|
|
22
26
|
L1PermissionResult,
|
|
@@ -355,7 +359,14 @@ class AgentRuntimeTool(RuntimeTool):
|
|
|
355
359
|
},
|
|
356
360
|
)
|
|
357
361
|
if out.status == "error":
|
|
358
|
-
|
|
362
|
+
if is_cancel_projection(
|
|
363
|
+
error_kind=out.error_kind,
|
|
364
|
+
terminal_reason=out.terminal_reason,
|
|
365
|
+
):
|
|
366
|
+
return self._present_cancelled(inp, out, source_status="error")
|
|
367
|
+
payload = out.error_message or (
|
|
368
|
+
out.errors[0] if out.errors else "Subagent execution failed"
|
|
369
|
+
)
|
|
359
370
|
return ToolResultEnvelope(
|
|
360
371
|
status="error",
|
|
361
372
|
tool_name=self.name,
|
|
@@ -368,16 +379,31 @@ class AgentRuntimeTool(RuntimeTool):
|
|
|
368
379
|
**_agent_termination_meta(out),
|
|
369
380
|
},
|
|
370
381
|
)
|
|
382
|
+
# interrupted(及未知非 completed/async)→ 取消投影(D-TR-3/4)
|
|
383
|
+
return self._present_cancelled(inp, out, source_status="interrupted")
|
|
384
|
+
|
|
385
|
+
def _present_cancelled(
|
|
386
|
+
self,
|
|
387
|
+
inp: AgentToolInput,
|
|
388
|
+
out: AgentToolOutput,
|
|
389
|
+
*,
|
|
390
|
+
source_status: str,
|
|
391
|
+
) -> ToolResultEnvelope:
|
|
392
|
+
"""层 C:取消双轨 payload;Envelope.status 仍为 error(D-TR-2)。"""
|
|
393
|
+
error_kind = out.error_kind or "aborted"
|
|
394
|
+
term = _agent_termination_meta(out)
|
|
395
|
+
term["error_kind"] = error_kind
|
|
371
396
|
return ToolResultEnvelope(
|
|
372
397
|
status="error",
|
|
373
398
|
tool_name=self.name,
|
|
374
399
|
summary=f"Agent({inp.subagent_type}) interrupted",
|
|
375
|
-
payload=
|
|
400
|
+
payload=resolve_cancel_model_payload(error_kind=error_kind),
|
|
376
401
|
meta={
|
|
377
402
|
"agent_id": out.agent_id,
|
|
378
403
|
"terminal_reason": out.terminal_reason,
|
|
379
404
|
"mode": self._mode,
|
|
380
|
-
|
|
405
|
+
"source_status": source_status,
|
|
406
|
+
**term,
|
|
381
407
|
},
|
|
382
408
|
)
|
|
383
409
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
1
|
+
langchain_agentx/__init__.py,sha256=_jWRBw5rNM52V1JxowuR91v-mVr2N_1x-Adhgz-4XrU,1614
|
|
2
2
|
langchain_agentx/command/__init__.py,sha256=Ej260S5uFQcmEtGZQG_NH7BYjHoStrpBLtGUsBTxyZk,631
|
|
3
3
|
langchain_agentx/command/allowed_tools.py,sha256=TVA0VM8rm98H-QbLK_GRiX5RhEAZFxKawliMi4kk96A,3359
|
|
4
4
|
langchain_agentx/command/context.py,sha256=DIGOGPBw5UGDBm0WNNJlKx1h_9rCRmcdROv4DT61Qug,731
|
|
@@ -24,9 +24,9 @@ langchain_agentx/health/__init__.py,sha256=fbav8Q7Yhd5Ykr_C6xjMeJccIip6FyeGkNTeg
|
|
|
24
24
|
langchain_agentx/health/checks.py,sha256=eUPT4lDjSBEiwRF8o_5wnEEIQArCWEL3M338OQoKwfA,17965
|
|
25
25
|
langchain_agentx/health/models.py,sha256=lHwdvn0w-qs-c8mI6I3nlnoCk8--y7gADj7C9u7eO9k,2055
|
|
26
26
|
langchain_agentx/health/preflight.py,sha256=wGysAnhT-jR1LfyjdaX9diO573Zzt5Z1M3vxDbpdcK0,5612
|
|
27
|
-
langchain_agentx/loop/__init__.py,sha256=
|
|
27
|
+
langchain_agentx/loop/__init__.py,sha256=JoFPFrlllvGCYFEk2RUuqq9AfUIdLVSNbrRdTRy6g_I,1428
|
|
28
28
|
langchain_agentx/loop/benchmark_frozen_config.py,sha256=YI4IaDVMBOEucF36F2oNv5mw1TqMG_rm_LJSpKAnFWM,13942
|
|
29
|
-
langchain_agentx/loop/loop_abort.py,sha256=
|
|
29
|
+
langchain_agentx/loop/loop_abort.py,sha256=cKlHxZh0u7-drCVqEiRtzkO270MHw6grJuZ1NpKoGfo,3250
|
|
30
30
|
langchain_agentx/loop/message_contract.py,sha256=Twl83TneU2PRdxgGRBxs6S9_ovRT6cb5WeCUzd6oJnE,2196
|
|
31
31
|
langchain_agentx/loop/run_context.py,sha256=dhNiLhvqD_QYacGMA0B5P3wWpSe3O6wU_XcVEzb86Z4,8303
|
|
32
32
|
langchain_agentx/loop/config/__init__.py,sha256=WdRQVXuvGU4myaGOZ8guFFvUb66jfsAIp9DLANJGieg,1083
|
|
@@ -67,11 +67,11 @@ langchain_agentx/loop/exit/resolution.py,sha256=taIMHzAUtgrQoU9446-CUXBN0PyCujhl
|
|
|
67
67
|
langchain_agentx/loop/exit/terminal.py,sha256=_DAZk-kS4Xd0nxMlayLo60-uj1Y5ozgBjLlWUP6RDdc,3524
|
|
68
68
|
langchain_agentx/loop/exit/withheld_error.py,sha256=LfX3j7Rgd9VoMzlz7GOs9TPuRe7RXr_Gxweyotx3tqQ,1846
|
|
69
69
|
langchain_agentx/loop/graph/__init__.py,sha256=9pUUqn7G87n3lV45iYIU24j0wcTHyCD--SSdtv3urh4,136
|
|
70
|
-
langchain_agentx/loop/graph/builtin_loop_control.py,sha256=
|
|
70
|
+
langchain_agentx/loop/graph/builtin_loop_control.py,sha256=31o4H6SDGC_vECSliXecuVEnzlD1ahlVZ5JzEpxYY04,9248
|
|
71
71
|
langchain_agentx/loop/graph/factory.py,sha256=TthwQtNiOeT5K-bgBhuW89yrkdP6iyYXCcL4tn-puJk,103958
|
|
72
72
|
langchain_agentx/loop/graph/graph_edges.py,sha256=kW6Npx-nMIPzcaisCxVPkTwf5Y9GOKy1NWCNkJWeJBQ,47370
|
|
73
73
|
langchain_agentx/loop/graph/reactive_compact_node.py,sha256=633b5hTnR3BK8GBEKWhzOwGB0BQrVRKozNxDomWFjLo,8762
|
|
74
|
-
langchain_agentx/loop/graph/runtime_tools_node.py,sha256=
|
|
74
|
+
langchain_agentx/loop/graph/runtime_tools_node.py,sha256=wQkbc0ophyefpPZEc0q1vKoJOIdS4S6d6hSsrkdRLDk,7387
|
|
75
75
|
langchain_agentx/loop/hook/__init__.py,sha256=rOjc1cqWBxrcp651w_SQX50dWY7o28L2c-r8iVs-SWU,2167
|
|
76
76
|
langchain_agentx/loop/hook/async_hook_runner.py,sha256=SNyuYnQoowZzBTBHQb6o5L9wTG2Rl7qryeMtqFyEsxI,3195
|
|
77
77
|
langchain_agentx/loop/hook/config.py,sha256=f8XtGGHtyjuRvZNhkU8ohbhyqOHD7mHZn_w0rVXMX_o,9157
|
|
@@ -132,7 +132,7 @@ langchain_agentx/loop/runtime/context_factory.py,sha256=m3ics_dOTqEKNlf6ETIOnVj6
|
|
|
132
132
|
langchain_agentx/loop/runtime/subagent_execution_paths.py,sha256=Bgyiom48uta1FZcnIE7fIxyrD-VFCRsw7s8IxIxmLb4,4033
|
|
133
133
|
langchain_agentx/loop/stream/graph_driver.py,sha256=qMZHEw3wJ19Tfbz31-VPLy8Mt6e90xKougIjAoaEFCE,3935
|
|
134
134
|
langchain_agentx/loop/stream/helpers.py,sha256=b2sqtwrAa5ic5B5AzNqItU8pmDHw13iGs3hBiShSIZk,1390
|
|
135
|
-
langchain_agentx/loop/stream/loop_driver.py,sha256=
|
|
135
|
+
langchain_agentx/loop/stream/loop_driver.py,sha256=v9XbmYv4O9ad-SLBXM3FN50V96_77bY8Tlvam13GnzQ,25028
|
|
136
136
|
langchain_agentx/loop/stream/result.py,sha256=xQb9r1cxGCQpoAZ0gNo71CjhSE3eYOiYp5KnoOBzBF8,1131
|
|
137
137
|
langchain_agentx/loop/stream/synthetic_events.py,sha256=Z3JT3iTuB9UQOoQXUprAYvaFV0vXEAuf5iqXYH9YSyI,2175
|
|
138
138
|
langchain_agentx/loop/stream/transcript_gate.py,sha256=4AH3O0l8OrY_GQdqf-TOKOVS3selPLUC0jYLWQUTfkg,4690
|
|
@@ -153,7 +153,7 @@ langchain_agentx/loop/subagent/progress.py,sha256=_4YCDnVDH8sBiwXhCQVB8OHaUQwsCO
|
|
|
153
153
|
langchain_agentx/loop/subagent/progress_dispatcher.py,sha256=e1Kd16WRUOAJSeAjupukrlxHoyCR_JO84SHqntMCzdQ,3831
|
|
154
154
|
langchain_agentx/loop/subagent/prompt.py,sha256=p7RgzibNfEJdfX8nBnZGEwQLa3_EgOn4E56T9MBRIEc,1722
|
|
155
155
|
langchain_agentx/loop/subagent/reasoning_extractor.py,sha256=KrCQtBdKzsjAEfA7H5COuXzBhuGGonT2Su8IEKYlUpA,1334
|
|
156
|
-
langchain_agentx/loop/subagent/runner.py,sha256=
|
|
156
|
+
langchain_agentx/loop/subagent/runner.py,sha256=kUge-LUA2scfSGkocCVACfIC_OApZeOCYG9JTJgGFsI,33446
|
|
157
157
|
langchain_agentx/loop/subagent/subagent_termination_classifier.py,sha256=ULArQeAibgy6ETSf4vRR0L6X1rsDM7-ZuLzRWQ_pmeY,3158
|
|
158
158
|
langchain_agentx/loop/subagent/transcript.py,sha256=-S0VjZcvqlJQGkVGu8VDMTYwTwOePdjDHfjYi3uu7BY,7187
|
|
159
159
|
langchain_agentx/loop/subagent/transcript_message_builder.py,sha256=gcDdbVOospJ9f0b-eM_0Y1uc4yTstaraWZdejokDyDQ,2081
|
|
@@ -390,9 +390,10 @@ langchain_agentx/task_runtime/tasks/trace_cleanup/executor.py,sha256=qez3WnzBTag
|
|
|
390
390
|
langchain_agentx/task_runtime/tasks/trace_cleanup/scheduler.py,sha256=lqWlCqucs26fhr4M9LfgY8I0xuNDxwSLkre6XjAJDhA,6877
|
|
391
391
|
langchain_agentx/tool_runtime/__init__.py,sha256=C4ZrS6UWSL65ErfNWMvBuZBnht6ZuGb0PBXwA0Xke74,5144
|
|
392
392
|
langchain_agentx/tool_runtime/accept_edits_fast_path.py,sha256=UMCOF9Ws7hx3pcYvL-Kq7vzIAGvOGdEfx69ccUDXT-k,2281
|
|
393
|
-
langchain_agentx/tool_runtime/adapter.py,sha256=
|
|
393
|
+
langchain_agentx/tool_runtime/adapter.py,sha256=rM-Y2zSZ--N-TIvx1HtI1J7ralPU0w4jJuX3bBvIVxM,33847
|
|
394
394
|
langchain_agentx/tool_runtime/agent_home_bypass.py,sha256=na9xQPgJ02qA3YiJ1F8jN0Uv4uBDPzqRk3lfPPE089g,8010
|
|
395
395
|
langchain_agentx/tool_runtime/base.py,sha256=Ez9W42DsaiZcWDDO8UfRKv3E7_Z6WBcABkwdAi6qTK8,21390
|
|
396
|
+
langchain_agentx/tool_runtime/cancel_projection.py,sha256=81_Bvg8ZiuPdl1usePt7ykXFuO6Sg7UXogYQBX6aysQ,2788
|
|
396
397
|
langchain_agentx/tool_runtime/capabilities.py,sha256=KHwu5KLqrpPKLOTwHhnvIIs9MTmQYeuS2hAFmI_Nsy8,3837
|
|
397
398
|
langchain_agentx/tool_runtime/classifier_denial_bridge.py,sha256=Wv40rYc5U3Mn7G3H7sUWrcG78HyLKnrRXvv7Yv9wI5U,2743
|
|
398
399
|
langchain_agentx/tool_runtime/classifier_denial_limit.py,sha256=RVLELUNO346_0ssg46bap52TRTp1dVzCOqdfjpuOpek,3793
|
|
@@ -420,7 +421,7 @@ langchain_agentx/tool_runtime/permission_rules.py,sha256=ev6-Hm3N8YtbJO3SLGpoGMU
|
|
|
420
421
|
langchain_agentx/tool_runtime/permission_settings_loader.py,sha256=Xu6dso0ghh4pb7Puf8nj20nXxjGeQvL4kIGycavwG_o,5624
|
|
421
422
|
langchain_agentx/tool_runtime/permission_settings_sync.py,sha256=g6TvjWwa7D0kw5rDznPsoMv7xylESPOyWA2vZW4HDm0,4590
|
|
422
423
|
langchain_agentx/tool_runtime/permission_snapshot.py,sha256=xFoi9fIcZjSw_zrIoyglneM-vJsDOLCEjiVoazr5Ack,1947
|
|
423
|
-
langchain_agentx/tool_runtime/pipeline.py,sha256=
|
|
424
|
+
langchain_agentx/tool_runtime/pipeline.py,sha256=b4ZlEMH5i3_rqWjPx3g7BD6Yv2hvk-R6qgKFgo-b3eU,54284
|
|
424
425
|
langchain_agentx/tool_runtime/policy.py,sha256=PLk_-5wu69_Gjqig0pj71brcu6VfLvGSIHsAUHOiYIQ,30068
|
|
425
426
|
langchain_agentx/tool_runtime/policy_decorator.py,sha256=XWbgh_TRlPJ-J-RFo1FKjIN_n_Z9rtLTfSwBQoyLTTg,2264
|
|
426
427
|
langchain_agentx/tool_runtime/process_loader.py,sha256=1YMn6ntnpdbIHJbO7wF1xuz7gNKYxK0UJAOxq96dZX4,6166
|
|
@@ -506,7 +507,7 @@ langchain_agentx/tools/agent/models.py,sha256=jPxah6JgPt0UlZVyrAQxQeZmVUDOBV7xyl
|
|
|
506
507
|
langchain_agentx/tools/agent/prompt.py,sha256=QQA40AfA5sEIl_LG7kn7UlUKFBOzsHSs_M8c5n-p7YU,8093
|
|
507
508
|
langchain_agentx/tools/agent/prompt_scope.py,sha256=xQup3hosuN0B6TFq7jYV5SE6gzZNwR6BOQ552dfef5I,2677
|
|
508
509
|
langchain_agentx/tools/agent/scope.py,sha256=6F2ZJgSvt-gFeciyKIQxaIiW5PBW9zUeTj3P0uxqrrg,6324
|
|
509
|
-
langchain_agentx/tools/agent/tool.py,sha256=
|
|
510
|
+
langchain_agentx/tools/agent/tool.py,sha256=h6yw2vDsla6teu47D5drOlVOn0KO_e973n_aih24qSU,17972
|
|
510
511
|
langchain_agentx/tools/agent/built_in/__init__.py,sha256=4YUHj8nUcUOvJW9p158W_t_RBoQt8Uh7eTt0idnCTE4,657
|
|
511
512
|
langchain_agentx/tools/agent/built_in/agentx_guide.py,sha256=Wz4791lK_URpAGtKWceEMvJlD_cu79KfmNQka5yXeUs,2719
|
|
512
513
|
langchain_agentx/tools/agent/built_in/explore.py,sha256=H5757yjmudceFVaMN0O4YDH267DKfFPMSxGR83waNkk,3504
|
|
@@ -758,8 +759,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
|
|
|
758
759
|
langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
|
|
759
760
|
langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
|
|
760
761
|
langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
|
|
761
|
-
langchain_agentx_python-2.
|
|
762
|
-
langchain_agentx_python-2.
|
|
763
|
-
langchain_agentx_python-2.
|
|
764
|
-
langchain_agentx_python-2.
|
|
765
|
-
langchain_agentx_python-2.
|
|
762
|
+
langchain_agentx_python-2.2.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
763
|
+
langchain_agentx_python-2.2.0.dist-info/METADATA,sha256=nn2x47UdjwxReSgREvpk012hKVMAXtw303b9JRhUdAM,24250
|
|
764
|
+
langchain_agentx_python-2.2.0.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
765
|
+
langchain_agentx_python-2.2.0.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
|
|
766
|
+
langchain_agentx_python-2.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-2.1.8.dist-info → langchain_agentx_python-2.2.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|