这份脚本和前面两份材料是一个连续关系:
6/3-prog:单 agent + 两个工具7/2-prog:两个 agent 串行协作所以它真正想讲清楚的是:
你可以把它理解成一个四步工作流:
最后会把完整结果写到本目录下的 Markdown 报告里。
脚本开头先导入:
argparsejsonmathoswarningsPathpydanticcrewai这里有两个很实用的小动作:
warnings.filterwarnings(...)它的作用是把当前环境里那个 logfire-plugin 的导入警告压掉。
这个警告不影响 demo 运行,但会把终端刷得很乱。
os.environ.setdefault("OTEL_SDK_DISABLED", "true")这行是为了减少 tracing / telemetry 相关的干扰。
对课堂 demo 来说,我们只想把例子跑通,不想让同学一上来就被 tracing 配置打断。
DEFAULT_PROMPT 和 MODEL_PRESETSDEFAULT_PROMPT这是默认题目。
内容和前面两版保持一致,还是那道无线链路分析题。
这样做的好处是:
MODEL_PRESETS这里定义了两个模型预设:
small -> qwen3.5:0.8bfull -> qwen3:4b这相当于一个方便的开关。
如果小模型卡住,就可以直接切到 full。
clear_proxy_env()这个函数会清掉:
http_proxyhttps_proxyall_proxy以及对应的大写版本。
为什么要这样做?
因为很多同学电脑里装过代理。
一旦这些环境变量存在,本来应该访问本地 http://localhost:11434/v1 的请求,就可能被错误地送到代理上去。
所以这一步的本质是:
Ollamaclass LinkAnalysisArgs(BaseModel)这是第一个工具的参数定义。
它列出了链路预算要用到的全部输入:
frequency_ghzdistance_kmtx_power_dbmtx_gain_dbirx_gain_dbiother_loss_dbbandwidth_mhznoise_figure_db这里用 Pydantic 的意义是:
class ModulationArgs(BaseModel)这是第二个工具的参数定义。
它非常简单,只需要一个:
snr_db_value因为第二个工具只负责把 SNR 变成调制建议。
这份 demo 没有上复杂的数据库 memory, 而是用了一个更适合课堂的最小版本:
project_memory.md里面写的是:
然后 manager 通过 read_project_memory 工具先把它读进来。
这能帮助同学区分三件事:
prompt:这一轮用户新说了什么context:前一个 task 给后一个 task 传了什么memory:系统把过去经验保留下来,并在后续任务里重新使用AnalyzeWirelessLinkTool这一类继承自 BaseTool。
namename: str = "analyze_wireless_link"这是工具名。
模型调用工具时,看到的就是这个名字。
descriptiondescription: str = "计算无线链路的路径损耗、接收功率、噪声底、SNR 和 Shannon 容量。"这是给模型看的工具说明。
说明写得越清楚,模型越容易正确选工具。
args_schemaargs_schema: type[BaseModel] = LinkAnalysisArgs这表示:
_run(...)这是工具真正执行的函数。
它逐行做了这些事:
path_loss = 92.45 + ...这是自由空间路径损耗的近似公式。
作用:
received = ...这是接收功率的链路预算公式。
它把:
放在一起,得到最终的接收功率。
bandwidth_hz = bandwidth_mhz * 1_000_000把 MHz 转成 Hz。
因为后面 Shannon 容量公式要用 Hz。
noise = -174 + 10 * math.log10(bandwidth_hz) + noise_figure_db这是热噪声底的常见写法。
含义是:
-174 dBm/Hz 是热噪声基准10log10(B) 是带宽放大后的噪声snr_value = received - noise接收功率减噪声底,就是 SNR。
capacity_bps = bandwidth_hz * math.log2(1 + 10 ** (snr_value / 10))这是 Shannon 容量公式。
注意这里的 SNR 要先从 dB 转成线性值。
SuggestModulationTool这和第一个工具是同样的写法,只是功能更简单。
它输入:
snr_db_value输出:
这样就能把“计算”和“工程判断”分成两个工具。
ReadProjectMemoryTool这个工具非常简单:
project_memory.md但它很重要, 因为它把 memory 变成了一个课堂上能看见、能解释的东西。
在这份 demo 里, memory 的作用不是“神奇地记住一切”, 而是:
build_llm(...)这里把 CrewAI 用的模型统一接到本地 Ollama /v1。
和前面一样:
model=f"openai/{model_name}" 表示走 OpenAI 风格接口base_url 实际指向本地 Ollamaapi_key="ollama" 只是一个占位字符串build_crew(...)这是整份脚本最核心的一部分。
manager_agent这个角色是:
通信项目经理它的工作不是自己去算, 而是:
这就是这份脚本和 7/2-prog 最本质的不同。
很多同学看到:
process=Process.hierarchical
manager_agent=manager_agent会以为背后是三个 agent 在“自由聊天、自由协商”。
但这份 demo 里,真实机制其实更具体,也更工程化:
你先手工定义了 3 个 agent
你再手工定义了 4 个 task
planning_taskbudget_taskmodulation_taskfinal_task每个 task 都明确绑定了一个 agent
所以系统不是“临时决定谁来做”,而是你已经写清楚了谁负责什么。
hierarchical 的作用是:
让 manager_agent 处在更高层的“统筹 / 把关”位置, 而不是把它当成一个普通执行者。
所以这份 demo 里, hierarchical 的真实含义更接近:
而不是:
真正让三个 agent 连成一条工作流的, 不是 “hierarchical” 这一个词本身, 而是:
Taskcontextmanager_agent尤其是 context。
在这份脚本里:
budget_task 读取 planning_taskmodulation_task 读取 planning_task + budget_taskfinal_task 读取前三步所以实际发生的事情是:
也就是说:
hierarchical 提供的是“角色关系”context 提供的是“信息流”两者一起,才构成了真正的多 Agent workflow。
这三个词很容易混在一起, 但在这份 demo 里其实分工很清楚:
process解决的是:
在这里就是:
Process.hierarchicalcontext解决的是:
在这里它负责:
memory解决的是:
在这里它体现为:
project_memory.md所以一句话说:
process 管流程形状context 管任务交接memory 管历史经验budget_specialist这个角色是:
链路预算专家它只负责:
analyze_wireless_linkmodulation_specialist这个角色是:
调制与容量专家它只负责:
suggest_modulationplanning_task这是 manager 的第一步任务。
它不做数值计算, 只做一件事:
这一步的教学价值非常大, 因为它把“多 Agent 不是乱分工”这件事显式写出来了。 同时这里还把 memory 放到了最前面:
这样同学更容易理解, memory 为什么会影响后面的 workflow。
budget_task这是链路预算专家的任务。
它和 7/2-prog 里的第一步很像, 但这次它会带着 manager 的 checklist 去执行。
这表示:
modulation_task这是调制专家的任务。
它会读取:
然后再调用 suggest_modulation。
所以这一步体现的是:
final_task这一步又回到 manager。
它会读取前三步结果, 输出最终报告。
注意这里写得很明确:
这就是 manager + specialists 模式里最典型的一步。
Crew(...)这里把三个 agent 和四个 task 组装起来。
这次最重要的改动是:
process=Process.hierarchical并且还加了:
manager_agent=manager_agent这表示:
这比 7/2-prog 更接近你在课堂上真正想讲的“manager + specialists”。
它仍然没有去追求“炫技式并行”, 而是把重点放在:
这些才是多 Agent 真正值得先学的部分。
summarize_task_output(...)这个函数的作用是:
这样最后写 Markdown 报告时, 我们就能看到每一步任务到底产出了什么。
write_markdown_report(...)这个函数会把运行结果写成本目录下的:
0-crewai-manager-specialists-agent-output.md里面包括:
这对教学特别有帮助, 因为你不只看到最后答案, 还看到 manager 和 specialist 分别做了什么。
main()这里主要做 4 件事:
其中最关键的一行是:
result = crew.kickoff(inputs={"user_prompt": args.prompt})这行就是真正启动整个多 Agent 工作流的地方。
最重要的不是“记住 CrewAI 的 API”。
而是下面这几件事:
Process.hierarchical 更适合讲 manager + specialistscontext 才是这份 demo 里真正的信息传递机制CrewAI 更适合讲工作流和多角色协作