Agentic AI(五)高度自主智能体的模式:规划与多智能体

文章内容

一张四行的太阳镜库存表,字段是 id / name / description / price / stock。顾客问:「有 100 美元以下、还有货的圆框墨镜吗?」表里没有「圆框」这一列,这个词埋在 description 的自由文本里;有没有货在 stock 列,贵不贵在 price 列。开发者当然可以把「查描述 → 查库存 → 查价格」这三步写死进代码,但下一个顾客问「我上个月退的那副处理好了吗」,写死的流程就接不住了。

规划工作流换了个做法:把工具清单和用户请求一起交给 LLM,由它输出要走哪几步。这条能力从「LLM 生成一段自然语言的步骤计划」起步,往前走到「计划输出成 JSON 让下游代码解析执行」,再往前是「让 LLM 直接写代码,一段代码装下多步计划」。当单个 agent 的任务单元大到装不下,就拆成多个 agent,这时候要挑的是它们之间的沟通模式。

一、规划:让智能体自己决定下一步

1.1 为什么不能写死每一步

前几个模块里的工作流,形状都是开发者画好的。反射是「生成 → 反思 → 改写」两次调用,工具使用是「判断要不要调工具 → 调 → 把结果喂回去」。步骤序列固定,LLM 负责的是每一步里的内容。

规划工作流:智能体自主决定任务步骤

1.2 客服智能体:库存表与工具集

课程用的库存表长这样

id name description price stock
1001 Aviator Timeless pilot style for any occasion, metal frame 80 12
1002 Catseye Glamorous 1950s profile, plastic frame 60 28
1003 Moon Oversized round style, plastic frame 120 15
1004 Classic Classic round profile, gold frame 60 9

顾客的原话是 Do you have any round sunglasses in stock that are under $100?。「round」这个约束落在 description 的语义上,「in stock」落在 stock 列,「under $100」落在 price 列。三个条件分散在三种不同性质的字段里,没有哪一个工具能一次答完。

agent 手上的六个工具是:get_item_descriptions、check_inventory、get_item_price、check_past_transactions、process_item_return、process_item_sale。前三个是读,后三个是查历史和写操作。

1.3 三步计划怎么生成

系统提示很短:

plain text
You have access to the following tools: {description of tools} Return a step-by-step plan to carry out the user's request.

工具描述被模板参数 {description of tools} 撑开,用户请求作为一条普通消息接在后面,LLM 的输出是一段带编号的自然语言:

plain text
3. Use get_item_price to see if in-stock results are <$100

这三步的顺序自带信息量。先按描述筛 round,能把四行缩到 Moon 和 Classic 两行,后面两步只需要处理两条记录;反过来先查价格,就得对全表取价再回头匹配语义。

客服智能体规划链路:请求 → LLM → 三步计划 → 逐步执行回填

1.4 每步怎么执行(回填)

计划生成完,执行链路:

  • Step 1 text → LLM → get_item_descriptions
  • Step 1 output, Step 2 text → LLM → check_inventory
  • Step 2 output, Step 3 text → LLM → get_item_price

数一下,这条链路上有 5 次 LLM 调用:1 次生成计划,3 次逐步执行(每次决定这一步该怎么调工具),1 次汇总成给顾客的答复。计划本身是一次性生成的静态文本,执行时按序取用,上一步的输出拼进下一步的输入,上下文单向增长。

放回那张表上走一遍:step 1 返回 Moon 和 Classic,step 2 看到库存 15 和 9(都有货),step 3 拿到价格 120 和 60,最终能给顾客的是 Classic(60 美元,还剩 9 副)。

这里也能看出这套流程的脆弱点:计划在拿到任何真实数据之前就定死了。如果 step 1 一条都没查到,step 2 和 step 3 照样会被执行。

二、把计划变成机器可读的 JSON

2.1 为什么要 JSON

上面那段计划是给人看的英文。下游代码要把「Use check_inventory to see if results are in stock」变成一次真实的函数调用,得从句子里认出函数名、认出参数、认出参数从哪儿来。用正则去抠很快就会碎,再叫一次 LLM 去解析等于多付一次延迟和成本。

课程给的做法是让 LLM 直接吐结构,下游只需要 json.loads 加一次查表分发。

2.2 系统提示怎么改

系统提示在原来那份上加了两句:

plain text
You have access to the following tools: {description of tools} Create a step-by-step plan in JSON format. Each step should have the following items:step number, description, tool name, and args.

2.3 JSON 计划示例与下游解析

同一个圆框墨镜请求,输出变成:

json
{  "plan": [    {      "step": 1,      "description": "Find round sunglasses",      "tool": "get_item_descriptions",      "args": {"query": "round sunglasses"}    },    {      "step": 2,      "description": "Check available stock",      "tool": "check_inventory",      "args": {"items": "results from step 1"}    }  ]}

step 是序号,description 是给人读的意图,tool 是函数名,args 是参数字典。四个字段里,前两个用于日志和排查,后两个是执行器真正执行的东西。

其中 step 2 的 args:{"items": "results from step 1"}。这不是一个值,是一句英文占位符。也就是说,JSON 化解决的是「该调哪个函数」的解析问题,「上一步的输出怎么变成下一步的入参」仍然留在运行时。执行器要么在跑到 step 2 时把 step 1 的返回替换进去,要么像第 1 节那样再叫一次 LLM 来填。

按这个结构,下游分发大致是这个形状:

python
# 工具名到真实函数的映射表,LLM 只能选到表里有的键TOOLS = {"get_item_descriptions": get_item_descriptions, "check_inventory": check_inventory} # 把模型输出的字符串解析成字典,解析失败就说明这次生成不合格式plan = json.loads(llm_output)["plan"] # 保存每一步的返回值,供后续步骤引用results = {} # 按 step 顺序执行,计划里的顺序就是执行顺序for s in sorted(plan, key=lambda x: x["step"]):    # 未知工具名直接判失败,不要让它静默跳过    fn = TOOLS[s["tool"]]    # args 里的 "results from step N" 这类占位符在这里被换成真实值    args = resolve_refs(s["args"], results)    # 执行并按步号存档,出错时能定位到具体是哪一步    results[s["step"]] = fn(**args)

输入是模型输出的字符串,输出是 results 这个按步号索引的字典,状态变化发生在每次 results[s["step"]] = ... 上。这段代码能成立的前提是 tool 字段落在 TOOLS 的键集合里;模型编出一个不存在的函数名时,TOOLS[s["tool"]] 会直接抛 KeyError,这比让它静默跳过好排查。

计划格式化为 JSON:系统提示 → LLM → plan 数组

三、让计划直接变成代码

3.1 为什么代码能表达复杂计划

第 3 节开场就说了:与其让 LLM 一次输出一步 JSON 计划再逐步执行,不如让它写代码,一段代码就装下多步——调这个函数、再调这个函数、再调这个函数。

Python 加上导入的 pandas,内置函数有几百甚至上千个,而这些函数「LLM 见过大量关于何时怎么调用它们的数据」。让它写代码,等于把可选动作从你手写的六七个工具,换成一整个成熟库的函数集合。

3.2 咖啡机销售表:工具方案 vs 代码方案

一个对照例子是一张咖啡机销售表 coffee_sales.csv,列是 date / price / coffee_name / Size(第 3 节关键帧 60759):

date price coffee_name Size
2024-01-28 3.87 Hot Chocolate M
2024-03-01 2.89 Cappuccino S
2024-03-04 3.87 Latte M
… … … …
2025-03-23 4.57 Latte L

配的工具是六个表格算子:get_column_max、get_column_mean、get_column_min、get_column_median、filter_rows、sum_rows。

问「哪个月热巧克力销量最高」,用这套工具能算出来:filter_rows 抽出 1 月的热巧克力交易,做统计,然后 2 月、3 月、4 月、5 月,一路排到 12 月,最后取最大值。

再问「上周有多少笔不重复的交易」,工具就不够用了,于是加一个 get_unique_entries。再问「最近 5 笔交易的金额分别是多少」,可能还得再加两个。

3.3 两个代码实例

换成让 LLM 写代码,系统提示是这样(关键帧 174177 原文):

plain text
Write code to solve the user's query.Return your answer as python code delimited with

<execute_python> 这对标签的作用是给执行器一个明确的截取边界,模型可以在标签外说话,标签内的内容原样送去执行。

第一个查询是「最近 5 笔交易的金额是多少」:

python
# 引入 pandas,后面所有表操作都建立在 DataFrame 上import pandas as pd # Load CSV# 把整张交易表读进内存,此时 date 还是字符串df = pd.read_csv("transactions.csv") # Ensure date column is parsed as datetime# 就地把 date 列转成 datetime,否则下一步排序会按字典序而不是时间序df["date"] = pd.to_datetime(df["date"]) # Sort by date to get most recent transactions# 按时间倒序排列,最新的排在最前面df_sorted = df.sort_values(by="date", ascending=False) # Select the last 5 transactions# 倒序之后取前 5 行,也就是时间上最近的 5 笔last_5 = df_sorted.head(5) # Show just the price column (amounts)# 打印一行标题,让输出结果对调用方可读print("Last 5 transaction amounts:")# 把这 5 行的 price 列转成 Python 列表输出print(last_5["price"].to_list())

输入是磁盘上的 CSV,输出是标准输出里的一行标题加一个数字列表,中间的状态变化是 df → df(date 列被替换成 datetime)→ df_sorted → last_5。字幕 158s–176s 把这段读成一个五步计划:加载 CSV、确保日期列被正确解析、按日期排序、取最近五笔、只展示价格列。计划以注释的形式写在代码里,执行以函数调用的形式一次完成。

第二个查询是「上周有多少笔不重复的交易」:

python
# 引入 pandasimport pandas as pd # Read CSV and parse the "date" column as datetime# 读表的同时让 parse_dates 完成日期解析,比读完再转少一步df = pd.read_csv("transactions.csv", parse_dates=["date"]) # Define time window# 取当前时刻作为窗口右端,注意这是运行时的机器时间today = pd.Timestamp.today()# 往前推 7 天作为窗口左端week_ago = today - pd.Timedelta(days=7) # Filter rows where date is within last week# between 默认闭区间,落在两个端点上的记录会被保留last_week = df[df["date"].between(week_ago, today)] # Drop duplicate rows and count# 整行去重后取行数,这就是模型对「不重复交易」的定义print(last_week.drop_duplicates().shape[0])

pd.Timestamp.today() 取的是运行时的机器时间,同一段代码在不同日期跑结果不同,做回归测试时要把时间固定住。drop_duplicates() 是整行去重,等于模型把「unique transactions」解释成「四列完全相同即为重复」;如果业务上的定义是按订单号去重,这段代码给出的数字就是错的。语义判断被移到了模型手里,而它写在注释里,可以审查。

3.4 code-as-action 研究结论

Code as Action 对比:code > JSON > text(研究结论)

四、多智能体工作流:单 CPU 与多进程的类比

一个 agent 的一次规划扛不下整件事,就把工作拆给多个 agent 协作完成。

假设一台电脑上就一个 CPU,但是可以把工作分解到多个进程上,照样能跑很多进程和很多程序;

多 agent 不会凭空多出算力,模型推理还是那些推理。拆开来的是上下文和职责(每个 agent 有自己的系统提示、自己那份工具集、自己的对话历史)。一个塞了二十个工具、要同时兼顾检索和写作和审校的系统提示,会让模型在每一步都从一堆无关选项里做选择;拆成三个各管一摊的 agent,每个的决策空间都变小了。代价是 agent 之间要传递信息,而传递本身会丢信息。

多智能体工作流:单 CPU 跑多进程的类比

五、多智能体系统的通信模式

5.1 组织架构图为什么难

难在这里没有单一正确答案:几个 agent、谁向谁汇报、谁能看到谁的输出、信息在哪一层汇总,每一项都是设计参数,组合起来空间很大,而且改动的影响要跑完整条链路才看得出来。

5.2 线性计划

简单示例:「为太阳镜做一场夏季营销活动」

plain text
Create a summer marketing campaign for sunglasses        ↓    researcher        ↓  research        │  "Here are current sunglasses trends and competitor offerings…" graphic designer        ↓  research, visualizations, artworks        │  "Here are 5 data visualizations and 5 artwork options for the report…"      writer        ↓   Final report

每个 agent 只跟下一个说话,中间产物是明确的交付物(一份调研、五张图表加五个视觉方案、一份最终报告)。这种形状的好处是排查方便:报告有问题,倒着看三份交付物就能定位到是哪一环。

线性计划通信模式:researcher → graphic designer → writer → final report

5.3 经理协调

第二种形状加了一个 marketing manager角色:

plain text
You are a marketing manager and have the following team of agents to work with: {description of agents} Return a step-by-step plan to carry out the user's request.

对照第一节那份 You have access to the following tools: {description of tools},改动的位置是把 Tools 换成了 Agents。经理输出的计划也还是那个带编号的形状:

plain text
1. Ask researcher to research current sunglasses trends2. Ask graphic designer to ...3. Ask writer to create report4. Review report

执行链路同样是 Step 1 text → researcher、Step 1 output, Step 2 text → graphic designer、Step 3 text → writer。

marketing manager 与 researcher、graphic designer、writer 各有一条双向连线,三个下属之间没有连线。所有信息都经过经理中转,这种层级结构是规划通信模式的常见做法。

于是「规划」和「多智能体」在这里合上了:多 agent 系统的经理,做的就是第一节那件事,把工具调用换成了 agent 调用。

5.4 一对多 vs 多对多

Deeper hierarchy:marketing manager 下面还是 researcher、graphic designer、writer 三个,但其中两个再往下带子 agent——researcher 下面挂 web researcher 和 fact checker,writer 下面挂 style writer 和 citation checker,graphic designer 这一支图上没有再分(画面如此)。层级从两层变成三层,每个叶子节点的职责窄到可以用一句话说清。

All-to-all:marketing manager、researcher、graphic designer、writer 四个节点两两之间都有双向箭头,没有中心。

这两种形状的差别在连接数上:层级结构里边数随节点数线性增长,All-to-all是 n(n−1)/2。边数决定了消息路径数,也就决定了 token 开销和你要排查的组合数。这大概也解释了为什么All-to-all「不常用」。

其他通信模式:更深层级 vs 全对全

六、结论:从规划到多智能体

回到开头那个问题:一个高度自主的 agent 怎么决定自己下一步做什么?它把工具清单和请求交给 LLM,拿回一份步骤计划,然后按序执行、逐步回填。这份计划可以是自然语言(好读,难解析)、JSON(好解析,参数传递仍要运行时处理)、或者一段代码(表达力最强,需要沙箱)。任务大到一个 agent 的一次规划装不下,就拆成多个 agent,此时要定的是通信图:能用线性就别上全对全,能让一个经理中转就别让四个节点互相喊话。

Agentic AI 课程总结回顾:五条主题


本文由 AgentWorkFlow 生成

觉得不错?给作者点个赞吧

分类:Agent

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Agentic AI(五)高度自主智能体的模式:规划与多智能体 · Yinchen 的博客