📌前置知识:已完成第一课至第八课
🎯本课目标:把模糊的计划步骤拆解为带类型、可验证的原子动作,让执行安全可控
💡核心概念:原子性 / 确定性 / Schema 验证 / 类型化执行
前言
第八课,Agent 会规划了。
plan=agent.create_plan("写一篇关于 AI Agent 的技术博客")# → {"steps": ["研究 AI Agent 最新进展", "确定文章大纲", "撰写初稿", "审校修改"]}但有一个问题——
"撰写初稿"到底要做什么?
- 写多长?1000 字还是 5000 字?
- 写给谁看?技术人员还是小白?
- 输出格式是什么?Markdown 还是纯文本?
- 写到什么程度算"完成"?
你不知道,Agent 也不知道。第八课的执行只是打印"已执行",因为步骤太模糊了,根本没有办法真正执行。
就像你给下属说"去做一下市场调研"。他可能花 10 分钟搜一下百度,也可能花 3 个月写一份 200 页的报告——因为你没有定义做什么、做到什么程度、输出是什么。
本课要解决这个问题。给每个步骤加上动作名称 + 参数 + 验证规则,让它变成一个可检查、可执行的最小单元。
一、"撰写初稿"为什么不能直接执行?
1.1 模糊步骤的三个问题
第八课的步骤是一个字符串:
"撰写初稿"这个字符串面临三个问题:
① 不可验证
你怎么知道 Agent "撰写初稿"做对了?因为没有标准。输出 50 字算完成吗?输出 5000 字也算完成吗?
② 不可执行
execute_step("撰写初稿")这个函数要怎么实现?你不知道该调什么工具、传什么参数。字符串步骤没法直接变成代码。
③ 不可调试
如果执行出了问题,你不知道是哪一步错了、为什么错。因为步骤没有结构化的输入和输出,只能靠猜。
1.2 原子动作的解法
把"撰写初稿"变成一个结构化的动作:
{"action":"generate_text","inputs":{"topic":"AI Agent 的技术博客","target_audience":"有编程基础的技术人员","length":"2000-3000字","format":"markdown"}}现在你可以:
| 操作 | 模糊步骤 | 原子动作 |
|---|---|---|
| 验证 | "撰写初稿"对吗?不知道 | action和inputs字段完整吗?可以检查 |
| 执行 | 怎么做?不知道 | 调用generate_text(topic, ...)可以实现 |
| 调试 | 哪里错了?不知道 | inputs.target_audience缺失导致输出不对 |
这就是原子动作的价值:把模糊变成具体,把不可控变成可控。
二、什么是原子动作?
2.1 原子性:最小的执行单元
原子动作是不可再分的最小操作。
就像化学里的原子——你不能再把"生成一段文字"拆成更小的有意义的操作了。
"写一篇关于 AI 的博客" ← 模糊步骤(不是一个原子动作) ↓ 拆解 "生成关于 AI 的 2000 字 Markdown 文本" ← 原子动作(有明确输入和输出)原子动作要么完全成功,要么完全失败。不存在"做了一半"的中间状态。
2.2 确定性:相同输入,可预测的输出
给定相同的原子动作,执行结果应该是可预测的。
{"action":"generate_text","inputs":{"topic":"Python 基础","length":"500字"}}不管执行几次,输出都应该是"大约 500 字、关于 Python 基础的文本"。如果topic是空的,或者length是 “一万亿字”,执行前就应该被拦截。
2.3 类型化执行:Schema 即合约
每个原子动作都有一份"合约"(Schema),规定了:
- 动作名称:这个动作叫什么(
generate_text、search_web、send_email……) - 必需输入:必须提供哪些参数(
topic、length……) - 验证规则:参数必须满足什么条件(
length必须是正数……)
Schema(合约): 动作名称:generate_text 必需输入:topic(字符串,不能为空)、length(字符串,如 "1000字") 可选输入:format(默认 markdown)、tone(默认 formal) 验证流程: 1. 检查 action 字段存在 → ✅ 2. 检查 inputs.topic 不为空 → ✅ 3. 检查 inputs.length 格式合理 → ✅ → 通过验证,可以执行核心洞察:小步骤 = 安全系统。动作越小,破坏半径越小。
三、原子动作 vs 模糊步骤
第八课和第九课,解决的是同一个问题的两个层次:
第八课:目标 → 计划步骤列表 → 逐步执行 "写博客" → ["研究", "写大纲", "撰写初稿", "审校"] → 每步标记"已执行" 第九课:计划步骤 → 原子动作列表 → 验证后执行 "撰写初稿" → {"action": "generate_text", "inputs": {...}} → 检查 schema → 执行| 第八课(模糊步骤) | 第九课(原子动作) | |
|---|---|---|
| 数据格式 | 字符串列表 | 结构化 JSON(action + inputs) |
| 能验证吗 | 不能(字符串没有结构) | 能(检查字段完整性) |
| 能执行吗 | 不能(不知道做什么) | 能(action 名对应具体函数) |
| 能调试吗 | 不能(没有输入输出) | 能(可以追踪每个 inputs 的值) |
| 出错了怎么办 | 只能重试整个步骤 | 可以定位到具体参数 |
第九课是对第八课的升级——同样的"逐步执行"模式,但每一步从"说一句话"变成了"调一个函数"。
四、代码实现
4.1 原子动作生成器:agent/planner.py
在第八课的planner.py中新增create_atomic_action()函数:
defcreate_atomic_action(llm,step:str)->dict|None:""" 将计划步骤转换为原子动作。 用于:第九课 Args: llm: 语言模型实例 step: 计划中的一个步骤 Returns: 原子动作字典,失败返回 None """fromshared.utilsimportextract_json_from_text prompt=f"""将以下计划步骤转换为一个具体的原子动作。只返回有效的 JSON。 规则: 1. 只返回有效的 JSON 2. 不要任何解释,不要 Markdown 3. 直接以 {{ 开头,以 }} 结尾 4. action 应该是一个简单、具体的操作名称 5. inputs 应该包含执行该动作所需的所有参数 6. 参数值应该具体,不要模糊 JSON 格式: {{"action": "动作名称", "inputs": {{"参数名": "参数值"}}}} 步骤:{step}请返回 JSON:"""forattemptinrange(3):response=llm.generate(prompt,temperature=0.0)action=extract_json_from_text(response)ifactionand"action"inaction:if"inputs"notinactionornotisinstance(action["inputs"],dict):action["inputs"]={}returnactionreturnNone逐段拆解:
① Prompt 设计
核心指令:“action 应该是简单、具体的操作名称,inputs 应该包含所有参数”。
对比第八课的 Prompt——第八课要求返回步骤列表,第九课要求返回单个动作 + 参数。粒度从"做什么"细化到了"怎么调用"。
② 验证逻辑
ifactionand"action"inaction:if"inputs"notinactionornotisinstance(action["inputs"],dict):action["inputs"]={}returnaction只检查action字段存在。inputs如果缺失或格式不对,自动补一个空字典——容错处理。本课先做最基本的验证,后续课程会加更严格的 schema 校验。
③ 还是老三样
extract_json_from_text()——第三课以来的老朋友- 重试 3 次 ——老规矩
temperature=0.0——原子动作需要确定性
4.2 Agent 中的原子动作方法
在agent/agent.py中新增:
defcreate_atomic_action(self,step:str)->dict|None:""" 将计划步骤转换为原子动作(第九课核心方法)。 Args: step: 计划中的一个步骤 Returns: 带 action 和 inputs 的原子动作字典 """fromagent.plannerimportcreate_atomic_actionasaction_fn atomic=action_fn(self,step)ifatomic:action_name=atomic.get("action","?")inputs=atomic.get("inputs",{})print(f"⚛️ 原子动作生成完成:{action_name}({inputs})")returnatomic注意设计细节:
① 同样委托给planner.py
和第八课的create_plan()一样,原子动作的生成逻辑放在planner.py,Agent 类只做调度。职责分离的原则贯穿始终。
② 生成时打印结果
print(f"⚛️ 原子动作生成完成:{action_name}({inputs})")方便调试。你可以清楚看到每个步骤被转换成了什么动作、带了什么参数。
③ 和create_plan()的关系
# 第八课:生成计划plan=agent.create_plan("写一篇博客")# → {"steps": ["研究", "写大纲", "撰写初稿", "审校"]}# 第九课:把每个步骤变成原子动作forstepinplan["steps"]:atomic=agent.create_atomic_action(step)# step "撰写初稿" → {"action": "generate_text", "inputs": {"topic": "...", "length": "..."}}第八课生成步骤列表,第九课把每个步骤转成可执行的动作。两步串联,就形成了"目标 → 计划 → 原子动作 → 执行"的完整链路。
五、运行示例
5.1 基础场景:单个步骤转换
fromagent.agentimportAgent agent=Agent(model="qwen2.5:7b")# 将一个模糊步骤转为原子动作step="写一篇关于 AI Agent 的入门介绍"atomic=agent.create_atomic_action(step)print(f"\n原始步骤:{step}")print(f"原子动作:{atomic}")预期输出(类似):
⚛️ 原子动作生成完成:generate_text({'topic': 'AI Agent 入门介绍', 'target_audience': '初学者', 'length': '1500-2000字', 'format': 'markdown'}) 原始步骤:写一篇关于 AI Agent 的入门介绍 原子动作:{'action': 'generate_text', 'inputs': {'topic': 'AI Agent 入门介绍', 'target_audience': '初学者', 'length': '1500-2000字', 'format': 'markdown'}}注意看:"写一篇关于 AI Agent 的入门介绍"变成了一个带 4 个参数的具体动作。
5.2 从计划到原子动作
# 先生成计划(第八课能力)plan=agent.create_plan("创建一个 Python 教程")# 再把每个步骤转为原子动作(第九课能力)ifplanand"steps"inplan:print("\n📋 计划 → 原子动作映射:\n")fori,stepinenumerate(plan["steps"],1):atomic=agent.create_atomic_action(step)ifatomic:print(f" 步骤{i}:{step}")print(f" 动作{i}:{atomic['action']}({atomic['inputs']})")print()预期输出(类似):
📋 计划 → 原子动作映射: ⚛️ 原子动作生成完成:search_web({'query': 'Python 教程 最佳实践 2026', 'num_results': '5'}) 步骤1:调研 Python 教程的最佳实践和热门主题 动作1:search_web({'query': 'Python 教程 最佳实践 2026', 'num_results': '5'}) ⚛️ 原子动作生成完成:generate_text({'topic': 'Python 基础教程大纲', 'sections': '5-8个章节', 'format': 'markdown'}) 步骤2:制定教程大纲和章节结构 动作2:generate_text({'topic': 'Python 基础教程大纲', 'sections': '5-8个章节', 'format': 'markdown'}) ...(略)这就是“目标 → 计划 → 原子动作”的完整转换链路。
5.3 对比:不同步骤的原子动作
同一个目标,不同步骤会产生不同类型的原子动作:
steps=["研究竞品的定价策略","写一封客户跟进邮件","整理上周的销售数据","制作季度汇报 PPT"]forstepinsteps:atomic=agent.create_atomic_action(step)可能的输出:
"研究竞品的定价策略" → {"action": "search_web", "inputs": {"query": "竞品定价策略分析", "sources": "行业报告、官网"}} "写一封客户跟进邮件" → {"action": "generate_text", "inputs": {"type": "商务邮件", "recipient": "客户", "tone": "专业友好"}} "整理上周的销售数据" → {"action": "process_data", "inputs": {"data_source": "销售系统", "time_range": "上周", "output": "汇总报表"}} "制作季度汇报 PPT" → {"action": "create_presentation", "inputs": {"topic": "季度汇报", "slides": "10-15页", "format": "pptx"}}不同的模糊步骤被转换成了不同类型的原子动作,每个都有针对性的参数。这正是"类型化执行"的价值。
5.4 交互模式
cdlesson09 python complete_example.py会进入交互模式,你可以输入任意步骤,观察它被转换成什么样的原子动作。
六、与第八课的本质区别
两课都处理"计划步骤",但粒度完全不同:
第八课(规划——步骤级别):
目标:"写一篇博客" ↓ 计划:["研究主题", "写大纲", "撰写初稿", "审校"] ↓ 执行:每步打印"已执行"步骤是字符串,没有结构。执行是占位符。
第九课(原子动作——操作级别):
步骤:"撰写初稿" ↓ 原子动作:{"action": "generate_text", "inputs": {"topic": "...", "length": "..."}} ↓ 验证:action 存在?inputs 合理? → 通过 → 可以执行动作是结构化数据,有 schema。执行前可以验证。
| 第八课(模糊步骤) | 第九课(原子动作) | |
|---|---|---|
| 输入 | 字符串:“撰写初稿” | 结构化:{"action": "generate_text", "inputs": {...}} |
| 能验证吗 | ❌ 字符串没有结构 | ✅ 检查 action + inputs |
| 能执行吗 | ❌ 不知道做什么 | ✅ action 名对应具体函数 |
| 出了错 | 只能重试 | 定位到具体参数 |
| 类比 | 给下属说"做个方案" | 给下属说"用 PPT 做 10 页方案,包含竞品分析和预算" |
第八课定义了"做什么"(What),第九课定义了"怎么做"(How)。
七、关键洞察
7.1 小步骤 = 安全系统
这是本课最重要的洞察:
动作越小,系统越安全。破坏半径越可控。
一个"写文章"的大动作,可能产生 10000 字的垃圾输出。但一个"生成 200 字的产品描述"的原子动作,最多浪费 200 字。
原子动作的四个安全特性:
| 特性 | 说明 |
|---|---|
| 易验证 | 执行前检查参数,不合格就不执行 |
| 易测试 | 每个动作可以独立测试 |
| 易调试 | 失败被隔离到具体动作和参数 |
| 破坏半径小 | 小动作即使出错,影响也有限 |
7.2 模糊是万恶之源
"写文章"是模糊的。模糊带来三个问题:
- LLM 不知道输出什么→ 结果不可预测
- 代码不知道调什么→ 无法实现
- 人不知道对不对→ 无法审查
"generate_text(topic=‘AI agents’, length=‘1000 words’)"是具体的。具体意味着:
- LLM 知道输出什么 → 结果可预测
- 代码知道调什么 →
generate_text(topic, length)可以实现 - 人知道对不对 → "1000 字"对不对,一眼就能看出来
核心原则:尽可能把每一步的具体程度推到极致。
7.3 验证在执行之前
原子动作的设计哲学是先验证,后执行。
传统方式:直接执行 → 出错了再处理 原子动作:验证 schema → 通过 → 执行 / 不通过 → 拦截就像你写代码先编译再运行,而不是直接跑然后看报错。验证越早,修复成本越低。
7.4 原子动作是构建块
单个原子动作很简单。但组合起来可以构建复杂的工作流:
generate_outline(topic) ← 原子动作 1 ↓ search_web(query) ← 原子动作 2(依赖动作 1 的输出) ↓ generate_text(topic, outline) ← 原子动作 3(依赖动作 1、2 的输出) ↓ review_and_edit(text) ← 原子动作 4(依赖动作 3 的输出)每个原子动作都很简单,但串联起来就是一篇完整的文章生成流程。第十课会讲如何处理这些依赖关系。
八、常见问题
Q:生成的原子动作还是很模糊怎么办?
A:优化 Prompt。加更具体的约束,比如"action 必须是以下之一:generate_text、search_web、send_email、create_file"。把动作名称限制在预定义集合里,模糊性会大幅降低。这就是"约束越强,输出越可控"。
Q:inputs 里的参数不合理怎么办?比如 length 是 “一亿字”?
A:本课做了基本的结构验证(action 和 inputs 字段存在),但没有做值的语义验证。后续可以通过加 schema 规则来实现,比如length必须是正数且不超过 10000。本课先把"结构化输出"这个模式跑通,value validation 是下一步。
Q:有些步骤很难拆成原子动作怎么办?
A:有些步骤本身就是"协调性"的,比如"和团队讨论确定方向"。这类步骤可能不适合原子化。一个策略是:先把能原子化的步骤原子化,不能原子化的保留为"人工步骤",让 Agent 跳过或提醒人类介入。
Q:原子动作和第五课的工具调用有什么区别?
A:很像,但角度不同。第五课是 Agent运行时动态选择工具调用,第九课是规划阶段预先确定每步用什么动作。第五课是"边想边做",第九课是"先想好再验证再做"。两种模式可以结合——规划时预定义动作,运行时根据情况调整。
Q:每次都要调 LLM 来生成原子动作,会不会很慢?
A:会。每个步骤都要一次 LLM 调用。但在实际场景中,这比"执行错了再回滚"的成本低得多。而且很多步骤的原子动作是可以缓存的——相似步骤复用之前的转换结果。
九、九课演进线
把前九课放在一起看,Agent 的能力越来越"像人":
第一课:能对话了(LLM + Ollama) 第二课:有角色了 + 能多轮对话(System Prompt + History) 第三课:能输出 JSON 了(结构化输出 + 验证 + 重试) 第四课:能做选择了(意图理解 → 动作路由) 第五课:能调用工具了(工具选择 + 参数提取 + 安全执行) 第六课:能循环了(Agent Loop + 状态追踪 + 终止条件) 第七课:能记住了(跨对话记忆存储 + 检索 + 显式管理) 第八课:能规划了(计划生成 → 验证 → 逐步执行) 第九课:能拆解了(模糊步骤 → 原子动作 → Schema 验证)还差什么?
- 第十课:思维链推理——让规划过程更严谨,处理步骤间的依赖关系
十、下期预告
第十课:思维链推理——让 Agent 学会"想清楚再做"
本课的原子动作已经是可验证的最小执行单元了。但还有一个问题:步骤之间的顺序和依赖。
比如"写初稿"必须在"写大纲"之后,"审校"必须在"写初稿"之后。第八课的执行是简单遍历列表,没有处理这些依赖关系。
下一课,我们将引入思维链推理(Chain of Thought)和依赖图,让 Agent 不仅知道"每步做什么",还能推理出"哪步先做、哪步后做、哪些可以并行做"。
这是从"逐步执行"到"智能调度"的关键升级。
敬请期待!
完整代码获取
本课涉及的完整代码包括:
agent/planner.py——规划器模块(新增create_atomic_action()函数)agent/agent.py——Agent 类(新增create_atomic_action()方法)complete_example.py——演示模式(4 个场景)+ 交互模式
代码获取请参照 本系列第一篇文章。
标签
#Python#AI Agent#LLM#原子动作#Schema验证#Ollama#Qwen#大模型#手搓Agent
本文为《手搓 AI Agent 从 0 到 1》系列教程第 9 课