Agentic GraphRAG在商业合同中的应用,附源码及源码解析


Agentic GraphRAG 旨在通过将法律信息结构化为知识图谱,提升商业合同领域的问答准确性。该方法利用 LangGraph Agent 构建和管理知识图谱,使法律文本的检索更加精准和高效。通过结合 RAG(Retrieval-Augmented Generation,检索增强生成) 技术,Agentic GraphRAG 能够更好地理解合同条款、法律条文及其相互关系,从而提供更加专业、准确的法律问答支持。


在任何企业中,法律合同都是定义各方关系、义务和责任的基础性文件。无论是合伙协议、保密协议还是供应商合同,这些文件通常包含影响决策、风险管理和合规性的关键信息。然而,阅读、理解并从合同中提取有效信息是一个复杂且耗时的过程。


在本文中,我们将探讨如何通过 Agentic GraphRAG 实现端到端的法律合同处理解决方案,以简化合同理解和管理流程。我将 GraphRAG 视为一个广义概念,涵盖所有基于知识图谱进行信息检索或推理的方法,从而实现更结构化、更具上下文意识的回答。


通过使用 Neo4j 将法律合同结构化为知识图谱,我们可以创建一个强大且易于查询和分析的信息库。在此基础上,我们将构建一个 LangGraph Agent,使用户能够针对合同内容提出具体问题,并迅速挖掘出有价值的信息。



源码链接在文末。


为什么数据结构化至关重要?  



在某些领域,基础的 RAG 方法已经能够很好地发挥作用。然而,在处理法律合同时,单纯依赖传统 RAG 方法存在诸多挑战。




如图所示,仅依赖向量索引来检索相关文本片段可能会带来风险,例如从无关合同中提取信息。这是因为法律语言高度结构化,不同合同可能存在相似的措辞,导致错误或误导性的检索结果。这种局限性凸显了采用更结构化方法(如 GraphRAG)的必要性,以确保检索结果的精准性和上下文相关性。


要实现 GraphRAG,首先需要构建知识图谱


下图展示了结合结构化与非结构化信息的法律知识图谱:



要构建法律合同的知识图谱,我们需要从文档中提取结构化信息,并将其与原始文本一起存储。大语言模型(LLM)可以帮助解析合同,识别关键要素,如合同方、日期、合同类型、关键条款等。


传统方法通常将合同视为一整块文本,而我们则将其拆解为结构化的法律组件,更准确地反映其法律含义。例如,LLM 可以识别出:

“ACME Inc. 同意从 2024 年 1 月 1 日起,每月支付 10000 美元”

这一句中包含付款义务生效日期,我们可以将这些信息存储为结构化数据,以便后续检索和分析。


在获得结构化数据后,我们将其存储到知识图谱 中,建立实体及其关系,如公司、合同、条款等。原始文本仍然可用,但结构化数据层可以帮助我们优化搜索,提高检索的精准度。


相比传统 RAG 仅根据向量相似度检索文本片段,我们可以利用合同的属性进行筛选。例如,GraphRAG 能够回答:

✅ 上个月签署了多少份合同?
✅ 我们是否有与某家公司仍然有效的协议?


这些问题需要聚合与筛选,而单纯基于向量检索的 RAG 方法是无法处理的


这种混合方法使检索更加上下文感知(Context-Aware)。例如,当用户询问合同的付款条款时,系统会限定检索范围,确保只搜索正确的合同,而不会因文本相似度误将无关条款纳入结果。这种方法克服了传统 RAG 的局限,使法律文档的分析更深层次、更可靠。


知识图谱的构建  




为了提取合同的结构化信息,我们将使用 LLM 结合 CUAD(Contract Understanding Atticus Dataset,合同理解 Atticus 数据集) 进行训练。


CUAD 数据集 是合同分析的常用基准数据集(CC BY 4.0 许可),包含 500 余份合同,是评估我们结构化信息提取流程的理想选择。


下方展示了合同的 token 计数分布,我们可以通过这些了解数据规模与处理需求。




在本数据集中,大多数合同的 token 数量少于 10000,属于较短的文档。然而,也存在一些超长合同,个别合同的 token 数量甚至高达 80000。不过,这些长合同十分罕见,整体分布呈现陡降趋势,说明长合同是例外,而非常规情况


我们使用 Gemini-2.0-Flash 进行信息提取,该模型的输入 token 上限为 100 万,因此处理这些合同完全不受限制。即使是最长的 80000 token 合同,也能完整输入并解析。由于大多数合同都较短,我们无需担心文本截断将文档拆分成小块进行处理。


结构化数据提取


大多数商业 LLM 都支持使用 Pydantic 对象来定义输出数据的结构化模式(Schema)。以下是一个 地点(Location) 的示例:



在 LLM 生成结构化数据时,Pydantic 通过定义清晰的数据模式(Schema),帮助规范化模型的输出。它不仅能指定属性类型(如 str 或 Optional[str]),还允许添加详细的描述,引导 LLM 生成符合预期格式的数据。


例如,在 Location(地点)模型中,我们可以定义关键字段,如地址(address)、城市(city)、州(state)、国家(country),明确数据格式和预期内容。对于 country 字段,我们采用两位字母的国家代码(ISO 3166-1 alpha-2 标准),如 “US”(美国)、”FR”(法国)、”JP”(日本),避免出现 United States、USA 等不一致的表达。


同样,其他数据字段也可以遵循标准格式,比如日期采用 ISO 8601 格式(YYYY-MM-DD),确保统一性和可读性。


总而言之,Pydantic 帮助提高 LLM 解析质量:

✅ 更可靠的输出:确保数据符合规范,减少格式错误。
✅ 机器可读:易于解析和存储,便于与数据库或 API 集成
✅ 降低后处理需求:通过清晰的字段描述,减少数据清洗和转换的工作量。


Pydantic 还可以定义更复杂的模式,例如 Contract(合同)模型,该模型捕捉法律协议的关键细节,确保 LLM 提取的数据符合标准化结构,从而提升法律合同数据的管理和利用效率。



这个合同模式(Contract Schema) 以结构化方式组织法律协议的关键信息,使其更容易被 LLM 解析和分析。


它包含不同类型的条款,如保密条款(Confidentiality)、终止条款(Termination),并附有简要摘要。此外,还包括:

✅ 合同方信息:列出各方名称、地点、角色
✅ 合同详情:涵盖生效日期、终止日期、总金额、适用法律(Governing Law)
✅ 嵌套模型(Nested Models):如适用法律(Governing Law)可作为嵌套对象存储,以支持更详细、复杂的数据结构。


嵌套数据结构适用于某些能处理复杂数据关系的 AI 模型,但某些模型可能难以解析过于深层的嵌套信息。因此,实际应用时需考虑模型对复杂结构的支持能力。


我们可以通过以下示例测试该方法的有效性。为了协调 LLM 的执行,我们使用 LangChain 框架进行编排,以确保合同解析的准确性和可扩展性



得到以下输出:



将合同数据导入 Neo4j:构建可查询的知识图谱


现在,我们已经将合同数据转换为结构化格式,接下来可以定义 Cypher 查询,将其导入 Neo4j

✅ 映射实体(Entities):如合同方(Parties)、合同条款(Clauses)、合同详情(Details)
✅ 建立关系(Relationships):定义合同方之间的关联、合同与条款的关联,确保数据结构合理。
✅ 关键条款(Key Clauses):如保密条款(Confidentiality)、终止条款(Termination),在知识图谱中建立索引,便于检索。


这一转换过程将原始提取数据组织成可查询的知识图谱,从而支持高效的合同信息检索与分析



这个 Cypher 查询通过创建具有摘要、合同类型、生效日期、持续时间和总金额等属性的 Contract 节点,将结构化的合同数据导入 Neo4j。如果指定了适用法律,它将合同与 Location 节点连接起来。合同相关方作为 Party 节点存储,每个方都与一个 Location 节点相连接,并在合同中分配相应的角色。查询还会处理条款,创建 Clause 节点,并将其与合同链接,同时存储条款的类型和摘要。


在处理并导入合同数据后,生成的图遵循以下图谱结构。



我们看一下单份合同。



该图表示了一个合同结构,其中合同(橙色节点)与多个条款(红色节点)、当事方(蓝色节点)和地点(紫色节点)相连接。该合同包含三个条款:续约与终止、责任与赔偿、以及保密与非披露。涉及两方:Modus Media International 和 Dragon Systems, Inc.,每一方都与各自的地点(荷兰和美国)相连接。合同受美国法律管辖。合同节点还包含额外的元数据,包括日期和其他相关细节。


使用以下信息可以访问一个包含 CUAD 法律合同的公开只读实例:



实体解析(Entity Resolution)


在法律合同中的实体解析(Entity Resolution)是一个具有挑战性的任务,因为公司、个人和地点的引用方式可能会有所不同。例如,一家公司可能在一个合同中被称为 Acme Inc.,而在另一个合同中则称为 Acme Corporation,这就需要一个过程来确定它们是否指的是同一个实体。


一种方法是使用文本嵌入(text embeddings)或字符串距离度量(如 Levenshtein 距离)来生成候选匹配。文本嵌入能够捕捉语义相似性,而字符串距离则衡量字符层面的差异。一旦识别出候选匹配,就需要进行额外的评估,通过比较元数据(如地址或税号),分析图中的共享关系,或者在关键情况下引入人工审核。


对于大规模的实体解析,开源解决方案如 Dedupe 和商业工具如 Senzing 提供了自动化的方法。选择合适的方案取决于数据质量、准确性要求以及是否能够进行人工监督。


构建完法律图谱后,我们可以继续进行 Agentic GraphRAG 实现。




Agentic GraphRAG  



Agentic 架构在复杂性、模块化和推理能力方面差异很大。这些架构的核心是一个作为中央推理引擎的 LLM,通常会配备工具、记忆和协调机制。关键的区分点在于 LLM 在决策时的自主性以及与外部系统交互的结构化方式。


其中一种最简单且有效的设计,特别适用于聊天机器人类的实现,是直接将 LLM 与工具结合的方法。在这种架构中,LLM 作为决策者,动态选择要调用的工具(如果有的话),在必要时重试操作,并按顺序执行多个工具以完成复杂的请求。



上图表示了一个简单的 LangGraph Agent 工作流。工作流从 __start__ 节点开始,接着进入 assistant(助手)节点,在这里 LLM 处理用户输入。之后,助手可以选择调用工具获取相关信息,或者直接过渡到 __end__ 节点来完成交互。如果使用了 tools(工具),助手会处理工具的响应,然后决定是调用另一个工具还是结束会话。这个结构使得 Agent 能够自主决定是否在响应之前需要外部信息。


这种方法特别适用于像 Gemini 或 GPT-4o 这样的强大商业模型,它们在推理和自我修正方面表现出色。


工具(Tools)


LLM 是强大的推理引擎,但它们的有效性往往取决于它们配备的外部工具。这些工具,无论是数据库查询、API 还是搜索功能,都扩展了 LLM 在检索事实、执行计算或与结构化数据交互方面的能力。



设计既能处理多样查询,又能精准返回有意义结果的工具,更像是一门艺术而非科学。我们真正构建的是 LLM 与底层数据之间的语义层。与其要求 LLM 理解 Neo4j 知识图谱或数据库模式的确切结构,不如定义抽象化的工具来屏蔽这些复杂性。


通过这种方法,LLM 无需知道合同信息是以图节点和关系的形式存储,还是以文档存储中的原始文本形式存储。它只需要调用合适的工具,根据用户的提问获取相关数据。


在我们的案例中,合同检索工具充当了这个语义接口。当用户询问合同条款、义务或相关方时,LLM 会调用一个结构化查询工具,将请求转化为数据库查询,检索相关信息,并以 LLM 能够理解和总结的格式呈现。这使得系统更加灵活且与模型无关,不同的 LLM 可以与合同数据交互,而无需直接了解数据的存储或结构。


没有一套适用于所有情况的工具设计标准。对某个模型有效的方法可能对另一个模型无效。有些模型能优雅地处理模糊的工具指令,而另一些则可能在复杂的参数处理上表现不佳,或者需要明确的提示。通用性与任务特定效率之间的权衡意味着工具设计需要反复迭代、测试和微调,以适应正在使用的 LLM。


对于合同分析,有效的工具应该能够检索合同并总结关键条款,而不要求用户严格地表达查询。实现这种灵活性依赖于精心设计的提示工程、强大的模式设计以及对不同 LLM 能力的适应。随着模型的演进,工具的设计策略也在不断变化,以使其更加直观和有效。


在这一部分,我们将探讨不同的工具实现方法,比较它们的灵活性、有效性和与各种LLM的兼容性。


我首选的方法是动态且确定性地构建一个 Cypher 查询,并对数据库执行该查询。该方法确保了查询生成的一致性和可预测性,同时保持实现的灵活性。通过这种方式构建查询,我们增强了语义层,使用户输入能够无缝地转化为数据库检索。这使得 LLM能够专注于检索相关信息,而不是理解底层的数据模型。


我们的工具旨在识别相关合同,因此需要为 LLM 提供基于各种属性搜索合同的选项。输入描述同样通过 Pydantic 对象提供。



在 LLM 工具中,属性根据其用途可以呈现不同的形式。有些字段是简单的字符串,例如合同类型(contract_type)和国家(country),它们存储单一的值。另一些字段,如当事方(parties),则是字符串的列表,允许多个条目(例如,多个实体参与合同)。


除了基本数据类型外,属性还可以表示复杂的对象。例如,货币值(monetary_value)使用一个 MonetaryValue 对象,其中包含结构化数据,如货币类型和操作符。尽管带有嵌套对象的属性提供了清晰且结构化的数据表示,但模型往往难以有效处理这些复杂对象,因此我们应尽量保持属性简单。


作为该项目的一部分,我们正在尝试增加一个 cypher_aggregation 属性,为 LLM 提供更大的灵活性,处理需要特定过滤或聚合的场景。



cypher_aggregation 属性允许 LLM 定义自定义的 Cypher 语句,用于执行高级聚合和分析。它通过附加由问题指定的聚合逻辑,扩展了基础查询,提供灵活的过滤和计算功能。


该功能支持诸如按合同类型计数、计算合同平均持续时间、分析合同随时间的分布,以及基于合同活动识别关键当事方等用例。通过利用此属性,LLM 可以动态生成适应特定分析需求的洞察,而无需预定义查询结构。


尽管这种灵活性非常有价值,但也需要仔细评估,因为更高的适应性会以操作复杂性增加为代价,可能导致一致性和鲁棒性降低。


在向 LLM 展示此功能时,我们必须清晰地定义函数的名称和描述。一个结构良好的描述有助于引导模型正确使用该函数,确保它理解函数的目的、预期的输入和输出。这可以减少歧义,并提高 LLM 生成有意义且可靠查询的能力。



最后,我们需要实现一个函数,处理给定的输入,构建相应的 Cypher 语句,并高效地执行它。


该函数的核心逻辑是构建 Cypher 语句。我们首先以合同为查询的基础进行匹配。



接下来,我们需要实现一个函数来处理输入参数。在这个示例中,我们主要使用属性来根据给定的标准过滤合同。


简单属性过滤

例如,contract_type 属性用于执行简单的节点属性过滤。



这段代码在使用查询参数传递值的同时,为 contract_type 加了一个 Cypher 过滤器,以防止查询注入安全问题。


由于可能的合同类型值已经在属性描述中给出,



推断属性过滤


我们正在为 LLM 构建与知识图谱交互的工具,这些工具充当结构化查询的抽象层。一个关键特性是能够在运行时使用推断属性,这类似于本体论,但它是动态计算的。



我们不必担心将值从输入映射到有效的合同类型,因为 LLM 会处理这个问题。


在这里active 充当了运行时分类,决定合同是进行中的(>= date())还是已过期的(< date())。这个逻辑通过在需要时计算属性,扩展了结构化知识图谱查询,使得LLM能够进行更灵活的推理。通过在工具中处理这样的逻辑,我们确保 LLM 与简化、直观的操作进行交互,使其专注于推理,而不是查询的构建。


邻居过滤


有时,过滤条件依赖于邻近节点,例如限制结果只包括涉及特定当事方的合同。

parties 属性是一个可选的列表,当提供时,它确保只考虑与这些实体相关联的合同。



这段代码根据合同关联的当事方进行过滤,将逻辑视为“与”(AND),意味着合同必须满足所有指定的条件才能被包括。它遍历提供的当事方列表,并构建一个查询,要求每个当事方条件都必须成立。


对于每个当事方,生成一个唯一的参数名称以避免冲突。EXISTS 句确保合同与包含指定值的当事方之间存在 PARTY_TO 关系。为了实现不区分大小写的匹配,名称被转换为小写。每个当事方条件单独添加,从而强制它们之间隐式地使用“与”(AND)逻辑。


如果需要更复杂的逻辑,例如支持“或”(OR)条件或允许不同的匹配标准,输入格式需要进行调整。此时,不再是简单的当事方名称列表,而是需要一个指定操作符的结构化输入格式。


此外,我们还可以实现一个容忍轻微拼写错误的当事方匹配方法,通过处理拼写和格式的变化,提升用户体验。


自定义操作符过滤


为了增加更多的灵活性,我们可以引入一个操作符对象作为嵌套属性,从而对过滤逻辑进行更多控制。我们不是硬编码比较逻辑,而是定义一个操作符的枚举,并动态使用它。


例如,在处理货币值时,可能需要根据合同的总金额是否大于、小于或等于指定值来过滤合同。我们不是假定固定的比较逻辑,而是定义一个枚举,表示可能的操作符:



这种方法使系统更具表现力。工具接口不仅允许 LLM 指定一个值,还可以指定如何进行比较,从而简化了处理更广泛查询的过程,同时保持 LLM 交互的简洁和声明性。

一些 LLM 在处理嵌套对象作为输入时存在困难,这使得基于结构化操作符的过滤变得更加复杂。引入“between”操作符增加了额外的复杂性,因为它需要两个单独的值,这可能导致解析和输入验证中的歧义。


最小值和最大值属性


为了简化处理,我倾向于使用最小值(min)和最大值(max)属性来处理日期,这样自然支持范围过滤,使“between”逻辑更加简单直观。



这个函数通过添加可选的下限和上限条件,根据 min_effective_date 和 max_effective_dat来过滤合同,确保仅包含在指定日期范围内的合同。


语义搜索


属性也可以用于语义搜索,在这种情况下,我们不依赖事先的向量索引,而是使用后过滤的方法进行元数据过滤。首先,应用结构化过滤条件,如日期范围、货币值或当事方,来缩小候选集。然后,在这个过滤后的子集上执行向量搜索,根据语义相似度对结果进行排名。



这段代码在提供 summary_search 时应用语义搜索,通过计算合同嵌入与查询嵌入之间的余弦相似度,按相关性对结果进行排序,并通过 0.9 的阈值筛选出低评分的匹配项。如果没有提供 summary_search,则默认按最近期的 effective_date 对合同进行排序。


动态查询


cypher_aggregation 属性是我想测试的一种实验,它赋予 LLM 一定程度的部分 text2cypher 能力,允许在初步结构化过滤之后动态生成聚合查询。与预定义每一个可能的聚合不同,这种方法让 LLM 根据需求指定计算,如计数、平均值或分组汇总,从而使查询更加灵活和富有表现力。然而,由于这将更多查询逻辑转移给 LLM,确保所有生成的查询都能正确执行变得具有挑战性,因为格式错误或不兼容的 Cypher 语句可能会导致执行失败。在设计系统时,这种灵活性与可靠性之间的权衡是一个关键考量因素。



如果没有提供 cypher_aggregation,我们将返回识别到的合同总数,并只展示五个示例合同,以避免给提示过多信息。处理过多行数据是非常重要的,因为如果 LLM 处理一个庞大的结果集时效率低下,它将变得不实用。此外,LLM 如果返回 100 个合同标题的答案,也不是一个良好的用户体验。



这个 Cypher 语句将所有匹配的合同收集到一个列表中,返回总数并展示最多五个示例合同,包含关键属性,如摘要、类型、范围、日期、货币价值、相关方及其角色,以及唯一的国家位置。


现在,我们的合同搜索工具已经构建完成,我们将其交给 LLM,这样就实现了 Agentic GraphRAG。


Agent 基准  



如果你认真打算实现 Agentic GraphRAG,你需要一个评估数据集,不仅作为基准,还作为整个项目的基础。一个精心构建的数据集有助于定义系统应处理的范围,确保初期开发与实际应用场景对接。除此之外,它还成为了评估性能的宝贵工具,让你能够衡量 LLM 如何与图谱互动、如何检索信息并进行推理。这对于提示工程优化至关重要,可以让你通过明确的反馈逐步改进查询、工具使用和响应格式,而不是依赖猜测。没有结构化的数据集,你就是在盲目前行,使得改进更加难以量化,且不一致的情况更难以发现。


该基准的代码可在 GitHub 上找到,链接在文末。


我已编制了一份包含 22 个问题的清单,我们将用来评估系统。此外,我们还将引入一个新的评估指标——answer_satisfaction,并提供自定义提示。



许多问题可能会返回大量信息。例如,查询签署于 2020 年之前的合同可能会产生数百个结果。由于 LLM 会接收到总数以及几个示例条目,我们的评估应重点关注总数,而不是 LLM 选择展示的具体示例。



提供的结果表明,所有评估的模型(Gemini 1.5 Pro、Gemini 2.0 Flash和GPT-4o)在大多数工具调用中表现相似,GPT-4o 稍微优于 Gemini 模型(0.82 vs. 0.77)。主要的显著差异出现在使用部分 text2cypher 时,特别是在执行各种聚合操作时。


需要注意的是,这只是 22 个相对简单的问题,因此我们并没有深入探讨 LLM 的推理能力。


此外,我已经看到一些项目通过利用 Python 进行聚合,显著提高了准确性,因为 LLM 通常更擅长生成和执行 Python 代码,而不是直接生成复杂的 Cypher 查询。




Web 应用程序  




我们还构建了一个简单的 React web 应用程序,由 FastAPI 托管的 LangGraph 驱动,直接将响应流式传输到前端。


你可以通过以下命令启动整个技术栈:



然后在浏览器中打开 localhost:5173





总结  



随着 LLM 推理能力的不断增强,结合合适的工具,它们可以成为强大的智能体,在像法律合同这样的复杂领域中发挥重要作用。在这篇文章中,我们仅仅触及了合同核心属性的表面,几乎没有涉及到实际协议中丰富多样的条款内容。未来有很大的发展空间,从扩展条款覆盖范围到精细化工具设计和交互策略,都能进一步提升其应用价值。


https://towardsdatascience.com/agentic-graphrag-for-commercial-contracts/


源码:

https://github.com/tomasonjo-labs/legal-tech-chat

Agent 基准源码:

https://github.com/tomasonjo-labs/legal-tech-chat/blob/main/research/benchmark/benchmark_evaluate.ipynb






(文:PyTorch研习社)

欢迎分享

发表评论