LlamaIndex Agent 双工具链:PGVector 集成指南
2025-01-19 23:42:13
LlamaIndex 中 Agent 与双工具链的构建方法(基于 PGVector)
在使用 LlamaIndex 构建问答系统时,将 Agent 与多个工具进行有效链式组合,特别是与 PGVector 向量数据库结合时,可能面临一些挑战。本篇文章将分析一种常见的场景:用户需要先基于结构化查询找到相关的向量数据,再根据找到的结果做向量语义查询,即形成“结构化查询 -> 语义搜索”的链条,最终完成一个需要组合工具才能解决的问题。这里重点关注使用 LlamaIndex 框架及 PostgreSQL 和 PGVector 向量数据库的集成。
问题
代码示例展示了一个构建智能代理的常见模式,该代理具有使用自然语言查询 SQL 数据库(structured_query
工具)和语义搜索文档向量(semantic_search
工具)的能力。当前问题是,该 Agent 无法有效地将这两个工具串联起来。比如,用户想查询 “2021 年巴黎发生的、与塞纳河洪水相关的事件的原因”,模型应首先使用 structured_query
工具找出符合“城市=巴黎”和“日期=2021”的事件 ID,然后利用这些 ID 和“塞纳河洪水”信息进行语义搜索。目前模型未能按照这一逻辑处理这类组合查询。
分析原因
- Agent 的工具调度逻辑限制 :Agent 使用的默认策略可能无法很好地理解并执行先结构化查询,再利用其结果进行语义搜索的这种链式操作。LLM 在给定指令和一系列工具时,可能会先使用不适合的工具,或是没有将中间结果恰当地用于后续步骤。
- 工具不充分 :如果工具的描述不清楚它们的使用时机,LLM 可能无法做出正确的判断。 比如,“structured_query”仅描述为 "Allows queries for synthesis on the number of incidents, their distribution etc" 并不足以指导 LLM 将该工具应用于初始查询,然后将结果反馈给 “semantic_search”。
- 缺少中间结果处理 :在初始
structured_query
获取到相关的事件ID后, Agent 当前的架构缺少一个明确机制将这些ID反馈到semantic_search
中进行搜索, 这也是一个问题。
解决方案 1:改进工具描述并使用 Function Calling
第一步,改善工具描述,强调它们的使用场景,并且更明确地要求代理链式使用这些工具。在一些环境中,Function Calling (在openai等LLM提供的工具功能)是一个非常有效的帮助 Agent 更好理解 工具调用的方法。
操作步骤 :
- 修改工具描述 :
structured_query
工具: 将描述修改为“查找与特定条件匹配的事件的ID和元数据,例如基于日期和地点等”。 明确地表达该工具用于初筛数据。semantic_search
工具: 将描述修改为“基于给定关键词和事件ID在文本数据中查找相关内容,它通常接收结构化查询的结果作为输入”。强调其以事件ID为先决条件的依赖。
- 显式使用 Function Calling : 部分支持 Function Calling 的模型或框架可以更好的处理这类场景,其原理在于可以将 tools 转换为 JSON Schema, 以提示 LLM 更有效理解和调用。
代码示例 :
query_engine_tools = [
QueryEngineTool(
query_engine=vector_query_engine,
metadata=ToolMetadata(
name= "semantic_search",
description= "Search for details of a list of incidents by its id, keywords, etc., takes output from `structured_query` as its input."
)
),
QueryEngineTool(
query_engine=sql_query_engine,
metadata=ToolMetadata(
name= "structured_query",
description= "Find IDs and metadata of the incident that matches conditions, such as by date and city. Returns the IDs in JSON string."
),
),
]
agent = OpenAIAgent.from_tools(query_engine_tools, llm=llm, verbose=True) # For LLMs supporting function call: use it.
原理分析 : 更为具体的描述使得 Agent 更明白每个工具的使用方法和目的,从而提高其根据上下文选择工具的能力。 使用Function Calling可以让 LLM 更精确的执行多步链条。
安全性建议 : 请始终确保在生产环境中进行全面测试,特别是在涉及对外部系统的调用时。 使用恰当的 API 安全和验证,避免安全隐患。
解决方案 2: 引入自定义 Agent 或中间编排逻辑
如果简单的调整描述仍无法有效解决问题,可能需要更复杂的方案:
- 自定义 Agent 类 : 自定义 Agent 可以显式控制每一步执行,即根据需要分别调用
structured_query
和semantic_search
工具,并且可以显式的存储中间结果。 - 中间编排层 :在 Agent 逻辑之上加入一个中间编排层,用来分解用户查询。首先识别结构化信息部分(例如 “2021年巴黎发生的事件”),然后传递给 SQL 查询工具;将语义搜索部分(例如,“塞纳河洪水相关” )交给 语义搜索工具。中间层负责衔接工具的调用,并返回最终结果。
操作步骤 :
- 创建自定义 Agent : 将原始的工具调用进行自定义编排,明确在不同条件下的使用顺序和中间变量的流转。
- 编排逻辑 : 将查询进行分解, 先使用 structured query tool, 获取到满足条件的 eventIds 后,将其拼接成为文本信息传入到下一个 semantic search tool 中。
代码示例(简化版):
def custom_agent(query: str):
sql_query = sql_query_engine.query(
f"select id from incidents where {提取用户query中类似‘城市=巴黎 and date=2021’的内容}; " # 关键部分,提取用户 query 中的筛选条件
)
ids = extract_ids_from_sql_result(sql_query) #假设有这个函数,能从sql结果里提取id
if ids:
semantic_result = vector_query_engine.query(f"{query}, IDs:{ids}" )
return semantic_result
else:
return "no result match"
result=custom_agent("What's the reason of the incident that occurs in Paris in 2021 and that implied Seine flood")
print(result)
原理分析 :自定义Agent能让我们完全掌控整个查询过程,同时也能进行更加复杂的业务逻辑判断。 例如: 在查询结果不满足预设条件后直接结束程序。 另外,中间层通过拆分用户查询意图来提高处理效率。通过对不同的子任务执行不同的逻辑,也可以在未来提高程序扩展性。
安全性建议 :需要对从 LLM 处获取的数据(尤其 SQL 查询结果和用户输入)进行必要的过滤,防止恶意注入或敏感数据泄露。 在引入中间层后,务必考虑其复杂性带来的性能损耗。
总结
成功地将 Agent 与双工具链在 LlamaIndex 和 PGVector 中协同工作的关键在于合理地设计工具的描述, 并采取必要的自定义逻辑以协调工具。针对性改进工具描述和在需要时添加自定义逻辑编排可以极大的提升 Agent 完成组合任务的执行能力。