[{"data":1,"prerenderedAt":309},["ShallowReactive",2],{"note:\u002Fprojects\u002Frag-knowledge-base\u002F06-主链路编排与置信度":3},{"id":4,"title":5,"body":6,"date":288,"description":289,"draft":290,"extension":291,"featured":290,"kind":292,"lastmod":288,"meta":293,"navigation":228,"path":297,"planned":298,"section":299,"seo":300,"source":301,"stem":302,"tags":303,"toc":228,"weight":307,"__hash__":308},"notes\u002Fprojects\u002Frag-knowledge-base\u002F06-主链路编排与置信度.md","主链路编排与答案置信度",{"type":7,"value":8,"toc":277},"minimark",[9,18,21,25,31,42,45,49,52,55,61,68,78,82,88,95,99,106,112,115,121,128,132,142,145,151,154,158,165,171,180,186,190,196,199,202,242,245,248,270,273],[10,11,12,13,17],"p",{},"Demo 和上线系统最本质的区别，不在于答对率，而在于",[14,15,16],"strong",{},"答错时会发生什么","。",[10,19,20],{},"知识库里没有答案时，模型不会说「我不知道」，它会流畅地编一个出来。\n而且编得越像，越难被发现。这一篇讲的就是这套项目怎么处理这个问题。",[22,23,24],"h2",{"id":24},"先给结论",[10,26,27,28],{},"编排层只有一条心法：",[14,29,30],{},"分层负责，参数透传。",[32,33,39],"pre",{"className":34,"code":36,"language":37,"meta":38},[35],"language-text","API 层         只做接入：限流、校验、异常转错误码、事件转发\nQAService      只做编排：场景解析、数据域、调用顺序，不碰检索和生成细节\npipeline       只做业务：检索步骤、置信度、引用、生成\n","text","",[40,41,36],"code",{"__ignoreMap":38},[10,43,44],{},"而「敢不敢回答」这件事，由三段置信度共同决定。",[22,46,48],{"id":47},"一为什么要有-qaservice-这一层","一、为什么要有 QAService 这一层",[10,50,51],{},"如果没有它，WebSocket 处理函数里会塞进场景解析、数据域裁剪、意图调用、检索调用、\n置信度计算、LLM 生成、落库——一个几百行、没人敢改的函数。",[10,53,54],{},"QAService 的边界画得很清楚：",[32,56,59],{"className":57,"code":58,"language":37,"meta":38},[35],"stream_query()      -> Generator 事件流：status \u002F token \u002F end \u002F error\ndebug_retrieval()   -> 检索诊断字典，不调用 LLM\n",[40,60,58],{"__ignoreMap":38},[10,62,63,64,67],{},"两个方法共用同一套「场景解析 + 数据域 + 检索准备」，区别只在于最后要不要生成。\n",[14,65,66],{},"调试接口和真实链路共用前半段","，这一点很关键：你在调试接口里看到的召回结果，\n就是线上真实召回的那一批，不存在「调试用的另一套逻辑」。",[10,69,70,71,74,75],{},"实例由 ",[40,72,73],{},"factory.get_qa_service()"," 以进程级单例管理，构造函数不保存任何请求状态——\n",[14,76,77],{},"无状态是横向扩容的前提。",[22,79,81],{"id":80},"二pipeline-为什么拆成三个文件","二、pipeline 为什么拆成三个文件",[32,83,86],{"className":84,"code":85,"language":37,"meta":38},[35],"rag.py        主流程编排：把步骤串起来，不写细节\nsteps.py      业务步骤：准备检索、检索 FAQ、检索文档、准备生成参数\nconfidence.py 置信度纯逻辑：可单独测试，无 IO\n",[40,87,85],{"__ignoreMap":38},[10,89,90,91,94],{},"拆分的收益不在「好看」，在于",[14,92,93],{},"可测试","。置信度被抽成纯函数之后，\n可以用一堆构造好的输入直接断言输出，不需要起 Milvus、不需要调 LLM。\n这是整套测试体系能跑得快的原因之一。",[22,96,98],{"id":97},"三检索分数-答案可信度","三、检索分数 ≠ 答案可信度",[10,100,101,102,105],{},"这是本篇最重要的一句话。Milvus 返回的 ",[40,103,104],{},"score"," 只说明「候选和 query 相关」，\n它回答不了「这段证据支不支持这个答案」。所以置信度分三段算：",[32,107,110],{"className":108,"code":109,"language":37,"meta":38},[35],"calculate_evidence_confidence()    生成前：证据本身够不够撑得住这个问题\ncalculate_generation_confidence()  生成后：答案有没有真的落在证据里\ncombine_answer_confidence()        合并：产出最终 answer_confidence\n",[40,111,109],{"__ignoreMap":38},[10,113,114],{},"配套的两级门槛写在配置里：",[32,116,119],{"className":117,"code":118,"language":37,"meta":38},[35],"faq_direct_score_threshold = 0.72   # FAQ 直出需要的最低分\nrag_min_score_threshold    = 0.2    # 进入 RAG 生成的最低证据门槛\n",[40,120,118],{"__ignoreMap":38},[10,122,123,124,127],{},"低于门槛就不生成。",[14,125,126],{},"拒答不是失败，是正确行为。","\n对内部制度类问答尤其如此：一个错误的报销标准，比「请咨询财务」危险得多。",[22,129,131],{"id":130},"四引用是强制的不是可选的","四、引用是强制的，不是可选的",[10,133,134,137,138,141],{},[40,135,136],{},"enforce_answer_citations()"," 会检查生成的答案是否带上了来源。\n这一层兜底的意义在于：",[14,139,140],{},"Prompt 里写了「必须引用来源」不等于模型一定会照做。","\n工程上不能依赖模型的自觉，要有代码层面的校验和补救。",[10,143,144],{},"召回结果进入 prompt 之前还有两道裁剪：",[32,146,149],{"className":147,"code":148,"language":37,"meta":38},[35],"final_context_top_n    = 4      # 最终给几段证据\nmax_context_doc_chars  = 1600   # 单段证据最长多少字符\nmax_prompt_context_chars = 6000 # 整个上下文预算\n",[40,150,148],{"__ignoreMap":38},[10,152,153],{},"上下文不是越多越好：无关证据会稀释注意力，超长上下文还会让模型忽略中间部分。",[22,155,157],{"id":156},"五prompt-按意图-风险双维度选","五、Prompt 按「意图 + 风险」双维度选",[10,159,160,161,164],{},"Prompt 模板不是一份，而是一个档位系统（",[40,162,163],{},"qa_core\u002Fprompts\u002Fprofiles.py","）：",[32,166,169],{"className":167,"code":168,"language":37,"meta":38},[35],"意图维度    FAQ 强调短、准、直接\n            知识查询允许结构化回答\n            追问强调不重复历史已答内容\n\n风险维度    费用类必须区分「已确认」与「未确认」\n            合规类使用更保守的措辞\n            排障类要求给出可执行步骤\n",[40,170,168],{"__ignoreMap":38},[10,172,173,174,164,177,17],{},"选择优先级是三级（",[40,175,176],{},"selector.py",[14,178,179],{},"风险类别专用 > 意图专属 > 默认通用",[10,181,182,183],{},"也就是说，一个问题即使被识别成普通知识查询，只要它属于费用类别，\n就一定会走费用专用模板。这个设计承认了一件事：",[14,184,185],{},"风险分类比意图分类更贴近业务底线。",[22,187,189],{"id":188},"六多轮对话怎么不跑偏","六、多轮对话怎么不跑偏",[32,191,194],{"className":192,"code":193,"language":37,"meta":38},[35],"max_history_messages        = 8    # 直接进 prompt 的最近消息数\nhistory_summary_after_messages = 14 # 超过 14 条触发摘要\nhistory_summary_max_chars   = 1200 # 摘要长度上限\n",[40,195,193],{"__ignoreMap":38},[10,197,198],{},"超长历史先摘要再拼接，而不是无脑全塞。追问场景下还会同步放宽 FAQ 召回，\n因为「那它的审批人是谁」这种问题，脱离上下文根本没法检索。",[22,200,201],{"id":201},"动手验证",[32,203,207],{"className":204,"code":205,"language":206,"meta":38,"style":38},"language-powershell shiki shiki-themes github-light github-dark","# 冒烟：接口连通性、流式事件、错误码\npython scripts\u002Facceptance_smoke.py --base-url http:\u002F\u002F127.0.0.1:18000\n\n# 端到端：完整问答链路\npython scripts\u002Fapi_e2e_smoke.py --base-url http:\u002F\u002F127.0.0.1:18000\n","powershell",[40,208,209,217,223,230,236],{"__ignoreMap":38},[210,211,214],"span",{"class":212,"line":213},"line",1,[210,215,216],{},"# 冒烟：接口连通性、流式事件、错误码\n",[210,218,220],{"class":212,"line":219},2,[210,221,222],{},"python scripts\u002Facceptance_smoke.py --base-url http:\u002F\u002F127.0.0.1:18000\n",[210,224,226],{"class":212,"line":225},3,[210,227,229],{"emptyLinePlaceholder":228},true,"\n",[210,231,233],{"class":212,"line":232},4,[210,234,235],{},"# 端到端：完整问答链路\n",[210,237,239],{"class":212,"line":238},5,[210,240,241],{},"python scripts\u002Fapi_e2e_smoke.py --base-url http:\u002F\u002F127.0.0.1:18000\n",[10,243,244],{},"人工验证拒答策略：问一个知识库里明确没有的问题（比如「公司明年会不会上市」），\n预期看到的是拒答或转人工提示，而不是一段编造的答案。再问一个费用类问题，\n观察回答里有没有区分「已确认」和「待确认」。",[22,246,247],{"id":247},"取舍与边界",[249,250,251,258,264],"ul",{},[252,253,254,257],"li",{},[14,255,256],{},"三段置信度是启发式，不是校准过的概率","：它擅长排序和拦截明显不足的证据，\n不要把它当作可以直接对外汇报的准确率指标。",[252,259,260,263],{},[14,261,262],{},"拒答率会上升","：门槛调高的直接后果是「本来能答的也拒答了」。\n这是产品决策，不是技术决策，需要业务方一起定。",[252,265,266,269],{},[14,267,268],{},"参数透传牺牲了封装","：加一个新的检索参数要改多个文件，\n换来的是「主链路可读、调试链路同源」。",[10,271,272],{},"下一篇讲那些不在主链路上、但决定了系统能不能长期运行的工程件：\n缓存、知识库版本、租户隔离和 Trace。",[274,275,276],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":38,"searchDepth":219,"depth":219,"links":278},[279,280,281,282,283,284,285,286,287],{"id":24,"depth":219,"text":24},{"id":47,"depth":219,"text":48},{"id":80,"depth":219,"text":81},{"id":97,"depth":219,"text":98},{"id":130,"depth":219,"text":131},{"id":156,"depth":219,"text":157},{"id":188,"depth":219,"text":189},{"id":201,"depth":219,"text":201},{"id":247,"depth":219,"text":247},"2026-07-03","拆分 API、QAService 与 Pipeline 的职责，并通过置信度、引用和降级策略控制错误答案风险。",false,"md","project-chapter",{"migration":294},{"generator":295,"sourceSha256":296},"nuxt-site\u002Fscripts\u002Fmigrate-content.mjs","26b4ab207a5dfde7885559ef1019a87015ab05edaa6a2bc9b6d965fbcd758953","\u002Fprojects\u002Frag-knowledge-base\u002F06-主链路编排与置信度",null,"projects",{"title":5,"description":289},"content\u002Fprojects\u002Frag-knowledge-base\u002F06-主链路编排与置信度.md","projects\u002Frag-knowledge-base\u002F06-主链路编排与置信度",[304,305,306],"RAG","Prompt","意图识别",36,"3_EOHcVJVnOaipXESp2L83HZkrEGce4FTA-Ouw6TUCA",1791279145430]