langchain-agentx-python 2.2.1__py3-none-any.whl → 2.2.3__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.2.1"
14
+ __version__ = "2.2.3"
15
15
 
16
16
  from .loop import ( # noqa: F401
17
17
  create_loop_agent,
@@ -51,6 +51,12 @@ from langchain_agentx.observability.events.agent_defaults import DEFAULT_AGENT_T
51
51
  from langchain_agentx.observability.events.subagent_window_text_guard import (
52
52
  SubagentWindowTextGuard,
53
53
  )
54
+ from langchain_agentx.observability.events.compact_event_gate import (
55
+ beneficial_light_stage_names,
56
+ extract_compaction_meta_from_chain_end_data,
57
+ is_user_visible_compact,
58
+ light_only_with_benefit,
59
+ )
54
60
  from langchain_agentx.observability.events.tool_call_id_resolver import (
55
61
  ToolCallIdResolver,
56
62
  )
@@ -121,8 +127,7 @@ class LangchainAgentEventType(Enum):
121
127
  FINISH_STEP = "finish-step"
122
128
  HOOK_PROGRESS = "hook-progress"
123
129
  HOOK_FINISHED = "hook-finished"
124
- COMPACT_START = "compact-start"
125
- COMPACT_END = "compact-end"
130
+ COMPACT_BOUNDARY = "compact-boundary"
126
131
  MEMORY_SAVED = "memory-saved"
127
132
  SUBAGENT_START = "subagent-start"
128
133
  SUBAGENT_END = "subagent-end"
@@ -396,12 +401,7 @@ class AgentEventProjector:
396
401
  step_index=self.state.step_index or None,
397
402
  )
398
403
 
399
- if self.enable_compact_events and self._is_compact_node(name):
400
- yield self._event(
401
- event_type=LangchainAgentEventType.COMPACT_START,
402
- data={},
403
- step_index=self.state.step_index or None,
404
- )
404
+ # D-CMP-EVT:不在 start 发 compact(CC 无 start/end;事后单边界)
405
405
 
406
406
  def _build_loop_observation_payload(self) -> Dict[str, Any]:
407
407
  snap = self.state.last_loop_snapshot or {}
@@ -752,7 +752,6 @@ class AgentEventProjector:
752
752
  result["tokens_freed"] = meta.get("tokens_freed_sum")
753
753
 
754
754
  stages = meta.get("stages")
755
- autocompact_found = False
756
755
  microcompact_found = False
757
756
 
758
757
  if isinstance(stages, list):
@@ -761,7 +760,6 @@ class AgentEventProjector:
761
760
  name = s.get("name")
762
761
  extra = s.get("extra") or {}
763
762
  if name == "autocompact":
764
- autocompact_found = True
765
763
  result["autocompact_mode"] = extra.get("autocompact_mode")
766
764
  result["folded_messages"] = extra.get("folded_messages")
767
765
  result["messages_summarized"] = extra.get("folded_messages")
@@ -782,10 +780,12 @@ class AgentEventProjector:
782
780
  break
783
781
 
784
782
  if "compact_type" not in result:
785
- if autocompact_found or not microcompact_found:
783
+ mode = result.get("autocompact_mode")
784
+ if isinstance(mode, str) and mode:
786
785
  result["compact_type"] = "autocompact"
787
- else:
786
+ elif microcompact_found:
788
787
  result["compact_type"] = "microcompact"
788
+ # 不再用「stages 出现过 autocompact 名」回落为 autocompact(假阳性)
789
789
 
790
790
  if "summary" not in result:
791
791
  messages = output.get("messages")
@@ -848,13 +848,30 @@ class AgentEventProjector:
848
848
  step_index=self.state.step_index or None,
849
849
  )
850
850
 
851
- if self.enable_compact_events and self._is_compact_node(name):
852
- compact_data = self._extract_compact_summary(data)
853
- yield self._event(
854
- event_type=LangchainAgentEventType.COMPACT_END,
855
- data=compact_data,
856
- step_index=self.state.step_index or None,
857
- )
851
+ if self._is_compact_node(name):
852
+ meta = extract_compaction_meta_from_chain_end_data(data)
853
+ if is_user_visible_compact(meta):
854
+ if self.enable_compact_events:
855
+ compact_data = self._extract_compact_summary(data)
856
+ if "trigger" not in compact_data:
857
+ compact_data["trigger"] = "auto"
858
+ yield self._event(
859
+ event_type=LangchainAgentEventType.COMPACT_BOUNDARY,
860
+ data=compact_data,
861
+ step_index=self.state.step_index or None,
862
+ )
863
+ elif light_only_with_benefit(meta):
864
+ # D-CMP-EVT-7:对齐 CC logForDebugging;不受 enable_compact_events 关闭影响
865
+ freed = None
866
+ if isinstance(meta, dict):
867
+ freed = meta.get("tokens_freed_sum")
868
+ stages = beneficial_light_stage_names(meta)
869
+ logger.debug(
870
+ "compact light-only: tokens_freed_sum=%s stages=%s "
871
+ "(no public compact-boundary; align CC logForDebugging)",
872
+ freed,
873
+ stages or None,
874
+ )
858
875
 
859
876
  if name == self.state.graph_name:
860
877
  if self.enable_reasoning_events and self.state.is_reasoning_active:
@@ -0,0 +1,152 @@
1
+ """
2
+ compact_event_gate.py — compact 公共事件门闩(对齐 CC compact_boundary)
3
+
4
+ 职责:
5
+ - 从 compaction_pipeline_meta 判定是否用户可见真压缩 / light-only 有收益
6
+ - 供 agent_event_projector 发射 compact-boundary 或 logger.debug
7
+
8
+ 链路位置:
9
+ - Layer 3: observability/events 投影门闩(不改 L0 压缩流水线)
10
+
11
+ 当前裁剪范围:
12
+ - 仅 meta 谓词;不创建 SSE 以外的事件类型
13
+ - session_memory applied 仅识别显式标记(字段可后续扩展)
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any, Optional
19
+
20
+ from langchain_agentx.loop.exit.query_source import QuerySource
21
+
22
+ # 实际折叠上下文的 autocompact_mode(不含仅 skipped)
23
+ APPLIED_AUTOCOMPACT_MODES: frozenset[str] = frozenset(
24
+ {
25
+ "llm",
26
+ "prune",
27
+ "prune_blocked_llm",
28
+ }
29
+ )
30
+
31
+ _LIGHT_STAGE_NAMES: frozenset[str] = frozenset(
32
+ {
33
+ "tool_result_budget",
34
+ "snip",
35
+ "microcompact",
36
+ "collapse",
37
+ }
38
+ )
39
+
40
+
41
+ def _as_dict(meta: Any) -> Optional[dict[str, Any]]:
42
+ return meta if isinstance(meta, dict) else None
43
+
44
+
45
+ def _stage_extra(stage: dict[str, Any]) -> dict[str, Any]:
46
+ extra = stage.get("extra")
47
+ return extra if isinstance(extra, dict) else {}
48
+
49
+
50
+ def _positive_number(value: Any) -> bool:
51
+ if value is None:
52
+ return False
53
+ try:
54
+ return float(value) > 0
55
+ except (TypeError, ValueError):
56
+ return False
57
+
58
+
59
+ def is_user_visible_compact(meta: Any) -> bool:
60
+ """真压缩已应用 → 应对齐 CC 发一条 compact-boundary。
61
+
62
+ 禁止用 tokens_freed_sum / stages 仅出现 autocompact 名 / compact_type 回落判定。
63
+ """
64
+ m = _as_dict(meta)
65
+ if m is None:
66
+ return False
67
+
68
+ # Session / 手动:L0 现网键(factory session_memory 路径无 stages[])
69
+ if m.get("session_memory_compact_applied") is True:
70
+ return True
71
+ if m.get("manual_compact_applied") is True:
72
+ return True
73
+ if m.get("session_memory_compaction") == "applied":
74
+ return True
75
+ if m.get("autocompact_mode") == QuerySource.SESSION_MEMORY:
76
+ return True
77
+
78
+ stages = m.get("stages")
79
+ if not isinstance(stages, list):
80
+ return False
81
+
82
+ for stage in stages:
83
+ if not isinstance(stage, dict):
84
+ continue
85
+ name = stage.get("name")
86
+ extra = _stage_extra(stage)
87
+ if name == "autocompact":
88
+ mode = extra.get("autocompact_mode")
89
+ if isinstance(mode, str) and mode in APPLIED_AUTOCOMPACT_MODES:
90
+ return True
91
+ if name == QuerySource.SESSION_MEMORY and extra.get("applied") is True:
92
+ return True
93
+ return False
94
+
95
+
96
+ def light_only_with_benefit(meta: Any) -> bool:
97
+ """非用户可见,但有轻量 stage(或 sum)释放 token → 应对齐 CC logForDebugging。"""
98
+ if is_user_visible_compact(meta):
99
+ return False
100
+ m = _as_dict(meta)
101
+ if m is None:
102
+ return False
103
+
104
+ stages = m.get("stages")
105
+ if isinstance(stages, list):
106
+ for stage in stages:
107
+ if not isinstance(stage, dict):
108
+ continue
109
+ if _positive_number(stage.get("tokens_freed")):
110
+ return True
111
+ # micro 规范化标记但 tokens 估计可能为 0:仍视为有 light 动作时可记
112
+ name = stage.get("name")
113
+ extra = _stage_extra(stage)
114
+ if name == "microcompact" and extra.get("microcompact_normalized"):
115
+ return True
116
+
117
+ return _positive_number(m.get("tokens_freed_sum"))
118
+
119
+
120
+ def beneficial_light_stage_names(meta: Any) -> list[str]:
121
+ """有收益的轻量 stage 名(供 debug 文案)。"""
122
+ m = _as_dict(meta)
123
+ if m is None:
124
+ return []
125
+ names: list[str] = []
126
+ stages = m.get("stages")
127
+ if not isinstance(stages, list):
128
+ return names
129
+ for stage in stages:
130
+ if not isinstance(stage, dict):
131
+ continue
132
+ name = stage.get("name")
133
+ if not isinstance(name, str):
134
+ continue
135
+ extra = _stage_extra(stage)
136
+ freed = _positive_number(stage.get("tokens_freed"))
137
+ micro = name == "microcompact" and bool(extra.get("microcompact_normalized"))
138
+ if freed or micro:
139
+ if name in _LIGHT_STAGE_NAMES or freed or micro:
140
+ names.append(name)
141
+ return names
142
+
143
+
144
+ def extract_compaction_meta_from_chain_end_data(data: Any) -> Optional[dict[str, Any]]:
145
+ """从 on_chain_end data 取出 compaction_pipeline_meta。"""
146
+ if not isinstance(data, dict):
147
+ return None
148
+ output = data.get("output")
149
+ if not isinstance(output, dict):
150
+ return None
151
+ meta = output.get("compaction_pipeline_meta")
152
+ return meta if isinstance(meta, dict) else None
@@ -28,6 +28,19 @@ from langchain_agentx.utils.subprocess_text import (
28
28
  from .models import GrepOutputMode
29
29
 
30
30
 
31
+ def resolve_ripgrep_subprocess_cwd(search_path: str) -> str:
32
+ """subprocess cwd 必须是目录;schema 允许 path 为文件时退到父目录。
33
+
34
+ Windows 上相对 glob 需要 cwd≈搜索根目录;但不能把文件路径直接当 cwd
35
+ (会 NotADirectoryError / Errno 20)。rg 命令行目标仍用原 search_path。
36
+ """
37
+ real = os.path.realpath(search_path)
38
+ if os.path.isfile(real):
39
+ parent = os.path.dirname(real)
40
+ return parent if parent else os.sep
41
+ return real
42
+
43
+
31
44
  class GrepTimeoutError(ToolRuntimeError):
32
45
  def __init__(self, message: str) -> None:
33
46
  super().__init__(message, code="GREP_TIMEOUT")
@@ -60,14 +73,16 @@ def run_ripgrep_lines(
60
73
  对齐 CC ripgrep.ts:returncode 1 -> [];EAGAIN 时以 -j 1 重试一次。
61
74
  供 RipgrepBackend 与 Glob 的 rg --files 列文件共用。
62
75
 
63
- SDK 补充:``subprocess cwd=search_path``。CC 隐式依赖进程 cwd≈searchDir;
64
- 编排布局 ``state_root≠repo`` 时须显式设 cwd,否则 Windows 上目录前缀 glob(如 ``alpha/*``)误空。
76
+ SDK 补充:``subprocess cwd`` 对齐搜索根目录(目录本身,或文件的父目录)。
77
+ CC 隐式依赖进程 cwd≈searchDir;编排布局 ``state_root≠repo`` 时须显式设 cwd
78
+ 否则 Windows 上目录前缀 glob(如 ``alpha/*``)误空。
65
79
 
66
80
  若 ``cancel_event`` 非空,使用可中断子进程路径(``RgSubprocessController``),
67
81
  不再走 ``text_runner.run``(便于测试 mock 时仅覆盖无取消路径)。
68
82
  """
69
- search_cwd = os.path.realpath(search_path)
70
- cmd = [rg_path, *args, search_cwd]
83
+ search_target = os.path.realpath(search_path)
84
+ search_cwd = resolve_ripgrep_subprocess_cwd(search_path)
85
+ cmd = [rg_path, *args, search_target]
71
86
  if cancel_event is not None:
72
87
  if cancel_event.is_set():
73
88
  raise GrepCancelledError("ripgrep cancelled before start")
@@ -321,7 +336,7 @@ class RipgrepBackend:
321
336
  plugin_glob_exclusions=plugin_glob_exclusions,
322
337
  )
323
338
  cmd = [self._rg_path, *args, os.path.realpath(search_path)]
324
- search_cwd = os.path.realpath(search_path)
339
+ search_cwd = resolve_ripgrep_subprocess_cwd(search_path)
325
340
  if cancel_event is not None and cancel_event.is_set():
326
341
  raise GrepCancelledError("ripgrep cancelled before start")
327
342
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langchain-agentx-python
3
- Version: 2.2.1
3
+ Version: 2.2.3
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=MJpPR7tuUrTR76iSfQUpDD67ydVKB93o5UEY78FBs28,1614
1
+ langchain_agentx/__init__.py,sha256=bMHXxvdlfRUJK-ZT43f2Hgef2tNuOs5bc3mv-F4BQRc,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
@@ -217,8 +217,9 @@ langchain_agentx/observability/evaluation/checkers/tool_behavior.py,sha256=o1nH8
217
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=eMri832yK_BZbwsB_APwUN9vu66zMpZVxTstChG5l0A,63010
220
+ langchain_agentx/observability/events/agent_event_projector.py,sha256=Rktmy2D3qizCejAChbuOiAa06Tnup2Z6cVNATfrrxkU,63995
221
221
  langchain_agentx/observability/events/api_retry_projection.py,sha256=XGb09sNOi6k1ZGW4YqMLBE8GDxNwNMprk62jV9TS9dQ,2433
222
+ langchain_agentx/observability/events/compact_event_gate.py,sha256=50RJemwB1lnQn_NFWOFSgU418ZUR8dbd_v0538PT7zM,4751
222
223
  langchain_agentx/observability/events/factory.py,sha256=gqMATIK9tBQzaPWSPhCCSPuCq-WF562ZDTnFUaMm9Qc,1570
223
224
  langchain_agentx/observability/events/finish_event_payload_composer.py,sha256=v7MyLNSZ_gR5u5WFxPJu_0MYNDvC9s9gI2iNNt9Vbr4,13189
224
225
  langchain_agentx/observability/events/finish_outcome.py,sha256=igoueAwduzuuGDwg6u9AbBB-T2UBiqbUfNoxWCBYc_g,10401
@@ -580,7 +581,7 @@ langchain_agentx/tools/glob/rg_list_backend.py,sha256=gTyFQE0eYHl-7LkSdah76S8bQn
580
581
  langchain_agentx/tools/glob/rg_pattern.py,sha256=YOV-1gdxC52vzdAwgz27l7mzPJvVuHqpqeWhiEnWQJs,1377
581
582
  langchain_agentx/tools/glob/tool.py,sha256=m746Vl_aI9eFmrXYECkg16zMln9YPVxqjtijPlc5ALA,14868
582
583
  langchain_agentx/tools/grep/__init__.py,sha256=AA9qa42Ap9tw6lPQkNgKzYmr4hu44eP93qVdyP-eFNU,162
583
- langchain_agentx/tools/grep/backend.py,sha256=DSKetuS6Wcta5wrDBVFYSVDLsou0VDXXZyvdFvN1nls,13885
584
+ langchain_agentx/tools/grep/backend.py,sha256=bzwRUJ_aZjs84oIG3l6IAVygGiLDyNsHPePiMaFMo-4,14539
584
585
  langchain_agentx/tools/grep/models.py,sha256=6fTSux0COK06weQ7PWYqCGBQx60OaNtqC5q7xKtf5i0,3860
585
586
  langchain_agentx/tools/grep/prompt.py,sha256=Euo1NErphk5WHxaKXwJH-1v7joblfwEFWHIYNthKkzo,1336
586
587
  langchain_agentx/tools/grep/rg_subprocess_controller.py,sha256=ijMJnKPo7iEDFBz-_YipTiPusrL6OO2XbWxHUuwpMfA,4056
@@ -759,8 +760,8 @@ langchain_agentx/workspace/root_grant_manager.py,sha256=W3gy8HTevbERbXqIgRcjzOXO
759
760
  langchain_agentx/workspace/tool_boundary.py,sha256=UDwX6swpSLsx9HNkYuuRRiV5o7ZZG_Bru2Yn0c5kNX8,5724
760
761
  langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
761
762
  langchain_agentx/workspace/view.py,sha256=PGasqTaqhlD03SXXazHuw4RHfV681AIdlsYqL70pEjc,4774
762
- langchain_agentx_python-2.2.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
763
- langchain_agentx_python-2.2.1.dist-info/METADATA,sha256=UfgeVWTPqOZJ3Nps3E7gtcupQdCo0YhTRY8uLS3uUiM,24250
764
- langchain_agentx_python-2.2.1.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
765
- langchain_agentx_python-2.2.1.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
766
- langchain_agentx_python-2.2.1.dist-info/RECORD,,
763
+ langchain_agentx_python-2.2.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
764
+ langchain_agentx_python-2.2.3.dist-info/METADATA,sha256=VcuvJI240NmP2VxSl2xxXD2BTvpnCLxnkXjg5aHzmQE,24250
765
+ langchain_agentx_python-2.2.3.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
766
+ langchain_agentx_python-2.2.3.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
767
+ langchain_agentx_python-2.2.3.dist-info/RECORD,,