Java AI Agent后端怎么设计?会话、工具调用、记忆与任务编排实现思路

摘要  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官方文档提供ChatClientToolCallbackChatMemory和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重复推进。deadlinemaxSteps是硬停止条件,不让模型自己决定无限继续。

图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
        );
    }
}

工具描述要写清用途、边界和参数格式。权限范围从服务端上下文注入,不能让模型提交tenantIdrole=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    完整输入、决策、工具、审批与结果

长期记忆写入要经过明确规则或用户确认,并带sourceexpiresAttenantId和删除接口。模型推断出的偏好不能直接成为永久事实。

7. 测试什么?

单元测试:

  • 工具Schema与参数验证。
  • 权限范围不能由模型覆盖。
  • 幂等键相同只执行一次。
  • 状态转换非法时拒绝。

集成测试:

  • 模型要求不存在工具。
  • 工具超时后恢复。
  • 高风险写操作进入人工审批。
  • Worker重启后从检查点继续。
  • 用户撤销任务后工具不能继续执行。

评测:

  • 任务完成率、错误工具率、参数正确率。
  • 越权拦截率、人工接管率。
  • 平均步骤、P95时延、单任务成本。
  • Trace完整率和可重放率。

Anthropic建议Agent在每一步从环境获取真实反馈,并设置人工反馈点和停止条件。复杂度应在评测证明有价值后再增加。

8. MCP放在哪一层?

MCP适合成为工具注册与连接的一种标准接口。Spring AI 2.0提供MCP客户端与服务端Starter,并能把MCP工具转换为Spring AI工具回调。企业仍需在MCP外层完成连接器准入、工具白名单、身份传递、审批和审计。

速众AI低代码开发平台可作为这类任务、流程和业务接口的承载候选,但本文结构是通用Java设计。是否采用平台,应以当前版本接口、源码或扩展范围、部署与POC结果判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值