系列第 16 篇 · 实战篇
前 15 篇把 Agent 的单点能力逐个拆开讲解:从跑通第一个 Agent、工具调用、结构化输出,到 PlanMode、记忆与 RAG,再到多 Agent、MCP、A2A,最后是 HarnessAgent、Workspace、Context 与权限容错可观测。但"零件齐全"不等于"装好能跑"------本篇要把它们串成一台真正的生产 Agent。
笔者从事软件行业多年,见过太多企业信息化建设的"拼凑"过程,拿传统零售商来说,ERP、CRM、WMS、电商中台由不同供应商拼装而成,数据模型和接口各说各话,要把它们打通、协同起来格外费劲。而这,恰恰是 Agent 最值得落地去解决的一类场景。
先介绍本文相关的两个真实零售场景,后续的实战将围绕这两个场景展开:
-
场景一 · 52 周 MD 周计划(以"周"为尺度的经营规划):店长一句话提出下一季度MD 诉求,Agent 以历史销量(同周同期、近周趋势)为实证基础,结合顾客生活节奏/季节/节气/节日/场景(辅以天气、客群、竞品情报),选出重点商品、产出周经营建议,并直接落实促销、生成文案、圈选会员。
-
场景二 · 智慧出清(以"日"为尺度的经营执行):每天傍晚闭店前的几个小时,是生鲜出清的主战场,Agent 需要按当晚天气、当日实时销售,并对照该品类历史销量(同天气/同时段去化基线),动态定出清折扣力度与最佳出清时段,落价前走人工确认。
一、项目背景与两个场景
1.1 零售信息化的"拼凑"现状
前面说的"拼凑",带来的不是某个单点问题,而是系统间天然异构、割裂:数据模型不一、接口协议各异、口径各说各话。门店想要"基于全局经营数据做一件小事"(比如选本周重点商品、定今晚出清折扣),都要先跨好几套系统取数、对齐、再判断。这件事人工做费时且靠经验,正好是 Agent + MCP 统一接入 + Harness 稳定编排最能发挥价值的地方。
1.2 场景一:52 周 MD 周计划
52 周 MD是生鲜超市以"年"为完整时间跨度的经营活动管理方法:把 1 年 52 周作为完整时间轴,根据顾客生活节奏、季节、节气、节日、场景,以周为单位推进执行,提前规划每周主推商品、陈列、促销、卖场主题,周周打造销售小高潮;采购‑营运‑营销‑门店全部门协同落地,并且每周复盘迭代优化。在这条时间轴上,重点商品的选择同样以时间为节点,结合顾客生活节奏、季节、节气、节日、场景等因素,选出既贴合消费者当下需求、又满足企业经营目标的商品,再据此安排一周的陈列、促销与营销。
一份 52 周 MD 周计划长这样:
powershell
本周(2026-W34,8 月第三周)52 周 MD
主轴:立秋末伏 · 开学前备货
历史销量参考:莲藕近 4 周周销 120kg、绿豆 90kg;去年同期(2025-W34)莲藕 +18%、绿豆 +12%------需求稳中有升
重点商品:
1. 应季鲜食:凉拌藕带、冰镇绿豆沙原料(莲藕 / 绿豆)------ 契合"立秋吃藕"习俗 + 高温消暑
2. 开学季组合:半成品早餐(馒头 / 包点)+ 学生奶 ------ 临近开学,社区家庭客群
3. 社区中老年:低糖杂粮、时令梨(润燥)------ 客群画像驱动
经营建议:
- 陈列:入口端架做"开学早餐 + 消暑"主题堆头
- 促销:莲藕 / 绿豆组合 8.8 折,学生奶第二件半价
- 社群:周三推"立秋养生"文案,圈选 25--45 岁家庭会员
1.3 场景二:智慧出清
生鲜保质期短,闭店前几小时把卖不动的货出清掉,是每家超市每天的"硬仗"。难点在于:折扣定深了亏毛利,定浅了卖不完报损;时段定早了扰乱正价,定晚了来不及。智慧出清的做法是------每天傍晚闭店前数小时,依据当晚天气(影响客流)、当日实时销售(剩余库存与动销率),并对照该品类历史销量(同天气 / 同时段的常规去化水平)设定合理去化基线,动态算出出清折扣力度与最佳出清时间段,落价前交店长确认。
一份出清方案长这样:
powershell
闭店前 3 小时出清方案(当日 19:00 触发)
天气:阵雨转凉,晚高峰客流回落
实时动销:叶菜售出率 62%(剩 38kg)、鲜奶售出率 55%(剩 120 盒)
历史参考:叶菜近 7 日同天气傍晚 21:00 前平均去化 78%,今晚 21:00 前预计仅能去化 55% → 需加深折扣
建议:
- 叶菜:20:30 起 7 折,21:00(闭店前 1h)起 5 折清完
- 鲜奶:20:00 起第二件 1 元,21:00 起 6 折
预估:较店长经验出清,动销去化 +12%,报损 -15%
1.4 两个场景的关系
| 维度 | 场景一 · 52 周 MD(周) | 场景二 · 智慧出清(日) | | --- | --- | --- | | 经营尺度 | 周计划,事前规划 | 日执行,事中应变 | | 触发方式 | 主动提出 / 按周定时 | 每天傍晚定时 | | 核心输入 | 历史销量 / 行事历 / 节气 / 天气 / 客群 / 竞品 | 当晚天气 / 历史销量(同天气同时段基线) / 当日实时销售 / 闭店倒计时 | | 核心产出 | 重点商品 + 经营建议 + 促销 / 文案 / 圈选 | 出清折扣力度 + 出清时段 + 降价 | | 是否写操作 | 是(创建促销 / 发券) | 是(执行降价) | | 都需要 | 意图识别、MCP 取数、权限确认、可观测 | 意图识别、MCP 取数、权限确认、可观测 |
二、遗留问题与解决思路
| 遗留疑难问题 | 根因 | AgentScope 解法(由 Harness 统一编排) | | --- | --- | --- | | 重点商品选品靠经验、常脱节时令 | 选品凭经验,难系统结合节气 / 天气 / 客群 / 竞品 | MCP 取行事历 / 节气 / 天气 / 会员 / 竞品情报,Agent 生成 52 周 MD 周计划 | | 促销活动效果难评估 | 多系统数据割裂,口径不一 | MCP 统一取数,结构化输出算 ROI,LLM 生成归因报告 | | 会员触达不精准 | 标签分散、画像陈旧 | Agent 检索长期记忆 + 生成个性化文案并圈选人群 | | 营销内容产出慢、落地难 | 文案 / 促销方案靠人工,跨系统创建费时 | 多 Agent 生成文案与方案,执行专员经 HITL 创建促销、发放优惠券 | | 生鲜出清靠拍脑袋、闭店前手忙脚乱 | 折扣与时段凭经验,不看实时天气与销售 | MCP 取天气 + 当日实时销售,Agent 动态定出清折扣与时段,落价前走 HITL 确认 | | 数据权限混乱 | 各系统自建权限,越权风险 | PermissionEngine 四态 + 自定义 Middleware 工具级拦截 | | 运营数据口径不一致 | 指标定义分散 | 口径沉淀为统一 @Tool 指标 API,Agent 只调用、不重算 |
三、总体架构设计
3.1 分层架构
采用分层 + 模块化架构:接入层把请求交给 HarnessAgent 做应用编排,MCP 作为基础设施集成协议。
swift
┌─────────────────────────────────────────────────────────────┐
│ 接入层 / Interface │
│ REST API | WebSocket
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 应用编排层 / Application(HarnessAgent 运行层) │
│ HarnessAgent(编排总控) │
│ ├─ SubagentDeclaration:意图/选品规划/营销执行/出清专/权限 │
│ ├─ Toolkit:@Tool 业务工具 + 注册自零售 MCP Server 的工具 │
│ ├─ Middleware:权限拦截(RBAC/ABAC)+ OtelTracing 观测 │
│ └─ Plan Mode / Memory / Workspace / Sandbox │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 业务服务层 / Service(@Tool 工具实现体,被 AgentScope 编排调用 │
│ 选品 / 营销 / 会员 等业务服务实现(经由 @Tool 暴露) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 基础设施层 / Infrastructure │
│ AgentScope-Java 2.0 Runtime | MCP Client/Server │
│ OpenTelemetry | Micrometer | Redis | DB │
│ Legacy ERP/WMS/POS/CRM 适配器 │
└─────────────────────────────────────────────────────────────┘
3.2 AgentScope-Java 2.0 提供了哪些支撑
-
Agent 运行时:
HarnessAgent作为编排总控,内部用无状态的ReActAgent跑推理循环;多 Agent 用SubagentDeclaration声明式编排。 -
LLM 接入:字符串模型 +
ModelRegistry,如"dashscope:qwen-max",自动读环境变量里的 Key。 -
工具抽象:
@Tool+Toolkit注册业务工具;结构化输出用agent.call(msgs, Class, ctx)。 -
MCP 支持:作为 MCP Client 调用外部 MCP Server(
McpClientBuilder/workspace/tools.json);也可把内部能力暴露为 MCP Server。 -
可观测扩展点:
Middleware通道(5 个钩子:onAgent/onReasoning/onActing/onModelCall/onSystemPrompt)与内置OtelTracingMiddleware,外加streamEvents()的 28 种类型化事件流。 -
与业务系统集成:自定义
@Tool、自定义Middleware、自定义Memory都通过标准扩展点接入,确定性业务逻辑留在工具实现里。
3.3 编排总控:把多智能体、工具、中间件拼起来
使用HarnessAgent.builder() 把子 Agent、工具集(含 MCP)、中间件一次性挂上去,推理循环、多 Agent 调度、权限与观测全由框架接管。先声明五个专员(subAgent):
swift
import io.agentscope.harness.HarnessAgent;
import io.agentscope.harness.agent.subagent.SubagentDeclaration;
// 意图专员:拿到结构化意图,决定走哪条业务线
SubagentDeclaration intentAgent = SubagentDeclaration.builder()
.name("intent_agent")
.description("意图专员:把用户问句识别为 md_plan(规划)/ md_execute(落实)/ clearance(出清)/ query(问询)之一并抽取槽位")
.inlineAgentsBody("你是意图专员,只输出结构化意图与槽位,不展开业务计算。")
.build();
// 选品规划专员:把复合 MD 诉求拆成有序步骤(如 选品规划 → 营销方案 → 落实执行)
SubagentDeclaration mdPlannerAgent = SubagentDeclaration.builder()
.name("md_planner_agent")
.description("选品规划专员:以历史销量(同周同期、近周趋势)为实证基础,结合顾客生活节奏/季节/节气/节日/场景(辅以天气、客群、竞品情报),选出本周重点商品并产出 52 周 MD 周计划")
.inlineAgentsBody("你是选品规划专员,先取历史销量做需求基线(同周同期、近周趋势),再结合顾客生活节奏/季节/节气/节日/场景选重点商品,给出一周经营建议。")
.build();
// 营销执行专员:持有 MCP + @Tool 工具集,生成文案并落地执行
SubagentDeclaration marketingAgent = SubagentDeclaration.builder()
.name("marketing_agent")
.description("营销执行专员:调用 weather/calendar/member/competitor/promotion 等 MCP 工具,生成营销文案并创建促销、发放优惠券")
.inlineAgentsBody("你是营销执行专员,严格按步骤调用工具生成文案、创建促销与发券,不臆造数据。")
.build();
// 出清专员:每天傍晚按天气与当日实时销售,动态定出生鲜出清折扣力度与最佳出清时间段
SubagentDeclaration clearanceAgent = SubagentDeclaration.builder()
.name("clearance_agent")
.description("出清专员:调取 weather.query 与当日实时销售,对照该品类历史销量(同天气/同时段去化基线),结合闭店倒计时,给出生鲜出清折扣与最佳出清时段建议")
.inlineAgentsBody("你是出清专员,根据天气、实时动销、历史销量基线(同天气/同时段)与闭店剩余时间,给出折扣力度与出清时机,不臆造数据。")
.build();
// 权限专员:调用前做 RBAC/ABAC 校验,高危操作发起 HITL 确认
SubagentDeclaration guardAgent = SubagentDeclaration.builder()
.name("guard_agent")
.description("权限专员:对工具调用做权限与数据范围校验,越权或高危时要求人工确认")
.inlineAgentsBody("你是权限专员,任何调用前先校验角色与数据范围。")
.build();
五个专员声明好后,用总控 Agent 把它们和工具集、中间件搭建好整个经营助手的完整骨架:
java
import io.agentscope.harness.HarnessAgent;
import io.agentscope.core.tool.Toolkit;
import io.agentscope.core.middleware.MiddlewareBase;
import io.agentscope.core.tracing.OtelTracingMiddleware;
import io.agentscope.core.memory.MemoryConfig;
import io.agentscope.core.context.CompactionConfig;
import io.agentscope.core.context.ToolResultEvictionConfig;
import java.nio.file.Path;
import java.util.List;
HarnessAgent assistant = HarnessAgent.builder()
.name("store-ops-assistant") // 总控(主编)
.model("dashscope:qwen-max")
.workspace(Path.of("./store-ops-workspace"))
.sysPrompt("""
你是门店经营智能助手的 52 周 MD 总控。流程:
1. agent_spawn intent_agent 识别意图(规划 or 落实 or 出清)与槽位;
2. agent_spawn md_planner_agent 先取历史销量做实证基线(同周同期、近周趋势),再结合顾客生活节奏/季节/节气/节日/场景(辅以天气、客群、竞品情报),选重点商品并出周计划;
3. agent_spawn marketing_agent 生成营销文案与促销方案,并调用工具落地;
4. 创建促销/发券等高危操作交给 guard_agent 校验,必要时发起人工确认;
5. 每天傍晚闭店前数小时,agent_spawn clearance_agent 按天气与实时销售动态定出清折扣与时段,落价前交 guard_agent 校验;
6. 汇总 52 周 MD 周计划与出清执行结果,输出可观测链路。
""")
.subagent(intentAgent).subagent(mdPlannerAgent) // 多智能体声明式编排
.subagent(marketingAgent).subagent(clearanceAgent)
.subagent(guardAgent)
.toolkit(toolkit) // 已注册 MCP 工具 + @Tool 业务工具
.middlewares(List.of(
new PermissionMiddleware(rbacService), // 权限拦截
new OtelTracingMiddleware())) // 可观测
.memory(MemoryConfig.builder().build()) // 长期记忆
.compaction(CompactionConfig.builder()
.triggerMessages(30).keepMessages(10)
.truncateArgs(CompactionConfig.TruncateArgsConfig.builder()
.maxArgLength(2000).build()).build()) // 上下文工程
.toolResultEviction(ToolResultEvictionConfig.defaults()) // 大结果卸载
.build();
四、代码细化:两个场景的落地实现
第三章(3.3)已经把 HarnessAgent 总控、五个子 Agent 和 MCP 通路搭好,本章 4.4 给出 Harness 的装配代码,第五章再用运行时视角讲清一次请求在 Harness 里怎么流转。这一节不再罗列"能力有哪些",而是把两个场景真正写进代码:4.1、4.2 分别是 52 周 MD 与智慧出清的业务落地,4.3 是两个场景共用的能力模块(意图 / MCP / 权限 / 可观测 / 记忆)代码。读这段代码,你看到的就是门店助手每天在跑的东西------"为什么这么做"都写在注释里。
4.1 场景一:52 周 MD 周计划代码落地
一句话提出下季度 MD 诉求,Agent 要:识别意图 → 取历史销量做实证基线 → 结合节气 / 天气 / 客群 / 竞品选品 → 出周计划 → 落地促销与发券(人工确认)。业务落地代码如下:
cs
// 场景一入口:店长提交 52 周 MD 周计划诉求
public Mono planWeeklyMd(String utterance, SessionContext ctx) {
// ① 意图识别(三层漏斗 + 结构化输出,见 4.3):把自然语言变成强类型意图,不让 LLM 自由发挥
IntentResult intent = intentAgent.call(
List.of(new UserMessage("user", utterance)),
IntentResult.class,
RuntimeContext.builder().sessionId(ctx.sid).userId(ctx.uid).build())
.block().getStructuredData(IntentResult.class);
// 预期 intent = { intent:"md_plan", slots:{ week:"2026-W34", season:"立秋",
// weather:"高温", segment:"社区中老年", event:"开学季" } }
// ② 选品规划:交给 md_planner_agent,它在 4.3 注册好的 MCP 通路上取数
// ------ 以「历史销量」为实证基线,不让模型凭空编选品;权限/数据范围由 PermissionMiddleware 在 onActing 拦截(见 4.3)
return mdPlannerAgent.call(
List.of(new UserMessage("user",
"为 " + intent.slots.get("week") + " 做 52 周 MD:" +
"先 sales.history 取同周同期+近周趋势,再 calendar.solar_terms 取节气、" +
"weather.query 取天气、member.segment 取客群、competitor.intel 取竞品," +
"最后 md.weekly_plan 出周计划")),
MdPlanReply.class,
RuntimeContext.builder().sessionId(ctx.sid).userId(ctx.uid).build())
.flatMap(reply -> {
MdPlanReply r = reply.getStructuredData(MdPlanReply.class);
// ③ 落地执行:营销执行专员创建促销 + 发券,属于高危写操作
// tools.json 已把 promotion.create / coupon.issue 标 ASK,框架调用前暂停并抛
// REQUIRE_USER_CONFIRM,前端用 getReplyId() 回传批准(见 4.3 权限 / 可观测)
marketingAgent.call(List.of(new UserMessage("user",
"基于周计划创建促销、生成社群文案、圈选会员:" + r.toBrief())),
Void.class, RuntimeContext.builder()
.sessionId(ctx.sid).userId(ctx.uid).build()).block();
// ④ 可观测:整条链路用 streamEvents 把工具调用/人工审批/文本增量实时流出(见 4.3)
return Mono.just(r);
});
}
选品工具本身用 @Tool 暴露,历史销量作为输入:
typescript
public class MdPlanningTools {
private final MdPlanningService mdService;
public MdPlanningTools(MdPlanningService svc) { this.mdService = svc; }
@Tool(name = "md_weekly_plan",
description = "以历史销量(同周同期、近周趋势)为实证基础,结合时间节点(顾客生活节奏/季节/节气/节日/场景),为指定周生成 52 周 MD 重点商品与经营建议")
public MdWeeklyPlanResult weeklyPlan(
@ToolParam(name = "week", description = "ISO 周,如 '2026-W34'") String week,
// 历史销量:指定周近 N 周销量与去年同期,用于实证选品与需求基线 ------ 这是 52 周 MD 的"地基"
@ToolParam(name = "historySales", description = "历史销量:同周同期/近周趋势,用于实证选品与需求基线") String historySales,
@ToolParam(name = "context", description = "上下文摘要:顾客生活节奏/季节/节气/节日/场景") String context) {
return mdService.plan(new MdPlanCommand(week, historySales, context));
}
}
场"确定性"来自两点:选品有
sales.history实证、落价有REQUIRE_USER_CONFIRM把关。
4.2 场景二:智慧出清代码落地
每天傍晚闭店前的出清是"定时触发 + 实时应变":到点由调度器拉起,按当晚天气、当日实时销售,并对照该品类历史销量(同天气 / 同时段去化基线)动态定折扣与时段,落价前交店长确认。
cs
// 场景二入口:调度器在闭店前 3 小时触发(cron / 调度框架),sessionId 固定便于崩溃恢复(见 4.3 记忆)
public Mono runClearance(SessionContext ctx) {
// ① 意图识别为 clearance(复用 4.3 同款三层漏斗),这里直接进入出清专员
return clearanceAgent.call(
List.of(new UserMessage("user",
"执行今晚出清:weather.query 取当晚天气,sales.history 取该品类同天气/同时段去化基线," +
"sales.realtime 取当日实时动销,结合闭店倒计时算 出清折扣力度 与 最佳出清时段")),
ClearanceReply.class,
RuntimeContext.builder()
.sessionId("clearance-daily") // 固定 sessionId:日清任务各自可恢复(保障三,见 4.3)
.userId(ctx.uid).build())
.flatMap(reply -> {
ClearanceReply r = reply.getStructuredData(ClearanceReply.class);
// ② 落价执行:clearance.apply 是高危写操作,tools.json 标 ASK,框架抛 REQUIRE_USER_CONFIRM,
// 等待店长在终端/App 确认(见 4.3 权限);折扣过深(< 6 折)时 PermissionMiddleware 还会要求区域经理审批
clearanceAgent.call(List.of(new UserMessage("user",
"按建议执行落价:" + r.toBrief())),
Void.class, RuntimeContext.builder()
.sessionId("clearance-daily").userId(ctx.uid).build()).block();
// ③ 可观测:出清全程 streamEvents 流出,落 AgentStateStore 可恢复(见 4.3)
return Mono.just(r);
});
}
出清的落价与建议同样用 @Tool 暴露,核心是"历史基线 + 实时动销 + 闭店倒计时"三者的差值决策:
typescript
public class ClearanceTools {
private final ClearanceService clearanceService;
public ClearanceTools(ClearanceService svc) { this.clearanceService = svc; }
@Tool(name = "clearance_suggest",
description = "结合当晚天气、当日实时动销与历史销量(同天气/同时段去化基线)、闭店倒计时,给出生鲜出清折扣力度与最佳出清时段")
public ClearanceSuggestion suggest(
@ToolParam(name = "weather", description = "当晚天气,影响晚高峰客流") String weather,
@ToolParam(name = "realtimeSales", description = "当日实时动销:剩余库存与售出率") String realtimeSales,
// 历史销量作为同天气/同时段的常规去化基线,用来判断"今晚比平时难卖多少"
@ToolParam(name = "historyBaseline", description = "历史销量同天气/同时段去化基线") String historyBaseline,
@ToolParam(name = "minutesToClose", description = "距闭店剩余分钟数") int minutesToClose) {
return clearanceService.suggest(new ClearanceCommand(weather, realtimeSales, historyBaseline, minutesToClose));
}
@Tool(name = "clearance_apply",
description = "执行出清落价(高危写操作,需店长确认;折扣低于 6 折需区域经理审批)")
public void apply(
@ToolParam(name = "discount", description = "出清折扣,如 0.7 表示 7 折") double discount,
@ToolParam(name = "startAt", description = "最佳出清时段,ISO 时间") String startAt) {
clearanceService.apply(new ApplyCommand(discount, startAt));
}
}
场景二与场景一共用同一套 Harness 机箱:意图、取数(MCP)、权限(PermissionMiddleware +
REQUIRE_USER_CONFIRM)、可观测(OtelTracingMiddleware +streamEvents)、记忆(长期MEMORY.md+ 短期AgentStateStore)。
4.3 共通能力模块
① 意图识别(三层漏斗 + 结构化输出)------规则 / 小模型在应用层,只有第三层走 AgentScope:
java
// 应用层:意图识别结果(结构化输出目标类)
public class IntentResult {
@JsonPropertyDescription("意图类型:md_plan / md_execute / clearance / query / unknown")
public String intent;
@JsonPropertyDescription("抽取出的关键槽位,如门店ID、商品SKU、时间范围")
public Map slots;
@JsonPropertyDescription("模型自评置信度 0~1")
public double confidence;
}
// 第三层:用 AgentScope-Java 2.0 的结构化输出把自然语言变成强类型对象
public class LlmIntentRecognizer implements IntentRecognizer {
private final HarnessAgent intentAgent;
public LlmIntentRecognizer(HarnessAgent intentAgent) { this.intentAgent = intentAgent; }
@Override
public IntentRecognitionResult recognize(String userInput, SessionContext context) {
Msg msg = intentAgent.call(
List.of(new UserMessage("user", userInput)),
IntentResult.class,
RuntimeContext.builder()
.sessionId(context.getSessionId())
.userId(context.getUserId())
.build())
.block();
IntentResult r = msg.getStructuredData(IntentResult.class);
return IntentRecognitionResult.of(r.intent, r.slots, r.confidence);
}
}
② MCP 工具接入------workspace/tools.json 声明式接入零售内部 MCP Server,启动即自动注册(更细控制可用 McpClientBuilder)。两个场景的工具都挂在同一条通路上:
css
{
"mcpServers": {
"retail-gateway": {
"command": "npx",
"args": ["-y", "@retail/mcp-server"],
"env": { "RETAIL_API_TOKEN": "${env:RETAIL_API_TOKEN}" }
}
}
}
java
Agent Tool Call → McpClientWrapper → MCP Server(零售内部工具网关)
├─ calendar.solar_terms 取节气/行事历 (场景一·选品输入)
├─ weather.query 取天气 (场景一/二 共用)
├─ member.segment 取客群标签 (场景一·营销圈选)
├─ competitor.intel 取竞品情报 (场景一·选品输入)
├─ md.weekly_plan 选重点商品+出周计划 (场景一·核心)
├─ sales.realtime 取当日实时动销 (场景二·出清输入)
├─ sales.history 取历史销量(同周同期/近周趋势/同天气同时段基线) (场景一·选品实证 / 场景二·出清基线)
├─ clearance.suggest 出清建议(折扣+时段) (场景二·核心)
├─ promotion.create 创建促销(高危写) (场景一·落实)
├─ clearance.apply 执行出清落价(高危写) (场景二·落实)
└─ coupon.issue 发放优惠券(高危写) (场景一·落实)
③ 权限控制(RBAC + ABAC + 数据权限)------粗粒度"能不能调"交给 PermissionEngine(声明式、含 HITL),细粒度"按什么规则、查哪个范围"在 onActing 钩子里落地。
cs
@Component
public class PermissionMiddleware extends MiddlewareBase {
private final RbacService rbacService;
public PermissionMiddleware(RbacService rbacService) { this.rbacService = rbacService; }
@Override
public Mono onActing(MiddlewareContext ctx, HookEvent event) {
UserPrincipal user = ctx.runtime().getUser();
String toolName = currentToolName(event);
// 1) RBAC 检查
if (!rbacService.hasPermission(user.getRole(), toolName)) {
return Mono.error(new PermissionDeniedException("无权限调用工具: " + toolName));
}
// 2) ABAC:创建促销/发券需店长或区域经理,单券面额超 50 元需二次确认
if ("promotion.create".equals(toolName) || "coupon.issue".equals(toolName)) {
BigDecimal faceValue = toolArg(event, "faceValue", BigDecimal.class);
if (faceValue != null && faceValue.compareTo(new BigDecimal("50")) > 0
&& !"REGION_MANAGER".equals(user.getRole())) {
return Mono.error(new PermissionDeniedException("券面额超过阈值,需区域经理审批"));
}
}
// 出清折扣过深(如低于 6 折)需区域经理审批
if ("clearance.apply".equals(toolName)) {
BigDecimal discount = toolArg(event, "discount", BigDecimal.class);
if (discount != null && discount.compareTo(new BigDecimal("0.6")) < 0
&& !"REGION_MANAGER".equals(user.getRole())) {
return Mono.error(new PermissionDeniedException("出清折扣低于 6 折,需区域经理审批"));
}
}
// 3) 数据权限注入:店长只能管本门店(写入 RuntimeContext,供工具实现层读取)
if ("md.weekly_plan".equals(toolName) || "promotion.create".equals(toolName)
|| "clearance.apply".equals(toolName)) {
ctx.runtime().setDataScope(new StoreDataScope(user.getStoreId()));
}
return Mono.just(event);
}
}
④ 可观测性(追踪 + 类型化事件流)------OtelTracingMiddleware 一行接入 OTel;streamEvents() 把模型调用 / 工具执行 / 人工审批作为类型化事件实时流出,两个场景的链路追踪与审计都靠它:
cs
HarnessAgent agent = HarnessAgent.builder()
.name("store-ops-assistant")
.model("dashscope:qwen-max")
.workspace(Path.of("./store-ops-workspace"))
.middlewares(List.of(
new PermissionMiddleware(rbacService), // ③ 权限拦截
new OtelTracingMiddleware())) // 可观测:一行接入 OTel
.build();
// 类型化事件流:前端实时跟进、埋点统计一次订阅全覆盖
agent.streamEvents(
List.of(new UserMessage("user",
"帮我做本周(8 月第三周)的 52 周 MD:立秋刚过、高温、社区中老年为主、临近开学季,选出重点商品;定稿后创建促销、生成社群文案、圈选会员。")),
RuntimeContext.builder().sessionId(sid).userId(uid).build())
.doOnNext(event -> {
switch (event.type()) {
case AGENT_START: /* 一次完整回复开始,全局进度 */ break;
case TEXT_BLOCK_DELTA: /* 文本逐字增量,驱动流式 UI */ break;
case TOOL_CALL_START: /* 工具调用开始,可观测 */ break;
case TOOL_RESULT_END: /* 工具返回结果 */ break;
case REQUIRE_USER_CONFIRM: /* 需要人工审批(HITL),触发权限确认 */ break;
case MODEL_CALL_START:
case MODEL_CALL_END: /* 模型调用耗时监控 */ break;
case AGENT_END: /* 回复结束 */ break;
}
})
.blockLast();
⑤ 记忆与上下文工程------长期记忆落 MEMORY.md、短期会话态按 sessionId 接 AgentStateStore(Redis)(场景二固定 clearance-daily 让日清各自可恢复);上下文有界靠 compaction + toolResultEviction:
java
HarnessAgent agent = HarnessAgent.builder()
.name("store-ops-assistant")
.model("dashscope:qwen-max")
.workspace(Path.of("./store-ops-workspace"))
.memory(MemoryConfig.builder().build()) // 长期记忆:画像/偏好落 MEMORY.md
.compaction(CompactionConfig.builder() // ① 结构化压缩
.triggerMessages(30).keepMessages(10)
.truncateArgs(CompactionConfig.TruncateArgsConfig.builder()
.maxArgLength(2000).build()) // ④ 参数截断
.build())
.toolResultEviction(ToolResultEvictionConfig.defaults()) // ② 大结果卸载
.build();
// ③ 溢出兜底:只要开了 compaction,模型返回 context_length_exceeded 时框架自动极端压缩并重试
4.4 HarnessAgent 装配:builder 与中间件通道
前面 4.1~4.3 都是"往 Harness 里填能力",这一节看 Harness 本身怎么用 builder 把外壳搭起来、用两个扩展通道把能力挂上去。下面就是门店助手启动时的装配代码:
java
HarnessAgent agent = HarnessAgent.builder()
.name("store-ops-assistant")
.model("dashscope:qwen-max")
// 通道一:Middleware ------ 在 5 个钩子(onSystemPrompt/onReasoning/onActing/onModelCall/onAgent)插逻辑
// 权限校验、追踪、上下文注入都挂这里;本篇 PermissionMiddleware / OtelTracingMiddleware 走此通道
.middlewares(List.of(new PermissionMiddleware(rbac), new OtelTracingMiddleware()))
// 通道二:Toolkit ------ Agent 的能力容器,@Tool 方法、MCP 客户端、检索工具、技能包都装进来
.toolkit(toolkit)
.build(); // 推理归 ReAct,运行归 Harness:只叠加、不替换
通道一 Middleware 落地:继承 MiddlewareBase,按需重写五个钩子(顺序 onSystemPrompt → onReasoning → onActing → onModelCall → onAgent),钩子职责见注释:
typescript
public class TracingMiddleware extends MiddlewareBase {
@Override public Mono onSystemPrompt(MiddlewareContext c, HookEvent e) { /* 注入 Workspace 人格+记忆(保障一) */ return Mono.just(e); }
@Override public Mono onReasoning(MiddlewareContext c, HookEvent e) { /* 安全审计/硬约束注入 */ return Mono.just(e); }
@Override public Mono onActing(MiddlewareContext c, HookEvent e) { /* 权限/脱敏主战场(见 4.3) */ return Mono.just(e); }
@Override public Mono onModelCall(MiddlewareContext c, HookEvent e) { /* 限流/计量/路由 */ return Mono.just(e); }
@Override public Mono onAgent(MiddlewareContext c, HookEvent e) { /* 生命周期埋点 */ return Mono.just(e); }
}
四项工程保障落到 builder 上就是几行配置:
javascript
HarnessAgent.builder()
.name("store-ops-assistant")
.workspace(Path.of("./store-ops-workspace")) // 保障一+资产 Workspace:人格+长期记忆+领域知识,每轮自动注入 prompt
.memory(MemoryConfig.builder().build()) // 保障一 的"记忆"侧(Workspace 持久化)
.compaction(CompactionConfig.defaults()) // 保障二+资产:自动结构化压缩,上下文有界
.toolResultEviction(ToolResultEvictionConfig.defaults()) // 保障二:大工具结果卸载
.middlewares(List.of(new PermissionMiddleware(rbac), // 保障三 横切保护 + 资产 Sandbox 安全侧(危险操作拦截/回滚)
new OtelTracingMiddleware()))
.toolkit(toolkit) // 保障四 能力积累 + 资产 Skill 四层组合:工具 + SubagentDeclaration 编排
// 保障三 状态可恢复:会话态按 sessionId 落 AgentStateStore(Redis),崩溃/发布可恢复
// 资产 Plan Mode:复杂任务先进 Plan Mode 生成步骤清单,确认/自动审批后再执行(避免一上来乱调工具)
.build();
五、HarnessAgent运行时
5.1 一次请求在 Harness 里如何流转
前面 4.4 给了 HarnessAgent 的装配代码,这里看它"跑起来"时长什么样:一条请求从接入层进来,会依次穿过五个 Middleware 钩子(onSystemPrompt → onReasoning → onActing → onModelCall → onAgent),每一步背后都亮起一项工程保障。下面用场景一的完整链路、场景二的定时链路各画一遍,看代码是怎么在运行时落地的:
我们先看场景一:
css
接入层(REST/WS)
│ 包成 RuntimeContext(sessionId, userId, tenant)
▼
HarnessAgent "store-ops-assistant"
│
├─① onSystemPrompt ── 注入 Workspace:人格 + MEMORY.md 长期记忆 + 领域知识 → 保障一 Identity persists
│
├─② 总控按 sysPrompt 5 步流程,agent_spawn intent_agent
│ → 结构化意图 {intent: md_plan, slots: {week: 2026-W34, season: 立秋, weather: 高温, segment: 社区中老年, event: 开学季}}
│
├─③ onReasoning ── 可在此钩子审计/改写(如注入"必须用工具数据,禁止臆造"硬约束)
│
├─④ md_planner_agent 拆步:
│ ① sales.history 取历史销量(同周同期、近周趋势)→ ② calendar.solar_terms 取节气行事历
│ ③ weather.query 取天气 → ④ member.segment 取客群 → ⑤ competitor.intel 取竞品
│ ⑥ md.weekly_plan 选重点商品 + 出周计划(历史销量作实证基线)
│
├─⑤ 每步执行前 onActing ── PermissionMiddleware 做 RBAC/ABAC + 数据范围注入(店长只能管本门店)
│ └─ promotion.create / coupon.issue 是高危写操作 → PermissionEngine 标 ASK → 抛 REQUIRE_USER_CONFIRM 等人工确认
│
├─⑥ onModelCall ── 限流/计量 → McpClientWrapper 走 JSON-RPC 到零售 MCP Server
│ → 业务服务计算 → 返回结果
│
├─⑦ 跑几十轮后上下文逼近上限 → CompactionConfig 自动结构化压缩 + ToolResultEviction 卸载大结果 → 保障二 Context stays bounded
│
├─⑧ streamEvents 实时流出 AGENT_START / TOOL_CALL_* / REQUIRE_USER_CONFIRM / AGENT_END → 前端渲染 + 审计 → 保障二 + 可观测(见 4.3 可观测性)
│
├─⑨ 回复结束,完整状态按 sessionId 落 AgentStateStore(Redis) → 崩溃/发布可恢复 → 保障三 State recoverable
│
└─⑩ 全程能力来自 .toolkit() 注册的工具与 SubagentDeclaration 编排 → 经验跨会话沉淀 → 保障四 Capabilities accrue
再来看场景二:
powershell
傍晚定时任务(cron / 调度器) → RuntimeContext(sessionId=clearance-daily, userId=店长)
│
├─ 意图专员识别为 clearance
├─ clearance_agent:
│ ① weather.query 取当晚天气(如骤雨/降温影响客流)
│ ② sales.history 取该品类历史销量(同天气/同时段去化基线)
│ ③ sales.realtime 取当日实时动销(叶菜/鲜奶剩余库存与售出率)
│ ④ 对照历史基线 + 结合闭店倒计时 → 计算 出清折扣力度(如 7 折) 与 最佳出清时段(如闭店前 2h)
│ ⑤ clearance.apply 落价(高危写)→ PermissionEngine 标 ASK → REQUIRE_USER_CONFIRM 待店长确认
└─ 全程 streamEvents 可观测,落 AgentStateStore 可恢复
把两条链路对照看,重点是同一个 Harness 机箱怎么"应对不同行为":场景一是"人提一句话 → 多 Agent 协作 → 高危操作等确认",场景二是"定时触发 → 实时应变 → 落价等确认"。二者共用同一套钩子与保障------onSystemPrompt 注入身份(保障一)、compaction 守住上下文(保障二)、onActing 拦危险写操作 + AgentStateStore 兜底状态(保障三)、.toolkit() 沉淀能力(保障四)。钩子职责与 builder 装配见 4.4,权限 / 可观测 / 记忆等模块见 4.3。
六、效果评估指标
前面把门店助手的能力和运行时都讲清了,最后一关是"上线后怎么证明它真有用"。下面这组指标同时把业务价值和工程确定性量化出来------前五项看业务效果(选品准不准、出清合不合理、损耗降没降),后三项看工程底线(权限零越权、延迟可控、成本可算):
| 指标 | 目标值 | 衡量方式 | | --- | --- | --- | | 意图识别准确率 | ≥ 90% | 人工标注测试集 | | 工具调用成功率 | ≥ 99% | 线上监控(streamEvents + Micrometer) | | 重点商品选品命中率 | ≥ 85% | 对比店长人工选品 / 实际销售动销 | | 出清折扣合理率 | ≥ 80% | 对比店长人工出清 / 实际动销去化 | | 生鲜损耗降低率 | ≥ 15% | 出清前后报损金额对比 | | 越权拦截率 | 100% | 权限测试用例 | | 端到端 P99 延迟 | ≤ 3s | 链路追踪(OtelTracingMiddleware) | | 单次交互成本 | ≤ 0.05 元 | Token 统计 |
这些数字不是 KPI 摆设:业务侧直接回应"拼凑系统导致的损耗与低效",工程侧则对应第五章的四项保障------越权拦截率 100% 是 onActing 权限闸门,P99 ≤ 3s 与单次成本来自 OtelTracingMiddleware 可观测 + compaction 结构化压缩,而选品 / 出清的命中率,正是 sales.history 实证基线换来的确定性。
七、总结
本方案用AgentScope-Java 2.0构建了零售生鲜超市的"52 周 MD 周计划 + 智慧出清日清"双场景助手。本文的重点是 Harness 运行层------它把"会想的推理核心"变成"能上岗的业务助手",四项保障(身份持久 / 上下文有界 / 状态可恢复 / 能力可积累)贯穿始终:
-
Harness 是骨架:
HarnessAgent+ 五个SubagentDeclaration(意图/选品规划/营销执行/出清/权限专员)串成总控;两个扩展通道(Middleware 五钩子 + Toolkit)承载所有横切逻辑与能力;Workspace / Sandbox / Plan Mode / Skill 四资产把"跑生产"落到实处(第三章、第五章)。两个场景------周计划与日清------都是在这套骨架上跑通的。 -
代码细化是齿轮:第四章把两个场景真正写进代码------MCP 统一工具层、
agent.call(Class)结构化输出、三层意图识别、PermissionEngine四态 +PermissionMiddleware细粒度拦截、OtelTracingMiddleware+streamEvents全链路观测、MemoryConfig+CompactionConfig上下文工程,全部以代码 + 注释落到 52 周 MD 与智慧出清两条业务线上(见第四章 4.3 共通模块)。每一项都是 Harness 保障的落地。
写在最后
写到这里,这趟 AgentScope Java 2.0 的旅程就正式收尾了。如果你一路跟着敲过来,应该已经具备用 AgentScope Java 2.0 构建企业级智能体的完整知识栈------从核心概念、工具调用,到记忆、RAG、多 Agent,再到生产级的 Harness、权限、容错与可观测。
但比起知识点,我更想和你说点心里话。
我知道,读这篇文章的你,最近大概率被一种情绪缠着------不是"学不动",是"会不会被替掉"。vibe coding 一日千里:V0、Cursor、Claude Code 们早已不只补全几行代码,而是能从一个自然语言描述直接蹦出整套前后端、自我迭代、自己修 bug。朋友圈里天天有人晒"半天搭出一个产品",公司大力提倡"让 AI 多干点"。一个写了多年代码的人,第一次真切地感到:自己最值钱的本领,好像正在被重新定价。
这种慌,太正常了,我懂。
其实我们这代人,已经算是被时代厚待过两回。我们生在信息化加速的年代,亲眼看着互联网把世界连成一张网,又看着移动互联网把这张网塞进了每个人的口袋。电商、移动支付、即时通讯......前两波红利实实在在地落进了生活,把"连接"和"效率"推到前所未有的高度,而我们几乎是顺手就接住了,没怎么费力。
现在,第三波浪潮拍到了脚边------但这一波不一样。前两波是"连接",把人和信息、人和服务更快对上;而 AI 这一波,直接动的是"写代码"这件事本身。它不会等谁准备好,也不会只犒赏旁观者。
所以我想说的是:恰恰因为 coding 正在被重新定义,你才更该亲手把 Agent 搭一遍。AI 能写出漂亮的 CRUD,但它不知道为什么这家生鲜超市要在下雨的傍晚加深折扣,不知道门店的库存数据藏在哪个"拼凑"出来的老系统里、字段还写错了。这些"业务里的脏活和真问题",才是你真正的护城河------而 AgentScope 这类工程化框架,正好给你一条把经验固化成系统的路。从你最熟的那块业务开始,亲手搭一个 Agent、接进真实系统跑一轮,你就不只是"写代码的人",而是"定义系统怎么思考的人"。
这趟旅程里,你不是一个人在摸索。感谢你读到这里。如果觉得有用,请收藏关注 栈知见 公众号,我们下个系列《LangChain + LangGraph 工程实战》再见。
如果这篇文章对你有帮助,欢迎关注公众号「栈知见」,我会持续输出有深度的技术实战笔记