基于RAG的智能审计问答系统:大模型驱动的底稿检索与生成实践 简介项目基于大语言模型构建智能审计问答系统内含Python源码与文档说明适用于毕业设计、期末大作业及课程设计等场景尤其适合需要完整可运行项目的计算机专业学生。系统功能完善界面美观操作简便代码附带详细注释从后端逻辑到前端页面均有覆盖简单部署即可使用并经过严格调试保证可运行。压缩包共34个文件以Python脚本、HTML页面、CSS样式、JavaScript脚本和图片素材为主辅以Markdown文档、文本说明及CSV数据文件包体约16.77MB目录结构清晰方便按模块查阅与二次开发。目前已有684人学习下载该成果曾获98分评价受到导师认可。下载后可获得审计问答系统完整实现包括API接口、用户管理、问答数据及前端展示页面既能用于答辩演示也可作为进一步扩展智能审计应用的起点。1. 智能审计问答系统大语言模型进场后审计底稿终于不用人肉翻了审计项目组里最耗人的活儿不是写结论而是翻底稿。项目经理问一句“去年固定资产折旧测试抽了哪几张凭证”助理得打开十几个Excel和PDF凭记忆找半天。基于大语言模型的智能审计问答系统就是把这一整摊“找证据”的活变成自然语言对话你用中文提问系统从审计底稿、财务报表、审计准则里检索出相关片段再交给大语言模型组织成带引用的答案。它的核心不是让模型“懂审计”而是让模型“只读你给的材料”避免一本正经地胡编。这套方案适合审计信息化开发、企业内部审计工具链也适合拿来做高分项目——python源码跑通RAG链路文档说明补上数据字典和部署步骤正好是评审爱看的完整度。2. 系统拆解从审计文档到问答对数据管道是第一个分水岭2.1 智能审计问答的输入输出边界要回答什么不回答什么很多人在第一步就翻车把整套审计底稿直接丢给大模型然后问“这家公司有没有财务造假风险”。这是把问答系统当算命系统用。审计问答的输入边界必须是可定位的证据比如“某一科目的底稿里抽了哪些样本”“某条审计调整的金额是多少”“某个报表项目对应哪条审计程序”。这些问题都能回到具体的底稿页、Excel行、扫描件段落。超出证据范围的问题比如“管理层是否诚信”系统应该拒绝作答或给出基于客观线索的提示而不是让模型去“推断”。输出边界同样要提前定死。答案必须携带来源引用格式建议统一为“结论 支持证据 引用路径”。例如“固定资产折旧测试共抽样5笔原值合计1,283,440.62元详见底稿FA-2023-012第3页表2。”引用的粒度至少到文件名和页码更严格的场景要到Excel单元格地址和PDF段落ID。这套约束决定了后面的切片方式和提示词写法也决定了系统能不能在复核阶段被信任。2.2 用python把审计报告切成可检索片段基于规则的切片与向量化流程审计材料的来源很杂Word的审计报告、PDF的扫描底稿、Excel的试算平衡表还有从审计软件里导出的HTML。第一步是先做文本抽取统一转成带元数据的JSON结构。常见做法是用pypdf抽PDFpython-docx抽Wordopenpyxl读Excel的指定sheet。文本抽取后不要整篇扔进向量库而是按“审计底稿的天然边界”切片标题、表格、段落。import re import json def split_audit_document(text, doc_id, max_chars800): 按审计报告常用标题层级切块保留标题作为上下文元数据。 返回片段列表每段带 doc_id、page_hint、标题路径。 # 先用行尾序号切出大节再按二级标题切碎片段 sections re.split(r\n(?\d\.\d\s[^\n]{2,40}), text) chunks [] for i, sec in enumerate(sections): lines [ln.strip() for ln in sec.splitlines() if ln.strip()] if not lines: continue title lines[0][:60] # 标题行作为上下文 body \n.join(lines[1:]) # 超长段落再按 max_chars 折行避免向量化时语义被稀释 for start in range(0, max(1, len(body)), max_chars): frag body[start:startmax_chars] chunks.append({ doc_id: doc_id, seq: i, page_hint: extract_page_hint(lines), title: title, text: frag }) return chunks def extract_page_hint(lines): # 很多底稿页脚带 “第 X 页” 或 “Page X” for ln in lines[-3:]: m re.search(r第\s*(\d)\s*页|Page\s*(\d), ln, re.I) if m: return int(m.group(1) or m.group(2)) return None切片的max_chars是第一个要调的参数。定到800字符覆盖一段完整审计说明加上几行表格数据如果定到2000向量化后语义模糊检索命中率会往下掉。page_hint从页脚正则里抓PDF没有文本层的扫描件需要OCR那一步单独做不要混进这条管道。切片完成后用SentenceTransformer做向量化我常用bge-small-zh-v1.5中文审计描述表现稳定显存占用也低。from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-small-zh-v1.5) vectors model.encode([c[text] for c in chunks], normalize_embeddingsTrue, batch_size32) for i, vec in enumerate(vectors): chunks[i][vector] vec.tolist()接着把这些向量和元数据一起写入向量库。轻量场景用chromadb数据量过百万再考虑milvus。向量库里要额外存一份text不要只存向量和id不然召回后还得回原始文件重新加载。排序索引建议用doc_id seq保证同一个底稿的前后片段能被追溯。import chromadb client chromadb.PersistentClient(path./audit_store) collection client.get_or_create_collection( nameaudit_chunks, metadata{hnsw:space: cosine} ) collection.add( ids[f{c[doc_id]}_{c[seq]} for c in chunks], embeddings[c[vector] for c in chunks], documents[c[text] for c in chunks], metadatas[{ doc_id: c[doc_id], page_hint: c[page_hint], title: c[title] } for c in chunks] )2.3 文档说明里最容易被跳过的部分问题模板与审计准则映射表高分项目和能进生产环境的系统差别往往不在模型而在一张问题模板表。这张表把审计用户真实会问的话转成标准化的检索query和期望答案结构。比如“测了多少笔”对应“抽样数量”“抽到哪些凭证”对应“凭证号清单”“底稿在哪”对应“来源路径”。这张表要写进文档说明并且做成可配置的json不要写死在代码里。{ templates: [ { intent: 抽样明细查询, patterns: [抽了哪些, 抽样凭证, sample list], required_slots: [subject, period], retrieval_query: 抽样 凭证号 金额 合计, answer_format: 底稿引用 明细表 }, { intent: 审计调整查询, patterns: [调整了什么, 审计调整, adjustment], required_slots: [subject, period], retrieval_query: 审计调整 借 贷 金额, answer_format: 调整分录 凭证号 } ] }映射表的意义是把“用户用大白话问”和“底稿里用专业词写”之间的缝隙补上。用户问“固定资产今年怎么测的”检索query拆成“固定资产 折旧 测试 样本”这样向量召回才能碰到底稿里的“折旧测试”小节。文档说明里还要画一张数据流转图原始文件 → 文本抽取 → 切片 → 向量化 → 入库 → 问答链路每一步标注输入输出格式这样接手的人不会把管道跑断。3. 本地部署大语言模型选型、量化与显存预算3.1 本地部署还是走API审计数据不出内网这条硬约束审计底稿属于企业敏感数据外调API这条基本可以否决。本地部署大语言模型不是炫技是为了满足“数据不出内网”的合规约束。选型时先看硬件预算16G显存能舒服跑7B量级模型24G可以上14B32G以上再考虑30B。审计问答对业界知识的依赖没有法律合同那么高7B~14B的中文指令模型足够关键是检索给得准不是模型本身有多大。本地部署的常见做法是两条路一是用Ollama这类推理服务二是直接用transformers加载模型。Ollama对开发和演示友好一条命令拉起服务python端用OpenAI兼容接口调用代码不用绑死某个框架。生产环境要求吞吐量时再换vLLM。7B模型用4bit量化后显存占用约5G响应速度在普通消费级显卡上能到每秒十几token这个水平对审计问答是够用的。3.2 用python调起本地推理服务的最小可用代码用Ollama拉起本地服务后python端只需要一个OpenAI客户端的壳。这里有一个容易忽略的点base_url要指向Ollama的v1路径api_key随便填非空字符串Ollama不校验。模型名要写带量化的标签比如qwen2.5:7b-instruct-q4_K_M不要只写qwen2.5否则拉取的是默认量化档位显存占用可能超出预期。from openai import OpenAI client OpenAI( base_urlhttp://localhost:11434/v1, api_keyollama, # Ollama 不校验但必须传非空 ) resp client.chat.completions.create( modelqwen2.5:7b-instruct-q4_K_M, messages[ {role: system, content: 你是审计助理只依据提供的底稿材料回答。}, {role: user, content: 固定资产折旧测试抽了哪些凭证} ], temperature0.1, max_tokens512, streamFalse ) print(resp.choices[0].message.content)这段代码跑通后先不要急着接RAG用几句审计领域问题测一下模型的底子温度。你会发现没有一个固定的审计材料源模型会编一套“凭证号20231201-001”出来这正是后面要用检索和提示词压住的问题。max_tokens512对大多数审计问题足够答案太长容易超出底稿引用范围变成车轱辘话。3.3 三个必调参数max_new_tokens、temperature与top_p怎么设审计问答不是创意写作参数要往“确定性”上靠。temperature我一般压在0.1到0.2超过0.3模型会开始换着花样重新表述同一个答案复核人看着两份结果不一致信任度立刻崩。top_p设0.9即可它和temperature是叠加关系不需要两个都往死里压。max_new_tokens根据答案里要不要带表格决定纯文本答案256足够要带明细表就512到768。# 更细的控制可以拆开传参兼容不同推理框架 gen_kwargs { temperature: 0.1, # 审计场景压到0.2以下 top_p: 0.9, max_new_tokens: 512, repetition_penalty: 1.1 }repetition_penalty是第四项容易被忽略的参数。审计底稿经常有连续表格行模型在生成“凭证号清单”时容易复读第一行。设1.1能把复读概率压下来但不要超过1.2不然答案句子会变得生硬。这几个参数要在文档说明里写清楚“为什么这么设”评审问起来你才有底气。4. 问答系统核心链路召回、重排、注入与答案生成4.1 检索增强生成RAG在审计问答里的落地形态纯靠模型常识答审计问题就是看诊不开检查单。RAG的落地形态是这样的先把用户问题转成向量去向量库召回最相似的topK个片段再把片段原文拼进提示词让模型只能基于这些片段作答。审计场景还要加一道强制校验模型输出的每一条关键结论都要能映射到某个片段里的原文句子否则宁可说“未在底稿中找到”。召回数量top_k是第一个调参点。审计答案经常分散在多个底稿top_k设太小会漏。我一般取10到20但提示词里不能把20个片段全塞进去否则模型分不清主次。正确做法是召回后先做重排只把最相关的5到6段注入。这一步能显著减少幻觉也省token。4.2 用python实现召回重排的最小demo召回用向量 关键词混合召回。纯向量对数字和专有名词不敏感用户问“凭证号20231128-015”向量检索可能因为分词把数字拆碎而命中不了所以要在同一套代码里并行跑一个BM25检索然后把两路结果合并。from rank_bm25 import BM25Okapi import jieba def hybrid_retrieve(query, chunks, top_k12): # 1. 向量召回 q_vec model.encode([query], normalize_embeddingsTrue)[0] hits collection.query( query_embeddings[q_vec.tolist()], n_resultstop_k ) # 2. 关键词召回BM25 对精确数字、凭证号更友好 tokenized_corpus [list(jieba.cut(c[text])) for c in chunks] bm25 BM25Okapi(tokenized_corpus) bm25_scores bm25.get_scores(list(jieba.cut(query))) # 3. 简单加权合并向量分 BM25分 merged [] for i, hit in enumerate(hits[metadatas][0]): vec_score 1 - hits[distances][0][i] # cosine距离转相似度 bm_score bm25_scores[int(hits[ids][0][i].split(_)[1])] merged.append((hit, vec_score 0.3 * bm_score, vec_score, bm_score)) merged.sort(keylambda x: x[1], reverseTrue) return merged[:top_k]重排有两种做法轻量用上面的加权分针对性再强一点用bge-reranker这种交叉编码模型。Reranker跑起来比bi-encoder慢但对审计长文本效果好值得加这一层。线上系统里可以把reranker放在一个独立进程避免阻塞主链路。4.3 提示词模板让大模型只读证据、不许编数提示词是整个链路里最“玄学”也最见效的一环。我的模板分三段角色约束、材料清单、输出规则。材料清单按“底稿标题 原文片段”逐条列出并明确告诉模型“以下片段是唯一可引用来源”。输出规则里写死答案必须带引用编号无法从片段直接推导的要说“未找到”。def build_prompt(query, retrieved): context for i, item in enumerate(retrieved): meta item[0] context ( f[{i1}] 来源{meta[doc_id]} 第{meta[page_hint]}页 f标题{meta[title]}\n f{item[text]}\n\n ) prompt f你是审计助理。只能使用下方【材料】回答用户问题。 【材料】 {context} 【规则】 - 答案必须引用材料编号例如“根据[1]”。 - 材料中没有的内容直接回答“未在底稿中找到”禁止编造。 - 涉及金额、凭证号、日期必须逐字核对材料后输出。 【问题】 {query} return prompt这段模板里有个关键设计引用编号[i1]是模型输出和材料之间的锚点。审计复核人看到一个结论能顺着编号翻到对应底稿这个系统才有信任基础。很多翻车案例就是提示词里没做这个锚定模型自由发挥。5. 避坑与常见问题排查为什么答非所问、引用错页、生成虚构凭证号5.1 现象回答引用了不存在的审计底稿编号系统一本正经地给出“根据底稿HD-2023-028第4页”但项目组根本找不到这个底稿。原因出在提示词未约束引用合法性模型把材料里语义相近的内容拼凑后顺手编了一个“看起来合理”的编号。解决分两步。第一步强制引用编号只能来源于材料列表里已有的[n]而不是让模型自己生成底稿编号第二步在后处理里做一次正则校验如果答案里出现 “HD-\d{4}-\d{3}” 这类编号格式但不在注入材料的doc_id集合中直接拦截并返回“引用校验失败请重新提问”。我在文档说明里专门写了这条校验函数的样例这比让模型“再想想”可靠得多。5.2 现象跨期问题答串把去年的凭证当今年问“2024年应收账款函证回函比例”模型却给出了2023年的数据。原因是切片元数据里没有period字段向量检索只看语义年份数字在embedding里区分度很低。解决是给切片打period标签在检索阶段做硬过滤。底稿文件名或表格标题里通常带年份抽取时用正则(20\d{2})抓出来存进元数据用户query里出现年份就先解析成过滤条件再进向量检索。这一步不依赖模型纯规则但能把跨期错误率直接砍掉大半。5.3 现象长文档检索命中率低问题里明明有数字却搜不到用户问“凭证号20231128-015对应的金额是多少”向量召回top10里没有这一条。原因前面提过embedding模型对纯数字串的语义编码几乎失效数字被切成无意义token。解决是走混合召回而且要把数字当成“专用词”处理。预处理时把连续数字串按原样保留不分词同时在BM25召回里对\d{4,}做正则加权。我再加了一招如果query里检测到“凭证号”“底稿编号”这类字段名就单独去一个按编号建的倒排索引里精确查一次命中结果直接置顶。这条线索被写进文档说明的“检索策略”章节成为项目的一个亮点。5.4 现象本地部署后首字延迟高用户以为卡死本地模型4bit量化后首token往往要2到4秒审计用户点完提问盯着界面下意识会觉得系统坏了。这个问题不是模型问题是交互设计问题。解决是开流式输出让第一个token尽早显示同时把检索耗时和生成耗时拆开打日志定位瓶颈。另外max_new_tokens不要设得过大比如统一设1024模型会倾向于把答案铺满首字延迟和总耗时会一起涨。我一般给界面提示“正在检索底稿…”检索完成后再切到“正在生成答案”用户对慢的感知会好很多。6. 验证与进阶用测试集给问答系统打分再谈接入业务系统跑通后最容易让人误判的是“试了两个问题觉得挺准”。审计问答必须量化验证。我建了一个30条左右的测试集覆盖抽样查询、调整分录、跨期比较、不存在事项四类。每条标注问题、预期来源底稿、预期答案关键点。跑完用两个指标看检索级hit5正确答案所在片段是否进top5和生成级“关键信息准确率”答案里的金额、凭证号、日期是否全部与底稿一致。指标发布前至少让两位审计人员独立复核一遍。再往后进阶可以做成“引证回查”的功能模型每输出一个引用编号系统就自动定位到底稿原文并高亮甚至弹窗展示扫描件图片。这个功能落地不需要改模型只用把page_hint和PDF渲染绑定起来但对用户信任度的提升是决定性的。最后说句我踩过的坑早期我把temperature调到0.7指望模型“灵活一点”结果同一道题跑两遍答案里抽样数量都变。后来全部压回0.1世界安静了。审计问答的价值不在生成而在可复核、可回溯、可一致复现。希望帮到你。本文还有配套的精品资源点击获取