智能体开发做久了,你会发现一个很反常的现象:模型越来越强、框架越来越多,但 Agent 依然经常做出“看起来合理、实际上完全跑不通”的决策。原因往往不在模型,而在 Agent 眼前那个“世界”。
如果你给 Agent 的世界只是一大段自然语言规则,它就只能靠语言推理去猜。猜 10 次可能对 5 次,剩下 5 次暴露出来的不是模型笨,而是世界本身不可执行。这个问题在 Agent 开发里比想象中更底层,更值得先想清楚。
所以我想认真聊一个概念:Code as Worlds——智能体发现可执行世界表征。它不只是一句口号,而是一套把环境、规则、状态和反馈全部变成可运行代码的 Agent 设计思路。看完这篇文章,你会理解可执行世界表征和自然语言提示词的区别,能自己搭一个最小可执行环境,并知道如何让智能体在真实反馈里收敛到可靠的动作策略。
1. 为什么智能体开发会卡在“世界”这一层
从 Dify、Coze 到自研 Agent 框架,智能体开发的热度已经持续很久了。很多人入门的路径很相似:先调一个模型 API,再定义一堆工具函数,然后把“你是谁、你能做什么”写进系统提示词,最后运行一个循环,让模型反复调用工具。
这个流程看起来没什么问题,但真正放到业务里,你会碰上一堆说不清的失败:
- 模型明明知道某操作会触发校验错误,却还是反复尝试同一个非法动作;
- Agent 选了工具,也传了参数,但因为没有收到足够细的反馈,它无法判断自己到底做没做对;
- 多个 Agent 共享同一个环境状态时,互相覆盖数据,整个流程直接崩溃;
- 你改了一版 prompt,Agent 行为发生很大变化,但你不知道变化来自规则措辞,还是模型随机性。
这些问题的共同根源,是 Agent 所面对的世界太过“模糊”。如果世界只是文字,Agent 的推理就没有可靠的锚点。它只能根据上下文猜,而猜出来的决策当然不稳定。
一个更工程化的思路是:把世界从“一段描述”升级为“一套可执行的代码”。让 Agent 的每一个动作都能改变状态,返回明确的结果,产生有意义的反馈。只有当 Agent 经历的不是文本而是真实反馈时,它才有可能形成对世界结构和边界的内部模型,也就是可执行世界表征。
这也是为什么现在智能体框架越来越多,但环境设计反而成为新的瓶颈。Dify、Coze 这类平台解决的是“Agent 工作流怎么编排”的问题,却很难帮你回答“你的业务世界到底应该暴露哪些状态和动作”。这个设计议题,更适合放在实际代码里展开。
2. Code as Worlds:可执行世界表征到底是什么
2.1 先从“表征”这个词说起
在 AI 领域,表征(representation)指的是系统对输入信息的内部建模方式。图片分类模型会在层与层之间形成视觉特征表征;对话模型会在上下文里形成对话状态表征。那 Agent 的“世界表征”是什么?
简单说,就是 Agent 对“我身处的环境现在是什么状态、动作会带来什么变化”的理解。
如果 Agent 的世界只有一个系统提示词,它唯一能依赖的表征就是语言的语义。一旦世界内部有复杂规则——比如库存不能超过上限、商品缺货不能发货、多个订单之间相互影响——语言描述的模糊性就会变成 Agent 的错误源头。
可执行世界表征的思路是完全不同的:它不依赖 Agent 从语言里猜世界,而是把世界定义成一段可执行代码。状态是数据结构,动作是函数,反馈是函数返回值。Agent 每一次行动都相当于在真实世界里做了一次测试,然后从测试结果里更新自己对世界的认知。
2.2 为什么“可执行”比“可描述”更重要
自然语言是压缩率很高的表达方式,但代价是丢失精确性。你说“库存不足时不能下单”,模型听起来很明白,但真到动作那一层,它可能需要知道:不足的阈值是多少?是下单入口被拒绝还是后续流程回滚?扣减库存和实际发货之间有没有延迟?
在可执行世界里,这些边界不需要用语言解释。Agent 发出一个order动作,世界返回一个具体的错误:“当前库存为 0,无法创建发货单”。Agent 试一次,就知道边界在哪里。再试一次,它就能把这条经验写进后续决策。
所以 Code as Worlds 的核心判断是:Agent 的能力上限,取决于它能否在一个可执行、可观测、可验证的世界里不断试错。可执行世界表征是 Agent 从“会聊天”走向“能做事”的分水岭。
2.3 和强化学习环境的关系
熟悉强化学习的读者应该已经发现了,这个思路和 RL 环境的设定非常接近。RL 里的env.step(action)返回next_state, reward, done, info,本质上就是一个可执行世界的最小接口。
但 Code as Worlds 不局限于经典 RL。它强调智能体(尤其是 LLM Agent)可以直接在这个环境里做推理和规划,而不是从零开始学习策略。Agent 仍然是一个大模型,只是它不再靠提示词硬撑,而是靠环境的真实反馈来修正自己的下一步。
这样一来,Agent 的每一次运行都会产出一条可审计的轨迹:状态、动作、反馈、再状态。这些轨迹就是最宝贵的训练数据,可以让 Agent 做后续的提示词优化、微调,甚至在真实系统里做多智能体协作。
3. 可执行世界 vs 自然语言世界:一个直观对比
| 对比维度 | 自然语言世界 | 可执行世界(Code as Worlds) |
|---|---|---|
| 状态表达 | 写在提示词里,模型靠上下文理解 | 由代码数据结构显式定义,随时可读取 |
| 动作边界 | 凭模型推测 | 由函数和参数校验定义,非法动作直接报错 |
| 反馈粒度 | 模糊的文本建议 | 具体的返回值、错误信息、状态变更 |
| 可测试性 | 很难自动化断言 | 可以写单元测试和集成测试 |
| 可回滚性 | 改 prompt 后无法自动回滚 | 代码有版本管理,错误可以回滚 |
| 多 Agent 协作 | 容易互相抢状态 | 环境状态集中管理,可以做到原子变更 |
| 学习潜力 | 只能靠示例和语言推理 | 能产生轨迹数据,支持进一步训练 |
从表里能看出,可执行世界不是在某个单项上更优,而是在工程可靠性上全面碾压。它把“Agent 应该怎么做”的问题,从 prompt 工程问题变成了软件工程问题。这也是我能给从事智能体开发的人最直接的建议:少花时间反复调提示词,多花时间把环境写扎实。
当然,自然语言也不是完全没有位置。它是 Agent 和人类之间沟通的界面,适合描述目标、解释策略、展示推理过程。但真正决定 Agent 能否稳定行动的,仍然是底层的可执行世界。
4. 环境准备与基础配置
下面会用一个最小可执行环境来演示。为了让思路能直接落地,我选 Python 作为主要语言,不需要额外的重型依赖。具体版本以你的实际项目为准,本文重点演示通用思路。
4.1 基础运行环境
建议环境如下:
- 操作系统:Windows / macOS / Linux 均可
- Python:3.10 及以上(版本以实际项目为准)
- 环境管理:venv 或 conda
- 核心依赖:json、typing、copy 等标准库即可
如果只跑演示代码,不需要安装第三方库。如果要接入真实的大模型 API,请按你所用模型服务商的 SDK 文档安装依赖,并把密钥配置到环境变量中,例如:
export LLM_API_KEY=your_api_key_here export LLM_BASE_URL=https://your-model-endpoint.example.com不要把密钥写进代码仓库,更不要提交到公共仓库里。
4.2 是否需要使用 Agent 框架
Dify、Coze 等平台能帮你快速搭建智能体工作流,但在做“可执行世界表征”实验时,我建议先从纯代码入手。
原因很简单:你需要完全掌握状态、反馈和轨迹三个核心环节,平台层会把这些细节封装掉,反而不利于你理解问题。当你跑通最小闭环之后,再移植到 Dify、Coze 或自研智能体框架都会轻松很多。
如果你的团队已经选了低代码 Agent 平台,也可以用同样的思路来设计平台的“工具接口”,让每个工具返回结构化反馈,而不是一段自然语言描述。平台只是载体,可执行世界才是真正的核心。
4.3 项目目录结构
我们用一个非常简单的目录来组织代码:
code-as-worlds-demo/ ├── world.py # 可执行世界定义 ├── agent_core.py # Agent 循环骨架 ├── world_config.json # 世界初始配置 ├── logs/ │ └── run_round_001.jsonl # 轨迹日志 └── README.md这个结构刻意精简,方便你复制后直接运行。真实项目里可以把 world、agent、evaluation 分成不同模块。
5. 核心流程拆解:从世界定义到 Agent 循环
搭建可执行世界表征,本质上是在做四件事:定义状态、定义动作、定义反馈、定义循环。这也是整个智能体开发里真正值得深思的部分。
5.1 定义状态
状态是 Agent 观察世界的窗口。每个状态字段都应该有明确含义:库存有多少、订单有多少、当前回合数、累计收入等。Agent 在每个回合开始时调用observe()方法拿到一份 JSON 序列化的状态快照。
这里容易犯的错误是:把状态和内部实现混在一起。比如直接把 Python 对象的私有字段暴露给 Agent,字段名不统一、类型不稳定,模型很难形成稳定的世界表征。正确做法是先定义对外状态 Schema,再让世界内部实现去适配。
5.2 定义动作
动作是 Agent 改变世界的方式。设计最小动作集,比设计一个大而全的动作集更重要。每个动作对应一个函数,参数必须明确。非法参数绝不静默忽略,而是返回错误反馈。
这里的一个常见坑是:动作函数内部逻辑耦合,导致 Agent 无法预测动作边界。比如order动作既要改库存,又要改订单表,还要改状态字段;某个环节失败时,前面已经改了一半状态,世界陷入不一致。这个问题在真实业务里非常常见,解决思路是让世界具有事务性,至少保证状态变更在动作失败时可以回滚。
5.3 定义反馈
反馈是 Agent 学习的核心信号。自然语言世界里的反馈可以是“操作失败”,但在可执行世界里,反馈应该更结构化:错误类型、错误信息、当前状态、建议字段。这样 Agent 才能在上一次失败中定位真正原因。
反馈不应该直接告诉 Agent 应该怎么做,而是提供足够信息让 Agent 自己推断。也就是说,世界不要成为 Agent 的“提示词机器”。
5.4 定义 Agent 循环
Agent 循环是串联状态、动作和反馈的骨架。流程如下:
- Agent 获取当前状态
- Agent 根据历史轨迹和当前状态生成一个动作
- 智能体向可执行世界提交动作
- 世界执行动作并返回反馈和下一个状态
- Agent 把反馈追加到历史轨迹
- 循环直到达到最大回合数或任务完成
这个循环可以应用到任何智能体框架,本质上就是 Dify、Coze 里“工具调用 + 工作流编排”的底层模型。
6. 完整示例:一个可执行库存世界
下面用一个“库存管理”的简化场景来演示完整实现。这个场景足够小,但包含了状态、动作、反馈边界和循环,非常适合理解 Code as Worlds 的核心。
6.1 示例 1:可执行世界定义
# 文件路径:world.py """一个极简的可执行库存世界。 设计思路: 1. 世界状态通过 observe() 序列化为 JSON,Agent 只能看到可观察字段。 2. 所有动作都通过 step(action) 入口执行,返回结构化反馈。 3. 非法动作不静默忽略,而是返回明确的错误信息。 """ import copy from typing import Any, Dict class InventoryWorld: def __init__(self, config: Dict[str, Any]): self.stock: Dict[str, int] = copy.deepcopy(config.get("stock", {})) self.max_capacity: int = config.get("max_capacity", 100) self.total_orders: int = 0 self.total_revenue: float = 0.0 self.rounds: int = 0 def observe(self) -> Dict[str, Any]: """返回当前世界的可观察快照。""" return { "round": self.rounds, "stock": copy.deepcopy(self.stock), "total_orders": self.total_orders, "total_revenue": self.total_revenue, "max_capacity": self.max_capacity, } def step(self, action: Dict[str, Any]) -> Dict[str, Any]: """统一动作入口,返回反馈信息。""" self.rounds += 1 action_type = action.get("action") if action_type == "query": return { "ok": True, "message": "查询成功", "state": self.observe(), } if action_type == "order": product = action.get("product") quantity = int(action.get("quantity", 0)) if not product or product not in self.stock: return { "ok": False, "message": f"未知商品: {product}", "state": self.observe(), } if quantity <= 0: return { "ok": False, "message": "下单数量必须大于 0", "state": self.observe(), } if self.stock[product] + quantity > self.max_capacity: return { "ok": False, "message": ( f"{product} 库存将超出最大容量 {self.max_capacity}," f"当前库存 {self.stock[product]}" ), "state": self.observe(), } self.stock[product] += quantity self.total_orders += 1 return { "ok": True, "message": f"下单成功,{product} 当前库存 {self.stock[product]}", "state": self.observe(), } return { "ok": False, "message": f"未知动作: {action_type}", "state": self.observe(), }这段代码的关键点有三个:
observe()方法显式控制 Agent 能看到的字段,避免泄露内部实现细节;step()是动作的唯一入口,所有校验都集中在世界内部;- 每个反馈都携带
ok、message、state,让 Agent 能同时获得结果判断和最新状态。
6.2 示例 2:世界初始配置
世界初始配置放在 JSON 文件里,便于修改和版本管理。
{ "stock": { "apple": 10, "banana": 0, "orange": 5 }, "max_capacity": 100 }这个配置文件在启动时被读入InventoryWorld,用来初始化世界状态。把初始状态从代码中抽出来,好处是后续可以做单元测试和回归测试:针对不同初始状态验证 Agent 策略。
6.3 示例 3:Agent 循环骨架
Agent 循环负责把大模型和世界连接起来。下面这段代码不绑定特定模型厂商,只给出通用骨架。
# 文件路径:agent_core.py """一个不绑定具体模型厂商的 Agent 循环骨架。 你可以把 call_llm 替换成任何模型服务商的接口。 """ import json from world import InventoryWorld SYSTEM_PROMPT = """ 你是一个库存管理 Agent。你只能执行两种动作: - {"action": "order", "product": "商品名", "quantity": 数量} - {"action": "query"} 每次你会收到:世界状态、上一步执行反馈。 你的目标:把库存补到不低于 20,但不要超出 max_capacity。 请根据反馈做出下一步动作,只输出 JSON,不要输出多余文字。 """.strip() def call_llm(messages): """接入你的模型服务。 这里留空,由实际项目实现。 可以使用 OpenAI SDK、Anthropic SDK,也可以是本地部署模型。 """ raise NotImplementedError("请替换为你的模型调用代码") def run_agent(world, max_rounds=10): history = [] for r in range(max_rounds): state = world.observe() history.append({ "role": "user", "content": f"世界状态: {json.dumps(state, ensure_ascii=False)}", }) # 截断历史,避免上下文过长。这里按最近 4 条消息处理。 messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.extend(history[-4:]) raw = call_llm(messages) action = json.loads(raw) result = world.step(action) history.append({"role": "assistant", "content": raw}) history.append({ "role": "user", "content": f"反馈: {result['message']}", }) print(f"回合 {r + 1}: action={action}, feedback={result['message']}") if result.get("done"): break return historycall_llm函数需要你在实际项目中接入模型服务。例如,使用 OpenAI SDK 时,可以这样替换:
def call_llm(messages): from openai import OpenAI client = OpenAI() response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, temperature=0.2, ) return response.choices[0].message.content具体接口名称和参数以你的模型 SD K 文档为准。重点是循环结构不变:状态 → 动作 → 反馈 → 历史 → 下一步。
6.4 示例 4:运行入口与轨迹记录
为了让过程可审计,建议把每一轮轨迹写入 JSONL 文件,方便后续分析或微调。
# 文件路径:main.py import json from world import InventoryWorld from agent_core import run_agent def load_config(path: str): with open(path, "r", encoding="utf-8") as f: return json.load(f) def save_history(history, path: str): with open(path, "w", encoding="utf-8") as f: for message in history: f.write(json.dumps(message, ensure_ascii=False) + "\n") if __name__ == "__main__": config = load_config("world_config.json") world = InventoryWorld(config) history = run_agent(world, max_rounds=5) save_history(history, "logs/run_round_001.jsonl") print("最终状态:", world.observe())运行前先创建logs目录:
mkdir -p logs python main.py运行成功后,logs/run_round_001.jsonl会记录 Agent 每个回合的完整轨迹。这就是 Agent 后续学习所需要的“过程数据”。
7. 运行结果与效果验证
当你接入真实模型后,运行main.py会得到类似下面的输出(具体内容取决于模型选择和历史截断策略):
回合 1: action={'action': 'query'}, feedback=查询成功 回合 2: action={'action': 'order', 'product': 'banana', 'quantity': 20}, feedback=下单成功,banana 当前库存 20 回合 3: action={'action': 'order', 'product': 'banana', 'quantity': 20}, feedback=下单成功,banana 当前库存 40 回合 4: action={'action': 'order', 'product': 'apple', 'quantity': 10}, feedback=下单成功,apple 当前库存 20 最终状态: {'round': 4, 'stock': {'apple': 20, 'banana': 40, 'orange': 5}, 'total_orders': 3, 'total_revenue': 0.0}判断实验成功至少要看三点:
- Agent 是否根据反馈修正动作:如果第一次返回“未知商品”,下一次不应再尝试同一商品名。
- 状态是否按预期变化:每个动作执行后,
world.observe()中的stock字段要与反馈一致。 - 轨迹日志是否完整:
logs/run_round_001.jsonl中每个回合都有完整的 user/assistant/user 消息链。
如果 Agent 输出不是合法 JSON,json.loads(raw)会直接抛错。此时建议在call_llm返回后增加 JSON 解析和修复逻辑,比如提取第一个{和最后一个}之间的内容。
如果 Agent 反复下单导致库存暴涨,可以进一步在world.py中加入“单商品最大入库量”校验,验证 Agent 是否能根据新边界调整策略。这也是可执行世界的好处:改完代码,就知道模型有没有真的理解规则。
8. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型输出非 JSON | 提示词约束不足、模型不稳定 | 打印模型原始输出,观察实际格式 | 在 prompt 中强调“只输出 JSON”,增加 JSON 修复层 |
| Agent 重复执行相同动作 | 反馈信息不够丰富,无法判断失败原因 | 检查 step() 返回的 message 和 state | 在反馈中增加“当前状态”和“已执行动作”的完整字段 |
| 世界状态被多个 Agent 互相污染 | 多个智能体共享同一个 world 实例 | 检查并发模型下 world 是否被多线程修改 | 每次运行创建新 World,或使用线程隔离 + 状态快照 |
| 动作执行后状态部分更新 | 动作函数没有事务性 | 检查 action 内对 state 的多处修改 | 先校验,再一次性提交状态变更 |
| 轨迹日志过大 | 历史消息未截断 | 查看 history 长度和日志大小 | 使用最近 N 条消息策略,或做消息摘要 |
| 模型忽略反馈,继续做不合法动作 | 模型对动作空间理解弱 | 检查系统提示词是否明确列出合法动作 | 在系统提示词中显式声明“只允许两个动作,非法动作无效”,并加入少量示例 |
这些排查思路看起来简单,但在真实 Agent 项目里很实用。尤其是“状态被污染”和“轨迹日志过大”,往往是多智能体协作和生产环境部署时最先暴露的问题。
9. 最佳实践与工程建议
9.1 世界状态和动作 Schema 先行
在写任何 Agent 代码之前,先定义世界状态 Schema 和动作 Schema。这相当于给 Agent 提供了一本“世界操作手册”,远胜于在提示词里写几百字描述。
- 状态字段统一使用小写和下划线命名;
- 动作参数尽量简单,避免嵌套复杂对象;
- 为字段和参数补充注释,让模型更好理解;
- 所有状态变更尽量做到可序列化。
9.2 反馈必须结构化
建议每个世界动作都返回统一结构:
{ "ok": true, "message": "状态变更描述", "state": {} }这样做的好处是 Agent 可以稳定地从ok字段判断成败,从state字段读取最新状态,从message字段理解失败原因。如果反馈格式每次都不一样,模型就要花大量推理能力去理解反馈,而不是思考下一步动作。
9.3 用版本管理维护世界代码
可执行世界和普通业务代码一样,需要版本管理。不要只保存“旧提示词”,而要保存“旧世界代码”。当 Agent 行为回归时,第一件事就是查看世界代码变更记录。
建议把世界配置也纳入版本管理,比如world_config.json。通过对比不同配置下 Agent 的行为差异,可以更快定位问题。
9.4 保证动作的幂等性和事务性
真实业务环境里,同一个动作可能因为网络超时被重复提交。建议在step()入口为每个动作生成唯一request_id,并判断是否已经执行过。这样可以避免重复入库、重复扣库存等灾难性后果。
如果动作涉及多个状态字段变更,务必先完成所有预校验,再一次性提交变更。否则中途失败会导致状态不一致。
9.5 让 Agent 的每一步都可审计
无论 Agent 是在模拟世界还是真实系统里运行,都必须记录完整轨迹。轨迹里至少要包含:时间戳、世界状态、模型输出、执行结果、延迟、异常堆栈。这些数据不仅是调试依据,更是后续做模型微调和策略评估的核心资产。
9.6 安全边界与权限控制
如果 Agent 最终要操作真实系统,可执行世界就是最后一道安全闸门。建议先在沙箱或测试环境验证全部动作,再逐步开放生产权限。涉及删除、覆盖、资金操作等高风险动作时,可以增加“人工确认”环节。任何时候都要遵守最小权限原则,不给 Agent 超出任务范围的能力。
10. 总结与后续学习方向
Code as Worlds 真正改变的地方,是把 Agent 开发的重心从“提示词怎么写”转移到“世界怎么做才能被 Agent 精准感知和操作”。在这个思路下,智能体不再是被动接受文本指令的对话模型,而是在一个可执行环境里通过反馈不断校准行动策略的问题求解者。
如果你接下来想继续深入,可以从这几个方向展开:
- 多智能体环境:让多个 Agent 共享同一个可执行世界,观察它们如何协作或竞争;
- 世界模型学习:利用轨迹日志训练一个预测模型,让 Agent 不只是反应,而是能预演未来状态;
- 评测体系设计:为可执行世界编写自动化测试用例,稳定评估 Agent 每次改动的效果;
- 平台化整合:把本文的最小世界封装成 Dify、Coze 或自研智能体框架里的工具接口,用同样方法结构化反馈。
先把一个最小可执行世界跑通,再评估 Agent 是否真的在“按照反馈修整行为”,这个过程比盲目堆工具和框架更有价值。建议把文章里的示例代码保存下来,后续做 Agent 项目时直接套用这个模式,会少踩很多坑。