1. 项目缘起:当“法律专家”Claude开始“胡说八道”

最近在折腾一个挺有意思的项目,叫Claude-for-Legal。顾名思义,这是基于Anthropic的Claude模型,专门为法律领域微调或构建的一个智能体(Agent)。它的愿景很美好:能理解复杂的法律条文、分析案例、起草合同,甚至预测案件走向,堪称一位不知疲倦的AI法律助理。

然而,在实际部署和测试中,我和团队遇到了一个非常典型且棘手的问题: 当我们为这个法律Agent平铺(即同时启用或集成)多个Skill(技能)或MCP(Model Context Protocol,模型上下文协议)服务器时,它的回答准确率会出现显著且难以预测的下降。 比如,你同时让它使用法律条文检索(RAG知识库)、案例查询(另一个MCP)和合同格式检查(一个Skill),它可能会把不同来源的信息张冠李戴,或者在需要精确引用法条时,给出一个模糊甚至错误的解释。

这可不是小事。在法律场景下,准确性就是生命线。一个错误的法条引用或案例解读,轻则闹笑话,重则可能引发严重的误导。这个问题不解决,Claude-for-LLegal就只能停留在玩具阶段,无法投入实际应用。

所以,这篇内容就来深度剖析一下Claude-for-Legal这个项目,并聚焦于解决“多Skill/MCP平铺导致准确率下降”这个核心痛点。我们会从问题现象入手,拆解其背后的技术原理,最后给出经过我们实战验证的解决方案和架构优化思路。无论你是正在开发垂直领域Agent的工程师,还是对RAG、MCP、Skill编排感兴趣的研究者,相信都能从中获得启发。

2. 核心概念拆解:Skill、MCP与Agent在法律场景下的协同与冲突

要解决问题,首先得理解问题中涉及的几个关键角色。它们听起来可能有些抽象,但我们可以用律师事务所的团队来类比。

Claude-for-Legal (Agent): 这就是我们团队的核心“合伙人律师”。它拥有深厚的法律知识基础(来自Claude模型的预训练和可能的领域微调),负责最终面对客户(用户),理解问题,组织答案,并做出判断。它的“大脑”就是大语言模型(LLM)。

Skill(技能): 可以理解为这位合伙律师掌握的“专项技能”或“内部工作流程”。比如:

  • 合同审阅Skill: 一套固定的分析模板和检查清单,律师拿到合同后,会自动套用,检查关键条款。
  • 法律文书生成Skill: 根据案件类型和客户信息,自动生成起诉状、答辩状等文书的初稿。
  • 法律检索Skill(内部版): 一个封装好的函数,能按照特定格式查询本地法规数据库。 Skill通常是编写好的、相对固定的代码或提示词(Prompt)模板,直接内嵌在Agent的“工作记忆”或调用流程中。它的特点是 响应快、流程固定 ,但灵活性和外部信息获取能力可能有限。

MCP(Model Context Protocol)服务器: 这就像是律师事务所合作的 外部专家团队或专业数据库服务商 。Anthropic推出MCP,就是为了让Claude等模型能安全、标准化地调用外部工具和数据源。

  • 法律条文数据库MCP: 一个实时更新的全国法律法规库,Claude可以通过标准协议向它查询“《民法典》第584条的具体内容是什么?”
  • 裁判文书网MCP: 连接至公开的司法案例数据库,可以检索类似案例。
  • 企业信息查询MCP: 连接至工商信息数据库,核实对方当事人资质。 MCP的核心价值在于 标准化接口和动态数据获取 。Agent不需要知道数据库的具体实现,只需要通过MCP协议发送请求,就能拿到最新的、结构化的外部信息。这极大地扩展了Agent的能力边界。

RAG(检索增强生成)知识库: 这是律师事务所的 内部核心知识库 ,包含了历年的典型案例总结、内部办案指引、合伙人笔记等非公开但极具价值的资料。当遇到新案件时,律师会先从这个知识库里检索相关历史经验和资料,再结合自己的分析形成观点。在技术实现上,RAG通常通过向量数据库存储知识片段,在用户提问时进行语义检索,并将检索到的相关文本作为上下文(Context)注入给LLM,从而生成更准确、更具针对性的回答。

那么,冲突是如何产生的?

想象一下这个场景:我们的“合伙人律师”(Agent)同时面对多位“专家”(MCP服务器)和多项“内部流程”(Skill)。当客户(用户)提出一个复杂问题,比如“起草一份涉及股权纠纷的借款合同,并评估我方风险”时:

  1. 合同起草Skill被触发,开始套用借款合同模板。
  2. 法律检索MCP被调用,去查询《公司法》、《合同法》相关条款。
  3. 案例查询MCP被调用,寻找类似股权纠纷的判决。
  4. RAG知识库被检索,查找所内处理过的类似合同范本和风险点。

问题来了:所有这些信息——固定的模板、动态的法条、具体的案例、历史的经验——几乎同时涌向Agent。LLM的上下文窗口(Context Window)是有限的(比如200K tokens)。信息过载会导致几个问题:

  • 注意力稀释: 关键信息(如某个特殊法条)被淹没在海量上下文中,模型无法有效聚焦。
  • 指令冲突: 不同来源的信息可能存在细微差异或表述不同,模型需要花费大量“精力”去理解和调和这些冲突,而不是专注于生成最佳答案。
  • 优先级混乱: 模型难以判断哪些信息是当前任务最相关的,可能导致它过于依赖某个Skill的固定输出,而忽略了更重要的MCP实时检索结果,或者相反。
  • 幻觉加剧: 在混乱的上下文中,模型“捏造”事实(即产生幻觉)的概率会大大增加,因为它试图从相互竞争的信息碎片中拼凑出一个合理的答案。

这就是“平铺”架构的弊端:简单粗暴地让所有能力同时待命,看似强大,实则引入了巨大的噪声和决策复杂度,最终导致输出质量下降。在法律这种高精度要求的领域,这种下降是致命的。

3. 问题根因深度剖析:为什么平铺架构会“失准”?

上一节我们看到了现象,这一节我们深入到技术层面,看看“失准”究竟是如何发生的。这不仅仅是信息太多那么简单,而是涉及LLM工作机理、上下文管理、任务调度等多个层面的系统性问题。

3.1 上下文污染与“注意力劫持”

LLM,包括Claude,本质上是一个基于概率的序列预测器。它的输出严重依赖于我们提供的输入上下文(Prompt + Context)。当我们把多个Skill的指令、多个MCP返回的原始数据、RAG检索出的多段文档,不加处理地全部塞进同一个上下文窗口时,就制造了一个高度“污染”的环境。

  • 无关噪声干扰: 对于“评估借款合同风险”这个任务,RAG知识库里关于“劳动合同”的片段、MCP返回的“诉讼程序法条”可能都是无关噪声。它们会占据宝贵的token位置,并分散模型的注意力。模型需要消耗计算资源去“理解”这些无关信息,并努力将它们与当前任务建立(可能并不存在的)联系,这直接损害了核心任务的推理质量。
  • 格式与指令冲突: 不同的Skill和MCP输出格式各异。一个Skill的输出可能是Markdown列表,另一个可能是JSON。一个MCP返回纯文本法条,另一个返回带注解的案例摘要。模型需要不断切换“解析模式”,这增加了认知负荷。更糟糕的是,如果两个来源对同一概念有不同表述(例如,对“不可抗力”的定义略有差异),模型就会陷入困惑,其输出会变得模糊或自相矛盾。
  • 关键信号被淹没: 假设有一条来自MCP的、非常关键的司法解释,但它被埋在了几十条其他法条和案例中间。由于Transformer架构的自注意力机制虽然是全局的,但在有限的计算深度内,过于分散的信息会导致对关键信号的“注意力权重”被稀释。模型可能“看到”了这条信息,但未能给予其应有的重视。

3.2 任务调度与编排缺失

“平铺”意味着所有组件处于平等、并发的状态,缺乏一个 智能调度器(Orchestrator) 。这就好比让律所里所有律师和助理同时涌进会议室回答客户一个问题,场面必然混乱。

  • 缺乏优先级判定: 系统没有机制判断,对于当前用户问题,是应该先执行合同起草Skill,还是先调用法律检索MCP?或者是先进行RAG检索获取背景知识?不同的执行顺序,会导致模型接收到截然不同的中间状态和信息流,最终输出结果天差地别。
  • 缺少循环与迭代: 复杂的法律咨询往往不是一步到位的。它可能是一个多轮对话:先厘清基本事实(调用事实查询MCP),再分析法律适用(调用法条MCP),最后评估风险(结合RAG案例)。平铺架构试图一步到位,把所有环节的结果一次性扔给模型,要求它完成所有推理,这超出了当前模型单次处理复杂逻辑链的稳健能力。
  • 错误传播与累积: 如果第一个被调用的Skill或MCP产生了错误或低质量的结果(例如,检索到了不相关的法条),这个错误结果会作为上下文的一部分,传递给后续的模型推理步骤。在平铺架构下,由于所有信息是同时呈现的,模型没有机会在得到错误信息后“重新思考”或“寻求更正”,错误会被固化在最终的输出中。

3.3 提示词(Prompt)工程失效

精心设计的提示词是引导LLM正确输出的关键。但在平铺架构下,我们为单个任务设计的精密提示词会失效。

  • 系统指令被淹没: 我们可能在系统提示词(System Prompt)中严格定义了Claude-for-Legal的角色:“你是一名严谨的中国律师,回答必须基于现行法律法规...” 然而,当上下文中充满了来自MCP的原始数据、Skill的中间输出时,模型对自身角色的“坚守”会被削弱。它可能更像一个“信息汇总者”,而不是一个“专业分析者”。
  • 少样本示例(Few-Shot)失效: 我们通常会在提示词中提供几个高质量的问答示例(Few-Shot Learning),来教模型如何回答特定类型的问题。但在海量的混乱上下文中,这些示例的示范作用会大打折扣。模型难以识别当前问题应该匹配哪一个示例的模式。
  • 指令位置敏感性: LLM对提示词中指令的位置有一定敏感性,通常越靠前、越清晰的指令影响力越大。在平铺模式下,用户的查询、Skill的输出、MCP的数据不断追加到上下文末尾,最重要的初始指令(系统角色、任务要求)在相对位置上的“权重”会降低。

3.4 评估与反馈环路断裂

一个健壮的Agent系统应该有自我评估和修正的能力。但在简单的平铺调用中,这一步是缺失的。

  • 无结果校验: Agent调用了MCP,拿到了数据,就直接塞进上下文。它没有机制去判断这个数据是否相关、是否准确、是否完整。例如,查询“股权质押”相关法条,MCP可能返回了10条,其中只有3条高度相关。平铺架构会把10条全部送入,而一个智能的调度器应该能过滤或标注出那3条核心条款。
  • 无置信度评估: 对于Skill产生的中间结果(比如一份自动生成的合同条款),Agent无法评估其置信度。这个条款是模板化的稳妥选择,还是一个需要重点提示用户审查的风险点?平铺架构无法体现这种差异。

总结来说,平铺架构的本质问题是 将复杂的、需要多步推理和决策的认知任务,简化成了一个单步的、信息过载的文本补全任务 。这违背了LLM当前的能力边界,尤其是在法律这种高严谨性领域,必然导致准确率崩塌。

4. 解决方案:从“平铺”到“编排”的架构演进

诊断清楚了病因,我们就可以对症下药。核心思路是将“平铺”的混乱架构,升级为“编排”(Orchestration)驱动的、有序的、可评估的智能流程。这不仅仅是优化,更是一种架构范式的转变。

4.1 引入智能调度层(Orchestrator)

这是最关键的一步。我们需要一个独立的“大脑中的大脑”,或者说是“律所主任”,来指挥Claude-for-Legal这个“合伙人律师”以及它背后的专家团队(Skill/MCP)。

这个调度层可以是一个简单的规则引擎,也可以是一个轻量级的LLM(例如使用GPT-4或Claude Haiku进行任务规划)。它的核心职责是:

  1. 意图识别与任务分解: 解析用户查询,判断其属于哪种法律问题(合同、咨询、诉讼、合规等),并将复杂问题分解为一系列原子子任务。
    • 例如,用户问:“公司想辞退一名试用期员工,怎么操作才合法?”
    • 调度器解析: 识别为“劳动法-解除劳动合同”问题。
    • 任务分解:
      • 子任务A:检索《劳动合同法》中关于试用期解除的条款(调用 法条MCP )。
      • 子任务B:检索类似案例,看司法实践中的认定标准(调用 案例MCP )。
      • 子任务C:根据A和B的结果,生成一个具体的操作步骤清单和风险提示(调用 清单生成Skill )。
      • 子任务D:将以上结果整合成一份给HR的简要指引(由 主Agent 完成)。
  2. 执行规划与依赖管理: 确定子任务的执行顺序。有些任务有依赖关系,比如必须先有法条(A),才能进行案例对比分析(B),最后才能生成清单(C)。调度器需要管理这些依赖。
  3. 工具选择与调用: 为每个子任务分配合适的Skill或MCP。例如,对于“查询上海地区2023年劳动争议案件数量”这样的具体数据查询,应该调用 统计数据库MCP ,而不是让主Agent去“思考”或使用通用的法律检索Skill。

技术实现参考(伪代码思路):

class LegalAgentOrchestrator:
    def __init__(self, llm_client, skill_registry, mcp_clients):
        self.llm = llm_client  # 用于规划的小模型
        self.skills = skill_registry
        self.mcps = mcp_clients

    def plan_and_execute(self, user_query):
        # 步骤1: 意图识别与规划
        plan_prompt = f"""
        用户问题:{user_query}
        你是一个法律任务调度专家。请将上述问题分解为一系列具体的子任务,并指定每个任务的最佳执行工具(SKILL或MCP)。
        可用的工具类型:
        - SKILL: 内部固定流程技能,如 `contract_review`, `document_generation`, `risk_checklist`。
        - MCP: 外部数据源,如 `law_retrieval`, `case_search`, `company_info`。
        输出格式为JSON列表,每个元素包含:`task_description`, `tool_type`, `tool_name`。
        """
        task_plan = self.llm.generate(plan_prompt) # 解析为结构化任务列表

        # 步骤2: 按顺序执行任务,收集结果
        context = []
        for task in task_plan:
            if task['tool_type'] == 'SKILL':
                result = self.skills[task['tool_name']].execute(user_query, context)
            elif task['tool_type'] == 'MCP':
                result = self.mcps[task['tool_name']].query(user_query, context)
            # 可以对结果进行初步清洗和评估
            cleaned_result = self._evaluate_and_clean(task, result)
            context.append({
                "task": task['task_description'],
                "result": cleaned_result
            })

        # 步骤3: 将规划过程和所有子任务结果作为高质量上下文,交给主Agent生成最终答案
        final_prompt = self._construct_final_prompt(user_query, context)
        final_answer = self.llm.generate(final_prompt) # 这里可以用更大的主模型,如Claude-3.5-Sonnet
        return final_answer

    def _evaluate_and_clean(self, task, raw_result):
        # 简单的评估逻辑:例如,检查MCP返回是否为空,是否包含明显错误关键词
        # 更复杂的可以实现一个“验证器”LLM来评分
        if not raw_result:
            return "[工具未返回有效结果]"
        # 这里可以添加更多清洗逻辑,如提取关键部分、格式化等
        return raw_result[:1000] # 限制长度,避免上下文过长

4.2 实施动态上下文管理

我们不能把所有中间结果都原封不动地堆给最终生成答案的主Agent。需要动态地、有选择地构建上下文。

  • 摘要与提炼: 对于MCP返回的大段法律条文或案例,调度器可以先用一个小模型(或让主模型快速处理)对其进行摘要,提取核心要件、裁判观点等,只将摘要放入最终上下文。这极大地节省了Token,并突出了重点。
  • 相关性过滤: 在将子任务结果加入上下文前,进行相关性打分。可以计算子任务结果与用户原始问题的语义相似度,过滤掉得分过低的内容。这能有效去除噪声。
  • 结构化组织: 不要将不同来源的信息混为一谈。在构建最终Prompt时,明确地分块、标注来源。例如:
    【法律依据】
    (来自法条MCP)《劳动合同法》第三十九条:劳动者有下列情形之一的,用人单位可以解除劳动合同:(一)在试用期间被证明不符合录用条件的;...
    
    【类似案例参考】
    (来自案例MCP)(2023)沪01民终1234号判决指出,用人单位以“不符合录用条件”解雇试用期员工,需承担充分的举证责任...
    
    【内部风险评估清单】
    (来自清单生成Skill)1. 证据固定:需有明确的录用条件书面文件... 2. 考核记录:试用期考核需客观、有记录...
    
    这种结构化的上下文,极大地降低了模型的解析难度,使其能更精准地定位和引用信息。

4.3 设计链式与树状工作流

对于复杂问题,采用链式(Sequential)或树状(Tree-of-Thought)的推理流程,取代并行平铺。

  • 链式调用: 这是最基本也最有效的改进。A任务的结果作为B任务的输入。例如: 用户提问 -> 调度器 -> 法条检索MCP -> 结果摘要 -> 案例检索MCP(用法条摘要作为查询关键词)-> 结果摘要 -> 主Agent生成最终答案 。每一步都基于上一步的精确输出,避免了信息混乱。
  • 思维树(ToT)或思维图(GoT): 对于非常复杂、存在多种可能路径的法律问题(例如,一个案件可能有多个案由可选),可以让调度器或主Agent模拟出几种不同的分析路径(分支),分别调用不同的工具链去验证,最后再评估、汇总所有路径的结果,选择最优解。这模仿了律师的“多角度思考”过程。

4.4 建立验证与回退机制

为系统增加“质检”环节。

  • 子结果验证: 在调用某个MCP后,可以设计一个简单的验证步骤。例如,调用法条MCP后,再用一个“法条一致性检查”的轻量级Skill(或Prompt)判断返回的法条是否与问题主题高度相关。如果相关性太低,可以触发重新查询或标记为低置信度信息。
  • 最终答案验证: 在生成最终答案后,可以将其中的关键主张(如引用的法条号、提到的案例名)反向抽取出来,再次调用MCP进行事实核查(Fact-Checking)。如果发现不一致,可以触发修正流程。
  • 回退策略: 当某个Skill或MCP调用失败、超时或返回空结果时,调度器应有备选方案。例如,主法律检索MCP失效时,自动回退到备用检索接口,或者提示用户“相关外部数据暂时无法获取,以下分析仅基于模型内部知识”。

4.5 优化提示词工程策略

在编排架构下,提示词可以设计得更精细、更有层次。

  • 分层提示词: 为调度器、各个Skill、验证器以及最终的主Agent分别设计专属的、高度优化的提示词。每个提示词只专注于一个简单的任务,而不是试图用一个庞大的提示词解决所有问题。
  • 上下文标记与元数据: 在传递给模型的上下文中,显式地加入元数据标记。例如, <LAW_SOURCE authority="high">...</LAW_SOURCE> <CASE_SUMMARY relevance="0.8">...</CASE_SUMMARY> 。这可以隐式地引导模型对不同来源和可信度的信息赋予不同的权重。
  • 强制格式化输出: 要求主Agent在最终输出时,必须采用“主张-依据-分析”的严格格式,并且依据部分必须明确指向上下文中的某个来源块(例如“参见【法律依据】第一部分”)。这不仅能提高答案的条理性,也使得答案更易于事后验证和审计。

通过以上五个方面的综合改进,我们可以将Claude-for-Legal从一个容易“信息过载”而胡言乱语的“实习生”,改造为一个在“律所主任”(调度器)指挥下,有条不紊地协同“专家团队”(MCP)和“内部流程”(Skill),最终产出严谨、可靠法律意见的“资深合伙人”。架构的转变,带来的是能力质变的可能。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐