☰
一个 Markdown 文件就是一个员工 —— AgentScope-Java 多智能体编排实战(核心篇)
2026/10/24 4:12:53 网站建设 项目流程

P3:一个 Markdown 文件就是一个员工 —— AgentScope-Java 多智能体编排实战(核心篇)

系列第三篇。P1 跑通了单个 Agent,P2 让它长出了双手(工具调用),这一篇解决真正的问题:一个 Agent 干不了的活,怎么让一队 Agent 分工干。


你能获得什么

  • 理解 AgentScope-Java 的多智能体编排模型:orchestrator-workers(总编-worker 模式)
  • 学会用一个 markdown 文件声明一个子 Agent——不改一行 Java 代码就能给团队加人
  • 看懂一次完整的多 Agent 协作流程:派活 → 隔离执行 → 结果回传 → 整合简报
  • 拿到一个可直接复用的「内容团队」模板:大纲、撰稿、审校三个角色,temperature 各不相同

一、为什么单个 Agent 不够用

P2 里我们的 Agent 已经会调工具了,但你很快会遇到三个天花板:

  1. 上下文污染。让一个 Agent 又查资料、又写初稿、又挑自己的错,它的上下文会被塞满中间过程,写到后半段质量明显下滑——它一边写一边被自己前面的草稿干扰。
  2. temperature 无法兼得。写初稿要发散(温度高一点才有创造力),审校要严谨(温度低一点才稳定)。一个 Agent 只有一个温度,鱼和熊掌不可兼得。
  3. 职责不可复用。审校逻辑写死在系统提示词里,下次做个「代码审查助手」还得重写一遍。

解法很经典:orchestrator-workers 模式。一个总编 Agent 负责理解需求、拆任务、派活;每个 worker 是独立的小 Agent,有自己的系统提示词、自己的温度、自己的上下文,干完活只回传结果。这和微服务里的「API 网关 + 微服务」是同一个思想——只不过这里的「服务」是会推理的。

AgentScope-Java 把这套模式做成了开箱即用的能力,而且声明一个 worker 只需要一个 markdown 文件。

二、核心机制:agents/ 目录就是团队花名册

先说结论:在 workspace 下的agents/目录里放一个 markdown 文件,就等于雇了一个新员工。

文件分两段:YAML front matter 定义「人事信息」,正文就是系统提示词。以我们内容团队的 outliner(结构策划)为例,这是仓库里.agentscope/workspace/agents/outliner.md的原文:

--- name: outliner description: 文章结构策划。把一个写作主题展开成结构化大纲,含开头钩子、核心论点、案例建议与结尾。需要列大纲时派给它。 temperature: 0.7 --- 你是一个资深的公众号文章结构策划,擅长把一个主题拆解成有传播力的结构。 用户会给你一个写作主题,你需要输出一份可直接执行的大纲: 1. **开头钩子**:一句能让人停下来读下去的话(痛点提问 / 反常识结论 / 数据冲击,三选一) 2. **核心论点**:3 个,每个配一句展开说明 3. **案例与素材建议**:每个论点配什么代码、截图或数据 4. **结尾**:收束方式 + 下期预告钩子 要求:只输出大纲本身,不要写正文,不要多余解释。

front matter 里各字段的门道:

字段作用注意
name员工工号,总编派活时的「收件人」必填,目录内唯一
description岗位 JD——总编就是靠它决定「这活该派给谁」必填,且要写清「什么时候派给它」
temperature这个角色的发散程度按职责定,见下文
tools该角色可用的工具名列表按名引用父级 Toolkit 注册的工具,不写则没有工具

description是最容易被低估的字段。总编自己不认识这些 worker,它选人完全依赖 description——写得含糊(比如只写「帮我干活」),总编就会乱派活。正确姿势是把 description 当 JD 写:职责 + 触发条件。上面 outliner 的描述里「需要列大纲时派给它」就是在给总编下路由规则。

再看 writer 的声明,注意它的tools:

--- name: writer description: 撰稿人。根据大纲或要点撰写公众号文章初稿,风格专业但易读,善用短段落和小标题。需要写初稿时派给它。 temperature: 0.8 tools: - save_draft --- 你是一个技术公众号的撰稿人,文风专业但不枯燥:短段落、多小标题、关键处给代码或例子。 …… 如果用户要求「保存」,调用 save_draft 工具把初稿存入草稿箱,标题取文章标题。

save_draft是 P2 里我们用@Tool写的自定义工具(存在草稿箱)。父子工具打通的方式:在总编的 Toolkit 里注册一次,子 Agent 在声明里按名引用。也就是工具只写一遍,全团队共用。

三个角色的 temperature 各不相同,这是我们刻意的设计:

角色temperature理由
outliner0.7出大纲要点创意,但不能太飘
writer0.8写初稿最需要发散
reviewer0.3审校要稳定、可复现,温度高了会「看走眼」

这就是第二节说的「一个 Agent 无法兼得 temperature」的解法——把不同温度拆给不同角色。

三、总编怎么搭:一个 builder 搞定

worker 是 markdown 声明的,总编则在 Java 里构建。完整代码来自仓库的MultiAgentDemo.java:

// 1. 父级 Toolkit:注册自定义工具,子 Agent 通过声明中的 tools 列表按名引用Toolkittoolkit=newToolkit();toolkit.registerTool(newDraftTools(Paths.get(".agentscope/workspace/drafts")));// 2. 总编:workspace 下的 agents/ 目录即「团队花名册」HarnessAgenteditorInChief=HarnessAgent.builder().name("editor-in-chief").sysPrompt(""" 你是一个公众号内容团队的总编,手下有三员干将:-outliner:结构策划,负责出大纲-writer:撰稿人,负责写初稿(可把成品存进草稿箱)-reviewer:审校,负责挑错 工作流:接到需求后,先派 outliner 出大纲;大纲满意后派 writer \ 写初稿;初稿完成后派 reviewer 质检。每一步必须等上一步结果回来再派下一棒。\ 最终把各环节结论整合成一份简报回复用户。""").model(ModelFactory.build()).workspace(Paths.get(".agentscope/workspace")).toolkit(toolkit).compaction(CompactionConfig.builder().triggerMessages(30).keepMessages(10).build()).build();

四个关键点:

  1. .workspace()指向的目录里如果存在agents/子目录,框架运行时会自动扫描加载,不需要手动注册任何子 Agent。这就是「声明式」的含义:加一个新员工 = 加一个 md 文件,Java 代码零改动。
  2. 总编的 sysPrompt 里写工作流,而不是写每个环节怎么做——每个环节的专业知识在各自 worker 的提示词里。总编只管「谁先谁后、等上一步结果」。
  3. .compaction()配了上下文压缩:超过 30 条消息时压缩只保留最近 10 条。整场编排下来事件很多,这个配置防止长会话把上下文撑爆(P4 会专门展开)。
  4. 没有任何一行代码涉及 agent_spawn 等编排工具——框架默认给 HarnessAgent 自带了一整套编排工具(agent_spawn/agent_send/agent_list,连同文件读写、memory、session 系列共 21 个内置工具),总编开箱就会「派活」。

四、跑起来:一次完整的三棒接力

给总编发一个需求:

UserMessagemessage=newUserMessage("选题:《Java 开发者如何零 Python 基础搭建多智能体系统》。"+"请按团队流程走一遍:出大纲 → 写初稿 → 审校,最后给我一份执行简报。");

用 P1 讲过的streamEvents()监听事件流,把「派活」和「回传」的过程打印出来:

editorInChief.streamEvents(message,ctx).doOnNext(event->{switch(event.getType()){caseTEXT_BLOCK_DELTA->System.out.print(((TextBlockDeltaEvent)event).getDelta());caseTOOL_CALL_START->System.out.println("\n[委派] "+((ToolCallStartEvent)event).getToolCallName()+" ← 总编正在派活");caseTOOL_RESULT_END->System.out.println("\n[子Agent回传] "+((ToolResultEndEvent)event).getToolCallName());default->{}}}).blockLast();

运行命令:

mvn-qexec:java-Dexec.mainClass="com.mk.agentscope.agent.MultiAgentDemo"

真实运行的控制台输出(节选)长这样:

=== 多智能体编排演示 === 子 Agent 声明目录:.agentscope/workspace/agents/ [委派] agent_spawn ← 总编正在派活 [子Agent回传] agent_spawn: 一、开头钩子:「你的同事在用 Python 写 Agent,而你还在写 CRUD?」…… [委派] agent_spawn ← 总编正在派活 [子Agent回传] agent_spawn: # 让 Agent 长出双手:……(初稿正文) 好,我用 save_draft 存一下草稿。 [委派] agent_spawn ← 总编正在派活 [子Agent回传] agent_spawn: P0 必须改:第 2 节「Schema 自动生成」表述不严谨…… 【执行简报】大纲已确认;初稿已存入草稿箱(drafts/p3-xxx.md); 审校提出 1 个 P0 问题、2 个 P1 建议,已附修改清单。

注意三个细节:

  • 三次[委派]都是agent_spawn——在总编眼里,派活就是一次普通的工具调用。多智能体编排没有引入任何新的执行模型,就是 ReAct 循环里多了一种「 spawning 子 Agent」的工具。这个设计非常干净。
  • writer 回传里出现了save_draft调用——子 Agent 在隔离上下文里自主干活,中途调用自己的工具,总编全程无感,只拿到最终结果。
  • 最后的「执行简报」是总编自己整合的——它不转发原始长文,而是汇总各环节结论。这正是 orchestrator 的价值:信息向上收敛。

五、背后发生了什么

把这次运行拆开看,一次三棒接力的完整时序:

用户需求 │ ▼ 总编(editor-in-chief,ReAct 循环) │ ① 推理:这活该 outliner 干 → 调 agent_spawn("outliner", 任务) │ └─ outliner(temp 0.7,隔离上下文)→ 大纲文本回传 │ ② 推理:大纲 OK → 调 agent_spawn("writer", 大纲) │ └─ writer(temp 0.8,隔离上下文)→ 初稿 + 调 save_draft 落盘 │ ③ 推理:初稿 OK → 调 agent_spawn("reviewer", 初稿) │ └─ reviewer(temp 0.3,隔离上下文)→ 分级问题清单回传 │ ④ 推理:信息齐了 → 整合执行简报 ▼ 最终简报返回用户

几个值得说的机制:

  • 隔离上下文:每个 worker 的对话发生在自己的上下文里,大纲讨论的 30 条消息不会进入 writer 的视野,writer 只拿到「大纲成品」。这就是解决「上下文污染」的方式。
  • 运行期加载:agents/目录的扫描发生在首次推理时(由框架的中间件完成),而不是 builder 阶段。好处是声明文件可以随时增删,下一轮对话即刻生效——「团队花名册」是活的。
  • 想验证声明文件写得对不对,不用起整个 Agent:AgentSpecLoader.loadFromDirectory(agentsDir, workspace)可以单独调用,直接拿到解析后的声明对象做单测。CI 里加一条检查,markdown 写错了在构建期就暴露。

六、四条编排设计经验

  1. worker 的 description 按 JD 写:职责 + 触发条件。它是总编派活的唯一依据。
  2. 专业知识下沉到 worker,工作流上浮到总编。两边别写串:总编提示词里出现「大纲要包含钩子」就是越权。
  3. temperature 按职责分配:创意型角色 0.7~0.9,质检型角色 0.1~0.3。这是多 Agent 相对单 Agent 最实在的收益之一。
  4. 控制回传体积:worker 结果别原样倾倒给总编,让 worker 自己先收敛(reviewer 输出分级清单而不是全文批注),或像 Demo 里那样对回传做截断预览。回传太长会快速吃掉总编的上下文。

七、踩坑记录

坑 1:静态声明目录是agents/,不是subagents/。
我们在搭建时先按直觉在 workspace 下建了subagents/,死活不生效。翻框架源码常量(WorkspaceConstants.AGENTS_DIR = "agents")才发现:静态声明的目录名是agents/,subagents/是运行期动态生成子 Agent 用的另一个机制。网上搜到的早期教程多写的是后者,注意区分。

坑 2:front matter 里description漏写不报错,但总编会乱派活。
声明文件解析对缺失字段相当宽容,漏了description也能加载成功。但总编路由全靠它——缺失后可能出现「该派 reviewer 的活派给了 writer」。建议用AgentSpecLoader写单测兜底。

坑 3:子 Agent 不会自动继承父级所有工具。
我们最初以为 Toolkit 注册的工具全团队默认可用,实际必须在子 Agent 声明的tools列表里按名引用。writer 最初没配tools,让它「存草稿」时它只能干瞪眼输出文本。权限最小化是设计使然,但第一次很容易踩。

八、下一篇预告

团队跑起来后,新的问题是持久与恢复:会话能不能断点续传?上下文太长怎么办?记忆怎么分层沉淀?P4 我们把JsonFileAgentStateStore换成 Redis,聊聊 AgentScope-Java 的工程化底盘——会话持久化、上下文压缩与分层记忆。


仓库地址:https://github.com/525128633/agentScope
系列文章:P1 上手实测 · P2 工具调用实战 · P3 本篇


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询