langchain-agentx-python 2.3.7__py3-none-any.whl → 2.3.9__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/loop_abort.py +14 -2
- langchain_agentx/loop/run_context.py +58 -2
- langchain_agentx/loop/subagent/context.py +5 -0
- langchain_agentx/loop/subagent/orchestrator.py +28 -1
- langchain_agentx/loop/subagent/progress.py +2 -0
- langchain_agentx/loop/subagent/progress_dispatcher.py +26 -0
- langchain_agentx/loop/subagent/runner.py +61 -13
- langchain_agentx/observability/events/agent_event_projector.py +14 -0
- langchain_agentx/observability/events/skill_progress_event_mapper.py +87 -0
- langchain_agentx/observability/events/subagent_event_mapper.py +10 -1
- langchain_agentx/workflow/execution.py +3 -1
- {langchain_agentx_python-2.3.7.dist-info → langchain_agentx_python-2.3.9.dist-info}/METADATA +1 -1
- {langchain_agentx_python-2.3.7.dist-info → langchain_agentx_python-2.3.9.dist-info}/RECORD +17 -16
- {langchain_agentx_python-2.3.7.dist-info → langchain_agentx_python-2.3.9.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-2.3.7.dist-info → langchain_agentx_python-2.3.9.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-2.3.7.dist-info → langchain_agentx_python-2.3.9.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -45,11 +45,23 @@ class LoopAbortSignal(Exception):
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def is_cancel_requested() -> bool:
|
|
48
|
-
"""
|
|
48
|
+
"""当前或祖先 RunContext 是否已请求取消(无 context 则为 False)。
|
|
49
|
+
|
|
50
|
+
祖先链:nested Parallelization ctx 下 leaf 可读到 L0 ``cancel_requested``。
|
|
51
|
+
"""
|
|
49
52
|
from .run_context import get_run_context
|
|
50
53
|
|
|
51
54
|
ctx = get_run_context()
|
|
52
|
-
|
|
55
|
+
seen: set[int] = set()
|
|
56
|
+
while ctx is not None:
|
|
57
|
+
ident = id(ctx)
|
|
58
|
+
if ident in seen:
|
|
59
|
+
break
|
|
60
|
+
seen.add(ident)
|
|
61
|
+
if bool(ctx.cancel_requested):
|
|
62
|
+
return True
|
|
63
|
+
ctx = ctx.parent
|
|
64
|
+
return False
|
|
53
65
|
|
|
54
66
|
|
|
55
67
|
def abort_signal_for_phase(
|
|
@@ -12,6 +12,7 @@ run_context.py — Workflow/Agent 结构化并发上下文(parent → child ca
|
|
|
12
12
|
asyncio 为主;thread 仅 bounded join(无法强制中断 LLM);anyio/trio 不保证 context 传播。
|
|
13
13
|
P1:reset 失败时在仍可见 owner 的 derived logical context 内 force-clear;
|
|
14
14
|
宿主 detached ingest 用 create_task_with_copied_context / run_context_scope。
|
|
15
|
+
nested Parallelization:child_contexts + 递归 cancel_tree;W4 可用 use_cancel_authority。
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
from __future__ import annotations
|
|
@@ -41,6 +42,10 @@ class RunContext:
|
|
|
41
42
|
root_task: asyncio.Task
|
|
42
43
|
child_tasks: set[asyncio.Task] = field(default_factory=set)
|
|
43
44
|
child_threads: set[threading.Thread] = field(default_factory=set)
|
|
45
|
+
# nested Parallelization/Chaining 再 install 时登记;cancel_tree 先深取消其 child_tasks。
|
|
46
|
+
# 用 list:RunContext 含 set/Task 不可哈希,不能放 set。
|
|
47
|
+
child_contexts: list["RunContext"] = field(default_factory=list, repr=False)
|
|
48
|
+
parent: Optional["RunContext"] = field(default=None, repr=False, compare=False)
|
|
44
49
|
cancel_requested: bool = False
|
|
45
50
|
# D-EVT-6:宿主在 cancel_tree / killAll 时写入;finish 投影读取;缺省 unknown。
|
|
46
51
|
# 取值:escape | workflow_stop | kill_agents | unknown;禁止从「是否有后台」猜测。
|
|
@@ -52,10 +57,42 @@ class RunContext:
|
|
|
52
57
|
def register_child_thread(self, thread: threading.Thread) -> None:
|
|
53
58
|
self.child_threads.add(thread)
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
def register_child_context(self, child: "RunContext") -> None:
|
|
61
|
+
"""登记 nested RunContext(install_run_context(parent=…))。"""
|
|
62
|
+
if all(existing is not child for existing in self.child_contexts):
|
|
63
|
+
self.child_contexts.append(child)
|
|
64
|
+
child.parent = self
|
|
65
|
+
|
|
66
|
+
def cancel_authority(self) -> "RunContext":
|
|
67
|
+
"""取消权威:最外层 RunContext(宿主 Stop / cancel_tree 目标)。"""
|
|
68
|
+
node: RunContext = self
|
|
69
|
+
seen: set[int] = set()
|
|
70
|
+
while node.parent is not None:
|
|
71
|
+
ident = id(node)
|
|
72
|
+
if ident in seen:
|
|
73
|
+
break
|
|
74
|
+
seen.add(ident)
|
|
75
|
+
node = node.parent
|
|
76
|
+
return node
|
|
77
|
+
|
|
78
|
+
def _mark_cancel_requested(self, *, source: str | None) -> None:
|
|
79
|
+
"""本层 + 全部 nested 写入 cancel_requested(协作闸可立即看到)。"""
|
|
56
80
|
self.cancel_requested = True
|
|
81
|
+
if source is not None and self.cancel_source is None:
|
|
82
|
+
self.cancel_source = source
|
|
83
|
+
for nested in list(self.child_contexts):
|
|
84
|
+
nested._mark_cancel_requested(source=source or self.cancel_source)
|
|
85
|
+
|
|
86
|
+
async def cancel_tree(self, timeout: float = 10.0) -> None:
|
|
87
|
+
"""取消本层 child_tasks,并递归 nested child_contexts(W4 / leaf producer)。"""
|
|
88
|
+
self._mark_cancel_requested(source=self.cancel_source)
|
|
57
89
|
deadline = time.monotonic() + timeout
|
|
58
90
|
|
|
91
|
+
# 深取消:先砍 nested 内 W4 producer,再 cancel 本层(含 nested.root_task)。
|
|
92
|
+
for nested in list(self.child_contexts):
|
|
93
|
+
remaining = max(0.0, deadline - time.monotonic())
|
|
94
|
+
await nested.cancel_tree(timeout=remaining)
|
|
95
|
+
|
|
59
96
|
for child in list(self.child_tasks):
|
|
60
97
|
child.cancel()
|
|
61
98
|
if self.child_tasks:
|
|
@@ -128,6 +165,7 @@ def install_run_context(
|
|
|
128
165
|
ctx = RunContext(root_task=current)
|
|
129
166
|
if parent is not None:
|
|
130
167
|
parent.register_child(ctx.root_task)
|
|
168
|
+
parent.register_child_context(ctx)
|
|
131
169
|
token = _current_run_context.set(ctx)
|
|
132
170
|
return ctx, token
|
|
133
171
|
|
|
@@ -205,15 +243,32 @@ async def drive_async_iterator_in_current_task(
|
|
|
205
243
|
yield item
|
|
206
244
|
|
|
207
245
|
|
|
246
|
+
def resolve_cancel_authority(
|
|
247
|
+
ctx: RunContext | None = None,
|
|
248
|
+
) -> RunContext | None:
|
|
249
|
+
"""解析取消权威 ctx;缺省取当前 ContextVar 的 outermost。"""
|
|
250
|
+
current = ctx if ctx is not None else get_run_context()
|
|
251
|
+
if current is None:
|
|
252
|
+
return None
|
|
253
|
+
return current.cancel_authority()
|
|
254
|
+
|
|
255
|
+
|
|
208
256
|
def register_spawned_task(
|
|
209
257
|
task: asyncio.Task,
|
|
210
258
|
*,
|
|
211
259
|
owner: RunContext | None = None,
|
|
260
|
+
use_cancel_authority: bool = False,
|
|
212
261
|
) -> None:
|
|
213
|
-
"""将 spawn 出的 asyncio task 注册到 RunContext(无 context 时 no-op)。
|
|
262
|
+
"""将 spawn 出的 asyncio task 注册到 RunContext(无 context 时 no-op)。
|
|
263
|
+
|
|
264
|
+
``use_cancel_authority=True``:挂到 outermost(Workflow W4),避免仅挂在
|
|
265
|
+
nested Parallelization ctx 上导致 L0 ``cancel_tree`` 砍不到 producer。
|
|
266
|
+
"""
|
|
214
267
|
ctx = owner if owner is not None else get_run_context()
|
|
215
268
|
if ctx is None:
|
|
216
269
|
return
|
|
270
|
+
if use_cancel_authority:
|
|
271
|
+
ctx = ctx.cancel_authority()
|
|
217
272
|
try:
|
|
218
273
|
owner_loop = ctx.root_task.get_loop()
|
|
219
274
|
task_loop = task.get_loop()
|
|
@@ -249,5 +304,6 @@ __all__ = [
|
|
|
249
304
|
"register_spawned_task",
|
|
250
305
|
"register_spawned_thread",
|
|
251
306
|
"reset_run_context",
|
|
307
|
+
"resolve_cancel_authority",
|
|
252
308
|
"run_context_scope",
|
|
253
309
|
]
|
|
@@ -127,6 +127,11 @@ class SubagentContext:
|
|
|
127
127
|
inherit_hooks: bool | frozenset[HookEvent] | None = True
|
|
128
128
|
"""True/None → DEFAULT_SUBAGENT_INHERITED_EVENTS;False → 不继承;frozenset → 自定义事件集。"""
|
|
129
129
|
|
|
130
|
+
progress_host_kind: str | None = None
|
|
131
|
+
"""Skill fork:``skill_progress`` 时 runner 只冒泡 CC 同构 skill progress,不发 subagent 双通道。"""
|
|
132
|
+
skill_prompt: str = ""
|
|
133
|
+
"""Skill fork:↔ CC ``onProgress`` ``data.prompt``(skill 正文)。"""
|
|
134
|
+
|
|
130
135
|
|
|
131
136
|
@dataclass
|
|
132
137
|
class SubagentResult:
|
|
@@ -25,7 +25,7 @@ from langchain_agentx.loop.runtime.subagent_execution_paths import (
|
|
|
25
25
|
resolve_subagent_effective_cwd,
|
|
26
26
|
resolve_subagent_workspace_root,
|
|
27
27
|
)
|
|
28
|
-
from langchain_agentx.loop.subagent.context import create_subagent_context
|
|
28
|
+
from langchain_agentx.loop.subagent.context import SubagentContext, create_subagent_context
|
|
29
29
|
from langchain_agentx.loop.subagent.hooks_pipeline import (
|
|
30
30
|
_apply_agent_frontmatter_hooks as _pipeline_apply_agent_hooks,
|
|
31
31
|
_apply_skill_fork_hooks as _pipeline_apply_skill_hooks,
|
|
@@ -54,6 +54,23 @@ from langchain_agentx.observability.logging import ObservabilityLoggerAdapter, b
|
|
|
54
54
|
from langchain_agentx.workspace import resolve_agent_state_config
|
|
55
55
|
from langchain_agentx.loop.config.thinking_config import ThinkingConfig
|
|
56
56
|
|
|
57
|
+
|
|
58
|
+
def _apply_skill_fork_progress_host(
|
|
59
|
+
subagent_ctx: SubagentContext,
|
|
60
|
+
*,
|
|
61
|
+
skill_hook_frontmatter: dict[str, Any] | None,
|
|
62
|
+
skill_prompt: str,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Skill fork:注入 CC 同构 skill_progress 冒泡上下文(Phase A · L8/L9)。"""
|
|
65
|
+
if skill_hook_frontmatter is None:
|
|
66
|
+
return
|
|
67
|
+
from langchain_agentx.tools.skill.fork import is_fork_execution_context
|
|
68
|
+
|
|
69
|
+
if is_fork_execution_context(skill_hook_frontmatter):
|
|
70
|
+
subagent_ctx.progress_host_kind = "skill_progress"
|
|
71
|
+
subagent_ctx.skill_prompt = skill_prompt or ""
|
|
72
|
+
|
|
73
|
+
|
|
57
74
|
if TYPE_CHECKING:
|
|
58
75
|
from langchain_agentx.loop.runtime import AgentLoopConfig
|
|
59
76
|
from langchain_agentx.tool_runtime.models import ToolExecutionContext
|
|
@@ -344,6 +361,11 @@ class SubagentOrchestrator:
|
|
|
344
361
|
is_builtin_subagent=getattr(subagent_config, "is_builtin", False),
|
|
345
362
|
inherit_hooks=True if inherit_hooks is None else inherit_hooks,
|
|
346
363
|
)
|
|
364
|
+
_apply_skill_fork_progress_host(
|
|
365
|
+
subagent_ctx,
|
|
366
|
+
skill_hook_frontmatter=skill_hook_frontmatter,
|
|
367
|
+
skill_prompt=inp.prompt or "",
|
|
368
|
+
)
|
|
347
369
|
subagent_ctx.mcp_child_session = mcp_child
|
|
348
370
|
scope = propagate_subagent_tool_scope(
|
|
349
371
|
parent_ctx=ctx,
|
|
@@ -564,6 +586,11 @@ class SubagentOrchestrator:
|
|
|
564
586
|
is_builtin_subagent=getattr(subagent_config, "is_builtin", False),
|
|
565
587
|
inherit_hooks=True if inherit_hooks is None else inherit_hooks,
|
|
566
588
|
)
|
|
589
|
+
_apply_skill_fork_progress_host(
|
|
590
|
+
subagent_ctx,
|
|
591
|
+
skill_hook_frontmatter=skill_hook_frontmatter,
|
|
592
|
+
skill_prompt=inp.prompt or "",
|
|
593
|
+
)
|
|
567
594
|
subagent_ctx.mcp_child_session = mcp_child
|
|
568
595
|
scope = propagate_subagent_tool_scope(
|
|
569
596
|
parent_ctx=ctx,
|
|
@@ -8,6 +8,8 @@ CC 对照:src/agent/subagent/progress.ts onProgress 回调参数类型
|
|
|
8
8
|
|
|
9
9
|
# transcript lane 在 tool_progress custom event 上的 phase 值(与 adapter mapper 冻结一致)
|
|
10
10
|
SUBAGENT_TRANSCRIPT_PHASE = "transcript_message"
|
|
11
|
+
# Skill fork CC 对齐:Typed Tool Progress(↔ CC data.type === 'skill_progress')
|
|
12
|
+
SKILL_PROGRESS_PHASE = "skill_progress"
|
|
11
13
|
|
|
12
14
|
from dataclasses import dataclass, field
|
|
13
15
|
from typing import Any, Literal
|
|
@@ -14,6 +14,7 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
from langchain_agentx.loop.subagent.context import SubagentContext
|
|
16
16
|
from langchain_agentx.loop.subagent.progress import (
|
|
17
|
+
SKILL_PROGRESS_PHASE,
|
|
17
18
|
SUBAGENT_TRANSCRIPT_PHASE,
|
|
18
19
|
SubagentProgressEvent,
|
|
19
20
|
)
|
|
@@ -46,6 +47,31 @@ class SubagentProgressDispatcher:
|
|
|
46
47
|
event_data["tool_input"] = dict(event.tool_call_detail.tool_input)
|
|
47
48
|
return event_data
|
|
48
49
|
|
|
50
|
+
@staticmethod
|
|
51
|
+
def build_skill_progress_payload(
|
|
52
|
+
ctx: SubagentContext,
|
|
53
|
+
*,
|
|
54
|
+
message_kind: str,
|
|
55
|
+
message: dict[str, Any],
|
|
56
|
+
step: int,
|
|
57
|
+
tool_name: str,
|
|
58
|
+
agent_id: str,
|
|
59
|
+
) -> dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"tool_call_id": ctx.parent_tool_call_id,
|
|
62
|
+
"agent_id": agent_id,
|
|
63
|
+
"phase": SKILL_PROGRESS_PHASE,
|
|
64
|
+
"step": step,
|
|
65
|
+
"tool_name": tool_name,
|
|
66
|
+
"message_kind": message_kind,
|
|
67
|
+
"message": dict(message),
|
|
68
|
+
"prompt": str(getattr(ctx, "skill_prompt", "") or ""),
|
|
69
|
+
"data": {
|
|
70
|
+
"subagent_type": ctx.subagent_type,
|
|
71
|
+
"type": "skill_progress",
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
49
75
|
@staticmethod
|
|
50
76
|
def build_transcript_payload(
|
|
51
77
|
ctx: SubagentContext,
|
|
@@ -265,6 +265,35 @@ async def _emit_progress(
|
|
|
265
265
|
await SubagentProgressDispatcher.dispatch_async(parent_config, payload)
|
|
266
266
|
|
|
267
267
|
|
|
268
|
+
def _uses_skill_progress_host(ctx: SubagentContext) -> bool:
|
|
269
|
+
return getattr(ctx, "progress_host_kind", None) == "skill_progress"
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def _emit_skill_progress(
|
|
273
|
+
ctx: SubagentContext,
|
|
274
|
+
*,
|
|
275
|
+
message_kind: str,
|
|
276
|
+
message: dict[str, Any],
|
|
277
|
+
step: int,
|
|
278
|
+
tool_name: str,
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Skill fork:单帧 CC 同构 skill_progress(L9 · 不发 subagent 双通道)。"""
|
|
281
|
+
parent_config = getattr(ctx, "runnable_config", None)
|
|
282
|
+
if parent_config is None:
|
|
283
|
+
return
|
|
284
|
+
payload = SubagentProgressDispatcher.build_skill_progress_payload(
|
|
285
|
+
ctx,
|
|
286
|
+
message_kind=message_kind,
|
|
287
|
+
message=message,
|
|
288
|
+
step=step,
|
|
289
|
+
tool_name=tool_name,
|
|
290
|
+
agent_id=ctx.agent_id,
|
|
291
|
+
)
|
|
292
|
+
if SubagentProgressDispatcher.dispatch_sync(parent_config, payload):
|
|
293
|
+
return
|
|
294
|
+
await SubagentProgressDispatcher.dispatch_async(parent_config, payload)
|
|
295
|
+
|
|
296
|
+
|
|
268
297
|
async def _emit_transcript_progress(
|
|
269
298
|
ctx: SubagentContext,
|
|
270
299
|
event: SubagentProgressEvent,
|
|
@@ -322,6 +351,21 @@ async def _handle_tool_start_event(
|
|
|
322
351
|
if not tool_call_id:
|
|
323
352
|
tool_call_id = f"{SYNTHETIC_TRANSCRIPT_TOOL_ID_PREFIX}{ctx.agent_id}:{next_step}"
|
|
324
353
|
|
|
354
|
+
message_kind, normalized = SubagentTranscriptMessageBuilder.build_tool_use(
|
|
355
|
+
tool_name=tool_name,
|
|
356
|
+
tool_input=tool_input if isinstance(tool_input, dict) else None,
|
|
357
|
+
tool_call_id=tool_call_id,
|
|
358
|
+
)
|
|
359
|
+
if _uses_skill_progress_host(ctx):
|
|
360
|
+
await _emit_skill_progress(
|
|
361
|
+
ctx,
|
|
362
|
+
message_kind=message_kind,
|
|
363
|
+
message=normalized,
|
|
364
|
+
step=next_step,
|
|
365
|
+
tool_name=tool_name,
|
|
366
|
+
)
|
|
367
|
+
return next_step, tool_name
|
|
368
|
+
|
|
325
369
|
await _emit_progress(
|
|
326
370
|
ctx,
|
|
327
371
|
SubagentProgressEvent(
|
|
@@ -335,12 +379,6 @@ async def _handle_tool_start_event(
|
|
|
335
379
|
trace=trace,
|
|
336
380
|
),
|
|
337
381
|
)
|
|
338
|
-
|
|
339
|
-
message_kind, normalized = SubagentTranscriptMessageBuilder.build_tool_use(
|
|
340
|
-
tool_name=tool_name,
|
|
341
|
-
tool_input=tool_input if isinstance(tool_input, dict) else None,
|
|
342
|
-
tool_call_id=tool_call_id,
|
|
343
|
-
)
|
|
344
382
|
await _emit_transcript_progress(
|
|
345
383
|
ctx,
|
|
346
384
|
SubagentProgressEvent(
|
|
@@ -419,6 +457,23 @@ async def _handle_tool_end_event(
|
|
|
419
457
|
synthetic=synthetic,
|
|
420
458
|
)
|
|
421
459
|
transcript.append(msg_dict)
|
|
460
|
+
|
|
461
|
+
message_kind, normalized = SubagentTranscriptMessageBuilder.build_tool_result(
|
|
462
|
+
tool_name=tool_name,
|
|
463
|
+
content=transcript_text,
|
|
464
|
+
tool_call_id=tool_call_id,
|
|
465
|
+
synthetic=synthetic,
|
|
466
|
+
)
|
|
467
|
+
if _uses_skill_progress_host(ctx):
|
|
468
|
+
await _emit_skill_progress(
|
|
469
|
+
ctx,
|
|
470
|
+
message_kind=message_kind,
|
|
471
|
+
message=normalized,
|
|
472
|
+
step=step,
|
|
473
|
+
tool_name=tool_name,
|
|
474
|
+
)
|
|
475
|
+
return
|
|
476
|
+
|
|
422
477
|
await _emit_progress(
|
|
423
478
|
ctx,
|
|
424
479
|
SubagentProgressEvent(
|
|
@@ -430,13 +485,6 @@ async def _handle_tool_end_event(
|
|
|
430
485
|
summary=summary[:200] if summary else "",
|
|
431
486
|
),
|
|
432
487
|
)
|
|
433
|
-
|
|
434
|
-
message_kind, normalized = SubagentTranscriptMessageBuilder.build_tool_result(
|
|
435
|
-
tool_name=tool_name,
|
|
436
|
-
content=transcript_text,
|
|
437
|
-
tool_call_id=tool_call_id,
|
|
438
|
-
synthetic=synthetic,
|
|
439
|
-
)
|
|
440
488
|
await _emit_transcript_progress(
|
|
441
489
|
ctx,
|
|
442
490
|
SubagentProgressEvent(
|
|
@@ -38,6 +38,9 @@ from langchain_agentx.observability.events.model_end_text_backfill import (
|
|
|
38
38
|
BackfillKind,
|
|
39
39
|
ModelEndTextBackfillPolicy,
|
|
40
40
|
)
|
|
41
|
+
from langchain_agentx.observability.events.skill_progress_event_mapper import (
|
|
42
|
+
SkillProgressEventMapper,
|
|
43
|
+
)
|
|
41
44
|
from langchain_agentx.observability.events.subagent_event_mapper import (
|
|
42
45
|
SubagentBoundaryPayloadBuilder,
|
|
43
46
|
SubagentProgressEventMapper,
|
|
@@ -136,6 +139,7 @@ class LangchainAgentEventType(Enum):
|
|
|
136
139
|
SUBAGENT_TOOL_RESULT = "subagent-tool-result"
|
|
137
140
|
SUBAGENT_TRANSCRIPT_MESSAGE = "subagent-transcript-message"
|
|
138
141
|
SUBAGENT_MODEL_STEP = "subagent-model-step"
|
|
142
|
+
SKILL_PROGRESS = "skill-progress"
|
|
139
143
|
ERROR = "error"
|
|
140
144
|
API_RETRY = "api-retry"
|
|
141
145
|
ASSISTANT_TURN_SUPERSEDED = "assistant-turn-superseded"
|
|
@@ -1553,6 +1557,16 @@ class AgentEventProjector:
|
|
|
1553
1557
|
if not isinstance(data, dict):
|
|
1554
1558
|
return
|
|
1555
1559
|
|
|
1560
|
+
skill_mapped = SkillProgressEventMapper.map_custom_tool_progress(data)
|
|
1561
|
+
if skill_mapped is not None:
|
|
1562
|
+
yield self._event(
|
|
1563
|
+
event_type=skill_mapped.event_type,
|
|
1564
|
+
data=skill_mapped.data,
|
|
1565
|
+
step_index=self.state.step_index,
|
|
1566
|
+
tool_name=skill_mapped.tool_name,
|
|
1567
|
+
)
|
|
1568
|
+
return
|
|
1569
|
+
|
|
1556
1570
|
if self.enable_subagent_events:
|
|
1557
1571
|
mapped = SubagentProgressEventMapper.map_custom_tool_progress(data)
|
|
1558
1572
|
if mapped is not None:
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
skill_progress_event_mapper.py — Skill fork tool_progress → skill-progress SSE
|
|
3
|
+
|
|
4
|
+
职责:将 custom event ``tool_progress``(phase skill_progress)映射为 LangchainAgentEventType.SKILL_PROGRESS。
|
|
5
|
+
链路位置:AgentEventProjector._handle_custom_event,优先于 SubagentProgressEventMapper。
|
|
6
|
+
当前裁剪范围:仅 Skill fork progress;不含 Agent agent_progress(另轨 subagent-*)。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
# 与 loop.subagent.progress.SKILL_PROGRESS_PHASE 同值;此处不 import loop,避免 observability↔loop 循环依赖
|
|
15
|
+
_SKILL_PROGRESS_PHASE = "skill_progress"
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from langchain_agentx.observability.events.langchain_agentx_event_adapter import (
|
|
19
|
+
LangchainAgentEventType,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class MappedSkillProgress:
|
|
25
|
+
event_type: "LangchainAgentEventType"
|
|
26
|
+
data: dict[str, Any]
|
|
27
|
+
tool_name: str | None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SkillProgressEventMapper:
|
|
31
|
+
"""Skill fork progress → ``skill-progress`` 公共协议。"""
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def is_skill_progress_payload(cls, data: dict[str, Any]) -> bool:
|
|
35
|
+
if not isinstance(data, dict):
|
|
36
|
+
return False
|
|
37
|
+
phase = data.get("phase")
|
|
38
|
+
return isinstance(phase, str) and phase == _SKILL_PROGRESS_PHASE
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def map_custom_tool_progress(
|
|
42
|
+
cls,
|
|
43
|
+
data: dict[str, Any],
|
|
44
|
+
) -> MappedSkillProgress | None:
|
|
45
|
+
from langchain_agentx.observability.events.langchain_agentx_event_adapter import (
|
|
46
|
+
LangchainAgentEventType,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if not cls.is_skill_progress_payload(data):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
message = data.get("message")
|
|
53
|
+
if not isinstance(message, dict):
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
message_kind = data.get("message_kind")
|
|
57
|
+
if message_kind not in ("assistant", "user"):
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
agent_id = data.get("agent_id")
|
|
61
|
+
if not isinstance(agent_id, str) or not agent_id.strip():
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
step_raw = data.get("step", 0)
|
|
65
|
+
step = int(step_raw) if isinstance(step_raw, (int, float)) else 0
|
|
66
|
+
|
|
67
|
+
event_data: dict[str, Any] = {
|
|
68
|
+
"tool_call_id": str(data.get("tool_call_id") or ""),
|
|
69
|
+
"agent_id": agent_id,
|
|
70
|
+
"type": "skill_progress",
|
|
71
|
+
"prompt": str(data.get("prompt") or ""),
|
|
72
|
+
"message_kind": message_kind,
|
|
73
|
+
"message": dict(message),
|
|
74
|
+
"step": step,
|
|
75
|
+
}
|
|
76
|
+
tool_name = str(data.get("tool_name") or "")
|
|
77
|
+
if tool_name:
|
|
78
|
+
event_data["tool_name"] = tool_name
|
|
79
|
+
|
|
80
|
+
return MappedSkillProgress(
|
|
81
|
+
event_type=LangchainAgentEventType.SKILL_PROGRESS,
|
|
82
|
+
data=event_data,
|
|
83
|
+
tool_name=tool_name or None,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = ["MappedSkillProgress", "SkillProgressEventMapper"]
|
|
@@ -12,6 +12,8 @@ from __future__ import annotations
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from typing import Any, TYPE_CHECKING
|
|
14
14
|
|
|
15
|
+
_SKILL_PROGRESS_PHASE = "skill_progress"
|
|
16
|
+
|
|
15
17
|
if TYPE_CHECKING:
|
|
16
18
|
from langchain_agentx.observability.events.langchain_agentx_event_adapter import (
|
|
17
19
|
LangchainAgentEventType,
|
|
@@ -21,7 +23,11 @@ _SUBAGENT_TRANSCRIPT_PHASE = "transcript_message"
|
|
|
21
23
|
|
|
22
24
|
_SUBAGENT_TEASER_PHASES = frozenset({"tool_use", "tool_result"})
|
|
23
25
|
_SUBAGENT_DROP_PHASES = frozenset({"model_step"})
|
|
24
|
-
_SUBAGENT_KNOWN_PHASES =
|
|
26
|
+
_SUBAGENT_KNOWN_PHASES = (
|
|
27
|
+
_SUBAGENT_TEASER_PHASES
|
|
28
|
+
| _SUBAGENT_DROP_PHASES
|
|
29
|
+
| {_SUBAGENT_TRANSCRIPT_PHASE, _SKILL_PROGRESS_PHASE}
|
|
30
|
+
)
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
@dataclass(frozen=True)
|
|
@@ -109,6 +115,9 @@ class SubagentProgressEventMapper:
|
|
|
109
115
|
if not isinstance(phase, str):
|
|
110
116
|
return None
|
|
111
117
|
|
|
118
|
+
if phase == _SKILL_PROGRESS_PHASE:
|
|
119
|
+
return None
|
|
120
|
+
|
|
112
121
|
if phase == _SUBAGENT_TRANSCRIPT_PHASE:
|
|
113
122
|
return cls._map_transcript(data, LangchainAgentEventType)
|
|
114
123
|
|
|
@@ -445,7 +445,9 @@ class WorkflowAgentLoopExecutor:
|
|
|
445
445
|
await queue.put(None)
|
|
446
446
|
|
|
447
447
|
task = asyncio.create_task(_producer())
|
|
448
|
-
|
|
448
|
+
# W4:挂到 cancel authority(outermost),避免 Parallelization nested
|
|
449
|
+
# install 后 L0.cancel_tree 只看见 nested.root、砍不到 leaf producer。
|
|
450
|
+
register_spawned_task(task, use_cancel_authority=True)
|
|
449
451
|
try:
|
|
450
452
|
while True:
|
|
451
453
|
event = await queue.get()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langchain_agentx/__init__.py,sha256=
|
|
1
|
+
langchain_agentx/__init__.py,sha256=P6NRAff0z_tvGxr7Vw8SjC7iBM818Lx6SXrGH9p_dvE,2014
|
|
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,9 +28,9 @@ langchain_agentx/health/models.py,sha256=lHwdvn0w-qs-c8mI6I3nlnoCk8--y7gADj7C9u7
|
|
|
28
28
|
langchain_agentx/health/preflight.py,sha256=wGysAnhT-jR1LfyjdaX9diO573Zzt5Z1M3vxDbpdcK0,5612
|
|
29
29
|
langchain_agentx/loop/__init__.py,sha256=JoFPFrlllvGCYFEk2RUuqq9AfUIdLVSNbrRdTRy6g_I,1428
|
|
30
30
|
langchain_agentx/loop/benchmark_frozen_config.py,sha256=YI4IaDVMBOEucF36F2oNv5mw1TqMG_rm_LJSpKAnFWM,13942
|
|
31
|
-
langchain_agentx/loop/loop_abort.py,sha256=
|
|
31
|
+
langchain_agentx/loop/loop_abort.py,sha256=wFlvCLjE9Avs83_8hqmUq0DlP58LSjtxOge-g1K4oPQ,3547
|
|
32
32
|
langchain_agentx/loop/message_contract.py,sha256=Twl83TneU2PRdxgGRBxs6S9_ovRT6cb5WeCUzd6oJnE,2196
|
|
33
|
-
langchain_agentx/loop/run_context.py,sha256=
|
|
33
|
+
langchain_agentx/loop/run_context.py,sha256=CRzbzuQziFmjvTTgZ6ctKLGyvzsuqq_oDr9zhLOhFik,10940
|
|
34
34
|
langchain_agentx/loop/config/__init__.py,sha256=0pf3Mml7LZpQ1C3_AtROFQClrBQegaGCG4YCTfSr9v8,1137
|
|
35
35
|
langchain_agentx/loop/config/agent_config.py,sha256=tOnAHFor6dkle3FvUOVx0YsmiYVTH11UrWSyTmOLV0c,3100
|
|
36
36
|
langchain_agentx/loop/config/compaction_config.py,sha256=MmXqY47-9nPDhdXvnU92Xp0XeaZ605ErQwX9d2emEkM,1176
|
|
@@ -146,7 +146,7 @@ langchain_agentx/loop/subagent/active_run_registry.py,sha256=KurhAMDTBnRkdxNidaM
|
|
|
146
146
|
langchain_agentx/loop/subagent/async_runner.py,sha256=gGx8UMZbsnxUMPzc_7-XkWgd1EiuRcFAoOVUfjhFltE,13740
|
|
147
147
|
langchain_agentx/loop/subagent/boundary_snapshot.py,sha256=9cQuqN29nJDRnHnx9c8b09KRmODE2zv15Z06QyjVJps,10008
|
|
148
148
|
langchain_agentx/loop/subagent/cache_safe_params.py,sha256=-qcgdazEYRy6s4ILQuttpxraE3gsxq17Ro7LYBnuv3E,4460
|
|
149
|
-
langchain_agentx/loop/subagent/context.py,sha256=
|
|
149
|
+
langchain_agentx/loop/subagent/context.py,sha256=tR2QQfNBKci0_rOm6e_p5wNXlo1gZntHpgkFfIuTLHE,24000
|
|
150
150
|
langchain_agentx/loop/subagent/display_projector.py,sha256=AKsGWS1c9reH0kuD5u4oQ24KsBgyt7X10iZM3ere6Fs,3504
|
|
151
151
|
langchain_agentx/loop/subagent/fork_boilerplate.py,sha256=2Uh3cwegL7V-DvtLIxjX7ik5uW5mXfY2NBzP-biREso,5005
|
|
152
152
|
langchain_agentx/loop/subagent/fork_ports.py,sha256=hZATCvlYOxNVZtR3PoOVbI1GkA0KhHEyux6lUIjG5_4,1323
|
|
@@ -157,13 +157,13 @@ langchain_agentx/loop/subagent/hook_attachment_messages.py,sha256=em3VXaUfURou_Y
|
|
|
157
157
|
langchain_agentx/loop/subagent/hooks_compose.py,sha256=enPKRsAbqjB5HqACE-YFzM8K3Job7azi4wpqI1kmgl8,2515
|
|
158
158
|
langchain_agentx/loop/subagent/hooks_pipeline.py,sha256=4OweLS52TRbcaUO56YpE-qX71ZPCjPRS0j9KzIcx5ls,6399
|
|
159
159
|
langchain_agentx/loop/subagent/mcp_child_servers.py,sha256=UJ9FtIZroYvP6KfNLUnHKbb7bqg2qSmo15MkzHcVKME,4395
|
|
160
|
-
langchain_agentx/loop/subagent/orchestrator.py,sha256=
|
|
160
|
+
langchain_agentx/loop/subagent/orchestrator.py,sha256=35gaY6gzdlCxEogyYiTRos5KtSpsSOADJBZIHazsQbc,41785
|
|
161
161
|
langchain_agentx/loop/subagent/parent_hook_snapshot.py,sha256=cwBC2eXvx1evc6QPpI42zq-iKThpObyoyWmkDNj9E7Q,2095
|
|
162
|
-
langchain_agentx/loop/subagent/progress.py,sha256=
|
|
163
|
-
langchain_agentx/loop/subagent/progress_dispatcher.py,sha256=
|
|
162
|
+
langchain_agentx/loop/subagent/progress.py,sha256=nef3VVT9QmB2ZWCDAO86cmtVwCJIR8lXMr6jczQKqOY,6650
|
|
163
|
+
langchain_agentx/loop/subagent/progress_dispatcher.py,sha256=KD5EdfoGplIaT8xvt5vgfXgSLPbvn9KqvY85wLpOX7A,4603
|
|
164
164
|
langchain_agentx/loop/subagent/prompt.py,sha256=p7RgzibNfEJdfX8nBnZGEwQLa3_EgOn4E56T9MBRIEc,1722
|
|
165
165
|
langchain_agentx/loop/subagent/reasoning_extractor.py,sha256=KrCQtBdKzsjAEfA7H5COuXzBhuGGonT2Su8IEKYlUpA,1334
|
|
166
|
-
langchain_agentx/loop/subagent/runner.py,sha256=
|
|
166
|
+
langchain_agentx/loop/subagent/runner.py,sha256=GCYWWMZtkJRH8L1sJAwrVwe0yOqO-6kBT-6p5J1w_s0,35142
|
|
167
167
|
langchain_agentx/loop/subagent/subagent_start_hooks.py,sha256=HIyW7_ta9JNxiyrgL8scNpZzcJrAyld4qaRehYx36GY,1359
|
|
168
168
|
langchain_agentx/loop/subagent/subagent_termination_classifier.py,sha256=ULArQeAibgy6ETSf4vRR0L6X1rsDM7-ZuLzRWQ_pmeY,3158
|
|
169
169
|
langchain_agentx/loop/subagent/transcript.py,sha256=-S0VjZcvqlJQGkVGu8VDMTYwTwOePdjDHfjYi3uu7BY,7187
|
|
@@ -264,7 +264,7 @@ langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8
|
|
|
264
264
|
langchain_agentx/observability/events/__init__.py,sha256=mORGJjMvJ6ob5tOqFOyyGhDLjK4gB-Q2HN0l-GPmamk,1624
|
|
265
265
|
langchain_agentx/observability/events/accumulated_text_scope.py,sha256=M4OvuQTgReF8SFrOmr-M5vyOJpzfvxj_DKD5bVpGb70,3169
|
|
266
266
|
langchain_agentx/observability/events/agent_defaults.py,sha256=gmX0P__lux5xbCb8dtAlVCb3wZ7avaqFleoW4Q5anw4,1187
|
|
267
|
-
langchain_agentx/observability/events/agent_event_projector.py,sha256=
|
|
267
|
+
langchain_agentx/observability/events/agent_event_projector.py,sha256=aWh3BsJyTmzcNdRlFXrvnLoCzNoJy9DX7mmFxvNcJYI,65285
|
|
268
268
|
langchain_agentx/observability/events/api_retry_projection.py,sha256=XGb09sNOi6k1ZGW4YqMLBE8GDxNwNMprk62jV9TS9dQ,2433
|
|
269
269
|
langchain_agentx/observability/events/compact_event_gate.py,sha256=50RJemwB1lnQn_NFWOFSgU418ZUR8dbd_v0538PT7zM,4751
|
|
270
270
|
langchain_agentx/observability/events/factory.py,sha256=gqMATIK9tBQzaPWSPhCCSPuCq-WF562ZDTnFUaMm9Qc,1570
|
|
@@ -274,7 +274,8 @@ langchain_agentx/observability/events/langchain_agentx_event_adapter.py,sha256=u
|
|
|
274
274
|
langchain_agentx/observability/events/mcp_event_sink.py,sha256=4gOS2_JfIGwr3bUWCH47H7uaztLypkfQ7Ews71Jbwn0,1989
|
|
275
275
|
langchain_agentx/observability/events/model_end_text_backfill.py,sha256=7RBnxWLi1Oe-Bw9WTYqN-B6IdytFlY-Jso1STtQr6rE,5157
|
|
276
276
|
langchain_agentx/observability/events/oob_event_sink.py,sha256=ijE2AI8gfoMPRPXDwQszNWi_aCKwODywpG884blo08E,5670
|
|
277
|
-
langchain_agentx/observability/events/
|
|
277
|
+
langchain_agentx/observability/events/skill_progress_event_mapper.py,sha256=3-TZcwOT57oSTxRu7GpUGbq_2wI6TimipTbM9dsaZmU,2878
|
|
278
|
+
langchain_agentx/observability/events/subagent_event_mapper.py,sha256=DihmbR3YZsb_xlNmbIbL5LsftEWNlbM5FHLqXcq7R0k,7009
|
|
278
279
|
langchain_agentx/observability/events/subagent_window_text_guard.py,sha256=myo4SSV2TKZjpyrCoe-g5beNGmZnYZefa8nqW76q0Xc,3101
|
|
279
280
|
langchain_agentx/observability/events/tool_call_id_resolver.py,sha256=Mun8jNb91tbV7hvIUkFG9g6NJGHVMW0kHS7Zt6N2gUA,4530
|
|
280
281
|
langchain_agentx/observability/events/tool_outcome_resolver.py,sha256=YqF9Io9-0xdIdz7iYQuJkY-PiUXLC0zR26Eb9n35L9g,4357
|
|
@@ -777,7 +778,7 @@ langchain_agentx/workflow/catalog_registry.py,sha256=tYUny5FkHwQU-RRUxOdxaJhiRV_
|
|
|
777
778
|
langchain_agentx/workflow/dag.py,sha256=Y1IR1IwnM79Xuev6HZnOvAE3Q23MRnA9lu4vg2Cie48,4469
|
|
778
779
|
langchain_agentx/workflow/display_enricher.py,sha256=2HJ4xJI8H4hwBoWHAi6D_LwE-dcbZD_pafnS7DMDYZ4,6147
|
|
779
780
|
langchain_agentx/workflow/display_resolver.py,sha256=hEaljqprjubG82zeqLOEeQ32AMAxaZwvwaLq3v-yt3c,10119
|
|
780
|
-
langchain_agentx/workflow/execution.py,sha256=
|
|
781
|
+
langchain_agentx/workflow/execution.py,sha256=1exCcD1zxZ8vSx4js6ygnJ4xXAnK1UwT8rpIAYAfxxc,19155
|
|
781
782
|
langchain_agentx/workflow/loop_factory.py,sha256=hCShf_gDnesW2A9JspaUY3upp3NCIguII_wRiXnSIUM,3954
|
|
782
783
|
langchain_agentx/workflow/memory_drain_registry.py,sha256=Xm_ytbB-BbRIu3-h-KhptsCsmmueOiNZePUuG1hk8zk,1681
|
|
783
784
|
langchain_agentx/workflow/memory_session_policy.py,sha256=2FI0ZbAA6MnxJexnLYLadQpiqipPQwVnUrTWgl55A74,3333
|
|
@@ -836,8 +837,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
|
|
|
836
837
|
langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
|
|
837
838
|
langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
|
|
838
839
|
langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
|
|
839
|
-
langchain_agentx_python-2.3.
|
|
840
|
-
langchain_agentx_python-2.3.
|
|
841
|
-
langchain_agentx_python-2.3.
|
|
842
|
-
langchain_agentx_python-2.3.
|
|
843
|
-
langchain_agentx_python-2.3.
|
|
840
|
+
langchain_agentx_python-2.3.9.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
841
|
+
langchain_agentx_python-2.3.9.dist-info/METADATA,sha256=pwTUN5gZ5zW2jrl7nwOYxD53dZUKhEEkZu19bdqS7Ew,23971
|
|
842
|
+
langchain_agentx_python-2.3.9.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
843
|
+
langchain_agentx_python-2.3.9.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
|
|
844
|
+
langchain_agentx_python-2.3.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-2.3.7.dist-info → langchain_agentx_python-2.3.9.dist-info}/top_level.txt
RENAMED
|
File without changes
|