ARTICLE DETAIL

资讯详情

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

RAG与Agent实战:构建可深挖的大模型简历项目

RAG与Agent实战:构建可深挖的大模型简历项目 做简历项目最怕什么不是写得不够多而是面试官一追问就露馅。很多同学的大模型项目是这样写的“独立开发了基于 LangChain 的企业知识库问答系统通过 RAG 解决了大模型幻觉问题上线后效果良好。”看起来完整实际一问就卡住数据是怎么清洗的切分策略为什么这么定向量检索用了什么距离召回几篇回答不忠实怎么评价本文要讲的不只是一个“能跑起来”的 demo而是一条可以直接写进简历的大模型岗位实战链路RAG 知识库问答 智能客服 Agent 多 Agent 协作。项目包含工程落地必需的数据处理、检索增强、工具调用、质量测评和错误排查。读完你能获得三样东西一套可以逐步复现的项目源码结构每个模块的关键取舍和面试官追问点保证简历经得起深挖的细节边界。 先说结论这类项目的核心价值不在“你会不会调 LangChain 的 API”而在于你是否能回答——知识怎么切、检索怎么评、Agent 怎么和真实业务系统协作。这才是大模型岗位真正在意的工程能力。1. 这篇文章真正要解决的问题1.1 大模型岗位项目经历面试官到底在看什么大模型应用开发岗位这几年很热但面试官筛选候选人的逻辑没有变你做系统时有没有清晰的边界判断能力。同样是“我做过 RAG”有人只会在 Jupyter Notebook 里vectorstore.similarity_search有人能说清楚知识库数据来自哪几种格式清洗时做了什么chunk_size 为什么是 400 而不是 2000embedding 用的是什么模型为什么不在线直接用 OpenAI 的 text-embedding检索结果是按什么距离排序的是 dense vector search 还是 BM25 混合检索top_k 取几上下文拼出来有多长模型回答“不忠实于材料”时怎么从指标上发现而不是靠感觉。后一种人即使代码写得不算花哨面试官也更愿意要。因为真实业务里的大模型应用本质上就是一连串需要做判断的工程问题。1.2 为什么选“知识库问答 智能客服 多 Agent”这个组合知识库问答是 RAG 最基础的场景用来训练你处理数据、检索、生成三条链路智能客服需要把 RAG 能力和工具调用结合让模型不再只会“说”而是能“做”多 Agent 协作则把问题复杂度再推一层让模型面对“查订单 判断退款规则 回复用户”这类组合任务时知道怎么拆解和执行。三个模块难度是递增的对应大模型岗位的三类常用能力模块核心能力简历上可以写成的关键词知识库问答RAG 流程设计、向量检索、prompt 约束RAG、Embedding、Faiss、召回评估智能客服Function Calling、工具调用、业务接口集成Agent、Tool Use、对话状态管理多 Agent 协作路由、编排、状态流转、质量校验LangGraph、多 Agent、反思机制、Agentic RAG有一点要提醒这类项目并不代表你完成了生产环境的全部工作。真实上线的系统还需要统一的身份鉴权、日志追踪、模型成本和容灾方案。本文先带你打通最小可用闭环把“面试会问但普通教程不讲”的部分补齐。2. RAG、LangChain、Agent 到底分别解决什么问题2.1 RAG给大模型配一个允许随时翻书的助手大模型的知识在训练完成后就固定了而且内部知识无法追溯到具体出处。企业场景里产品文档、售后政策、内部制度往往每天变化模型不可能及时知道。RAG 的做法是用户提问时先从外部知识库里检索相关内容把检索到的片段拼进 prompt再让模型参考这些材料回答。它最直接的价值有两个解决知识时效性不需要反复重新训练模型降低幻觉概率因为模型有了明确可引用的信息来源。RAG 不是神秘的技术它四条链路很清楚文档加载 - 文本切分 - Embedding 向量化并建索引 - 检索增强生成。这里的“检索”主流实现就是 dense vector search把文本映射为稠密向量用向量相似度找到最相关的候选片段。工程化时真正麻烦的是前两步数据格式乱七八糟、切分后语义被截断、Embedding 模型没选好。后面章节会逐个处理。2.2 Agent让模型从“只说不做”变成“能调用、能决策”RAG 只能回答知识类问题。如果用户问“帮我查一下订单到哪了”知识库里不可能也不应该存某个用户的订单物流因为这是实时数据必须通过业务接口拿到。这时需要 Agent。一个具备了工具调用能力的 Agent工作方式可以理解为三步循环理解用户意图决定调用哪个工具、传入什么参数拿到工具返回结果后继续组织最终回答。比如用户说“我买了你的耳机现在想退货订单号是 A2024001”。纯 RAG 模型会去知识库里找“退货政策”带了工具能力的 Agent 会先通过订单工具确认订单状态再结合退货政策给出结论。判断一个项目是否需要引入 Agent不需要看概念多高级只看一条用户任务里是否需要做实时查询或状态变更。不需要就老老实实用 RAG需要再考虑 Agent。2.3 LangChain 和 LangGraph 的真正区别搜索热词里最常见的问题是“LangChain 和 LangGraph 的区别”。很多同学理解成新框架替代老框架这是误区。LangChain 的核心价值是做组件抽象文档加载器、文本切分器、向量库、模型等都能统一成接口然后用一条固定的链串联。适合流程明确、不需要修改执行路径的任务比如标准 RAG。LangGraph 解决的是 LangChain 在“有状态、循环、分支”场景里的不自然。它的核心是一个可执行的图每个节点是一个函数节点之间的边可以是顺序、条件或循环。多 Agent 场景里常有“先检索质量不行就改写再检索”这种循环用 LangGraph 表达比手动维护 while 循环要清晰得多。用一句话记忆LangChain 是把零件拼成流水线LangGraph 是把流水线升级成一张可以回头的路网。本文以 LangChain 风格的组件作为基础代码在多 Agent 部分给出类似 LangGraph 的编排思路。这样即使你不熟悉某个具体 API也能理解它的运行逻辑。3. 项目技术选型与整体架构设计3.1 三大模块怎么协作完整的项目可以放在一个仓库里目录上按数据层、索引层、服务层、Agent 层拆分避免面试时被问到“项目结构怎么分层”时回答不出来。三个模块之间不追求微服务化重点是让代码清晰体现能力分层。模块一实现基础检索问答模块二在模块一基础上加工具调用模块三把模块一和模块二包装成可以被编排调用的 Agent。3.2 技术栈选择为了让项目既能演示又不依赖在线付费服务建议使用以下组合组件作用建议Python 3.10开发语言建议用虚拟环境不污染全局LangChain文档加载、切分、组件编排用稳定版本即可本文不依赖过新的实验 APIsentence-transformers本地 Embedding可选用 BAAI/bge-small-zh-v1.5 这类中文模型Faiss向量检索索引faiss-cpu 足够启动阶段使用OpenAI SDK调用大模型走 OpenAI 兼容协议方便切换不同服务FastAPI可选做服务接口项目成熟后可把内部函数暴露成 HTTP API大模型接口统一采用 OpenAI 兼容协议通过环境变量配置base_url、api_key、model正式代码里不要硬编码。版本细节以你实际安装为准重点是理解流程。3.3 项目目录设计与环境变量项目目录建议这样设计rag-agent-project/ ├── data/ │ ├── docs/ # 原始知识文档放 md/txt/pdf │ └── faiss_index.bin # 向量索引文件 ├── src/ │ ├── loader.py # 加载与切分 │ ├── vector_index.py # 构建索引 │ ├── retriever.py # 检索逻辑 │ ├── llm_client.py # 统一的大模型访问封装 │ ├── rag_pipeline.py # 模块一RAG 问答 │ ├── agent_customer.py # 模块二智能客服 Agent │ └── agent_multi.py # 模块三多 Agent 协作 ├── eval/ │ └── eval_retrieval.py # 召回评估脚本 ├── .env.example └── requirements.txt.环境变量文件.env.example如下LLM_BASE_URLhttps://your-llm-service.com/v1 LLM_API_KEYsk-your-key LLM_MODELyour-model-name EMBEDDING_MODELBAAI/bge-small-zh-v1.5这样的设计在简历里很容易写成一句有说服力的话“项目支持通过环境变量切换不同模型服务知识索引与推理服务解耦。”这句话比“我搭建了大模型问答系统”专业得多。4. 模块一基于 RAG 的知识库问答实现4.1 文档加载与文本切分为什么不能直接整篇丢给模型很多同学做 RAG 时直接拿整份 PDF 去窗口里做相似度搜索效果很差。因为大模型上下文有限而且用户问题往往只涉及文档中的一小块内容。检索单位太大向量会被无关内容稀释命中率自然不高。正确的做法是把文档切成长度适中的片段同时保留语义完整性。LangChain 的RecursiveCharacterTextSplitter是常用的切分器它优先按段落分隔符切再按句子标点切尽量不让一句话被硬生生截断。下面的示例代码以 markdown 和 txt 文档为例。如果你的原始数据是 PDF可以换成 PDF 加载器。# src/loader.py from pathlib import Path from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter def load_and_split(source_dir: str data/docs, chunk_size: int 500, chunk_overlap: int 50): 加载目录下的文档并切分成 chunk docs [] for file_path in Path(source_dir).glob(*.md): loader TextLoader(str(file_path), encodingutf-8) docs.extend(loader.load()) splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapchunk_overlap, separators[\n\n, \n, 。, , , , , ], ) chunks splitter.split_documents(docs) print(f切分后文档块数量: {len(chunks)}) return chunks这里有两个重点chunk_overlap不能随便设成 0。相邻片段做少量重叠可以避免一个完整知识点刚好被切到两个片段边界而检索不到。separators里加入中文标点比默认的英文分隔符对中文更友好。切分长度要综合考虑你的 Embedding 模型支持长度、知识文档的结构、用户问题的颗粒度。如果只是做 demo先跑通后面再根据评测调参。4.2 Embedding 与向量索引构建文本切完后需要把每个片段转成向量。这里选用本地 sentence-transformers 模型优点是离线、可控、适合项目演示也方便后续扩展成特定的领域语义模型。向量索引采用 Faiss。Faiss 支持多种索引启动阶段用IndexFlatIP内积或IndexFlatL2欧氏距离即可。经过归一化后使用内积等同于计算余弦相似度。# src/vector_index.py import json import faiss import numpy as np from sentence_transformers import SentenceTransformer INDEX_PATH data/faiss_index.bin META_PATH data/chunk_meta.json def build_index(chunks, model_name: str BAAI/bge-small-zh-v1.5): 将文档片段向量化并写入本地 Faiss 索引 model SentenceTransformer(model_name) texts [c.page_content for c in chunks] metas [{page_content: c.page_content, **c.metadata} for c in chunks] embeddings model.encode(texts, normalize_embeddingsTrue) embeddings np.asarray(embeddings, dtypefloat32) dim embeddings.shape[1] index faiss.IndexFlatIP(dim) index.add(embeddings) faiss.write_index(index, INDEX_PATH) with open(META_PATH, w, encodingutf-8) as f: json.dump(metas, f, ensure_asciiFalse, indent2) print(f索引已保存: {INDEX_PATH}, 共 {len(texts)} 条)调用方式from src.loader import load_and_split from src.vector_index import build_index chunks load_and_split() build_index(chunks)normalize_embeddingsTrue这一步很关键。如果不做归一化IndexFlatIP 受向量模长影响检索结果的可解释性会变差。Faiss 索引和 metadata 建议分开存储索引里只放向量文本内容放 JSON后续更新文档时不用重新写入全部向量。4.3 检索与答案生成自己写检索反而更可控项目里不直接使用黑盒的create_retrieval_chain而是手写检索函数。原因很简单面试官问“你的 top_k 怎么定的”你不能只能说“框架默认值”。自己写检索函数参数和数据流向一目了然。# src/retriever.py import json import faiss import numpy as np from sentence_transformers import SentenceTransformer INDEX_PATH data/faiss_index.bin META_PATH data/chunk_meta.json MODEL_NAME BAAI/bge-small-zh-v1.5 _index None _meta None _model None def _load_resource(): global _index, _meta, _model if _index is None: _index faiss.read_index(INDEX_PATH) if _meta is None: with open(META_PATH, r, encodingutf-8) as f: _meta json.load(f) if _model is None: _model SentenceTransformer(MODEL_NAME) return _index, _meta, _model def search(query: str, top_k: int 4): 返回 (命中文档列表, 相似度分数列表) index, meta, model _load_resource() q_vec model.encode([query], normalize_embeddingsTrue) q_vec np.asarray(q_vec, dtypefloat32) scores, ids index.search(q_vec, top_k) docs [meta[i][page_content] for i in ids[0]] return docs, scores.tolist()[0]生成阶段我们统一用 OpenAI 兼容 client 去调用模型。这样代码不绑定某一家模型Switch 起来很方便。# src/llm_client.py import os from openai import OpenAI _client None def get_client(): global _client if _client is None: _client OpenAI( base_urlos.getenv(LLM_BASE_URL), api_keyos.getenv(LLM_API_KEY), ) return _client def chat(messages, temperature: float 0.2): client get_client() resp client.chat.completions.create( modelos.getenv(LLM_MODEL), messagesmessages, temperaturetemperature, ) return resp.choices[0].message.contentRAG 的主流程是先检索再把检索结果拼进 system prompt让模型明确只能引用材料回答。# src/rag_pipeline.py from src.retriever import search from src.llm_client import chat def rag_answer(question: str, top_k: int 4): docs, scores search(question, top_ktop_k) context \n\n---\n\n.join(docs) messages [ { role: system, content: ( 你是企业知识库问答助手。请严格根据提供的资料回答问题\n 如果资料中没有相关信息直接回答“资料中未找到相关内容”不要编造。\n 回答时优先引用原文口径。 ), }, { role: user, content: f资料如下\n{context}\n\n问题{question}, }, ] answer chat(messages) return {answer: answer, source_docs: docs, scores: scores}这个环节你会注意到我们并没有依赖 LangChain 的 chain 对象而是用了最直白的函数。原因是核心链路应该由自己控制LangChain 的价值在于前期的文档加载、切分以及后续如果你需要把它接入更大编排体系时的标准化表达。4.4 最小验证写好上面代码后准备一个data/docs/product_help.md内容随便写几条产品 FAQ比如“退换货政策”“保修期多久”“发货时间”。然后运行python -c from src.rag_pipeline import rag_answer; print(rag_answer(产品保修期是多久))预期效果答案里能看到知识库中写明的保修期限同时source_docs里返回命中的原文片段。如何判断成功先别看答案流畅不流畅先检查检索命中文档里是否包含正确的原文片段。如果检索就错了后面生成再流畅也是错的。检索正确后再看回答有没有加入材料之外的信息。5. 模块二智能客服 Agent 与 Function Calling5.1 为什么纯 RAG 做不了合格的客服如果把模块一直接接到客服场景会出现一个很尴尬的咨询“我的订单为什么还没发货”知识库里有“发货时间一般是 48 小时”但用户的订单状态在订单系统里模型完全不知道这个订单是已支付、待发货还是已发货。此时模型只能含糊回答“请您耐心等待”。真实客服需要的是实时查订单然后结合物流知识给结论。这里就需要 Function Calling。它不是让模型自己去访问数据库而是让模型从用户话里提取参数然后决定调用哪个函数。真正执行函数的是我们的可信代码结果再交回模型组织语言。5.2 定义订单查询工具先写一个工具函数。出于安全考虑这里用 mock 数据模拟真实业务接口真实项目中这个函数内部应该调用经过鉴权的订单服务。# src/order_tool.py import json def get_order_status(order_id: str) - str: 模拟订单系统接口。 真实项目里应在这里调用内部订单 API 并确保当前用户对该订单有访问权限。 mock_order { A2024001: { order_id: A2024001, status: 已发货, courier: 顺丰速运, tracking_no: SF1234567890, logistics: 包裹正在运输途中, }, A2024002: { order_id: A2024002, status: 退款中, courier: , tracking_no: , logistics: 退款申请已提交预计1-3个工作日到账, }, } order mock_order.get(order_id) if not order: return json.dumps({error: 未查询到该订单}, ensure_asciiFalse) return json.dumps(order, ensure_asciiFalse)注意当真实接入订单系统时必须做权限校验。比如用户 A 不能通过构造order_id查到用户 B 的订单详情。这是 Agent 工具落地的安全红线。5.3 组装 Function Calling 的主流程下面的代码演示一个最小但完整的 Function Calling 流程模型先返回tool_calls我们再执行对应工具再把工具结果作为新的消息传回模型。# src/agent_customer.py import json import os from openai import OpenAI from src.order_tool import get_order_status client OpenAI( base_urlos.getenv(LLM_BASE_URL), api_keyos.getenv(LLM_API_KEY), ) model os.getenv(LLM_MODEL) TOOLS [ { type: function, function: { name: get_order_status, description: 根据订单号查询订单当前状态和物流信息, parameters: { type: object, properties: { order_id: { type: string, description: 用户提供的订单号, } }, required: [order_id], }, }, } ] def customer_agent(user_text: str) - str: messages [ { role: system, content: ( 你是电商智能客服。你可以查询订单状态。 当用户询问订单物流时先使用工具查询。 ), }, {role: user, content: user_text}, ] resp client.chat.completions.create( modelmodel, messagesmessages, toolsTOOLS, ) msg resp.choices[0].message if not getattr(msg, tool_calls, None): return msg.content or # 模型决定调用工具执行并回传结果 tool_calls msg.tool_calls messages.append(msg) for tc in tool_calls: if tc.function.name get_order_status: args json.loads(tc.function.arguments) result get_order_status(args[order_id]) messages.append({ role: tool, tool_call_id: tc.id, content: result, }) final_resp client.chat.completions.create( modelmodel, messagesmessages, ) return final_resp.choices[0].message.content需要特别强调的是 Tool 消息里的tool_call_id必须与助手消息中模型返回的tc.id一致。很多时候 function calling 报错不是工具写错而是消息顺序和 id 没对上。调用方式from src.agent_customer import customer_agent print(customer_agent(你好帮我查一下订单 A2024001 到哪了))预期输出模型不仅能说“您的订单已发货”还会结合工具返回的物流公司、运单号说明“正在运输途中”。这一段的面试考点是Function Calling 的执行是模型决定、代码执行不要在大模型侧传敏感参数所有工具函数都要做输入校验工具返回结果必须先被模型看到再由模型组织回答。6. 模块三多 Agent 协作与 Agentic RAG6.1 什么时候需要多 Agent很多教程把多 Agent 包装得很玄好像多个模型角色在里面互相聊天就是多 Agent。实际项目里滥用会带来三个问题调用成本成倍增加、错误更难追踪、回复延迟明显上升。什么时候才值得用多 Agent我认为需要满足两个条件用户任务可以被清晰拆成多个有边界的子任务每个子任务分别依赖不同的工具或知识库且有验证中间结果的需要。举个具体场景用户问“我买的耳机有点问题申请退款的话订单已经发货了还能退吗”简单 RAG 能回答的是知识库里写了退货政策。简单 Agent 能回答的是当前订单状态。但完整回答需要模型先判断“订单状态是否影响退货政策适用范围”然后把“订单已发货 政策允许 7 天无理由退货”两个信息组合起来。这个组合过程如果一次输错很难定位是检索环节错了还是模型判断环节错了。所以多 Agent 的核心不是“用多个模型聊天”而是把流程拆成多个可独立检查的节点。每个节点负责一件事节点之间用显式规则或另一个调度模型连接。6.2 supervisor worker 的落地设计项目里最值得实现的设计是 supervisor worker主管 Agent 负责理解用户请求然后把任务路由到不同 worker知识库 worker负责检索产品手册和售后政策订单 worker负责查询实时订单状态质检 worker负责检查最终回答是否忠实于材料。如果团队刚开始落地不一定要用复杂图引擎。用普通 Python 函数也可以先跑通一个“检索 - 生成 - 质检 - 反馈改进”的循环。这个循环在业内常被称为 Agentic RAG比直接 RAG 多出的部分就是“模型自己判断答案质量不好时可以改写检索词再来一轮”。下面给一个轻量但面试会认可的实现# src/agent_multi.py from src.retriever import search from src.llm_client import chat def generate_draft(question: str, context: str) - str: messages [ { role: system, content: 你是知识库问答 Agent严格根据给定材料回答问题。, }, { role: user, content: f材料{context}\n\n问题{question}, }, ] return chat(messages) def judge_answer(question: str, draft: str, context: str): 质检 Agent判断答案是否忠实于材料并输出结构化结论 messages [ { role: system, content: ( 你是质检 Agent。请判断下面的回答是否完全基于给定材料 不要要求回答包含材料外的内容。 输出 JSON{\pass\: true/false, \reason\: \原因\} ), }, { role: user, content: f材料{context}\n\n问题{question}\n\n回答{draft}, }, ] result chat(messages, temperature0) return result def rewrite_query(question: str, reason: str) - str: 改写 Agent根据质检反馈把问题改写得更利于检索 messages [ { role: system, content: 你是检索改写 Agent根据反馈把原问题改写成更适合知识库检索的形式只输出改写后的问题。, }, { role: user, content: f原问题{question}\n\n质检反馈{reason}, }, ] return chat(messages, temperature0) def multi_agent_resolve(question: str, max_rounds: int 3): query question best_draft for round_idx in range(max_rounds): docs, _ search(query, top_k5) context \n\n---\n\n.join(docs) draft generate_draft(question, context) best_draft draft verdict judge_answer(question, draft, context) if pass: true in verdict or pass: True in verdict: return {answer: draft, rounds: round_idx 1, verdict: verdict} rewrite_reason 检索结果可能不完整需要换关键词重新检索 query rewrite_query(question, rewrite_reason) return {answer: best_draft, rounds: max_rounds, verdict: reach max rounds}这个实现虽然不复杂却展示了多 Agent 最核心的价值答案不是一次性生成的而是经过“生成 - 质检 - 改写 - 再检索”的闭环。简历上写“引入质检 Agent对回答进行 faithfulness 校验失败时自动改写查询词循环检索最终将无效回答比例降低”比空泛地写“使用 LangGraph 实现多 Agent”有说服力得多。真实项目如果准备上 LangGraph以上内容正好可以映射成图上的节点和条件边检索节点 - 生成节点 - 质检节点 - 条件边通过进返回不通过进改写节点。这也是 LangGraph 与 LangChain 最直观的区别LangChain 适合固定直线流程LangGraph 适合这种会回头的循环流程。7. 怎么评测 RAG 效果避免简历项目被问倒7.1 没有指标项目就只能停留在“调通 demo”很多同学做完 RAG 后只给出一句“效果不错”。面试官继续追问“怎么评价效果不错”就卡住了。原因在于RAG 的好坏不能靠肉眼判断一两轮对话。项目至少要能回答三类问题检索准不准——相关片段有没有被召回生成忠实不忠实——模型有没有照着材料回答答案能不能解决用户问题。你可以先做一个检索集来评估召回效果。准备一个简单的测试集格式如下question,expected_content 产品保修期是多久,保修期为一年 如何申请退款,在订单详情页点击申请退款 发货一般需要几天,48小时内发货再写一个计算命中率的脚本# eval/eval_retrieval.py import csv from src.retriever import search def load_questions(path: str): with open(path, r, encodingutf-8) as f: return list(csv.DictReader(f)) def eval_hit_rate(path: str, top_k: int 4): data load_questions(path) hit 0 for row in data: docs, _ search(row[question], top_ktop_k) joined \n.join(docs) if row[expected_content] in joined: hit 1 print(fHit{top_k}: {hit / len(data):.2%})这个脚本的价值是让你能回答“top_k 为什么要设为 4”。你可以跑top_k2和top_k4两组数据看命中率变化说明你做过参数实验。7.2 理解 RAG 知识库的核心指标更专业的 RAG 评测框架会输出多类指标建议理解并写到简历里。比如指标分类直观含义回答什么问题Context Precision检索结果里有多大比例是必要的给大模型的材料有没有夹带太多无关内容Context Recall该召回的上下文是否真的被召回了漏关键材料没有Faithfulness生成内容是否忠实于检索材料大模型有没有自己编造Answer Relevance回答是否切题模型答非所问Answer Correctness回答与标准答案是否一致最终业务结论对不对项目中即使只跑通 Hit Rate也应该能讲清楚上表每一个词。面试官考察的不是你“用过 RAGAS”而是你能否解释“哪些指标衡量检索、哪些指标衡量生成”。需要提醒不要在生产环境拿用户真实问题直接灌给在线评测服务。涉及业务数据时优先基于脱敏后的测试集做离线评测。8. 常见问题与排查思路问题现象可能原因排查方式解决方案检索结果明显不对答非所问Embedding 模型与领域不适配打印返回的source_docs人工检查语义换成中文领域效果更好的 embedding 模型或引入 BM25 混合检索模型总把知识库没有的内容写成“已知”Prompt 没有强制约束检查 prompt 里有没有“只能根据材料回答”的指令明确 system prompt让模型不知道时直说“资料中未找到”切分后句子被截断、上下文割裂chunk_size 太小或分隔符不合适查看相邻 chunk 的末尾与开头调整 chunk_size 和 overlap把中文标点加入 separators设置了相同参数但效果不稳模型 temperature 过高记录多次输出对比知识问答场景 temperature 调低比如 0.1 到 0.3Function Calling 没有触发工具描述不清晰或模型不支持打印模型返回的完整响应检查工具描述是否包含足够触发条件换支持函数调用的模型服务tool_call_id 报错消息顺序不对打印 messages 列表确保 assistant tool_calls 消息在 tool 消息之前且 id 一一对应更换公司模型后 API 经常 401环境变量没生效检查启动环境里的 key使用.env统一管理禁止提交明文 key 到代码仓库8.1 在线模型效果差要不要先排查这几个环节不要一上来就换大模型。建议顺序是先看检索回来的文本对不对再看 prompt 约束够不够最后才看模型本身。RAG 项目里检索决定了回答的上限生成只是把上限尽量兑现。另外在做数据清洗时要注意删除文档里的导航栏、版权声明、广告等噪声否则这些文本会被向量化并干扰检索。这个动作不需要很高技术含量但决定项目效果下限。9. 怎么写进简历以及下一步怎么继续深入如果你决定把本项目的代码完整整理出来简历里可以参考下面这种“动作 方法 结果”的句式每条都对应实际代码面试被追问也有据可答设计并实现企业知识库 RAG 问答系统完成文档加载、中文文本切分、本地向量索引构建、检索增强生成全流程基于 Function Calling 开发智能客服 Agent通过工具调用完成订单状态查询并形成工具参数校验与结果回传闭环引入质检 Agent 对模型回答进行 faithfulness 校验失败时自动改写检索词循环重试验证了拒答与兜底策略构建小型离线评测集统计 HitK 召回命中率并基于该指标调整 chunk_size 和 top_k 参数。注意不要把自己没做的事写成已上线。简历里写“完成离线 demo”和“上线生产环境”是两回事面试官更反感的是过度包装。从学习路径看下一步可以按以下顺序继续深入把检索升级为“向量召回 关键词召回”的混合检索观察命中率变化在编排层尝试用 LangGraph 将多 Agent 流程显式建模条件边连接质检节点与改写节点为每个请求记录 tracequery、召回文档、模型输出、耗时、评测判定形成可观测闭环如果条件允许接入真实业务接口时把权限校验做成独立的工具调用层避免 Agent 裸奔。这套项目做完后你对大模型应用开发的认识会更接近“工程系统”而不是“API 封装工具”。写简历之前先花一天时间把项目目录整理干净把.env.example配好把 README 写清楚。项目质量往往从 README 就能看出一个候选人的
返回列表