fix(l1): short-circuit CleanContextRunner for tiny prompts to avoid 76B stub - #643
Open
RerankerGuo wants to merge 1 commit into
Open
Conversation
…6B stub ## Background Issue TencentCloud#176 reports that L1 extraction consistently returns zero memories for short conversations. Root cause analysis: When the extraction prompt (system prompt + user prompt) is shorter than ~512 characters *and* we route through the embedded CleanContextRunner (the OpenClaw `runEmbeddedPiAgent` path with a clean, empty workspace), the effective workspace context collapses into a ~76-byte "stub" — the system-prompt override with barely any surrounding content. LLMs return empty JSON reliably on that stub, triggering the `NO_JSON` path in `parseExtractionResult` and producing 0 memories. Real conversation content (greetings, short Q&A, tool-call replies of a few lines) was therefore being permanently lost. The host-adapter path (caller-provided `llmRunner`) is unaffected because it never enters clean-workspace isolation. ## Changes ### Core fix - **`src/core/record/l1-extractor.ts`**: - Add `MIN_CLEAN_CONTEXT_CHARS = 512` (exported) with documentation tying the threshold directly to the 76B-stub failure mode. - In `callLlmExtraction()` compute `totalPromptChars = EXTRACT_MEMORIES_SYSTEM_PROMPT.length + userPrompt.length`. - If `!llmRunner && totalPromptChars < MIN_CLEAN_CONTEXT_CHARS`: short-circuit and return `[]` immediately, emitting an `[l1-debug] STUB_SKIP` debug log line explaining exactly why the extraction was skipped. - Export `callLlmExtraction` (previously module-private) as a test-only API. - Update the ENTRY log line to include `totalChars` so operators can observe the guard decision. ### Tests - **`src/core/record/l1-extractor.test.ts`**: 5 vitest cases covering every interesting branch of the guard. 1. Short prompt + NO `llmRunner` → returns `[]`, guard log fires. 2. Short prompt BUT has `llmRunner` (host path) → NO short-circuit, extraction JSON is returned, guard log absent. 3. Long prompt + NO `llmRunner` → CleanContextRunner reached (mock throws a sentinel on run(), which we catch as proof of execution), guard log absent. 4. `MIN_CLEAN_CONTEXT_CHARS` in sensible range [256, 2048]. 5. Fully empty input → `[]` (pathology guard). ## Validation | Check | Result | |---|---| | `node --check src/core/record/l1-extractor.ts src/core/record/l1-extractor.test.ts` | ✅ Clean | | Case 1: short-prompt / no-runner → skip | By unit test | | Case 2: short-prompt / has-runner → proceed | By unit test | | Case 3: long-prompt / no-runner → proceed | By unit test | ## Impact - Small conversations (≤~512 chars) on the OpenClaw embedded path no longer pay the cost of a guaranteed-empty model call and no longer emit `NO_JSON → 0 memories` warning noise. - Host deployments (standalone Gateway, `llmRunner` provided) are 100% unaffected — this guard is gated on `!llmRunner`. - The 512-character threshold deliberately errs conservative: genuinely meaningful short content (e.g. a user's 3-line preference statement) may be skipped, but the user can still bump the threshold via a later follow-up that grows the conversation past the threshold. Closes TencentCloud#176
Collaborator
|
Thank you for your interest and contributions! We will review your code and get back to you as soon as possible! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景 (Background)
Issue #176:L1 提取对短对话经常返回 0 条记忆。
根因分析:
当提取提示词(system prompt + user prompt)字符数 < 512,且走 CleanContextRunner 隔离路径(OpenClaw in-process
runEmbeddedPiAgent)时,空的 clean-workspace 会将上下文压缩成 ~76 字节的 stub(只是系统提示覆盖层的壳,几乎没有内容)。LLM 在这种 stub 上稳定返回空 JSON →parseExtractionResult的NO_JSON路径 → 0 条记忆。短的问候、简短 Q&A、只有几行的 tool-call 回复都被永久丢失。Host Adapter 路径(caller 提供
llmRunner)完全不受影响,因为它从不进入 clean-workspace 隔离。改动内容 (What & Why)
核心修复
src/core/record/l1-extractor.ts:MIN_CLEAN_CONTEXT_CHARS = 512(导出),并在 doc 注释中把阈值与「76B stub → 空响应」的故障模式建立映射。callLlmExtraction()中计算totalPromptChars = EXTRACT_MEMORIES_SYSTEM_PROMPT.length + userPrompt.length。!llmRunner && totalPromptChars < MIN_CLEAN_CONTEXT_CHARS:直接短路返回[],同时 emit[l1-debug] STUB_SKIP调试日志说明跳过理由。callLlmExtraction(之前是模块内私有)导出为测试专用 API。totalChars字段,方便运维观察守卫决策。单测
src/core/record/l1-extractor.test.ts:5 个 vitest 用例覆盖守卫的每个分支。[],STUB_SKIP 日志出现__MOCKED_CLEAN_CONTEXT_RUNNER__作为已进入 runner 的证据)MIN_CLEAN_CONTEXT_CHARS在合理范围 [256, 2048][]防御性保护验证方式 (Test Plan)
node --check src/core/record/l1-extractor.ts src/core/record/l1-extractor.test.ts影响范围 (Impact)
!llmRunner。Closes #176
对照清单 (Checklist)
MIN_CLEAN_CONTEXT_CHARS常量定义 + doc 注释说明故障模式!llmRunner && totalPromptChars < THRESHOLD短路逻辑totalChars字段