From 4e71f79a1096a2ef694a9043a52857d83f8c3269 Mon Sep 17 00:00:00 2001 From: butubb <1422726308@qq.com> Date: Thu, 10 Sep 2026 13:41:10 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BB=8F=E9=AA=8C=E5=BA=93=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E6=95=B0=E7=AE=97=E9=94=99(=E5=8D=95=E6=9D=A1?= =?UTF-8?q?=E6=81=92=E6=98=BE=E7=A4=BA=200)+=20=E8=92=B8=E9=A6=8F=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E8=AF=AF=E6=9D=80=20+=20=E5=91=BD=E4=B8=AD=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E5=B8=A6=E6=91=98=E8=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _find_experiences 改为返回 (注入文本, 命中配方列表):此前卡片用 exp_ctx.count('\n- ') 计数,单条恒为 0、两条为 1(永远少 1),用户看到 「命中 0 条」误判为没参考;现用 len(列表) 得真实条数并附配方摘要 - 新增 _clean_recipe():清洗模型可能加的「操作配方:」前缀,替换旧判定 "配方" not in recipe[:50]——该判定与蒸馏提示词(要求以「操作配方」作答) 自相矛盾,模型照做即被整条丢弃(静默),是经验存不下来的主因 - doc/API.md:SSE step 说明补 🧠 伪卡片语义(N 为实际条数 + 摘要) 实测:同一指令由「命中 0 条」变为「命中 1 条同类历史经验,已注入参考:<摘要>」 --- doc/API.md | 2 +- web/agent_api.py | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/doc/API.md b/doc/API.md index 369fba6..c143a20 100644 --- a/doc/API.md +++ b/doc/API.md @@ -1193,7 +1193,7 @@ AI 可用设备列表(在线状态 + 是否有任务运行,前端据此把 b 订阅事件流(SSE,EventSource)。事件: - `event: delta` `{text, kind: content|reasoning}` — 流式文本增量 -- `event: step` `{tool, args, image?}` — 工具调用完成(MCP 步骤,image 为缩略截图) +- `event: step` `{tool, args, image?}` — 工具调用完成(MCP 步骤,image 为缩略截图)。另有两类伪卡片:`tool="🧠 经验记忆"` 表示命中历史经验(args 形如「命中 N 条同类历史经验,已注入参考:<配方摘要>」,N 为实际条数)或本轮已写入经验库 - `event: done` `{answer}` — 完成 - `event: error` `{message}` — 失败(若因 MCP Server 未启动/不可达,message 为明确文案「MCP server(8033) 不可达 …」,不再是 SDK 原始的 `Server returned an error response`) - 空闲时每 15s 发一行 `: keepalive` 注释防超时;`done`/`error` 后关流 diff --git a/web/agent_api.py b/web/agent_api.py index e374562..29a0fde 100644 --- a/web/agent_api.py +++ b/web/agent_api.py @@ -186,23 +186,24 @@ def _qualify_experience(prompt, recipe): def _find_experiences(prompt, limit=2, threshold=0.10): """按 bigram 重叠检索相似历史经验(prompt 与任务描述的字符相似度)。 - 命中的经验 hits+1(回写),让被反复参考的有效经验浮到前面。 + 返回 (注入文本, 命中的配方列表);命中的经验 hits+1(回写),让被反复 + 参考的有效经验浮到前面。配方列表供调用方展示「命中 N 条」与摘要。 """ try: if _flask_app is None: - return "" + return "", [] with _flask_app.app_context(): _ensure_exp_table() rows = db.session.execute(db.text( "SELECT id, task_prompt, recipe, hits FROM agent_experience " "WHERE recipe != '' ORDER BY hits DESC, id DESC LIMIT 50")).fetchall() except Exception: - return "" + return "", [] if not rows: - return "" + return "", [] cur = _bigrams(prompt) if not cur: - return "" + return "", [] scored = [] for row_id, task_prompt, recipe, hits in rows: sim = len(cur & _bigrams(task_prompt)) / len(cur) @@ -220,10 +221,24 @@ def _find_experiences(prompt, limit=2, threshold=0.10): db.session.commit() except Exception: pass - parts = [] - for sim, _hits, recipe, _row_id in scored[:limit]: - parts.append(f"- {recipe[:600]}") - return "\n".join(parts) + picked = [recipe for _sim, _hits, recipe, _row_id in scored[:limit]] + parts = [f"- {r[:600]}" for r in picked] + return "\n".join(parts), picked + + +def _clean_recipe(recipe): + """清洗蒸馏输出:去掉模型可能加的「操作配方:」之类前缀标签与包裹引号。 + + 旧逻辑用 `"配方" not in recipe[:50]` 直接拒绝——而蒸馏提示词本就要求以 + 「操作配方」作答,模型一旦照做就会被整条丢弃(静默),导致经验很难存下。 + 改为清洗前缀而非丢弃。 + """ + t = (recipe or "").strip() + if not t: + return "" + t = re.sub(r'^[^\n]{0,12}?配方[::]\s*', '', t, count=1).strip() + t = re.sub(r'^(操作步骤|步骤|流程)[::]\s*', '', t).strip() + return t.strip('"“”\'').strip() def _distill_experience(cfg, prompt, tool_seq): @@ -862,11 +877,13 @@ def _agent_thread(run_id, prompt, serial, cfg): stop_evt = _stop_events.get(run_id) # 经验检索:相似历史任务的操作配方注入 system(自进化记忆) - exp_ctx = _find_experiences(prompt) + exp_ctx, exp_items = _find_experiences(prompt) if exp_ctx: _log.info("命中历史经验,注入参考配方") + brief = ";".join("".join(x.split())[:16] for x in exp_items[:2]) q.put(("step", {"tool": "🧠 经验记忆", - "args": f"命中 {exp_ctx.count(chr(10) + '- ')} 条同类历史经验,已注入参考", + "args": f"命中 {len(exp_items)} 条同类历史经验,已注入参考" + + (f":{brief}" if brief else ""), "image": None})) mcp_url = getattr(getattr(agent, "s", None), "mcp_url", "") @@ -935,8 +952,8 @@ def _agent_thread(run_id, prompt, serial, cfg): # 提炼/保存失败静默(不阻塞、不影响结果),只在成功时推送 🧠 卡片。 if tool_seq: try: - recipe = _distill_experience(cfg, prompt, " -> ".join(tool_seq)) - if recipe and "配方" not in recipe[:50]: + recipe = _clean_recipe(_distill_experience(cfg, prompt, " -> ".join(tool_seq))) + if recipe: if _qualify_experience(prompt, recipe): if _save_experience(prompt, recipe, " -> ".join(tool_seq)): _log.info("经验已写入记忆库,随事件流提示")