从原型到生产:构建高可靠RAG系统面临的四大挑战与解决方案

目录

挑战一:检索质量不稳定

挑战二:长文档与上下文窗口限制

挑战三:回答的事实一致性与幻觉

挑战四:多轮对话中的上下文管理

总结


在构建 Smart Contract Sentinel(智能合同哨兵) 的过程中,我们需要处理大量的法律法规和合同文本。最初,我们通过简单的"Embedding + 向量数据库"构建了基础的 RAG(检索增强生成)系统,但在实际应用中,我们很快遇到了检索不精准、回答幻觉、上下文丢失等典型问题。

本文将结合 Smart Contract Sentinel 的实际代码和架构,分享我们在构建高可靠 RAG 系统时面临的四大挑战及解决方案。

整体架构图

为了直观展示我们正在构建的 RAG 系统优化路径,以下是系统的整体数据流转图:

复制代码

挑战一:检索质量不稳定

问题描述

在法律场景下,用户查询往往包含精确的法律术语(如"不可抗力"、"违约责任")。单纯依赖语义检索(Dense Retrieval)有时会因为语义泛化而忽略了关键词的精确匹配,导致检索结果相关性不足。

当前现状

在我们的 backend/utils/ragSetting/vector_store.py 中,目前主要使用了 Milvus 进行向量检索:

python 复制代码
# backend/utils/ragSetting/vector_store.py
async def milvus_async_query(query: str, collection_name: str = "law_default", top_k: int = 5) -> str:
    # ... 省略部分初始化代码 ...
    retriever = index.as_retriever(similarity_top_k=top_k)
    nodes = retriever.retrieve(query)
    # ...

retriever=index.as_retriever(similarity_top_k=top_k)

创建了一个检索器(Retriever) 对象:

index.as_retriever():从向量索引中创建一个检索器

similarity_top_k=top_k:设置相似度检索的返回结果数量为 top_k 个最相关的文档

nodes = retriever.retrieve(query)

这行代码执行实际的检索操作:

retriever.retrieve(query):使用检索器对查询语句 query 进行检索

返回值 nodes:包含检索到的相关文档节点列表

解决方案:混合检索(Hybrid Search) + 重排序(Re-ranking)

为了解决这个问题,我们计划引入 混合检索 策略,结合 BM25 稀疏检索 (擅长关键词匹配)和 Embedding 密集检索(擅长语义理解)。

python 复制代码
# 伪代码示例:混合检索实现
def hybrid_search(query, dense_weight=0.7, sparse_weight=0.3):
    # 1. 密集检索 (原有逻辑)
    dense_results = dense_retrieval(query)
    # 2. 稀疏检索 (新增 BM25)
    sparse_results = bm25_retrieval(query)
    
    # 3. 融合策略 (Reciprocal Rank Fusion)
    combined = weighted_fusion(
        dense_results, sparse_results, 
        dense_weight, sparse_weight
    )
    
    # 4. 重排序 (Re-ranking)
    # 使用 Cross-Encoder 对最终结果进行精细排序
    reranked = reranker.rank(query, combined)
    return reranked

通过这种方式,我们可以确保既能召回语义相关的法条,又不会漏掉包含特定关键词的关键条款。


挑战二:长文档与上下文窗口限制

问题描述

法律法规和合同通常篇幅巨大。如果直接将整个文档塞给 LLM,很容易超出上下文窗口限制,或者导致"Lost in the Middle"(中间信息丢失)现象。

解决方案:结构化切分与递归检索

在 Smart Contract Sentinel 中,我们并没有简单地按字符数切分文档,而是针对法律条文的结构进行了定制化切分。

backend/utils/ragSetting/law_cut.py 中,我们实现了基于正则的层级切分:

python 复制代码
# backend/utils/ragSetting/law_cut.py
def split_law_text(text: str, law_name: str = "劳动合同法") -> List[Document]:
    # ...
    article_pattern = re.compile(r"^\s*(第[零一二三四五六七八九十百]+条)\s*(.*)")
    
    # 按"条"为单位进行切分,保留章节元数据
    # ...
            metadata = {
                "law_name": law_name,
                "chapter_title": current_chapter_title,
                "section_title": current_section_title,
                "article_title": current_article_title,
                "content_type": "article"
            }
            documents.append(Document(text=full_text, metadata=metadata))
    # ...

这种 基于语义结构(Semantic Structure) 的切分方式,保证了每一个检索到的 chunk 都是一个完整的法律条款,而不是被截断的碎片。

进阶优化

未来我们可以引入 父子索引(Parent-Child Indexing):检索时匹配细粒度的子块(如某一款),但在生成答案时返回其父块(完整的法条或章节),以提供更完整的上下文。


挑战三:回答的事实一致性与幻觉

问题描述

在法律咨询中,模型的"幻觉"是不可接受的。模型必须基于确凿的法律条文回答,而不能"自由发挥"。

解决方案:强制引用溯源与工具调用

我们在 backend/analytics/review_graph.py 中,通过 System Prompt 严格限制了模型的行为,并强制其使用检索工具:

python 复制代码
# backend/analytics/review_graph.py
review_system_prompt = """
你是合同审查助手,负责输出结构化的审查结果。
...
如果未发现风险,risks 为空数组,summary 和 suggestion 仍需给出。相应的法律可以调用工具rag_search
    collection_name可选的参数有:
        1.劳动合同法:civil_code22
"""

通过将 rag_search 作为一个明确的 Tool 提供给 Agent(基于 LangGraph),模型在需要法律依据时会主动发起检索,而不是凭空捏造。

优化方向

我们可以增加 自一致性检查(Self-Consistency Check):让模型生成多个回答,或者在生成回答后,再调用一次 LLM 来验证答案中的引用是否真实存在于检索到的文档中。

挑战四:多轮对话中的上下文管理

问题描述

用户在咨询法律问题时,往往会进行多轮追问。例如:

  1. 用户:"劳动合同没签会怎么样?"
  2. 用户:"那如果是试用期呢?"

如果直接检索"那如果是试用期呢?",系统无法理解其含义。

chat_graph.py 文件中的对话历史处理机制。

对话历史处理机制

1. 状态定义

ChatState 类型定义中,对话历史被定义为:

复制代码
class ChatState(TypedDict):
    history: List[BaseMessage]  # 存储对话历史消息

2. 历史消息处理流程

agent_node函数中:

复制代码
def agent_node(state: ChatState) -> dict:
    history = state.get("history") or []  # 获取历史消息,默认为空列表
    input_text = state.get("input_text") or ""
    agent = _build_agent(state.get("model") or "deepseek")
    messages = history + [HumanMessage(content=input_text)]  # 将历史与新消息合并
    agent_result = agent.invoke({"messages": messages})
    # ... 后续处理

3. 关键特点

  1. 消息类型 :使用 BaseMessage 及其子类(如 HumanMessage)来存储消息
  2. 累积式处理:每次调用都会将当前输入与历史消息合并
  3. 状态保持 :对话历史通过 ChatState 在整个会话过程中保持
  4. 顺序维护:消息按时间顺序排列,最新消息追加在末尾

4. 消息格式

  • 历史消息:List[BaseMessage] 包含所有之前的对话消息
  • 新消息:HumanMessage(content=input_text) 包装当前用户输入
  • 合并后:完整的消息序列传递给 LangChain agent

5. 处理流程图示


解决方案:查询重写(Query Rewriting)

虽然目前的 backend/chat/chat_graph.py 主要处理了基本的对话历史,但为了提高检索准确率,我们需要在检索前引入 查询重写 模块。

💡 实际示例

用户输入 : "这个条款的违约金规定是什么?"
对话历史 : 之前讨论了"劳动合同解除"相关条款
重写后 : "劳动合同解除条款中关于违约金的具体规定"

用户输入 : "它有效吗?"
对话历史 : 之前询问了"电子签名法律效力"
重写后 : "电子签名在法律上的有效性认定标准"

实现思路

在调用检索工具前,先让 LLM 基于对话历史将当前问题改写为独立完整的查询。

python 复制代码
# 伪代码:查询重写
current_query = "那如果是试用期呢?"
history = ["用户: 劳动合同没签会怎么样?", "AI: ..."]

rewritten_query = llm.rewrite(history, current_query)
# 结果: "试用期内未签劳动合同会有什么法律后果?"

# 使用重写后的查询进行检索
results = rag_search(rewritten_query)

这样可以显著提高多轮对话场景下的检索命中率。

🛠️具体实现方案

基于当前 chat_graph.py 的架构,建议以下实现方式:

1. 创建查询重写工具
python 复制代码
# 在 chat_graph.py 中添加
def query_rewrite_tool(history: List[BaseMessage], current_query: str) -> str:
    """
    基于对话历史重写当前查询
    """
    # 提取历史对话内容
    conversation_context = "\n".join([
        f"{'用户' if isinstance(msg, HumanMessage) else '助手'}: {msg.content}"
        for msg in history[-6:]  # 最近6条消息作为上下文
    ])
    
    # 使用LLM进行查询重写
    rewrite_prompt = f"""
基于以下对话历史和当前查询,生成一个完整、明确的检索查询:

对话历史:
{conversation_context}

当前查询:{current_query}

请生成一个适合法律文档检索的完整查询语句,要求:
1. 解析并明确所有指代关系
2. 补充必要的上下文信息
3. 使用专业法律术语
4. 保持查询的简洁性和准确性

优化后的查询:
"""
    
    # 调用LLM进行重写(可以使用现有的ChatOpenAI实例)
    rewritten_query = llm.invoke(rewrite_prompt)
    return rewritten_query.strip()
2. 修改 rag_search 调用
python 复制代码
# 在 agent_node 函数中修改检索调用
def agent_node(state: ChatState) -> dict:
    history = state.get("history") or []
    input_text = state.get("input_text") or ""
    
    # 在调用 rag_search 前进行查询重写
    optimized_query = query_rewrite_tool(history, input_text)
    
    # 使用优化后的查询进行检索
    agent_result = agent.invoke({
        "messages": history + [HumanMessage(content=input_text)],
        "query": optimized_query  # 传递优化后的查询
    })
3. 集成架构

🎯 核心价值

  • 提升检索精度:将模糊查询转换为明确的法律术语检索
  • 增强上下文理解:充分利用对话历史中的关键信息
  • 改善指代消解:自动解析代词和上下文引用

总结

构建一个生产级的 RAG 系统远比调用几个 API 复杂。通过 Smart Contract Sentinel 的实践,我们认识到:数据处理的精细度(如结构化切分)、检索策略的组合(混合检索)、以及 Agent 流程的编排(LangGraph) 才是决定系统上限的关键因素。

我们正在持续优化这些模块,致力于打造一个更专业、更可靠的智能合同审查助手。

相关推荐
AI周红伟1 天前
周红伟:Sglang+Vllm+Qwen3.5企业级部署案例实操
大数据·人工智能·大模型·智能体
AI周红伟1 天前
大模型部署入门教程,消费级显卡跑通Qwen3.5-Plus,最低配置部署教程,不能在简单了
大数据·人工智能·大模型·智能体
Java.慈祥1 天前
My First AI智能体!!!
人工智能·python·ai编程·智能体·coze·coze工作流·agent开发
cxr8281 天前
全栈规模化虚拟企业:下一代商业物种的系统演进与架构重构
人工智能·重构·架构·智能体·ai智能体·openclaw
涛涛讲AI2 天前
AI选型终极指南:智能体与技能的优缺点深度对比
技能·智能体·coze·扣子
梦想画家2 天前
无前端编码,解锁Langflow无限可能:自定义Python组件开发全指南
python·智能体·langflow
南_山无梅落2 天前
从LangChain到LangGraph:构建智能Agent的实战指南(二)——LangGraph,当Agent需要“思考循环“
智能体·langgraph
lhxcc_fly3 天前
Coze开发平台
ai·api·sdk·提示词·应用·智能体·coze
Ray Liang4 天前
吊打OpenClaw!国产AI助理MindX开源:Token消耗砍至10%,还能养出专属数字分身
ai·智能体·ai助手·openclaw