
精通知识图谱代理:使用 llamaindex 和 text2cypher 构建高效工作流的 5 个步骤
构建 Text2Cypher 智能体接口的蓝图
使用 ChatGPT 渲染的图智能体
检索增强生成 (RAG) 将会持续发展,并且是有充分理由的。它是一个强大的框架,将高级语言模型与有针对性的信息检索技术相结合,从而能够更快地访问相关数据并生成更准确、具有上下文感知能力的响应。虽然 RAG 应用通常侧重于非结构化数据,但我非常喜欢将结构化数据整合到其中,这是一种重要但经常被忽视的方法。我最喜欢的方法之一是利用像 Neo4j 这样的图数据库。
通常,从图中检索数据的首选方法是 Text2Cypher,其中自然语言查询会自动转换为 Cypher 语句以查询图数据库。此技术依赖于语言模型(或基于规则的系统),该模型解释用户查询,推断其潜在意图,并将其转换为有效的 Cypher 查询,从而使 RAG 应用程序能够从知识图中检索相关信息并生成准确的答案。
使用 LLM 生成 Cypher — 图片来源:https://neo4j.com/developer-blog/fine-tuned-text2cypher-2024-model/
Text2Cypher 提供了卓越的灵活性,因为它允许用户使用自然语言提出问题,而无需了解底层图模式或 Cypher 语法。然而,由于语言解释的细微差别以及对精确的模式特定细节的需求,其准确性仍然可能不足,如下面的 Text2Cypher 文章所示。
使用 Neo4j Text2Cypher (2024) 数据集的基准测试
作者:Makbule Gulcin Ozsoy, Leila Messallem, Jon Besga
medium.com](https://readmedium.com/benchmarking-using-the-neo4j-text2cypher-2024-dataset-d77be96ab65a)
基准测试最重要的结果显示在以下可视化中。
Text2Cypher 基准测试结果 — 图片来自 https://readmedium.com/benchmarking-using-the-neo4j-text2cypher-2024-dataset-d77be96ab65a
从高层次的角度来看,该基准测试比较了三组模型:
- 针对 Text2Cypher 任务进行微调的模型
- 开放的基础模型
- 封闭的基础模型
该基准测试使用两个指标评估它们在生成正确 Cypher 查询方面的性能:Google BLEU(顶部图表)和 ExactMatch(底部图表)。
Google BLEU 指标衡量生成的查询与参考查询之间的重叠程度(以 n-gram 为单位)。较高的分数通常表示与参考的对齐程度更高,但它们不一定保证查询将在数据库上下文中正确运行。
另一方面,ExactMatch 是一个基于执行的指标。它表示生成的查询与正确查询文本完全匹配的百分比,这意味着它们在执行时会产生相同的结果。这使得 ExactMatch 成为衡量正确性的更严格标准,并且与查询在现实世界设置中的实际效用更直接相关。
尽管通过微调取得了一些有希望的结果,但总体准确度水平表明 Text2Cypher 仍然是一项不断发展的技术。一些模型仍然难以在每种情况下生成完全正确的查询,这突显了需要进一步改进。
在本文中,我们将尝试使用 LlamaIndex Workflows 来实现更具智能体策略的 Text2Cypher。我们将尝试一种多步骤方法,而不是依赖于单次查询生成(这通常是大多数基准测试的运行方式),该方法允许重试或备选查询公式。通过结合这些额外的步骤和后备选项,我们旨在提高整体准确性并减少有缺陷的 Cypher 生成的实例。
代码可在 GitHub 上找到。
托管的 Web 应用程序,其中包含所有智能体,可在 https://text2cypher-llama-agent.up.railway.app/ 访问
LlamaIndex Workflows
LlamaIndex Workflows 是一种通过事件驱动系统连接不同操作来组织多步骤 AI 流程的实用方法。它们有助于将复杂的任务分解为更小、更易于管理的部分,这些部分可以以结构化的方式相互通信。工作流程中的每个步骤都会处理特定事件并产生新事件,从而创建一个可以完成文档处理、问题解答或内容生成等任务的操作链。该系统会自动处理步骤之间的协调,从而更容易构建和维护复杂的 AI 应用程序。
Naive Text2Cypher 流程
Naive Text2Cypher 架构是一种将自然语言问题转换为 Neo4j 图数据库的 Cypher 查询的简化方法。 它通过一个三阶段的工作流程运行:
- 它使用少量样本学习,并结合存储在向量数据库中的类似示例,从输入问题生成 Cypher 查询。
- 系统针对图数据库执行生成的 Cypher 查询。
- 它通过语言模型处理数据库结果,生成直接回答原始问题的自然语言响应。
该架构维护了一个简单而有效的管道,利用向量相似性搜索,例如少量样本检索和 LLM 来生成 Cypher 查询和格式化响应。
可视化的 Naive Text2Cypher 工作流程如下所示。
Naive Text2Cypher 流程
值得注意的是,大多数 Neo4j 模式生成方法都难以处理多标签节点。 这个问题不仅由于增加了复杂性而出现,还因为标签的组合爆炸,这可能会使提示不堪重负。 为了缓解这种情况,我们在模式生成过程中排除了 Actor
和 Director
标签:
schema = graph_store.get_schema_str(exclude_types=["Actor", "Director"])
该管道从 generate_cypher
步骤开始:
@step
async def generate_cypher(self, ctx: Context, ev: StartEvent) -> ExecuteCypherEvent:
question = ev.input
# Cypher query generation using an LLM
cypher_query = await generate_cypher_step(
self.llm, question, self.few_shot_retriever
)
# Streaming event information to the web UI.
ctx.write_event_to_stream(
SseEvent(
label="Cypher generation",
message=f"Generated Cypher: {cypher_query}",
)
)
## Return for the next step
return ExecuteCypherEvent(question=question, cypher=cypher_query)
generate_cypher 步骤获取一个自然语言问题,并通过使用语言模型并从向量存储中检索类似的示例,将其转换为 Cypher 查询。 该步骤还将生成的 Cypher 查询实时流回用户界面,从而提供有关查询生成过程的即时反馈。 您可以检查 整个代码和提示。
带有重试流程的 Naive Text2Cypher
这个增强版本的 Text2Cypher 带有重试功能,它在原始架构的基础上增加了自我修正机制。 当生成的 Cypher 查询无法执行时,系统不会直接失败,而是尝试通过将错误信息反馈给 CorrectCypherEvent
步骤中的语言模型来修复查询。 这使得系统更具弹性和能够处理最初的错误,类似于人类在收到错误反馈后可能会修改他们的方法。
可视化的带有重试流程的 Naive Text2Cypher 如下所示。
带有重试流程的 Naive Text2Cypher
让我们看一下 ExecuteCypherEvent
:
@step
async def execute_query(
self, ctx: Context, ev: ExecuteCypherEvent
) -> SummarizeEvent | CorrectCypherEvent:
# Get global var
retries = await ctx.get("retries")
try:
database_output = str(graph_store.structured_query(ev.cypher))
except Exception as e:
database_output = str(e)
# Retry
if retries < self.max_retries:
await ctx.set("retries", retries + 1)
return CorrectCypherEvent(
question=ev.question, cypher=ev.cypher, error=database_output
)
return SummarizeEvent(
question=ev.question, cypher=ev.cypher, context=database_output
)
execute 函数首先尝试运行查询,如果成功,则将结果传递给摘要。 但是,如果出现问题,它不会立即放弃 - 相反,它会检查它是否还有任何剩余的重试尝试,如果是,则将查询发回以进行更正,以及有关出了什么问题的相关信息。 这创建了一个更宽容的系统,可以从错误中学习,就像我们在收到反馈后可能会修改我们的方法一样。 您可以检查 整个代码和提示。
带有重试和评估流程的 Naive Text2Cypher
在带有重试流程的 naive Text2Cypher 的基础上,这个增强版本增加了一个评估阶段,用于检查查询结果是否足以回答用户的问题。如果结果被认为不足,系统会将查询发回进行更正,并提供关于如何改进它的信息。如果结果可以接受,流程将继续进行最终的总结步骤。这个额外的验证层进一步增强了管道的弹性,确保用户最终能够收到最准确和最完整的答案。
带有重试和评估流程的 Naive Text2Cypher
额外的评估步骤是这样实现的:
@step
async def evaluate_context(
self, ctx: Context, ev: EvaluateEvent
) -> SummarizeEvent | CorrectCypherEvent:
# Get global var
retries = await ctx.get("retries")
evaluation = await evaluate_database_output_step(
self.llm, ev.question, ev.cypher, ev.context
)
if retries < self.max_retries and not evaluation == "Ok":
await ctx.set("retries", retries + 1)
return CorrectCypherEvent(
question=ev.question, cypher=ev.cypher, error=evaluation
)
return SummarizeEvent(
question=ev.question, cypher=ev.cypher, context=ev.context
)
函数 evaluate_check
是一个简单的检查,用于确定查询结果是否充分地解决了用户的问题。如果评估表明结果不足,并且还有重试尝试,它将返回一个 CorrectCypherEvent
,以便可以改进查询。否则,它将继续执行 SummarizeEvent
,表明结果适合最终总结。
后来我意识到捕捉流程通过更正无效的 Cypher 语句而成功自我修复的实例是一个绝妙的主意。然后,这些示例可以用作未来 Cypher 生成的动态 few-shot 提示。这种方法不仅可以使代理自我修复,还可以持续地自我学习和改进。 存储这些 few-shot 示例的示例代码 仅为此流程实现(因为它提供了最佳的自我修复准确性)。
@step
async def summarize_answer(self, ctx: Context, ev: SummarizeEvent) -> StopEvent:
retries = await ctx.get("retries")
# If retry was successful:
if retries > 0 and check_ok(ev.evaluation):
# print(f"Learned new example: {ev.question}, {ev.cypher}")
# Store success retries to be used as fewshots!
store_fewshot_example(ev.question, ev.cypher, self.llm.model)
迭代规划器流程
最终的流程是最复杂的,而且恰好是我雄心勃勃地首先设计的。我把它保留在代码中,这样你就可以从我的探索中学习。
迭代规划器流程通过实现迭代规划系统引入了一种更复杂的方法。它没有直接生成 Cypher 查询,而是首先创建一个子查询计划,在执行之前验证每个子查询 Cypher 语句,并包含一个信息检查机制,如果初始结果不足,该机制可以修改计划。系统最多可以进行三次信息收集迭代,每次都根据之前的结果改进其方法。这创建了一个更彻底的问答系统,它可以通过将复杂查询分解为可管理的步骤并在每个阶段验证信息来处理复杂查询。
可视化的迭代规划器工作流程如下所示。
迭代规划流程
让我们检查一下查询规划器提示。我开始的时候非常雄心勃勃。我期望 LLM 产生以下响应:
class SubqueriesOutput(BaseModel):
"""Defines the output format for transforming a question into parallel-optimized retrieval steps."""
plan: List[List[str]] = Field(
description=(
"""A list of query groups where:
- Each group (inner list) contains queries that can be executed in parallel
- Groups are ordered by dependency (earlier groups must be executed before later ones)
- Each query must be a specific information retrieval request
- Split into multiple steps only if intermediate results return ≤25 values
- No reasoning or comparison tasks, only data fetching queries"""
)
)
输出表示一个结构化的计划,用于将一个复杂的问题转换为顺序和并行的查询步骤。每个步骤都由一组可以并行执行的查询组成,后面的步骤取决于前面步骤的结果。查询严格用于信息检索,避免推理任务,如果需要管理结果大小,则将其分解为更小的步骤。例如,以下计划首先并行列出两个演员的电影,然后是一个步骤,用于从第一步的结果中识别票房最高的电影。
plan = [
### 2 steps in parallel
[
"List all movies made by Tom Hanks in the 2000s.",
"List all movies made by Tom Cruise in the 2000s.",
],
### Second step
["Find the highest profiting movie among winner of step 1"],
]
这个想法无疑很酷。这是一种将复杂问题分解为更小、可操作的步骤,甚至使用并行性来优化检索的智能方法。这听起来像是一种可以真正加速事情的策略。但在实践中,期望 LLM 能够可靠地执行此操作有点雄心勃勃。并行性在理论上是有效的,但它引入了很多复杂性。依赖关系、中间结果以及在并行步骤之间保持逻辑一致性很容易使即使是高级模型也出错。顺序执行虽然不那么引人注目,但目前更可靠,并且显着降低了模型上的认知开销。
此外,LLM 经常难以遵循结构化工具输出,如列表列表,尤其是在推理步骤之间的依赖关系时。在这里,我很好奇仅提示(没有工具输出)可以在多大程度上提高模型在这些任务上的性能。
查看 代码 以了解迭代规划流程。
基准测试
为在 LlamaIndex 工作流架构中评估 text2ypher 代理创建基准数据集,感觉是向前迈出的激动人心的一步。
我们寻求替代传统的单次 Cypher 执行指标,例如 ExactMatch,这些指标通常无法捕捉迭代规划等工作流的全部潜力。在这些工作流中,采用多个步骤来完善查询和检索相关信息,使得单步执行指标不足。
这就是我们选择使用 Ragas 的 answer_relevancy 的原因——它感觉更符合我们想要衡量的。在这里,我们使用 LLM 生成答案,然后用它作为判断者,将其与 ground truth 进行比较。我们准备了一个包含大约 50 个样本的自定义数据集,这些样本经过精心设计,以避免生成压倒性的输出(意味着过大或过于详细的数据库结果)。此类输出会使 LLM 判断者难以有效地评估相关性,因此保持结果简短可以确保对单步和多步工作流进行公平而集中的比较。
以下是结果。
基准测试结果
Claude 3.5 Sonnet、Deepseek-V3 和 GPT-4o 在答案相关性方面脱颖而出,成为前三名模型,每个模型的得分都超过 0.80。NaiveText2CypherRetryCheckFlow 倾向于产生最高的总体相关性,而 IterativePlanningFlow 的排名一直较低(低至 0.163)。
尽管 OpenAI o1 模型相当准确,但它可能并非名列前茅,这要归因于多次超时(设置为 90 秒)。Deepseek-V3 尤其有前景,因为它在获得高分的同时,延迟相对较低。总的来说,这些结果强调了在实际部署场景中,不仅原始准确性,而且稳定性和速度的重要性。
查看另一张表格,可以轻松检查流程之间的提升。
基准测试结果
Sonnet 3.5 从 NaiveText2CypherFlow 中的 0.596 分稳步上升到 NaiveText2CypherRetryFlow 中的 0.616 分,然后在 NaiveText2CypherRetryCheckFlow 中大幅跃升至 0.843 分。GPT-4o 总体上表现出类似的模式,从 NaiveText2CypherFlow 中的 0.622 分略微下降到 NaiveText2CypherRetryFlow 中的 0.603 分,然后在 NaiveText2CypherRetryCheckFlow 中显着攀升至 0.837 分。这些改进表明,添加重试机制和最终验证步骤显着提高了答案相关性。
查看 benchmark code。
请注意,基准测试结果可能相差至少 5%,这意味着您可能会在不同的运行中观察到略有不同的结果和领先者。
经验与投入生产
这是一个为期两个月的项目,在这个过程中我学到了很多。其中一个亮点是在测试基准中实现了 84% 的相关性,这是一个显著的成就。然而,这是否意味着您在生产环境中也能达到 84% 的准确率?可能不会。
生产环境带来了其自身的一系列挑战——真实世界的数据通常比基准数据集更嘈杂、更多样化且结构化程度更低。我们尚未讨论过,但您将在实际应用和用户中看到的是对生产就绪步骤的需求。这意味着不仅要专注于在受控基准中实现高准确性,还要确保系统在真实世界条件下可靠、适应性强并提供一致的结果。
在这些设置中,您需要实施某种护栏来阻止无关的问题通过 Text2Cypher 管道。
这是一个用户询问“科幻”电影的例子。问题在于,数据库中将类型存储为“Sci-Fi”,导致查询没有返回任何结果。
经常被忽视的是空值的存在。在真实世界的数据中,空值很常见,必须对其进行处理,尤其是在执行排序或类似任务时。未能正确处理它们可能会导致意外的结果或错误。
在这个例子中,我们得到了一部电影,其评级为 Null
值。要解决这个问题,查询需要添加一个额外的子句 WHERE m.imdbRating IS NOT NULL
。
还有一些情况,缺失的信息不仅仅是一个数据问题,而是一个模式限制。例如,如果我们询问获得奥斯卡奖的电影,但模式不包含任何关于奖项的信息,那么查询根本无法返回所需的结果。
由于 LLM 经过训练是为了取悦用户,因此 LLM 仍然会提供一些符合模式但无效的内容。我还不清楚如何最好地处理此类示例。
我想提到的最后一件事是查询规划部分。我使用了以下计划查询来回答这个问题:
在 2000 年代,汤姆·汉克斯和汤姆·克鲁斯谁拍的电影更多,并为获胜者找到他们票房最高的电影?
计划是:
plan = [
### 2 steps in parallel
[
"List all movies made by Tom Hanks in the 2000s.",
"List all movies made by Tom Cruise in the 2000s.",
],
### Second step
["Find the highest profiting movie among winner of step 1"],
]
它看起来令人印象深刻,但现实是 Cypher 具有高度的灵活性,GPT-4o 可以在单个查询中处理这个问题。
我敢说在这种情况下并行处理是过度的。如果您正在处理真正需要它的复杂问题类型,您可以包含一个查询规划器,但请记住,许多多跳问题可以通过单个 Cypher 语句有效地处理。
这个例子突出了一个不同的问题:最终答案是模棱两可的,因为 LLM 仅获得了有限的信息。具体来说,获胜者是汤姆·克鲁斯,电影是《世界之战》。在这种情况下,推理已经在数据库中进行,因此不需要 LLM 来处理该逻辑。然而,LLM 倾向于默认以这种方式运行,这突出了为 LLM 提供完整上下文以确保准确和明确的响应的重要性。
最后,您还必须考虑如何处理返回大量结果的问题。
返回大量结果
在我们的实现中,我们对结果强制执行 100 条记录的硬性限制。虽然这有助于管理数据量,但在某些情况下它仍然可能过多,甚至可能在推理过程中误导 LLM。
此外,本博客中介绍的并非所有代理都是会话式的。您可能需要在开始时进行问题重写步骤以使其具有会话性,或者它可以是护栏步骤的一部分。如果您有一个大型图模式,无法在提示中全部传递,则必须提出一个动态获取相关图模式的系统。
投入生产时有很多事情需要注意!
总结
代理可能非常有用,但最好从简单开始,避免一开始就陷入过于复杂的实现。专注于建立一个可靠的基准,以有效地评估和比较不同的架构。关于工具输出,请考虑尽量减少它们的使用或坚持使用最简单的工具,因为许多代理难以有效地处理工具输出,通常需要手动解析。
[## GitHub - tomasonjo-labs/text2cypher_llama_agent: A collection of LlamaIndex Workflows-powered…
A collection of LlamaIndex Workflows-powered agents that convert natural language to Cypher queries designed to…
github.com](https://github.com/tomasonjo-labs/text2cypher_llama_agent)
Web UI:
[## text2cypher_agent
Text2Cypher Llama Agents A collection of LlamaIndex Workflows-powered agents that convert natural language to Cypher…
text2cypher-llama-agent.up.railway.app](https://text2cypher-llama-agent.up.railway.app/)