ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于RAG与LangChain的C语言智能问答系统构建实战

基于RAG与LangChain的C语言智能问答系统构建实战 简介面向C语言学习者与系统级编程爱好者的智能问答系统基于RAG架构并以LangChain框架实现文档加载与知识检索针对知识获取效率低、模型幻觉等学习痛点提供精准可靠的即时答疑。资源包共51个文件压缩后约78MB包含12个Python核心源码、9个HTML交互页面、相关文档与向量索引等代码、配置与数据完整齐备。已有66人学习下载适合正处于C语言进阶阶段或对自然语言处理应用感兴趣的开发者参考实践。除可运行的前后端完整代码外还附赠PDF版教材、示例项目、IPython笔记及详细使用说明便于读者快速掌握LangChain构建知识库的流程直接运行体验并二次开发助力系统级编程能力的有效提升。1. 基于RAG架构的C程序设计智能问答系统把LangChain变成系统级编程学习的检索问答引擎很多人第一次听到“RAG架构的C程序设计智能问答系统”第一反应是套个网页聊天框、往里扔几本C语言教材PDF然后让大模型有问必答。真这么做翻车速度会比你预想快得多你问它“指针数组和数组指针到底怎么区分”它信心满满给出一个编译都不通过的示例你追问“realloc失败之后原指针还在不在”它开始自由发挥。这类问题的根子在于模型天生会猜而系统级编程恰恰不允许猜。这篇笔记讲的就是一条可复现的落地路径用LangChain把C语言教材、讲义、标准库文档和课程代码整理成可检索的知识库再通过RAG的“先检索、后生成”机制约束回答范围把知识获取效率提上去把模型幻觉压下来。适合正在学C的系统级编程初学者、想把大模型接进课程答疑工具的开发者和准备做RAG项目的工程师。后面所有章节都按我实际搭这套系统时验证过的做法来讲参数和坑都写在明处。2. 为什么C语言学习问答要上RAG知识获取效率和幻觉的双重困境2.1 直接问大模型的翻车现场看似专业实则编译不过我最早试过不接知识库直接把C语言问题丢给通用大模型。最初几个简单题还行一旦进入系统级编程的核心地带问题就暴露了。第一类是未定义行为。问“char *p 指向局部变量函数返回后 p 还能用吗”模型能答出“这是悬垂指针不能使用”大方向没错。但继续往深问让它给出一个演示代码它经常写出“返回局部变量地址后用另一个变量重新占用该内存”的例子。这个例子本身没有错难的是模型在解释时造出一些标准里根本不存在的函数和头文件熟练的人一眼看穿初学者会被带偏。第二类是标准库函数的边界行为。strncpy是否保证以\0结尾、realloc失败时原指针是否释放、printf的返回值到底是多少这些在 C 标准里有精确答案但模型的记忆是概率性的。同一个问题换个问法它可能给出完全相反的结论。而系统级编程的黑匣子就在这里表面看起来合理的代码底层行为全错。第三类是对学习阶段不敏感。同一个“堆和栈的区别”大一新生要的是宏观概念准备校招的要的是内存布局和溢出场景。模型没有上下文约束时倾向于回答一个四平八稳的版本两边都不满意。说白了知识获取效率低不是信息少而是信息粒度不对。RAG 的价值就是先把“正确答案的素材”送到模型面前再让它组织语言而不是让它凭记忆猜。2.2 RAG架构解决的其实是“先查后答”的漏斗问题RAG 的全称是 Retrieval-Augmented Generation检索增强生成。它的工作方式可以理解成一个漏斗用户提问后系统先把问题转成向量在预先切好并索引的知识库片段里召回最相关的 Top K 个文本块把这些文本块和问题一起拼进 Prompt再交给大模型生成最终答案。模型的任务从“回忆知识”变成“总结给定材料”范围和约束都不一样了。这套架构天然适合 C 语言学习。C 知识的特点是事实密集、边界明确函数签名是确定的标准行为是写死的指针和内存的规则是几十年来沉淀下来的。这类知识非常适合做成索引而每个学习者遇到的具体困惑千差万别RAG 正好可以把“标准答案片段”和“当前问题”现场拼接。我一般会把这套系统的定位说成一句话“带引用的知识助手而不是自动写码机。”它能帮你快速找到某个知识点在教材里的准确说法能基于你指定的课程代码回答问题但它不负责编造一个全新的程序逻辑也不会在你没提供代码的情况下瞎猜运行时行为。明确这个边界后面设计 Prompt 和评测指标时就不会跑偏。2.3 为什么选LangChain而不是自己写检索代码做这个场景选 LangChain不是因为它时髦而是它能省掉大量重复工程。LangChain 把整个 RAG 流程抽象成几个稳定概念Document 是文本单元的载体Embeddings 负责向量化VectorStore 管存储和召回Retriever 是统一的检索接口LCEL 表达式则把 Prompt、模型、输出解析串成一条链。这套抽象的价值在于更换嵌入模型、切换向量库、调整检索策略时上游代码几乎不用动。我最早手写过一版“OpenAI embedding FAISS 自建索引”代码量不多但一旦要加多轮对话改写、来源引用、混合检索整个项目就变成了毛线团根本维护不下去。LangChain 的生态也帮大忙。文档加载器覆盖 PDF、Markdown、代码目录文本分割器有针对 C/C 的语言感知版本向量库适配 Chroma、FAISS、Milvus 等基本不用重复造轮子。网上关于 LangChain 入门、菜鸟教程和 RAG 实战的资料已经很多遇到问题容易搜到对应解法这一点在工程踩坑时比某个花哨特性更值钱。也有不少人问 LangChain 和 LangGraph 到底怎么选。我的看法是单轮或简单多轮的检索问答LCEL 表达式足够不需要上 StateGraph只有当你要编排多步骤决策比如“第一轮检索不满意就改写问题再检索、再不满意就换一路召回来源”这类带条件分支的流程时LangGraph 才值得引入。后面第 4.4 节我会给一个轻量的 Agentic RAG 升级方案不依赖重量级编排框架也能落地。2.4 系统级编程场景下RAG的适用边界必须承认RAG 不是银弹。知识库覆盖不了的运行时行为它答不了涉及具体编译器版本、具体操作系统调用的行为差异它也只能给原则性解释。我在第一个版本里就吃过一次亏。知识库里放的是教材和讲义用户却问“为什么我的 Qt 程序在 Ubuntu 上段错误但在 Windows 上没问题”。这种问题依赖用户现场信息光靠检索教材片段根本不够。后来我在系统里做了明确约定知识库负责原理和规范编译器、调试器负责事实验证两者必须搭伴。用户拿到的代码片段如果涉及具体编译系统会提示“请在实际环境中跑一遍”。这不是推卸责任而是系统级编程的基本常识——任何一个只读文档从没跑过运行时的问答系统都不应该对这类问题打包票。边界想清楚了后面每一步才踏实数据准备时知道该收什么文档Prompt 设计时知道该怎么拒绝问题评测时也知道该用什么指标衡量好坏。3. 搭建C语言知识库文档加载、分块与向量化的LangChain实现3.1 先决定知识库里放什么教材、标准文档、课程代码各司其职这套系统第一个要解决的问题不是写代码而是“文档源选什么”。我见到很多 RAG 项目死在这一步随便扔几十个 PDF 进去检索结果乱七八糟。对于 C 语言系统级编程学习我建议知识库由三类来源组成。第一类是教材和讲义主流的《C语言程序设计》教材、大学课件、名师公开课讲义都可以第二类是标准库参考资料比如常见头文件的说明文档、在线手册里关于malloc、realloc、strncpy的条目这类内容精确回答“边界行为”问题第三类是课程代码与习题集像慕课版的编程练习、实验指导书里的代码片段它们解决“具体怎么写”的需求。准备 raw 数据时要做一次清洗。PDF 扫描件必须先 OCR排版里的页眉页脚、页码、水印要去掉Markdown 课件里多余的 HTML 标签要清理。否则分块之后向量库里全是噪音检索命中率怎么调都上不去。3.2 用LangChain的加载器统一读入文本与PDF清洗完的文档放进data/notes和data/textbook两个目录用 LangChain 的加载器读进来。常见的做法是 Markdown 和纯文本用DirectoryLoader批量读PDF 用PyPDFLoader单独处理。from langchain_community.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader # 批量读取 Markdown 和纯文本讲义指定 UTF-8 编码避免中文乱码 md_loader DirectoryLoader( ./data/notes, glob*.md, loader_clsTextLoader, loader_kwargs{encoding: utf-8}, ) md_docs md_loader.load() # PDF 教材单独加载PyPDFLoader 会把每页变成一个 Document pdf_loader PyPDFLoader(./data/textbook/c_programming.pdf) pdf_docs pdf_loader.load() print(fmarkdown docs: {len(md_docs)}, pdf docs: {len(pdf_docs)})加载完成后每个文档都是一个Document对象内部有page_content和metadata两个关键字段。page_content是正文metadata我会在后续步骤里手动补上“来源文件名”“章节标题”等信息这是做答案溯源的基础。有一点容易忽略metadata中不要存列表类型Chroma 等向量库序列化时容易报错。存字符串、整数最多存一个由|拼接的路径字符串。3.3 分块策略为什么C语言教材不能按固定字数硬切分块是整个 RAG 系统里最玄学的环节但也是回报率最高的调优点。直接按固定字符数切是最省事的做法却在 C 语言场景里最容易出事故——后面避坑章节我会细讲。这里先给出我验证过的可行参数。对教材类自然语言文本我用RecursiveCharacterTextSplitter分隔符按优先级排列让中文句子优先断开。对.c和.h文件用from_language切出带 C 语言语法的分割器它会优先保留函数定义的整体性。from langchain_text_splitters import RecursiveCharacterTextSplitter, Language # 教材、讲义这类自然语言按段落、句子边界切不要切碎一句话 md_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , ], ) md_chunks md_splitter.split_documents(md_docs) # 代码文件按 C 语言语法边界切尽量不让函数体断成两截 code_splitter RecursiveCharacterTextSplitter.from_language( languageLanguage.C, chunk_size300, chunk_overlap40, ) code_docs [d for d in md_docs if d.metadata.get(source, ).endswith((.c, .h))] code_chunks code_splitter.split_documents(code_docs) print(fmd chunks: {len(md_chunks)}, code chunks: {len(code_chunks)})先说明逻辑chunk_size控制每一段的长度。教材文本取 500 左右是考虑到中文一句话通常 50 到 100 字500 字能容纳 3 到 5 个完整句子既不会太快超出模型上下文也不会让向量召回时信息太稀疏。代码取 300因为一个典型函数几十行300 字差不多刚好覆盖一个完整函数体。chunk_overlap是相邻块的重复区间用来补偿跨块语义断裂教材设 80代码设 40具体数值可以看召回效果微调。参数调完记得做一次人工抽检随机挑 20 个 chunk看有没有从句子中间断开、有没有代码块被拦腰截断。这一步做扎实后面检索命中率会稳定很多。3.4 嵌入模型选型本地小模型还是API模型嵌入模型决定“语义理解”的上限。C 语言代码里大量出现malloc、realloc、strncpy这类短标识符通用嵌入模型如果不擅长代码语义检索时容易张冠李戴。我试过两套方案各有适用场景。本地部署用HuggingFaceEmbeddings加载BAAI/bge-m3效果稳定且不依赖外网追求更强代码理解时可以用 Ollama 跑 qwen 系列的 Coder 模型做嵌入或者直接调 API。要注意的是本地模型推理慢知识库如果上十万级 chunk建议提前把向量一次性算完存到磁盘不要在查询时实时算。from langchain_community.embeddings import HuggingFaceEmbeddings embedding HuggingFaceEmbeddings( model_nameBAAI/bge-m3, model_kwargs{device: cpu}, encode_kwargs{normalize_embeddings: True}, )normalize_embeddings设为True很关键它把向量归一化到单位长度余弦相似度计算更稳定Chroma 默认的距离计算方式也能拿到更合理的结果。模型跑在 CPU 上时首次加载会把权重载入内存300MB 到 2GB 不等注意预留内存。3.5 写入向量库并验证召回效果最小的Chroma落地向量库我用 Chroma 起步原因是轻量、无需单独部署服务、适合课程项目和本地实验。把上一节的文本块连同 embedding 一起写入本地目录然后立刻做一次检索验证。from langchain_chroma import Chroma vectorstore Chroma.from_documents( documentsmd_chunks code_chunks, embeddingembedding, persist_directory./c_rag_db, ) retriever vectorstore.as_retriever(search_kwargs{k: 4}) # 验证召回打印前三段内容和来源文件 hits retriever.invoke(指针数组和数组指针怎么区分) for i, hit in enumerate(hits[:3]): print(i, hit.metadata.get(source, unknown)) print(hit.page_content[:80])k4是经验值。取太少知识片段拼不出完整答案取太多大模型会被不相关内容干扰回答变得发散。在我这套 C 语言教材库上4 到 6 之间比较合适再往上会明显感觉答案变水。这段代码跑通后建议做一个检索手测准备 10 个典型问题把检索结果的前 3 条读一遍确认召回片段真的对应问题。如果相关性差优先调整分块参数其次是换嵌入模型不要急着动代码链路。3.6 从向量检索升级到混合检索BM25与向量召回怎样配合向量检索擅长语义近似的匹配但 C 语言里最要命的是符号精确匹配。比如用户搜“realloc 失败”向量检索可能把malloc 失败排得很靠前因为malloc和realloc在语义空间里太近了。这时候需要引入 BM25 这种基于词频的检索它能把包含“realloc”字样的片段精确凑出来。LangChain 里可以很轻量地做混合检索from langchain.retrievers import BM25Retriever, EnsembleRetriever bm25_retriever BM25Retriever.from_documents(md_chunks code_chunks) bm25_retriever.k 4 ensemble_retriever EnsembleRetriever( retrievers[bm25_retriever, retriever], weights[0.5, 0.5], ) hits ensemble_retriever.invoke(realloc 失败 原指针)weights控制两路召回的贡献比例。BM25 对精确词频敏感向量对语义敏感各取 0.5 是保守起点如果你的知识库里代码片段多可以把 BM25 权重调高到 0.6因为代码问题大多依赖符号匹配。跑完对照测试会发现混合检索让“realloc 和 malloc 的区别”这类问题的命中率明显提升代价是召回结果里偶尔会混入一个纯粹因为词频高而进来的无关块通过 Prompt 里的来源标注可以掩盖掉一部分。4. 组装智能问答链路检索增强生成与幻觉抑制的实战配置4.1 用LCEL从Retriever拼出问答链路最小可跑代码知识库就绪后下一步是把检索器和语言模型串起来。我推荐用 LCELLangChain Expression Language而不是老式的RetrievalQA链因为 LCEL 更透明、更容易调试也方便随时插入中间环节。先看能直接跑通的最小链路版本from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser from langchain_community.chat_models import ChatOllama prompt ChatPromptTemplate.from_template( 你是C语言学习助手。请优先使用下面给出的知识片段回答问题。 如果知识片段不足以回答请直接说“知识库中未找到”不要自行补充。 context {context} /context 问题{question} 回答要求先给结论再给原理或代码。若涉及代码请给出完整代码。 ) llm ChatOllama(modelqwen2.5-coder:7b, temperature0.1) def format_docs(docs): return \n\n---\n\n.join( f[来源:{d.metadata.get(source, 未知)}]\n{d.page_content} for d in docs ) qa_chain ( {context: ensemble_retriever | format_docs, question: RunnablePassthrough()} | prompt | llm | StrOutputParser() ) answer qa_chain.invoke(realloc失败之后原指针还有效吗) print(answer)这段代码的逻辑链条是用户问题进入后ensemble_retriever完成混合检索format_docs把召回的多个片段拼成带来源标记的文本块ChatPromptTemplate把上下文和问题填进模板ChatOllama生成回答最后StrOutputParser把输出解析成纯文本。参数上最值得留意的是temperature0.1。C 语言问答属于强事实场景模型温度越高越容易自由发挥。0.1 是我反复测试后确定的值它让回答更稳定、更贴近材料但不会像 0 一样机械到丢掉正常的语言组织。实际部署时如果你追求完全可控甚至可以设成 0。顺带补一句format_docs的作用它不只做拼接还把每个片段标上了“来源”。这个细节在后面做答案溯源时有奇效大模型看到带来源的内容会更倾向于引用而不是改写。4.2 Prompt模板设计三个关键约束来源、完整性、越权处理LCEL 链路里真正决定回答质量的其实是 Prompt 模板。我在模板里定了三条硬约束缺一条效果都会打折扣。第一条是限定来源。模板明确写“优先使用知识片段片段不够就承认没找到”。这直接抑制了模型脱离检索结果自由发挥的倾向。很多 RAG 翻车案例里模型宁可编一个答案也不说“不知道”就是因为 Prompt 里没有给它拒绝的出口。第二条是要求代码完整可编译。C 语言提问里用户要的不是“大概思路”而是能贴上 IDE 跑起来的代码。所以我在模板里追加了一句“若涉及代码请给出完整代码”。实践中这会提高模型输出代码块的频率也会让它在面对残缺检索片段时更谨慎。第三条是越权处理。C 语言之外的问题比如问 Python、问数学题或者问“为什么我的 VSCode 配置完还是不能编译”模板没有给出处理路径模型就容易硬答。常见的做法是在 Prompt 末尾加一句“如果问题与C语言系统级编程无关请告知用户问题超出知识库范围”系统会礼貌拒绝而不是瞎答。4.3 幻觉抑制三板斧引用来源、召回分数阈值、上下文压缩Prompt 约束只是第一层。真正要系统性压住幻觉得从三个位置同时设防。第一板斧是引用来源。在format_docs里我已经把每个片段标注了文件名模板再要求模型在回答末尾列出参考来源。用户看到答案能自己回溯验证模型因为知道答案会被追踪编造的概率也会下降。第二板斧是召回分数阈值。向量检索返回的结果自带相似度分数分数很低的片段说明与问题关联弱。常见做法是过滤掉低于阈值的召回不让它们进 Prompt。相似度阈值要按嵌入模型表现来标定我这边实测 0.45 左右比较合适太高会漏掉有效信息太低又会混入噪音。注意不同 embedding 模型的分数分布不一样换模型后阈值必须重新标定。第三板斧是上下文压缩。召回 Top 4 的片段每一段 500 字四个片段加一起有两千字里面真正和当前问题相关的可能只有三分之一。用 LangChain 的ContextualCompressionRetriever做一层压缩提取与问题相关的句子能显著减少模型被无关内容带偏的概率同时降低提示词长度、节省模型推理时间。4.4 从RAG到Agentic RAG多轮追问和检索词改写的实用升级单轮问答跑通后第一个挡路的是多轮对话。用户上一轮问“malloc 和 calloc 的区别”下一轮追问“那 realloc 呢”如果你直接把“那 realloc 呢”拿去检索召回结果大概率乱七八糟。解决办法是加一道查询改写。在进入检索前先把用户当前问题和最近几轮对话历史交给模型让它改写成一句独立完整的检索词比如“那 realloc 呢”被改写成“realloc 和 malloc 的区别与异常处理”。这一步成本极低收益非常明显。LangChain 里可以在 LCEL 链前面插一个 RunnableLambda 完成改写不一定非要上 LangGraph。再往上一层就是 Agentic RAG也就是检索本身变成可动态决策的流程首轮检索结果分数太低改写查询再检索一次还是不行就换一路召回来源比如从代码片段切到教材章节得到足够材料后才交给生成模型。我验证过的轻量方案是写一个route_by_score的函数用RunnableBranch在“直接回答”和“再次检索”之间做分支。LangGraph 能把这套流程可视化编排得更好但项目规模没到多智能体协作时用函数加分支已经足够别为了用框架而上框架。5. C语言RAG系统落地避坑5个翻车现场与解决办法5.1 分块把函数定义和调用拆散答案给出一段残血代码现象用户问“冒泡排序怎么写”问答系统返回的代码只有函数前半段后半段在另一个没有被召回的分块里甚至更糟函数头和函数体被分到两个检索结果里模型拼出一个编译不过的代码。原因直接用固定字符数切代码文件chunk_size500从函数中间一刀切下去。C 代码最重要的单位是函数整体切散了检索结果永远不完整。解决代码文件改用RecursiveCharacterTextSplitter的from_language(languageLanguage.C)方案它会把函数、花括号、预处理指令当作边界参考。教材文本则要先用标题分割器把章节切出来再对每个章节内部做二次分块。分块后做一轮抽样检查凡是看到半个函数体就把chunk_size加大或者把chunk_overlap提高 20 到 40。5.2 模型输出自信但编译不过幻觉依然存在现象某一次问答系统返回了“用指针交换两个变量”的代码写得有模有样但函数参数是传值调用交换只发生在函数内部根本影响不到外部变量。用户拿去编译能过逻辑全错。原因RAG 只保证“回答基于检索材料”不保证“代码经过验证”。模型在组织语言时会自己补充细节补充的内容一旦超出检索片段约束就重新落入幻觉区间。解决我在链路末尾加了一道编译体检。检测到回答中包含代码块时把代码写入临时.c文件调用本机gcc -fsyntax-only做语法校验编译失败就让模型基于错误信息重答一次。实现很朴素但它是 C 语言场景下最有效的幻觉拦截器。实际执行时要注意subprocess调用必须设置timeout避免恶意或异常代码卡住进程。5.3 LangChain升级后API变脸老教程代码全部报错现象照着网上一份 LangChain 老教程写代码from langchain.vectorstores import Chroma直接报 ImportError旧版RetrievalQA链的写法在新版里也被标记为 deprecated程序跑着跑着就抛异常。原因LangChain 0.1 到 1.0 迭代期间大量组件从主包迁移到了langchain-community、langchain-chroma这类独立包API 路径大面积变化。教程截图里能跑不代表当前安装版本能跑。解决项目一开始就把依赖版本锁死pip freeze requirements.lock并且只跟随大版本升级。遇到网上代码先看它属于哪个版本区间再对照迁移手册改 import 路径。我踩过的血泪经验是不要在项目中途顺手把 LangChain 升级到最新RAG 链路涉及加载器、分割器、向量库、模型接口四层依赖任何一层不兼容都够排查半天。5.4 向量检索对代码符号不敏感完全匹配不到精确写法现象知识库里明明有strncpy的详细说明用户搜“strncpy 和 memcpy 的区别”召回结果前几名却是关于printf和sprintf的内容模型答得文不对题。原因嵌入模型对自然语言语义敏感对代码中的短标识符不够敏感。strncpy和memcpy在语义向量里距离太近而printf因为文档片段多被误认为“相关”。解决升级为 BM25 与向量检索的混合方案BM25 负责精确符号匹配向量负责语义扩展。另一个有效路径是用 LangChain 的MultiVectorRetriever用法把整篇文档做向量嵌入用于召回再让检索器返回对应的子块。这样整篇文档的语义更完整返回的子块又保持精细粒度适合教科书和代码混存的知识库。注意混合检索的 RRF 去重逻辑在不同语言实现里有差异如果发现去重结果异常先检查两路检索返回的文档 ID 是否一致。5.5 多轮对话里历史信息变成噪音回答越聊越偏现象第一轮问“什么是 NULL 指针”系统回答正确第二轮追问“那把它赋值给整型变量呢”系统开始讲整型变量的内存布局完全脱离了 NULL 指针的话题。原因对话历史被整体拼进上下文向量检索时历史文本的权重太高当前问题反而成了配角。模型被历史信息诱导回答了“整型变量”而不是“NULL 指针赋值给整型变量是否合法”。解决加上查询改写步骤先把当前问题和历史压缩成独立检索词再送去检索。改写后的检索词如果相似度分数仍低于阈值就回复用户“知识库没有覆盖到这个组合问题”不要硬答。这一步被我用作系统中最重要的后悔药宁可让用户知道知识库边界也不要让模型在错误检索基础上越聊越远。6. 用“检索命中率”和“编译通过率”给智能问答系统验收系统搭完不能只看演示效果要有一套能重复执行的验收方法。我给这个 C 语言问答系统建立了一个小型评测集按原理型、API 型、代码型、错误排查型四类各出 25 题题目来自教材章末练习和常见编程题。每道题预设一个答案关键词片段比如“realloc 失败时原指针不变”作为判定命中标准。评测脚本只做两件事。第一步是测“检索命中率”把每个问题交给检索器判断召回的前 5 个片段里是否包含预设的答案关键词第二步是测“编译通过率”把问答链路返回的代码块提取出来写入临时文件用gcc -fsyntax-only做语法检查。golden_set { realloc失败后原指针是否有效: 原指针仍然有效, 数组指针和指针数组的区别: 指向数组的指针, } results {total: 0, retrieval_hit: 0, compile_pass: 0} for question, gold_segment in golden_set.items(): top_docs ensemble_retriever.invoke(question)[:5] results[total] 1 if any(gold_segment in doc.page_content for doc in top_docs): results[retrieval_hit] 1 answer qa_chain.invoke(question) code extract_code_block(answer) if code and compile_syntax_only(code): results[compile_pass] 1 print(results)这里extract_code_block负责从回答中抽取代码块compile_syntax_only是带超时机制的 gcc 调用的封装。两套指标分开看能定位问题出在检索层还是生成层命中率低回头调分块和混检权重命中率高但编译通过率低重点查 Prompt 约束和代码补全校验。我个人做这套系统留下的最值钱习惯是维护一个 badcase 清单。每一次用户追问“这个答案和教材第 X 章说的不一样”或者每一次编译体检失败我都会把问题和现场检索输出记下来每周复盘一次把组合问题沉淀回知识库把冲突片段重新分块。迭代两周后命中率和通过率都能稳定上一个台阶。希望从这篇笔记出发做 RAG 项目的你也能一上来就避开我踩过的坑。本文还有配套的精品资源点击获取
返回列表