Type something to search...
惊人进化!用Gemini 2.0与LangGraph构建您的多工具自主代理!

惊人进化!用Gemini 2.0与LangGraph构建您的多工具自主代理!

一个实用的教程,包含完整代码示例,用于构建和运行多工具代理

大型语言模型(LLMs)非常出色——它们可以记忆大量信息,回答常识性问题,编写代码,生成故事,甚至修正你的语法。然而,它们并非没有局限性。它们会幻觉,知识截止日期可能从几个月到几年不等,并且仅限于生成文本,无法与现实世界互动。这限制了它们的实用性,尤其是在需要实时数据、来源引用或超出文本生成功能的任务中。这正是代理和工具试图解决的主要问题:它们通过增强 LLM 的额外功能来填补这一空白。这些改进使 LLM 能够访问最新信息,与 API 互动、搜索,甚至影响物理世界,比如调整智能家居的温度。

在本教程中,我们将构建一个简单的 LLM 代理,该代理配备了四种工具,可以用来回答用户的问题。该代理将具有以下规格:

  • 能够用最新的可验证信息回答常识性问题。
  • 可以使用四种类型的工具:DuckDuckGo 搜索、获取网页内容、维基百科搜索、获取维基百科页面内容。
  • 允许 LLM 理解用户的输入、消息历史、之前的工具调用及其结果,以决定是否使用下一个工具,如果使用,则使用哪个参数。
  • 该代理允许在每个时间步骤中同时使用多个工具。

我们将使用LangGraph进行代理实现,并使用 Gemini 2.0 作为我们的 LLM。然而,您可以通过最小的代码更改切换到大多数其他 LLM 提供商。完整代码请见:https://github.com/CVxTz/document_ai_agents/blob/master/document_ai_agents/document_multi_tool_agent.py

工具

首先,让我们构建其中一个工具,例如网页搜索工具:

from duckduckgo_search import DDGS
from pydantic import BaseModel

class PageSummary(BaseModel):
    page_title: str
    page_summary: str
    page_url: str


class SearchResponse(BaseModel):
    page_summaries: list[PageSummary]


def search_duck_duck_go(search_query: str) -> SearchResponse:
    """
    在 duckduckgo 页面中搜索。
    :param search_query: 发送到 DuckDuckGo 搜索的查询。
    即使这意味着多次调用该工具,也要一次搜索一个项目。
    :return:
    """
    max_results = 10

    with DDGS() as dd:
        results_generator = dd.text(
            search_query,
            max_results=max_results,
            backend="api",
        )

    return SearchResponse(
        page_summaries=[
            PageSummary(
                page_title=x["title"], page_summary=x["body"], page_url=x["href"]
            )
            for x in results_generator
        ]
    )

我们的“工具”是一个简单的 Python 函数,使用 duckduckgo_search 库获取与搜索查询相关的搜索结果。将由 LLM 决定根据用户的消息选择哪个查询。 该函数的输出如下:

{'page_summaries': [{'page_summary': 'Stevia is a plant-based sweetener that '
                                     'is 200 to 400 times sweeter than sugar '
                                     'and has no calories or carbohydrates. '
                                     'Learn about its health benefits, side '
                                     'effects, and how to use it in cooking '
                                     'and baking.',
                     'page_title': 'Stevia: Health Benefits and Risks - WebMD',
                     'page_url': 'https://www.webmd.com/food-recipes/what-is-stevia'},
                    {'page_summary': 'Stevia is a herb that can be used as a '
                                     'zero-calorie, zero-carb sugar '
                                     'substitute. Learn about its history, '
                                     'safety, potential health benefits and '
                                     'drawbacks from Cleveland Clinic experts.',
                     'page_title': 'Stevia: What Is It and Is It Healthy? - '
                                   'Cleveland Clinic Health Essentials',
                     'page_url': 'https://health.clevelandclinic.org/what-is-stevia'},
                    {'page_summary': 'Stevia is a sugar substitute extracted '
                                     'from the leaves of Stevia rebaudiana, a '
                                     'plant native to Paraguay and Brazil. '

...

让我们再看看 get_wikipedia_page 函数:

def get_wikipedia_page(page_title: str, max_text_size: int = 16_000):
    """
    获取维基百科页面的完整内容
    :param page_title: 通过先调用工具 "search_wikipedia" 确保该页面存在。
    :param max_text_size: 默认为 16000
    :return:
    """

    page = wikipedia.page(title=page_title, auto_suggest=False)
    full_content = strip_tags(page.html())
    full_page = FullPage(
        page_title=page.title,
        page_url=page.url,
        content=full_content[:max_text_size],
    )

    return full_page

该函数获取页面的完整 HTML 并将其去除 HTML 标签后返回给调用者。输出如下:

{'content': 'Sweetener and sugar substitute\n'
            'This article is about the sweetener. For other uses, see Stevia '
            '(disambiguation).\n'
            '\n'
            'Stevia (/ˈstiːviə, ˈstɛviə/)[1][2] is a sweet sugar substitute '
            'that is about 50 to 300\xa0times sweeter than sugar.[3] It is '
            'extracted from the leaves of Stevia rebaudiana, a plant native to 
            ...
            ...
            '^ Samuel, Priscilla; Ayoob, Keith T; Magnuson, Bernadene A; '
            'Wölwer-Rieck, Ursula; Jeppesen, Per Bendix; Rogers, Peter J; '
            'Rowland, Ian; Mathews, Rebecca (1 July 2018). "Stevia Leaf to '
            'Stevia Sweetener: Exploring Its Science, Benefits, and Future '
            'Potential". The Journal of Nutrition. 148 (7): 1186S – 1205S. '
            'doi:10.1093/jn/nxy102. '
            'hdl:1983/618532e2-6caa-4fbe-bd6e-6eb0cb608981. ISSN\xa00022-3166. '
            'PMID\xa029982648.\n'
            '\n'
            '^ a b c d "Has stevia been approved by FDA to be used as a '
            'sweetener?". US Foo',
 'page_title': 'Stevia',
 'page_url': 'https://en.wikipedia.org/wiki/Stevia'}

总体而言,我们定义了四个这样的函数:

  • search_wikipedia(search_query: str)
  • get_wikipedia_page(page_title: str, max_text_size: int = 16_000):
  • search_duck_duck_go(search_query: str)
  • get_page_content(page_title: str, page_url: str)

所有这些函数都作为以下内容传递给 Gemini 客户端:

model = genai.GenerativeModel(
    "gemini-2.0-flash-exp",
    tools=[
            get_wikipedia_page,
            search_wikipedia,
            search_duck_duck_go,
            get_page_content,
        ]
   )

客户端将从函数定义中推断调用参数及其类型。它还将把函数的文档字符串传递到这个生成的模式中,因此我们需要在文档字符串中向 LLM 解释工具的工作原理,以获得最佳效果。

LLM

我们将使用 Gemini 客户端与 LLM 进行交互,特别是 Gemini 2.0。要开始使用,您需要设置一个 API 密钥,该密钥可以从 Google AI Studio 获得。使用此客户端,我们将提示 LLM 生成响应或创建函数调用 (或两者)。这些函数调用将应用于我们上面定义的工具。

当使用提示调用时,客户端可能会以常规文本或包含函数调用的内容进行响应,例如:

{'function_call': 
  {'name': 'search_wikipedia', 'args': {'search_query': 'Trey Parker'}}
}

这种类型的响应包含工具的名称及其参数。这使得 LLM 能够选择要访问的外部资源及其访问方式。

代理

以下是我们代理的完整实现(仅70行代码):

class ToolCallAgent:
    def __init__(self, tools: list[Callable], model_name="gemini-2.0-flash-exp"):
        self.model_name = model_name
        self.model = genai.GenerativeModel(
            self.model_name,
            tools=tools,
            system_instruction="You are a helpful agent that has access to different tools. Use them to answer the "
            "user's query if needed. Only use information from external sources that you can cite. "
            "You can use multiple tools before giving the final answer. "
            "If the tool response does not give an adequate response you can use the tools again with different inputs."
            "Only respond when you can cite the source from one of your tools."
            "Only answer I don't know after you have exhausted all ways to use the tools to search for that information.",
        )
        self.tools = tools
        self.tool_mapping = {tool.__name__: tool for tool in self.tools}
        self.graph = None
        self.build_agent()
    def call_llm(self, state: AgentState):
        response = self.model.generate_content(
            state.messages,
            request_options=RequestOptions(
                retry=retry.Retry(initial=10, multiplier=2, maximum=60, timeout=300)
            ),
        )
        return {
            "messages": [
                type(response.candidates[0].content).to_dict(
                    response.candidates[0].content
                )
            ]
        }
    def use_tool(self, state: AgentState):
        assert any("function_call" in part for part in state.messages[-1]["parts"])

        tool_result_parts = []

        for part in state.messages[-1]["parts"]:
            if "function_call" in part:
                name = part["function_call"]["name"]
                func = self.tool_mapping[name]
                result = func(**part["function_call"]["args"])
                tool_result_parts.append(
                    {
                        "function_response": {
                            "name": name,
                            "response": result.model_dump(mode="json"),
                        }
                    }
                )
        return {"messages": [{"role": "tool", "parts": tool_result_parts}]}
    @staticmethod
    def should_we_stop(state: AgentState) -> str:
        logger.debug(
            f"Entering should_we_stop function. Current message: {state.messages[-1]}"
        )  # Added log
        if any("function_call" in part for part in state.messages[-1]["parts"]):
            logger.debug(f"Calling tools: {state.messages[-1]['parts']}")
            return "use_tool"
        else:
            logger.debug("Ending agent invocation")
            return END
    def build_agent(self):
        builder = StateGraph(AgentState)
        builder.add_node("call_llm", self.call_llm)
        builder.add_node("use_tool", self.use_tool)

        builder.add_edge(START, "call_llm")
        builder.add_conditional_edges("call_llm", self.should_we_stop)
        builder.add_edge("use_tool", "call_llm")
        self.graph = builder.compile()
  • 首先,我们定义了带有工具集和简单提示的LLM客户端。
  • 其次,我们定义了第一个节点“call_llm”,用于通过消息历史调用LLM API。
  • 第三,我们定义了一个工具调用节点,执行LLM请求的所有函数调用。
  • 最后,我们定义代理的

代理将继续运行,直到LLM的新响应不包含任何工具调用。这在图中创建了一个循环,使代理的执行能够精炼搜索查询和工具使用,直到找到用户所询问的内容。

示例运行:

让我们对这个代理进行一次测试。我们将从一个简单的查询开始:

河豚有毒吗?如果有,请解释原因并列出一些其他有毒(非毒液性)鱼类。请不要只引用维基百科。

这是交流的方式(我为了简洁省略了大部分文本):

***用户:***南方公园(South Park)中关于时间旅行移民的那集的集数和季数是什么?那集的导演是谁?他出生于哪里和何时?给我他的维基百科页面链接。

***模型:***function_call:name: search_duck_duck_goargs:search_query: South Park time traveling immigrants episode number and season

***工具:***function_response:name: search_duck_duck_goresponse:page_summaries:

  • page_title: South Park: Season X Episode Y - Time Traveling Immigrants page_summary: In this episode, the boys encounter time traveling immigrants. It is a satirical take on immigration issues. … page_url: https://www.southparkstudios.com/episodes/…
  • ….

***模型:***The episode you are referring to is Season X, Episode Y. The director of this episode is Z. He was born in A, on B. Here is his Wikipedia page: https://en.wikipedia.org/wiki/Z

此交流涉及多轮工具调用,包括使用DuckDuckGo的网络搜索,足以让LLM获得所需的所有信息。

没有工具,模型回答如下:

您提到的《南方公园》剧集是第18季,第7集,标题为“Grounded Vindaloop”。它并不涉及时间旅行的移民,而是集中于……

这个答案是错误的。

现在,使用工具:

***用户:***南方公园中关于时间旅行移民的那集的编号和季节是什么?那一集的导演是谁?他在哪里和何时出生?给我他的维基百科页面链接。

***模型:***function_call:name: search_wikipediaargs:search_query: south park time traveling immigrants episode

***工具:***function_response:name: search_wikipediaresponse:page_summaries:

  • page_title: Goobacks page_summary: “Goobacks”是动画电视系列《南方公园》第八季的第七集,也是该系列的第118集。按制作顺序,… page_url: https://en.wikipedia.org/wiki/Goobacks
  • page_title: 时间旅行作品列表 page_summary: 时间旅行是虚构作品中的一个常见情节元素。以下列出了在其中占据重要角色的作品。有关古代时间旅行故事,请参见时间旅行概念的历史。 page_url: https://en.wikipedia.org/wiki/List_of_time_travel_works_of_fiction

模型:您提到的《南方公园》一集叫做“Goobacks。” 它是第八季的第七集。**

***模型:***function_call:name: get_wikipedia_pageargs:page_title: Goobacks

工具:****function_response:name: get_wikipedia_pageresponse:page_title: Goobackspage_url: https://en.wikipedia.org/wiki/Goobackscontent: 《南方公园》第八季的第七集。“Goobacks”是动画电视系列《南方公园》的第七集,也是该系列的第118集。按制作顺序,它是该季的第六集。它于2004年4月28日在美国的喜剧中心首播。该集由Trey Parker**执导。

模型:这一集“Goobacks”是由Trey Parker执导的。

***模型:***function_call:name: search_wikipediaargs:search_query: Trey Parker

***工具:***function_response:name: search_wikipediaresponse:page_summaries:

  • page_title: Trey Parker page_summary: Randolph Severn “Trey” Parker III(出生于1969年10月19日)是一位美国演员、动画师、编剧、制作人、导演和音乐家… page_url: https://en.wikipedia.org/wiki/Trey_Parker

***模型:*Trey Parker出生于1969年10月19日。

***模型:***function_call:name: get_wikipedia_pageargs:page_title: Trey Parker

工具:****function_response:name: get_wikipedia_pageresponse:page_title: Trey Parkerpage_url: https://en.wikipedia.org/wiki/Trey_Parkercontent: Randolph Severn “Trey” Parker III(出生于1969年10月19日)是一位美国演员、动画师、编剧、制作人、导演和音乐家。他出生于科尼费尔,科罗拉多州,美国。**

模型:****Trey Parker出生于科尼费尔,科罗拉多州,美国*,时间是1969年10月19日。*

这是他的维基百科页面链接:https://en.wikipedia.org/wiki/Trey_Parker

代理能够迭代进行工具调用(共四次调用),以回答我们复杂的问题, definitive答案并不在第一个结果中,而是需要多步推理和搜索查询。

结论

总之,我们构建了一个自主的 LLM 代理,能够搜索网络、浏览维基百科,并通过其检索到的信息进行推理,核心 LLM 为 Gemini 2.0。该代理不仅能够获取相关数据,还能根据初始结果优化搜索查询,以定位用户所请求的确切信息。

此实现为创建您自己的自主代理提供了良好的基础。通过将自定义工具定义为 Python 函数并将其集成到代理中,您可以轻松地根据特定需求进行调整。得益于 LangGraph 框架,实施的简单性使得定制变得直观。

现在,您已具备开始构建强大且高效的代理的能力,以应用于您自己的用例和应用程序。

Code: https://github.com/CVxTz/document_ai_agents/blob/master/document_ai_agents/document_multi_tool_agent.py

Related Posts

使用 ChatGPT 搜索网络功能的 10 种创意方法

使用 ChatGPT 搜索网络功能的 10 种创意方法

例如,提示和输出 你知道可以使用 ChatGPT 的“搜索网络”功能来完成许多任务,而不仅仅是基本的网络搜索吗? 对于那些不知道的人,ChatGPT 新的“搜索网络”功能提供实时信息。 截至撰写此帖时,该功能仅对使用 ChatGPT 4o 和 4o-mini 的付费会员开放。 ![](https://images.weserv.nl/?url=https://cdn-im

阅读更多
在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和技术领域保持领先地位的 10 项必学技能 📚

在人工智能和科技这样一个动态的行业中,保持领先意味着不断提升你的技能。无论你是希望深入了解人工智能模型性能、掌握数据分析,还是希望通过人工智能转变传统领域如法律,这些课程都是你成功的捷径。以下是一个精心策划的高价值课程列表,可以助力你的职业发展,并让你始终处于创新的前沿。 1. 生成性人工智能简介课程: [生成性人工智能简介](https://genai.works

阅读更多
10 个强大的 Perplexity AI 提示,让您的营销任务自动化

10 个强大的 Perplexity AI 提示,让您的营销任务自动化

在当今快速变化的数字世界中,营销人员总是在寻找更智能的方法来简化他们的工作。想象一下,有一个个人助理可以为您创建受众档案,建议营销策略,甚至为您撰写广告文案。这听起来像是一个梦想? 多亏了像 Perplexity 这样的 AI 工具,这个梦想现在成为现实。通过正确的提示,您可以将 AI 转变为您的 个人营销助理。在本文中,我将分享 10 个强大的提示,帮助您自动

阅读更多
10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

10+ 面向 UI/UX 设计师的顶级 ChatGPT 提示

人工智能技术,如机器学习、自然语言处理和数据分析,正在重新定义传统设计方法。从自动化重复任务到实现个性化用户体验,人工智能使设计师能够更加专注于战略思维和创造力。随着这一趋势的不断增长,UI/UX 设计师越来越多地采用 AI 驱动的工具来促进他们的工作。利用人工智能不仅能提供基于数据的洞察,还为满足多样化用户需求的创新设计解决方案开辟了机会。 1. 用户角色开发 目的

阅读更多
在几分钟内完成数月工作的 100 种人工智能工具

在几分钟内完成数月工作的 100 种人工智能工具

人工智能(AI)的快速发展改变了企业的运作方式,使人们能够在短短几分钟内完成曾经需要几周或几个月的任务。从内容创作到网站设计,AI工具帮助专业人士节省时间,提高生产力,专注于创造力。以下是按功能分类的100个AI工具的全面列表,以及它们在现实世界中的使用实例。 1. 研究工具 研究可能耗时,但人工智能工具使查找、分析和组织数据变得更加容易。**ChatGPT, Cop

阅读更多
你从未知道的 17 个令人惊叹的 GitHub 仓库

你从未知道的 17 个令人惊叹的 GitHub 仓库

Github 隐藏的宝石!! 立即收藏的代码库 学习编程相对简单,但掌握编写更好代码的艺术要困难得多。GitHub 是开发者的宝藏,那里“金子”是其他人分享的精心编写的代码。通过探索 GitHub,您可以发现如何编写更清晰的代码,理解高质量代码的样子,并学习成为更熟练开发者的基本步骤。 1. notwaldorf/emoji-translate *谁需

阅读更多