langchain-agentx-python 0.3.2__py3-none-any.whl → 0.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.
- langchain_agentx/loop/context/__init__.py +4 -0
- langchain_agentx/loop/context/compaction_service.py +22 -1
- langchain_agentx/loop/context/pipeline.py +81 -1
- langchain_agentx/loop/context/stages/autocompact.py +113 -1
- langchain_agentx/loop/context/stages/base.py +9 -0
- langchain_agentx/loop/context/types.py +27 -0
- langchain_agentx/loop/graph/factory.py +13 -9
- langchain_agentx/loop/model/model_node.py +16 -1
- langchain_agentx/memory/session/compact_bridge.py +20 -10
- langchain_agentx/session/agent_session.py +18 -2
- {langchain_agentx_python-0.3.2.dist-info → langchain_agentx_python-0.3.4.dist-info}/METADATA +1 -1
- {langchain_agentx_python-0.3.2.dist-info → langchain_agentx_python-0.3.4.dist-info}/RECORD +15 -15
- {langchain_agentx_python-0.3.2.dist-info → langchain_agentx_python-0.3.4.dist-info}/LICENSE +0 -0
- {langchain_agentx_python-0.3.2.dist-info → langchain_agentx_python-0.3.4.dist-info}/WHEEL +0 -0
- {langchain_agentx_python-0.3.2.dist-info → langchain_agentx_python-0.3.4.dist-info}/top_level.txt +0 -0
|
@@ -20,6 +20,8 @@ from .stages import (
|
|
|
20
20
|
ToolResultBudgetStage,
|
|
21
21
|
)
|
|
22
22
|
from .types import (
|
|
23
|
+
CompactProgress,
|
|
24
|
+
CompactProgressCallback,
|
|
23
25
|
CompactionConfig,
|
|
24
26
|
ContextCompactionContext,
|
|
25
27
|
StageResult,
|
|
@@ -40,6 +42,8 @@ __all__ = [
|
|
|
40
42
|
"active_compaction_settings",
|
|
41
43
|
"AutocompactLlmPromptKind",
|
|
42
44
|
"AutocompactStage",
|
|
45
|
+
"CompactProgress",
|
|
46
|
+
"CompactProgressCallback",
|
|
43
47
|
"CompactionBlockingGuard",
|
|
44
48
|
"CompactionConfig",
|
|
45
49
|
"CompactionSettings",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
注意:
|
|
13
13
|
- 不修改 `should_end` / `finish_reason` 等 exit 字段;只产出 `messages` 增量补丁。
|
|
14
|
+
- `arun` 方法为 async 版本,支持进度回调(供 /compact 命令使用)。
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
from __future__ import annotations
|
|
@@ -19,7 +20,7 @@ from typing import Any, Dict, Mapping
|
|
|
19
20
|
|
|
20
21
|
from .pipeline import ContextPipeline
|
|
21
22
|
from .settings import CompactionSettings
|
|
22
|
-
from .types import ContextCompactionContext
|
|
23
|
+
from .types import CompactProgressCallback, ContextCompactionContext
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class LoopContextCompaction:
|
|
@@ -42,6 +43,26 @@ class LoopContextCompaction:
|
|
|
42
43
|
ctx = ContextCompactionContext.from_state(state, model)
|
|
43
44
|
return self._pipeline.run(state, ctx)
|
|
44
45
|
|
|
46
|
+
async def arun(
|
|
47
|
+
self,
|
|
48
|
+
state: Mapping[str, Any],
|
|
49
|
+
model: Any | None = None,
|
|
50
|
+
progress_callback: CompactProgressCallback = None,
|
|
51
|
+
) -> Dict[str, Any]:
|
|
52
|
+
"""Async 版本(供 /compact 命令使用),支持进度回调。
|
|
53
|
+
|
|
54
|
+
参数:
|
|
55
|
+
state: LangGraph state
|
|
56
|
+
model: 可选的 LLM model(供 autocompact 使用)
|
|
57
|
+
progress_callback: 进度回调函数,接收 CompactProgress 对象
|
|
58
|
+
|
|
59
|
+
返回:
|
|
60
|
+
与 run() 相同的补丁格式
|
|
61
|
+
"""
|
|
62
|
+
ctx = ContextCompactionContext.from_state(state, model)
|
|
63
|
+
ctx.progress_callback = progress_callback
|
|
64
|
+
return await self._pipeline.arun(state, ctx)
|
|
65
|
+
|
|
45
66
|
|
|
46
67
|
_default_loop_context_compaction: LoopContextCompaction | None = None
|
|
47
68
|
|
|
@@ -30,7 +30,7 @@ from .stages import (
|
|
|
30
30
|
ToolResultBudgetStage,
|
|
31
31
|
)
|
|
32
32
|
from .stages.base import CompactionStage
|
|
33
|
-
from .types import ContextCompactionContext
|
|
33
|
+
from .types import CompactProgress, ContextCompactionContext
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class DefaultCompactionStages:
|
|
@@ -120,6 +120,86 @@ class ContextPipeline:
|
|
|
120
120
|
return {}
|
|
121
121
|
return patch
|
|
122
122
|
|
|
123
|
+
async def arun(
|
|
124
|
+
self,
|
|
125
|
+
state: dict[str, Any],
|
|
126
|
+
ctx: ContextCompactionContext,
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
"""Async 版本:每个 stage 完成后 emit 进度(供 /compact 进度条使用)。
|
|
129
|
+
|
|
130
|
+
与 run() 的区别:
|
|
131
|
+
- 走 async 路径,支持 AutocompactStage 的流式 LLM 调用。
|
|
132
|
+
- 通过 ctx.progress_callback 回调进度更新。
|
|
133
|
+
"""
|
|
134
|
+
messages = state.get("messages")
|
|
135
|
+
if not isinstance(messages, list) or not messages:
|
|
136
|
+
return {}
|
|
137
|
+
|
|
138
|
+
s = self._settings_override or get_active_compaction_settings()
|
|
139
|
+
estimator = TokenEstimator(num_chars_per_token=s.num_chars_per_token)
|
|
140
|
+
original_input = list(messages)
|
|
141
|
+
current = original_input
|
|
142
|
+
guard = CompactionBlockingGuard(settings=s)
|
|
143
|
+
tw = guard.calculate_token_warning_state(original_input)
|
|
144
|
+
tokens_before = estimator.estimate_messages_tokens(original_input)
|
|
145
|
+
stages_meta: list[dict[str, Any]] = []
|
|
146
|
+
total_freed = 0
|
|
147
|
+
n = len(self._stages)
|
|
148
|
+
|
|
149
|
+
for i, stage in enumerate(self._stages):
|
|
150
|
+
if ctx.progress_callback:
|
|
151
|
+
ctx.progress_callback(
|
|
152
|
+
CompactProgress(
|
|
153
|
+
stage=stage.name,
|
|
154
|
+
stage_index=i,
|
|
155
|
+
total_stages=n,
|
|
156
|
+
percent=int(i / n * 100),
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
# autocompact 走 async(内部 streaming),其余走 sync
|
|
160
|
+
sr = await stage.arun(current, ctx)
|
|
161
|
+
total_freed += sr.tokens_freed
|
|
162
|
+
stages_meta.append(
|
|
163
|
+
{
|
|
164
|
+
"name": stage.name,
|
|
165
|
+
"tokens_freed": sr.tokens_freed,
|
|
166
|
+
"extra": dict(sr.extra),
|
|
167
|
+
}
|
|
168
|
+
)
|
|
169
|
+
current = sr.messages
|
|
170
|
+
ctx.snip_tokens_freed_carry += sr.tokens_freed
|
|
171
|
+
|
|
172
|
+
if ctx.progress_callback:
|
|
173
|
+
ctx.progress_callback(
|
|
174
|
+
CompactProgress(
|
|
175
|
+
stage="done",
|
|
176
|
+
stage_index=n,
|
|
177
|
+
total_stages=n,
|
|
178
|
+
percent=100,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
patch: dict[str, Any] = {}
|
|
183
|
+
if current is not original_input:
|
|
184
|
+
patch["messages"] = current
|
|
185
|
+
|
|
186
|
+
tokens_after = estimator.estimate_messages_tokens(current)
|
|
187
|
+
if s.emit_compaction_pipeline_meta and (
|
|
188
|
+
"messages" in patch or total_freed > 0
|
|
189
|
+
):
|
|
190
|
+
patch["compaction_pipeline_meta"] = {
|
|
191
|
+
"estimated_tokens_before": tokens_before,
|
|
192
|
+
"estimated_tokens_after": tokens_after,
|
|
193
|
+
"tokens_freed_sum": total_freed,
|
|
194
|
+
"snip_tokens_freed_carry_final": ctx.snip_tokens_freed_carry,
|
|
195
|
+
"token_warning_level": tw.level,
|
|
196
|
+
"stages": stages_meta,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if not patch:
|
|
200
|
+
return {}
|
|
201
|
+
return patch
|
|
202
|
+
|
|
123
203
|
|
|
124
204
|
__all__ = [
|
|
125
205
|
"ContextPipeline",
|
|
@@ -13,6 +13,7 @@ Autocompact 阶段:超阈值时折叠前缀消息(对齐 CC `deps.autocompac
|
|
|
13
13
|
|
|
14
14
|
注意:
|
|
15
15
|
- `query_source in autocompact_skip_query_sources`(如 `compact`)时跳过,防递归压缩。
|
|
16
|
+
- `arun()` 方法为 async 版本,支持流式 LLM 调用与进度回调。
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
19
|
from __future__ import annotations
|
|
@@ -25,7 +26,7 @@ from ..blocking_guard import CompactionBlockingGuard
|
|
|
25
26
|
from ..message_utils import stringify_content, total_estimated_tokens_for_messages
|
|
26
27
|
from ...prompt.compact import build_autocompact_llm_instruction_prompt, get_compact_user_summary_message
|
|
27
28
|
from ..settings import CompactionSettings, get_active_compaction_settings
|
|
28
|
-
from ..types import ContextCompactionContext, StageResult
|
|
29
|
+
from ..types import CompactProgress, ContextCompactionContext, StageResult
|
|
29
30
|
from .base import CompactionStage
|
|
30
31
|
|
|
31
32
|
|
|
@@ -105,6 +106,70 @@ class AutocompactStage(CompactionStage):
|
|
|
105
106
|
extra={"autocompact_mode": mode, "folded_messages": len(head)},
|
|
106
107
|
)
|
|
107
108
|
|
|
109
|
+
async def arun(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
110
|
+
"""Async 版本:LLM 摘要使用 astream 流式生成,支持进度回调。"""
|
|
111
|
+
s = self._effective_settings()
|
|
112
|
+
if ctx.query_source in s.autocompact_skip_query_sources:
|
|
113
|
+
return StageResult(messages=messages, tokens_freed=0, extra={"autocompact_skipped": "query_source"})
|
|
114
|
+
|
|
115
|
+
total = total_estimated_tokens_for_messages(messages, s.num_chars_per_token)
|
|
116
|
+
threshold = int(s.default_max_context_tokens * s.default_compress_trigger_fraction)
|
|
117
|
+
slack = ctx.snip_tokens_freed_carry if s.autocompact_snip_carry_threshold_slack else 0
|
|
118
|
+
if total <= threshold + slack:
|
|
119
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
120
|
+
|
|
121
|
+
# 跳过所有 SystemMessages(不保留),对齐 SystemMessage 单一来源原则
|
|
122
|
+
idx = 0
|
|
123
|
+
while idx < len(messages) and isinstance(messages[idx], SystemMessage):
|
|
124
|
+
idx += 1
|
|
125
|
+
rest = messages[idx:]
|
|
126
|
+
k = max(1, s.default_compress_keep_recent_messages)
|
|
127
|
+
if len(rest) <= k:
|
|
128
|
+
return StageResult(messages=messages, tokens_freed=0)
|
|
129
|
+
|
|
130
|
+
head = rest[:-k]
|
|
131
|
+
tail = rest[-k:]
|
|
132
|
+
|
|
133
|
+
block_llm = CompactionBlockingGuard(settings=s).should_block_compact_llm(messages)
|
|
134
|
+
if (
|
|
135
|
+
s.autocompact_use_llm_when_available
|
|
136
|
+
and ctx.model is not None
|
|
137
|
+
and not block_llm
|
|
138
|
+
):
|
|
139
|
+
summary = await _astream_summary(ctx.model, s, head, ctx.progress_callback)
|
|
140
|
+
if summary:
|
|
141
|
+
folded = [
|
|
142
|
+
HumanMessage(
|
|
143
|
+
content=get_compact_user_summary_message(
|
|
144
|
+
summary,
|
|
145
|
+
recent_messages_preserved=True,
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
] + tail
|
|
149
|
+
new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
|
|
150
|
+
return StageResult(
|
|
151
|
+
messages=folded,
|
|
152
|
+
tokens_freed=max(0, total - new_total),
|
|
153
|
+
extra={"autocompact_mode": "llm", "folded_messages": len(head)},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# fallback: prune
|
|
157
|
+
folded = [
|
|
158
|
+
HumanMessage(
|
|
159
|
+
content=(
|
|
160
|
+
f"[Context autocompacted: {len(head)} earlier messages omitted; "
|
|
161
|
+
"details dropped for token budget.]"
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
] + tail
|
|
165
|
+
new_total = total_estimated_tokens_for_messages(folded, s.num_chars_per_token)
|
|
166
|
+
mode = "prune_blocked_llm" if block_llm else "prune"
|
|
167
|
+
return StageResult(
|
|
168
|
+
messages=folded,
|
|
169
|
+
tokens_freed=max(0, total - new_total),
|
|
170
|
+
extra={"autocompact_mode": mode, "folded_messages": len(head)},
|
|
171
|
+
)
|
|
172
|
+
|
|
108
173
|
|
|
109
174
|
def _resolve_autocompact_llm_instruction(s: CompactionSettings) -> str:
|
|
110
175
|
if s.autocompact_llm_instructions_override is not None:
|
|
@@ -139,3 +204,50 @@ def _invoke_summary(model: Any, s: CompactionSettings, head: list[Any]) -> str |
|
|
|
139
204
|
return None
|
|
140
205
|
text = stringify_content(getattr(resp, "content", None))
|
|
141
206
|
return text if text else None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
async def _astream_summary(
|
|
210
|
+
model: Any, s: CompactionSettings, head: list[Any], progress_callback: Any = None
|
|
211
|
+
) -> str | None:
|
|
212
|
+
"""流式生成摘要,每个 chunk 触发 progress callback。
|
|
213
|
+
|
|
214
|
+
进度计算:
|
|
215
|
+
- autocompact 占 80%-100%,用 chunk 数做粗略进度。
|
|
216
|
+
- 每收到约 10 个 chunk 增加 2%,上限 99%。
|
|
217
|
+
"""
|
|
218
|
+
cap = max(256, s.autocompact_llm_excerpt_chars_per_message)
|
|
219
|
+
parts: list[str] = []
|
|
220
|
+
for m in head:
|
|
221
|
+
label = getattr(m, "type", type(m).__name__)
|
|
222
|
+
chunk = stringify_content(getattr(m, "content", None))[:cap]
|
|
223
|
+
parts.append(f"{label}: {chunk}")
|
|
224
|
+
body = "\n\n---\n\n".join(parts)
|
|
225
|
+
excerpt_msg = HumanMessage(
|
|
226
|
+
content="[Conversation excerpt to summarize]\n\n" + body
|
|
227
|
+
)
|
|
228
|
+
instruction = _resolve_autocompact_llm_instruction(s)
|
|
229
|
+
instruction_msg = HumanMessage(content=instruction)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
chunks: list[str] = []
|
|
233
|
+
async for chunk in model.astream([excerpt_msg, instruction_msg]):
|
|
234
|
+
token = stringify_content(getattr(chunk, "content", None))
|
|
235
|
+
if token:
|
|
236
|
+
chunks.append(token)
|
|
237
|
+
if progress_callback:
|
|
238
|
+
# autocompact 占 80%-100%,用 chunk 数做粗略进度
|
|
239
|
+
pct = min(80 + len(chunks) * 2 // 10, 99)
|
|
240
|
+
from ..types import CompactProgress
|
|
241
|
+
|
|
242
|
+
progress_callback(
|
|
243
|
+
CompactProgress(
|
|
244
|
+
stage="autocompact",
|
|
245
|
+
stage_index=4,
|
|
246
|
+
total_stages=5,
|
|
247
|
+
percent=pct,
|
|
248
|
+
detail="generating summary...",
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
return "".join(chunks) or None
|
|
252
|
+
except Exception:
|
|
253
|
+
return None
|
|
@@ -30,3 +30,12 @@ class CompactionStage(ABC):
|
|
|
30
30
|
@abstractmethod
|
|
31
31
|
def run(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
32
32
|
"""输入当前消息列表,返回更新后的列表与可选 tokens_freed。"""
|
|
33
|
+
|
|
34
|
+
async def arun(self, messages: list[Any], ctx: ContextCompactionContext) -> StageResult:
|
|
35
|
+
"""Async 版本,默认委托 sync run()。AutocompactStage 覆盖此方法。
|
|
36
|
+
|
|
37
|
+
子类覆盖说明:
|
|
38
|
+
- 仅 AutocompactStage 需要覆盖以支持流式 LLM 调用。
|
|
39
|
+
- 其他阶段继承此默认实现即可。
|
|
40
|
+
"""
|
|
41
|
+
return self.run(messages, ctx)
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
|
|
12
12
|
注意:
|
|
13
13
|
- `TokenWarningState` 供 `blocking_guard` 与 `compaction_pipeline_meta` 观测对齐 CC token 告警语义。
|
|
14
|
+
- `CompactProgress` 用于 /compact 进度条回调,CLI 通过 progress_callback 接收进度更新。
|
|
14
15
|
"""
|
|
15
16
|
|
|
16
17
|
from __future__ import annotations
|
|
17
18
|
|
|
19
|
+
from collections.abc import Callable
|
|
18
20
|
from dataclasses import dataclass, field
|
|
19
21
|
from typing import Any, Literal, Mapping
|
|
20
22
|
|
|
@@ -37,6 +39,7 @@ class ContextCompactionContext:
|
|
|
37
39
|
snip_tokens_freed_carry: int = 0
|
|
38
40
|
query_source: str | None = None
|
|
39
41
|
config: CompactionConfig = field(default_factory=CompactionConfig)
|
|
42
|
+
progress_callback: CompactProgressCallback = None
|
|
40
43
|
|
|
41
44
|
@classmethod
|
|
42
45
|
def from_state(
|
|
@@ -70,8 +73,32 @@ class TokenWarningState:
|
|
|
70
73
|
level: TokenWarningLevel
|
|
71
74
|
|
|
72
75
|
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class CompactProgress:
|
|
78
|
+
"""压缩进度通知(供 CLI 进度条消费)。
|
|
79
|
+
|
|
80
|
+
属性:
|
|
81
|
+
stage: 当前阶段名(tool_result_budget / snip / microcompact / collapse / autocompact / done)
|
|
82
|
+
stage_index: 0-based 阶段序号
|
|
83
|
+
total_stages: 总阶段数(默认 5)
|
|
84
|
+
percent: 0-100 整体进度百分比
|
|
85
|
+
detail: 可选描述(如 "generating summary...")
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
stage: str
|
|
89
|
+
stage_index: int
|
|
90
|
+
total_stages: int
|
|
91
|
+
percent: int
|
|
92
|
+
detail: str | None = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
CompactProgressCallback = Callable[[CompactProgress], None] | None
|
|
96
|
+
|
|
97
|
+
|
|
73
98
|
__all__ = [
|
|
74
99
|
"CompactionConfig",
|
|
100
|
+
"CompactProgress",
|
|
101
|
+
"CompactProgressCallback",
|
|
75
102
|
"ContextCompactionContext",
|
|
76
103
|
"StageResult",
|
|
77
104
|
"TokenWarningLevel",
|
|
@@ -35,7 +35,7 @@ from typing import (
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
from langchain_core.language_models.chat_models import BaseChatModel
|
|
38
|
-
from langchain_core.messages import SystemMessage
|
|
38
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
39
39
|
from langchain_core.runnables import RunnableConfig
|
|
40
40
|
from langgraph._internal._runnable import RunnableCallable
|
|
41
41
|
from langgraph.constants import END, START
|
|
@@ -92,11 +92,8 @@ from ...memory.memdir.agent_memory import (
|
|
|
92
92
|
)
|
|
93
93
|
from ...memory.memdir.runtime import MemoryPromptBootstrap
|
|
94
94
|
from ...memory.memdir.extractor import MemoryExtractionCoordinator
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
SessionMemoryManager,
|
|
98
|
-
make_session_memory_section,
|
|
99
|
-
)
|
|
95
|
+
# Session memory imports use lazy imports to avoid circular dependency
|
|
96
|
+
# SessionMemoryCompactBridge uses lazy import to avoid circular dependency with memory.session.session_memory
|
|
100
97
|
from ..prompt import SystemPromptSection, build_effective_system_prompt
|
|
101
98
|
from ..prompt.session_context import get_default_session_context_sections
|
|
102
99
|
from ...tools.agent.prompt import build_explore_plan_guidance
|
|
@@ -263,9 +260,10 @@ class LoopGraphBuilder:
|
|
|
263
260
|
)
|
|
264
261
|
self._model_profile_registry = self._raw_capabilities.model_profile_registry
|
|
265
262
|
self._model_context_resolver = ModelContextResolver()
|
|
266
|
-
|
|
267
|
-
self._session_memory_compact_bridge: SessionMemoryCompactBridge | None = None
|
|
263
|
+
# Lazy imports to avoid circular dependency with memory.session
|
|
268
264
|
if self._raw_enable_session_memory:
|
|
265
|
+
from ...memory.session import SessionMemoryManager, make_session_memory_section
|
|
266
|
+
from ...memory.session.compact_bridge import SessionMemoryCompactBridge
|
|
269
267
|
session_memory_path = self._workspace_cfg.session_memory_path(session_id)
|
|
270
268
|
self._session_memory_manager = SessionMemoryManager(
|
|
271
269
|
session_memory_path=session_memory_path,
|
|
@@ -277,6 +275,9 @@ class LoopGraphBuilder:
|
|
|
277
275
|
extraction_state=self._session_memory_manager.extraction_state,
|
|
278
276
|
)
|
|
279
277
|
self._dynamic_sections.append(make_session_memory_section(session_memory_path))
|
|
278
|
+
else:
|
|
279
|
+
self._session_memory_manager = None
|
|
280
|
+
self._session_memory_compact_bridge = None
|
|
280
281
|
if self._raw_include_default_session_context:
|
|
281
282
|
self._dynamic_sections.extend(
|
|
282
283
|
get_default_session_context_sections(
|
|
@@ -1113,7 +1114,10 @@ class LoopGraphBuilder:
|
|
|
1113
1114
|
hint = skill_prefetch_provider._build_listing_hint(scope=skill_init_scope)
|
|
1114
1115
|
if hint is None:
|
|
1115
1116
|
return {}
|
|
1116
|
-
|
|
1117
|
+
# 对齐 CC:skill_listing 是 UserMessage,不是 SystemMessage
|
|
1118
|
+
# CC src/utils/messages.ts: createUserMessage({ isMeta: true, ... })
|
|
1119
|
+
# CC 中包裹在 <system-reminder> 中,但 role 是 "user"
|
|
1120
|
+
return {"messages": [HumanMessage(content=f"[skill_listing] {hint.summary}")]}
|
|
1117
1121
|
|
|
1118
1122
|
graph_inner.add_node(
|
|
1119
1123
|
"skill_init",
|
|
@@ -22,7 +22,7 @@ import os
|
|
|
22
22
|
from typing import Any, Dict, List
|
|
23
23
|
|
|
24
24
|
from langchain.agents.structured_output import ResponseFormat
|
|
25
|
-
from langchain_core.messages import AIMessage, SystemMessage
|
|
25
|
+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
|
26
26
|
from langchain_core.runnables import Runnable
|
|
27
27
|
from langchain_core.runnables.config import RunnableConfig
|
|
28
28
|
from langgraph.types import Command
|
|
@@ -97,6 +97,9 @@ def _messages_for_model_invoke(req: Any) -> list[Any]:
|
|
|
97
97
|
|
|
98
98
|
对齐 CC normalizeMessagesForAPI:剥离 state["messages"] 中的违规 SystemMessage,
|
|
99
99
|
确保 OpenAI 兼容 API "System message must be at the beginning" 约束。
|
|
100
|
+
|
|
101
|
+
L2 兜底:确保至少一条 HumanMessage 存在(对齐 CC prependUserContext 的防御层),
|
|
102
|
+
防止 compaction 路径遗漏导致 provider 400 "No user query found"。
|
|
100
103
|
"""
|
|
101
104
|
messages = list(req.messages or [])
|
|
102
105
|
s = get_active_compaction_settings()
|
|
@@ -138,6 +141,18 @@ def _messages_for_model_invoke(req: Any) -> list[Any]:
|
|
|
138
141
|
|
|
139
142
|
messages = filtered
|
|
140
143
|
|
|
144
|
+
# L2 兜底:确保至少一条 HumanMessage 存在(防止 compaction 路径遗漏)
|
|
145
|
+
has_human = any(isinstance(m, HumanMessage) for m in messages)
|
|
146
|
+
if not has_human:
|
|
147
|
+
logger.warning(
|
|
148
|
+
"No HumanMessage found in messages after sanitization; "
|
|
149
|
+
"injecting placeholder to avoid provider 400 error."
|
|
150
|
+
)
|
|
151
|
+
messages = [
|
|
152
|
+
HumanMessage(content="[No user message available after context compaction.]"),
|
|
153
|
+
*messages,
|
|
154
|
+
]
|
|
155
|
+
|
|
141
156
|
if req.system_message:
|
|
142
157
|
return [req.system_message, *messages]
|
|
143
158
|
return messages
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
import time
|
|
20
20
|
from typing import Any
|
|
21
21
|
|
|
22
|
-
from langchain_core.messages import AIMessage, BaseMessage, SystemMessage, ToolMessage
|
|
22
|
+
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage, ToolMessage
|
|
23
23
|
|
|
24
24
|
from langchain_agentx.memory.session.prompts import (
|
|
25
25
|
DEFAULT_SESSION_MEMORY_TEMPLATE,
|
|
@@ -124,8 +124,15 @@ def adjust_index_to_preserve_api_invariants(
|
|
|
124
124
|
return 0
|
|
125
125
|
|
|
126
126
|
|
|
127
|
-
def _build_summary_message(session_memory_summary: str) ->
|
|
128
|
-
|
|
127
|
+
def _build_summary_message(session_memory_summary: str) -> HumanMessage:
|
|
128
|
+
"""Return HumanMessage summary for compaction (not SystemMessage).
|
|
129
|
+
|
|
130
|
+
对齐原则 #10「SystemPrompt 单一来源」与 CC summaryMessages: UserMessage[]:
|
|
131
|
+
- 压缩输出禁止包含 SystemMessage
|
|
132
|
+
- 压缩后第一条必须是 HumanMessage(summary)
|
|
133
|
+
- 确保 L1 sanitizer 剥离后仍有 user 消息存在
|
|
134
|
+
"""
|
|
135
|
+
return HumanMessage(
|
|
129
136
|
content=(
|
|
130
137
|
f"{SUMMARY_PREFIX}\n\n"
|
|
131
138
|
f"<session_memory_summary>\n{session_memory_summary}\n</session_memory_summary>\n"
|
|
@@ -158,17 +165,20 @@ def try_session_memory_compaction(
|
|
|
158
165
|
if cut_index == 0:
|
|
159
166
|
return None
|
|
160
167
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
# 跳过所有 leading SystemMessages(对齐原则 #10 与 autocompact.py:56-60)
|
|
169
|
+
idx = 0
|
|
170
|
+
while idx < len(messages) and isinstance(messages[idx], SystemMessage):
|
|
171
|
+
idx += 1
|
|
172
|
+
remaining = messages[idx:]
|
|
173
|
+
if idx > 0:
|
|
174
|
+
cut_index = max(0, cut_index - idx)
|
|
175
|
+
|
|
167
176
|
tail = remaining[cut_index:]
|
|
168
177
|
if not tail:
|
|
169
178
|
return None
|
|
170
179
|
|
|
171
|
-
|
|
180
|
+
# 重建: [HumanMessage(summary), ...tail] — 不含 SystemMessage
|
|
181
|
+
rebuilt = [_build_summary_message(truncated_summary), *tail]
|
|
172
182
|
return rebuilt
|
|
173
183
|
|
|
174
184
|
|
|
@@ -202,9 +202,25 @@ class AgentSession:
|
|
|
202
202
|
def clear_context(self) -> None:
|
|
203
203
|
self._messages.clear()
|
|
204
204
|
|
|
205
|
-
async def compact_context(
|
|
205
|
+
async def compact_context(
|
|
206
|
+
self, *, progress_callback: Any = None
|
|
207
|
+
) -> tuple[int, int]:
|
|
208
|
+
"""压缩会话上下文,可选传入进度回调。
|
|
209
|
+
|
|
210
|
+
参数:
|
|
211
|
+
progress_callback: 可选的进度回调函数,接收 CompactProgress 对象
|
|
212
|
+
|
|
213
|
+
返回:
|
|
214
|
+
(before_tokens, after_tokens) 元组
|
|
215
|
+
"""
|
|
206
216
|
before_tokens = total_estimated_tokens_for_messages(self._messages, num_chars_per_token=4)
|
|
207
|
-
|
|
217
|
+
# 注意:model=None 表示不使用 LLM 摘要,仅走 prune 路径
|
|
218
|
+
# 如果需要 LLM 摘要,调用方需要通过其他方式提供 model
|
|
219
|
+
patch = await default_loop_context_compaction().arun(
|
|
220
|
+
{"messages": self._messages},
|
|
221
|
+
model=None,
|
|
222
|
+
progress_callback=progress_callback,
|
|
223
|
+
)
|
|
208
224
|
if isinstance(patch.get("messages"), list):
|
|
209
225
|
self._messages = list(patch["messages"])
|
|
210
226
|
after_tokens = total_estimated_tokens_for_messages(self._messages, num_chars_per_token=4)
|
|
@@ -21,16 +21,16 @@ langchain_agentx/loop/config/agent_loop_config.py,sha256=ZzmrFO9nIVQst9-mAICTgG0
|
|
|
21
21
|
langchain_agentx/loop/config/model_context_resolver.py,sha256=qZWDLyGlvr0YVZkvpDk72h6_Qbe0mvhWwlvTRsTfJ_E,3916
|
|
22
22
|
langchain_agentx/loop/config/runtime_settings.py,sha256=uKkwyxgx5u34qbjP1DFrIRt0EygRCtsH8tMN0kWA0Yo,1551
|
|
23
23
|
langchain_agentx/loop/config/token_estimator.py,sha256=2-Rp2Bdc5a-60D1nm88z4-sNzS25nv8hkKIfaBqE78E,4883
|
|
24
|
-
langchain_agentx/loop/context/__init__.py,sha256=
|
|
24
|
+
langchain_agentx/loop/context/__init__.py,sha256=VMYkkJSqjLpkx_6MqGpiASQIW-QDF8L7OwffSn_CnoI,2000
|
|
25
25
|
langchain_agentx/loop/context/blocking_guard.py,sha256=Rmt5qZdE8Zf0bt-Q6WwMSfG869772PE8w1mNPvBQRCQ,3789
|
|
26
|
-
langchain_agentx/loop/context/compaction_service.py,sha256=
|
|
26
|
+
langchain_agentx/loop/context/compaction_service.py,sha256=nvLG8dfEqoJouuE9BHczmIZEb7pxU5lcuhIJJ8Pi5Sg,3004
|
|
27
27
|
langchain_agentx/loop/context/message_utils.py,sha256=6qIwtHeh63OlMqOnPUpEEWRknU9KJTGkcY1v3tz6feY,2010
|
|
28
|
-
langchain_agentx/loop/context/pipeline.py,sha256=
|
|
28
|
+
langchain_agentx/loop/context/pipeline.py,sha256=P_3oK_dfnx1BT5nCPYo3cmD94QhZP9xeiSOXv3-oCeU,7418
|
|
29
29
|
langchain_agentx/loop/context/settings.py,sha256=cbfb5RyBupsIY1CF-QZ8OsuIrpdx9QzNwXRAezkMl8U,5918
|
|
30
|
-
langchain_agentx/loop/context/types.py,sha256=
|
|
30
|
+
langchain_agentx/loop/context/types.py,sha256=HIiuNlOl47CmBlRr0yp-NCGaZ29omb3z2VRWq8mIsTI,3194
|
|
31
31
|
langchain_agentx/loop/context/stages/__init__.py,sha256=wk9CFh8_Akw8qs3Dldnx14VE1MhyF5i9Cg3phTVChhg,892
|
|
32
|
-
langchain_agentx/loop/context/stages/autocompact.py,sha256=
|
|
33
|
-
langchain_agentx/loop/context/stages/base.py,sha256=
|
|
32
|
+
langchain_agentx/loop/context/stages/autocompact.py,sha256=En8K7dkqAnueZH0mQ4eY6r8-wwY8oXikTKaXcri_Mhs,11784
|
|
33
|
+
langchain_agentx/loop/context/stages/base.py,sha256=LVfCp-DLySrTExy0J-M40tDJvzbOXxfZxXvi7XQayA8,1380
|
|
34
34
|
langchain_agentx/loop/context/stages/collapse.py,sha256=yOf3nfWZr83rq2qnhUpuTCLFHcGvF7my8klMLmfZ7jc,2971
|
|
35
35
|
langchain_agentx/loop/context/stages/microcompact.py,sha256=aTF3TjBkQ_fDx818Kfwr3d7hdx3dtAW8St3bAOiZTMg,2779
|
|
36
36
|
langchain_agentx/loop/context/stages/noop.py,sha256=UKKPeKfyDoxd9NBzjIai6OGcr-9RrraKmx2E9Vpf3vc,1069
|
|
@@ -41,7 +41,7 @@ langchain_agentx/loop/exit/exit_logic.py,sha256=TZKAOpB_9jDryX21XW5cy4LuSQnmM3KO
|
|
|
41
41
|
langchain_agentx/loop/exit/reason_codes.py,sha256=vQcsqfute1_weyxtDDWm7CR6vvt6ePED7lcGPw0OXn8,2117
|
|
42
42
|
langchain_agentx/loop/graph/__init__.py,sha256=9pUUqn7G87n3lV45iYIU24j0wcTHyCD--SSdtv3urh4,136
|
|
43
43
|
langchain_agentx/loop/graph/builtin_loop_control.py,sha256=bkf__SSMzmR_SvZWhH-QdfjWZEpnnrP29CjGjjcQPeY,6955
|
|
44
|
-
langchain_agentx/loop/graph/factory.py,sha256=
|
|
44
|
+
langchain_agentx/loop/graph/factory.py,sha256=At6trJjj5lsgG3m9g42ZTAb_i4_7R_x7DY-VkOejRZU,64999
|
|
45
45
|
langchain_agentx/loop/graph/graph_edges.py,sha256=uUCcRyI5NpZMghV6T5QmcBEO2aHZQXhhYgzaBJwhY84,32503
|
|
46
46
|
langchain_agentx/loop/hook/__init__.py,sha256=LS5AE6Flqg3btLe0MQ7qDZRjaf1AdHiZYdEgQ96vbOQ,1255
|
|
47
47
|
langchain_agentx/loop/hook/async_hook_runner.py,sha256=UIrdpgAcUumyxs7ASeJh6ehK2NSo-FDQVj9ICpuPook,2146
|
|
@@ -59,7 +59,7 @@ langchain_agentx/loop/hook/executors/prompt.py,sha256=oYwOk-acH-dopzfzNUEjpPdXGk
|
|
|
59
59
|
langchain_agentx/loop/injection/__init__.py,sha256=rEiAXYqdr_Ry8mhHBu6xZKLypnyJOxcxNe6sJns1rnc,362
|
|
60
60
|
langchain_agentx/loop/injection/dedup.py,sha256=5wv-Tb8HUean9C82VazVIkTkQdIZi9gewMABukiclpI,2548
|
|
61
61
|
langchain_agentx/loop/model/__init__.py,sha256=8wOiwa2Yvh7QdR6ilhk4faI7_iwNT6QSEFgewJNhhOw,57
|
|
62
|
-
langchain_agentx/loop/model/model_node.py,sha256=
|
|
62
|
+
langchain_agentx/loop/model/model_node.py,sha256=TVl1Hf9UZeipIU_I4dTA3uEulezn_eNPiMI5jynTOtA,28899
|
|
63
63
|
langchain_agentx/loop/model/model_nodes.py,sha256=4nctxTm3FHdMQ35Tp_y2hUm4q9AYa9HiMI9xXheFAYM,26955
|
|
64
64
|
langchain_agentx/loop/model/orphan_tool_results.py,sha256=sQd2_Y0y9wSLsMZnkQybuEp1gSYGpMWYkwt19LVzY-8,1299
|
|
65
65
|
langchain_agentx/loop/model/retrier.py,sha256=XGDTsTEJDs6z_tR5vN57szUR7Uw2EJgVY5qzQWf1Yas,10045
|
|
@@ -108,7 +108,7 @@ langchain_agentx/memory/memdir/runtime.py,sha256=dyD79SoeTaphPswLmgac9DmTGOKteO-
|
|
|
108
108
|
langchain_agentx/memory/memdir/scan.py,sha256=QB5oEYDoQKYeMUbnoj0CKL-a-eWzumapXH8MvlwROwI,4180
|
|
109
109
|
langchain_agentx/memory/memdir/types.py,sha256=9vBJ1uKFoyHDNemphubM4gypQkr_vqKNtbxbE2O8Oqk,2776
|
|
110
110
|
langchain_agentx/memory/session/__init__.py,sha256=IaLfgs7ek9T7tCGsGA7llTaqDpmEVAMROprS33sD2WU,2358
|
|
111
|
-
langchain_agentx/memory/session/compact_bridge.py,sha256=
|
|
111
|
+
langchain_agentx/memory/session/compact_bridge.py,sha256=R0y4ZdfTyJ3IeO4dJVydwqdIJo-Q8Yzf5A2OVKCZ0rM,7276
|
|
112
112
|
langchain_agentx/memory/session/prompts.py,sha256=4SzAnS-rZSNsBfhhx8fDTzZt8AO3uf16cVYdF-yJVm4,6885
|
|
113
113
|
langchain_agentx/memory/session/session_memory.py,sha256=fdTCv8_oMQBy8VpORg-Wk0t1P8EpiaItM8x8y4WqqP4,10420
|
|
114
114
|
langchain_agentx/observability/__init__.py,sha256=pYNHLfbVnWoOyWXEjz5-S-u2MPufHntWG_H7ihV2EV4,1539
|
|
@@ -157,7 +157,7 @@ langchain_agentx/provider/env.py,sha256=bmELhixPGoJ3b0rtgEPCB512gJF2cVS00bRQfclk
|
|
|
157
157
|
langchain_agentx/provider/model_profile.py,sha256=3m3iVtYYuBLMADRu-36L_JwODjH8vYe1BfkuEMNkKJc,6245
|
|
158
158
|
langchain_agentx/provider/openai.py,sha256=mNPRPhxtkKRhBVnXqJD8r1Vmo22HNM-hT-Di_VNL-ng,2996
|
|
159
159
|
langchain_agentx/session/__init__.py,sha256=UO-bRS9oYSzdYoavow4FSXdY7qzw5O3VoUH9_FTzGTI,497
|
|
160
|
-
langchain_agentx/session/agent_session.py,sha256=
|
|
160
|
+
langchain_agentx/session/agent_session.py,sha256=jMc6vwsv8wNa0B_3HtTyXpJAH72xuR0D4rqQJwlPlRs,14352
|
|
161
161
|
langchain_agentx/session/conversation_factory.py,sha256=Nhi_8iH0D8-NmfsJqgnKxIEa8Y32qfOFpjRNqWVksEQ,2992
|
|
162
162
|
langchain_agentx/session/conversation_recovery.py,sha256=U0M3p4NKaSz6heEvNzlzehC8oTX9EB2Vz-QdYhhyk0U,5813
|
|
163
163
|
langchain_agentx/session/conversation_session.py,sha256=phzXQZMfNCOzOa9dDlaA0zaPCOVuHiu6_EXmg3zpuok,8007
|
|
@@ -444,8 +444,8 @@ langchain_agentx/workspace/config.py,sha256=K2XaAvlkBr5aA2mgH5zNxfAZg-LGmwwDVNcE
|
|
|
444
444
|
langchain_agentx/workspace/path_key_normalizer.py,sha256=ICk9uGiiNFH-zaXJG3_vx91uOpgWmgOPIjJoSYevKOc,1113
|
|
445
445
|
langchain_agentx/workspace/resolver.py,sha256=9-wRLLIC7KJCqu9i8nAL-gJ9lIWsTPOpjnZnZEGLunk,6281
|
|
446
446
|
langchain_agentx/workspace/validators.py,sha256=tQt-6TOcL8Fw7Ig5ebA9S7vGWh1rby920eFW6x8Tk9E,1439
|
|
447
|
-
langchain_agentx_python-0.3.
|
|
448
|
-
langchain_agentx_python-0.3.
|
|
449
|
-
langchain_agentx_python-0.3.
|
|
450
|
-
langchain_agentx_python-0.3.
|
|
451
|
-
langchain_agentx_python-0.3.
|
|
447
|
+
langchain_agentx_python-0.3.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
448
|
+
langchain_agentx_python-0.3.4.dist-info/METADATA,sha256=sHEp6L8XfBnBH_0kpxtfCaWXJexcX2GqjmtwdeExYUU,20832
|
|
449
|
+
langchain_agentx_python-0.3.4.dist-info/WHEEL,sha256=51RkbunBAw4BWsgaQWTpPhg4Diwp3c9P5iaLk67Hdtg,92
|
|
450
|
+
langchain_agentx_python-0.3.4.dist-info/top_level.txt,sha256=Ge284pniNt8xea0OLk2o9o32GqVpDhOYk20fwE-0xxA,17
|
|
451
|
+
langchain_agentx_python-0.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{langchain_agentx_python-0.3.2.dist-info → langchain_agentx_python-0.3.4.dist-info}/top_level.txt
RENAMED
|
File without changes
|