DeepSeek R1 初次发布时展现了出色的推理能力。在这篇博客文章中,我们将详细介绍我们使用 DeepSeek R1 构建检索增强生成 (RAG) 系统的经验,该系统专为法律文件量身定制。

我们选择法律文件是因为法律专业人士经常面临一项艰巨的任务:在大量的案例、法规和非正式法律评论库中查找信息。即使是最有条理的研究也可能陷入检索正确文档的困境,更不用说准确地总结它们了。这对于 RAG 来说是一个极好的应用领域(另请参阅 Harvey AI)!

我们基于大量的法律文档数据集构建了 RAG 系统,使用了 DeepSeek R1 和 Qwen2 嵌入。我们开源了整个构建 RAG 的流程(由 vLLM 和 SkyPilot 提供支持),并分享了一些来之不易的经验——哪些方法有效,哪些无效。

想看看实际效果 🔥?此处尝试我们的托管演示。

我们已在此处开源了构建此 RAG 的流程。来探索我们如何结合 vLLM + SkyPilot 实现快速、弹性且可扩展的性能。

RAG System Website

检索增强生成对于准确性和可追溯性至关重要的领域(如法律)至关重要。通过将专门的检索器与强大的 LLM 生成器相结合,我们可以兼得两者的优势:

  1. 高相关性的文档检索
  2. 富含推理的文本生成
  3. 通过直接引用减少幻觉

为了构建这个 RAG,我们使用了

  • pile-of-law/pile-of-law 数据集的一个子集(专注于法律建议),
  • ChromaDB 作为向量数据库用于存储和查询嵌入,
  • Qwen2 嵌入用于检索,以及
  • DeepSeek R1 用于最终答案生成。

整个设置在 vLLM 上运行,并通过 SkyPilot 无缝部署到任何云端,以获得最佳的 GPU 可用性和成本。请在此处查找我们的开源流程。

来之不易的经验:建议与禁忌

1. 不要使用 DeepSeek R1 进行检索

尽管 DeepSeek R1 具有出色的推理能力,但它擅长生成嵌入——至少目前是这样。

我们发现了一些示例,展示了 DeepSeek R1 生成的嵌入与专门的嵌入模型 Alibaba-NLP/gte-Qwen2-7B-instruct(当前 MTEB 排行榜 上最好的嵌入模型)相比有多糟糕。

我们使用这两个模型为数据集生成嵌入,并构建了两个向量数据库。然后,我们对这两个模型使用相同的查询,并在各自模型生成的向量数据库中找到相似度最高的 5 个嵌入。

查询: 我想终止租约。我的房东不允许。

Qwen 的结果DeepSeek-R1 的结果
租赁代理说我必须再住一个月因为需要提前 30 天通知?汽车事故我负全责,现在保险公司将我的案件转至“诉讼部门”
搬进新公寓,但我的一个室友不能签租约,因为她与前男友还有几个月的另一份租约。房东告诉租户我们必须支付烟囱清理费,并且必须使用她选择的清理服务。
房东要求签署新租约前女友偷了我的车
亚利桑那州房东要求我们腾空房屋以便看房。五分之一的室友没付房租
[密苏里州] 我俩的名字都在租约上 - 如果我想把男朋友赶出去,最好的办法是什么?我出了车祸,然后遭到了一定程度的袭击。

查询: 我收到了小额索赔法庭关于有争议债务的传票。我该如何准备?

Qwen 的结果DeepSeek-R1 的结果
[加利福尼亚州] 关于小额索赔的问题我可以起诉那些通过不停打电话来违法雇佣电话推销员的公司吗?
[科罗拉多州] 刚收到一张医院账单的传票,我的保险已经支付了... 我有证明,但我还需要请律师来解决这个问题吗?前女友的女儿因商店盗窃被捕,我们该怎么办?
我因一笔拖欠的 3,500 美元贷款而被起诉夜很深了,你正要结束轮班。你走到停车场,那里大部分是空的,除了你看到两个人正在闯入你的车。你让他们停下,但他们不理你。你有一把隐藏的手枪。你能做什么?
室友的猫吃了些混合零食,不得不去看兽医。账单超过 1500 美元,她威胁要告我上小额索赔法庭让我支付。没有证据证明那些零食是我的。我应该担心吗?密苏里州持有大麻指控,没有物证
我最近收到了一份关于一笔我认为我正在支付的债务的民事传票。“放狗追人”合法吗?

为什么会有这种差异?

在上面的表格中,DeepSeek R1 的检索结果明显更差。为什么?

我们认为根本问题在于 DeepSeek-R1 的训练方式。DeepSeek-R1 主要被设计为一个推理引擎,专注于顺序思考和逻辑连接。这意味着 DeepSeek-R1 不会将文档映射到语义空间中。

相比之下,Qwen2 模型变体(gte-Qwen2-7B-instruct专门针对语义相似性任务进行训练,创建了一个高维空间,在该空间中,概念上相似的文档会紧密地聚集在一起,无论具体措辞如何。

这种训练过程的差异意味着 Qwen 擅长捕捉查询背后的意图,而 DeepSeek-R1 有时会沿着导致主题相关但实际无关结果的推理路径前进。

除非 DeepSeek-R1 针对嵌入进行了微调,否则不应将其用作 RAG 的检索嵌入模型。

2. 务必使用 R1 进行生成:推理能力令人印象深刻

虽然 R1 在嵌入方面表现不佳,但我们发现其生成能力非常出色。通过利用 R1 的思维链方法,我们看到

  • 更强的连贯性:模型综合了多份文档中的见解,清晰地引用了相关段落。
  • 减少幻觉:R1 在内部“大声思考”,通过您提供的数据验证每个结论。

让我们看几个例子

示例 1:终止租约

问题: 我想终止租约。我的房东不允许。

查看 DeepSeek R1 的思考过程

好的,我想终止租约,但我的房东不允许。我不确定我有多少选择。让我尝试根据提供的文件来弄清楚这一点。

首先,我记得读到过关于提前终止的罚款。在文档 1 中,有人问他们是否可以在 18 个月的租约生效 15 个月后退出。答案是可以,但前提是房东同意或找到新租户。因此,如果我想提前终止租约,房东可能会让我承担责任,除非他们同意或能快速重新出租。这似乎是一条可能的途径,但我不确定我的房东是否愿意协商。

然后,看文档 2,有一种情况是有人因为结婚而想终止租约。答案表明租约可能允许支付费用终止,或者他们可能必须等到租约结束。所以,我或许可以查看我的租约是否有允许终止的条款,比如转租或转让条款。如果没有,我可能不得不支付罚款或等到租约期满。

文档 3 讨论了因服务犬而终止租约的情况。选项包括支付罚款、转租、寻找另一只服务犬或等到租约期满。这不直接适用于我,但它表明终止租约通常会带来一些后果,除非有特定原因。

在文档 4 中,房东正在提高租金,用户质疑通知期太短。答案提到,技术上应该有 30 天的通知期,但房东可能提供一种解决方案,即同意在下个周期前支付旧租金。如果我的房东拒绝让我终止租约,也许提出支付增加后的租金可能是一个选项,但我不知道这在财务上对我是否可行。

文档 5 提到房东因为一只猫而拒绝续签租约。答案说法律没有强制续签的义务,所以房东可以选择不续签。这让我觉得我的房东可能也有类似的酌情权,但我不知道这是否是他们拒绝的原因。

文档 6 是关于包含新条款的租约续签。答案说房东必须履行当前租约直到期满。因此,如果我的房东拒绝让我终止租约,也许他们必须等到租约期满,除非有特定条款允许他们不续签。

文档 7 和 8 是关于租赁办公室故意刁难续租。它们提到房东可能因为差评或其他原因而不愿续租,但法律上,房东可以选择不续签。这表明房东有一定的权力,但他们可能没有法律义务允许我终止租约,除非有特定原因。

文档 9 谈到搬出需要提前 30 天通知的要求。即使我在入住时没有签署任何文件,房东可能仍然要求通知。因此,如果我想在不通知的情况下搬出,我可能会面临罚款或不得不支付完整租期的租金。

最后,文档 10 是关于一份租约,其中一个室友要离开,租户必须承担租金。答案建议转租以帮助分担费用。也许如果我要离开,我可以尝试转租我那部分的租约,但这取决于租约条款。

综合以上所有信息,我认为我需要查看我的租约,看看是否有关于提前终止的条款。如果没有明确规定,我可能不得不支付罚款,除非房东同意让我退出。如果我有一个很好的理由,比如搬家,我可以尝试与房东协商。如果可能的话,转租或许有帮助,但我需要查看租约是否允许。如果找不到其他安排,我也应该考虑等到租约结束。

查看 DeepSeek R1 的回答

根据对所提供文件的分析,以下是当房东拒绝允许时终止租约的结构化方法:

1. 审查您的租约协议

  • 提前终止条款:检查您的租约中是否有任何关于提前终止的条款。如果存在此类条款,如果您希望终止租约,您可能在法律上有义务支付罚款或提供通知 [1]。
  • 转租或转让:确定您的租约是否允许转租或将租约转让给他人。这有助于您避免罚款或法律诉讼 [2]。

2. 理解房东的酌情权

  • 酌情续签:房东可能拥有拒绝续签租约的酌情权,尤其是在您的租约中没有允许提前终止的特定条款的情况下。在这种情况下,您可能被要求履行租约直至期满 [6][8]。

3. 考虑财务后果

  • 罚款:如果您未经房东同意终止租约,请准备好应对潜在的财务罚款。这可能包括额外的租金支付或损害押金 [1][9]。

4. 探讨协商

  • 协商条款:如果您有充分的理由终止租约,例如搬家,您可以与房东协商,以减少罚款或允许提前终止 [2]。

5. 将转租作为解决方案

  • 转租您的空间:如果可能,考虑将您那部分的租约转租给他人,以帮助支付您的财务义务 [10]。

6. 规划未来

  • 等到租约结束:如果终止租约不可行,计划住到租约期满。这可以避免任何罚款和法律问题 [6][8]。

结论:

在采取任何行动之前,彻底审查您的租约条款。如果租约不允许提前终止,考虑转租或等到租约期满。如果您能与房东协商,那可能是最好的办法。请务必咨询法律专业人士,获取针对您具体情况的建议。

示例 2:小额索赔法庭

问题: 我收到了小额索赔法庭关于有争议债务的传票。我该如何准备?

查看 DeepSeek R1 的思考过程

好的,我刚刚收到了关于一笔有争议债务的小额索赔法庭传票。我不太确定从何处开始,但我需要弄清楚如何为此做准备。从搜索结果中,我看到有几个人经历过类似的情况,所以也许我可以从他们的经验中学习。

首先,我应该弄清楚债务的性质。在文档 5 中,有人收到了关于一笔他们以为自己正在支付的债务的传票。他们不明白为什么会再次被传唤。因此,我需要核实这笔债务是否有效。也许我应该检查我的记录或要求债权人提供债务证明。根据文档 10,否认申诉并要求证明是一个好主意。这样,我就可以质疑债务的可信度。

接下来,我应该准备我的案件。在文档 1 中,有人处理类似问题,建议是确保案件可以在 10 分钟或更短时间内陈述完毕。这意味着我需要简洁明了,并整理好所有证据。我应该收集任何能证明我不欠这笔债务的文件,例如付款收据、债权人表明和解的信函,或者像文档 2 中提到的保险文件。

我还需要考虑证人。在文档 4 中,一个室友威胁要因为兽医账单起诉当事人,建议是除非被起诉,否则忽略威胁。但如果我被起诉了,我应该准备好能支持我的证人。也许是在我尝试支付时和我在一起的朋友,或者像文档 7 中提到的能证明房产状况的前租户。

理解法庭程序至关重要。根据文档 7,在赢得小额索赔案件后,房东上诉意味着需要再次出庭。当事人不得不再次经历相同的过程。所以,我应该知道即使我赢了,也可能存在上诉程序。如果需要,我应该准备再次陈述我的案件。

我还需要考虑传票和诉状。在文档 5 中,当事人必须对诉状提交答辩。如果我不这样做,我可能会面临缺席判决。所以,我必须按时回应并清楚陈述我的辩护。

提交答辩是下一步。文档 10 提到,即使有提议,我也应该始终否认并要求证明。这给了我日后谈判更好和解方案的筹码。我应该在我的答辩书中写下我所有的论点和证据。

考虑我的财务状况也很重要。在文档 6 中,有人担心因无力偿还而被监禁,但其中澄清说不存在债务人监狱。不过,我仍然应该沟通我的财务状况。如果我无法支付,我需要表明我已经尽了合理努力,并提供一个还款计划,就像文档 5 中那样。

如果我不确定法律问题,也许我应该咨询律师。在文档 3 中,当事人正在搬家并有法庭日期。咨询债务和解律师可能会有帮助,特别是如果我还有其他债务或情况复杂。

最后,我应该为法庭听证会做准备。准时、着装得体、并准备好所有文件至关重要。在文档 7 中,当事人因准备充分并带有证据而获胜,所以我应该效仿那个例子。

所以,总而言之,我需要:核实债务、准备证据、了解法庭程序、正确回应传票、可能咨询律师,并为听证会做好组织工作。引用相关文件将有助于支持我的决定。

查看 DeepSeek R1 的回答

根据对所提供的法律文件的分析,以下是为小额索赔法庭关于有争议债务的传票做准备的结构化和有条理的计划:

  1. 核实债务
  • 检查债权人的证明:首先审查债权人的任何文件,以确认债务的有效性和详细信息。使用这些信息与您自己的记录或之前的任何通信进行交叉比对。
  • 要求提供文件:联系债权人,要求提供正式的债务证明,例如法庭判决文件或和解协议。根据文档 10 的建议,这一步对于质疑债务的可信度至关重要。
  1. 准备您的案件
  • 收集证据:收集所有相关证据,例如付款收据、债权人表明和解的信函,或文档 2 和文档 8 中提到的保险文件。
  • 组织和提交证据:确保所有证据都已组织好并准备在法庭上提交,遵循文档 1 中的建议。如果需要,考虑包含证人陈述,例如在尝试支付时在场的朋友,或前租户关于房产状况的证词。
  1. 理解法庭程序
  • 准备应对多次听证:如果您赢得了案件,请注意可能存在上诉程序,如文档 7 中详细介绍的。如果需要,准备再次陈述您的案件。
  • 熟悉法庭程序:了解小额索赔法庭的基本知识,包括听证会的结构和法官的角色。
  1. 回应传票和诉状
  • 提交答辩:确保您按时回应传票,以避免缺席判决。根据文档 10 的建议,在您的答辩书中清楚陈述您的辩护理由并要求提供债务证明。
  • 否认申诉:始终否认申诉,以利用谈判机会,如文档 10 中所述。
  1. 考虑财务状况
  • 沟通财务状况:如果您无法支付债务,请说明您的财务状况并提供还款计划,如文档 5 中所述。表明您已尽了合理努力解决债务。
  1. 如有必要,咨询律师
  • 寻求法律建议:如果情况复杂或您不确定自己的法律地位,请咨询债务和解律师,特别是如果涉及其他债务或复杂因素,如文档 3 中建议的。
  1. 准备法庭听证会
  • 准时并做好准备:提前到达,着装得体,并带上所有必要文件。参考文档 7 的例子,充分准备的案件带来了成功的结果。
  1. 考虑其他步骤
  • 通知室友或证人:如果债务与共有房产有关,考虑通知任何可以提供支持的证人或室友,如文档 4 中提到的。

通过遵循这些步骤,您可以自信地面对小额索赔法庭听证会,确保您的案件得到有效陈述,您的权利受到保护。

从这些例子中,我们观察到 DeepSeek R1 出色的推理能力。其思考过程清楚地展示了结论是如何从源法律文件中推导出来的。

  • R1 首先构建了一个法律问题的连贯模型,这在其详细的思考过程中得到了体现:首先,我记得读到过关于提前终止的罚款... 文档 1 提到... 这种推理优先的方法使得模型能够在检索之前系统地连接多个来源的概念。
  • 在处理诸如终止租约或小额索赔法庭问题等复杂场景时,我们观察到 R1 明确地通过其对每份文档的理解(综合以上所有信息...)进行推理,没有产生幻觉
  • 最后,生成器以精确的引用解释其推理过程,将结论与来源联系起来。这在问题、推理和答案之间建立了清晰的联系,确保了严谨性和可访问性。

我们使用不同的法律查询尝试了该模型,模型始终展示出不仅能从源文档中提取信息,还能从中学习并进行推理的能力。

要点:对于问答和摘要,R1 是循序渐进法律逻辑的宝库。将其用于生成阶段,您几乎不会后悔。

3. 工程仍然很重要

提示词设计

高水平的推理能力并不能消除精心设计的提示词的需求。我们发现在提示词中包含明确的指导方针对于以下方面至关重要:

  • 在生成的答案中鼓励文档引用
  • 采用“引用或声明不知道”的方法防止幻觉
  • 以用户友好的方式组织最终答案.

在我们的整个实验过程中,我们构建了以下提示词:

You are a helpful AI assistant analyzing legal documents and related content. When responding, please follow these guidelines:
- In the search results provided, each document is formatted as [Document X begin]...[Document X end], where X represents the numerical index of each document.
- Cite your documents using [citation:X] format where X is the document number, placing citations immediately after the relevant information.
- Include citations throughout your response, not just at the end.
- If information comes from multiple documents, use multiple citations like [citation:1][citation:2].
- Not all search results may be relevant - evaluate and use only pertinent information.
- Structure longer responses into clear paragraphs or sections for readability.
- If you cannot find the answer in the provided documents, say so - do not make up information.
- Some documents may be informal discussions or reddit posts - adjust your interpretation accordingly.
- Put citation as much as possible in your response. 

First, explain your thinking process between <think> tags.
Then provide your final answer after the thinking process.

文档切块

此外,我们发现有效的文档切块对于准确的文档检索非常重要。对文档进行切块有助于使每个嵌入更简洁地代表一个特定主题,并减少生成每个嵌入所需的处理 token 数量。

我们使用了句子感知分割(通过 NLTK)来对文档进行切块。我们还让每个块的开头和结尾包含与相邻块重叠的内容。这有助于模型在不丢失大局的情况下更好地解释部分引用。文档切块的代码:

def chunk_document(document, chunk_size=2048, overlap=512):
    """Split document into overlapping chunks using sentence-aware splitting."""
    text = document['text']
    chunks = []
    
    # Split into sentences first
    sentences = nltk.sent_tokenize(text)

    current_chunk = []
    current_length = 0

    for sentence in sentences:
        sentence_len = len(sentence)

        # If adding this sentence would exceed chunk size, save current chunk
        if current_length + sentence_len > chunk_size and current_chunk:
            chunk_text = ' '.join(current_chunk)
            chunks.append({
                'id': document['id'],
                'name': document['name'],
                'content': document['text'],
                'chunk_start': len(' '.join(current_chunk[:-(2 if overlap > 0 else 0)])) if overlap > 0 else 0,
                # Additional metadata fields...
            })

            # Keep last few sentences for overlap
            overlap_text = ' '.join(current_chunk[-2:])  # Keep last 2 sentences
            current_chunk = [overlap_text] if overlap > 0 else []
            current_length = len(overlap_text) if overlap > 0 else 0

        current_chunk.append(sentence)
        current_length += sentence_len + 1  # +1 for space

要点

  1. 使用 NLTK 进行句子感知分词而非基于字符的切块
  2. 通过块之间重叠的句子保留文档上下文

4. vLLM 高效且快速

由于法律文档包含大量数据,为 RAG 生成嵌入可能需要很长时间。

最初,我们使用了默认的 HuggingFace 库 sentence_transformer。我们首先使用典型的 Nvidia L4 GPU 运行,但遇到了我们“最喜欢”的错误:CUDA 内存不足。在 Nvidia A100 上尝试后,我们发现 sentence_transformer 需要 57GB 显存来加载完整的 Alibaba-NLP/gte-Qwen2-7B-instruct 模型。

sentence_transformer_oom

我们切换到 vLLM,这是一个用于 LLM 的高吞吐量、内存高效的推理和服务引擎。

使用 vLLM,我们可以使用标准的 Nvidia L4 GPU 运行模型,vLLM 需要大约 24G GPU 显存。L4 也比 A100 便宜得多:在 GCP 上,Nvidia L4 每小时花费超过 0.7 美元,而 Nvidia A100 至少每小时 2.9 美元。

在配备 80GB 显存的 Nvidia A100 上比较 vllm 和 sentence transformer 时,我们发现使用 vLLM 为 Qwen2 模型生成嵌入的速度比 sentence transformer 快高达 5.5 倍

对于包含 10,000 份法律文档和 15,000 个块的语料库,处理时间如下:

  • 标准 sentence transformers:约 5.5 小时
  • vLLM 实现:约 1 小时

vllm Comparison

以下是我们使用 vLLM 为 Alibaba-NLP/gte-Qwen2-7B-instruct 模型生成嵌入的 SkyPilot YAML 文件:

使用 vLLM 生成嵌入的 SkyPilot YAML

name: compute-legal-embeddings

resources:
    # this needs ~ 24G DRAM GPU for vllm, 57G with SentenceTransformer
    accelerators: {L4:1, A100:1} 

envs:
    START_IDX: ${START_IDX}
    END_IDX: ${END_IDX}

file_mounts:
    /output:
        name: my-bucket-for-embedding-output
        mode: MOUNT

setup: |
    pip install torch==2.5.1 vllm==0.6.6.post
    ...
    
envs: 
    MODEL_NAME: "Alibaba-NLP/gte-Qwen2-7B-instruct"
    
run: |
    python -m vllm.entrypoints.openai.api_server \
    --host 0.0.0.0 \
    --model $MODEL_NAME \
    --max-model-len 3072 \
    --task embed &

    python scripts/compute_embeddings.py \
    --start-idx $START_IDX \
    --end-idx $END_IDX \
    --chunk-size 2048 \
    --chunk-overlap 512 \
    --vllm-endpoint http://localhost:8000 

5. 不要让嵌入生成耗时 100+ 小时 — 使用 SkyPilot 进行并行化

使用单个 L4 GPU 为法律文档生成嵌入耗时过长——高达 100 小时。我们可以利用 Spot 实例,这可以显著减少嵌入生成的时间和成本。

名称原方案100 个 Spot 工作节点
时间100 小时1 小时(快 100 倍)
成本$70.128.2 美元(节省 2.4 倍)

因此,我们使用支持以下功能的 SkyPilot 托管作业(Managed Jobs)扩展了嵌入生成:

  1. 大规模并行作业:我们将数据分成多个分区,并在多个云上启动了多个嵌入生成作业。
  2. 使用带自动恢复的 Spot 实例:如果 Spot 实例或节点出现故障,SkyPilot 会自动重试,从中断的地方继续。
  3. 统一存储:所有嵌入数据直接流式传输到云存储桶中(采用 Apache Parquet 格式)。不再丢失工作或处理部分输出。

下图显示了并行运行 10 多个 Spot 作业进行嵌入生成,并且 SkyPilot 跨不同区域寻找最佳的 GPU 可用性:

Dashboard

结果:我们不再需要等待数天来生成嵌入。我们的作业可以动态扩展,让我们即使处理大型数据集也能快速从原型阶段进入生产阶段。

结论

为法律文档构建一个 DeepSeek R1 RAG 教会了我们几个关键的经验:

  1. 利用专门的嵌入模型(如 Qwen2)实现强大的检索。
  2. 在生成阶段利用 R1 的推理能力处理复杂的法律查询。
  3. 提示词工程仍然是控制引用和组织内容的关键。
  4. 使用 vLLM 加速推理,显著提高效率和速度。
  5. 使用 SkyPilot 在云端和您自己的基础设施上扩展 AI,缩短计算时间并控制预算。

想了解更多?

注意:此 RAG 系统应被视为法律研究和分析的强大工具,而不是合格法律专业人士的替代品。其输出不应被视为法律建议。