DeepSeek + RAG 构建政策文件智能解读系统:从选型到部署实践
2026/9/24 12:45:35 网站建设 项目流程

简介:围绕DeepSeek在政务场景中的落地应用,这份PDF全面呈现政策文件智能解读系统的建设方法论。文档首先从政务数字化背景与政策解读需求切入,概述DeepSeek的神经网络架构、训练机制,以及在自然语言处理、计算机视觉等领域的应用案例;随后按系统建设流程,逐一讲解需求分析、总体架构设计、数据收集与清洗标注、模型训练与优化、功能模块开发、系统集成部署、测试评估和案例实践等内容。其中对政策文件上传管理、智能解读、检索查询、可视化展示、数据安全、多语言支持等关键环节均给出设计思路与实施要点,并配有地方政务案例的效果展示与经验总结,帮助读者形成从需求到落地的完整认知。资源为1个PDF文件,共37页,约2.06MB,目录清晰、内容完整,已有147人学习浏览,适合政务信息化从业者、AI应用开发人员及高校相关专业学生参考学习。

1. 政务数字化实践:为什么政策文件解读最适合先上大模型

政务数字化喊了好几年,真正落到办事人员手上的工具并不多。一个很具体的痛点:政策文件以 PDF 形式下发,量大、更新快、表述严谨,新来的同事翻几十页文件找一条申报条件,老同事靠经验记「大概在第三部分」。这套工作流消耗的时间成本极高,而 DeepSeek 这类开源大模型恰好把「读文件、找依据、答问题」这件事的成本打了下来——不需要微调,不需要从头训练,把 PDF 解析、向量检索和提示词工程串起来,就能搭出一个政策文件智能解读系统。

这篇文章要讲的就是这套系统的完整建设路径:从选型、架构到落地,包含我实际部署中踩过的坑和调过的参数。适合政务信息中心的开发人员、做企业政策申报系统的团队,以及任何需要在内部网络里处理大量非结构化政策文档的从业者。我不写理论框架,只写能照着复现的操作。

2. 选型与整体架构:DeepSeek 做政策解读的合理性和系统组成

2.1 为什么选 DeepSeek:可私有化部署是政务场景的硬门槛

政务场景对数据出境和数据安全的要求比一般企业严格得多。政策文件可能涉及尚未公开发布的征求意见稿、内部口径汇总,甚至包含地方财政补贴的具体金额和分配方案,这些数据不可能送到公网 API 上去跑。DeepSeek 这类开源模型的最大价值就在这里——权重完全开放,可以部署在政务云或内网的 GPU 服务器上,做到数据不出域。相比之下,调用公网闭源 API 虽然在效果上也可能不错,但合规审核这一关大概率过不了。

选 DeepSeek 而不是其他开源模型的另一个理由是中文长文本理解能力。政策文件的特点是长句子、多重复、大量「原则上」「视情」「按照有关规定」这类带有弹性空间的表述,对模型的语义理解要求高。DeepSeek 系列模型在中文语料上的表现属于第一梯队,特别是在零样本提取和指令跟随方面,做「给定一段政策原文,提取申报条件」这类任务,实测下来输出的结构化程度高于同参数规模的其他开源模型。

还有一个现实考虑:部署门槛。如果用满血版模型,需要多张高性能显卡,预算不够的团队可以直接用量化版本或中等尺寸版本跑 CPU 推理,速度慢一些但能用。这一条对经费有限的区县级政务单位非常重要。

2.2 系统四层架构:从 PDF 到问答结果的完整链路

政策文件智能解读系统的架构可以拆成四层:

数据接入层负责收集和整理政策源文件,包括 PDF、Word、网页通知,处理后统一格式。知识构建层把非结构化文本切成小段、清洗、向量化,存入向量数据库。检索推理层接收用户问题,先从知识库召回相关片段,再拼进提示词模板交给大模型生成答案。应用交互层面向最终用户,提供一个简单的问答界面,或者通过 API 给其他业务系统调用。

整个系统的核心技术栈围绕 RAG 展开。为什么不直接微调?因为政策文件更新频繁,一个县一年要发几百份新文件,每来一批新文件就微调一次模型,从数据标注到训练调参的周期太长,成本也高。RAG 的更新逻辑是增量的——新文件解析入库就立刻生效,模型本身完全不用动。而且 RAG 能在回答时标注「根据 XX 文号第 X 条」,让用户能回到原文核对,这对政务问答场景来说是刚需。

核心组件选型如下表所示,都是我在实际项目里验证过可用的组合:

组件推荐选型选择理由
大模型DeepSeek-R1 系列蒸馏版 / DeepSeek-V3 量化版中文理解强,可私有化部署
PDF 解析PyMuPDF + PaddleOCR 组合PyMuPDF 处理文本型 PDF,OCR 兜底扫描件
向量化bge-m3 本地部署中文向量效果好,支持多种粒度
向量数据库Milvus 或 Elasticsearch支持混合检索和过滤条件
应用框架FastAPI + 后端(Python)政企环境熟悉 Python 技术栈,生态完整

这套架构没有引入太重的东西。如果单位里已经有 Elasticsearch 在跑,直接复用它的向量检索能力,少维护一个组件。如果是从零开始,Milvus 部署更简单,官方有 Docker Compose 一键启动的配置。

2.3 最小可用链路:三个命令先跑通再谈优化

很多项目死在「设计得太完美,第一步迈不出去」。我的建议是第一天先搭一条最小链路,不管效果好不好,先把路走通。最小链路只需要三件事:把 PDF 文本抽出来、把文本切块后向量化存起来、接上 DeepSeek 做问答。

第一步,启动 DeepSeek 模型服务。以 vLLM 部署为例,一条命令启动 OpenAI 兼容的 API 服务:

vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-14B \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85

逻辑说明:vllm serve是 vLLM 0.6 以上版本提供的便捷命令,会自动加载模型并启动一个兼容 OpenAI 协议的 HTTP 服务。政务内网里其他系统对接时,可以直接用标准的 OpenAI SDK 指向这个服务地址。--max-model-len 8192表示上下文窗口长度,处理政策条文时建议至少设到这个值,太短会导致长段落被截断。--gpu-memory-utilization 0.85允许 vLLM 使用 85% 的显存,留出余量避免 OOM。如果显存只有 16G,改用 7B 或 8B 的量化版本,参数相同。

第二步,用 FastAPI 写一个最小的问答接口:

from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8000/v1", api_key="EMPTY", ) def ask_policy(question: str, context: str) -> str: response = client.chat.completions.create( model="deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", messages=[ {"role": "system", "content": "你是政策解读助手,严格依据提供的政策原文回答问题,不得编造内容。"}, {"role": "user", "content": f"政策原文:\n{context}\n\n问题:{question}"} ], temperature=0.1, max_tokens=1024, ) return response.choices[0].message.content

逻辑说明:temperature=0.1是为了让生成结果稳定,政务场景不需要创造性,需要的是每次回答尽量一致。max_tokens=1024给足输出空间,政策解读的回答通常几百字。注意api_key填什么都行,vLLM 默认不校验,这只是格式要求。

第三步,向量化并检索。用一个轻量的 python 脚本验证链路:

from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-m3") docs = ["第一章 总则 第一条 为规范专项资金管理...", "第二条 本办法适用于..."] embeddings = model.encode(docs, normalize_embeddings=True) print(embeddings.shape)

逻辑说明:SentenceTransformer是加载 embedding 模型最常用的方式,BAAI/bge-m3是智源开源的中文向量模型,支持 8192 长度输入,对长政策段落友好。normalize_embeddings=True做归一化,后续用点积算相似度时结果就是余弦相似度。输出维度为 [段落数, 嵌入维度],bge-m3 的维度是 1024 维。

这三步跑通,说明「PDF → 文本 → 向量 → 检索 → 生成」的技术链路是通的。后面所有工作都是在每一环上做精细化。

3. 政策文件解析与知识库构建:PDF 处理是整条链路最脏最累的活

3.1 政策文件 PDF 的三种类型:解析策略完全不同

政务场景的 PDF 远没有想象中那么规整。我把实际遇到的 PDF 分成三类,每一类的处理方式不一样:

第一类是「数字排版型」PDF,公文系统直接导出的,文字层完整,复制粘贴不会乱码。这种最简单,用 PyMuPDF 直接抽取文本就行。第二类是「扫描盖章型」PDF,纸质文件扫描成图片再合成的 PDF,肉眼看着清楚,但没有任何文字层。必须先 OCR。第三类是「混合型」PDF,主体是文字层但夹杂表格、流程图、红头文件的图片扫描页。最麻烦的是带红头的文件,红头部分经常是图片嵌在上面,而正文是文字——解析时容易把抬头丢了。

这里有一个很多新手会踩的坑:拿到 PDF 先看一下有没有文字层,不要上来就 OCR。OCR 又慢又容易出错,一个字错了在政策文件里可能就改变了申报条件的含义。快速判断文字层是否存在的方法很简单,用 PyMuPDF 抽取前几页文本,如果抽出来的字符数低于某阈值(比如每页少于 50 个字符),再决定走 OCR 流程。判断代码如下:

import fitz def has_text_layer(pdf_path: str, check_pages: int = 5) -> bool: doc = fitz.open(pdf_path) total_chars = 0 for page in doc[:check_pages]: text = page.get_text() total_chars += len(text.strip()) doc.close() return total_chars / check_pages > 50

逻辑说明:fitz.open打开 PDF 文件,doc[:check_pages]取前五页做抽样检查,page.get_text()返回该页所有文本内容。如果平均每页字符数小于 50,基本可以断定是扫描件,需要走 OCR。50 这个阈值是我试出来的经验值,考虑了一些封面页、空白页的干扰。

3.2 文本抽取与清洗:PyMuPDF 为主、PaddleOCR 兜底的完整流程

文本型 PDF 的抽取比较简单,但抽完要洗。政策文件里有页眉页脚、发文字号、印章说明这类与正文无关的内容,还有一些「第 X 页 共 Y 页」的页码标记,这些都要处理掉。我一般按行处理,过滤掉纯数字、包含「第 页」字样的行,以及重复出现的页眉内容。

对于扫描件,用 PaddleOCR 做完整的 OCR 管线。PaddleOCR 虽然后期维护节奏慢了下来,但中文识别能力仍然是最强的开源方案之一,模型体积和推理速度都在可接受范围内。推荐用 PP-OCRv4 的中文模型,精度比 v3 有明显提升,特别是对仿宋_GB2312 这类政务常用字体的识别效果改善很大——这种字体细长、笔画密集,用通用 OCR 模型经常识别错字。完整的抽取流程写在下面:

import fitz from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang="ch", show_log=False) def extract_pdf_text(pdf_path: str) -> str: doc = fitz.open(pdf_path) full_text = [] for page_num, page in enumerate(doc): text = page.get_text().strip() if len(text) < 50: # 扫描页:渲染成图片后走 OCR pix = page.get_pixmap(dpi=200) img_path = f"temp_page_{page_num}.png" pix.save(img_path) result = ocr.ocr(img_path, cls=True) lines = [line[1][0] for res in result for line in res] if result else [] full_text.append("\n".join(lines)) else: full_text.append(text) doc.close() return "\n".join(full_text)

逻辑说明:对每一页先尝试抽取文字层,len(text) < 50判定为扫描页或图片页。扫描页用page.get_pixmap(dpi=200)渲染成 PNG 图片,dpi 选择 200 是精度与速度的平衡点——150 太糊识别容易错,300 效果好但慢一倍。然后交给ocr.ocr()识别并拼接文字。这段代码是完整可跑的,但如果 PDF 页数很多,建议加个缓存机制,别重复渲染同一页。

抽取后的清洗我有几条经验:把全角括号统一成半角括号,把「(一)」「1.」这类序号的变体格式统一,去掉行首行尾的空白字符。政策文件里「第一条」「(一)」「1.」这些层级标记非常重要,它们是后续分块的主心骨,清洗时千万别把它们当噪音删掉。

3.3 分块策略:政策文件必须按「条」切,不能按固定字数切

这是整个系统里对最终效果影响最大却最容易被低估的环节。很多通用 RAG 教程教大家按固定窗口大小切块,比如每 512 个字符一块、重叠 128 个字符。这种做法对新闻文章、技术博客没问题,但用在政策文件上会出大问题——政策文件的最小信息单元是「条」,一条可能几十字也可能几千字,一条里往往同时包含适用对象、条件、时限多个要素。按固定窗口切,极有可能把同一条内容切到两个块里,导致检索时只召回一半,模型回答问题时看到的上下文不完整,给出的解读就是错的。

我实测过的正确做法是用「层级感知分块」:先按章节大标题切出章,再在章内部按「第 X 条」切出条,最后对超长条做二次切分。实现思路用正则配合状态机:

import re def split_by_article(text: str): # 匹配 "第X条" 作为切分点 pattern = re.compile(r'(第[一二三四五六七八九十百零\d]+条)') segments = [] current = [] for line in text.split("\n"): line = line.strip() if not line: continue if pattern.match(line): if current: segments.append("\n".join(current)) current = [line] else: current.append(line) if current: segments.append("\n".join(current)) return segments

逻辑说明:这个脚本的逻辑是逐行扫描,遇到「第 X 条」开头的行就开始一个新段落,其他行追加到当前段落。比用正则一次性切更可靠,因为「第 X 条」可能是行首也可能是夹在段落中间的,逐行处理能处理前者。切完后给每段生成一个元数据标记,如{"文号": "X政发〔2024〕12号", "章节": "第三章", "条款": "第八条"},这个标记后面做过滤检索时非常有用。

分块之后每块的文本量级差异很大。把chunk_size设定为基于 token 数而不是字符数,用tokenizer统计后再决定要不要二次拆分。我在项目里预定义了max_tokens = 500的阈值,超过这个长度就要继续切。二次切分的时候按标点层级找切点:句号 > 分号 > 逗号,保证切出来的块语义完整。

4. 检索与生成:向量召回、重排序和提示词模板设计

4.1 向量检索参数:top_k、相似度阈值与 Embedding 选型

政策解读的检索场景有自己的特殊要求。通用问答可能允许「相关就行」,但政策场景里用户问「高新技术企业的认定条件是什么」,如果检索结果里只有半句话,模型就有很大概率瞎编另一半。所以检索阶段的目标是:宁可少召回,不可漏关键信息。

Embedding 选型上,bge-m3 是当前中文场景比较稳妥的选择。它在长文本上的支持比早期模型好很多,向量维度 1024,检索效果比 OpenAI 的 text-embedding-3-small 在中文场景里不落下风,且可以完全本地部署。如果单位硬件条件有限,也可以用 bge-large-zh-v1.5,512 维,显存占用更小,效果稍逊但对政策文件的区分度仍然够用。

检索参数方面,我建议设置两个关键数值:

def retrieve(query: str, top_k: int = 8, min_score: float = 0.60): query_vec = embedding_model.encode([query], normalize_embeddings=True) # 假设 doc_collection 是向量数据库的集合对象 results = doc_collection.query( query_embeddings=query_vec.tolist(), n_results=top_k, include=["documents", "metadatas", "distances"] ) filtered = [ r for r in results if (1 - r.distance) >= min_score ] return filtered

逻辑说明:min_score=0.60这个阈值是经验值。1 - distance是把 Milvus 等数据库返回的距离转成相似度分数,0.60 表示检索结果与问题的相关度达到六成以上才会被送入大模型。阈值调得太低(比如 0.4),一些弱相关的片段会混进来,浪费上下文窗口不说,还可能把模型带偏;调得太高(比如 0.8),很多真实相关的政策条文因为表述方式和问题差距较大而没有被召回。0.60 是我跑了多轮测试后觉得比较稳的设定,如果你的政策库非常垂直(比如全是工信口的文件),可以尝试调到 0.65。

top_k的市场共识是 6 到 10 之间。政务场景里我习惯取 8,因为政策文件有很多「但同时应当满足以下条件」这种跨条款的关联,取太少容易漏,取太多提示词太长,模型容易分不清主次。

4.2 重排序:只用向量检索不够,BM25 与 bge-reranker 的互补

向量检索擅长找语义相似的内容,但存在一个天然弱点:如果政策原文用了「从业满三年」这种表述,而用户问的是「需要几年工作经验」,向量嵌入能把它匹配上,但如果用户问「工作年限有什么要求」,语义距离远一点,向量召回可能排到很后面。这时候需要关键词检索来互补,传统做法是 BM25 算法,它在精确匹配上比向量检索可靠得多——政策文件里「资质」「备案」「专项资金」这类术语,用关键词一查一个准。

我推荐用混合检索然后做融合,具体做法是:向量检索取回 top 20,BM25 也取回 top 20,合并去重后再用一个交叉编码器重排序。bge-reranker-v2-m3 是目前效果和性能比较平衡的重排序模型。重排序这一步能显著提升准确率,因为交叉编码器是让查询和文档一起过一遍模型,能捕捉到向量内积捕捉不到的细粒度相关性,代价就是推理速度慢,不适合对大量文档做排序——所以一定是先粗召回再精排。

融合和重排序的流程示意:

from rank_bm25 import BM25Okapi from cross_encoder import CrossEncoder bm25 = BM25Okapi([doc.split() for doc in all_docs]) bm25_hits = bm25.get_top_n(query.split(), all_docs, n=20) vector_hits = retrieve(query, top_k=20, min_score=0.0) # 先不过滤 candidates = deduplicate(bm25_hits + vector_hits) reranker = CrossEncoder("BAAI/bge-reranker-v2-m3") pairs = [(query, doc) for doc in candidates] scores = reranker.predict(pairs) ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True) final_context = [doc for doc, score in ranked[:8]]

逻辑说明:BM25Okapi是最常用的 BM25 实现,get_top_n直接返回评分最高的文档列表。向量检索这一步不要把min_score设得太高,目的是多召回候选,精排交给 reranker。CrossEncoder对每个 (查询, 文档) 对独立打分,输出一个相关性分数,按分数倒序取前 8 个作为最终送入大模型的上下文。这套流程的效果对比单纯向量检索,核心指标——回答被判定为「完全正确」的比例——能提升一到两成,值得做。

4.3 提示词模板:让模型用引用的原文说话,不会就承认不会

RAG 系统做出来以后,效果好不好要看两方面:上下文里的材料全不全,以及模型会不会「照着材料说话」。后者几乎完全取决于提示词写得怎么样。政策解读的提示词有一条铁律:强制模型引用原文。模型生成的每一条结论都必须对应到政策文件的文号、条款号和原文摘录,这样用户可以回到原文核对,也方便审核人员判断模型是不是在胡编。

我的提示词模板基本长这样:

你是政务政策解读助手。请严格按照提供的政策文件片段回答问题。 要求: 1. 优先引用原文表述,标注出处(文号+条款)。 2. 如果问题在提供的文件中找不到依据,直接回复"未找到相关内容",不要推测。 3. 如果问题涉及多个条款,分别列出来并说明条款之间的关系。 4. 回答保持书面语风格,不用口语化表达。 5. 不确定的信息不得补充说明。 政策片段如下: {context} 用户问题: {question}

这段提示词有几个点值得注意。第二点「未找到相关内容」的兜底非常重要——没有这一步,模型在检索不到内容时会脑补一段合理的政策条文,这在政务场景里是不可接受的。第五点「不确定的信息不得补充说明」也是我踩了雷之后加的:早期测试时模型回答「根据《XX办法》第十二条,申报企业应当具有独立法人资格」,但原文第十二条只写了「具有独立法人资格或视同法人单位」,模型把「视同」两个字漏掉了,这个信息就失真了。

另外,我在系统层面做了一个强制约束:在上文提示词之外,用 API 参数里的logit_bias或后处理逻辑,检测回答中出现的条款编号,确认是否存在于本次检索的上下文范围内。不在范围内的条款号直接标记为「疑似引用错误」,返回给用户前先拦截。这个兜底逻辑虽然粗暴,但在真实业务中非常管用。

5. 系统落地部署与避坑记录:从开发环境到政务内网的五道坎

5.1 部署模式选择与硬件估算

政务场景的部署方式主要取决于服务器在哪里。常见的有三种:政务云、独立内网服务器、本地工作站。我做过一个区级项目用的是政务云上的 GPU 节点,网络隔离做得很好,数据不出域但算力共享,高峰期有其他业务抢占资源,推理延迟波动比较大。另一个市级项目是独立机柜部署的,两张国产加速卡跑量化后的 14B 模型,稳定性和速度都可控。

模型尺寸和硬件的匹配关系,这里有一组经验数据可以作参考。7B 到 8B 的量化模型需要约 8G 显存,16G 单卡能跑;14B 模型 FP16 精度大约需要 32G 显存,量化到 INT4 后可以降到 12G 左右。如果只有 CPU 环境,能跑 7B 量化模型,生成一个 300 字回答大约要两三分钟,作为内部辅助工具不是不能用,但体验谈不上好。

向量数据库部署相对轻量。Milvus 单机版用 Docker 启动,8G 内存就够跑十万级向量的场景。政务单位手上的政策文件存量通常也就几千份,分块后一两万个向量,这数据量对 Milvus 来说毫无压力,不需要上分布式集群。

5.2 七个常见坑与对策

以下是这套系统在真实部署过程中最常见的七个问题,每个都按「现象→原因→解决」展开,基本都是我用真金白银换来的经验。

坑一:PDF 解析后出现大量乱码和缺字。现象是正文里「鼓励」「补贴」这类词变成了「���励」「补�贴」。原因很可能是 PDF 里的字体使用了自定义编码映射,PyMuPDF 拿到的字符映射表不全。解决方法是改用 OCR 管线处理这些异常页面,不要试图在字符层面修补。另外在解析时强制用 UTF-8 编码输出,避免后续写入数据库时丢字符。

坑二:向量检索总返回相似但错误的内容。现象是查「小微企业税收优惠」,召回的都是「中型企业税收优惠」。原因是政策文件里「小微」「中小微」「中小企业」这些词在语义上高度接近,embedding 模型分不清它们的差异。解决方法是构建一个同义词/易混淆词表,在检索时做实体级别的精确匹配增强——把文档里出现「中小微企业」的片段单独建一个关键词索引,用户问「小微企业」时先做一次精确匹配,匹配到了就把相似度分数加上一个权重。

坑三:回答引用了不存在的条款。现象是生成的回答标注「根据〔2024〕12号文第X条」,但打开原文件没有这一条。原因是模型在生成时把上下文里的信息记忆错乱了,自己组合了一个不存在的条号。解决方法是后面加一个数字校验层,用正则提取回答里的「第X条」和文号,去原文索引里比对,匹配不上就拦截并重新生成。我建议这个校验做成强制逻辑,不要只靠提示词约束——大模型在长文本生成中「记错出处」的概率比想象中高。

坑四:上下文窗口溢出,长文件被截断。现象是问一个涉及全文多个章节的问题时,模型回答「没有找到相关内容」。原因是嵌入进提示词的 8 个检索片段拼接后超过了模型上下文长度。解决方法是控制每个片段长度在 300 字以内,并动态调整top_k——用户问题越复杂,每个片段就越要精炼而不是增加数量。

坑五:政务内网无法访问外网模型仓库,模型下载困难。现象是部署服务器在隔离网络,huggingface-cli download直接超时。解决方法是提前在外网下载模型文件打包,用移动硬盘导入内网。注意模型下载时不要只下载权重文件,配置文件和 tokenizer 文件也要一起带进去,少了tokenizer_config.json启动时会报错。

坑六:并发高时 GPU 显存溢出导致服务崩溃。现象是几个用户同时提交长问题,vLLM 服务直接 OOM。原因是不加限制地接收请求,多个并发请求叠加占满显存。解决方法是启动时加--max-num-seqs参数限制并发数,再在应用层要做排队:

vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-14B \ --max-num-seqs 4 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192

逻辑说明:--max-num-seqs 4把并发请求数限制在 4 个,多余的请求会在 vLLM 内部排队等待。显存 24G 的卡跑 14B 模型时这个并发数可以让每个请求都稳定分配足够 KV cache,吞吐量和延迟是吞吐量和延迟的最佳平衡点。如果业务要求高并发,需要扩容多张卡或多节点做负载均衡,单卡硬扛不现实。

坑七:模型回答「官腔官调」但无法直接用于业务。现象是模型用一套正确的废话回答了具体问题,比如问「申报需要哪些材料」,回答「申报材料应齐全、真实、有效」。原因是政策原文就是这么写的,模型只是忠实复述。这是 RAG 系统在政策场景里的天然局限——原文对任何阅读者来说依然是原文。解决的思路是在提示词中追加指令,要求按「申报主体—条件—材料—时限」四要素列举。这不是模型能力问题,是提示词设计问题。

5.3 评测与回归机制:用一套固定题目盯住系统不跑偏

系统上线后最怕的不是效果差,而是效果越来越差——换了一版 embedding 模型、调整了分块参数、更新了向量库中的文件、甚至只是改了检索权重,都可能让某个之前答对的题开始答错。而且没人能说出来是哪一步导致的。所以我在项目里固定保留了一套评测集,每次变更后跑一遍再放行,这套方法比任何代码评审都管用。

评测集的结构:每个政策文件抽取 5 到 10 个问答对,问题覆盖「条件查询」「流程查询」「时效查询」「豁免查询」四类。标注答案时写清楚依据的条款号和原文摘录。评测时对系统回答的判定标准不是「写得差不多的内容」而是「依据、结论、表述三个纬度是否都正确」。打分逻辑代码示意:

def evaluate(question: str, llm_answer: str, true_answer: str) -> bool: # 抽取 LLM 回答引用的条款号 cited = re.findall(r'第[一二三四五六七八九十百零\d]+条', llm_answer) # 判断 LLM 引用的条款号是否在标准答案引用的条款集合中 return all(any(c in ref for ref in true_answer['citations']) for c in cited)

逻辑说明:这个简化版本只判断引用条款号是否命中标准答案的范围,是比较严格的判定方式——只要有一条引用的条款不在集合内,这题就算错。实际使用中你可以调得更细:核心结论对、补充引用多余算半对,具体按业务容忍度来定。

每次跑评测集,把没有通过的题目记录在案。这是很典型的「回归测试」思路:如果上一次提交所有题都过了,本次提交同一道题却答错了,那一定是系统变更引入的回归,必须追查定位。这套机制配合按文件增量更新的向量库,可以保证系统在长期运行中日拱一卒、稳定不出大坡。

6. 让系统从「能答」到「好用」:四个值得投入的进阶方向

基础链路跑通后,系统的形态已经可以满足内部辅助查询需求了。但如果想把使用体验做到政策申报人员真正愿意天天用,还有四个方向值得投入精力。

第一个方向是政策关联图谱。政策文件之间常有引用关系,比如某区的实施细则引用市里的管理办法,市里的办法又引用省里的条例。现在 RAG 系统回答问题时只检索到了区里的细则,如果没有把上级文件的上下文一并拉入,解读可能不完整。我试过在文档元数据中维护「关联文件」字段,检索时先把关联文件抓取进来做二次扩展。这个方案不算复杂,但对解读质量提升非常明显,特别是处理多层级的政策体系时基本是刚需。

第二个方向是表格与数据抽取。大量政策文件的核心内容在表格里——补贴标准、申报时间节点、审核流程。当前的分块主要基于文本,「表格里的内容」经常被解析得七零八落。尝试过把表格单独识别出来转成结构化 JSON 存入附件库。用户问「省级专精特新补贴多少钱」时,模型不再翻文本,而是直接读 JSON。这块对准确率的提升仅次于重排序。

第三个方向是问题日志驱动的知识库迭代。系统使用一段时间后会积累大量「用户问了什么、模型答得如何」的日志。定期把这些日志里的失败案例整理成样本,反过来优化分块策略和提示词。如果你发现很多问题集中在某类文件上,大概率是这类文件的解析质量有问题——检查一下是不是扫描件没走 OCR,或者表格结构被破坏了。这种利用生产数据反馈的机制,比任何离线调优都有效。

第四个方向,在正式对外提供服务之前,建议增设一个人工复核入口。政务场景的容错率极低,AI 解读只能作为建议参考,不能直接作为办事依据。常见的做法是给每个回答加一个「复核并发送」的按钮,由业务人员确认后通过短信或邮件转发给用户。在这个人机协同的闭环里,模型的职责是大幅提高业务人员的处理效率而不是替代决策。

最后说一个我自己形成的工作习惯:每次改完检索参数或提示词,先在评测集上跑一遍,再拿 3 个真实业务问题做人工复核,确认没有引入新问题才发布到正式环境。这套系统的价值完全建立在「可信」二字上——让业务人员信任模型给出的每一条依据都可以追溯到原文,信任系统的每一个回答逻辑都是稳定的、可验证的。做到这一点,技术上的所有投入才是值得的。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询