用GenAI工具箱构建AI代理

Google最近与LangChain合作推出的数据库专用Gen AI工具箱,现在包括了对Neo4j的支持。Neo4j的加入为工具箱用户带来了知识图谱能力,扩展了数据库管理和生成式AI应用的功能。

用GenAI工具箱构建AI代理

Google最近与LangChain合作推出的数据库专用Gen AI工具箱,现在包括了对Neo4j的支持。

这次合作始于与Kurtis Van Gent在GraphRAG聚会后的讨论,他提出了改进数据库集成的想法,以提升代理系统作为数据源和工具的功能。Neo4j的加入为工具箱用户带来了知识图谱能力,扩展了数据库管理和生成式AI应用的功能。

1、代理架构

虽然你可能已经听说过许多关于AI代理的内容,这里有一个快速回顾,说明它们与简单的检索增强生成(RAG)方法有何不同及其影响。

在传统的RAG流程中,用户的问题不是由大型语言模型(LLM)直接回答的,而是首先从可信的数据源中检索相关的信息,然后将这些信息与问题一起作为LLM提示的一部分提供给它以用于回答。因此,LLM仅用于其语言技能,而不是预训练的知识。这有助于提高答案的相关性和准确性,并部分提高了可解释性。

通过GraphRAG,知识图谱是数据源,远远超出了向量存储中的文本片段检索。图中的连接信息提供了来自相关实体、文档和结构化摘要的相关上下文。由于检索到的信息具有结构化的性质,解释并透明地显示答案的详细来源也变得更加容易。

代理架构中,LLM配备了用于信息检索和代表用户采取行动的一组工具。LLM在分析和分解问题方面扮演了更积极的角色,创建了一个计划,即使用一组工具来检索不同输入和所需结果的信息,并执行该计划。在执行过程中,代理-LLM可以选择按顺序或循环多次调用工具,甚至可以通过使用先前检索到的结果作为新工具调用的输入来深入检索更复杂的信息。在运行代理循环时,它会观察收集的数据是否足以回答用户的问题,并应用护栏和其他技术,如重新排名、判断和相关性选择。

代理架构构建块

由于对工具调用进行了结构化分析和参数提取,向量相似度搜索的需求减少了。总的来说,LLM代理的工作可以被视为数据库世界中的查询规划(和执行)过程。

提供给代理的工具有多种选项和形式,从在(Python)沙盒中执行代码到REST API调用,再到使用数据库进行检索,以及其他LLM——可能性很多。大多数工具将用于信息检索或处理,而每个代理只允许少数工具进行更新和修改操作。

如今,大多数LLM已经通过函数调用支持工具调用,这允许传递函数签名(名称、描述、参数、结果)列表,然后LLM从中选择并提供执行参数。代理框架增加了大量额外功能——如护栏、基于图的工作流、执行记忆以及上述技术。

尽管代理是一种强大的机制(如下例所示),即使在回答复杂问题和任务以及保持对话流畅方面也能表现得非常好,但也存在一些挑战,包括:

  • 工具配置和描述必须一致且足够,以便LLM做出正确的选择。
  • 工具执行管理通常留给开发人员,因此根据他们对数据基础设施的熟悉程度,资源管理往往不够理想。
  • 数据库查询和其他工具无法由数据分析师配置,因为需要提供大量的额外工作(OpenAPI规范、函数调用签名、执行的Python代码)。
  • 必须正确选择合适的工具,因此工具的名称和描述必须非常清楚地表明每种工具的作用以及适用于哪些子任务。
  • 工具必须非常具体地说明参数的种类和数据类型,以便LLM在将参数传递给工具之前进行转换和翻译。
  • 参数需要以安全的方式使用,以防止代码注入,这可能导致数据泄露或未经授权的修改。
  • 在代理设置中,向量搜索的相关性较低,因为你很少会将用户的提问作为短语传递给嵌入生成和向量相似度搜索,而是进行结构化参数提取和特定工具调用。

2、Gen AI工具箱

当涉及到创建访问数据库的工具时,通常会遇到一些挑战,这些挑战在当今的编排框架中很难解决:认证、授权、清理、连接池等。这些问题很快成为负担,减缓了开发速度,并在反复实现相同样板代码时留下了犯错的空间。

进入工具箱——开源服务器,使构建用于与数据库交互的生成式AI工具变得更容易。它通过处理连接池、认证等复杂问题,让你更轻松、更快捷、更安全地开发工具。工具箱允许你在单一位置定义工具,并通过少于三行代码与LangGraph代理集成:

from toolbox_langchain import ToolboxClient  

# 更新URL以指向你的服务器  
client = ToolboxClient("http://127.0.0.1:5000")  

# 这些工具可以传递给你的代理!  
tools = await client.aload_toolset()

工具箱确实是开源的:作为发布的一部分,Google Cloud与包括Neo4j在内的其他数据库供应商合作,支持大量开源和专有数据库:

我们还有一系列功能可以帮助您的应用程序更快地投入生产——包括内置功能,如支持工具中的最终用户身份验证和内置可观测性 通过 OpenTelemetry。

3、Neo4j Cypher 源和工具

Google 添加到 Gen AI 工具箱中的 Neo4j 集成支持定义 Cypher 执行的源和工具。表示如何连接到您的 Neo4j 实例——例如 URI、API 密钥等。工具表示代理可以执行的具体操作——例如读取或写入到源。

Neo4j 是领先的开源图数据库——它将信息管理为实体(节点)及其之间的关系,允许灵活且强大的连接信息表示。图形在许多领域中添加了独特的功能,如生物医学、供应链、制造、欺诈检测和运输物流。您可以将其视为组织数字孪生体的知识图谱(人员、流程、产品、合作伙伴等),是 LLM 语言技能很好的“事实记忆”伴侣。

获取工具箱二进制文件 后,您可以通过配置数据库连接详细信息(URL、用户名、密码)并在启动工具箱二进制文件时提供名为 tools.yaml 的文件来为多个 Neo4j 提供定义。

稍后定义的工具会绑定到一个源,因为在大多数情况下,查询特定于该数据集的数据模型。

sources:  
    my-neo4j-source:  
        kind: "neo4j"  
        uri: "neo4j+s://xxxx.databases.neo4j.io:7687"  
        user: "neo4j"  
        password: "my-password"  
        database: "neo4j"

工具 是您希望针对某个源执行的具体语句。为了与代理设置一起使用,非常重要的是以足够的细节描述工具、参数和结果,以便 LLM 可以推理其适用性。

工具中的语句可以是通用的——例如通过谓词获取单个实体、获取一级或二级邻域,甚至找到实体之间的最短路径。它们也可以是具体的,由主题专家编写,遵循数据中的更复杂路径(例如检索药物的基因或蛋白质表达目标,或为用户获取产品推荐)。工具还可以通过更新属性或添加表示业务概念的节点和关系来更新图中的数据

但工具还可以利用高级功能,如运行聚类图算法、进行空间操作,或执行向量相似性或全文搜索文本短语。

下面,您可以看到一个简单的 neo4j-cypher 工具 示例,通过演员和起始发行年份检索一组电影。注意工具和参数的详细描述,这允许 LLM 推理并选择每个规划和执行步骤的正确工具。

每个工具都有以下配置:

  • 名称
  • 关联的源
  • 使用参数、执行图模式匹配并返回结果的 Cypher 查询语句
  • 工具/查询的描述
  • 每个参数的名称、类型和描述
tools:  
 search_movies_by_actor:  
    kind: neo4j-cypher  
    source: my-neo4j-movies-instance  
    statement: |  
      MATCH (m:Movie)<-[:ACTED_IN]-(p:Person)  
      WHERE p.name = $name AND m.year > $year  
      RETURN m.title, m.year  
      ORDER BY m.year DESC  
      LIMIT 10  
    description: |  
使用此工具获取特定演员和给定最低发行年份的电影列表。输入完整的演员姓名,例如 "Tom Hanks" 和年份,例如 1993,并返回电影标题和发行年份的列表。不要使用电影标题调用此工具。不要猜测演员姓名或年份。演员姓名是一个全名,名字和姓氏之间用空格分隔。例如,如果给出 "Hanks, Tom",则演员姓名为 "Tom Hanks"。如果工具返回多个选项,请选择最近的电影。
      示例:  
      {{  
          "name": "Meg Ryan",  
          "year": 1993  
      }}  
      示例:  
      {{  
          "name": "Clint Eastwood",  
          "year": 2000  
      }}  
    parameters:  
      - name: name  
        type: string  
        description: 完整演员姓名,"firstname lastname"  
      - name: year  
        type: integer  
        description: 从 1900 年开始的四位数年份,直到当前年份

4、投资研究代理

这是一个具有使用 GraphRAG 模式结合全文和图搜索的工具的代理 LangChain 应用程序示例。

该示例代表了一个可用于查找公司、其投资者、竞争对手、合作伙伴和行业的最新新闻的投资研究代理。它由导入 Neo4j 的 Diffbot 知识图谱中的数据提供支持。

示例代码可以在 此存储库 中找到:

公司和文章:Diffbot 数据集

数据集是一个关于公司、相关行业、在公司工作或投资公司的人员以及报道这些公司的文章的图。

文章、行业和公司图

新闻文章被分块,块也存储在图中。

数据库公开可用,带有只读用户。你可以在 https://demo.neo4jlabs.com:7473/browser/ 上探索数据。

  • URI: neo4j+s://demo.neo4jlabs.com
  • 用户: companies
  • 密码: companies
  • 公司: companies

我们使用与 Vertex AI 的代理 LangChain 集成,这允许我们将与工具箱注册的工具自动传递给 LLM 进行工具调用。我们将使用混合搜索,以及父-子检索器和 GraphRAG(提取相关信息)。

在我们的配置中,我们提供了利用全文索引以及图检索查询的工具,这些查询检索以下附加信息:

  • ChunkParent 文章(聚合单篇文章的所有块)
  • 提到的 Organization(s)
  • OrganizationIndustryCategory(ies)
  • Organization 相关的 Person(s) 及其角色(例如,投资者、董事长、首席执行官)

5、工具

这些是我们为代理在工具箱中提供的具有特定查询的工具。

每个工具从代理接收参数,执行图查询并返回结构化结果:

  • industries — 行业类别名称列表
  • companies_in_industry — 给定行业中公司(ID、名称、摘要)列表
  • companies — 通过全文搜索的公司(ID、名称、摘要)列表
  • articles_in_month — 在给定日期(yyyy-mm-dd)的时间范围内一个月内的文章(ID、作者、标题、日期、情感)列表
  • article — 通过文章 ID 获取单篇文章详情(ID、作者、标题、日期、情感、站点、摘要、内容)
  • companies_in_articles — 在文章列表中提到的公司(ID、名称、摘要)
  • people_at_company — 通过公司 ID 关联的公司人员(姓名、角色)

6、工具箱中的配置

6.1 Neo4j 源配置

在 tools.yaml 文件的 sources 部分中,我们配置了公共 Neo4j 公司图数据库连接作为源:

sources:  
    companies-graph:  
        kind: "neo4j"  
        uri: "neo4j+s://demo.neo4jlabs.com"  
        user: "companies"  
        password: "companies"  
        database: "companies"

6.2 Neo4j Cypher 工具设置

现在我们可以配置要与工具箱一起测试的第一个两个工具;其他工具将以类似的方式配置和描述,如下所示:

  • industries — 行业名称列表
  • companies_in_industry — 给定行业中公司(ID、名称、摘要)列表
tools:  
  industries:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      MATCH (i:IndustryCategory)   
      RETURN i.name as industry  
    description: 行业名称列表  
  
  companies_in_industry:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      MATCH (:IndustryCategory {name:$industry})<-[:HAS_CATEGORY]-(c)   
      WHERE NOT EXISTS { (c)<-[:HAS_SUBSIDARY]-() }  
      RETURN c.id as company_id, c.name as name, c.summary as summary  
    description: 给定行业中公司(company_id、名称、摘要)列表  
    parameters:  
      - name: industry  
        type: string  
        description: 用于过滤公司的行业名称

在使用我们的 YAML 配置文件启动工具箱后,它通过 API 提供工具定义,可以通过 ToolboxClient 检索并传递给 LangChain:

# one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64  
export OS="linux/amd64"   
curl -O https://storage.googleapis.com/genai-toolbox/v0.1.0/$OS/toolbox  
chmod +x toolbox  
  
./toolbox --tools_file tools.yaml  
2025-02-13T13:50:22.052198+01:00 INFO "Initialized 1 sources."   
2025-02-13T13:50:22.052967+01:00 INFO "Initialized 0 authSources."   
2025-02-13T13:50:22.053095+01:00 INFO "Initialized 2 tools."   
2025-02-13T13:50:22.053146+01:00 INFO "初始化了 1 个工具集。"  
2025-02-13T13:50:22.053742+01:00 INFO "服务器已准备好提供服务!"

我们现在可以使用 LangChain 和 Gemini 2.0 Flash 模型,并将我们的工具定义传递给模型以进行快速测试。我们可以按照 Toolbox 文档中的 快速入门示例 进行操作。

import asyncio  
import os  
  
from langgraph.prebuilt import create_react_agent  
from langchain_google_vertexai import ChatVertexAI  
from langgraph.checkpoint.memory import MemorySaver  
  
from toolbox_langchain import ToolboxClient  
  
prompt = """  
  你是一名投资研究助理。  
  你可以使用提供的工具来搜索公司、公司中的人、行业以及 2023 年的新闻文章。  
  不要向用户请求确认。  
  用户:  
"""  
  
queries = [  
    "哪些行业涉及计算机制造?",  
    "列出 5 家计算机制造行业的公司及其描述。",  
]  
  
def main():  
    model = ChatVertexAI(model_name="gemini-1.5-pro")  
  
    client = ToolboxClient("http://127.0.0.1:5000")  
    tools = client.load_toolset()  
  
    agent = create_react_agent(model, tools, checkpointer=MemorySaver())  
  
    config = {"configurable": {"thread_id": "thread-1"}}  
    for query in queries:  
        inputs = {"messages": [("user", prompt + query)]}  
        response = agent.invoke(inputs, stream_mode="values", config=config)  
        print(response["messages"][-1].content)  
  
main()

我们得到的输出是:

与计算机制造相关的行业有:  
计算机硬件公司、电子产品制造商和计算机存储公司。  

以下是计算机硬件行业的 5 家公司及其描述:  

1.  **微软埃及**:微软在埃及的分公司  
2.  **Apigee**:总部位于美国加利福尼亚州圣何塞的软件公司,由谷歌所有  
3.  **Microsemi**:通信公司  
4.  **Intermec**:美国电子制造公司  
5.  **精英集团计算机系统**:无摘要可用

既然这已经成功运行,我们可以添加其他工具:

  • companies — 通过全文搜索获取公司列表(ID、名称、摘要)
  • articles_in_month — 在给定日期(yyyy-mm-dd)的时间范围内返回一个月内文章列表(ID、作者、标题、日期、情感)
  • article — 根据文章 ID 获取单篇文章详细信息(ID、作者、标题、日期、情感、站点、摘要、内容)
  • companies_in_articles — 根据文章 ID 列表返回文章中提到的公司(ID、名称、摘要)
  • people_at_company — 根据公司 ID 返回与公司相关联的人(姓名、角色)

请注意,为了处理数组参数,您需要提供一个“items”子组来指定子类型和描述:

工具:  

  companies:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      CALL db.index.fulltext.queryNodes('entity', $search, {limit: 100})   
      YIELD node as c, score WHERE c:Organization   
      AND NOT EXISTS { (c)<-[:HAS_SUBSIDARY]-() }  
      RETURN c.id as company_id, c.name as name, c.summary as summary  
    description: 通过全文搜索获取公司列表(company_id、name、summary)  
    parameters:  
      - name: search  
        type: string  
        description: 要搜索的公司名称的一部分  
  
  articles_in_month:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      MATCH (a:Article)  
      WHERE date($date) <= date(a.date) < date($date) + duration('P1M')  
      RETURN a.id as article_id, a.author as author, a.title as title, toString(a.date) as date, a.sentiment as sentiment  
      LIMIT 25  
    description: 在给定日期的时间范围内返回一个月内的文章列表(article_id、author、title、date、sentiment)  
    parameters:  
      - name: date  
        type: string  
        description: 开始日期,格式为 yyyy-mm-dd  
  
  article:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      MATCH (a:Article)-[:HAS_CHUNK]->(c:Chunk)  
      WHERE a.id = $article_id  
      WITH a, c ORDER BY id(c) ASC  
      WITH a, collect(c.text) as contents  
      RETURN a.id as article_id, a.author as author, a.title as title, toString(a.date) as date,  
      a.summary as summary, a.siteName as site, a.sentiment as sentiment, apoc.text.join(contents, ' ') as content  
    description: 根据文章 ID 获取单篇文章详细信息(article_id、author、title、date、sentiment、site、summary、content)  
    parameters:  
      - name: article_id  
        type: string  
        description: 要检索的文章 ID  
  
  companies_in_articles:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      MATCH (a:Article)-[:MENTIONS]->(c)  
      WHERE a.id = $article_id AND not exists { (c)<-[:HAS_SUBSIDARY]-() }  
      RETURN c.id as company_id, c.name as name, c.summary as summary  
    description: 根据文章 ID 列表返回文章中提到的公司(company_id、name、summary)  
    parameters:  
      - name: article_id  
        type: string  
        description: 要查找提及公司的文章 ID  
  
  people_at_company:  
    kind: neo4j-cypher  
    source: companies-graph  
    statement: |  
      MATCH (c:Organization)-[role]-(p:Person) WHERE c.id = $company_id  
      RETURN replace(type(role),"HAS_","") as role, p.name as person_name, c.id as company_id, c.name as company_name  
    description: 根据公司 ID 返回与公司相关联的人(person_name、role、company_id、company_name)  
    parameters:  
      - name: company_id  
        type: string  
        description: 要查找关联人员的公司 ID

6.3 使用代理—对话

我被以下对话以及大型语言模型正确使用工具的能力所打动,直到收集到回答问题所需的所有信息。

大型语言模型不仅能够一次性调用端点,还能回顾对话历史并确保相关上下文被考虑在内。

它不仅可以修复拼写错误或不正确的参数为正确的内容,还可以重新格式化日期为所需的形状等。API 函数会被多次调用,如果需要的话还会级联。例如,先检索某些上下文的 ID 列表,然后对每个 ID 逐一调用以获取详细信息。

它还能够解析代词和指示代词(例如,“这些”被解析为具体的公司 ID 或文章列表,然后用于进行多个 API 调用)。

我会对每个问题评论其复杂性以及大型语言模型代理如何解决它。

让我们看看更多针对我们的研究代理的问题,并观察它的表现:

  • 哪五个行业涉及神经植入物?
  • 列出这三个行业中三家公司的描述,并随后按加利福尼亚州过滤。
  • 这些公司里谁在工作?
  • 2023 年 1 月提到哪些公司在新闻中有积极的情感?
  • 谁在那里担任董事会成员?

答案:

哪五个行业涉及神经植入物?

首先,我们做一个简单的任务——这里需要调用行业端点,但限制结果为五个(端点不接受限制参数)。此外,我们对实际行业描述得比较模糊。

基于现有行业,以下似乎与神经植入物最相关:

  • 医疗技术公司
  • 医疗保健公司
  • 生物技术公司
  • 制药公司
  • 机器人公司

列出这三个行业中三家公司的描述,并随后按加利福尼亚州过滤。

利用先前的信息,修正参数,并进行后过滤。在这里,我们在问题中提供了关于行业的通用引用以及一个不是参数但需要后过滤的位置。

它使用对话中的先前信息来修正行业名称,并应用后过滤和限制以仅显示三家位于加利福尼亚州的公司。

好的,我有很多公司。我将过滤位于加利福尼亚州的公司,然后列出其中五家及其描述:

  • goBalto:(生物技术公司、制药公司)总部位于美国加利福尼亚州旧金山的软件公司
  • Quantenna Communications:(半导体公司、电子产品制造商)总部位于美国加利福尼亚州圣何塞的制造公司,由 ON Semiconductor 所有

这些公司里谁在工作?

在下一个问题中,我们通过“这些”引用公司,并询问在单独端点中可用的详细信息。但是代理必须多次调用 people_at_company 端点,因为它只能接受单个公司 ID。

以下是这些公司的一些员工:

  • goBalto:Sujay Jadhav(首席执行官)、Darren Hite(董事会成员)、Masashi Kiyomine(董事会成员)
  • Quantenna Communications:Sam Heidari(首席执行官)、Dmitry Akhanov(董事会成员)、Edwin B Hooper III “Ned”(董事会成员)、Jack Lazar(董事会成员)、Mark Stevens(董事会成员)
  • Finisar:Bob Bashaw(首席执行官)、Mary Jane Raymond(董事会成员)

2023 年 1 月提到哪些公司在新闻中有积极的情感?

*在这里,我们想要做两件事,转换会话从公司到文章而不重新启动会话。我们给出了一部分日期“2023 年 1 月”,必须将其转换为 API 调用的 yyyy-mm-dd 格式。它必须理解“良好情感”指的是数值评分,并且我们希望获得最高评分。带有情感输出的文章列表,模型需要读取、理解、按情感降序排序、选择排名最高的文章并输出其标题。以及这些文章中提到的公司。它不是同一个端点,因此需要获取文章 ID,并逐一检查每篇文章中的 companies_in_article 工具。

以下是 2023 年 1 月具有积极情感得分的文章及其提及的公司:

  • BackBox 宣布推出定制化产品更新(0.886): 我没有工具来识别这篇文章中提到的公司。
  • Mozilla 的 Pocket 应用刚刚进行了重大更新(0.838, 0.83): Mozilla
  • Onapsis 和 Wipro 帮助企业保护其 SAP 应用程序(0.612): Onapsis、Wipro、SAP
  • Onapsis 宣布与 Wipro 合作以消除数字化转型道路上的安全障碍(0.804, 0.735): Onapsis、Wipro
  • 休斯敦地铁和 Lone Star 学院启动了新的试点服务供 Lone Star College-Houston North Fallbrook 校区使用(0.816): Lone Star College
  • ……

谁在那里担任董事会成员?

  • 在这里,我们回溯到之前提到的公司,并且对 API 调用选择的角色要求描述得比较模糊。代理需要多次调用 people_at_company 端点,使用之前会话历史中检索到的公司 ID,然后汇总并筛选信息以生成响应。

我未能为所有公司检索到董事会成员的信息。以下是我找到的信息:

  • Wipro: Azim Premji (董事会成员)
    Rishad Premji (董事会成员)
  • SAP: Hasso Plattner (董事会成员)
    Jennifer Morgan (董事会成员)
  • Google: Larry Page (董事会成员)
    Sergey Brin (董事会成员)

6.4 部署

Toolbox 服务器和 LangGraph 应用程序的部署超出了本文的范围。请参阅 GenAI Toolbox 文档Cloud Run 上的 LangChain

7、结束语

我们展示了如何使用 Google Gen AI Toolbox 构建集成数据库工具的自主应用程序。虽然我们专注于 Neo4j 中的知识图谱 GraphRAG 使用案例,但你可以在同一自主应用程序中结合不同的数据源和类型的数据库。

由于 Toolbox 与不同的代理框架集成,你可以定义工具一次并在多个用例中重用它们。作为开源项目,为 Toolbox 做出贡献非常简单。我们希望你将利用 反馈和讨论机制 添加自己的数据源并提供反馈和改进建议。

你可以了解更多关于知识图谱的内容 《什么是知识图谱?》。并且一定要查看 GraphRAG 宣言:为 GenAI 添加知识


原文链接:Building AI Agents With the Google Gen AI Toolbox and Neo4j Knowledge Graphs

汇智网翻译整理,转载请标明出处