Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 61 additions & 65 deletions design/vue/src/pages/ChatPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -171,79 +171,75 @@
</div>
</div>

<!-- Pending Interrupt Card: clarify 或 danger approval -->
<!-- Pending Interrupt: clarify 或 danger approval -->
<div v-if="pendingApproval" class="flex justify-start">
<!-- 澄清追问卡 -->
<div v-if="pendingApproval.is_clarify" class="max-w-[80%]">
<div class="rounded-xl border border-sky-200 bg-sky-50 shadow-sm overflow-hidden">
<div class="flex items-center gap-2 px-4 py-2.5 border-b border-sky-200 bg-sky-100/50">
<svg class="w-4 h-4 text-sky-600 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
</svg>
<span class="text-xs font-semibold text-sky-800">需要澄清</span>
</div>
<div class="px-4 py-3">
<p class="text-sm text-sky-900 leading-relaxed">{{ pendingApproval.detail }}</p>
<!-- 选项按钮 -->
<div v-if="pendingApproval.options?.length" class="mt-3 flex flex-wrap gap-2">
<button
v-for="(opt, idx) in pendingApproval.options"
:key="idx"
@click="approvePending(opt)"
class="px-3 py-1.5 text-xs font-medium rounded-lg bg-white hover:bg-sky-50 text-sky-700 border border-sky-200 transition-colors"
>{{ opt }}</button>
</div>
<!-- 自由输入 -->
<div class="mt-3">
<div class="flex gap-2">
<input
v-model="clarifyInput"
@keyup.enter="submitClarify"
placeholder="也可以直接输入你的回答..."
class="flex-1 px-3 py-1.5 text-xs rounded-lg border border-sky-200 bg-white text-slate-700 focus:outline-none focus:ring-2 focus:ring-sky-300"
/>
<div class="max-w-[80%] px-4 py-3 rounded-2xl bg-white text-slate-800 text-sm leading-relaxed">
<!-- 澄清追问 -->
<template v-if="pendingApproval.is_clarify">
<div class="flex items-start gap-2">
<span class="text-sky-500 mt-0.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
</span>
<div class="flex-1">
<p class="text-slate-700">{{ pendingApproval.detail }}</p>
<!-- 选项按钮 -->
<div v-if="pendingApproval.options?.length" class="mt-3 flex flex-wrap gap-2">
<button
@click="submitClarify"
:disabled="!clarifyInput.trim()"
class="px-3 py-1.5 text-xs font-medium rounded-lg bg-sky-600 hover:bg-sky-700 disabled:bg-sky-300 text-white transition-colors"
>提交</button>
v-for="(opt, idx) in pendingApproval.options"
:key="idx"
@click="approvePending(opt)"
class="px-3 py-1.5 text-xs font-medium rounded-lg bg-slate-50 hover:bg-slate-100 text-slate-700 border border-slate-200 transition-colors"
>{{ opt }}</button>
</div>
<!-- 自由输入 -->
<div class="mt-3">
<div class="flex gap-2">
<input
v-model="clarifyInput"
@keyup.enter="submitClarify"
placeholder="输入你的回答..."
class="flex-1 px-3 py-1.5 text-xs rounded-lg border border-slate-200 bg-white text-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-300 focus:border-slate-300"
/>
<button
@click="submitClarify"
:disabled="!clarifyInput.trim()"
class="px-3 py-1.5 text-xs font-medium rounded-lg bg-slate-800 hover:bg-slate-900 disabled:bg-slate-300 text-white transition-colors"
>提交</button>
</div>
</div>
</div>
<button
@click="cancelApproval"
class="mt-2 px-3 py-1 text-xs text-slate-400 hover:text-slate-600"
>暂不处理</button>
</div>
</div>
</div>
<!-- 危险工具审批卡 -->
<div v-else class="max-w-[80%]">
<div class="rounded-xl border border-amber-200 bg-amber-50 shadow-sm overflow-hidden">
<div class="flex items-center gap-2 px-4 py-2.5 border-b border-amber-200 bg-amber-100/50">
<svg class="w-4 h-4 text-amber-600 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
<span class="text-xs font-semibold text-amber-800">需要人工确认</span>
<span v-if="pendingApproval.tool_name" class="ml-auto text-[11px] text-amber-600 bg-amber-100 px-2 py-0.5 rounded-full font-mono">{{ pendingApproval.tool_name }}</span>
</div>
<div class="px-4 py-3">
<p class="text-sm text-amber-900 leading-relaxed">{{ pendingApproval.detail }}</p>
<div class="mt-3 flex items-center gap-2">
<button
@click="approvePending('approve')"
class="px-4 py-1.5 text-xs font-medium rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white transition-colors shadow-sm"
>同意执行</button>
<button
@click="approvePending('reject')"
class="px-4 py-1.5 text-xs font-medium rounded-lg bg-white hover:bg-slate-50 text-slate-700 border border-slate-200 transition-colors"
>拒绝</button>
<button
@click="cancelApproval"
class="px-3 py-1.5 text-xs text-slate-400 hover:text-slate-600 ml-auto"
class="mt-2 px-3 py-1 text-xs text-slate-400 hover:text-slate-600"
>暂不处理</button>
</div>
</div>
</div>
</template>
<!-- 危险工具审批 -->
<template v-else>
<div class="flex items-start gap-2">
<span class="text-amber-500 mt-0.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</span>
<div class="flex-1">
<p class="text-slate-700">{{ pendingApproval.detail }}</p>
<div v-if="pendingApproval.tool_name" class="mt-1 text-[11px] text-slate-400 font-mono">{{ pendingApproval.tool_name }}</div>
<div class="mt-3 flex items-center gap-2">
<button
@click="approvePending('approve')"
class="px-4 py-1.5 text-xs font-medium rounded-lg bg-slate-800 hover:bg-slate-900 text-white transition-colors"
>同意执行</button>
<button
@click="approvePending('reject')"
class="px-4 py-1.5 text-xs font-medium rounded-lg bg-white hover:bg-slate-50 text-slate-600 border border-slate-200 transition-colors"
>拒绝</button>
<button
@click="cancelApproval"
class="px-3 py-1.5 text-xs text-slate-400 hover:text-slate-600 ml-auto"
>暂不处理</button>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
Expand Down
22 changes: 13 additions & 9 deletions internal/agent/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,15 @@ func buildReActSystemPrompt(ctx context.Context, allTools []einoTool.BaseTool, i
// ── 工作原则 ──
sb.WriteString("## 工作原则\n")
sb.WriteString("1. **先检索再回答**:第一步始终是 knowledge_search。即使你认为知道答案也必须先检索知识库\n")
sb.WriteString("2. **按需补充**:知识库结果不足时,再调用其他工具(grep_chunks 精准查找、get_document_info 查元数据等)\n")
sb.WriteString("3. **不重复调用**:已获得足够信息时,直接给出答案,不要为了'再确认'重复调用\n")
sb.WriteString("4. **工具上限 3 次**:工具调用总数不超过 3 次,用完必须收敛\n")
sb.WriteString("5. **强制收敛**:达到最大推理轮次或用完工具次数时,立即总结已有信息给出最终答案。禁止再规划'下一步应该'、'我还需要'等思考性输出\n")
sb.WriteString("6. **答案分层**:有 ToolCalls 的轮次 Message.Content 只写 1-2 句简短推理(不会展示给用户);只有 ToolCalls 为空的轮次才是完整、可读、面向最终用户的答案正文\n")
sb.WriteString("2. **知识库内部工具各有分工**:knowledge_search 是语义搜索(找相关内容片段),list_knowledge_bases / list_knowledge_chunks 是列清单(看有哪些知识库/文档),grep_chunks 是关键词精准查找,get_document_info 是查文档详情。根据用户意图选择合适的工具——如果 knowledge_search 不满足需求(比如用户要'列出所有文档'而不是'找内容'),可以继续调用其他知识库内部工具\n")
sb.WriteString("3. **联网搜索谨慎使用**:knowledge_search 或其他知识库内部工具已经返回了相关内容时,**不要主动联网搜索补充细节**。联网搜索仅在以下情况使用:\n")
sb.WriteString(" - 用户明确要求'联网'、'最新'、'实时'、'当前'、'2024年后'等时效性内容\n")
sb.WriteString(" - 所有知识库工具都返回空结果或结果与问题完全不匹配\n")
sb.WriteString(" - 知识库内容有明显时间戳且已过时\n")
sb.WriteString("4. **不重复调用**:已获得足够信息时,直接给出答案,不要为了'再确认'重复调用\n")
sb.WriteString("5. **工具上限 3 次**:工具调用总数不超过 3 次,用完必须收敛\n")
sb.WriteString("6. **强制收敛**:达到最大推理轮次或用完工具次数时,立即总结已有信息给出最终答案。禁止再规划'下一步应该'、'我还需要'等思考性输出\n")
sb.WriteString("7. **答案分层**:有 ToolCalls 的轮次 Message.Content 只写 1-2 句简短推理(不会展示给用户);只有 ToolCalls 为空的轮次才是完整、可读、面向最终用户的答案正文\n")

// 危险工具补充说明
hasDangerous := false
Expand All @@ -75,7 +79,7 @@ func buildReActSystemPrompt(ctx context.Context, allTools []einoTool.BaseTool, i
}
}
if hasDangerous {
sb.WriteString("7. **危险工具审批**:delete_document 等危险工具会在执行前暂停并等待用户审批,调用后流程中断,用户确认后自动继续\n")
sb.WriteString("8. **危险工具审批**:delete_document 等危险工具会在执行前暂停并等待用户审批,调用后流程中断,用户确认后自动继续\n")
sb.WriteString(" - ⚠️ **目标不明确先反问**:当用户说'删除那个文档'、'清理一下'、'把上面的删了'这类模糊指令,且从对话历史无法唯一确定目标时,**绝对不能编造参数调用工具**。先调用 ask_clarify 反问用户明确目标(例如:'你要删除的是《压力 - 07/13 16:03》那个文档吗?还是另一个?')\n")
sb.WriteString(" - ⚠️ **禁止猜测参数**:document_id 等关键参数必须来自可靠来源(用户明确提供、get_document_info 工具查询结果、历史对话中已确认的 ID)。严禁从模糊描述或'看起来像是'的文本中猜测或编造\n")
sb.WriteString(" - 调用危险工具时务必在参数里写清楚目标和原因,便于用户决策\n")
Expand Down Expand Up @@ -105,15 +109,15 @@ func buildReActSystemPrompt(ctx context.Context, allTools []einoTool.BaseTool, i
}
}
if len(externals) > 0 {
sb.WriteString(fmt.Sprintf("8. **联网搜索**:当 knowledge_search 返回空结果或与问题不相关时,必须调用 %s 联网搜索。用户明确要求'联网'、'最新'时即使知识库有结果也应联网\n",
strings.Join(externals, " 或 ")))
sb.WriteString(fmt.Sprintf("\n## 联网搜索(%s)\n", strings.Join(externals, " 或 ")))
sb.WriteString("谨慎使用,触发条件见上面的工作原则第 3 条。**知识库已有相关内容或可以用其他内部工具解决时,不要主动联网补充。**\n")
}
sb.WriteString("\n")

// ── 禁止 ──
sb.WriteString("## 禁止\n")
sb.WriteString("- 不检索知识库就用自身知识回答\n")
sb.WriteString("- 知识库检索失败不联网搜索就直接回答\n")
sb.WriteString("- 知识库已有相关结果时,为了'再确认'或'补充细节'而联网搜索\n")
sb.WriteString("- 在有可用工具时直接给用户'请去界面手动操作'的建议——你应该调用工具来完成\n\n")

// ── 回答要求 ──
Expand Down
Loading