langchain-agentx-python 2.1.6__py3-none-any.whl → 2.1.7__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/exit/reason_codes.py +4 -0
- langchain_agentx/loop/run_context.py +3 -0
- langchain_agentx/loop/subagent/async_runner.py +77 -15
- langchain_agentx/observability/__init__.py +16 -0
- langchain_agentx/observability/events/__init__.py +18 -0
- langchain_agentx/observability/events/agent_event_projector.py +18 -0
- langchain_agentx/observability/events/finish_event_payload_composer.py +43 -1
- langchain_agentx/observability/events/finish_outcome.py +49 -5
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +9 -1
- langchain_agentx/observability/events/oob_event_sink.py +183 -0
- langchain_agentx/observability/events/workflow_aware_event_adapter.py +9 -0
- langchain_agentx/session/agent_session.py +22 -0
- langchain_agentx/session/conversation_session.py +12 -0
- langchain_agentx/workflow/base.py +20 -2
- {langchain_agentx_python-2.1.6.dist-info → langchain_agentx_python-2.1.7.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.1.6.dist-info → langchain_agentx_python-2.1.7.dist-info}/RECORD +20 -19
- {langchain_agentx_python-2.1.6.dist-info → langchain_agentx_python-2.1.7.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.1.6.dist-info → langchain_agentx_python-2.1.7.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.1.6.dist-info → langchain_agentx_python-2.1.7.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -48,6 +48,7 @@ TRANSITION_TOKEN_BUDGET_CONTINUATION = "token_budget_continuation"
|
|
|
48
48
|
DEFAULT_TOKEN_BUDGET_MAX_CONTINUATIONS = 3
|
|
49
49
|
|
|
50
50
|
# 错误结束 terminal_reason 集合(finish payload is_error 判据)
|
|
51
|
+
# D-EVT-3:含 aborted_* / hook_stopped,与 build_loop_terminal 对齐
|
|
51
52
|
ERROR_TERMINAL_REASONS = frozenset(
|
|
52
53
|
{
|
|
53
54
|
TERMINAL_REASON_ERROR,
|
|
@@ -60,6 +61,9 @@ ERROR_TERMINAL_REASONS = frozenset(
|
|
|
60
61
|
TERMINAL_REASON_MAX_TOKENS,
|
|
61
62
|
TERMINAL_REASON_BLOCKING_LIMIT,
|
|
62
63
|
TERMINAL_REASON_CLASSIFIER_DENIAL_LIMIT,
|
|
64
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
65
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
66
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
63
67
|
}
|
|
64
68
|
)
|
|
65
69
|
|
|
@@ -42,6 +42,9 @@ class RunContext:
|
|
|
42
42
|
child_tasks: set[asyncio.Task] = field(default_factory=set)
|
|
43
43
|
child_threads: set[threading.Thread] = field(default_factory=set)
|
|
44
44
|
cancel_requested: bool = False
|
|
45
|
+
# D-EVT-6:宿主在 cancel_tree / killAll 时写入;finish 投影读取;缺省 unknown。
|
|
46
|
+
# 取值:escape | workflow_stop | kill_agents | unknown;禁止从「是否有后台」猜测。
|
|
47
|
+
cancel_source: str | None = None
|
|
45
48
|
|
|
46
49
|
def register_child(self, task: asyncio.Task) -> None:
|
|
47
50
|
self.child_tasks.add(task)
|
|
@@ -13,6 +13,7 @@ loop/subagent/async_runner.py — 子 Agent 异步生命周期管理
|
|
|
13
13
|
|
|
14
14
|
当前裁剪范围:
|
|
15
15
|
P0:删除 C4;后台仅 C3。P2:killAll 仅遍历 _BACKGROUND_HANDLES(可 Task.cancel)。
|
|
16
|
+
P2 投影:lifecycle / _mark_cancelled_terminal → OOB task-notification(D-EVT-5)。
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
19
|
from __future__ import annotations
|
|
@@ -22,7 +23,7 @@ import logging
|
|
|
22
23
|
import os
|
|
23
24
|
import uuid
|
|
24
25
|
from dataclasses import dataclass
|
|
25
|
-
from typing import Any
|
|
26
|
+
from typing import Any, Literal
|
|
26
27
|
|
|
27
28
|
from langchain_agentx.loop.exit.reason_codes import (
|
|
28
29
|
TERMINAL_REASON_ABORTED_STREAMING,
|
|
@@ -106,11 +107,34 @@ def _is_cancel_like_result(result: Any) -> bool:
|
|
|
106
107
|
return getattr(result, "error_kind", None) == "aborted"
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
def _emit_task_notification_bookend(
|
|
111
|
+
*,
|
|
112
|
+
task_id: str,
|
|
113
|
+
status: Literal["completed", "failed", "stopped"],
|
|
114
|
+
output_file: str = "",
|
|
115
|
+
summary: str = "",
|
|
116
|
+
agent_id: str | None = None,
|
|
117
|
+
terminal_reason: str | None = None,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""D-EVT-5:后台终态 OOB bookend;同 task_id 去重在 sink 内完成。"""
|
|
120
|
+
from langchain_agentx.observability.events.oob_event_sink import get_oob_event_sink
|
|
121
|
+
|
|
122
|
+
get_oob_event_sink().emit_task_notification(
|
|
123
|
+
task_id=task_id,
|
|
124
|
+
status=status,
|
|
125
|
+
output_file=output_file,
|
|
126
|
+
summary=summary,
|
|
127
|
+
agent_id=agent_id,
|
|
128
|
+
terminal_reason=terminal_reason,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
109
132
|
def _mark_cancelled_terminal(
|
|
110
133
|
*,
|
|
111
134
|
task_id: str,
|
|
112
135
|
output_file: str,
|
|
113
136
|
task_runtime: Any | None,
|
|
137
|
+
agent_id: str | None = None,
|
|
114
138
|
) -> None:
|
|
115
139
|
"""写 cancelled 输出 + TaskRuntime STOPPED(对齐 TaskRuntime.cancel / CC killed)。"""
|
|
116
140
|
try:
|
|
@@ -119,18 +143,27 @@ def _mark_cancelled_terminal(
|
|
|
119
143
|
fp.write("cancelled")
|
|
120
144
|
except Exception:
|
|
121
145
|
logger.debug("failed to write cancelled output for task_id=%s", task_id, exc_info=True)
|
|
122
|
-
if task_runtime is None:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
if task_runtime is not None:
|
|
147
|
+
try:
|
|
148
|
+
task_runtime.mark_terminal(
|
|
149
|
+
task_id,
|
|
150
|
+
TaskStatus.STOPPED,
|
|
151
|
+
"subagent cancelled",
|
|
152
|
+
output_file=output_file,
|
|
153
|
+
terminal_reason="cancelled",
|
|
154
|
+
)
|
|
155
|
+
except Exception:
|
|
156
|
+
logger.debug(
|
|
157
|
+
"failed to mark cancelled terminal for task_id=%s", task_id, exc_info=True
|
|
158
|
+
)
|
|
159
|
+
_emit_task_notification_bookend(
|
|
160
|
+
task_id=task_id,
|
|
161
|
+
status="stopped",
|
|
162
|
+
output_file=output_file,
|
|
163
|
+
summary="cancelled",
|
|
164
|
+
agent_id=agent_id,
|
|
165
|
+
terminal_reason="cancelled",
|
|
166
|
+
)
|
|
134
167
|
|
|
135
168
|
|
|
136
169
|
async def _execute_background_lifecycle(
|
|
@@ -146,6 +179,7 @@ async def _execute_background_lifecycle(
|
|
|
146
179
|
"""共享后台生命周期:run_subagent → 写 output → task_runtime 终态。"""
|
|
147
180
|
execution_ctx = ctx
|
|
148
181
|
execution_ctx.is_async = False
|
|
182
|
+
agent_id = getattr(ctx, "agent_id", None)
|
|
149
183
|
try:
|
|
150
184
|
result = await run_subagent(
|
|
151
185
|
ctx=execution_ctx,
|
|
@@ -160,37 +194,64 @@ async def _execute_background_lifecycle(
|
|
|
160
194
|
task_id=task_id,
|
|
161
195
|
output_file=output_file,
|
|
162
196
|
task_runtime=task_runtime,
|
|
197
|
+
agent_id=agent_id,
|
|
163
198
|
)
|
|
164
199
|
return
|
|
165
200
|
with open(output_file, "w", encoding="utf-8") as fp:
|
|
166
201
|
fp.write(result.content)
|
|
202
|
+
summary = (
|
|
203
|
+
result.content[:500]
|
|
204
|
+
if result.content
|
|
205
|
+
else (result.error_message or "subagent completed")
|
|
206
|
+
)
|
|
207
|
+
notif_status: Literal["completed", "failed"] = (
|
|
208
|
+
"completed" if result.status == "completed" else "failed"
|
|
209
|
+
)
|
|
167
210
|
if task_runtime is not None:
|
|
168
211
|
status = TaskStatus.COMPLETED if result.status == "completed" else TaskStatus.FAILED
|
|
169
212
|
task_runtime.mark_terminal(
|
|
170
213
|
task_id,
|
|
171
214
|
status,
|
|
172
|
-
|
|
215
|
+
summary,
|
|
173
216
|
output_file=output_file,
|
|
174
217
|
terminal_reason=result.terminal_reason or None,
|
|
175
218
|
)
|
|
219
|
+
_emit_task_notification_bookend(
|
|
220
|
+
task_id=task_id,
|
|
221
|
+
status=notif_status,
|
|
222
|
+
output_file=output_file,
|
|
223
|
+
summary=summary,
|
|
224
|
+
agent_id=agent_id,
|
|
225
|
+
terminal_reason=result.terminal_reason or None,
|
|
226
|
+
)
|
|
176
227
|
except asyncio.CancelledError:
|
|
177
228
|
_mark_cancelled_terminal(
|
|
178
229
|
task_id=task_id,
|
|
179
230
|
output_file=output_file,
|
|
180
231
|
task_runtime=task_runtime,
|
|
232
|
+
agent_id=agent_id,
|
|
181
233
|
)
|
|
182
234
|
raise
|
|
183
235
|
except Exception as exc:
|
|
184
236
|
with open(output_file, "w", encoding="utf-8") as fp:
|
|
185
237
|
fp.write(str(exc))
|
|
238
|
+
summary = f"Subagent background lifecycle failed: {exc}"
|
|
186
239
|
if task_runtime is not None:
|
|
187
240
|
task_runtime.mark_terminal(
|
|
188
241
|
task_id,
|
|
189
242
|
TaskStatus.FAILED,
|
|
190
|
-
|
|
243
|
+
summary,
|
|
191
244
|
output_file=output_file,
|
|
192
245
|
terminal_reason=TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
|
|
193
246
|
)
|
|
247
|
+
_emit_task_notification_bookend(
|
|
248
|
+
task_id=task_id,
|
|
249
|
+
status="failed",
|
|
250
|
+
output_file=output_file,
|
|
251
|
+
summary=summary,
|
|
252
|
+
agent_id=agent_id,
|
|
253
|
+
terminal_reason=TERMINAL_REASON_GRAPH_RUNTIME_ERROR,
|
|
254
|
+
)
|
|
194
255
|
|
|
195
256
|
|
|
196
257
|
def register_background_agent(
|
|
@@ -309,6 +370,7 @@ def kill_background_agent(task_id: str) -> bool:
|
|
|
309
370
|
task_id=task_id,
|
|
310
371
|
output_file=handle.output_file,
|
|
311
372
|
task_runtime=handle.task_runtime,
|
|
373
|
+
agent_id=handle.agent_id,
|
|
312
374
|
)
|
|
313
375
|
return True
|
|
314
376
|
|
|
@@ -10,13 +10,21 @@ from .events import (
|
|
|
10
10
|
LangchainAgentEvent,
|
|
11
11
|
LangchainAgentEventType,
|
|
12
12
|
LangGraphToLangchainAgentEventAdapter,
|
|
13
|
+
OOB_EVENT_QUEUE_CAP,
|
|
14
|
+
OobEventSink,
|
|
13
15
|
RecoveryAction,
|
|
14
16
|
RecoveryActionResolver,
|
|
15
17
|
RecoveryPolicy,
|
|
16
18
|
RecoveryPolicyMode,
|
|
17
19
|
coerce_recovery_policy,
|
|
18
20
|
create_langgraph_event_adapter,
|
|
21
|
+
drain_oob_events,
|
|
19
22
|
enrich_run_result,
|
|
23
|
+
ensure_oob_event_sink,
|
|
24
|
+
get_oob_event_sink,
|
|
25
|
+
install_oob_event_sink,
|
|
26
|
+
reset_oob_event_sink,
|
|
27
|
+
teardown_oob_event_sink,
|
|
20
28
|
)
|
|
21
29
|
from .replay.service import (
|
|
22
30
|
export_call_chain,
|
|
@@ -62,6 +70,8 @@ __all__ = [
|
|
|
62
70
|
"LogContext",
|
|
63
71
|
"LoggingConfigBuilder",
|
|
64
72
|
"OBS_SCHEMA_VERSION",
|
|
73
|
+
"OOB_EVENT_QUEUE_CAP",
|
|
74
|
+
"OobEventSink",
|
|
65
75
|
"ObservabilityLoggerAdapter",
|
|
66
76
|
"build_log_context",
|
|
67
77
|
"configure_logging_once",
|
|
@@ -69,6 +79,12 @@ __all__ = [
|
|
|
69
79
|
"LangchainAgentEventType",
|
|
70
80
|
"LangGraphToLangchainAgentEventAdapter",
|
|
71
81
|
"create_langgraph_event_adapter",
|
|
82
|
+
"drain_oob_events",
|
|
83
|
+
"ensure_oob_event_sink",
|
|
84
|
+
"get_oob_event_sink",
|
|
85
|
+
"install_oob_event_sink",
|
|
86
|
+
"reset_oob_event_sink",
|
|
87
|
+
"teardown_oob_event_sink",
|
|
72
88
|
"RecoveryAction",
|
|
73
89
|
"RecoveryActionResolver",
|
|
74
90
|
"RecoveryPolicy",
|
|
@@ -22,6 +22,16 @@ from .factory import (
|
|
|
22
22
|
ILangGraphEventAdapter,
|
|
23
23
|
create_langgraph_event_adapter,
|
|
24
24
|
)
|
|
25
|
+
from .oob_event_sink import (
|
|
26
|
+
OOB_EVENT_QUEUE_CAP,
|
|
27
|
+
OobEventSink,
|
|
28
|
+
drain_oob_events,
|
|
29
|
+
ensure_oob_event_sink,
|
|
30
|
+
get_oob_event_sink,
|
|
31
|
+
install_oob_event_sink,
|
|
32
|
+
reset_oob_event_sink,
|
|
33
|
+
teardown_oob_event_sink,
|
|
34
|
+
)
|
|
25
35
|
|
|
26
36
|
__all__ = [
|
|
27
37
|
"LangchainAgentEventType",
|
|
@@ -40,4 +50,12 @@ __all__ = [
|
|
|
40
50
|
"enrich_run_result",
|
|
41
51
|
"ILangGraphEventAdapter",
|
|
42
52
|
"create_langgraph_event_adapter",
|
|
53
|
+
"OOB_EVENT_QUEUE_CAP",
|
|
54
|
+
"OobEventSink",
|
|
55
|
+
"drain_oob_events",
|
|
56
|
+
"ensure_oob_event_sink",
|
|
57
|
+
"get_oob_event_sink",
|
|
58
|
+
"install_oob_event_sink",
|
|
59
|
+
"reset_oob_event_sink",
|
|
60
|
+
"teardown_oob_event_sink",
|
|
43
61
|
]
|
|
@@ -139,6 +139,8 @@ class LangchainAgentEventType(Enum):
|
|
|
139
139
|
TASK_NOTIFICATION_ENQUEUED = "task-notification-enqueued"
|
|
140
140
|
TASK_NOTIFICATION_CONSUMED = "task-notification-consumed"
|
|
141
141
|
TASK_TERMINATED = "task-terminated"
|
|
142
|
+
# D-EVT-5:OOB 后台终态书档(对标 CC system/task_notification)
|
|
143
|
+
TASK_NOTIFICATION = "task-notification"
|
|
142
144
|
|
|
143
145
|
|
|
144
146
|
@dataclass
|
|
@@ -1249,6 +1251,22 @@ class AgentEventProjector:
|
|
|
1249
1251
|
tool_name=name,
|
|
1250
1252
|
session_id=str(child_session_id) if child_session_id else None,
|
|
1251
1253
|
)
|
|
1254
|
+
# D-EVT-5′:图内 SUBAGENT_END 覆盖时登记;若 payload 含 task_id 则抑制同 id OOB
|
|
1255
|
+
end_task_id: str | None = None
|
|
1256
|
+
if isinstance(output, dict):
|
|
1257
|
+
raw_tid = output.get("task_id")
|
|
1258
|
+
if not isinstance(raw_tid, str):
|
|
1259
|
+
artifact = output.get("artifact")
|
|
1260
|
+
if isinstance(artifact, dict):
|
|
1261
|
+
raw_tid = artifact.get("task_id")
|
|
1262
|
+
if isinstance(raw_tid, str) and raw_tid.strip():
|
|
1263
|
+
end_task_id = raw_tid.strip()
|
|
1264
|
+
if end_task_id:
|
|
1265
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
1266
|
+
get_oob_event_sink,
|
|
1267
|
+
)
|
|
1268
|
+
|
|
1269
|
+
get_oob_event_sink().note_terminal_projected(end_task_id)
|
|
1252
1270
|
self._on_subagent_end_boundary()
|
|
1253
1271
|
|
|
1254
1272
|
self.state.current_tool = None
|
|
@@ -11,6 +11,7 @@ finish_event_payload_composer.py — finish 事件 data 组装协作者
|
|
|
11
11
|
当前裁剪范围:
|
|
12
12
|
- 不发射事件、不读 LangGraph raw event
|
|
13
13
|
- 不处理 SubAgent 终态;model_retry 进度见 retry_progress.py(Phase 6)
|
|
14
|
+
- INTERRUPT 文案仅经 display_interrupt_text 投影(D-EVT-4),不入 messages
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
from __future__ import annotations
|
|
@@ -22,8 +23,11 @@ from langchain_core.messages import AIMessage
|
|
|
22
23
|
|
|
23
24
|
from langchain_agentx.loop.exit.reason_codes import (
|
|
24
25
|
ERROR_TERMINAL_REASONS,
|
|
26
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
27
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
25
28
|
TERMINAL_REASON_COMPLETED,
|
|
26
29
|
TERMINAL_REASON_ERROR,
|
|
30
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
27
31
|
TERMINAL_REASON_MAX_TOKENS,
|
|
28
32
|
TRANSITION_API_ERROR_TERMINAL,
|
|
29
33
|
TRANSITION_CONTEXT_WINDOW_RECOVERY_EXHAUSTED,
|
|
@@ -32,6 +36,7 @@ from langchain_agentx.loop.exit.reason_codes import (
|
|
|
32
36
|
)
|
|
33
37
|
from langchain_agentx.loop.exit.terminal import read_terminal_reason
|
|
34
38
|
from langchain_agentx.loop.exit.withheld_error import resolve_overflow_kind
|
|
39
|
+
from langchain_agentx.loop.run_context import get_run_context
|
|
35
40
|
|
|
36
41
|
_RECOVERY_EXHAUSTED_TRANSITIONS = frozenset(
|
|
37
42
|
{
|
|
@@ -41,6 +46,17 @@ _RECOVERY_EXHAUSTED_TRANSITIONS = frozenset(
|
|
|
41
46
|
}
|
|
42
47
|
)
|
|
43
48
|
|
|
49
|
+
# D-EVT-2/4:用户取消 display 文案(对齐 CC INTERRUPT;禁止注入 messages)
|
|
50
|
+
DEFAULT_DISPLAY_INTERRUPT_TEXT = "[Request interrupted by user]"
|
|
51
|
+
ERROR_KIND_USER_CANCELLED = "user_cancelled"
|
|
52
|
+
ERROR_KIND_HOOK_STOPPED = "hook_stopped"
|
|
53
|
+
_USER_CANCELLED_TERMINAL_REASONS = frozenset(
|
|
54
|
+
{
|
|
55
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
56
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
44
60
|
_SENSITIVE_PATTERNS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
45
61
|
(re.compile(r"sk-[a-zA-Z0-9_-]{8,}", re.I), "<redacted_api_key>"),
|
|
46
62
|
(re.compile(r"bearer\s+\S+", re.I), "Bearer <redacted>"),
|
|
@@ -157,7 +173,7 @@ class FinishEventPayloadComposer:
|
|
|
157
173
|
messages=messages,
|
|
158
174
|
)
|
|
159
175
|
|
|
160
|
-
|
|
176
|
+
payload: dict[str, Any] = {
|
|
161
177
|
"graph_name": graph_name,
|
|
162
178
|
"answer": answer,
|
|
163
179
|
"terminal_reason": terminal_reason,
|
|
@@ -168,6 +184,10 @@ class FinishEventPayloadComposer:
|
|
|
168
184
|
"error_kind": error_kind,
|
|
169
185
|
"recovery_exhausted": recovery_exhausted,
|
|
170
186
|
}
|
|
187
|
+
if error_kind == ERROR_KIND_USER_CANCELLED:
|
|
188
|
+
payload["display_interrupt_text"] = DEFAULT_DISPLAY_INTERRUPT_TEXT
|
|
189
|
+
payload["cancel_source"] = self._resolve_cancel_source(snap)
|
|
190
|
+
return payload
|
|
171
191
|
|
|
172
192
|
@staticmethod
|
|
173
193
|
def _resolve_terminal_reason(
|
|
@@ -265,6 +285,25 @@ class FinishEventPayloadComposer:
|
|
|
265
285
|
if terminal_reason == TERMINAL_REASON_COMPLETED:
|
|
266
286
|
return "unknown"
|
|
267
287
|
|
|
288
|
+
if terminal_reason in _USER_CANCELLED_TERMINAL_REASONS:
|
|
289
|
+
return ERROR_KIND_USER_CANCELLED
|
|
290
|
+
|
|
291
|
+
if terminal_reason == TERMINAL_REASON_HOOK_STOPPED:
|
|
292
|
+
return ERROR_KIND_HOOK_STOPPED
|
|
293
|
+
|
|
294
|
+
return "unknown"
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def _resolve_cancel_source(snap: dict[str, Any]) -> str:
|
|
298
|
+
"""D-EVT-6:宿主写入优先;缺省 unknown;禁止猜测反推。"""
|
|
299
|
+
raw = snap.get("cancel_source")
|
|
300
|
+
if raw is not None and str(raw):
|
|
301
|
+
return str(raw)
|
|
302
|
+
ctx = get_run_context()
|
|
303
|
+
if ctx is not None:
|
|
304
|
+
source = getattr(ctx, "cancel_source", None)
|
|
305
|
+
if source is not None and str(source):
|
|
306
|
+
return str(source)
|
|
268
307
|
return "unknown"
|
|
269
308
|
|
|
270
309
|
@staticmethod
|
|
@@ -323,6 +362,9 @@ class FinishEventPayloadComposer:
|
|
|
323
362
|
|
|
324
363
|
|
|
325
364
|
__all__ = [
|
|
365
|
+
"DEFAULT_DISPLAY_INTERRUPT_TEXT",
|
|
366
|
+
"ERROR_KIND_HOOK_STOPPED",
|
|
367
|
+
"ERROR_KIND_USER_CANCELLED",
|
|
326
368
|
"FinishEventPayloadComposer",
|
|
327
369
|
"sanitize_finish_error_message",
|
|
328
370
|
]
|
|
@@ -20,8 +20,15 @@ from dataclasses import dataclass
|
|
|
20
20
|
from enum import Enum
|
|
21
21
|
from typing import Any, Literal
|
|
22
22
|
|
|
23
|
+
from langchain_agentx.loop.exit.reason_codes import (
|
|
24
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
25
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
26
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
27
|
+
)
|
|
23
28
|
from langchain_agentx.loop.exit.terminal import read_terminal_reason
|
|
24
29
|
from langchain_agentx.observability.events.finish_event_payload_composer import (
|
|
30
|
+
ERROR_KIND_HOOK_STOPPED,
|
|
31
|
+
ERROR_KIND_USER_CANCELLED,
|
|
25
32
|
FinishEventPayloadComposer,
|
|
26
33
|
)
|
|
27
34
|
|
|
@@ -53,6 +60,27 @@ FINISH_OUTCOME_KEYS = frozenset(
|
|
|
53
60
|
|
|
54
61
|
RecoveryPolicyMode = Literal["interactive", "auto"]
|
|
55
62
|
|
|
63
|
+
_CANCEL_ERROR_KINDS = frozenset(
|
|
64
|
+
{
|
|
65
|
+
ERROR_KIND_USER_CANCELLED,
|
|
66
|
+
ERROR_KIND_HOOK_STOPPED,
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
_CANCEL_TERMINAL_REASONS = frozenset(
|
|
70
|
+
{
|
|
71
|
+
TERMINAL_REASON_ABORTED_STREAMING,
|
|
72
|
+
TERMINAL_REASON_ABORTED_TOOLS,
|
|
73
|
+
TERMINAL_REASON_HOOK_STOPPED,
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _is_cancel_outcome(outcome: "FinishOutcome") -> bool:
|
|
79
|
+
"""用户取消 / hook 终局:禁止建议 compact(D-EVT-2 / CLI 投影)。"""
|
|
80
|
+
if outcome.error_kind in _CANCEL_ERROR_KINDS:
|
|
81
|
+
return True
|
|
82
|
+
return outcome.terminal_reason in _CANCEL_TERMINAL_REASONS
|
|
83
|
+
|
|
56
84
|
|
|
57
85
|
@dataclass(frozen=True)
|
|
58
86
|
class RecoveryPolicy:
|
|
@@ -149,6 +177,7 @@ class FinishOutcomeParser:
|
|
|
149
177
|
snap = dict(state or {})
|
|
150
178
|
terminal = snap.get("terminal") if isinstance(snap.get("terminal"), dict) else None
|
|
151
179
|
if terminal is not None and "is_error" in terminal:
|
|
180
|
+
# from_terminal 已对缺失 error_kind 做 composer 对齐补推(D-EVT-2)
|
|
152
181
|
return self.from_terminal(terminal, answer=answer)
|
|
153
182
|
msgs = messages if messages is not None else snap.get("messages")
|
|
154
183
|
if not isinstance(msgs, list):
|
|
@@ -178,11 +207,23 @@ class FinishOutcomeParser:
|
|
|
178
207
|
*,
|
|
179
208
|
answer: str = "",
|
|
180
209
|
) -> FinishOutcome:
|
|
210
|
+
terminal_reason = _optional_str(read_terminal_reason(terminal))
|
|
211
|
+
is_error = bool(terminal.get("is_error"))
|
|
212
|
+
error_kind = _optional_str(terminal.get("error_kind"))
|
|
213
|
+
if is_error and error_kind is None:
|
|
214
|
+
error_kind = FinishEventPayloadComposer._infer_error_kind(
|
|
215
|
+
terminal_reason=terminal_reason,
|
|
216
|
+
is_error=True,
|
|
217
|
+
withheld_error=None,
|
|
218
|
+
transition_reason=None,
|
|
219
|
+
terminal=terminal,
|
|
220
|
+
messages=None,
|
|
221
|
+
)
|
|
181
222
|
return FinishOutcome(
|
|
182
|
-
terminal_reason=
|
|
183
|
-
is_error=
|
|
223
|
+
terminal_reason=terminal_reason,
|
|
224
|
+
is_error=is_error,
|
|
184
225
|
recoverable=bool(terminal.get("recoverable")),
|
|
185
|
-
error_kind=
|
|
226
|
+
error_kind=error_kind,
|
|
186
227
|
recovery_exhausted=bool(terminal.get("recovery_exhausted")),
|
|
187
228
|
errors=tuple(str(e) for e in (terminal.get("errors") or []) if e),
|
|
188
229
|
answer=answer,
|
|
@@ -220,6 +261,8 @@ class RecoveryActionResolver:
|
|
|
220
261
|
) -> RecoveryAction:
|
|
221
262
|
if not outcome.is_error:
|
|
222
263
|
return RecoveryAction.NONE
|
|
264
|
+
if _is_cancel_outcome(outcome):
|
|
265
|
+
return RecoveryAction.FAIL
|
|
223
266
|
if not outcome.recoverable:
|
|
224
267
|
return RecoveryAction.FAIL
|
|
225
268
|
|
|
@@ -242,12 +285,13 @@ class RecoveryActionResolver:
|
|
|
242
285
|
|
|
243
286
|
@staticmethod
|
|
244
287
|
def blocks_auto_compact(outcome: FinishOutcome) -> bool:
|
|
245
|
-
"""Workflow 门禁:recovery_exhausted 后禁止再次 auto-compact
|
|
288
|
+
"""Workflow 门禁:cancel / recovery_exhausted 后禁止再次 auto-compact。"""
|
|
246
289
|
if not outcome.is_error:
|
|
247
290
|
return False
|
|
291
|
+
if _is_cancel_outcome(outcome):
|
|
292
|
+
return True
|
|
248
293
|
return outcome.recovery_exhausted
|
|
249
294
|
|
|
250
|
-
|
|
251
295
|
def enrich_run_result(
|
|
252
296
|
result: dict[str, Any],
|
|
253
297
|
*,
|
|
@@ -28,6 +28,7 @@ from langchain_agentx.observability.events.agent_event_projector import (
|
|
|
28
28
|
LangchainAgentEvent,
|
|
29
29
|
LangchainAgentEventType,
|
|
30
30
|
)
|
|
31
|
+
from langchain_agentx.observability.events.oob_event_sink import drain_oob_events
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class LangGraphToLangchainAgentEventAdapter:
|
|
@@ -51,12 +52,19 @@ class LangGraphToLangchainAgentEventAdapter:
|
|
|
51
52
|
async def adapt(
|
|
52
53
|
self, events: AsyncIterator[Dict[str, Any]]
|
|
53
54
|
) -> AsyncIterator[LangchainAgentEvent]:
|
|
54
|
-
"""主转换函数:将 LangGraph 事件流转换为 LangChain AgentX 风格事件流。
|
|
55
|
+
"""主转换函数:将 LangGraph 事件流转换为 LangChain AgentX 风格事件流。
|
|
56
|
+
|
|
57
|
+
每批 raw 事件后合并 OOB task-notification(D-EVT-5 / D-EVT-10)。
|
|
58
|
+
"""
|
|
55
59
|
self._projector.reset_stream_closeout_diagnostics()
|
|
56
60
|
try:
|
|
57
61
|
async for event in events:
|
|
58
62
|
async for oc_event in self._projector.consume(event):
|
|
59
63
|
yield oc_event
|
|
64
|
+
for oob in drain_oob_events():
|
|
65
|
+
yield oob
|
|
66
|
+
for oob in drain_oob_events():
|
|
67
|
+
yield oob
|
|
60
68
|
finally:
|
|
61
69
|
self._projector.log_stream_closeout_diagnostics()
|
|
62
70
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
oob_event_sink.py — 后台 task 终态 OOB 事件队列(对标 CC sdkEventQueue)
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
入队 task-notification(completed|failed|stopped);按 task_id 去重(D-EVT-5′);
|
|
6
|
+
供 Session/adapter drain 合并进 LangchainAgentEvent 流(D-EVT-10)。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
async_runner lifecycle / _mark_cancelled_terminal → emit
|
|
10
|
+
LangGraphToLangchainAgentEventAdapter.adapt / Workflow adapt_isolated → drain
|
|
11
|
+
|
|
12
|
+
当前裁剪范围:
|
|
13
|
+
- 始终 enqueue(无 TUI/headless 分叉,D-EVT-10)
|
|
14
|
+
- cap=1000 丢最旧;不依赖 langchain_agentx_trace
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from collections import deque
|
|
21
|
+
from contextvars import ContextVar, Token
|
|
22
|
+
from typing import Literal
|
|
23
|
+
|
|
24
|
+
from langchain_agentx.observability.events.agent_event_projector import (
|
|
25
|
+
LangchainAgentEvent,
|
|
26
|
+
LangchainAgentEventType,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
OOB_EVENT_QUEUE_CAP = 1000
|
|
32
|
+
|
|
33
|
+
TaskNotificationStatus = Literal["completed", "failed", "stopped"]
|
|
34
|
+
|
|
35
|
+
_current_sink: ContextVar["OobEventSink | None"] = ContextVar(
|
|
36
|
+
"oob_event_sink", default=None
|
|
37
|
+
)
|
|
38
|
+
_module_sink: OobEventSink | None = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OobEventSink:
|
|
42
|
+
"""Out-of-band LangchainAgentEvent 队列(task-notification bookend)。"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, *, capacity: int = OOB_EVENT_QUEUE_CAP) -> None:
|
|
45
|
+
self._capacity = max(1, int(capacity))
|
|
46
|
+
self._queue: deque[LangchainAgentEvent] = deque()
|
|
47
|
+
self._projected_task_ids: set[str] = set()
|
|
48
|
+
self._dropped = 0
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def dropped_count(self) -> int:
|
|
52
|
+
return self._dropped
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def pending_count(self) -> int:
|
|
56
|
+
return len(self._queue)
|
|
57
|
+
|
|
58
|
+
def clear(self) -> None:
|
|
59
|
+
"""测试 / 会话重置:清空队列与去重登记。"""
|
|
60
|
+
self._queue.clear()
|
|
61
|
+
self._projected_task_ids.clear()
|
|
62
|
+
self._dropped = 0
|
|
63
|
+
|
|
64
|
+
def note_terminal_projected(self, task_id: str) -> None:
|
|
65
|
+
"""D-EVT-5′:图内 SUBAGENT_END(或其它终态)已覆盖时登记,后续 OOB no-op。"""
|
|
66
|
+
tid = str(task_id or "").strip()
|
|
67
|
+
if tid:
|
|
68
|
+
self._projected_task_ids.add(tid)
|
|
69
|
+
|
|
70
|
+
def emit_task_notification(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
task_id: str,
|
|
74
|
+
status: TaskNotificationStatus,
|
|
75
|
+
output_file: str = "",
|
|
76
|
+
summary: str = "",
|
|
77
|
+
tool_use_id: str | None = None,
|
|
78
|
+
agent_id: str | None = None,
|
|
79
|
+
terminal_reason: str | None = None,
|
|
80
|
+
) -> bool:
|
|
81
|
+
"""入队一条 task-notification;同 task_id 已投影则 no-op。返回是否入队。"""
|
|
82
|
+
tid = str(task_id or "").strip()
|
|
83
|
+
if not tid:
|
|
84
|
+
return False
|
|
85
|
+
if tid in self._projected_task_ids:
|
|
86
|
+
return False
|
|
87
|
+
self._projected_task_ids.add(tid)
|
|
88
|
+
|
|
89
|
+
data: dict[str, object] = {
|
|
90
|
+
"task_id": tid,
|
|
91
|
+
"status": status,
|
|
92
|
+
"output_file": output_file or "",
|
|
93
|
+
"summary": summary or "",
|
|
94
|
+
}
|
|
95
|
+
if tool_use_id is not None:
|
|
96
|
+
data["tool_use_id"] = tool_use_id
|
|
97
|
+
if agent_id is not None:
|
|
98
|
+
data["agent_id"] = agent_id
|
|
99
|
+
if terminal_reason is not None:
|
|
100
|
+
data["terminal_reason"] = terminal_reason
|
|
101
|
+
|
|
102
|
+
event = LangchainAgentEvent(
|
|
103
|
+
event_type=LangchainAgentEventType.TASK_NOTIFICATION,
|
|
104
|
+
data=data,
|
|
105
|
+
)
|
|
106
|
+
if len(self._queue) >= self._capacity:
|
|
107
|
+
self._queue.popleft()
|
|
108
|
+
self._dropped += 1
|
|
109
|
+
logger.debug(
|
|
110
|
+
"OobEventSink capacity=%s exceeded; dropped oldest (total_dropped=%s)",
|
|
111
|
+
self._capacity,
|
|
112
|
+
self._dropped,
|
|
113
|
+
)
|
|
114
|
+
self._queue.append(event)
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
def drain(self) -> list[LangchainAgentEvent]:
|
|
118
|
+
"""取出并清空当前队列(保留 projected_task_ids 去重)。"""
|
|
119
|
+
items = list(self._queue)
|
|
120
|
+
self._queue.clear()
|
|
121
|
+
return items
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_oob_event_sink() -> OobEventSink:
|
|
125
|
+
"""当前 ContextVar sink,否则模块级单例(D-EVT-10:始终可 enqueue)。"""
|
|
126
|
+
global _module_sink
|
|
127
|
+
current = _current_sink.get()
|
|
128
|
+
if current is not None:
|
|
129
|
+
return current
|
|
130
|
+
if _module_sink is None:
|
|
131
|
+
_module_sink = OobEventSink()
|
|
132
|
+
return _module_sink
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def install_oob_event_sink(sink: OobEventSink | None = None) -> tuple[OobEventSink, Token]:
|
|
136
|
+
"""安装 ContextVar 级 sink(测试 / 隔离会话)。"""
|
|
137
|
+
target = sink if sink is not None else OobEventSink()
|
|
138
|
+
token = _current_sink.set(target)
|
|
139
|
+
return target, token
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def ensure_oob_event_sink() -> tuple[OobEventSink, Token | None]:
|
|
143
|
+
"""若 ContextVar 已有 sink 则复用(嵌套 Workflow);否则安装新 sink。
|
|
144
|
+
|
|
145
|
+
返回的 Token 为 None 表示未新装,teardown 时应跳过。
|
|
146
|
+
"""
|
|
147
|
+
current = _current_sink.get()
|
|
148
|
+
if current is not None:
|
|
149
|
+
return current, None
|
|
150
|
+
sink, token = install_oob_event_sink()
|
|
151
|
+
return sink, token
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def reset_oob_event_sink(token: Token) -> None:
|
|
155
|
+
_current_sink.reset(token)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def teardown_oob_event_sink(token: Token | None) -> None:
|
|
159
|
+
"""clear + reset;token 为 None 时 no-op(嵌套复用父 sink)。"""
|
|
160
|
+
if token is None:
|
|
161
|
+
return
|
|
162
|
+
try:
|
|
163
|
+
get_oob_event_sink().clear()
|
|
164
|
+
finally:
|
|
165
|
+
reset_oob_event_sink(token)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def drain_oob_events() -> list[LangchainAgentEvent]:
|
|
169
|
+
"""便捷:drain 当前 sink。"""
|
|
170
|
+
return get_oob_event_sink().drain()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
__all__ = [
|
|
174
|
+
"OOB_EVENT_QUEUE_CAP",
|
|
175
|
+
"OobEventSink",
|
|
176
|
+
"TaskNotificationStatus",
|
|
177
|
+
"drain_oob_events",
|
|
178
|
+
"ensure_oob_event_sink",
|
|
179
|
+
"get_oob_event_sink",
|
|
180
|
+
"install_oob_event_sink",
|
|
181
|
+
"reset_oob_event_sink",
|
|
182
|
+
"teardown_oob_event_sink",
|
|
183
|
+
]
|
|
@@ -28,6 +28,7 @@ from langchain_agentx.observability.events.agent_event_projector import (
|
|
|
28
28
|
EventAdapterState,
|
|
29
29
|
LangchainAgentEvent,
|
|
30
30
|
)
|
|
31
|
+
from langchain_agentx.observability.events.oob_event_sink import drain_oob_events
|
|
31
32
|
from langchain_agentx.observability.events.workflow_event_mapper import (
|
|
32
33
|
WorkflowEventMapper,
|
|
33
34
|
)
|
|
@@ -80,6 +81,8 @@ class WorkflowAwareLangGraphEventAdapter:
|
|
|
80
81
|
try:
|
|
81
82
|
async for mapped in self._project_raw_event(raw_event):
|
|
82
83
|
yield mapped
|
|
84
|
+
for oob in drain_oob_events():
|
|
85
|
+
yield oob
|
|
83
86
|
except (GeneratorExit, asyncio.CancelledError):
|
|
84
87
|
raise
|
|
85
88
|
except (WorkflowScopeResolutionError, ValueError) as exc:
|
|
@@ -108,6 +111,8 @@ class WorkflowAwareLangGraphEventAdapter:
|
|
|
108
111
|
mapped = self._workflow_mapper.map(workflow_event)
|
|
109
112
|
if mapped is not None:
|
|
110
113
|
yield mapped
|
|
114
|
+
for oob in drain_oob_events():
|
|
115
|
+
yield oob
|
|
111
116
|
|
|
112
117
|
async def _project_raw_event(
|
|
113
118
|
self, raw_event: Dict[str, Any]
|
|
@@ -145,6 +150,8 @@ class WorkflowAwareLangGraphEventAdapter:
|
|
|
145
150
|
async for raw_event in events:
|
|
146
151
|
async for mapped in self._project_raw_event(raw_event):
|
|
147
152
|
yield mapped
|
|
153
|
+
for oob in drain_oob_events():
|
|
154
|
+
yield oob
|
|
148
155
|
emit_finalize = True
|
|
149
156
|
except (GeneratorExit, asyncio.CancelledError):
|
|
150
157
|
raise
|
|
@@ -158,6 +165,8 @@ class WorkflowAwareLangGraphEventAdapter:
|
|
|
158
165
|
mapped = self._workflow_mapper.map(workflow_event)
|
|
159
166
|
if mapped is not None:
|
|
160
167
|
yield mapped
|
|
168
|
+
for oob in drain_oob_events():
|
|
169
|
+
yield oob
|
|
161
170
|
|
|
162
171
|
def _sync_workflow_session_id(self, raw_event: Dict[str, Any]) -> None:
|
|
163
172
|
"""Deprecated: session 同步已内联至 _project_raw_event()。"""
|
|
@@ -123,6 +123,7 @@ class AgentSession:
|
|
|
123
123
|
)
|
|
124
124
|
self._cleanup_registry = CleanupRegistry()
|
|
125
125
|
self._session_closing = False
|
|
126
|
+
self._oob_sink_token = None
|
|
126
127
|
|
|
127
128
|
def register_cleanup(
|
|
128
129
|
self,
|
|
@@ -143,8 +144,13 @@ class AgentSession:
|
|
|
143
144
|
)
|
|
144
145
|
|
|
145
146
|
async def __aenter__(self) -> "AgentSession":
|
|
147
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
148
|
+
install_oob_event_sink,
|
|
149
|
+
)
|
|
150
|
+
|
|
146
151
|
if self._graph is None:
|
|
147
152
|
self._graph = self._build_graph()
|
|
153
|
+
_, self._oob_sink_token = install_oob_event_sink()
|
|
148
154
|
if self._plugin_loader is not None:
|
|
149
155
|
load_result = await self._plugin_loader.load_all(container_type="interactive")
|
|
150
156
|
load_result.log_errors(logger)
|
|
@@ -152,6 +158,10 @@ class AgentSession:
|
|
|
152
158
|
return self
|
|
153
159
|
|
|
154
160
|
async def __aexit__(self, *_) -> None:
|
|
161
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
162
|
+
teardown_oob_event_sink,
|
|
163
|
+
)
|
|
164
|
+
|
|
155
165
|
self._session_closing = True
|
|
156
166
|
self._cleanup_registry.mark_closing()
|
|
157
167
|
try:
|
|
@@ -193,6 +203,8 @@ class AgentSession:
|
|
|
193
203
|
)
|
|
194
204
|
finally:
|
|
195
205
|
self._cleanup_registry.clear()
|
|
206
|
+
teardown_oob_event_sink(self._oob_sink_token)
|
|
207
|
+
self._oob_sink_token = None
|
|
196
208
|
|
|
197
209
|
async def run_loop(self, user_input: str) -> dict[str, Any]:
|
|
198
210
|
result = await self._run_loop_turn(user_input=user_input)
|
|
@@ -356,6 +368,16 @@ class AgentSession:
|
|
|
356
368
|
self._session_id,
|
|
357
369
|
)
|
|
358
370
|
|
|
371
|
+
def drain_oob_agent_events(self) -> list[Any]:
|
|
372
|
+
"""拉取 OOB task-notification(D-EVT-10)。
|
|
373
|
+
|
|
374
|
+
经 LangchainAgentEvent adapter 消费时已自动 drain;raw ``stream_loop_events``
|
|
375
|
+
宿主可在 kill / tick 后调用本方法合并到 UI。
|
|
376
|
+
"""
|
|
377
|
+
from langchain_agentx.observability.events.oob_event_sink import drain_oob_events
|
|
378
|
+
|
|
379
|
+
return drain_oob_events()
|
|
380
|
+
|
|
359
381
|
@property
|
|
360
382
|
def last_finish_outcome(self) -> dict[str, Any] | None:
|
|
361
383
|
"""最近一次 run_loop / resume / stream 结束后的 finish_outcome 快照。
|
|
@@ -100,6 +100,7 @@ class ConversationSession:
|
|
|
100
100
|
)
|
|
101
101
|
self._cleanup_registry = CleanupRegistry()
|
|
102
102
|
self._session_closing = False
|
|
103
|
+
self._oob_sink_token = None
|
|
103
104
|
|
|
104
105
|
def register_cleanup(
|
|
105
106
|
self,
|
|
@@ -120,6 +121,11 @@ class ConversationSession:
|
|
|
120
121
|
)
|
|
121
122
|
|
|
122
123
|
async def __aenter__(self) -> "ConversationSession":
|
|
124
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
125
|
+
install_oob_event_sink,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
_, self._oob_sink_token = install_oob_event_sink()
|
|
123
129
|
if self._plugin_loader is not None:
|
|
124
130
|
load_result = await self._plugin_loader.load_all(container_type="conversation")
|
|
125
131
|
load_result.log_errors(logger)
|
|
@@ -127,6 +133,10 @@ class ConversationSession:
|
|
|
127
133
|
return self
|
|
128
134
|
|
|
129
135
|
async def __aexit__(self, *_) -> None:
|
|
136
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
137
|
+
teardown_oob_event_sink,
|
|
138
|
+
)
|
|
139
|
+
|
|
130
140
|
self._session_closing = True
|
|
131
141
|
self._cleanup_registry.mark_closing()
|
|
132
142
|
try:
|
|
@@ -168,6 +178,8 @@ class ConversationSession:
|
|
|
168
178
|
)
|
|
169
179
|
finally:
|
|
170
180
|
self._cleanup_registry.clear()
|
|
181
|
+
teardown_oob_event_sink(self._oob_sink_token)
|
|
182
|
+
self._oob_sink_token = None
|
|
171
183
|
|
|
172
184
|
async def run_turn(self, user_input: str) -> dict[str, Any]:
|
|
173
185
|
"""每次用户请求调用一次,graph 每次新建。"""
|
|
@@ -415,6 +415,12 @@ class BaseWorkflow(ABC):
|
|
|
415
415
|
}
|
|
416
416
|
parent_ctx = get_run_context() if parent_workflow is not None else None
|
|
417
417
|
run_ctx, run_ctx_token = install_run_context(parent=parent_ctx)
|
|
418
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
419
|
+
ensure_oob_event_sink,
|
|
420
|
+
teardown_oob_event_sink,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
_, oob_token = ensure_oob_event_sink()
|
|
418
424
|
|
|
419
425
|
try:
|
|
420
426
|
return await graph.ainvoke(initial_state, config=effective_config)
|
|
@@ -428,7 +434,10 @@ class BaseWorkflow(ABC):
|
|
|
428
434
|
try:
|
|
429
435
|
await self._drain_pending_memory_extractions()
|
|
430
436
|
finally:
|
|
431
|
-
|
|
437
|
+
try:
|
|
438
|
+
teardown_oob_event_sink(oob_token)
|
|
439
|
+
finally:
|
|
440
|
+
self._active_run_config = previous_active
|
|
432
441
|
|
|
433
442
|
def as_node(
|
|
434
443
|
self,
|
|
@@ -575,6 +584,12 @@ class BaseWorkflow(ABC):
|
|
|
575
584
|
}
|
|
576
585
|
parent_ctx = get_run_context() if parent_workflow is not None else None
|
|
577
586
|
run_ctx, run_ctx_token = install_run_context(parent=parent_ctx)
|
|
587
|
+
from langchain_agentx.observability.events.oob_event_sink import (
|
|
588
|
+
ensure_oob_event_sink,
|
|
589
|
+
teardown_oob_event_sink,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
_, oob_token = ensure_oob_event_sink()
|
|
578
593
|
try:
|
|
579
594
|
graph = self.compile()
|
|
580
595
|
effective_config = self._resolve_effective_run_config(
|
|
@@ -605,7 +620,10 @@ class BaseWorkflow(ABC):
|
|
|
605
620
|
try:
|
|
606
621
|
await self._drain_pending_memory_extractions()
|
|
607
622
|
finally:
|
|
608
|
-
|
|
623
|
+
try:
|
|
624
|
+
teardown_oob_event_sink(oob_token)
|
|
625
|
+
finally:
|
|
626
|
+
self._active_run_config = previous_active
|
|
609
627
|
|
|
610
628
|
def _build_langgraph_adapter_config(
|
|
611
629
|
self,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
1
|
+
langchain_agentx/__init__.py,sha256=YCgjkZ-ETOiTsxprdpJbKhr-a726t37vZ6KsVTZcr2M,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
|
|
@@ -28,7 +28,7 @@ langchain_agentx/loop/__init__.py,sha256=DspRAfaf_j6NkMByH6TqG1jTis8gyFGAipREVfn
|
|
|
28
28
|
langchain_agentx/loop/benchmark_frozen_config.py,sha256=YI4IaDVMBOEucF36F2oNv5mw1TqMG_rm_LJSpKAnFWM,13942
|
|
29
29
|
langchain_agentx/loop/loop_abort.py,sha256=IWcbqDA1ye4k00e2-mRjCgOtQl15EDk9tTosbgR4haU,2459
|
|
30
30
|
langchain_agentx/loop/message_contract.py,sha256=Twl83TneU2PRdxgGRBxs6S9_ovRT6cb5WeCUzd6oJnE,2196
|
|
31
|
-
langchain_agentx/loop/run_context.py,sha256=
|
|
31
|
+
langchain_agentx/loop/run_context.py,sha256=dhNiLhvqD_QYacGMA0B5P3wWpSe3O6wU_XcVEzb86Z4,8303
|
|
32
32
|
langchain_agentx/loop/config/__init__.py,sha256=WdRQVXuvGU4myaGOZ8guFFvUb66jfsAIp9DLANJGieg,1083
|
|
33
33
|
langchain_agentx/loop/config/agent_config.py,sha256=tOnAHFor6dkle3FvUOVx0YsmiYVTH11UrWSyTmOLV0c,3100
|
|
34
34
|
langchain_agentx/loop/config/interaction_profile.py,sha256=UWrlI3aTWjYRq1mDeXL-xL-iCrQU-bXDXhETP6q9icY,10020
|
|
@@ -62,7 +62,7 @@ langchain_agentx/loop/exit/__init__.py,sha256=3-CMz5ApkJeBc549vRfgNABenyYKVbib4I
|
|
|
62
62
|
langchain_agentx/loop/exit/api_error_detection.py,sha256=QzyMTov4VrkpHmn288Tw7DN00hiyJNXMKlOrCbvsiAA,1479
|
|
63
63
|
langchain_agentx/loop/exit/exit_logic.py,sha256=nJzerVtTmS1iIY3PyBRet7jZ9v9pi-jUO3mJwzqxcnw,13922
|
|
64
64
|
langchain_agentx/loop/exit/query_source.py,sha256=b_VB13zxNbLAqUAuDt1E3SII3jNLagY-FdN6SbczTsI,2082
|
|
65
|
-
langchain_agentx/loop/exit/reason_codes.py,sha256=
|
|
65
|
+
langchain_agentx/loop/exit/reason_codes.py,sha256=Nk1x2ipftRIRa4U1Z2goyXXWB9TsPFL9TW6WfvtMCEo,4810
|
|
66
66
|
langchain_agentx/loop/exit/resolution.py,sha256=taIMHzAUtgrQoU9446-CUXBN0PyCujhl6wBQfbxR1qI,1427
|
|
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
|
|
@@ -138,7 +138,7 @@ langchain_agentx/loop/stream/synthetic_events.py,sha256=Z3JT3iTuB9UQOoQXUprAYvaF
|
|
|
138
138
|
langchain_agentx/loop/stream/transcript_gate.py,sha256=4AH3O0l8OrY_GQdqf-TOKOVS3selPLUC0jYLWQUTfkg,4690
|
|
139
139
|
langchain_agentx/loop/subagent/__init__.py,sha256=V5G_R0STODOi2Of0ALq1ZxiMPllw_mxDhgb5bc9HuqA,2677
|
|
140
140
|
langchain_agentx/loop/subagent/active_run_registry.py,sha256=KurhAMDTBnRkdxNidaMUynCTRzdDy67Tu2NwJt3UC_g,4310
|
|
141
|
-
langchain_agentx/loop/subagent/async_runner.py,sha256=
|
|
141
|
+
langchain_agentx/loop/subagent/async_runner.py,sha256=gGx8UMZbsnxUMPzc_7-XkWgd1EiuRcFAoOVUfjhFltE,13740
|
|
142
142
|
langchain_agentx/loop/subagent/boundary_snapshot.py,sha256=9cQuqN29nJDRnHnx9c8b09KRmODE2zv15Z06QyjVJps,10008
|
|
143
143
|
langchain_agentx/loop/subagent/cache_safe_params.py,sha256=-qcgdazEYRy6s4ILQuttpxraE3gsxq17Ro7LYBnuv3E,4460
|
|
144
144
|
langchain_agentx/loop/subagent/context.py,sha256=skkSPvnshEN4Lh5kWoD7zjUtCOKcjdb4Ho1eM1apXFo,22297
|
|
@@ -201,7 +201,7 @@ langchain_agentx/memory/session/extractor.py,sha256=UUprKKtcMqn0pVhEWSnwCcc2tlB6
|
|
|
201
201
|
langchain_agentx/memory/session/prompts.py,sha256=MA6UaNQsn9jrV78YuPbTTSKTmzvex_HY7kAaM9gVhUA,7690
|
|
202
202
|
langchain_agentx/memory/session/session_memory.py,sha256=DdHkX-3rGOkyuxc3byq1hWNkHgucyo-n1jwUEWjxuP8,16642
|
|
203
203
|
langchain_agentx/memory/session/tool_scope.py,sha256=DDhhzy_BEdZZe9SsisqZ9GOgMyt-aWUnCX-NH3QSPng,1684
|
|
204
|
-
langchain_agentx/observability/__init__.py,sha256=
|
|
204
|
+
langchain_agentx/observability/__init__.py,sha256=hKf_W4rTExbGQbK1ZyrrvmJ2aX-5Tx5hhY-Q8-gwkSg,2591
|
|
205
205
|
langchain_agentx/observability/evaluation/__init__.py,sha256=yqkw8noWqD-7p6xnhlF-M4F2FwYukS2XDpEYoXFNYZ4,430
|
|
206
206
|
langchain_agentx/observability/evaluation/retention_scheduler.py,sha256=8iKw9G8jImIaKfmGhO9Xtm3GhvSICuGwEZp7GGczDNo,2250
|
|
207
207
|
langchain_agentx/observability/evaluation/service.py,sha256=E0MZIBY67aVDooOYuri3_vTM_uguXJxCxNqQ5rJxFOg,3660
|
|
@@ -214,21 +214,22 @@ langchain_agentx/observability/evaluation/checkers/degradation.py,sha256=80-oENY
|
|
|
214
214
|
langchain_agentx/observability/evaluation/checkers/exit_quality.py,sha256=kcV2VYLeavinm5-hysCVhNfIjv2Gj_m4Xt65B403FoY,1274
|
|
215
215
|
langchain_agentx/observability/evaluation/checkers/session_memory.py,sha256=jYDdIDUAhYktggsymkBADBunrSbxdC66veM-tZt3a3s,1579
|
|
216
216
|
langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8SeE9145H7ej4naahRcmgw_9st21dY1W3IQa2sg,2097
|
|
217
|
-
langchain_agentx/observability/events/__init__.py,sha256=
|
|
217
|
+
langchain_agentx/observability/events/__init__.py,sha256=Zr9Iguf9XsJQLsCJy13YFdBtXHPEnI9aSWXWmA2MisA,1497
|
|
218
218
|
langchain_agentx/observability/events/accumulated_text_scope.py,sha256=M4OvuQTgReF8SFrOmr-M5vyOJpzfvxj_DKD5bVpGb70,3169
|
|
219
219
|
langchain_agentx/observability/events/agent_defaults.py,sha256=gmX0P__lux5xbCb8dtAlVCb3wZ7avaqFleoW4Q5anw4,1187
|
|
220
|
-
langchain_agentx/observability/events/agent_event_projector.py,sha256=
|
|
220
|
+
langchain_agentx/observability/events/agent_event_projector.py,sha256=eMri832yK_BZbwsB_APwUN9vu66zMpZVxTstChG5l0A,63010
|
|
221
221
|
langchain_agentx/observability/events/api_retry_projection.py,sha256=XGb09sNOi6k1ZGW4YqMLBE8GDxNwNMprk62jV9TS9dQ,2433
|
|
222
222
|
langchain_agentx/observability/events/factory.py,sha256=gqMATIK9tBQzaPWSPhCCSPuCq-WF562ZDTnFUaMm9Qc,1570
|
|
223
|
-
langchain_agentx/observability/events/finish_event_payload_composer.py,sha256=
|
|
224
|
-
langchain_agentx/observability/events/finish_outcome.py,sha256=
|
|
225
|
-
langchain_agentx/observability/events/langchain_agentx_event_adapter.py,sha256=
|
|
223
|
+
langchain_agentx/observability/events/finish_event_payload_composer.py,sha256=v7MyLNSZ_gR5u5WFxPJu_0MYNDvC9s9gI2iNNt9Vbr4,13189
|
|
224
|
+
langchain_agentx/observability/events/finish_outcome.py,sha256=igoueAwduzuuGDwg6u9AbBB-T2UBiqbUfNoxWCBYc_g,10401
|
|
225
|
+
langchain_agentx/observability/events/langchain_agentx_event_adapter.py,sha256=ujtD6G8erb9bwnHLvJYa-soWjzP99vjAE0tNKSMUGe8,7841
|
|
226
226
|
langchain_agentx/observability/events/model_end_text_backfill.py,sha256=7RBnxWLi1Oe-Bw9WTYqN-B6IdytFlY-Jso1STtQr6rE,5157
|
|
227
|
+
langchain_agentx/observability/events/oob_event_sink.py,sha256=ijE2AI8gfoMPRPXDwQszNWi_aCKwODywpG884blo08E,5670
|
|
227
228
|
langchain_agentx/observability/events/subagent_event_mapper.py,sha256=9M9cKzMz7_OXOod0JKtLb-FtUhAKiAVikKGRF6h6Ttk,6860
|
|
228
229
|
langchain_agentx/observability/events/subagent_window_text_guard.py,sha256=myo4SSV2TKZjpyrCoe-g5beNGmZnYZefa8nqW76q0Xc,3101
|
|
229
230
|
langchain_agentx/observability/events/tool_call_id_resolver.py,sha256=Mun8jNb91tbV7hvIUkFG9g6NJGHVMW0kHS7Zt6N2gUA,4530
|
|
230
231
|
langchain_agentx/observability/events/tool_outcome_resolver.py,sha256=YqF9Io9-0xdIdz7iYQuJkY-PiUXLC0zR26Eb9n35L9g,4357
|
|
231
|
-
langchain_agentx/observability/events/workflow_aware_event_adapter.py,sha256=
|
|
232
|
+
langchain_agentx/observability/events/workflow_aware_event_adapter.py,sha256=AxJ_TjfZsUKHifGUl_3JvG5dDk8rc-IhuNLVw1ffhf8,7896
|
|
232
233
|
langchain_agentx/observability/events/workflow_container_projector.py,sha256=0UdtZwKBM9k9yWV7gbbwMquT0I_cZlNqnjO0SCbvZ9s,22414
|
|
233
234
|
langchain_agentx/observability/events/workflow_event_mapper.py,sha256=EA0GFWmrSIyqCOehixKnYvylF0NOj5RkRhdLA50_qrU,3703
|
|
234
235
|
langchain_agentx/observability/logging/__init__.py,sha256=mDtPOxYZZFx8ezQCSwnmnfrdVi305DeJFzThwHBtnrY,508
|
|
@@ -292,11 +293,11 @@ langchain_agentx/provider/semantics/families/openai_finish_reason.py,sha256=t5eb
|
|
|
292
293
|
langchain_agentx/provider/semantics/families/openai_interpreter.py,sha256=zT7EFfHJ5fmYZ_F59vDfyrYdEwcMJANxkoW1Pt3iocI,1915
|
|
293
294
|
langchain_agentx/provider/semantics/families/openai_normalizer.py,sha256=oaf2Riz8EY6VQpc6SNKtsb-naMELw3gYV3IsnZoRBYU,2155
|
|
294
295
|
langchain_agentx/session/__init__.py,sha256=uXxjBzhW1FtvSfDTTaGZPbyeSFRzRQb2MfxDumKViug,1234
|
|
295
|
-
langchain_agentx/session/agent_session.py,sha256=
|
|
296
|
+
langchain_agentx/session/agent_session.py,sha256=NEIKJIjp-whbpmduSatBaF1b6YIcebvUYB1ginu12WE,24617
|
|
296
297
|
langchain_agentx/session/cleanup_registry.py,sha256=aaPUn9psCwgZ27fB1qW7lzc1TeKaBvQAxhcHDiNs2Rs,4137
|
|
297
298
|
langchain_agentx/session/conversation_factory.py,sha256=zsGBx9OwAOW21sqFsjK4d9mYLRRyxC5Bp1R9OCijxzI,5911
|
|
298
299
|
langchain_agentx/session/conversation_recovery.py,sha256=OXzaxywRmqRwNA2Q1UNOpUvMlIjBzgdbsNPxPGHwoaA,5803
|
|
299
|
-
langchain_agentx/session/conversation_session.py,sha256=
|
|
300
|
+
langchain_agentx/session/conversation_session.py,sha256=aQJBZlQsgg2-q_it4qvRwhPkvKNODDZ1TDsrMXMwvCE,17563
|
|
300
301
|
langchain_agentx/session/factory.py,sha256=qzZF8EkELFKPzQ-fRI2C72Usnqk_hASWB1GM8_mYjZ8,6172
|
|
301
302
|
langchain_agentx/session/plan_mode.py,sha256=GjPLBG6eVrTB7M-CeMwLqkHCQDdD93cCgwIAvOF0wRM,4710
|
|
302
303
|
langchain_agentx/session/plan_mode_attachments.py,sha256=byIJC2F3RTJ9BTWVhpbE1e1rRwahAH5anPKsMXFvUE8,13861
|
|
@@ -691,7 +692,7 @@ langchain_agentx/utils/permissions/filesystem.py,sha256=79vRSRPnPadHlDOOYvvbuJaJ
|
|
|
691
692
|
langchain_agentx/utils/settings/__init__.py,sha256=UXzHZmrBJsO8ZpDmHfclHXpS7rfpmqRoXEnmTFbtVpE,172
|
|
692
693
|
langchain_agentx/utils/settings/managed_path.py,sha256=MVcw4F9bTrhKqTLom8ZiiqSqCREMbGcAK9Lt8hkD67M,1155
|
|
693
694
|
langchain_agentx/workflow/__init__.py,sha256=wgjJM2wW0DbFHjsNbq2bKYgFs3BIPFRuQbH8Ry5kfys,4324
|
|
694
|
-
langchain_agentx/workflow/base.py,sha256=
|
|
695
|
+
langchain_agentx/workflow/base.py,sha256=rzZTH0ciepTglXfhDMkhl1uiJV4d_bXkOgpHlmu-N0w,33409
|
|
695
696
|
langchain_agentx/workflow/batch.py,sha256=rdE9xbQ2-SI3-6yNTsKoSylDoTD5B0djLSKwqy1BdZ8,5474
|
|
696
697
|
langchain_agentx/workflow/catalog_registry.py,sha256=tYUny5FkHwQU-RRUxOdxaJhiRV_9VRXU636Yn_Gzt-g,2109
|
|
697
698
|
langchain_agentx/workflow/dag.py,sha256=Y1IR1IwnM79Xuev6HZnOvAE3Q23MRnA9lu4vg2Cie48,4469
|
|
@@ -756,8 +757,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
|
|
|
756
757
|
langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
|
|
757
758
|
langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
|
|
758
759
|
langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
|
|
759
|
-
langchain_agentx_python-2.1.
|
|
760
|
-
langchain_agentx_python-2.1.
|
|
761
|
-
langchain_agentx_python-2.1.
|
|
762
|
-
langchain_agentx_python-2.1.
|
|
763
|
-
langchain_agentx_python-2.1.
|
|
760
|
+
langchain_agentx_python-2.1.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
761
|
+
langchain_agentx_python-2.1.7.dist-info/METADATA,sha256=l7wjAieBJ28Tvtsfo0pNg503DMYjggzW8kQHm8t4T3M,24250
|
|
762
|
+
langchain_agentx_python-2.1.7.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
763
|
+
langchain_agentx_python-2.1.7.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
|
|
764
|
+
langchain_agentx_python-2.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-2.1.6.dist-info → langchain_agentx_python-2.1.7.dist-info}/top_level.txt
RENAMED
|
File without changes
|