DeepSeek+RAG构建政策问答系统:从切块到部署的工程实践 简介这是一份围绕政务数字化场景的DeepSeek案例详解PDF主要面向政务信息化从业者、AI应用开发者和NLP方向研究者。内容以构建政策问答大脑为主线先介绍政务数字化的背景与政策问答大脑的作用再剖析DeepSeek的核心架构、学习机制及语义理解与生成能力随后按技术架构、数据层、模型层、服务层、应用层逐层拆解覆盖数据收集、清洗、标注、特征工程、模型训练与优化并给出功能实现代码示例、系统集成与容器化部署方案。最后还通过准确性、及时性、易用性等评估指标和问卷、日志分析等采集方法验证群众满意度提升38%的效果。资源合计1个PDF文件共30页打包大小约1.89MB目录结构完整清晰。目前已有64人学习适合需要参考大模型政务落地全流程的读者使用。1. 政策问答大脑不是聊天机器人DeepSeek在政务数字化里的正确打开方式政务场景里群众问政策背后藏着一个 DeepSeek 最容易翻车、也最能体现工程价值的问题回答必须准、必须有出处、还得跟得上文件更新。群众问法口语化像我退休了想把户口迁回去怎么弄答案却分散在几十份文件、几千个条款里。只把 DeepSeek 接上就上线得到的多半是流畅但没依据的答复。政务数字化项目里跑通的做法是用 DeepSeek 做生成、RAG 做知识注入政策文档切成条款块走向量召回→重排→生成管线满意度提升才有工程保障。下面把切块、检索、部署、提示词、评价闭环讲透适合在给政企客户搭知识问答的工程师。2. 用DeepSeek搭政策问答RAG先拆清数据、检索、生成三层2.1 为什么选RAG不选微调政策问答的三个硬约束政策问答和普通智能客服有一个本质差异内容会定期更新、答案必须可溯源、错了要能快速下线。微调模型在这三点上都不占优。一次微调从数据清洗、训练到评测14B 规模的模型在单机多卡上也要跑几天政策文件一个月更新一批意味着每个月都要重推一遍人力成本扛不住。RAG 没有这个问题新文件入库就是切块→向量化→写入索引十分钟内新政策就能被检索到。第二个约束是溯源。微调把知识揉进了参数模型回答时你无法指出它依据的是哪一条RAG 的每个片段都带着文件名、条款号、发布日期答案可以精确回链到原始文件。第三个约束是撤回。文件废止了微调模型不知道RAG 只需把对应切块的 status 字段置为已废止并在检索前过滤。所以对政策问答这类强时效、强溯源的场景RAG 是默认解微调只在你想改变模型表达风格时才值得考虑。2.2 政策文档切块按条拆分与元数据设计政策文件通常由文件头文号、发文机关、成文日期、正文章、节、条、附件三部分组成。切块时最忌讳按固定字数硬切比如每 500 字一刀很容易把第十五条从中间劈开检索回来的片段没有完整条义生成阶段只能靠猜。常见做法是先把第X条作为锚点拆开再对超长条款做二次切分。import re from langchain_text_splitters import RecursiveCharacterTextSplitter def split_policy_chunks(doc: dict) - list[dict]: doc 需包含 title、content 等字段 text doc[content] # 用正则前瞻从第X条处切开条款号保留在每段开头 parts re.split(r(?第[一二三四五六七八九十百零0-9]条), text) splitter RecursiveCharacterTextSplitter( chunk_size400, chunk_overlap80, separators[\n\n, \n, 。, ], ) chunks [] for part in parts: if len(part) 30: continue m re.match(r第[一二三四五六七八九十百零0-9]条, part) article_no m.group(0) if m else 未标条款 if len(part) 500: chunks.append({article_no: article_no, text: part}) else: # 长条款按句号/分号二次切分编号加后缀避免重复 for i, sub in enumerate(splitter.split_text(part)): chunks.append({article_no: f{article_no}-{i}, text: sub}) # 追加文档级元数据 for c in chunks: c.update({k: v for k, v in doc.items() if k ! content}) return chunkschunk_size 定 400、overlap 定 80 是中文场景的起步值。原因有两个一是 embedding 模型有最大序列长度限制bge-m3 虽然支持到 8192 token但很多团队用的 text2vec-large-chinese 上限只有 512 token400 字中文约 600 token再大就会被截断二是生成阶段要拼 5 个片段进上下文每片 400 字共约 2000 字DeepSeek 处理起来游刃有余且响应快。overlap 用 80 是为了跨段语义比如一个条件状语落在段尾、结论落在段首。条款号是后面根据第X条引用的锚点切碎了引用就没法定位。注意切块的 chunk_size 必须和 embedding 模型的最大序列长度对齐而不是和生成模型对齐。先按 400/80 跑通再看召回去调。切块只是第一步真正让检索精准的是元数据。政策文件跨年常出现新文件替代旧文件同一个事项新旧两份都在库里不把旧的标成已废止模型就可能引用失效条款这是满意度掉分最隐蔽的一个原因。元数据字段示例值在检索中的作用doc_idGW-2024-015去重、答案回链title《区级政务服务事项办理指南》引用展示issuer区政务服务中心来源可信度判断publish_date2024-06-30时效过滤status有效 / 已废止检索前过滤article_no第十五条定位到条款category户籍 / 社保 / 公积金业务域过滤2.3 向量关键词混合检索解决口语问法和文号精确匹配政策问答的检索和普通文档检索有个明显差异群众的提问口语化户口本和文件里的居民户口簿词面完全不同纯关键词搜不到必须靠向量模型做语义映射。但反过来文件里的文号如〔2024〕12号是精确标识embedding 对这类短编码的检索效果很差容易召回一堆看起来像的无关文档。所以混合检索是标准做法向量走语义BM25 或稀疏向量走词面精确匹配最后用 RRF 融合排序。from qdrant_client import QdrantClient, models client QdrantClient(host10.0.0.8, port6333, prefer_grpcTrue) hits client.query_points( collection_namepolicy_chunks, prefetch[ models.Prefetch(queryquery_embedding, usingpolicy_vec, limit20), models.Prefetch(queryquery_raw_text, usingkeywords, limit20), ], querymodels.FusionQuery(fusionmodels.Fusion.RRF), limit10, with_payloadTrue, )query_embedding 是用户问题经 embedding 模型转成的向量写入索引和检索时必须用同一个模型否则向量空间不一致召回成绩直接崩。prefetch 里两路各取 20 条RRF 融合后取前 10 条意图是让语义相关但词面不匹配和词面精确但语义远的两类候选都保留到重排阶段再裁决。如果项目初期不想上 Qdrant也可以用 FAISS 加 rank_bm25 顶一阵但生产环境建议 Qdrant 或 Milvus因为过滤条件status、category和持久化支持更完整这两个能力是政策时效性的前提。2.4 生成层上下文组装让DeepSeek引用有出处检索完成后生成层不能把片段简单拼接而是要给每个片段标注来源让 DeepSeek 在生成时看得到依据、贴得上引用。context \n\n.join( f[来源{c[title]} {c[article_no]} f({c[publish_date]}{c[issuer]})]\n{c[text]} for c in top5 ) user_prompt f请依据下面的【政策依据】回答群众提问答案中引用到的条款必须给出出处。 【政策依据】 {context} 【群众提问】 {question} 把《文件名》第十五条、发布日期拼进上下文而不是只拼正文是有意为之。DeepSeek 会学着这个格式在答案里输出带书名号的引用后续做引用校验时才能用正则提取书名号和检索命中的标题做比对。注意不要把所有候选都塞进去生成阶段给 5 条质量最高的即可塞 10 条反而引入噪声模型容易在无关条款里找答案输出更长更绕。3. DeepSeek API调用与本地部署参数表、命令和对话上限处理3.1 DeepSeek API怎么调用三行代码接上开放平台网上 DeepSeek 使用教程大多停在网页版聊天真正落到工程接入核心就三件事API key、base_url、模型名。DeepSeek 开放平台提供的是 OpenAI 兼容接口不用引入专用 SDKopenai 库改两个配置就能用LangChain、LlamaIndex 里的重试、日志组件全部复用。from openai import OpenAI client OpenAI( api_keysk-xxxxxxxx, # DeepSeek 开放平台申请的 key base_urlhttps://api.deepseek.com # OpenAI 兼容接口地址 ) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_prompt}, ], temperature0.2, max_tokens1024, ) print(resp.choices[0].message.content)model 字段用 deepseek-chat对应通用对话模型适合在线答复。API 还提供 deepseek-reasoner 深度推理模型但政策问答主流程不建议用它推理模型会先产出思维链响应变慢而且对 temperature 这类采样参数不敏感可控性弱。reasoner 更适合离线分析比如拆解群众复杂问句而不是在线答复。base_url 目前是 https://api.deepseek.com以官方文档为准。3.2 政策问答的DeepSeek参数表temperature、top_p和max_tokens参数设置直接决定答复的确定性政策问答不是创意写作同一个问题今天答一个样、明天答另一个样回归测试都没法做。参数API默认值政策问答推荐值调参逻辑temperature1.00.10.3政策答复要唯一温度越低答复越确定top_p1.00.6 左右和低温搭配收窄采样空间max_tokens40968001500限制答复篇幅避免长而无物frequency_penalty00政策文本本就有固定句式不额外惩罚presence_penalty00保持引用原文的重复表达streamfalsefalse渠道层需要打字机效果时再开temperature 从 1.0 降到 0.2 后同样的输入基本能一致复现同一个答复骨架这对 evaluation 回归很关键。但不要直接设 0完全贪心采样会让长答案出现局部重复0.2 是兼顾确定性和自然度的常见落点。如果答复需要结构化输出比如材料清单要 JSON 给前端渲染可以请求参数里带 response_format{type: json_object}同时 prompt 里必须给出 JSON 示例否则模型不知道你要什么结构。3.3 本地部署DeepSeek从ollama试跑切到vLLM上生产什么场景要本地部署政策数据敏感不能出内网、公网 API 链路在业务高峰期不可控、按调用量测算长期成本高于自建。政务项目里数据不出域是实际约束本地部署蒸馏版是常见做法。先在本机用 ollama 验证效果ollama run deepseek-r1:7b这条命令拉起 7B 蒸馏模型显存 8G 左右就能跑适合验证提示词和切块在本地模型上的表现。但 ollama 在并发、前缀缓存上偏弱上生产用 vLLM 更合适vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-14B \ --tensor-parallel-size 1 \ --max-model-len 16384 \ --gpu-memory-utilization 0.9 \ --served-model-name policy-qa \ --port 8000启动后同样用 OpenAI SDK 访问base_url 改为 http://localhost:8000/v1模型名填 policy-qa。和 API 路线相比代码层面只差一个 base_url这就是兼容接口的工程价值。提示别把本地部署 DeepSeek理解成部署 V3 主模型。671B 的 MoE 不是单机显存能装下的本地部署实操对象是 R1 蒸馏版选型先跑验证集看效果再定尺寸。模型R1-Distill-QwenFP16 显存量化后定位7B~16GB~8GB试点验证、低并发14B~32GB~16GB单机生产默认选型32B~64GB~32GB答复质量要求极高、有整卡预算DeepSeek-V3多机集群不可行走 API 路线3.4 对话长度上限与多轮追问会话压缩而不是清空政策问答主流程是单轮问答但群众经常追问那要带什么材料周六能办吗追问省略了主语单独生成必然跑偏。常见做法是维护会话历史把上一轮问答拼进当前请求。这里有一个 DeepSeek 的典型坑上下文达到对话长度上限后API 会提示达到对话长度上限请开启新对话。政务场景不能把群众晾在那正确做法是会话压缩import json def condense_history(messages: list, client: OpenAI) - list: 把最近若干轮对话压成摘要替换原历史 resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: 把对话压缩成100字内摘要要点群众要办的事、已确认条件、待补材料。只输出摘要。}, {role: user, content: json.dumps(messages[-6:], ensure_asciiFalse)}, ], max_tokens150, temperature0, ) return [{role: system, content: 前情摘要 resp.choices[0].message.content}]触发时机建议在上下文长度到阈值的 70% 就提前压缩不要等报错再补救。摘要保留三个要素要办的事、已确认的条件、待补的材料正好覆盖群众追问时最常缺的主语和前置信息。压缩后历史对话虽然丢了细节但前情摘要作为 system 消息注入追问时模型依然知道上下文。4. 提示词模板与重排序把准确率和可信度调出来4.1 政策问答提示词模板约束条款比引导话术重要提示词里引导是次要的约束才是核心。政策问答的提示词要明确四条边界只用依据、先结论后引用、依据不足就转人工、数字必须抄原文。SYSTEM_PROMPT 你是政务政策问答助手。回答必须遵守以下约束 1. 只能依据【政策依据】中给出的文件内容作答不得使用文件外的知识补充 2. 先给出结论再列出依据引用格式根据《文件名》第X条文号/日期原文要点 3. 依据不足时回答知识库中未查到相关政策已为你转人工不得编造 4. 材料清单用分号分隔的短句列出避免大段文字 5. 涉及补贴金额、办理时限的数字必须引用原文数值不得换算或估算。第 1 条是防幻觉的总闸。RAG 时代的幻觉主要来自文件外补充模型看到相似内容会顺手把训练时见过的知识写进去直接禁止即可。第 3 条对应拒答设计模型在依据不足时倾向于给一个大概政企场景里大概就是差评来源明确要求转人工反而保住了满意度。第 5 条最容易忽略模型会把每年最高补贴2000元换算成每月约166元一换算就错必须要求引用原文数值。4.2 重排序参数从20条候选中选出5条可信片段向量召回的双塔结构把问题和文档分别编码语义细节有损失重排序阶段用 CrossEncoder 把问题和片段拼在一起过 Transformer能建模 token 级交互精度高一个档次。政策文件里配偶和直系亲属这种细微差别往往在重排阶段才分得清。from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-v2-m3) pairs [(question, hit.payload[text]) for hit in hits] scores reranker.predict(pairs) ranked sorted(zip(hits, scores), keylambda x: x[1], reverseTrue) top5 [h for h, s in ranked[:5] if s 0.3]top5 就是最终进入生成上下文的片段。score 阈值 0.3 是起步值不要直接抄应该拿 200 条历史真实问答做标注后校准统计可回答和不可回答两类样本的分数分布取分界点。阈值宁可偏高转人工还有兜底答错直接消耗满意度。4.3 答案引用校验用正则表达式拦下幻觉引用即使提示词里写了只依据政策依据模型偶尔还是会引用上下文之外的文件名。政企场景里引用了一篇不存在的文件比不引用更糟。在生成后置处理里加一道校验成本极低import re cited_titles set(re.findall(r《([^》])》, answer)) retrieved_titles {c[title] for c in top5} if not cited_titles retrieved_titles: # 引用了检索上下文之外的文件降级处理 logger.warning(越界引用: %s, cited_titles - retrieved_titles) answer fallback_answer(question)原理很简单答案里的书名号标题必须属于本次检索命中的片段集合出现差集就说明模型越界了。降级动作可以是重新生成一次把温度降到 0.1也可以直接转人工。这一步配合 4.1 的引用格式要求能拦住绝大多数幻觉引用。4.4 拒答阈值检索分不高时直接转人工很多人舍不得拒答觉得答个大概也比转人工强在政策问答里这个判断是错的。检索阶段 hits 为空、或者重排后最高分低于阈值时不应该进入生成阶段直接走转人工。硬答的失败率远高于转人工且失败一次群众就会对整个渠道失去信任。拒答话术要给出明确出口知识库中未查到该事项的办理政策已为你转接人工窗口稍后会有工作人员联系您。既承认没查到又给了下一步动作。5. 满意度提升38%的度量方式指标、badcase回流和渠道接入先说结论满意度提升 38% 不是模型换出来的是工程指标改善后自然累积的业务结果。想复现先把指标定义清楚。技术指标用 RAGAS 每轮发版前跑回归业务指标从渠道后台直接统计。from ragas import evaluate from ragas.metrics import faithfulness, context_precision result evaluate( dataseteval_set, # 每轮发版前跑的标注问答集约200条 metrics[faithfulness, context_precision], )层级指标计算口径建议目标技术faithfulness 忠实度回答与检索依据的一致性≥0.9技术context_precision召回片段中与答案相关的比例≥0.85业务首轮解决率未转人工的会话占比≥85%业务平均响应时长提问到收到答复的时间秒级业务差评率评价渠道中差评占比3%两类指标要分开看faithfulness 高但满意度不高通常是答对了但没解决——比如只给了政策条文没说怎么办转人工率高优先查召回而不是查生成。用这两个视角切 badcase定位会快很多。badcase 回流是满意度提升的发动机。每周从渠道后台导出转人工、差评、追问超3轮的会话标注后分三类处理切块问题条款拆错、缺 status改数据检索问题该召回没召回到调切块参数和重排阈值生成问题依据对但答得绕改提示词。两周一个迭代每次只动一类变量避免数据、提示词同时改完最后不知道哪个变量起了作用。政策更新要形成固定动作新文件发布当天入库走切块→向量化→写入→上线四步脚本同时把同主题旧文件的 status 置为已废止。这一步没做满意度会在政策更新后第一周明显回落因为群众问的全是新规模型答的还是旧文。渠道层常见做法是企业微信或公众号做咨询入口后端接 DeepSeek 的 RAG 服务好差评按钮直接挂在答案卡片上差评自动触发工单转人工同时该对话进入 badcase 池。实践里一个高性价比的细节在答案卡片底部显示依据《文件名》第X条并支持点击查看原文群众看到出处后差评率会明显下降因为可信度是满意度里权重最高的那个维度而 RAG 的可溯源能力恰好是纯 LLM 给不了的。本文还有配套的精品资源点击获取