众力资讯网

合同检索翻车后,我悟了:别死磕模型

# 项目死在检索上,没人死在模型上晚上十一点,朋友圈刷到一条动态。一个做法律AI的哥们儿发了张截图,线上问答准确率跌到68%,配文就一句:"模型没问题,数据没问题,就是找不着东西。"评论区一片共鸣。这事儿我太熟悉了。半年前我们团队做合同审查RAG,demo演示时投资人眼睛都直了——你问"违约金比例多少",它秒回"20%",还附上条款出处。漂亮得像个魔术。结果一上真实流量,用户问的是"如果我方违约,最坏情况要赔多少钱",系统直接懵了,召回来的全是"违约责任"章节的泛泛之谈,模型被无关信息带偏,答非所问。那一刻才反应过来:选对模型,是整个LLM应用里最简单的一环。真正折磨人的,是检索到的上下文到底可不可靠、工具调用的参数有没有经过验证、模型输出的结构能不能被下游系统直接消费。这三个问题不解决,demo再光鲜,流量一上来就原形毕露。## 别自己造轮子,但你也别把框架当神见过太多团队,一上来就自己撸代码,文档切分器自己写、向量检索自己拼、提示词模板自己攒,折腾三个月,做出来一个比LangChain难用十倍的玩意儿。也有另一个极端,把LangChain当万能钥匙,单轮问答也套个Agent,结果延迟多出两秒,成本翻了三倍,就为了一句"你好,有什么可以帮您"。我的判断标准很简单,就四条:- 回答要锚定在自有知识库、合同、产品文档上,答案必须有出处——上RAG- LLM要以结构化入参调用外部工具,还要校验返回值——上工具链- 检索和生成深度耦合,需要针对数据特性调嵌入模型、调检索策略、调重排逻辑——上LangChain- 需要持久化状态、人工介入、可恢复的长时任务——这其实是图的问题,直接LangGraph如果只是单轮问答,没有检索、没有工具、没有多步逻辑,直接调模型API,便宜又简单。别为了框架而框架。国内有家做智能客服的创业公司,早期团队迷信自研,从文档解析到向量检索全自己写,结果运维成本压垮了利润。后来换成了LangChain加国内向量数据库的组合,一个月节省了40%的研发工时。框架存在的意义就是这个——用工业验证过的积木搭管道,不是自造轮子。## 一个能上生产的RAG,长什么样先把架构代码给你看,版本锁定,别乱升级,血的教训:```pythonfrom langchain_community.document_loaders import PyPDFLoaderfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_openai import OpenAIEmbeddingsfrom langchain_pinecone import PineconeVectorStore# 1. 加载与切分loader = PyPDFLoader("data/contract.pdf")docs = loader.load()splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n\n", "\n", "。", ";", ""],)chunks = splitter.split_documents(docs)# 2. 向量化入库embeddings = OpenAIEmbeddings(model="text-embedding-3-small")vector_store = PineconeVectorStore.from_documents( chunks, embeddings, index_name="contract-knowledge")```切分这个环节看着不起眼,直接决定检索质量。512字符块配64字符重叠,中文语料必须把分隔符换成"。;"层级,不然语义就被拦腰截断。之前有个同事拿默认参数切中文合同,切出来的片段全是半句话,检索出来的东西驴唇不对马嘴。向量化用`text-embedding-3-small`,1536维对合同场景足够。有人非要上`text-embedding-3-large`拉到3072维,检索精度没涨多少,延迟和存储成本倒是翻了一倍多,何必呢。## 召回和生成:RAG的两个生死关单纯的向量检索,在合同问答这种高精度场景根本不够用。语义相近但根本不是答案的段落,召回得贼积极。你在向量库里拿"违约金"去搜,能给你拉回来一堆"赔偿金""保证金""定金"的片段,模型一看,开始自由发挥,答案就废了。引入交叉编码器重排,是业界成本收益比最高的一步优化:```pythonfrom langchain.retrievers import ContextualCompressionRetrieverfrom langchain_community.document_compressors import CrossEncoderReranker# 3. 向量召回 + 重排base_retriever = vector_store.as_retriever( search_type="similarity", search_kwargs={"k": 20})compressor = CrossEncoderReranker( model_name="BAAI/bge-reranker-large", top_n=5)retriever = ContextualCompressionRetriever( base_compressor=compressor, base_retriever=base_retriever,)```召回20篇,重排后只留5篇进上下文。这个策略看着简单,实测下来,合同问答的准确率从71%直接干到89%。代价是重排器在CPU上跑BGE模型要200毫秒以上,但你想想,用一个200毫秒的代价换18个点的准确率,这笔账怎么算都划算。bm25/knn混合检索在某些场景还能再捞3-5个点的召回提升,但会引入额外组件,物理机部署的话运维成本自己掂量。RAG生成环节最容易踩的坑,我踩过,你也别踩。第一个坑:把检索到的全部文本无差别塞进上下文,让模型被无关信息带偏。你召回20篇,就塞20篇,模型连哪个是重点都不知道,回答起来东拉西扯,像极了考试时看了一堆教材但没划重点的学渣。第二个坑:提示词里没定义"不知道"的边界,模型开始一本正经地胡说八道。合同里没写"知识产权归属",你问它,它能给你编出个"归乙方所有",胆儿肥得很。下面这个写法,同时规避两个坑:```pythonfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnablePassthroughfrom langchain_openai import ChatOpenAIprompt = ChatPromptTemplate.from_messages([ ("system", "你是法律助理,严格依据检索材料回答。若材料未覆盖,直接回答:材料未覆盖该问题"), ("human", "检索材料:\n\n{context}\n\n问题:{question}"),])def format_docs(docs): return "\n\n".join(d.page_content for d in docs)model = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_tokens=1024)rag_chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | model)answer = rag_chain.invoke("合同约定的违约金比例是多少?")print(answer.content)````temperature`降到0,压缩随机性。`max_tokens`限制输出长度,防止模型在长回答里自我发挥。LangChain 0.2的Runnable接口用管道符串联每一步,方便在任意节点插入日志和断点。## 可观测性和线上陷阱生产系统和demo的最大区别,就是能不能复盘。上周重排模型从BGE换成了别的,效果是升了还是降了?哪个用户问题检索不到正确答案?没有追踪,这些问题全是瞎猜。LangChain生态的追踪方案,三段环境变量就能搞定:```pythonimport osos.environ["LANGCHAIN_TRACING_V2"] = "true"os.environ["LANGCHAIN_PROJECT"] = "contract-rag"os.environ["LANGCHAIN_API_KEY"] = "ls_你的key"```这一步不能省。没有追踪,重排模型替换后效果是升是降、某个用户问题为何检索不到正确答案,全部无法复盘。之前有次线上反馈"检索结果变差了",团队争论了半天,最后查trace才发现,是有人改了切分参数忘了通知。评估门控从离线测评开始。拉一批真实用户问题建评测集,每个问题配好标准答案和可接受文档集合。用`RagEvaluatorChain`接入LLM-as-a-judge,在CI里跑回归。每次改动检索策略或提示词,都要求回答可追溯至引用文档,否则不放行。原型赶工出来的RAG演示,再过两个星期,线上流量一变就撑不住了。原因主要是检索精度追不上查询分布的漂移——用户提法跟训练语料里的说法不一样,你合同里写"甲方逾期交付",用户问的是"如果对方拖了三个月还没交货怎么办",字面不匹配,向量检索直接抓瞎。延迟预算也不达标,向量检索本身要控制在80ms内返回,重排器在CPU上跑BGE模型可能拖到200ms以上,用户的耐心可没这么大,多点几次就跑了。再加上召回片段里混入相似但不是答案的内容,模型被带偏,回答质量急转直下。这些问题,光靠调提示词解决不了。要在评估集上持续迭代切分策略、检索参数和重排阈值。重排成本如果无法接受,把召回候选从20降到10,配合MMR多样性检索缓解上下文污染,也是一条路。## 最后说点实在的LangChain能不能生产落地,取决于你怎么用它。它更像是一套基础设施,提供已验证的组件和生态集成,但检索策略、评估体系、延迟预算、可观测链路,才是RAG系统真正拉开差距的地方。单轮问答,别用框架。多步检索加工具调用,LangChain足够。需要持久化状态、人工审批或长时恢复的工作流,LangGraph是下一步的自然演进。原型之上,是每一条trace都可追踪、每一个改动都有评测数据支撑的交付物。别等到线上流量教做人,才想起把这些补上。毕竟,做一个能跑的系统容易,做一个能站稳的系统,需要的是对自己的项目较真。