fix: 经验库命中数算错(单条恒显示 0)+ 蒸馏前缀误杀 + 命中卡片带摘要
- _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 条同类历史经验,已注入参考:<摘要>」
This commit is contained in:
+1
-1
@@ -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` 后关流
|
||||
|
||||
+30
-13
@@ -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("经验已写入记忆库,随事件流提示")
|
||||
|
||||
Reference in New Issue
Block a user