多智能体框架的教程大多默认你接云端大模型 API——好用,但每一轮 agent 循环都在向外发数据、产生账单。如果你处理的数据不适合出本地,或者只想低成本把多智能体协作的机制跑通,CrewAI + Ollama 是社区验证充分的组合:CrewAI 负责角色分工与任务编排,Ollama 在本机提供 OpenAI 兼容接口的模型服务,CrewAI 用 ollama/ 前缀直连,全程零 API 账单、数据不出机器。本篇从装环境到跑通 sequential 与 hierarchical 两种协作形态,再到本地记忆与工具,给你一条可复现的完整路径。
先理解:本地多智能体的取舍
| 维度 | 本地 Ollama 模型 | 云端 API |
|---|---|---|
| 数据边界 | 不出机器,敏感场景友好 | 提示与工具结果都发往供应商 |
| 成本 | 电费;无按 token 计费 | 按 token 计费,多智能体轮次多、放大明显 |
| 指令遵循 | 小模型对角色/格式指令遵循明显偏弱,需收紧提示 | 前沿模型可靠得多 |
| 速度与并发 | 受本机算力限制,多 agent 串行时明显偏慢 | 弹性并发 |
结论很实际:本地组合适合练机制、跑原型、处理不出本地的数据;对输出质量要求高的生产任务,让关键角色走云端、辅助角色走本地,是常见的混合形态。CrewAI 两种都支持,切换只改模型配置那一行。
版本信息以官方发布为准。本文写作时参照 CrewAI 1.15.x(Python 3.10-3.13)与 Ollama v0.34.x,两者迭代都很快;类名、参数、模型名随版本变动,装出来的版本若与文中不同,以你本地 crewai --version 与 Ollama 官方文档为准。另外 CrewAI 默认发送匿名使用遥测,本教程 Step 2 会关掉它——本地优先就贯彻到底。
Step 1:环境准备
# 1) Python 侧(推荐用 uv 管理项目与依赖)
uv init crew-local && cd crew-local
uv add crewai crewai-tools
# 2) 模型侧:安装 Ollama 后拉取两个模型
ollama pull qwen3:14b # 主力推理模型(按显存换 7b/32b)
ollama pull nomic-embed-text # 本地记忆用的嵌入模型
# 3) 验证 Ollama 服务在跑
ollama list
curl http://localhost:11434 # 返回 Ollama is running 即正常
模型选择的量力原则:显存够就上 14B 级,多智能体的角色扮演与工具调用对模型能力有实打实的要求;资源紧张用 7B 级起步,但要把任务拆得更细、提示写得更死(原因见 Step 5 的 warn)。嵌入模型 nomic-embed-text 很小,随便装。
ollama run qwen3:14b 聊两句,确认回答速度可接受。模型层慢,CrewAI 层怎么调都白搭。Step 2:关掉遥测,锁定本地边界
# 在运行 crew 的 shell 里设置(或写进 .env / 项目启动脚本)
export CREWAI_DISABLE_TELEMETRY=true
# 验证:设置后运行一次 crew,确认无联网上传的遥测请求
# (本地防火墙日志里不应出现 CrewAI 相关外联)
这一步的意义不只是隐私洁癖:既然选择本地化的动机是数据边界,那么框架自身的遥测通道就是同一条边界上的口子。把开关写进项目启动脚本而不是靠记忆手工设置,边界才稳定。
Step 3:定义角色 Agent
from crewai import Agent, LLM, Task, Crew, Process
# 绑定本地 Ollama:ollama/ 前缀直连本机 11434 的
# OpenAI 兼容端点,无需 API Key
local_llm = LLM(
model="ollama/qwen3:14b",
base_url="http://localhost:11434",
temperature=0.2,
)
researcher = Agent(
role="市场调研员",
goal="围绕给定主题收集并列出关键事实,不编造数据",
backstory="十年行业研究经验,坚持只写有来源的事实,"
"查不到就明确说查不到。",
llm=local_llm,
allow_delegation=False, # 执行者不委派,职责单一
)
writer = Agent(
role="技术写手",
goal="把调研事实整理成 300 字以内的中文小结",
backstory="资深编辑,文风克制,绝不添加调研未提供的信息。",
llm=local_llm,
)
role/goal/backstory 不是装饰——它们会拼进提示词塑造模型行为,本地小模型对此尤其敏感。写法上两个要点:负面约束写具体(「不编造」「查不到就说」比「认真负责」有用得多);每个 Agent 职责单一,别让一个角色既调研又审校,小模型撑不住复合角色。
Step 4:sequential 流程跑通双 Agent 协作
task_research = Task(
description="调研「2026 年 AI 智能体在制造业的落地场景」,"
"列出至少 3 个有事实支撑的场景。",
expected_output="带要点的中文事实清单,每条一句话",
agent=researcher,
)
task_write = Task(
description="基于上一任务的调研结果写一段 300 字以内小结。",
expected_output="300 字以内中文小结,不含调研之外的信息",
agent=writer,
context=[task_research], # 接收上游产出作为上下文
)
crew = Crew(
agents=[researcher, writer],
tasks=[task_research, task_write],
process=Process.sequential, # 顺序执行
)
result = crew.kickoff()
print(result)
预期效果:researcher 先跑,输出事实清单;writer 拿着清单(context 显式指定依赖)产出小结。expected_output 是质量闸门——它告诉模型「什么算做完了」,对输出格式不稳的本地模型尤其重要,每个任务都要写清楚。初次运行要等两轮模型推理,14B 模型在消费级显卡上单轮十几秒到几十秒属正常,别当成卡死。
verbose=True(Crew 或 Agent 级)能看到每轮的思考与工具调用轨迹。排查「产出跑偏」时,先看轨迹判断是哪一环跑偏,再定向收紧那一环的提示。Step 5:升级 hierarchical,让 manager 动态派活
crew = Crew(
agents=[researcher, writer],
tasks=[task_write], # hierarchical 下任务目标交给经理拆解
process=Process.hierarchical,
manager_llm=local_llm, # 经理也用本地模型;配置形态随版本而定
)
result = crew.kickoff()
hierarchical 模式引入一个 manager 角色:不再按固定顺序执行,而是由经理把任务目标拆解、动态分配给 worker、汇总结果。它比 sequential 灵活,代价是推理调用更多、行为更难预测——经理本身也是一轮模型调用,拆解质量取决于模型能力。
本地小模型当经理容易翻车。社区经验一致:小模型在 hierarchical 模式下常出现重复派活、提前收工、任务描述失真。如果 7B 级模型做经理不稳,两个选择:把经理换成你本地最大的模型;或经理走云端、worker 留本地——CrewAI 支持按角色配不同模型,混合形态就是为此准备的。
Step 6:开本地记忆,让 Crew 越用越顺
crew = Crew(
agents=[researcher, writer],
tasks=[task_research, task_write],
process=Process.sequential,
memory=True, # 打开 CrewAI 内置记忆
embedder={
"provider": "ollama",
"config": {"model": "nomic-embed-text",
"url": "http://localhost:11434"},
},
)
CrewAI 的记忆分短期(会话内上下文)、长期(跨次运行的实体与结论)、实体记忆几层,默认需要嵌入模型做向量化——配置指向 Ollama 后,记忆的存储与检索都在本机完成。记忆的价值在重复性任务上最明显:同一 Crew 第二次跑相似主题时,能带上上次的结论与偏好,减少重复劳动。跑完一轮后到项目目录看生成的存储文件,确认记忆确实落盘在本地。
Step 7:加工具与运行护栏
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class PriceRangeInput(BaseModel):
product: str = Field(description="产品名称")
class LocalPriceTool(BaseTool):
name: str = "local_price_query"
description: str = "查询本地价格库中某产品的近期价格区间"
args_schema: type = PriceRangeInput # Pydantic 校验入参
def _run(self, product: str) -> str:
# 只读查询你自己的数据库/文件,返回结构化文本
return f"{product} 近期价格区间:……"
researcher.tools = [LocalPriceTool()]
# 运行护栏:单任务超时与总轮次上限(参数名随版本而定,
# 以所用版本文档为准),并给任务设 expected_output 兜底
给 agent 挂工具时,本地模型对「何时调用工具、传什么参数」的判断弱于云端模型,所以 args_schema 的 Pydantic 校验与工具 description 一定要写到位——description 就是模型的工具使用说明书。另外两个社区高频提醒:CodeInterpreterTool 已从 crewai-tools 中移除,老教程里还在用它做代码执行的要换成外部方案;工具只给真正需要的角色,不给全员。
防死循环与失控成本。本地模型更容易陷入「反复调用同一工具」或「任务永不完成」的循环。上线前给 Crew 配好最大迭代次数与单任务超时,跑批场景在外层加失败重试上限——多智能体 × 死循环 = 一晚上的电费和日志,别问是怎么知道的。
常见问题速查
| 你遇到的现象 | 大概率原因 & 解决 |
|---|---|
| 连接报错 Connection refused | Ollama 服务没起。先跑 ollama list 与 curl 验证 11434 端口 |
| 模型输出格式混乱、无视角色设定 | 本地小模型能力上限。收紧 goal/backstory 负面约束、写细 expected_output,或换更大模型 |
| hierarchical 模式重复派活/提前结束 | 经理模型太弱。经理换本地最大模型或走云端,worker 可留小模型 |
| 跑得极慢 | 模型超出显存走了内存交换。换小模型或量化版本;多 agent 任务本身轮次多,属正常 |
| 工具从不被调用 / 参数乱传 | description 与 args_schema 描述不清。把工具说明当「给模型看的文档」来写 |
| crew 卡住不结束 | 循环上限/超时没配。加上限后重跑,并检查是否工具反复调用 |
| 想做代码执行工具 | CodeInterpreterTool 已从 crewai-tools 移除。改用自建沙箱执行方案,别再按老教程 import 它 |