langchain-agentx-python 1.3.4__py3-none-any.whl → 1.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langchain_agentx/__init__.py +1 -1
- langchain_agentx/loop/graph/factory.py +2 -0
- langchain_agentx/loop/hook/graph_wiring.py +27 -27
- langchain_agentx/memory/memdir/extractor.py +27 -6
- langchain_agentx/memory/memdir/tunables.py +69 -0
- langchain_agentx/observability/__init__.py +4 -0
- langchain_agentx/observability/events/__init__.py +6 -0
- langchain_agentx/observability/events/agent_event_projector.py +1355 -0
- langchain_agentx/observability/events/factory.py +46 -0
- langchain_agentx/observability/events/langchain_agentx_event_adapter.py +71 -1428
- langchain_agentx/observability/events/workflow_aware_event_adapter.py +86 -0
- langchain_agentx/observability/events/workflow_event_mapper.py +84 -0
- langchain_agentx/workflow/__init__.py +50 -18
- langchain_agentx/workflow/base.py +28 -136
- langchain_agentx/workflow/event_adapter/__init__.py +20 -0
- langchain_agentx/workflow/event_adapter/adapter.py +12 -14
- langchain_agentx/workflow/event_adapter/handlers/parallel_handler.py +19 -14
- langchain_agentx/workflow/event_adapter/handlers/route_handler.py +11 -11
- langchain_agentx/workflow/event_adapter/handlers/stage_handler.py +12 -10
- langchain_agentx/workflow/event_adapter/types.py +86 -13
- langchain_agentx/workspace/path_canonical.py +24 -24
- {langchain_agentx_python-1.3.4.dist-info → langchain_agentx_python-1.3.6.dist-info}/METADATA +1 -1
- {langchain_agentx_python-1.3.4.dist-info → langchain_agentx_python-1.3.6.dist-info}/RECORD +26 -21
- {langchain_agentx_python-1.3.4.dist-info → langchain_agentx_python-1.3.6.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-1.3.4.dist-info → langchain_agentx_python-1.3.6.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-1.3.4.dist-info → langchain_agentx_python-1.3.6.dist-info}/top_level.txt +0 -0
langchain_agentx/__init__.py
CHANGED
|
@@ -876,6 +876,7 @@ class LoopGraphBuilder:
|
|
|
876
876
|
)
|
|
877
877
|
from ...memory.memdir.fork_concurrency import default_fork_concurrency_limiter
|
|
878
878
|
from ...memory.memdir.metrics import default_memory_fork_metrics
|
|
879
|
+
from ...memory.memdir.tunables import get_memory_extraction_turn_threshold
|
|
879
880
|
|
|
880
881
|
metrics = default_memory_fork_metrics()
|
|
881
882
|
return MemoryExtractionCoordinator(
|
|
@@ -889,6 +890,7 @@ class LoopGraphBuilder:
|
|
|
889
890
|
trace_collector=trace_collector,
|
|
890
891
|
session_id=session_id,
|
|
891
892
|
),
|
|
893
|
+
turn_threshold=get_memory_extraction_turn_threshold(self._workspace_cfg),
|
|
892
894
|
)
|
|
893
895
|
|
|
894
896
|
def _build_session_memory_extractor(
|
|
@@ -123,34 +123,27 @@ class HookGraphWiring:
|
|
|
123
123
|
return patch
|
|
124
124
|
|
|
125
125
|
async def loop_end_node_async(self, state: dict[str, Any], runtime: Any) -> dict[str, Any]:
|
|
126
|
-
"""async loop_end
|
|
127
|
-
|
|
128
|
-
base_patch = self.loop_end_node(state, runtime)
|
|
129
|
-
if not memory_patch:
|
|
130
|
-
return base_patch
|
|
131
|
-
if not base_patch:
|
|
132
|
-
return memory_patch
|
|
133
|
-
merged = dict(base_patch)
|
|
134
|
-
if "_hook_ui_events" in memory_patch:
|
|
135
|
-
merged.setdefault("_hook_ui_events", []).extend(
|
|
136
|
-
memory_patch.get("_hook_ui_events") or []
|
|
137
|
-
)
|
|
138
|
-
return merged
|
|
126
|
+
"""async loop_end:等价 CC ``stopHooks.ts:149`` 的 ``void executeExtractMemories(...)``
|
|
127
|
+
fork-and-forget 语义 —— 主回合不等抽取任务。
|
|
139
128
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
129
|
+
抽取任务已由 ``stop_hooks_node`` 通过
|
|
130
|
+
``Coordinator.trigger_from_state() → loop.create_task(...)`` 调度到事件循环;
|
|
131
|
+
task ownership 跟随顶层 owner,而非单个 turn。drain 由更高层负责:
|
|
132
|
+
|
|
133
|
+
* ``AgentSession.__aexit__`` / ``ConversationSession.__aexit__`` 60 s drain
|
|
134
|
+
* ``Workflow.run() finally`` ``_drain_pending_memory_extractions()`` 60 s drain
|
|
135
|
+
* ``BackgroundAgent`` 收尾路径独立 owner
|
|
136
|
+
|
|
137
|
+
以下场景不在本节点处理:
|
|
138
|
+
|
|
139
|
+
* **本回合内已就绪的 ``memory_saved`` UI 事件**:由 ``loop_end_node`` 内部
|
|
140
|
+
调用 ``drain_memory_saved_ui_events()`` 回收;async 路径完全对称走 sync 实现。
|
|
141
|
+
* **未就绪的抽取任务**:留给下一回合 trailing 回收 或 owner 边界 drain。
|
|
142
|
+
|
|
143
|
+
历史:曾经在此 ``await drain(5_000)``,等价 ``fork()`` 后立即 ``waitpid()``,
|
|
144
|
+
把 fork-and-forget 设计破坏;慢模型下必然超时 abort 导致记忆丢失。已删除。
|
|
145
|
+
"""
|
|
146
|
+
return self.loop_end_node(state, runtime)
|
|
154
147
|
|
|
155
148
|
def loop_end_node(self, state: dict[str, Any], runtime: Any) -> dict[str, Any]:
|
|
156
149
|
if self.memory_prefetch_coordinator is not None:
|
|
@@ -161,6 +154,13 @@ class HookGraphWiring:
|
|
|
161
154
|
patch.update(self._drain_hook_ui_events())
|
|
162
155
|
if result.state_patch:
|
|
163
156
|
patch.update(dict(result.state_patch))
|
|
157
|
+
# 回收已就绪的 memory_saved UI 事件(抽取完成,下一 turn trailing)
|
|
158
|
+
if self.memory_extractor is not None:
|
|
159
|
+
drain_saved = getattr(self.memory_extractor, "drain_memory_saved_ui_events", None)
|
|
160
|
+
if callable(drain_saved):
|
|
161
|
+
saved_ui = drain_saved()
|
|
162
|
+
if saved_ui:
|
|
163
|
+
patch.setdefault("_hook_ui_events", []).extend(saved_ui)
|
|
164
164
|
if self.trace_lifecycle_collector is not None:
|
|
165
165
|
patch.update(self.trace_lifecycle_collector.loop_end_node(state))
|
|
166
166
|
return patch
|
|
@@ -41,7 +41,6 @@ logger = logging.getLogger(__name__)
|
|
|
41
41
|
|
|
42
42
|
_WRITE_TOOLS = {"Write", "Edit"}
|
|
43
43
|
DEFAULT_EXTRACTION_WINDOW_MESSAGES = 30
|
|
44
|
-
STOP_HOOK_DRAIN_TIMEOUT_MS = 5_000
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def _as_int(value: Any) -> int:
|
|
@@ -95,12 +94,14 @@ class MemoryExtractionCoordinator:
|
|
|
95
94
|
cursor_store: MemoryExtractionCursorStore | None = None,
|
|
96
95
|
metrics: MemoryForkMetrics | None = None,
|
|
97
96
|
on_memory_saved: MemorySavedCallback | None = None,
|
|
97
|
+
turn_threshold: int = 3, # ← 闸门 7: 默认每 3 轮 trigger 才执行一次 fork 抽取
|
|
98
98
|
) -> None:
|
|
99
99
|
self._executor = extraction_executor
|
|
100
100
|
self._forked_runner = forked_runner
|
|
101
101
|
self._cursor_store = cursor_store or MemoryExtractionCursorStore()
|
|
102
102
|
self._metrics = metrics or default_memory_fork_metrics()
|
|
103
103
|
self._on_memory_saved = on_memory_saved
|
|
104
|
+
self._turn_threshold = max(1, int(turn_threshold)) # 钳位 ≥1
|
|
104
105
|
self._memory_saved_ui_pending: list[dict[str, Any]] = []
|
|
105
106
|
self._in_progress = False
|
|
106
107
|
self._pending_context: MemoryExtractionContext | None = None
|
|
@@ -118,7 +119,7 @@ class MemoryExtractionCoordinator:
|
|
|
118
119
|
container_type: str,
|
|
119
120
|
force: bool = True,
|
|
120
121
|
) -> None:
|
|
121
|
-
"""stop hook:L2 唯一触发入口;force=True
|
|
122
|
+
"""stop hook:L2 唯一触发入口;force=True 无 token 门槛,turn 节流仍生效。"""
|
|
122
123
|
if not self._can_extract_from_state(state, container_type, workspace_cfg):
|
|
123
124
|
return
|
|
124
125
|
context = self._build_context(
|
|
@@ -127,7 +128,7 @@ class MemoryExtractionCoordinator:
|
|
|
127
128
|
session_id=session_id,
|
|
128
129
|
container_type=container_type,
|
|
129
130
|
force=force,
|
|
130
|
-
skip_throttle=True
|
|
131
|
+
skip_throttle=False, # 闸门 7 生效;pending 重放的 trailing 路径仍设 True
|
|
131
132
|
)
|
|
132
133
|
self.trigger(context)
|
|
133
134
|
|
|
@@ -166,10 +167,19 @@ class MemoryExtractionCoordinator:
|
|
|
166
167
|
self._tasks.add(task)
|
|
167
168
|
task.add_done_callback(self._tasks.discard)
|
|
168
169
|
|
|
169
|
-
def drain_on_stop(self, timeout_ms: int =
|
|
170
|
+
def drain_on_stop(self, timeout_ms: int = 60_000) -> None:
|
|
170
171
|
"""跨线程阻塞等待 in-flight 抽取(勿在 owner 事件循环线程调用,会死锁)。
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
语义:仅供 **进程退出 hook / sync owner 关闭** 等"无 event loop 可用"的
|
|
174
|
+
外部 caller 使用。等价 CC ``print.ts:968`` 进程退出前的一次性 drain。
|
|
175
|
+
|
|
176
|
+
Loop graph 路径**不再**调用本方法(Phase 2 已删除 turn-级 drain):
|
|
177
|
+
async owner(AgentSession / ConversationSession.__aexit__、
|
|
178
|
+
Workflow.run() finally、BackgroundAgent 收尾)应直接
|
|
179
|
+
``await drain_pending_extraction(60_000)``。
|
|
180
|
+
|
|
181
|
+
默认 ``timeout_ms`` 与 owner-级 60 s 对齐,对齐 CC
|
|
182
|
+
``extractMemories.ts:579`` 的 ``drainer`` 默认值。
|
|
173
183
|
"""
|
|
174
184
|
if not self._tasks:
|
|
175
185
|
return
|
|
@@ -279,6 +289,18 @@ class MemoryExtractionCoordinator:
|
|
|
279
289
|
self._advance_cursor(context, new_messages, cursor)
|
|
280
290
|
return
|
|
281
291
|
|
|
292
|
+
# —— 闸门 7: turn 节流(对齐 CC tengu_bramble_lintel) ——
|
|
293
|
+
if not context.skip_throttle:
|
|
294
|
+
cursor.turns_since_last_extraction += 1
|
|
295
|
+
if cursor.turns_since_last_extraction < self._turn_threshold:
|
|
296
|
+
self._cursor_store.save(
|
|
297
|
+
workspace_cfg=context.workspace_cfg,
|
|
298
|
+
session_id=context.session_id,
|
|
299
|
+
container_type=context.container_type,
|
|
300
|
+
state=cursor,
|
|
301
|
+
)
|
|
302
|
+
return
|
|
303
|
+
|
|
282
304
|
succeeded = True
|
|
283
305
|
if self._executor is not None:
|
|
284
306
|
await self._executor(context, new_messages)
|
|
@@ -603,7 +625,6 @@ def build_trace_memory_saved_callback(
|
|
|
603
625
|
__all__ = [
|
|
604
626
|
"MemoryExtractionContext",
|
|
605
627
|
"MemoryExtractionCoordinator",
|
|
606
|
-
"STOP_HOOK_DRAIN_TIMEOUT_MS",
|
|
607
628
|
"build_extract_prompt",
|
|
608
629
|
"build_trace_memory_saved_callback",
|
|
609
630
|
"create_auto_mem_can_use_tool",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tunables.py — L2 记忆抽取可调参数。
|
|
3
|
+
|
|
4
|
+
职责:
|
|
5
|
+
提供 get_memory_extraction_turn_threshold(),封装 env / settings.json / 默认值
|
|
6
|
+
三层读取,作为闸门 7(turn 节流)的配置入口。
|
|
7
|
+
|
|
8
|
+
链路位置:
|
|
9
|
+
factory.py _build_memory_extraction_coordinator
|
|
10
|
+
→ get_memory_extraction_turn_threshold(workspace_cfg)
|
|
11
|
+
→ MemoryExtractionCoordinator(turn_threshold=...)。
|
|
12
|
+
|
|
13
|
+
当前裁剪范围:
|
|
14
|
+
仅 turn 节流阈值;其它 L2 可调参数(fork concurrency / drain timeout 等)保持现状。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from langchain_agentx.workspace import ResolvedAgentStateConfig
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_ENV_TURN_THRESHOLD = "LANGCHAIN_AGENTX_MEMORY_TURN_THRESHOLD"
|
|
29
|
+
_SETTINGS_KEY = "memoryExtractionTurnThreshold"
|
|
30
|
+
_DEFAULT_TURN_THRESHOLD = 3
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_memory_extraction_turn_threshold(
|
|
34
|
+
workspace_cfg: ResolvedAgentStateConfig,
|
|
35
|
+
) -> int:
|
|
36
|
+
"""L2 闸门 7 阈值;env > settings.json > 默认 3,钳位 ≥1。"""
|
|
37
|
+
raw = os.environ.get(_ENV_TURN_THRESHOLD)
|
|
38
|
+
if raw is not None:
|
|
39
|
+
try:
|
|
40
|
+
return max(1, int(raw))
|
|
41
|
+
except ValueError:
|
|
42
|
+
logger.warning(
|
|
43
|
+
"ignore non-integer %s=%r, falling back to settings/default",
|
|
44
|
+
_ENV_TURN_THRESHOLD,
|
|
45
|
+
raw,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
settings = _read_workspace_settings(workspace_cfg)
|
|
49
|
+
value = settings.get(_SETTINGS_KEY)
|
|
50
|
+
if isinstance(value, int) and not isinstance(value, bool) and value >= 1:
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
return _DEFAULT_TURN_THRESHOLD
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _read_workspace_settings(workspace_cfg: ResolvedAgentStateConfig) -> dict[str, Any]:
|
|
57
|
+
"""读取 workspace_config.json;缺失或损坏时返回空 dict。"""
|
|
58
|
+
path = workspace_cfg.workspace_config_path
|
|
59
|
+
if not path.exists():
|
|
60
|
+
return {}
|
|
61
|
+
try:
|
|
62
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
63
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
64
|
+
logger.warning("ignore corrupt workspace settings %s: %s", path, exc)
|
|
65
|
+
return {}
|
|
66
|
+
return data if isinstance(data, dict) else {}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = ["get_memory_extraction_turn_threshold"]
|
|
@@ -6,12 +6,14 @@ from .events import (
|
|
|
6
6
|
FINISH_OUTCOME_KEYS,
|
|
7
7
|
FinishOutcome,
|
|
8
8
|
FinishOutcomeParser,
|
|
9
|
+
ILangGraphEventAdapter,
|
|
9
10
|
LangchainAgentEvent,
|
|
10
11
|
LangchainAgentEventType,
|
|
11
12
|
LangGraphToLangchainAgentEventAdapter,
|
|
12
13
|
RecoveryAction,
|
|
13
14
|
RecoveryActionResolver,
|
|
14
15
|
RecoveryPolicyMode,
|
|
16
|
+
create_langgraph_event_adapter,
|
|
15
17
|
enrich_run_result,
|
|
16
18
|
)
|
|
17
19
|
from .replay.service import (
|
|
@@ -54,6 +56,7 @@ __all__ = [
|
|
|
54
56
|
"DECISION_LAYERS",
|
|
55
57
|
"DebugBurstController",
|
|
56
58
|
"DebugBurstSession",
|
|
59
|
+
"ILangGraphEventAdapter",
|
|
57
60
|
"LogContext",
|
|
58
61
|
"LoggingConfigBuilder",
|
|
59
62
|
"OBS_SCHEMA_VERSION",
|
|
@@ -63,6 +66,7 @@ __all__ = [
|
|
|
63
66
|
"LangchainAgentEvent",
|
|
64
67
|
"LangchainAgentEventType",
|
|
65
68
|
"LangGraphToLangchainAgentEventAdapter",
|
|
69
|
+
"create_langgraph_event_adapter",
|
|
66
70
|
"RecoveryAction",
|
|
67
71
|
"RecoveryActionResolver",
|
|
68
72
|
"RecoveryPolicyMode",
|
|
@@ -16,6 +16,10 @@ from .finish_outcome import (
|
|
|
16
16
|
RecoveryPolicyMode,
|
|
17
17
|
enrich_run_result,
|
|
18
18
|
)
|
|
19
|
+
from .factory import (
|
|
20
|
+
ILangGraphEventAdapter,
|
|
21
|
+
create_langgraph_event_adapter,
|
|
22
|
+
)
|
|
19
23
|
|
|
20
24
|
__all__ = [
|
|
21
25
|
"LangchainAgentEventType",
|
|
@@ -30,4 +34,6 @@ __all__ = [
|
|
|
30
34
|
"RecoveryActionResolver",
|
|
31
35
|
"RecoveryPolicyMode",
|
|
32
36
|
"enrich_run_result",
|
|
37
|
+
"ILangGraphEventAdapter",
|
|
38
|
+
"create_langgraph_event_adapter",
|
|
33
39
|
]
|