v0.16.1: 优化Reflecting阶段说了做但没做检测逻辑,降低误判率

This commit is contained in:
thzxx
2026-07-11 23:46:55 +08:00
parent 64b7d307e7
commit d479666182
6 changed files with 64 additions and 23 deletions
+3 -3
View File
@@ -14,7 +14,7 @@
</p> </p>
<p align="center"> <p align="center">
<img src="https://img.shields.io/badge/version-v0.16.0-E8734A?style=flat-square" alt="version"> <img src="https://img.shields.io/badge/version-v0.16.1-E8734A?style=flat-square" alt="version">
<img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron"> <img src="https://img.shields.io/badge/electron-33+-47848F?style=flat-square&logo=electron" alt="electron">
<img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript"> <img src="https://img.shields.io/badge/typescript-5.7+-3178C6?style=flat-square&logo=typescript" alt="typescript">
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license"> <img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="license">
@@ -253,7 +253,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
``` ```
产出:`release/Metona Ollama Setup v0.16.0.exe` 产出:`release/Metona Ollama Setup v0.16.1.exe`
## 🛠️ 常用命令 ## 🛠️ 常用命令
@@ -501,7 +501,7 @@ npm start
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm run dist
``` ```
Output: `release/Metona Ollama Setup v0.16.0.exe` Output: `release/Metona Ollama Setup v0.16.1.exe`
## 🛠️ Common Commands ## 🛠️ Common Commands
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.16.0", "version": "0.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.16.0", "version": "0.16.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ffmpeg-static": "^5.2.0", "ffmpeg-static": "^5.2.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "metona-ollama-desktop", "name": "metona-ollama-desktop",
"version": "0.16.0", "version": "0.16.1",
"description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端", "description": "Metona Ollama - TypeScript + Electron 桌面 AI 聊天客户端",
"main": "dist/main/main.js", "main": "dist/main/main.js",
"author": "thzxx", "author": "thzxx",
+1 -1
View File
@@ -101,7 +101,7 @@ export function createMenu(): void {
dialog.showMessageBox(mainWindow!, { dialog.showMessageBox(mainWindow!, {
type: 'info', type: 'info',
title: '关于 Metona Ollama', title: '关于 Metona Ollama',
message: 'Metona Ollama Desktop v0.16.0', message: 'Metona Ollama Desktop v0.16.1',
detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama', detail: 'TypeScript + Electron Ollama AI 聊天客户端\n\nhttps://gitee.com/thzxx/metona-ollama',
icon: getIconPath() icon: getIconPath()
}); });
+1 -1
View File
@@ -28,7 +28,7 @@
<div class="header-left"> <div class="header-left">
<img class="logo" src="./assets/icons/llama.png" alt="logo" /> <img class="logo" src="./assets/icons/llama.png" alt="logo" />
<span class="app-title">Metona Ollama</span> <span class="app-title">Metona Ollama</span>
<span class="app-version">v0.16.0</span> <span class="app-version">v0.16.1</span>
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助"> <button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/> <circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
+56 -15
View File
@@ -2601,25 +2601,66 @@ async function handleReflecting(
// ── 异常3: 模型说了要做但没调用工具 → 注入提醒再给一次机会 ── // ── 异常3: 模型说了要做但没调用工具 → 注入提醒再给一次机会 ──
// 场景:AI 回复"我来写入文件"但未发出 write_file 工具调用 // 场景:AI 回复"我来写入文件"但未发出 write_file 工具调用
// 优化:区分"意图声明"(将来时)与"结果描述"(完成时),避免对总结性回复误判
if (ctx.toolCalls.length === 0 && ctx.content.trim() && ctx.loopCount <= 3) { if (ctx.toolCalls.length === 0 && ctx.content.trim() && ctx.loopCount <= 3) {
const text = ctx.content.toLowerCase(); const text = ctx.content.toLowerCase();
const actionHints: Array<{ pattern: RegExp; tool: string; hint: string }> = [ const contentLen = ctx.content.trim().length;
{ pattern: /写入|写文件|写入文件|保存.*文件|创建.*文件|生成.*文件|输出.*文件|write.*file/i, tool: 'write_file', hint: '你提到了写入/创建文件,但没有调用 write_file 工具。请直接调用 write_file 工具执行写入操作。' },
{ pattern: /搜索|查找|搜一下|查一下|检索|search/i, tool: 'web_search', hint: '你提到了搜索,但没有调用 web_search 工具。请直接调用 web_search 工具执行搜索。' }, // 结果描述迹象 — 回复中包含这些,说明模型在总结/描述而非空谈
{ pattern: /抓取|获取.*内容|fetch|获取.*网页/i, tool: 'web_fetch', hint: '你提到了抓取网页,但没有调用 web_fetch 工具。请直接调用 web_fetch 工具获取网页内容。' }, const resultIndicators = [
{ pattern: /运行.*命令|执行.*命令|run.*command/i, tool: 'run_command', hint: '你提到了运行命令,但没有调用 run_command 工具。请直接调用 run_command 工具执行命令。' }, /根据.*(结果|内容|返回|输出)/, /搜索结果/, /查询结果/, /查找结果/,
{ pattern: /下载/i, tool: 'download_file', hint: '你提到了下载,但没有调用 download_file 工具。请直接调用 download_file 工具执行下载。' }, /结果显示/, /结果如下/, /已找到/, /已获取/, /已完成/, /已经.*(写入|保存|创建|搜索|执行)/,
/从.*获取/, /已写入/, /已保存/, /已创建/, /已执行/,
/search result/, /found that/, /according to/i,
]; ];
const matched = actionHints.find(h => h.pattern.test(text) && !ctx.allToolRecords.some(r => r.name === h.tool)); const isDescribingResult = resultIndicators.some(p => p.test(text));
if (matched) {
logWarn(`Reflecting: 检测到"说了做但没做"${matched.tool},注入提醒`); // 意图声明模式 — 只有明确表达"将要做"时才视为"说了没做"
ctx.messages.push({ const intentPatterns = [
role: 'user', /我来.*(写入|写文件|创建|生成|保存|搜索|查找|抓取|运行|执行|下载)/,
content: matched.hint, /让我.*(写入|写文件|创建|生成|保存|搜索|查找|抓取|运行|执行|下载)/,
ephemeral: true, /我将.*(写入|写文件|创建|生成|保存|搜索|查找|抓取|运行|执行|下载)/,
/接下来.*(写入|写文件|创建|生成|保存|搜索|查找|抓取|运行|执行|下载)/,
/现在.*(写入|写文件|创建|生成|保存|搜索|查找|抓取|运行|执行|下载)/,
/需要.*(写入|写文件|创建|生成|保存|搜索|查找|抓取|运行|执行|下载)/,
/i('?)ll (write|create|search|fetch|run|execute|download)/i,
/let me (write|create|search|fetch|run|execute|download)/i,
/i will (write|create|search|fetch|run|execute|download)/i,
/i need to (write|create|search|fetch|run|execute|download)/i,
];
const hasIntentStatement = intentPatterns.some(p => p.test(text));
// 上一轮已调用的工具 — 本轮可能在总结结果,不触发
const lastLoopToolNames = (ctx.prevToolCalls || []).map(c => c.function.name);
// 触发条件:有意图声明(高置信度),或短回复中匹配关键词且非结果描述(低置信度)
const shouldCheck = hasIntentStatement || (contentLen < 300 && !isDescribingResult);
if (shouldCheck) {
const actionHints: Array<{ pattern: RegExp; tool: string; hint: string }> = [
{ pattern: /写入|写文件|写入文件|保存.*文件|创建.*文件|生成.*文件|输出.*文件|write.*file/i, tool: 'write_file', hint: '你提到了写入/创建文件,但没有调用 write_file 工具。请直接调用 write_file 工具执行写入操作。' },
// web_search 正则保持宽泛,由 resultIndicators + lastLoopToolNames 两层防护避免误判
{ pattern: /搜索|搜一下|检索|search/i, tool: 'web_search', hint: '你提到了搜索,但没有调用 web_search 工具。请直接调用 web_search 工具执行搜索。' },
{ pattern: /抓取|获取.*内容|fetch|获取.*网页/i, tool: 'web_fetch', hint: '你提到了抓取网页,但没有调用 web_fetch 工具。请直接调用 web_fetch 工具获取网页内容。' },
{ pattern: /运行.*命令|执行.*命令|run.*command/i, tool: 'run_command', hint: '你提到了运行命令,但没有调用 run_command 工具。请直接调用 run_command 工具执行命令。' },
{ pattern: /下载/i, tool: 'download_file', hint: '你提到了下载,但没有调用 download_file 工具。请直接调用 download_file 工具执行下载。' },
];
const matched = actionHints.find(h => {
if (!h.pattern.test(text)) return false;
if (ctx.allToolRecords.some(r => r.name === h.tool)) return false; // 整个会话已调用过
if (lastLoopToolNames.includes(h.tool)) return false; // 上一轮调用过,本轮可能在总结
return true;
}); });
transition(ctx, S.THINKING); if (matched) {
return; logWarn(`Reflecting: 检测到"说了做但没做" → ${matched.tool},注入提醒`);
ctx.messages.push({
role: 'user',
content: matched.hint,
ephemeral: true,
});
transition(ctx, S.THINKING);
return;
}
} }
} }