CrewAI 通信 Agent 代码说明

这份脚本和 6/3-prog/0-pydanticai-openai-comm-agent.py 做的是同一件事:
都通过本地 Ollama /v1qwen 模型,解决一个通信领域的小问题。

区别在于:

所以这份代码更适合讲清楚 CrewAI 的几个核心词:

这份脚本整体在做什么

你可以把它理解成一个两步工作流:

  1. 第一个 agent 先做链路预算计算
  2. 第二个 agent 再根据 SNR 做调制建议

最后把两个阶段的结果合成一份中文结论,并写到本目录下的 Markdown 报告里。


第一部分:导入库和基础设置

脚本开头先导入:

这里有两个很实用的小动作:

warnings.filterwarnings(...)

它的作用是把当前环境里那个 logfire-plugin 的导入警告压掉。
这个警告不影响 demo 运行,但会把终端刷得很乱。

os.environ.setdefault("OTEL_SDK_DISABLED", "true")

这行是为了减少 tracing / telemetry 相关的干扰。
对课堂 demo 来说,我们只想把例子跑通,不想让同学一上来就被 tracing 配置打断。


第二部分:DEFAULT_PROMPTMODEL_PRESETS

DEFAULT_PROMPT

这是默认题目。
内容和 6/3-prog 保持一致,还是那道无线链路分析题。

这样做的好处是:

MODEL_PRESETS

这里定义了两个模型预设:

这相当于一个方便的开关。
如果小模型卡住,就可以直接切到 full


第三部分:clear_proxy_env()

这个函数会清掉:

以及对应的大写版本。

为什么要这样做?

因为很多同学电脑里装过代理。
一旦这些环境变量存在,本来应该访问本地 http://localhost:11434/v1 的请求,就可能被错误地送到代理上去。

所以这一步的本质是:


第四部分:两个工具的参数结构

class LinkAnalysisArgs(BaseModel)

这是第一个工具的参数定义。

它列出了链路预算要用到的全部输入:

这里用 Pydantic 的意义是:

class ModulationArgs(BaseModel)

这是第二个工具的参数定义。
它非常简单,只需要一个:

因为第二个工具只负责把 SNR 变成调制建议。


第五部分:第一个工具 AnalyzeWirelessLinkTool

这一类继承自 BaseTool

name

name: str = "analyze_wireless_link"

这是工具名。
模型调用工具时,看到的就是这个名字。

description

description: str = "计算无线链路的路径损耗、接收功率、噪声底、SNR 和 Shannon 容量。"

这是给模型看的工具说明。
说明写得越清楚,模型越容易正确选工具。

args_schema

args_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

这是热噪声底的常见写法。

含义是:

snr_value = received - noise

接收功率减噪声底,就是 SNR

capacity_bps = bandwidth_hz * math.log2(1 + 10 ** (snr_value / 10))

这是 Shannon 容量公式。

注意这里的 SNR 要先从 dB 转成线性值,所以有:

10 ** (snr_value / 10)

最后返回的是一个字典。
这样后面的 agent 就能直接读这些字段。


第六部分:第二个工具 SuggestModulationTool

这个工具更简单。

它的输入只有一个:

然后按区间给出建议:

这不是严谨的系统设计结论,而是:

也正因为它简单,所以特别适合拿来说明:


第七部分:build_llm()

return LLM(
    model=f"openai/{model_name}",
    base_url=base_url,
    api_key="ollama",
    temperature=0,
)

这一段是整个 6/4-prog6/3-prog 最接近的地方。

它表示:

为什么 model=f"openai/{model_name}"

因为在这条接法里:

为什么 api_key="ollama"

因为 OpenAI-compatible 接口通常要求带一个 key 字段。
对本地 Ollama 来说,这里只是占位值。

为什么 temperature=0

为了让课堂演示更稳定。
这样每次输出不会飘得太厉害。


第八部分:build_crew()

这是这份脚本最核心的部分。

第一个 agent:analysis_agent

它的任务是:

所以它只挂了一个工具:

第二个 agent:advisor_agent

它的任务是:

所以它只挂了一个工具:

这就是 CrewAI 的一个重要特点:


第九部分:两个 Task

analysis_task

这个任务明确要求:

这一步是在强行把第一位 agent 的职责收紧。
不让它一边算、一边解释、一边建议。

advice_task

这个任务有一行很关键:

context=[analysis_task]

这表示:

所以 advisor_agent 不需要重新算链路预算,
它只需要读前面的结果,再做调制建议。

这就是 CrewAI 里“任务串起来”的最基本方法。


第十部分:Crew(...)

return Crew(
    agents=[analysis_agent, advisor_agent],
    tasks=[analysis_task, advice_task],
    process=Process.sequential,
    verbose=False,
    tracing=False,
)

这里最重要的是:

agents=[...]

表示这套系统里有哪些 agent。

tasks=[...]

表示要执行哪些任务。

process=Process.sequential

表示:

先做第一步,再做第二步。

这和前面 6/3-prog 的区别非常明显:


第十一部分:报告写入

summarize_task_output(task_output)

这个函数把每个任务输出整理成一个更容易保存的字典。

里面保留了:

write_markdown_report(...)

这个函数会把运行结果写到:

里面包含:

这和我们前面几套 demo 一样,目的都是:


第十二部分:main()

这里主要做三件事:

  1. 解析命令行参数
  2. 构建 crew 并执行
  3. 把结果打印出来并写入 Markdown

--model-preset

这是你前面刚加过的模型开关:

crew.kickoff(inputs={"user_prompt": args.prompt})

这行表示真正开始执行 crew。

同时把用户题目传进 task 模板里的 {user_prompt}

print(result.raw)

把最终输出先打印到终端。

write_markdown_report(...)

把结果再写到文件。


这份代码最该看懂什么

如果你是编程初学者,看完这份脚本,最该抓住的是四件事:

  1. Tool 其实就是“给模型调用的 Python 能力”
  2. Agent 是“谁来做这件事”
  3. Task 是“这一步到底要完成什么”
  4. Crew 是“把多步任务按顺序串起来”

如果你把这四层关系看明白了,再回去看:

你就能很清楚地区分: