news 2026/4/22 11:06:10

【手搓 AI Agent 从 0 到 1】第九课:原子动作——让每一步执行都可验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【手搓 AI Agent 从 0 到 1】第九课:原子动作——让每一步执行都可验证

📌前置知识:已完成第一课至第八课
🎯本课目标:把模糊的计划步骤拆解为带类型、可验证的原子动作,让执行安全可控
💡核心概念:原子性 / 确定性 / 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"}}

现在你可以:

操作模糊步骤原子动作
验证"撰写初稿"对吗?不知道actioninputs字段完整吗?可以检查
执行怎么做?不知道调用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_textsearch_websend_email……)
  • 必需输入:必须提供哪些参数(topiclength……)
  • 验证规则:参数必须满足什么条件(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 课

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 11:04:36

FFmpeg在直播带货中的实战:如何用一条命令实现多平台推流与画质优化

FFmpeg在直播带货中的实战:如何用一条命令实现多平台推流与画质优化 直播带货的火爆让实时视频处理技术成为电商运营的刚需。想象一下,当你需要同时向抖音、B站、视频号三个平台推送高清直播流时,传统方案可能需要三台编码设备或复杂的推流软…

作者头像 李华
网站建设 2026/4/22 10:57:46

**时序数据库实战:用Go语言构建高性能时间序列数据存储系统**在现代物联网、监控告警和金融交易等场景中,**时序数据**

时序数据库实战:用Go语言构建高性能时间序列数据存储系统 在现代物联网、监控告警和金融交易等场景中,时序数据(Time Series Data)的处理能力直接决定了系统的实时性和稳定性。传统的通用关系型数据库在面对高频写入、高并发查询和…

作者头像 李华