Skip to content

Commit 45bd0bd

Browse files
nieaoclaude
andcommitted
feat(challenge): 反驳卡片加 Hermes 二次验证, 评估反驳是否成立
用户图 43 反馈: LLM 生成的反驳脱离实际 (PDF 不会超 1GB 但反驳照搬 OOM)。 ChallengeNode 待办区下方加"⚖ 二次验证"按钮: - 触发 challenge:verify-hermes 事件 - KnowledgeGraph 监听后调 LLM 做 sanity check (system prompt 明确"避免 LLM 编造不切实际论点") - 返回 { score, verdict, reason }, 写到节点 data.verification - UI 显示彩色可信度数字 + 一句话理由 - 70+ 绿 / 40+ 暖 / <40 红粉 — 视觉直接看出反驳质量 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent de9ae56 commit 45bd0bd

2 files changed

Lines changed: 97 additions & 0 deletions

File tree

src/components/canvas/ChallengeNode.jsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ function ChallengeNodeImpl({ id, data, selected }) {
2929
const evidence = Array.isArray(data.evidence) ? data.evidence : []
3030
const todos = Array.isArray(data.todos) ? data.todos : []
3131
const chainRunning = data.chainRunning === true
32+
const verifyRunning = data.verifyRunning === true
33+
const verification = data.verification || null // { score, verdict, reason }
3234

3335
const onChainTodos = (e) => {
3436
e.stopPropagation()
@@ -38,6 +40,14 @@ function ChallengeNodeImpl({ id, data, selected }) {
3840
}))
3941
}
4042

43+
const onVerify = (e) => {
44+
e.stopPropagation()
45+
if (verifyRunning) return
46+
window.dispatchEvent(new CustomEvent('challenge:verify-hermes', {
47+
detail: { challengeId: id, claim, angle, severity, sourceTitle },
48+
}))
49+
}
50+
4151
return (
4252
<div
4353
className="relative shadow-sm transition-all duration-300"
@@ -152,6 +162,54 @@ function ChallengeNodeImpl({ id, data, selected }) {
152162
</div>
153163
)}
154164

165+
{/* === 二次验证: Hermes sanity check === */}
166+
{/* 让 LLM 做"反思"判断这个反驳是否切合实际 (用户图 43 反馈反驳脱离实际, 比如说 PDF 超 1GB) */}
167+
<div className="mt-2.5 pt-2" style={{ borderTop: `1px dashed ${meta.color}30` }}>
168+
<div className="flex items-center justify-between gap-2">
169+
<span
170+
className="text-[9px]"
171+
style={{ color: 'var(--text-muted)', letterSpacing: '0.15em' }}
172+
>
173+
{verification ? `验证 · ${verification.verdict || '已审'}` : '反驳是否成立?'}
174+
</span>
175+
<button
176+
type="button"
177+
onClick={onVerify}
178+
disabled={verifyRunning}
179+
className="text-[9px] px-2 py-0.5 rounded transition-all duration-300"
180+
style={{
181+
border: '1px solid var(--accent)',
182+
color: verifyRunning ? 'var(--text-faint)' : 'var(--accent)',
183+
background: verification ? 'var(--accent-bg, rgba(245,240,235,0.4))' : 'transparent',
184+
cursor: verifyRunning ? 'wait' : 'pointer',
185+
letterSpacing: '0.05em',
186+
}}
187+
title="让 LLM 二次审查这个反驳: 是否切合实际, 给可信度评分"
188+
>
189+
{verifyRunning ? '审中…' : verification ? '重审' : '⚖ 二次验证'}
190+
</button>
191+
</div>
192+
{verification && (
193+
<div className="mt-1.5 p-1.5 rounded" style={{
194+
background: verification.score >= 70 ? 'rgba(123,196,127,0.08)' : verification.score >= 40 ? 'rgba(200,168,130,0.08)' : 'rgba(178,124,139,0.08)',
195+
border: `1px solid ${verification.score >= 70 ? '#7bc47f55' : verification.score >= 40 ? '#c8a88255' : '#b27c8b55'}`,
196+
}}>
197+
<div className="flex items-center gap-1.5" style={{ marginBottom: 3 }}>
198+
<span style={{
199+
fontSize: 12,
200+
fontFamily: '"Noto Serif SC", Georgia, serif',
201+
color: verification.score >= 70 ? '#3a8a3e' : verification.score >= 40 ? 'var(--accent)' : '#7a3a4a',
202+
fontWeight: 600,
203+
}}>{Math.round(verification.score)}</span>
204+
<span style={{ fontSize: 9, color: 'var(--text-faint)', letterSpacing: '0.15em' }}>可信度 / 100</span>
205+
</div>
206+
<div style={{ fontSize: 10, color: 'var(--text-secondary)', lineHeight: 1.4 }}>
207+
{verification.reason}
208+
</div>
209+
</div>
210+
)}
211+
</div>
212+
155213
{/* 攻击对象 (源节点) */}
156214
{sourceTitle && (
157215
<div className="text-[9px] mt-2.5 pt-1.5" style={{ color: 'var(--text-muted)', borderTop: '1px dashed var(--border-subtle)' }}>

src/pages/KnowledgeGraph.jsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,42 @@ export default function KnowledgeGraph() {
509509
}
510510
}
511511

512+
// 反驳节点的"二次验证"按钮 — 让 LLM 反思这个反驳是否切合实际, 给可信度评分
513+
// (用户图 43 反馈: 反驳脱离实际, 比如 PDF 不会超 1GB 但反驳照搬 OOM 风险)
514+
const onVerifyHermes = async (e) => {
515+
const { challengeId, claim, angle, severity, sourceTitle } = e.detail || {}
516+
if (!challengeId || !claim) return
517+
const store = useCanvasStore.getState()
518+
store.updateNode(challengeId, { verifyRunning: true })
519+
try {
520+
const { callLLM } = await import('../services/aiProvider')
521+
const { tryParseLLMJson } = await import('../services/aiService')
522+
const system = `你是 Hermes 二次审查官. 给定一个对原方案的反驳, 你要判断这个反驳是否站得住脚 — 是否切合实际语境 (避免 LLM 编造不切实际的论点, 比如假设 PDF 超 1GB 之类).
523+
按 JSON 输出: { "score": 0-100 整数 (反驳的可信度), "verdict": "成立"|"勉强"|"不成立", "reason": "一句话理由 (50 字内)" }`
524+
const prompt = `原方案 (针对): ${sourceTitle || '未知'}
525+
反驳角度: ${angle || ''}
526+
反驳论点: ${claim}
527+
反驳严重度: ${severity || 'medium'}
528+
529+
请审视这个反驳是否切合实际, 还是 LLM 想当然脱离场景的臆测? 评分 + 一句话理由.`
530+
const raw = await callLLM({ system, prompt, jsonMode: true })
531+
const parsed = tryParseLLMJson(raw)
532+
const score = typeof parsed?.score === 'number' ? Math.max(0, Math.min(100, parsed.score)) : 50
533+
const verdict = parsed?.verdict || (score >= 70 ? '成立' : score >= 40 ? '勉强' : '不成立')
534+
const reason = parsed?.reason || '审查官未给出理由'
535+
useCanvasStore.getState().updateNode(challengeId, {
536+
verifyRunning: false,
537+
verification: { score, verdict, reason, ts: Date.now() },
538+
})
539+
} catch (err) {
540+
console.error('[verify-hermes] 失败:', err)
541+
useCanvasStore.getState().updateNode(challengeId, {
542+
verifyRunning: false,
543+
verification: { score: 0, verdict: '审查失败', reason: err?.message || String(err) },
544+
})
545+
}
546+
}
547+
512548
// 反驳节点的"下一步"按钮 — 把 todos 派给元认知, 串成下一轮可执行项目
513549
const onChainTodos = (e) => {
514550
const { challengeId, todos = [], claim = '', sourceTitle = '' } = e.detail || {}
@@ -543,6 +579,7 @@ export default function KnowledgeGraph() {
543579
window.addEventListener('node-change-type', onNodeChangeType)
544580
window.addEventListener('group-color-change', onGroupColorChange)
545581
window.addEventListener('challenge:chain-todos', onChainTodos)
582+
window.addEventListener('challenge:verify-hermes', onVerifyHermes)
546583
window.addEventListener('canvas-auto-save', onAutoSave)
547584

548585
return () => {
@@ -557,6 +594,8 @@ export default function KnowledgeGraph() {
557594
window.removeEventListener('node-change-type', onNodeChangeType)
558595
window.removeEventListener('group-color-change', onGroupColorChange)
559596
window.removeEventListener('challenge:chain-todos', onChainTodos)
597+
window.removeEventListener('challenge:verify-hermes', onVerifyHermes)
598+
window.removeEventListener('canvas-auto-save', onAutoSave)
560599
}
561600
}, [handleFileDrop, addBookmarkNode, addConceptNode, addNoteNode, addImageNode, addVideoNode, addFileNode, onNodesChange])
562601

0 commit comments

Comments
 (0)