P1 AIP
可解释问答:决策证据链
NLQ 管"查数",决策证据链管"答疑":把"销售额到底怎么算""华东区下滑对业绩影响多大"这类问题变成一次 5 步链——问题拆解→三路检索→证据核验→答案合成→引用溯源,答案必须是 markdown + [n] 引用,每一步都写 ai_decision 审计事件。答得对不对,不用信 AI 的嘴,直接看引用和审计回放。看完这 4 个故事,你就能自己提一个决策问题并读懂每一步。
业务分析师
销售总监
管理员
决策问答
证据链
引用溯源
共 4 个故事
能 / 不能速览
✅ 这个主题能做
- 决策/口径类问答:POST /decision/answer 同步执行 5 步链
- 三路检索:RAG(metadata/knowledge/history/fewshot)+ 实体(aie_entities)+ 结构化 NLQ,融合去重后截断 12 条
- 证据核验:LLM 批量判定 support / refute / irrelevant,refute/irrelevant 被丢弃
- 答案只基于证据:markdown 三节结构(结论/分析/建议)+ [n] 引用编号
- 引用溯源:cite 步骤确定性映射 [n] 到 URI(doc:// / object://aie/ / datasource://),ad_citations 落表
- 每步 ai_decision 审计留痕(refID=trace_id),/admin/ai-audit 可回放整条决策链
⛔ 这个主题做不了
- 结构化 NLQ 通道本批未注入(NLQRunner 传 nil,恒 skipped;数据源为空也 skipped)
- RAG 非 metadata 通道的引用 URI 是内容寻址降级 doc://aip/<md5>,非真实文档 ID
- 同步执行:整链多步 LLM,耗时随证据数与 LLM 速度增长,前端要放宽超时(300s)
- 证据内容截断:进入 prompt/引用最多 600 字符;引用上限 10 条
- verify 漏判的条目保守保留(按 support 处理),宁可多给证据不少给
适用角色
本主题面向三个角色:
- 业务分析师:核心使用者,用决策问答替代"翻群聊/找数据工程师问口径",拿到带引用的答案。
- 销售总监 / 业务经理:消费结论与引用;有疑问时要求管理员回放决策链,理解"凭什么这么答"。
- 管理员:在 /admin/ai-audit 回放 ai_decision 审计,定位检索漏路或核验降级。
页面在 /decision,任意登录用户可提问(protected),审计回放页面为管理员入口。
能力速览(能做什么)
决策问答(POST /decision/answer)
同步执行 5 步链,返回 answer(markdown)+ steps(5 步明细)+ citations(引用清单)。
问题拆解(decompose)
LLM 把问题拆成 1~4 个完整中文疑问句,提升检索召回;坏 JSON 重试 1 次,失败降级为原始问题。
三路检索(retrieve)
RAG 四/五通道 + 实体直查 aie_entities + 结构化 NLQ,URI/内容 MD5 去重、分数降序、截断 12 条。
证据核验(verify)
LLM 批量判定每条候选证据 support/refute/irrelevant,非法输出降级 unverified(诚实标注)。
答案合成(synthesize)
仅基于证据生成三节 markdown,结论句末标 [n] 引用;无证据走"证据不足"兜底,不编造。
引用溯源(cite)+ 审计
确定性映射 [n]→引用(未引用证据按分数追加,上限 10);每步 ai_decision 审计留痕可回放。
调整指南(怎么调整)
- 带数据源提问:question 之外加 data_source 字段;有数据源时结构化 NLQ 通道才可能执行(需注入 NLQRunner)。
- 补知识底料:答案"证据不足"时,先补知识库文档或触发实体抽取,再重新提问。
- 看步骤状态:steps[].status 是 ok/degraded/skipped,HTTP 200 不等于业务成功;LLM 降级看 verify 的 unverified 标注。
- 改证据数量:融合后候选截断 12 条、引用上限 10 条是常量,证据多时答案只基于最强的 12 条。
- 回放定位问题:答案不满意先看 /admin/ai-audit?trace_id=,定位是检索漏了路还是核验降了级。
做得好的场景
决策证据链在"口径答疑 + 可追溯"上收益最明显:
- 几十秒出可溯源口径:以往问口径要翻群聊/找数据工程师,半天到一天;现在一条提问 + 证据引用,几十秒给到可溯源的答案。
- 答案不是黑盒:5 步链全透明,[n] 引用指向 doc:// / object://aie/ 资源,点开即溯源。
- 审计可回放:每一步一条 ai_decision 事件(refID=trace_id),"为什么这么答"可以逐步骤解释给管理层。
- 诚实不编造:无证据就说"证据不足",LLM 不可用就标"未经推理与核验",降级结果绝不冒充推理结论。
限制与不足
以下是明确的边界,使用前先知道:
- 结构化通道本批 skipped:NLQRunner 未注入(接线传 nil),数据源为空也 skipped;要启用需注入受控 NLQ 实现。
- 引用 URI 内容寻址降级:RAG 非 metadata 通道的文档引用是 doc://aip/<md5>,不带真实 docID,可后续经 Retriever 适配层回填。
- 同步耗时:/decision/answer 是多步 LLM 同步执行,随证据数增长;前端 timeout 300s,未任务化。
- 证据截断:进入 prompt/引用最多 600 字符;verify 漏判的条目保守保留。
- 依赖数据底料:知识库/实体库/数据源里都没有的内容,只能诚实回"证据不足"。
场景故事
故事 1
问"销售额的计算口径",5 步链走全 + [n] 引用可溯源
场景:口径答疑
角色:业务分析师
耗时:约 1 分钟
- 背景
- 王姐在 /decision 页面输入"本季度销售额的计算口径是什么",带上数据源 aip_demo_warehouse。前置已就绪:知识库有《销售指标口径说明》文档,实体抽取已确认 销售额 实体。她想看看系统能不能答清楚"含不含退款"这种只有文档里才有的口径。
- 传统做法对比
- 以前问口径要翻群聊、找数据工程师,半天到一天才能定论;现在一条提问 + 证据引用,几十秒给到可溯源的答案,审计轨迹还能回放"为什么这么答"。
- 角色
- 业务分析师(提问,带数据源名)。
- 操作步骤
-
- 进入 /decision 页面,输入问题并选数据源
- 提交后等待 5 步链同步完成(loading 等待)
- 查看 5 步进度、答案 markdown 与引用面板
- 点击引用 URI 溯源到文档 chunk / 实体
- 系统响应
- 返回答案 + 5 步明细 + 引用清单:
{
"code": 0,
"data": {
"answer": { "id": "<trace_id>", "question": "本季度销售额的计算口径是什么", "status": "done",
"answer": "## 结论\n\n本月销售额 = 订单金额 - 退款金额 [1],指已成交订单的实付金额 [2]。\n\n## 分析\n...\n\n## 建议\n...",
"steps": "...", "citations": "...", "created_by": "u1", "created_at": "..." },
"steps": [
{ "step": "decompose", "status": "ok", "detail": { "sub_questions": ["销售额如何定义", "是否含退款"], "llm_ok": true } },
{ "step": "retrieve", "status": "ok", "detail": { "rag": 2, "entity": 1, "nlq": "skipped_no_datasource", "candidates": 3, "fused": 3 } },
{ "step": "verify", "status": "ok", "detail": { "llm_ok": true, "llm_retries": 0, "supported": 2, "dropped": 1 } },
{ "step": "synthesize", "status": "ok", "detail": { "llm_ok": true, "evidence_used": 2, "answer_chars": 120 } },
{ "step": "cite", "status": "ok", "detail": { "citations": 2 } }
],
"citations": [
{ "id": "<uuid>", "answer_id": "<trace_id>", "seq": 1, "uri": "doc://aip/3?chunk=2", "title": "销售指标口径说明", "snippet": "销售额 = 订单金额 - 退款金额", "score": 0.9, "source": "rag" },
{ "id": "<uuid>", "answer_id": "<trace_id>", "seq": 2, "uri": "object://aie/<entity_id>", "title": "销售额", "snippet": "实体「销售额」(类型: metric)", "score": 0.85, "source": "entity" }
]
}
}
- 结果洞察
- decompose 把问题拆成"销售额如何定义/是否含退款"两个子问题;retrieve 三路中 RAG 命中 2 条、实体命中 1 条,结构化 NLQ 因本批未注入恒 skipped;verify 把无关历史对话判为 irrelevant 丢弃;synthesize 只基于 2 条 support 证据生成三节 markdown,结论句末 [1][2] 对应引用面板。
- 调整建议
- 提问带 data_source 字段结构化通道才可能执行;答案不满意先看 steps 哪一步 degraded;要提升实体命中,前置去 /entity-extract 确认实体别名。
- 动手试一试
- 登录:http://127.0.0.1:18080,admin / admin1。页面路径:/decision。输入:问题"销售额的计算口径是什么",数据源 aip_demo_warehouse。预期结果:5 步全 ok,answer 三节结构带 [n],citations 面板 2 条可溯源(doc:// 与 object://aie/)。
- 限制提示
- /decision/answer 是同步多步 LLM,耗时随证据数/LLM 速度增长,前端 timeout 300s;RAG 非 metadata 通道引用 URI 是内容寻址降级 doc://aip/<md5>,非真实文档 ID。
故事 2
管理层追问依据:ai-audit 回放整条决策链
场景:审计回放
角色:管理员 + 销售总监
耗时:约 5 分钟
- 背景
- 李经理是销售总监,对王姐给出的"销售额 = 订单金额 - 退款金额"有疑问:"这个口径哪来的?"王姐把答案的 trace_id 交给管理员,管理员在 /admin/ai-audit?trace_id= 回放决策链,逐步解释答案的依据。
- 传统做法对比
- 以前 AI 给个结论就是黑盒,追问只能再问人、靠人背书;现在每一步都有 ai_decision 审计事件(refID=trace_id,stepType=decompose/retrieve/verify/synthesize/cite),可逐步骤解释"为什么这么答"。
- 角色
- 管理员(回放审计)+ 销售总监(核验答案依据)。
- 操作步骤
-
- 提问完成后记录 answer.id(= trace_id)
- 管理员打开 /admin/ai-audit?trace_id=<trace_id>
- 核对 5 条 ai_decision 事件(stepType 逐个对应)
- 查看 retrieve 各通道命中数与 verify 结论,定位问题
- 系统响应
- 审计回放按 trace_id 返回决策链事件(GET /api/v1/ai-audit?trace_id=):
ref_type=ai_decision event_type=DECISION_ANSWER ref_id=<trace_id>
step1 decompose result=success detail={"sub_questions":["销售额如何定义","是否含退款"]}
step2 retrieve result=success detail={"rag":2,"entity":1,"nlq":"skipped_no_datasource","candidates":3,"fused":3}
step3 verify result=success detail={"llm_ok":true,"supported":2,"dropped":1}
step4 synthesize result=success detail={"evidence_used":2,"answer_chars":120}
step5 cite result=success detail={"citations":2}
- 结果洞察
- 每步经 LogEventRef(refType=ai_decision, refID=trace_id, stepType=步骤名) 打点,result 按步骤状态映射(ok→success / degraded→degraded / skipped→skipped)。李经理据此看到:答案基于 RAG 命中的《销售指标口径说明》chunk 与 销售额 实体,检索了 3 条候选、核验后剩 2 条 support——口径出处一目了然。这类回放还能定位"检索漏了哪个通道"或"证据核验是否降级"。
- 调整建议
- 提问时带数据源名,审计里能区分结构化通道是 skipped 还是执行;要留作汇报证据,记下 trace_id 即可随时回放。
- 动手试一试
- 登录:admin / admin1。操作:/decision 提一个问题,复制返回的 answer.id,在 /admin/ai-audit?trace_id=<id> 回放。预期结果:5 条 ai_decision 事件,stepType 与 5 步链一一对应,retrieve 步骤能看到各路命中数。
- 限制提示
- 审计回放依赖 ai_decision 事件写入;audit 未注入时不打点;HTTP 200 不等于业务成功——LLM 降级、通道 skipped、证据不足都以 steps[].status / 答案前缀为准。
故事 3
LLM 网关不可用:诚实降级,不把摘要冒充结论
场景:降级
角色:业务分析师
耗时:约 1 分钟
- 背景
- 某次演示时 LLM 网关不可用,王姐不知道,照常提交了同一个口径问题。系统仍返回 200——没有报错,但答案前多了一段说明,步骤状态也变了。她要确认"这段答案能不能直接当结论用"。
- 传统做法对比
- 不少系统此时要么整链报错,要么把降级结果当推理结论返回,用户无从分辨;本模块 200 + 诚实标注——答案明确说"未经推理与核验",降级结果绝不冒充。
- 角色
- 业务分析师(识别降级,判断答案可用性)。
- 操作步骤
-
- 断开/失效 LLM key(或模拟网关 500)
- 在 /decision 再次提交问题
- 看答案前缀与 steps 状态(degraded / unverified)
- 确认降级后不要直接当推理结论使用
- 系统响应
- HTTP 200,但步骤降级、答案带"未推理"前缀:
answer: "> 说明:LLM 服务暂不可用,以下为检索证据摘要(未经推理与核验),仅供参考。\n1. **销售指标口径说明**(来源: rag, URI: doc://aip/3?chunk=2)\n2. **销售额**(来源: entity, URI: object://aie/<id>)..."
steps: [
{ "step": "decompose", "status": "degraded", "detail": { "sub_questions": ["本季度销售额的计算口径是什么"], "llm_ok": false } },
{ "step": "verify", "status": "degraded", "detail": { "llm_ok": false, "unverified": true, "supported": 2, "dropped": 0 } },
{ "step": "synthesize", "status": "degraded", "detail": { "llm_ok": false, "evidence_used": 2, "answer_chars": 120 } }
]
- 结果洞察
- 降级三连:decompose 降级为原始问题(degraded);verify 全部证据按 support 处理并标注 unverified=true(诚实标注"未经核验");synthesize 输出证据摘要并加"未经推理与核验"前缀(fallbackEvidenceMarkdown)。链路不断、前端可渲染、答案明确标注——不会把降级结果冒充推理结论。llm 为 nil 时全链同样走这套降级。
- 调整建议
- 恢复 LLM key 后重新提问即恢复完整推理;看到 unverified=true 或"未推理"前缀时,结论需人工核对原始证据,不要直接引用。
- 动手试一试
- 登录:admin / admin1。操作:临时让 LLM 网关不可用,在 /decision 提问。预期结果:HTTP 仍 200,答案前缀出现"LLM 服务暂不可用…未经推理与核验",verify 步骤 detail.unverified=true。
- 限制提示
- 降级时 decompose/verify/synthesize 的 llm_ok 均为 false,审计事件 result 为 degraded;引用 URI 在降级路径仍按来源兜底生成(entity→object://aie/、rag 其他通道→doc://aip/<md5>)。
故事 4
查不到证据:系统诚实说"证据不足",不编造
场景:无证据
角色:业务分析师
耗时:约 1 分钟
- 背景
- 王姐问了一个知识库、实体库、数据源里都没有的问题:"虚拟订单的跨境结算周期是几天?"知识库文档没写,实体库里没有对应实体,演示库也没有这张表。她想看系统会不会硬编一个答案。
- 传统做法对比
- 有些问答系统会"编造"一个看似合理的答案;决策证据链 prompt 硬性要求:证据不足必须写"证据不足",三路检索均无命中时 retrieve 标 degraded,宁缺毋滥。
- 角色
- 业务分析师(提问冷门问题,验证诚实性)。
- 操作步骤
-
- 在 /decision 输入知识底料里不存在的问题
- 查看 retrieve 步骤状态与 detail
- 查看 answer 与 citations(空引用)
- 系统响应
- 三路均无命中,答案走 noEvidenceAnswer 兜底:
answer: "未找到与该问题相关的证据,无法给出有依据的结论。"
steps: [
{ "step": "retrieve", "status": "degraded", "detail": { "rag": 0, "entity": 0, "nlq": "skipped_no_datasource", "note": "三路检索均无命中或均失败", "candidates": 0, "fused": 0 } }
]
citations: []
- 结果洞察
- retrieve 三路都空,步骤标 degraded 并注明"三路检索均无命中或均失败";synthesize 因无证据走 noEvidenceAnswer(诚实回答"未找到证据,无法给出有依据的结论",llm_ok=true);cite 生成空引用。全部证据被判 refute/irrelevant 时同理:答案会如实说明证据不足。宁可诚实,绝不编造。
- 调整建议
- 想让这类问题有答案,先补料:知识库写文档、触发实体抽取、确认数据源元数据;补完重新提问。
- 动手试一试
- 登录:admin / admin1。操作:/decision 输入"虚拟订单的跨境结算周期是几天"。预期结果:answer 为"未找到与该问题相关的证据…",retrieve 步骤 degraded、note 注明三路均无命中,citations 为空。
- 限制提示
- 检索三路中任一路失败/未注入只跳过该路(detail 标 skipped/failed),不阻断整链;verify 漏判的条目保守保留(宁可多给证据不少给),可能让个别无关证据进入答案。
常见问题
决策证据链和 NLQ 查数有什么区别?
NLQ 是"查数":Text2SQL 生成 SQL 并执行,返回表格;决策证据链是"答疑":检索知识/实体/数据证据,核验后合成带引用的 markdown 答案。NLQ 主链路对 knowledge_qa 意图短路返回占位提示,本模块补上了知识问答/决策问答的完整推理链路。
引用 [n] 是怎么映射的?
cite 步骤做确定性映射(无 LLM):解析答案中 [n] 标记(首次出现顺序去重)对应证据成为引用(seq 1..);未被引用的证据按分数降序追加;按 URI 或内容 MD5 去重,上限 10 条。
为什么 retrieve 里的结构化通道总是 skipped?
结构化 NLQ 通道依赖 NLQRunner 注入,本批接线传 nil;且仅当请求带 data_source 才执行。注入了自定义实现(适配 orchestrator 的 Text2SQL+executor)后通道才启用。
审计在哪儿回放?
管理员在 /admin/ai-audit?trace_id=<trace_id> 回放,trace_id 就是答案 ID。5 步链每步一条 ref_type=ai_decision 事件,stepType 为 decompose/retrieve/verify/synthesize/cite。
答案一定可靠吗?
不一定。HTTP 200 不等于业务成功:LLM 降级、通道 skipped、证据不足都以 steps[].status / detail / 答案前缀为准。答案只基于核验通过的证据合成,且带引用可自查,但证据本身质量取决于知识库/实体库/数据源底料。
主题小结
一句话:决策证据链 = decompose→retrieve→verify→synthesize→cite 五步链,把决策问题变成"带引用、可回放、不编造"的可解释答案。记住边界:结构化通道本批 skipped、RAG 文档引用是内容寻址降级、同步执行耗时较长、无底料只能诚实说"证据不足"。看步骤状态比看 HTTP 状态码更能判断答案质量。