首页
/ ACP 多智能体协作实战:用 CrewAI 与 Smolagents 构建“生成—校验”研究摘要工作流(ai-engineering-hub)

ACP 多智能体协作实战:用 CrewAI 与 Smolagents 构建“生成—校验”研究摘要工作流(ai-engineering-hub)

2026-09-05 20:10:52作者:昌雅子Ethen

本文基于 acp-code/README.md 的完整实操流程展开,演示如何通过 Agent Communication Protocol(ACP)让两个由不同框架(CrewAI 与 Smolagents)构建的 Agent 跨框架协作:第一个 Agent 为指定主题生成研究摘要初稿,第二个 Agent 借助网页搜索工具对初稿做事实核查与信息增强。读完本文,你将掌握 ACP 服务端/客户端的完整搭建方法、两个 Agent 服务的端口与模型配置细节,以及源码层面 Server/Client/@server.agent() 装饰器的调用链原理。

项目定位:跨框架的 Agent 互操作

ACP(Agent Communication Protocol)的核心价值在于“协议层解耦”:不同框架、不同语言实现的 Agent 只要遵循同一套消息与调用约定,就可以像调用远程服务一样互相协作。acp-code 模块用最小化的三文件结构展示了这一点:

文件 角色 运行端口
crew_acp_server.py CrewAI 构建的“研究起草”Agent 服务 8000
smolagents_acp_server.py Smolagents 构建的“事实核查/增强”Agent 服务 8001
acp_client.py ACP 客户端,串联两个服务触发工作流

工作流为典型的“生成—校验”(draft & verify)两阶段模式:research_drafter 先生成草稿,research_verifier 基于草稿在线检索最新信息并输出修订版。

环境准备:Ollama 本地模型

按照 acp-code/README.md 的说明,项目默认使用 Ollama 在本地运行 Qwen2.5 14B 模型,避免依赖外部 API。

1. 安装 Ollama 并拉取模型

# Setting up Ollama on linux
curl -fsSL https://ollama.com/install.sh | sh

# Pull the Qwen2.5 model
ollama pull qwen2.5:14b

Ollama 默认监听 http://localhost:11434,这也是后面两个 Agent 服务的 LLM 端点地址(见 crew_acp_server.py 中的 base_urlsmolagents_acp_server.py 中的 api_base)。

2. 用 uv 初始化项目并安装依赖

要求 Python 3.10 及以上。先安装 uv

# MacOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

然后创建项目、虚拟环境并安装依赖:

# Create a new directory for our project
uv init acp-project
cd acp-project

# Create virtual environment and activate it
uv venv
source .venv/bin/activate  # MacOS/Linux

.venv\Scripts\activate     # Windows

# Install dependencies
uv add acp-sdk crewai smolagents duckduckgo-search ollama

依赖的职责对应关系如下:

  • acp-sdk:ACP 协议的 Python SDK,提供 ServerClientMessageMessagePart 等核心类型;
  • crewai:第一个 Agent 的底层框架(角色/任务/Crew 抽象);
  • smolagents:第二个 Agent 的底层框架(CodeAgent + 工具调用);
  • duckduckgo-search:为核查 Agent 提供 DuckDuckGoSearchTool 联网搜索能力;
  • ollama:本地模型推理服务端。

可选:切换其他 LLM 提供商

README 同时说明可以改用 OpenAI 或 Anthropic。创建 .env 文件并写入 API Key:

OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key

注意:示例代码中 LLM 是硬编码指向 Ollama 的(model="ollama_chat/qwen2.5:14b"),若要切换提供商,需相应修改 crew_acp_server.py 中的 LLM(...)smolagents_acp_server.py 中的 LiteLLMModel(...) 参数。

服务端实现一:CrewAI 的 research_drafter

crew_acp_server.py 共 35 行,完整展示了“把框架 Agent 包装为 ACP 服务”的模式。

1. LLM 配置(指向本地 Ollama)

llm = LLM(
    model="ollama_chat/qwen2.5:14b",   # CrewAI 中通过 LiteLLM 路由到 Ollama
    base_url="http://localhost:11434", # Ollama 默认端口
    max_tokens=8192                     # 单次生成上限 8K tokens
)

model 字段使用 ollama_chat/ 前缀,说明 CrewAI 底层经由 LiteLLM 统一接口调用 Ollama;base_url 必须与本机 Ollama 的监听地址一致。

2. 用装饰器注册 Agent

server = Server()

@server.agent()
async def research_drafter(input: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
    """Agent that creates a general research summary on a given topic."""

    agent = Agent(
        role="Research summarizer",
        goal="Draft an informative and structured research summary based on the topic",
        backstory="You are a researcher who summarizes complex topics for general readers.",
        llm=llm
    )

    task = Task(
        description=f"Write a brief, clear summary on: {input[0].parts[0].content}",
        expected_output="A concise paragraph summarizing the topic",
        agent=agent
    )

    crew = Crew(agents=[agent], tasks=[task])
    task_output = await crew.kickoff_async()
    yield Message(parts=[MessagePart(content=str(task_output))])

几个关键设计点:

  • 入口取法:ACP 客户端传入的 inputlist[Message],主题文本位于 input[0].parts[0].content——这是 ACP 消息体的统一访问方式;
  • 异步生成器签名:函数签名为 AsyncGenerator[RunYield, RunYieldResume],意味着 ACP Agent 可以流式 yield 中间结果;本例只 yield 一次最终 Message,属于最简单的“一次性返回”用法;
  • CrewAI 侧:单 Agent 单 Task 的轻量 Crew,通过 crew.kickoff_async() 异步执行(而非阻塞的 kickoff()),以匹配 ACP 的 async 事件循环;
  • 输出封装:结果统一包装为 Message(parts=[MessagePart(content=...)]),屏蔽了 CrewAI TaskOutput 与 ACP 消息模型的差异。

3. 启动服务

if __name__ == "__main__":
    server.run(port=8000)

服务固定监听 8000 端口,客户端即通过该地址找到 research_drafter

服务端实现二:Smolagents 的 research_verifier

smolagents_acp_server.py 与上一个服务结构同构,但底层框架与能力完全不同——它带联网工具,负责“事实核查 + 信息增强”。

1. 模型配置(LiteLLMModel)

model = LiteLLMModel(
    model_id="ollama_chat/qwen2.5:14b",
    api_base="http://localhost:11434",
    # api_key="your-api-key",
    num_ctx=8192
)

同样经由 LiteLLM 路由到本地 Ollama。num_ctx=8192 设定了上下文窗口大小,与 CrewAI 服务中的 max_tokens=8192 在数量级上保持一致。注意 Smolagents 的对应参数名是 model_id/api_base,而 CrewAI 是 model/base_url——这正是“两个框架、同一模型后端”的具体体现。

2. 带工具的 CodeAgent

@server.agent()
async def research_verifier(input: list[Message]) -> AsyncGenerator[RunYield, RunYieldResume]:
    """Agent that fact-checks and enhances a research summary using web search."""

    agent = CodeAgent(tools=[DuckDuckGoSearchTool()], model=model)

    prompt = input[0].parts[0].content
    response = agent.run(prompt)

    yield Message(parts=[MessagePart(content=str(response))])
  • CodeAgent 是 Smolagents 的代码执行型 Agent,tools=[DuckDuckGoSearchTool()] 赋予其 DuckDuckGo 网页搜索能力——这是它能“用最新信息更新摘要”的前提;
  • 与 CrewAI 服务不同,这里 agent.run(prompt) 是同步调用(在 async 函数内直接阻塞等待),对短任务可接受,但可推断若任务耗时较长,更稳妥的做法是在事件循环外执行或使用异步工具调用;
  • 服务监听 8001 端口(server.run(port=8001)),与起草服务错开,避免端口冲突。

客户端编排:acp_client.py 串联两个服务

acp_client.py 展示了 ACP 客户端如何以“远程服务”的姿态编排跨框架工作流,全部 23 行:

import asyncio
from acp_sdk.client import Client

async def run_workflow() -> None:
    async with Client(base_url="http://localhost:8000") as drafter, \
        Client(base_url="http://localhost:8001") as verifier:
        topic = "Impact of climate change on agriculture in 2025."

        response1 = await drafter.run_sync(
            agent="research_drafter",
            input=topic
        )
        draft = response1.output[0].parts[0].content
        print(f"\nDraft Summary:\n{draft}")

        response2 = await verifier.run_sync(
            agent="research_verifier",
            input=f"Enhance the following research summary using the latest information available online providing a more accurate and updated version:\n{draft}"
        )
        final_summary = response2.output[0].parts[0].content
        print(f"\nVerified & Enriched Summary:\n{final_summary}")

if __name__ == "__main__":
    asyncio.run(run_workflow())

从源码结构看,客户端编排有四个要点:

  1. 两个 Client 上下文管理器:分别绑定 8000/8001 端口的服务。客户端完全不感知对端是 CrewAI 还是 Smolagents——它只看到“某个 base_url 上叫 research_drafter/research_verifier 的 Agent”,这正是 ACP 互操作的价值所在;
  2. run_sync(agent=..., input=...):按 Agent 名称路由请求。字符串 topic 会被 SDK 封装为 Message,与服务端的 input[0].parts[0].content 取法对应;
  3. 响应取法对称response.output[0].parts[0].content 与服务端 yield Message(parts=[MessagePart(content=...)]) 的封装一一镜像;
  4. 串联式数据流:第一阶段输出 draft 被拼接进第二阶段的 prompt(要求“用在线最新信息增强该摘要”),形成“草稿 → 事实核查增强”的接力。客户端最终打印两段输出:先是 Draft Summary,再是 Verified & Enriched Summary,与 README 描述的 Output 部分(“first agent 的通用摘要 + second agent 的核查修订版”)完全一致。

运行步骤与预期输出

acp-code/README.md 的 Usage 说明,在两个独立终端分别启动两个 ACP 服务:

# Terminal 1
uv run crew_acp_server.py

# Terminal 2
uv run smolagents_acp_server.py

随后运行 ACP 客户端触发工作流:

uv run acp_client.py

预期输出分两段(见 acp_client.pyprint 语句):

  • Draft Summaryresearch_drafter(CrewAI)对“2025 年气候变化对农业的影响”这一主题生成的通用研究摘要;
  • Verified & Enriched Summaryresearch_verifier(Smolagents)基于 DuckDuckGo 搜索最新信息后输出的更准确、更及时的修订版摘要。

小结:这个示例说明了什么

acp-code 用一个 87 行源码(三个文件)的规模,验证了 ACP 的几个工程要点:

  1. 框架无关的服务封装:无论底层是 CrewAI 的 Crew/Task/Agent 抽象,还是 Smolagents 的 CodeAgent + tools,对外都收敛为“Server + @server.agent() 异步生成器 + Message 消息体”这一统一契约;
  2. 端口即服务边界:8000/8001 两个本地端口把两个 Agent 变成可独立启动、独立部署的远程服务,客户端只做 base_url + agent 名 寻址;
  3. 消息模型双向对称Message(parts=[MessagePart(content=...)]) 的封装与 output[0].parts[0].content 的读取是同一数据结构的写读两端,理解这一点对扩展多 Agent 工作流(链式、并行、带条件分支)至关重要;
  4. 本地推理 + 联网工具的组合:两个服务共用同一个 Ollama Qwen2.5 14B 后端,能力差异来自工具层(DuckDuckGoSearchTool)而非模型层,展示了“同一模型、不同 Agent 角色”的低成本协作方式。

若要在此基础上扩展,自然的演进方向是:为核查 Agent 增加更多工具(如网页抓取)、将客户端从线性串联扩展为并行起草多主题、或在 .env 基础上把 LLM(...)/LiteLLMModel(...) 改为从环境变量读取,以便在 Ollama、OpenAI、Anthropic 之间切换。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384