langchain-agentx-python 2.3.3__py3-none-any.whl → 2.3.4__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.
@@ -11,7 +11,7 @@ from langchain_agentx import create_loop_agent
11
11
  ```
12
12
  """
13
13
 
14
- __version__ = "2.3.3"
14
+ __version__ = "2.3.4"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -0,0 +1,38 @@
1
+ """Compaction 控制面 env 解析(对齐 CC DISABLE_COMPACT / DISABLE_AUTO_COMPACT)。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+
8
+ def _parse_bool_env(name: str) -> bool | None:
9
+ raw = os.environ.get(name, "").strip().lower()
10
+ if not raw:
11
+ return None
12
+ return raw in ("1", "true", "yes", "on")
13
+
14
+
15
+ def resolve_is_auto_compact_enabled(*, config_is_auto_compact: bool = True) -> bool:
16
+ """CC:DISABLE_COMPACT 关全部;DISABLE_AUTO_COMPACT 仅关 proactive autocompact。"""
17
+ if _parse_bool_env("DISABLE_COMPACT") is True:
18
+ return False
19
+ if _parse_bool_env("DISABLE_AUTO_COMPACT") is True:
20
+ return False
21
+ return config_is_auto_compact
22
+
23
+
24
+ def compute_autocompact_trigger_token_threshold(
25
+ *,
26
+ context_window_tokens: int,
27
+ max_output_tokens_reserved: int,
28
+ compact_buffer_tokens: int,
29
+ ) -> int:
30
+ """对齐 CC:effectiveWindow - AUTOCOMPACT_BUFFER_TOKENS。"""
31
+ effective = max(0, int(context_window_tokens) - int(max_output_tokens_reserved))
32
+ return max(0, effective - int(compact_buffer_tokens))
33
+
34
+
35
+ __all__ = [
36
+ "compute_autocompact_trigger_token_threshold",
37
+ "resolve_is_auto_compact_enabled",
38
+ ]
@@ -253,6 +253,7 @@ class AgentLoopConfig:
253
253
  token_budget_max_continuations: int = 3
254
254
  compact_buffer_tokens: int = 13_000
255
255
  max_output_tokens_reserved: int = 20_000
256
+ is_auto_compact: bool = True
256
257
 
257
258
  # -----------------------------------------------------------------------
258
259
  # 运行模式
@@ -33,17 +33,33 @@ from .stages.base import CompactionStage
33
33
  from .types import CompactProgress, ContextCompactionContext
34
34
 
35
35
 
36
+ def _apply_autocompact_failure_patch(
37
+ state: dict[str, Any],
38
+ stages_meta: list[dict[str, Any]],
39
+ patch: dict[str, Any],
40
+ ) -> None:
41
+ ac = next((m for m in stages_meta if m.get("name") == "autocompact"), None)
42
+ if ac is None:
43
+ return
44
+ extra = ac.get("extra") or {}
45
+ prev = int(state.get("consecutive_autocompact_failures") or 0)
46
+ if extra.get("autocompact_llm_failed"):
47
+ patch["consecutive_autocompact_failures"] = prev + 1
48
+ elif extra.get("autocompact_mode") == "llm":
49
+ patch["consecutive_autocompact_failures"] = 0
50
+
51
+
36
52
  class DefaultCompactionStages:
37
53
  """CC 五段默认装配(顺序即契约)。"""
38
54
 
39
55
  @staticmethod
40
- def build() -> tuple[CompactionStage, ...]:
56
+ def build(settings: CompactionSettings | None = None) -> tuple[CompactionStage, ...]:
41
57
  return (
42
- ToolResultBudgetStage(),
43
- SnipCompactStage(),
44
- MicrocompactStage(),
45
- ContextCollapseStage(),
46
- AutocompactStage(),
58
+ ToolResultBudgetStage(settings),
59
+ SnipCompactStage(settings),
60
+ MicrocompactStage(settings),
61
+ ContextCollapseStage(settings),
62
+ AutocompactStage(settings),
47
63
  )
48
64
 
49
65
 
@@ -65,7 +81,7 @@ class ContextPipeline:
65
81
  settings: CompactionSettings | None = None,
66
82
  ) -> ContextPipeline:
67
83
  """使用 `DefaultCompactionStages` 构建管道。"""
68
- return cls(DefaultCompactionStages.build(), settings=settings)
84
+ return cls(DefaultCompactionStages.build(settings), settings=settings)
69
85
 
70
86
  @property
71
87
  def stages(self) -> tuple[CompactionStage, ...]:
@@ -117,6 +133,7 @@ class ContextPipeline:
117
133
  "mode": ctx.mode,
118
134
  }
119
135
 
136
+ _apply_autocompact_failure_patch(state, stages_meta, patch)
120
137
  if not patch:
121
138
  return {}
122
139
  return patch
@@ -198,6 +215,7 @@ class ContextPipeline:
198
215
  "mode": ctx.mode,
199
216
  }
200
217
 
218
+ _apply_autocompact_failure_patch(state, stages_meta, patch)
201
219
  if not patch:
202
220
  return {}
203
221
  return patch
@@ -32,6 +32,8 @@ class CompactionSettings:
32
32
  prune_protected_tools: FrozenSet[str] = field(default_factory=lambda: frozenset({"Skill"}))
33
33
  default_max_context_tokens: int = 120_000
34
34
  default_compress_trigger_fraction: float = 0.8
35
+ # 非 None 时 AutocompactStage 用绝对触发线(由 AgentLoopConfig compact_buffer 派生)。
36
+ autocompact_trigger_token_threshold: int | None = None
35
37
  default_compress_keep_recent_messages: int = 8
36
38
  # --- autocompact LLM 提示词(对齐 `compact.ts` + `prompt.ts`)---
37
39
  # 摘要插在保留 tail 之前 → 默认 `partial_up_to`;全量折叠无尾窗时用 `full`;与 CC partial pivot「from」一致时用 `partial_from`。
@@ -62,6 +64,8 @@ class CompactionSettings:
62
64
  default_factory=QuerySourcePolicy.autocompact_skip_sources
63
65
  )
64
66
  autocompact_use_llm_when_available: bool = True
67
+ is_auto_compact_enabled: bool = True
68
+ max_consecutive_autocompact_failures: int = 3
65
69
  autocompact_llm_excerpt_chars_per_message: int = 20_000
66
70
  # --- snip carry → autocompact 阈值放宽(对齐 CC snipTokensFreed 计入阈值)---
67
71
  # True:将本流水线已累计的 `snip_tokens_freed_carry` 加到触发阈值上,避免 snip 已释放后仍立刻 autocompact。
@@ -89,6 +93,12 @@ class CompactionSettings:
89
93
  active_compaction_settings: CompactionSettings = CompactionSettings()
90
94
 
91
95
 
96
+ def autocompact_trigger_threshold_tokens(settings: CompactionSettings) -> int:
97
+ if settings.autocompact_trigger_token_threshold is not None:
98
+ return settings.autocompact_trigger_token_threshold
99
+ return int(settings.default_max_context_tokens * settings.default_compress_trigger_fraction)
100
+
101
+
92
102
  def get_active_compaction_settings() -> CompactionSettings:
93
103
  """返回当前生效的策略(默认即模块级 `active_compaction_settings`)。"""
94
104
  return active_compaction_settings
@@ -104,6 +114,7 @@ __all__ = [
104
114
  "AutocompactLlmPromptKind",
105
115
  "CompactionSettings",
106
116
  "active_compaction_settings",
117
+ "autocompact_trigger_threshold_tokens",
107
118
  "get_active_compaction_settings",
108
119
  "set_active_compaction_settings",
109
120
  ]
@@ -25,7 +25,11 @@ from langchain_core.messages import HumanMessage, SystemMessage
25
25
  from ..blocking_guard import CompactionBlockingGuard
26
26
  from ..message_utils import stringify_content, total_estimated_tokens_for_messages
27
27
  from ...prompt.compact import build_autocompact_llm_instruction_prompt, get_compact_user_summary_message
28
- from ..settings import CompactionSettings, get_active_compaction_settings
28
+ from ..settings import (
29
+ CompactionSettings,
30
+ autocompact_trigger_threshold_tokens,
31
+ get_active_compaction_settings,
32
+ )
29
33
  from ..types import CompactProgress, ContextCompactionContext, StageResult
30
34
  from .base import CompactionStage
31
35
 
@@ -45,10 +49,25 @@ class AutocompactStage(CompactionStage):
45
49
 
46
50
  def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
47
51
  s = self._effective_settings()
52
+ if not s.is_auto_compact_enabled:
53
+ return StageResult(
54
+ messages=messages,
55
+ tokens_freed=0,
56
+ extra={"autocompact_skipped": "auto_compact_disabled"},
57
+ )
58
+ if ctx.consecutive_autocompact_failures >= s.max_consecutive_autocompact_failures:
59
+ return StageResult(
60
+ messages=messages,
61
+ tokens_freed=0,
62
+ extra={
63
+ "autocompact_skipped": "consecutive_failures",
64
+ "consecutive_autocompact_failures": ctx.consecutive_autocompact_failures,
65
+ },
66
+ )
48
67
  if ctx.query_source in s.autocompact_skip_query_sources:
49
68
  return StageResult(messages=messages, tokens_freed=0, extra={"autocompact_skipped": "query_source"})
50
69
  total = total_estimated_tokens_for_messages(messages, s.num_chars_per_token)
51
- threshold = int(s.default_max_context_tokens * s.default_compress_trigger_fraction)
70
+ threshold = autocompact_trigger_threshold_tokens(s)
52
71
  slack = ctx.snip_tokens_freed_carry if s.autocompact_snip_carry_threshold_slack else 0
53
72
  if total <= threshold + slack:
54
73
  return StageResult(messages=messages, tokens_freed=0)
@@ -66,11 +85,12 @@ class AutocompactStage(CompactionStage):
66
85
  tail = rest[-k:]
67
86
 
68
87
  block_llm = CompactionBlockingGuard(settings=s).should_block_compact_llm(messages)
69
- if (
88
+ attempted_llm = (
70
89
  s.autocompact_use_llm_when_available
71
90
  and ctx.model is not None
72
91
  and not block_llm
73
- ):
92
+ )
93
+ if attempted_llm:
74
94
  summary = _invoke_summary(ctx.model, s, head)
75
95
  if summary:
76
96
  # 修复后:不包含 leading,state 不含 SystemMessage
@@ -104,20 +124,38 @@ class AutocompactStage(CompactionStage):
104
124
  ] + tail
105
125
  new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
106
126
  mode = "prune_blocked_llm" if block_llm else "prune"
127
+ extra: dict[str, Any] = {"autocompact_mode": mode, "folded_messages": len(head)}
128
+ if attempted_llm:
129
+ extra["autocompact_llm_failed"] = True
107
130
  return StageResult(
108
131
  messages=folded,
109
132
  tokens_freed=max(0, total - new_total),
110
- extra={"autocompact_mode": mode, "folded_messages": len(head)},
133
+ extra=extra,
111
134
  )
112
135
 
113
136
  async def arun(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
114
137
  """Async 版本:LLM 摘要使用 astream 流式生成,支持进度回调。"""
115
138
  s = self._effective_settings()
139
+ if not s.is_auto_compact_enabled:
140
+ return StageResult(
141
+ messages=messages,
142
+ tokens_freed=0,
143
+ extra={"autocompact_skipped": "auto_compact_disabled"},
144
+ )
145
+ if ctx.consecutive_autocompact_failures >= s.max_consecutive_autocompact_failures:
146
+ return StageResult(
147
+ messages=messages,
148
+ tokens_freed=0,
149
+ extra={
150
+ "autocompact_skipped": "consecutive_failures",
151
+ "consecutive_autocompact_failures": ctx.consecutive_autocompact_failures,
152
+ },
153
+ )
116
154
  if ctx.query_source in s.autocompact_skip_query_sources:
117
155
  return StageResult(messages=messages, tokens_freed=0, extra={"autocompact_skipped": "query_source"})
118
156
 
119
157
  total = total_estimated_tokens_for_messages(messages, s.num_chars_per_token)
120
- threshold = int(s.default_max_context_tokens * s.default_compress_trigger_fraction)
158
+ threshold = autocompact_trigger_threshold_tokens(s)
121
159
  slack = ctx.snip_tokens_freed_carry if s.autocompact_snip_carry_threshold_slack else 0
122
160
  if total <= threshold + slack:
123
161
  return StageResult(messages=messages, tokens_freed=0)
@@ -135,11 +173,12 @@ class AutocompactStage(CompactionStage):
135
173
  tail = rest[-k:]
136
174
 
137
175
  block_llm = CompactionBlockingGuard(settings=s).should_block_compact_llm(messages)
138
- if (
176
+ attempted_llm = (
139
177
  s.autocompact_use_llm_when_available
140
178
  and ctx.model is not None
141
179
  and not block_llm
142
- ):
180
+ )
181
+ if attempted_llm:
143
182
  summary = await _astream_summary(ctx.model, s, head, ctx.progress_callback)
144
183
  if summary:
145
184
  folded = [
@@ -172,10 +211,13 @@ class AutocompactStage(CompactionStage):
172
211
  ] + tail
173
212
  new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
174
213
  mode = "prune_blocked_llm" if block_llm else "prune"
214
+ extra: dict[str, Any] = {"autocompact_mode": mode, "folded_messages": len(head)}
215
+ if attempted_llm:
216
+ extra["autocompact_llm_failed"] = True
175
217
  return StageResult(
176
218
  messages=folded,
177
219
  tokens_freed=max(0, total - new_total),
178
- extra={"autocompact_mode": mode, "folded_messages": len(head)},
220
+ extra=extra,
179
221
  )
180
222
 
181
223
 
@@ -38,6 +38,7 @@ class ContextCompactionContext:
38
38
  model: Any | None = None
39
39
  snip_tokens_freed_carry: int = 0
40
40
  query_source: str | None = None
41
+ consecutive_autocompact_failures: int = 0
41
42
  config: CompactionConfig = field(default_factory=CompactionConfig)
42
43
  progress_callback: CompactProgressCallback = None
43
44
  mode: str = "proactive"
@@ -47,11 +48,14 @@ class ContextCompactionContext:
47
48
  def from_state(
48
49
  cls, state: Mapping[str, Any], model: Any | None, mode: str = "proactive"
49
50
  ) -> ContextCompactionContext:
51
+ raw_failures = state.get("consecutive_autocompact_failures")
52
+ failures = int(raw_failures) if isinstance(raw_failures, int) else 0
50
53
  return cls(
51
54
  model=model,
52
55
  query_source=state.get("query_source")
53
56
  if isinstance(state.get("query_source"), str)
54
57
  else None,
58
+ consecutive_autocompact_failures=max(0, failures),
55
59
  mode=mode,
56
60
  )
57
61
 
@@ -204,6 +204,7 @@ def create_initial_state(
204
204
  # 恢复路径守卫状态
205
205
  "collapse_retry_available": False,
206
206
  "has_attempted_reactive_compact": False,
207
+ "consecutive_autocompact_failures": 0,
207
208
  "compaction_pipeline_meta": None,
208
209
  "max_output_tokens_override": None,
209
210
  "max_output_tokens_recovery_count": 0,
@@ -248,6 +249,7 @@ class AgentLoopState(TypedDict):
248
249
  withhold_retry_count: int # withhold 已重试次数
249
250
  collapse_retry_available: bool # collapse_drain_retry 相关守卫
250
251
  has_attempted_reactive_compact: bool # reactive_compact 重试守卫
252
+ consecutive_autocompact_failures: int # proactive autocompact LLM 连续失败(CC 熔断)
251
253
  compaction_pipeline_meta: dict[str, Any] | None # prune_and_compress 观测
252
254
  max_output_tokens_override: int | None # max_output_tokens 升档参数
253
255
  max_output_tokens_recovery_count: int # max_output_tokens 恢复计数
@@ -10,20 +10,15 @@ LangChain AgentX Agent Factory
10
10
 
11
11
  参考设计文档:.claude/design/create-opencode-style-agent-design.md
12
12
 
13
- LangChain AgentX Agent 状态图(文字版,含 loop_controller):
13
+ LangChain AgentX Agent 状态图(文字版,含 loop_controller · proactive compact gate):
14
14
 
15
- START
15
+ START → hook_loop_start → prune_and_compress → hook_before_model → model
16
16
  ↓
17
- hook_loop_start
18
- ↓
19
- model 节点(同步/异步,带 OpenCode 退出判断)
20
- ↓
21
- hook_stop
22
- ↓
23
- loop_controller(集中处理 resolution / terminal / 工具调用)
24
- ├─ resolution.kind=terminal → END(或 task reconcile / recovery 分支)
25
- ├─ 有工具调用 → tools → (task_event_*/prune_and_compress) → model
26
- └─ 无工具调用 → END
17
+ hook_stop → loop_controller
18
+ ├─ terminal → hook_loop_end → END
19
+ ├─ tools → (task_event_* →) prune_and_compress → hook_before_model → model
20
+ ├─ 再进 model(无 tools)→ prune_and_compress → hook_before_model → model
21
+ └─ reactive_compact → prune_and_compress(或 task 链)→ … → model
27
22
  """
28
23
 
29
24
  from __future__ import annotations
@@ -39,6 +34,16 @@ from langchain_core.messages import HumanMessage, SystemMessage
39
34
  from langchain_core.runnables import RunnableConfig
40
35
  from langgraph._internal._runnable import RunnableCallable
41
36
  from langgraph.constants import END, START
37
+
38
+ _PRE_MODEL_COMPACT_NODE = "prune_and_compress"
39
+ _BEFORE_MODEL_NODE = "hook_before_model"
40
+
41
+
42
+ def _pre_model_gate_destination(loop_entry_node: str) -> str:
43
+ """CC 对齐:直连 hook_before_model 的 loop 续跑须先过 proactive compact gate。"""
44
+ if loop_entry_node == _BEFORE_MODEL_NODE:
45
+ return _PRE_MODEL_COMPACT_NODE
46
+ return loop_entry_node
42
47
  from langgraph.graph.state import StateGraph
43
48
  import asyncio
44
49
  import dataclasses
@@ -66,8 +71,9 @@ from ..model.schema_and_format import (
66
71
  )
67
72
  from ..model.model_node import ModelNode
68
73
  from ..model.model_nodes import make_model_nodes
69
- from ..context import default_loop_context_compaction
74
+ from ..context import LoopContextCompaction, default_loop_context_compaction
70
75
  from ..context.settings import CompactionSettings
76
+ from ..context.types import CompactProgress
71
77
  from ..injection.dedup import filter_attachment_dicts_by_task_command_id
72
78
  from ..message_contract import to_record
73
79
  from ..model.tool_call_degradation_corrector import (
@@ -102,6 +108,10 @@ from ...memory.memdir.paths import is_memory_enabled
102
108
  from ..prompt import SystemPromptSection, build_effective_system_prompt
103
109
  from ..prompt.session_context import get_default_session_context_sections
104
110
  from ..config import ModelContextResolver
111
+ from ..config.compaction_config import (
112
+ compute_autocompact_trigger_token_threshold,
113
+ resolve_is_auto_compact_enabled,
114
+ )
105
115
  from ..config.interaction_profile import (
106
116
  assemble_services,
107
117
  normalize_config,
@@ -556,7 +566,7 @@ class LoopGraphBuilder:
556
566
  # 运行时需要的额外值存储在 self._* 字段中
557
567
 
558
568
  self._compaction_settings = self._build_compaction_settings()
559
- self._loop_context_compaction = default_loop_context_compaction().with_default_pipeline(
569
+ self._loop_context_compaction = LoopContextCompaction.with_default_pipeline(
560
570
  settings=self._compaction_settings
561
571
  )
562
572
  self._model = model
@@ -1354,15 +1364,29 @@ class LoopGraphBuilder:
1354
1364
 
1355
1365
  def _build_compaction_settings(self) -> CompactionSettings:
1356
1366
  base = CompactionSettings()
1367
+ context_window = self._model_context_resolver.resolve_context_window(self._config)
1368
+ trigger_threshold = compute_autocompact_trigger_token_threshold(
1369
+ context_window_tokens=context_window,
1370
+ max_output_tokens_reserved=self._config.max_output_tokens_reserved,
1371
+ compact_buffer_tokens=self._config.compact_buffer_tokens,
1372
+ )
1373
+ auto_compact_enabled = resolve_is_auto_compact_enabled(
1374
+ config_is_auto_compact=self._config.is_auto_compact
1375
+ )
1376
+ # G1-A (CC 0331): auto + reactive 路径下不在 call 前 synthetic blocking_limit。
1377
+ enable_main_token_block = base.enable_main_model_context_token_block
1378
+ if auto_compact_enabled:
1379
+ enable_main_token_block = False
1357
1380
  return CompactionSettings(
1358
1381
  num_chars_per_token=base.num_chars_per_token,
1359
1382
  prune_protect_tokens=base.prune_protect_tokens,
1360
1383
  prune_minimum_tokens=base.prune_minimum_tokens,
1361
1384
  prune_protected_tools=base.prune_protected_tools,
1362
- default_max_context_tokens=self._model_context_resolver.resolve_context_window(
1363
- self._config
1364
- ),
1385
+ default_max_context_tokens=context_window,
1365
1386
  default_compress_trigger_fraction=base.default_compress_trigger_fraction,
1387
+ autocompact_trigger_token_threshold=trigger_threshold,
1388
+ is_auto_compact_enabled=auto_compact_enabled,
1389
+ max_consecutive_autocompact_failures=base.max_consecutive_autocompact_failures,
1366
1390
  default_compress_keep_recent_messages=base.default_compress_keep_recent_messages,
1367
1391
  autocompact_llm_prompt_kind=base.autocompact_llm_prompt_kind,
1368
1392
  compact_custom_instructions=base.compact_custom_instructions,
@@ -1382,7 +1406,7 @@ class LoopGraphBuilder:
1382
1406
  compaction_token_warn_fraction=base.compaction_token_warn_fraction,
1383
1407
  compaction_block_compact_llm_fraction=base.compaction_block_compact_llm_fraction,
1384
1408
  compaction_block_main_model_fraction=base.compaction_block_main_model_fraction,
1385
- enable_main_model_context_token_block=base.enable_main_model_context_token_block,
1409
+ enable_main_model_context_token_block=enable_main_token_block,
1386
1410
  enable_tool_transcript_repair_before_llm=base.enable_tool_transcript_repair_before_llm,
1387
1411
  emit_compaction_pipeline_meta=base.emit_compaction_pipeline_meta,
1388
1412
  )
@@ -1426,19 +1450,21 @@ class LoopGraphBuilder:
1426
1450
  context_schema=self._context_schema,
1427
1451
  )
1428
1452
  graph_inner.add_node("model", model_node)
1453
+ graph_inner.add_node(
1454
+ _PRE_MODEL_COMPACT_NODE,
1455
+ RunnableCallable(
1456
+ self._prune_and_compress_node,
1457
+ self._prune_and_compress_node_async,
1458
+ trace=False,
1459
+ ),
1460
+ )
1429
1461
  if tool_node is not None:
1430
1462
  graph_inner.add_node("tools", tool_node)
1431
-
1432
- graph_inner.add_node(
1433
- "prune_and_compress",
1434
- RunnableCallable(
1435
- self._prune_and_compress_node,
1436
- self._prune_and_compress_node_async,
1437
- trace=False,
1438
- ),
1439
- )
1440
1463
  return graph_inner
1441
1464
 
1465
+ def _wire_prune_and_compress_to_before_model(self, graph_inner: Any) -> None:
1466
+ graph_inner.add_edge(_PRE_MODEL_COMPACT_NODE, _BEFORE_MODEL_NODE)
1467
+
1442
1468
  def _prune_and_compress_node(self, state: dict[str, Any], runtime: Any) -> dict[str, Any]:
1443
1469
  return self._run_prune_and_compress_with_trace(state)
1444
1470
 
@@ -1461,6 +1487,31 @@ class LoopGraphBuilder:
1461
1487
  "skipping wait_for_extraction (use ainvoke for CC-aligned wait)"
1462
1488
  )
1463
1489
 
1490
+ @staticmethod
1491
+ def _compact_progress_phase(
1492
+ progress: CompactProgress,
1493
+ *,
1494
+ seen: bool,
1495
+ ) -> tuple[str, bool]:
1496
+ if not seen:
1497
+ return "start", True
1498
+ if progress.stage == "done" and progress.percent >= 100:
1499
+ return "end", True
1500
+ return "tick", True
1501
+
1502
+ def _merge_compact_progress_into_patch(
1503
+ self,
1504
+ patch: dict[str, Any],
1505
+ progress_events: list[dict[str, Any]],
1506
+ ) -> None:
1507
+ if not progress_events:
1508
+ return
1509
+ meta = patch.get("compaction_pipeline_meta")
1510
+ if not isinstance(meta, dict):
1511
+ meta = {}
1512
+ patch["compaction_pipeline_meta"] = meta
1513
+ meta["compact_progress"] = progress_events
1514
+
1464
1515
  def _apply_prune_and_compress(self, state: dict[str, Any]) -> dict[str, Any]:
1465
1516
  if self._session_memory_compact_bridge is not None:
1466
1517
  compacted_messages = self._session_memory_compact_bridge.try_compact(
@@ -1481,6 +1532,55 @@ class LoopGraphBuilder:
1481
1532
  patch["collapse_retry_available"] = bool(compaction_meta.get("tokens_freed_sum", 0))
1482
1533
  return patch
1483
1534
 
1535
+ async def _apply_prune_and_compress_async(self, state: dict[str, Any]) -> dict[str, Any]:
1536
+ if self._session_memory_compact_bridge is not None:
1537
+ compacted_messages = self._session_memory_compact_bridge.try_compact(
1538
+ list(state.get("messages") or [])
1539
+ )
1540
+ if compacted_messages is not None:
1541
+ return {
1542
+ "messages": compacted_messages,
1543
+ "collapse_retry_available": True,
1544
+ "compaction_pipeline_meta": {
1545
+ "autocompact_mode": "session_memory",
1546
+ "session_memory_compaction": "applied",
1547
+ },
1548
+ }
1549
+ progress_events: list[dict[str, Any]] = []
1550
+ seen_progress = False
1551
+
1552
+ def _on_progress(progress: CompactProgress) -> None:
1553
+ nonlocal seen_progress
1554
+ phase, seen_progress = self._compact_progress_phase(
1555
+ progress, seen=seen_progress
1556
+ )
1557
+ progress_events.append(
1558
+ {
1559
+ "phase": phase,
1560
+ "stage": progress.stage,
1561
+ "stage_index": progress.stage_index,
1562
+ "total_stages": progress.total_stages,
1563
+ "percent": progress.percent,
1564
+ "detail": progress.detail,
1565
+ }
1566
+ )
1567
+
1568
+ emit_progress = bool(
1569
+ self._compaction_settings.is_auto_compact_enabled
1570
+ and self._compaction_settings.emit_compaction_pipeline_meta
1571
+ )
1572
+ patch = await self._loop_context_compaction.arun(
1573
+ state,
1574
+ model=self._model,
1575
+ progress_callback=_on_progress if emit_progress else None,
1576
+ )
1577
+ if emit_progress:
1578
+ self._merge_compact_progress_into_patch(patch, progress_events)
1579
+ compaction_meta = patch.get("compaction_pipeline_meta")
1580
+ if isinstance(compaction_meta, dict):
1581
+ patch["collapse_retry_available"] = bool(compaction_meta.get("tokens_freed_sum", 0))
1582
+ return patch
1583
+
1484
1584
  def _run_prune_and_compress_with_trace(self, state: dict[str, Any]) -> dict[str, Any]:
1485
1585
  """执行 compaction,并在当前 session 内记录 compaction span(sync 路径)。"""
1486
1586
  collector = state.get("_trace_collector")
@@ -1546,7 +1646,7 @@ class LoopGraphBuilder:
1546
1646
  try:
1547
1647
  if self._session_memory_compact_bridge is not None:
1548
1648
  await self._session_memory_compact_bridge.wait_for_extraction()
1549
- patch = self._apply_prune_and_compress(state)
1649
+ patch = await self._apply_prune_and_compress_async(state)
1550
1650
  except Exception as exc:
1551
1651
  if span_id is not None:
1552
1652
  collector.span_end(
@@ -1593,6 +1693,7 @@ class LoopGraphBuilder:
1593
1693
  skill_prefetch_provider=skill_prefetch_provider,
1594
1694
  entry_node=entry_node,
1595
1695
  )
1696
+ self._wire_prune_and_compress_to_before_model(graph_inner)
1596
1697
  graph_inner.add_edge("hook_before_model", "model")
1597
1698
  return {
1598
1699
  "entry_node": entry_node,
@@ -1896,12 +1997,8 @@ class LoopGraphBuilder:
1896
1997
  RunnableCallable(_task_event_reserve_node, None, trace=False),
1897
1998
  )
1898
1999
  graph_inner.add_edge("task_event_commit_ack", "task_event_reserve")
1899
- # Phase 5A:压缩收束到「每轮进入 model 之前」唯一入口;有 task 链时在注入附件之后再压缩。
1900
- if tool_node is not None:
1901
- graph_inner.add_edge("task_event_reserve", "prune_and_compress")
1902
- graph_inner.add_edge("prune_and_compress", raw_loop_entry_node)
1903
- else:
1904
- graph_inner.add_edge("task_event_reserve", raw_loop_entry_node)
2000
+ # Phase 5A / proactive autocompact:task 注入后再压缩,再进 hook_before_model。
2001
+ graph_inner.add_edge("task_event_reserve", _PRE_MODEL_COMPACT_NODE)
1905
2002
  return "task_event_commit_ack"
1906
2003
 
1907
2004
  def _find_skill_prefetch_provider(
@@ -1984,10 +2081,10 @@ class LoopGraphBuilder:
1984
2081
  )
1985
2082
  graph_inner.add_edge(START, "skill_init")
1986
2083
  graph_inner.add_edge("skill_init", "hook_loop_start")
1987
- graph_inner.add_edge("hook_loop_start", entry_node)
2084
+ graph_inner.add_edge("hook_loop_start", _PRE_MODEL_COMPACT_NODE)
1988
2085
  return
1989
2086
  graph_inner.add_edge(START, "hook_loop_start")
1990
- graph_inner.add_edge("hook_loop_start", entry_node)
2087
+ graph_inner.add_edge("hook_loop_start", _PRE_MODEL_COMPACT_NODE)
1991
2088
 
1992
2089
  def _wire_main_edges(self, *, graph_inner: Any, tool_node: Any, routing: dict[str, Any]) -> None:
1993
2090
  loop_entry_node = routing["loop_entry_node"]
@@ -2040,8 +2137,8 @@ class LoopGraphBuilder:
2040
2137
  tools_model_destination = loop_entry_node
2041
2138
  tools_to_model_destinations = [loop_entry_node, exit_node]
2042
2139
  else:
2043
- tools_model_destination = "prune_and_compress"
2044
- tools_to_model_destinations = ["prune_and_compress", exit_node]
2140
+ tools_model_destination = _PRE_MODEL_COMPACT_NODE
2141
+ tools_to_model_destinations = [_PRE_MODEL_COMPACT_NODE, exit_node]
2045
2142
 
2046
2143
  graph_inner.add_conditional_edges(
2047
2144
  "tools",
@@ -2056,8 +2153,6 @@ class LoopGraphBuilder:
2056
2153
  ),
2057
2154
  tools_to_model_destinations,
2058
2155
  )
2059
- if not wire_task_event_nodes:
2060
- graph_inner.add_edge("prune_and_compress", loop_entry_node)
2061
2156
  graph_inner.add_edge(loop_exit_node, "hook_after_model")
2062
2157
  graph_inner.add_edge("hook_after_model", "hook_stop")
2063
2158
  self._wire_loop_controller(
@@ -2079,11 +2174,12 @@ class LoopGraphBuilder:
2079
2174
  wire_task_event_nodes: bool,
2080
2175
  tools_destination: str | None = None,
2081
2176
  ) -> None:
2177
+ model_gate = _pre_model_gate_destination(loop_entry_node)
2082
2178
  graph_inner.add_node(
2083
2179
  "loop_controller",
2084
2180
  RunnableCallable(
2085
2181
  _make_loop_controller_node(
2086
- model_destination=loop_entry_node,
2182
+ model_destination=model_gate,
2087
2183
  tools_destination=tools_destination or loop_entry_node,
2088
2184
  structured_output_tools=self._structured_output_tools,
2089
2185
  end_destination=exit_node,
@@ -2107,7 +2203,7 @@ class LoopGraphBuilder:
2107
2203
  def _reactive_compact_node(state, runtime):
2108
2204
  node = ReactiveCompactNode(
2109
2205
  compaction_service=default_loop_context_compaction(),
2110
- loop_entry_destination=loop_entry_node,
2206
+ loop_entry_destination=model_gate,
2111
2207
  end_destination=exit_node,
2112
2208
  )
2113
2209
  return node(state)
@@ -2116,13 +2212,14 @@ class LoopGraphBuilder:
2116
2212
  "reactive_compact",
2117
2213
  RunnableCallable(_reactive_compact_node, None, trace=False),
2118
2214
  )
2215
+ reactive_destinations = [model_gate, exit_node]
2119
2216
  graph_inner.add_conditional_edges(
2120
2217
  "reactive_compact",
2121
- lambda state: state.get("destination", loop_entry_node),
2122
- [loop_entry_node, exit_node],
2218
+ lambda state: state.get("destination", model_gate),
2219
+ reactive_destinations,
2123
2220
  )
2124
2221
 
2125
- loop_controller_destinations = [loop_entry_node, exit_node, "reactive_compact"]
2222
+ loop_controller_destinations = [model_gate, exit_node, "reactive_compact"]
2126
2223
  if tools_destination is not None:
2127
2224
  loop_controller_destinations.insert(0, tools_destination)
2128
2225
  if wire_task_event_nodes and "task_event_commit_ack" not in loop_controller_destinations:
@@ -2144,16 +2241,17 @@ class LoopGraphBuilder:
2144
2241
  wire_task_event_nodes: bool,
2145
2242
  ) -> None:
2146
2243
  graph_inner.add_edge(loop_exit_node, "hook_after_model")
2244
+ model_gate = _pre_model_gate_destination(loop_entry_node)
2147
2245
  graph_inner.add_conditional_edges(
2148
2246
  "hook_after_model",
2149
2247
  RunnableCallable(
2150
2248
  _make_model_to_model_edge(
2151
- model_destination=loop_entry_node,
2249
+ model_destination=model_gate,
2152
2250
  end_destination="hook_stop",
2153
2251
  ),
2154
2252
  trace=False,
2155
2253
  ),
2156
- [loop_entry_node, "hook_stop"],
2254
+ [model_gate, "hook_stop"],
2157
2255
  )
2158
2256
  self._wire_loop_controller(
2159
2257
  graph_inner=graph_inner,
@@ -128,6 +128,7 @@ class LangchainAgentEventType(Enum):
128
128
  HOOK_PROGRESS = "hook-progress"
129
129
  HOOK_FINISHED = "hook-finished"
130
130
  COMPACT_BOUNDARY = "compact-boundary"
131
+ COMPACT_PROGRESS = "compact-progress"
131
132
  MEMORY_SAVED = "memory-saved"
132
133
  SUBAGENT_START = "subagent-start"
133
134
  SUBAGENT_END = "subagent-end"
@@ -850,6 +851,16 @@ class AgentEventProjector:
850
851
 
851
852
  if self._is_compact_node(name):
852
853
  meta = extract_compaction_meta_from_chain_end_data(data)
854
+ if isinstance(meta, dict) and self.enable_compact_events:
855
+ raw_progress = meta.get("compact_progress")
856
+ if isinstance(raw_progress, list):
857
+ for item in raw_progress:
858
+ if isinstance(item, dict):
859
+ yield self._event(
860
+ event_type=LangchainAgentEventType.COMPACT_PROGRESS,
861
+ data=dict(item),
862
+ step_index=self.state.step_index or None,
863
+ )
853
864
  if is_user_visible_compact(meta):
854
865
  if self.enable_compact_events:
855
866
  compact_data = self._extract_compact_summary(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.3.3
3
+ Version: 2.3.4
4
4
  Summary: LangChain/LangGraph-based agent utilities for CodeBaseX.
5
5
  Author-email: GoodMood2008 <GoodMood2008@users.noreply.github.com>
6
6
  License: Apache License
@@ -1,4 +1,4 @@
1
- langchain_agentx/__init__.py,sha256=6ftpvlLKaDt-1ujsfwn1CpOzcZoVDPlRwOsS7KZDCR0,1614
1
+ langchain_agentx/__init__.py,sha256=t5u-2g4slzGHVtYyJTJcFQgv_d5BfqHyJWeSDe9FMHY,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
@@ -31,8 +31,9 @@ langchain_agentx/loop/message_contract.py,sha256=Twl83TneU2PRdxgGRBxs6S9_ovRT6cb
31
31
  langchain_agentx/loop/run_context.py,sha256=dhNiLhvqD_QYacGMA0B5P3wWpSe3O6wU_XcVEzb86Z4,8303
32
32
  langchain_agentx/loop/config/__init__.py,sha256=0pf3Mml7LZpQ1C3_AtROFQClrBQegaGCG4YCTfSr9v8,1137
33
33
  langchain_agentx/loop/config/agent_config.py,sha256=tOnAHFor6dkle3FvUOVx0YsmiYVTH11UrWSyTmOLV0c,3100
34
+ langchain_agentx/loop/config/compaction_config.py,sha256=MmXqY47-9nPDhdXvnU92Xp0XeaZ605ErQwX9d2emEkM,1176
34
35
  langchain_agentx/loop/config/interaction_profile.py,sha256=UWrlI3aTWjYRq1mDeXL-xL-iCrQU-bXDXhETP6q9icY,10020
35
- langchain_agentx/loop/config/loop_config.py,sha256=b87oJNcy9rYi-bYnw32OFCndXOPj54-ysrpe0F1fR2w,16147
36
+ langchain_agentx/loop/config/loop_config.py,sha256=3kYDXg91PSDNVUdHGm6In0mHJRpfrkITs4OLKdOngYY,16180
36
37
  langchain_agentx/loop/config/loop_runtime_overlay.py,sha256=PuB4vFdqCHXRbAFhAJIrtwq9pUIrHafyn0ApD4s06KU,2440
37
38
  langchain_agentx/loop/config/model_context_resolver.py,sha256=o9RR0Vnu07TD4Y3V8jk5V1NfkMcPcLjE8uaaLOe22Qo,4067
38
39
  langchain_agentx/loop/config/model_retry_config.py,sha256=bakCbGeNRO-_SXMT_LLE3nu3m9scZe1MSxPdyEGO99w,8178
@@ -45,13 +46,13 @@ langchain_agentx/loop/context/__init__.py,sha256=RXVTFjHcH6_8q1x13Qk9ZP8RVbqZoyh
45
46
  langchain_agentx/loop/context/blocking_guard.py,sha256=Rmt5qZdE8Zf0bt-Q6WwMSfG869772PE8w1mNPvBQRCQ,3789
46
47
  langchain_agentx/loop/context/compaction_service.py,sha256=MwAPPaT31XbzgwqpuknGBZlYBRKxwyhWXDifEnBgh2E,3725
47
48
  langchain_agentx/loop/context/message_utils.py,sha256=6qIwtHeh63OlMqOnPUpEEWRknU9KJTGkcY1v3tz6feY,2010
48
- langchain_agentx/loop/context/pipeline.py,sha256=Ry90p-lrno2jgoKBLEUJle9evJCFJ_IBSe9sru0XjPM,7486
49
+ langchain_agentx/loop/context/pipeline.py,sha256=PrhCWWKUGGwMGd-F8UX6vt3U7tlZo2ZZDLV0CTTDSJ4,8279
49
50
  langchain_agentx/loop/context/query_source.py,sha256=-wqVpST64jXF1ZelcGdvIsJE4ngv54eco4gmeS3pyyg,5118
50
51
  langchain_agentx/loop/context/recovery_mode.py,sha256=ytHGnE-g91pDQIkyVcfxEw6XgJGZ5eVJ12jb3zaeXWU,1100
51
- langchain_agentx/loop/context/settings.py,sha256=K9WRpvyUEo57gDxt4qpo0ouE8ihN5L0vGy3NmdIbssw,5987
52
- langchain_agentx/loop/context/types.py,sha256=UpnurkJfYsmbZXkrwbaDuVpZYJ4ydPHRNiUijL6fh3s,3382
52
+ langchain_agentx/loop/context/settings.py,sha256=EO35T2P5tIUIqkO42ffLTRD-U-w2aRxeAYuHFHYc0nE,6589
53
+ langchain_agentx/loop/context/types.py,sha256=jWXibGiJY6sQnd5-OQu9z7ZZM8Efi1X-6M8zJHNutqw,3637
53
54
  langchain_agentx/loop/context/stages/__init__.py,sha256=wk9CFh8_Akw8qs3Dldnx14VE1MhyF5i9Cg3phTVChhg,892
54
- langchain_agentx/loop/context/stages/autocompact.py,sha256=C2CW-H5NQbNSN7fCcxJ8o-liAc6uuPTBvoy4PJjdkGk,12014
55
+ langchain_agentx/loop/context/stages/autocompact.py,sha256=7segcCEl0kk9hK6YRHAbM67e8As1sE_CRaUUfUnHFh0,13581
55
56
  langchain_agentx/loop/context/stages/base.py,sha256=LVfCp-DLySrTExy0J-M40tDJvzbOXxfZxXvi7XQayA8,1380
56
57
  langchain_agentx/loop/context/stages/collapse.py,sha256=yOf3nfWZr83rq2qnhUpuTCLFHcGvF7my8klMLmfZ7jc,2971
57
58
  langchain_agentx/loop/context/stages/microcompact.py,sha256=WnT58w85xcaSzsGEww5TEnU1noAzkao3ZgAARo65a7Y,3142
@@ -60,7 +61,7 @@ langchain_agentx/loop/context/stages/snip.py,sha256=AZy3D2XPrL1GQU_gE9isKApcphXw
60
61
  langchain_agentx/loop/context/stages/tool_result_budget.py,sha256=zQcJynRnC8QF30cEDduHkso6jChYjckVcnOjYSrCrtg,3833
61
62
  langchain_agentx/loop/exit/__init__.py,sha256=3-CMz5ApkJeBc549vRfgNABenyYKVbib4IdkErVtGcM,528
62
63
  langchain_agentx/loop/exit/api_error_detection.py,sha256=QzyMTov4VrkpHmn288Tw7DN00hiyJNXMKlOrCbvsiAA,1479
63
- langchain_agentx/loop/exit/exit_logic.py,sha256=nJzerVtTmS1iIY3PyBRet7jZ9v9pi-jUO3mJwzqxcnw,13922
64
+ langchain_agentx/loop/exit/exit_logic.py,sha256=3I63n9pyjHD9CfpboWSLNm2O2J8c5PlVjcxesir40k8,14068
64
65
  langchain_agentx/loop/exit/query_source.py,sha256=b_VB13zxNbLAqUAuDt1E3SII3jNLagY-FdN6SbczTsI,2082
65
66
  langchain_agentx/loop/exit/reason_codes.py,sha256=Nk1x2ipftRIRa4U1Z2goyXXWB9TsPFL9TW6WfvtMCEo,4810
66
67
  langchain_agentx/loop/exit/resolution.py,sha256=taIMHzAUtgrQoU9446-CUXBN0PyCujhl6wBQfbxR1qI,1427
@@ -68,7 +69,7 @@ langchain_agentx/loop/exit/terminal.py,sha256=_DAZk-kS4Xd0nxMlayLo60-uj1Y5ozgBjL
68
69
  langchain_agentx/loop/exit/withheld_error.py,sha256=LfX3j7Rgd9VoMzlz7GOs9TPuRe7RXr_Gxweyotx3tqQ,1846
69
70
  langchain_agentx/loop/graph/__init__.py,sha256=9pUUqn7G87n3lV45iYIU24j0wcTHyCD--SSdtv3urh4,136
70
71
  langchain_agentx/loop/graph/builtin_loop_control.py,sha256=31o4H6SDGC_vECSliXecuVEnzlD1ahlVZ5JzEpxYY04,9248
71
- langchain_agentx/loop/graph/factory.py,sha256=-RDgiprUAydk_tSA86CyI7zgj9wC6gPen2p39OFscIU,105591
72
+ langchain_agentx/loop/graph/factory.py,sha256=n0lszl2jYBq1G_rzgmt0-0mCmTa3zuTqrVCow4lUMso,109909
72
73
  langchain_agentx/loop/graph/graph_edges.py,sha256=kW6Npx-nMIPzcaisCxVPkTwf5Y9GOKy1NWCNkJWeJBQ,47370
73
74
  langchain_agentx/loop/graph/reactive_compact_node.py,sha256=633b5hTnR3BK8GBEKWhzOwGB0BQrVRKozNxDomWFjLo,8762
74
75
  langchain_agentx/loop/graph/runtime_tools_node.py,sha256=wQkbc0ophyefpPZEc0q1vKoJOIdS4S6d6hSsrkdRLDk,7387
@@ -217,7 +218,7 @@ langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8
217
218
  langchain_agentx/observability/events/__init__.py,sha256=Zr9Iguf9XsJQLsCJy13YFdBtXHPEnI9aSWXWmA2MisA,1497
218
219
  langchain_agentx/observability/events/accumulated_text_scope.py,sha256=M4OvuQTgReF8SFrOmr-M5vyOJpzfvxj_DKD5bVpGb70,3169
219
220
  langchain_agentx/observability/events/agent_defaults.py,sha256=gmX0P__lux5xbCb8dtAlVCb3wZ7avaqFleoW4Q5anw4,1187
220
- langchain_agentx/observability/events/agent_event_projector.py,sha256=Rktmy2D3qizCejAChbuOiAa06Tnup2Z6cVNATfrrxkU,63995
221
+ langchain_agentx/observability/events/agent_event_projector.py,sha256=SthsPhWuM__Bfm3GoxHw-JCoMQ5xgn_eDgi5n4ib6-4,64600
221
222
  langchain_agentx/observability/events/api_retry_projection.py,sha256=XGb09sNOi6k1ZGW4YqMLBE8GDxNwNMprk62jV9TS9dQ,2433
222
223
  langchain_agentx/observability/events/compact_event_gate.py,sha256=50RJemwB1lnQn_NFWOFSgU418ZUR8dbd_v0538PT7zM,4751
223
224
  langchain_agentx/observability/events/factory.py,sha256=gqMATIK9tBQzaPWSPhCCSPuCq-WF562ZDTnFUaMm9Qc,1570
@@ -766,8 +767,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
766
767
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
767
768
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
768
769
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
769
- langchain_agentx_python-2.3.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
770
- langchain_agentx_python-2.3.3.dist-info/METADATA,sha256=e_CGB6uG3nhxDnh2VADLt975KFC-uVAARQlRtha-3es,23942
771
- langchain_agentx_python-2.3.3.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
772
- langchain_agentx_python-2.3.3.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
773
- langchain_agentx_python-2.3.3.dist-info/RECORD,,
770
+ langchain_agentx_python-2.3.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
771
+ langchain_agentx_python-2.3.4.dist-info/METADATA,sha256=zMKaBp2iVa62_ASW-y0xHpoqDFuMu64NDF414ESG2XA,23942
772
+ langchain_agentx_python-2.3.4.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
773
+ langchain_agentx_python-2.3.4.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
774
+ langchain_agentx_python-2.3.4.dist-info/RECORD,,