教程:使用 Haystack 创建多智能体系统
最后更新:2025 年 6 月 23 日
- 级别:高级
- 完成时间:20 分钟
- 使用的组件:
Agent、DuckduckgoApiWebSearch、OpenAIChatGenerator、DocumentWriter - 先决条件:您需要一个 OpenAI API 密钥,并且需要提前设置好一个 Notion 集成
- 目标:完成本教程后,您将学会如何在 Haystack 中构建一个多智能体系统,其中每个智能体都专门用于执行特定任务。
概述
多智能体系统由多个智能体组成,它们协同工作,比单个智能体更有效地解决复杂任务。每个智能体承担特定的角色或技能,从而实现分布式推理、任务专业化以及在一个统一系统内的顺畅协调。
Haystack 中实现这一点之所以可行,是因为能够将智能体作为其他智能体的工具。这种强大的模式允许您组合模块化、专业化的智能体,并通过主智能体进行编排,该主智能体根据上下文委派任务。
在本教程中,您将构建一个简单而强大的多智能体设置,其中包含一个主智能体和两个子智能体:一个专注于研究信息,另一个专注于保存信息。
准备环境
首先,让我们安装所需的包
%%bash
pip install -q haystack-ai duckduckgo-api-haystack
输入 API 密钥
输入本教程所需的 API 密钥。在创建 Notion 集成后,可以 在此处了解如何获取您的 NOTION_API_KEY。
from getpass import getpass
import os
if not os.environ.get("OPENAI_API_KEY"):
os.environ["OPENAI_API_KEY"] = getpass("Enter your OpenAI API key:")
if not os.environ.get("NOTION_API_KEY"):
os.environ["NOTION_API_KEY"] = getpass("Enter your NOTION API key:")
为研究智能体创建工具
让我们为您的研究智能体设置工具。它的工作是收集有关某个主题的信息,在本教程中,它将只使用两个来源:整个网络和 Wikipedia。在其他场景中,它还可以连接到与文档存储关联的检索或 RAG 管道。
您将使用 DuckduckgoApiWebSearch 组件创建两个 ComponentTool:一个用于通用网络搜索,另一个通过设置 allowed_domain 来限制在 Wikipedia 中搜索。
在完成之前还有最后一步:DuckduckgoApiWebSearch 返回一个文档列表,但在这种情况下,Agent 在工具返回单个字符串时效果最好。为了处理这种情况,定义一个 doc_to_string 函数,将文档列表转换为字符串。此函数用作 outputs_to_string 处理程序,还可以在返回输出之前添加自定义元素,例如文件名或链接。
from haystack.tools import ComponentTool
from duckduckgo_api_haystack import DuckduckgoApiWebSearch
def doc_to_string(documents) -> str:
"""
Handles the tool output before conversion to ChatMessage.
"""
result_str = ""
for document in documents:
result_str += f"File Content for {document.meta['link']}\n\n {document.content}"
if len(result_str) > 150_000: # trim if the content is too large
result_str = result_str[:150_000] + "...(large file can't be fully displayed)"
return result_str
web_search = ComponentTool(
component=DuckduckgoApiWebSearch(top_k=5, backend="lite"),
name="web_search",
description="Search the web",
outputs_to_string={"source": "documents", "handler": doc_to_string},
)
wiki_search = ComponentTool(
component=DuckduckgoApiWebSearch(top_k=5, backend="lite", allowed_domain="https://en.wikipedia.org"),
name="wiki_search",
description="Search Wikipedia",
outputs_to_string={"source": "documents", "handler": doc_to_string},
)
如果您在 DuckDuckGo 的使用中遇到了速率限制,可以在网络搜索组件中使用 SerperDevWebSearch。您需要输入免费的 Serper API 密钥才能使用 SerperDevWebSearch。
# from getpass import getpass
# import os
# from haystack.components.websearch import SerperDevWebSearch
# from haystack.tools import ComponentTool
# if not os.environ.get("SERPERDEV_API_KEY"):
# os.environ["SERPERDEV_API_KEY"] = getpass("Enter your SERPER API key:")
# web_search = ComponentTool(
# component=SerperDevWebSearch(top_k=5),
# name="web_search",
# description="Search the web",
# outputs_to_string={"source": "documents", "handler": doc_to_string},
# )
# wiki_search = ComponentTool(
# component=SerperDevWebSearch(top_k=5, allowed_domains=["https://www.wikipedia.org/", "https://en.wikipedia.org"]),
# name="wiki_search",
# description="Search Wikipedia",
# outputs_to_string={"source": "documents", "handler": doc_to_string},
# )
初始化研究智能体
现在是时候让您的研究智能体活跃起来了。这个智能体将全权负责查找信息。使用 OpenAIChatGenerator 或任何其他支持函数调用的 聊天生成器。
传入您之前创建的 web_search 和 wiki_search 工具。为了保持透明,请使用内置的 print_streaming_chunk 函数启用流式传输。这将实时显示智能体的工具调用和结果,以便您可以逐步了解其操作。
from haystack.components.agents import Agent
from haystack.dataclasses import ChatMessage
from haystack.components.generators.utils import print_streaming_chunk
from haystack.components.generators.chat import OpenAIChatGenerator
research_agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"),
system_prompt="""
You are a research agent that can find information on web or specifically on wikipedia.
Use wiki_search tool if you need facts and use web_search tool for latest news on topics.
Use one tool at a time. Try different queries if you need more information.
Only use the retrieved context, do not use your own knowledge.
Summarize the all retrieved information before returning response to the user.
""",
tools=[web_search, wiki_search],
streaming_callback=print_streaming_chunk,
)
result = research_agent.run(
messages=[ChatMessage.from_user("Can you tell me about Florence Nightingale's contributions to nursery?")]
)
通过 result["last_message"].text 打印智能体的最终答案。
print("Final Answer:", result["last_message"].text)
为写作智能体创建工具
接下来,让我们为您的第二个智能体——写作智能体设置工具。它的工作是保存内容,无论是保存到 Notion 还是文档存储(在本教程中是 InMemoryDocumentStore)。
Notion 写入工具
首先创建一个 自定义组件,该组件可以在给定页面标题和内容的情况下向 Notion 工作区添加一个新页面。要使用它,您需要一个活动的 Notion 集成,并具有对父页面的访问权限,新内容将存储在该父页面中。从 URL 中获取该父页面的 page_id,并在初始化组件时将其作为 init 参数传递。
以下是一个基本的实现,供您开始
from haystack import component
from typing import Optional
from haystack.utils import Secret
import requests
@component
class NotionPageCreator:
"""
Create a page in Notion using provided title and content.
"""
def __init__(
self,
page_id: str,
notion_version: str = "2022-06-28",
api_key: Secret = Secret.from_env_var("NOTION_API_KEY"), # to use the environment variable NOTION_API_KEY
):
"""
Initialize with the target Notion database ID and API version.
"""
self.api_key = api_key
self.notion_version = notion_version
self.page_id = page_id
@component.output_types(success=bool, status_code=int, error=Optional[str])
def run(self, title: str, content: str):
"""
:param title: The title of the Notion page.
:param content: The content of the Notion page.
"""
headers = {
"Authorization": f"Bearer {self.api_key.resolve_value()}",
"Content-Type": "application/json",
"Notion-Version": self.notion_version,
}
payload = {
"parent": {"page_id": self.page_id},
"properties": {"title": [{"text": {"content": title}}]},
"children": [
{
"object": "block",
"type": "paragraph",
"paragraph": {"rich_text": [{"type": "text", "text": {"content": content}}]},
}
],
}
response = requests.post("https://api.notion.com/v1/pages", headers=headers, json=payload)
if response.status_code == 200 or response.status_code == 201:
return {"success": True, "status_code": response.status_code}
else:
return {"success": False, "status_code": response.status_code, "error": response.text}
对该组件进行快速测试,以确认其正常工作。
notion_writer = NotionPageCreator(page_id="<your_page_id>")
notion_writer.run(title="My first page", content="The content of my first page")
💡 将自定义组件转换为工具时,使用
ComponentTool,请确保其输入参数定义良好。您可以通过以下两种方式之一进行操作:
- 将
properties字典传递给ComponentTool,或者 - 在
run方法的 docstring 中使用参数注释,如下所示:
def run(self, title: str, content: str):
"""
:param title: The title of the Notion page.
:param content: The content of the Notion page.
"""
此方法也适用于设置工具的 description。
from haystack.tools import ComponentTool
notion_writer = ComponentTool(
component=NotionPageCreator(page_id="<your_page_id>"),
name="notion_writer",
description="Use this tool to write/save content to Notion.",
)
notion_writer.parameters # see how parameters are automatically generated by the ComponentTool
文档存储写入工具
现在让我们为写作智能体构建另一个工具,这个工具会将内容保存到 InMemoryDocumentStore。
要实现此目的,首先创建一个包含自定义 DocumentAdapter 组件以及 DocumentWriter 的管道。管道准备好后,将其包装在 SuperComponent 中,然后使用 ComponentTool 将其转换为工具。
💡 提示:您也可以 创建一个工具,该工具由运行管道的简单函数生成。但是,推荐的方法是将
SuperComponent与ComponentTool一起使用,特别是当您计划使用 Hayhooks 部署该工具时,因为这种方法支持更好的序列化。在 教程:创建自定义 SuperComponents 中了解更多关于SuperComponents的信息。
from haystack import Pipeline, component, Document, SuperComponent
from haystack.components.writers import DocumentWriter
from haystack.document_stores.in_memory import InMemoryDocumentStore
from typing import List
@component
class DocumentAdapter:
@component.output_types(documents=List[Document])
def run(self, content: str, title: str):
return {"documents": [Document(content=content, meta={"title": title})]}
document_store = InMemoryDocumentStore()
doc_store_writer_pipeline = Pipeline()
doc_store_writer_pipeline.add_component("adapter", DocumentAdapter())
doc_store_writer_pipeline.add_component("writer", DocumentWriter(document_store=document_store))
doc_store_writer_pipeline.connect("adapter", "writer")
doc_store_writer = ComponentTool(
component=SuperComponent(doc_store_writer_pipeline),
name="doc_store_writer",
description="Use this tool to write/save content to document store",
parameters={
"type": "object",
"properties": {
"title": {"type": "string", "description": "The title of the Document"},
"content": {"type": "string", "description": "The content of the Document"},
},
"required": ["title", "content"],
},
)
doc_store_writer.parameters
初始化写作智能体
现在让我们让写作智能体活跃起来。它的工作是使用您设置的工具来保存或写入信息。
提供 notion_writer 和 doc_writer 工具作为输入。在其他用例中,您可以包括适用于其他平台的工具,如 Google Drive,或使用 MCPTool 连接到 MCP 服务器。
使用内置的 print_streaming_chunk 函数启用流式传输,以实时查看智能体的操作。请务必编写清晰且描述性的系统提示来指导智能体的行为。
最后,设置 exit_conditions=["notion_writer", "doc_writer"],以便智能体知道在调用其中一个工具后停止。由于这个智能体的工作是执行操作,而不是回复,所以我们不希望它返回最终响应。
from haystack.components.agents import Agent
from haystack.dataclasses import ChatMessage
from haystack.components.generators.utils import print_streaming_chunk
from haystack.components.generators.chat import OpenAIChatGenerator
writer_agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"),
system_prompt="""
You are a writer agent that saves given information to different locations.
Do not change the provided content before saving.
Infer the title from the text if not provided.
When you need to save provided information to Notion, use notion_writer tool.
When you need to save provided information to document store, use doc_store_writer tool
If no location is mentioned, use notion_writer tool to save the information.
""",
tools=[doc_store_writer, notion_writer],
streaming_callback=print_streaming_chunk,
exit_conditions=["notion_writer", "doc_store_writer"],
)
让我们测试写作智能体
result = writer_agent.run(
messages=[
ChatMessage.from_user(
"""
Save this text on Notion:
Florence Nightingale is widely recognized as the founder of modern nursing, and her contributions significantly transformed the field.
Florence Nightingale's legacy endures, as she set professional standards that have shaped nursing into a respected and essential component of the healthcare system. Her influence is still felt in nursing education and practice today.
"""
)
]
)
创建多智能体系统
到目前为止,您已经构建了两个子智能体,一个用于研究,一个用于写作,以及它们各自的工具。现在是时候将所有内容整合到一个单一的多智能体系统中了。
为此,请使用 ComponentTool 包装 research_agent 和 writer_agent,然后将它们作为工具传递给您的 main_agent。此设置允许主智能体通过将任务委派给正确子智能体来协调整体工作流程,每个子智能体都知道如何处理自己的工具。
from haystack.components.agents import Agent
from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.components.generators.utils import print_streaming_chunk
research_tool = ComponentTool(
component=research_agent,
description="Use this tool to find information on web or specifically on wikipedia",
name="research_tool",
outputs_to_string={"source": "last_message"},
)
writer_tool = ComponentTool(
component=writer_agent,
description="Use this tool to write content into document store or Notion",
name="writer_tool",
outputs_to_string={"source": "last_message"},
)
main_agent = Agent(
chat_generator=OpenAIChatGenerator(model="gpt-4o-mini"),
system_prompt="""
You are an assistant that has access to several tools.
Understand the user query and use relevant tool to answer the query.
You can use `research_tool` to make research on web and wikipedia and `writer_tool` to save information into the document store or Notion.
""",
streaming_callback=print_streaming_chunk,
tools=[research_tool, writer_tool],
)
让我们测试这个多智能体系统!
from haystack.dataclasses import ChatMessage
result = main_agent.run(
messages=[
ChatMessage.from_user(
"""
Can you research the history of the Silk Road?
"""
)
]
)
result["last_message"].text
result = main_agent.run(
messages=[
ChatMessage.from_user(
"""
Summarize how RAG pipelines work and save it in Notion
"""
)
]
)
下一步
🎉 恭喜!您刚刚使用 Haystack 构建了一个多智能体系统,其中专业化的智能体协同工作进行研究和写作,每个智能体都有自己的工具和职责。您现在拥有一个灵活的基础来构建更复杂、模块化的智能体工作流程。
想继续探索吗?这里有一些很好的下一步
要随时了解最新的 Haystack 发展,您可以订阅我们的新闻通讯或加入 Haystack Discord 社区。
