摘要 Java Agent后端应被设计成“有状态任务系统”,而不是一个循环调用模型的Controller。模型负责提出下一步动作,应用负责状态持久化、工具鉴权、幂等执行、超时重试、人工接管和审计。本文以Java 21、Spring Boot 4.0.x、Spring AI 2.0.0为接口基线,给出类结构、状态流和工具契约。
1. 架构先分控制面和执行面
mermaid
flowchart LR
API["Task API"] --> ORC["AgentOrchestrator"]
ORC --> MODEL["ChatClient/ChatModel"]
ORC --> REG["ToolRegistry"]
REG --> EXEC["ToolExecutor"]
EXEC --> SYS["业务系统"]
ORC --> STATE["TaskStore/Checkpoint"]
ORC --> MEM["MemoryService"]
ORC --> HITL["Human Approval"]
ORC --> OBS["Trace/Audit/Eval"]
- 控制面管理Agent定义、工具目录、权限策略、模型配置和版本。

图1 Java AI Agent控制面与执行面(概念示意,依据正文结构整理)
ALT建议:Java AI Agent控制面与执行面,主要包含:API与身份入口、任务控制面、Agent执行循环、工具适配层、记忆与事件存储、审计、指标与人工接管。
- 执行面按任务ID运行状态机,调用模型与工具。
- 记忆服务保存模型需要的上下文;审计库保存完整事件,两者不要混用。
Spring AI 2.0.0官方文档提供ChatClient、ToolCallback、ChatMemory和MCP集成。它们减少模型与工具调用样板代码,但业务任务状态、权限和幂等仍应由应用实现。
2. 任务状态如何建模?
java
public enum TaskStatus {
RECEIVED,
PLANNING,
TOOL_PENDING,
TOOL_RUNNING,
WAITING_HUMAN,
OBSERVING,
COMPLETED,
FAILED_RETRYABLE,
FAILED_FINAL,
CANCELLED
}
public record AgentTask(
UUID taskId,
String tenantId,
String userId,
String agentVersion,
TaskStatus status,
int step,
int maxSteps,
Instant deadline,
long version
) {}
version用于乐观锁,避免同一任务被两个Worker重复推进。deadline和maxSteps是硬停止条件,不让模型自己决定无限继续。

图2 Agent任务状态机(概念示意,依据正文结构整理)
ALT建议:Agent任务状态机,主要包含:CREATED、RUNNING、WAITING_TOOL、WAITING_HUMAN、SUCCEEDED / FAILED。
任务事件单独存储:
java
public record TaskEvent(
UUID eventId,
UUID taskId,
int step,
String type,
String payloadDigest,
String actor,
Instant occurredAt
) {}
敏感Prompt、工具参数和结果不应无条件写入事件表。可以记录摘要、脱敏字段和受控对象存储地址。
3. 工具接口怎样设计?
Spring AI允许用@Tool把方法声明为工具,但生产项目仍建议在外面增加业务工具契约。
java
public interface EnterpriseTool<I, O> {
String name();
Class<I> inputType();
ToolRisk risk();
O execute(I input, ToolExecutionContext context);
}
public record ToolExecutionContext(
UUID taskId,
String tenantId,
String userId,
Set<String> permissions,
String idempotencyKey,
Instant deadline
) {}
public enum ToolRisk {
READ_ONLY,
WRITE_REVERSIBLE,
WRITE_IRREVERSIBLE
}
一个库存查询工具可以暴露给模型,但租户与用户不能由模型参数决定:
java
@Component
class InventoryTools {
private final InventoryService inventoryService;
private final AgentSecurityContext security;
@Tool(description = "按SKU查询当前用户有权访问仓库的可用库存")
InventoryResult queryInventory(
@ToolParam(description = "SKU编码") String sku
) {
var actor = security.requireActor();
return inventoryService.query(
actor.tenantId(),
actor.allowedWarehouses(),
sku
);
}
}
工具描述要写清用途、边界和参数格式。权限范围从服务端上下文注入,不能让模型提交tenantId或role=admin。
4. 编排循环如何控制?
下面是简化伪代码:
java
AgentResult run(UUID taskId) {
AgentTask task = taskStore.lock(taskId);
while (!terminal(task.status())) {
guardDeadlineAndSteps(task);
taskStore.transition(taskId, TaskStatus.PLANNING);
ModelDecision decision = planner.next(
task,
memoryService.context(taskId),
toolRegistry.visibleTools(task)
);
switch (decision) {
case FinalAnswer answer -> complete(task, answer);
case AskHuman ask -> waitForHuman(task, ask);
case ToolRequest call -> executeToolStep(task, call);
}
task = taskStore.reload(taskId);
}
return taskStore.result(taskId);
}
工具执行:
java
void executeToolStep(AgentTask task, ToolRequest call) {
EnterpriseTool<?, ?> tool = toolRegistry.require(call.name());
policy.checkVisible(task, tool);
policy.checkArguments(task, tool, call.arguments());
if (tool.risk() == ToolRisk.WRITE_IRREVERSIBLE) {
taskStore.savePendingCall(task.taskId(), call);
taskStore.transition(task.taskId(), TaskStatus.WAITING_HUMAN);
return;
}
String key = task.taskId() + ":" + task.step() + ":" + call.digest();
ToolResult result = executor.execute(tool, call, key);
memoryService.appendObservation(task.taskId(), result.forModel());
taskStore.appendAudit(task.taskId(), result.auditRecord());
taskStore.transition(task.taskId(), TaskStatus.OBSERVING);
}
写工具必须支持幂等键或业务去重。HTTP超时后,客户端不知道服务端是否已成功处理,直接重试可能重复下单。
5. 异常、重试和人工接管怎么做?
|
错误类型 |
处理 |
是否交给模型 |
|
网络超时、临时不可用 |
有限退避重试,超过阈值转人工 |
只返回标准化状态 |
|
参数校验失败 |
不执行,允许模型修正一次 |
可以 |
|
权限拒绝 |
立即停止该动作并审计 |
不允许绕过 |
|
业务拒绝 |
作为事实返回,例如库存不足 |
可以据此换合法方案 |
|
部分成功 |
进入补偿或人工队列 |
不让模型猜测 |
|
未知异常 |
失败并保留Trace |
不暴露堆栈和密钥 |

图3 自动重试与人工接管边界(概念示意,依据正文结构整理)
ALT建议:自动重试与人工接管边界,主要包含:自动处理,瞬时网络错误,幂等读操作,可验证参数修复、人工接管,高风险写操作,权限冲突,重复失败或结果不确定。
Spring AI的工具执行异常可通过ToolExecutionExceptionProcessor处理。对于企业写操作,建议让异常上抛给任务编排层,由任务状态机决定重试、补偿或人工接管,而不是把原始异常文本直接交给模型。
6. 记忆与完整历史怎样分开?
Spring AI文档明确区分Chat Memory与完整Chat History。MessageWindowChatMemory维护有限窗口,完整审计历史应另存。
建议分三层:
text
working_context 当前任务必要消息与观察
durable_memory 经确认的长期偏好或业务事实
audit_history 完整输入、决策、工具、审批与结果
长期记忆写入要经过明确规则或用户确认,并带source、expiresAt、tenantId和删除接口。模型推断出的偏好不能直接成为永久事实。
7. 测试什么?
单元测试:
- 工具Schema与参数验证。
- 权限范围不能由模型覆盖。
- 幂等键相同只执行一次。
- 状态转换非法时拒绝。
集成测试:
- 模型要求不存在工具。
- 工具超时后恢复。
- 高风险写操作进入人工审批。
- Worker重启后从检查点继续。
- 用户撤销任务后工具不能继续执行。
评测:
- 任务完成率、错误工具率、参数正确率。
- 越权拦截率、人工接管率。
- 平均步骤、P95时延、单任务成本。
- Trace完整率和可重放率。
Anthropic建议Agent在每一步从环境获取真实反馈,并设置人工反馈点和停止条件。复杂度应在评测证明有价值后再增加。
8. MCP放在哪一层?
MCP适合成为工具注册与连接的一种标准接口。Spring AI 2.0提供MCP客户端与服务端Starter,并能把MCP工具转换为Spring AI工具回调。企业仍需在MCP外层完成连接器准入、工具白名单、身份传递、审批和审计。
速众AI低代码开发平台可作为这类任务、流程和业务接口的承载候选,但本文结构是通用Java设计。是否采用平台,应以当前版本接口、源码或扩展范围、部署与POC结果判断。

371

被折叠的 条评论
为什么被折叠?



