目录 · 第 07 章4 卷 12 章

第 07 章 · 卷二 搞懂 AI 怎么工作

Agent、上下文与工作流编排

什么时候让模型自己决定下一步,什么时候用固定流程兜住。

约 13 分钟9 节
已知事实

无人机实战平台已有设备接入、航线规划、任务下发、遥测回传和告警处置能力。Copilot 需要把用户的自然语言目标转换为可执行的巡检任务,并支持人工确认、飞行监控和报告生成。技术栈包括 Java 21、Spring Boot 3、Spring AI、Spring AI Alibaba、PostgreSQL、Redis、RocketMQ、Milvus、MinIO、Vue 3、Cesium 和 SSE。

目标设计

使用 Agent Framework 承担智能决策,使用 Graph 管理多步骤执行流程。通过持久化任务状态、人工审批、上下文管理和错误恢复,控制模型的执行范围。以下类名、接口、表结构及代码均为方案设计,不代表现有项目已经实现。

验证结果

暂无,本文只做方案设计。

1要解决的问题

复杂任务不是调用一次大模型就能完成的。

用户在三维地图上圈选一块区域,说:“这块地今天巡一遍消防通道。”

系统需要完成一连串工作:

  1. 理解用户目标,提取巡检区域、时间和巡检对象。
  2. 查询附近可用的无人机、机场、电量和设备状态。
  3. 检查空域限制、天气条件及任务执行约束。
  4. 根据区域和设备能力规划航线。
  5. 检查航线是否可执行,设备是否满足任务要求。
  6. 生成任务草稿,等待用户确认。
  7. 确认后下发任务,跟踪遥测和视觉告警。
  8. 发现异常时进入相应的处置流程,结束后生成报告。

这些步骤存在依赖关系。没有设备,不能直接下发任务;没有通过空域检查,不能执行航线;用户没有确认,不能因为模型认为方案合理就直接起飞。

真正要解决的问题是:让模型负责理解和有限决策,让程序负责流程、状态和执行边界。

这里有四个不同层次的能力。

普通 LLM 调用:输入一段文本,模型返回一段文本。例如解释某个告警的含义。模型不会自动查询设备,也不会因为回答中提到了航线就真的规划航线。

Tool Calling:模型决定调用哪个工具,并按工具定义提供参数。例如调用 queryAvailableDrones 查询设备。工具由 Java 程序实际执行,模型只负责提出调用请求。一次 Tool Calling 不等于完整的 Agent。

Agent:模型可以根据当前结果,决定下一步调用什么工具,并根据工具结果继续判断。例如发现一架无人机电量不足后,改查其他设备。它通常包含多轮模型调用和工具调用。

Workflow:程序提前定义步骤、分支和状态流转。例如必须先查设备、再查空域、再规划航线、再等待人工确认。模型可以参与单个节点,但不能随意跳过程序规定的步骤。

一次巡检任务可以同时使用四者:LLM 解析用户意图,Tool Calling 查询设备,Agent 在设备不足时寻找替代方案,Workflow 控制空域校验、审批和任务下发。

它们不是四种互斥的产品。它们分别解决文本生成、工具调用、动态决策和流程控制的问题。

2在整个系统中的位置

Copilot 位于现有业务平台之上,调用现有能力,而不是重新实现一套无人机控制系统。

交互层

Vue 3 + Cesium · 地图圈选 · 对话 · 审批 · SSE 状态推送

Copilot 应用层

意图解析 · Agent 决策 · Graph 编排 · 上下文管理 · 权限校验

业务工具层

设备查询 · 空域检查 · 航线规划 · 任务草稿 · 任务下发 · 告警处置

现有业务系统与数据层

设备服务 · 飞行任务服务 · PostgreSQL · Redis · RocketMQ · MinIO

各部分职责需要分清。

  • Vue 3 和 Cesium 负责接收用户输入、展示地图和航线、显示任务状态以及提供人工确认入口。
  • Copilot 应用层负责组织模型调用、执行流程、上下文和权限检查。
  • 业务工具层将模型的意图转换为已有业务接口能够理解的请求。
  • 现有业务系统负责真正的设备控制、任务执行和遥测处理。

例如,模型可以请求“创建巡检任务草稿”,但不能直接拼接数据库 SQL,也不能直接向无人机发送飞控指令。Java 服务需要验证参数、用户权限、设备状态和任务状态,再调用对应业务服务。

任务状态应以 PostgreSQL 等持久化数据为准。Redis 可以保存短期会话数据和运行期缓存,RocketMQ 可以传递异步任务事件。不能把一次模型对话的内存当作任务执行记录。

3总体设计

本章采用“固定主流程 + 局部 Agent”的设计。

固定主流程负责安全边界和业务顺序。Agent 只在需要动态判断的节点里工作。例如设备不足时,Agent 可以分析原因、查询替代设备;但它不能决定跳过空域检查,也不能跳过人工审批直接起飞。

3.1 Agent 执行循环

  1. 接收用户请求获取用户指令、地图圈选区域和会话标识
  2. 意图解析提取巡检目标、时间、区域和约束
  3. 读取当前状态加载任务进度、设备查询结果和剩余执行预算
  4. 模型决策根据当前状态决定调用哪个允许使用的工具
  5. 工具执行Java 服务校验参数、权限和业务条件后执行工具
  6. 写入执行结果更新 Graph 状态,记录工具结果和任务事件
  7. 检查执行条件判断是否完成、需要重试、需要人工介入或应该终止
  8. 继续决策未完成且允许继续时,再次进入模型决策
  9. 返回执行结果返回任务草稿、等待审批、执行失败或任务完成状态

这条循环不是无限运行的。每轮执行都需要检查允许调用的工具、最大迭代次数、超时、剩余模型调用预算和当前任务状态。

模型提出调用请求后,Java 服务先做校验,再执行操作。工具返回结果后,系统更新状态,再决定是否进入下一轮。

3.2 分层职责

建议把实现拆成四个部分。

组件主要职责
CopilotAgentService调用模型,完成有限范围内的动态决策
InspectionGraphFactory定义节点、条件分支、暂停点和状态流转
ContextService加载短期对话、长期偏好和当前任务所需上下文
InspectionToolService封装设备、空域、航线和任务等业务接口

这些名称是建议的应用层类名,不是 Spring AI Alibaba 自带的固定类名。

3.3 Agent Framework 与 Graph 的分工

Spring AI Alibaba 的 Agent Framework 面向 Agent 的构建与运行,关注模型如何决策、如何使用工具,以及如何组织 Agent 的行为。

Graph 面向有状态的流程编排,关注节点之间怎么连接、什么条件下走哪个分支、执行中断后如何恢复。具体 API 和持久化能力需要根据项目锁定的 Spring AI Alibaba 版本确认,不能把本文的业务类名直接当成框架 API。

在这个项目中,可以这样划分:

  • Agent Framework: 负责设备替代方案分析、工具选择、有限的多轮决策。
  • Graph: 负责意图解析、设备检查、空域检查、航线校验、人工审批和任务下发的整体流程。
  • 业务服务: 负责设备查询、空域校验、任务创建等真实业务操作。
  • 上下文服务: 负责给模型提供当前步骤真正需要的信息。

不要让 Agent 同时承担全部流程控制。否则模型一旦误判,可能跳过关键步骤,系统也很难精确恢复到中断前的业务状态。

4完整执行链路

以用户圈选区域后发起消防通道巡检为例。

4.1 正常执行链路

Graph 接收请求后,先校验地图区域和用户权限,再解析自然语言目标。意图解析的结果需要结构化,例如:

json
{
  "missionType": "FIRE_LANE_INSPECTION",
  "areaId": "area-001",
  "requestedAt": "2026-10-29",
  "requireHumanApproval": true
}

这是示例数据,不是实际运行记录。真实系统还应保存地图几何范围、坐标系、时间约束和来源信息。

后续节点按顺序执行:

  1. query_devices:查询设备位置、在线状态、电量和载荷能力。
  2. check_airspace:检查目标区域及计划航线涉及的空域约束。
  3. plan_route:根据设备能力和区域生成航线。
  4. validate_plan:检查航线、设备、任务约束是否满足要求。
  5. create_draft:生成任务草稿,保存航线版本和校验结果。
  6. human_approval:暂停执行,等待用户确认。
  7. dispatch_task:重新检查关键条件,调用现有任务服务下发。
  8. monitor_mission:接收任务事件、遥测和视觉告警。
  9. generate_report:汇总执行记录、异常和证据,生成报告。

其中,任务下发不是“用户确认时直接执行一条命令”。审批通过后,还要检查设备是否仍在线、空域条件是否仍有效、任务草稿是否仍是当前版本。

4.2 分支:设备不够 → 换机场 → 重新规划

假设目标区域附近只有一架可用无人机,但它无法满足任务的续航要求。系统不能把“不够用”的设备硬塞进任务。

  1. query_devices查询候选无人机及机场
  2. check_capacity检查数量、续航、载荷和任务覆盖能力
  3. capacity_sufficient判断现有设备是否满足约束
  4. use_current_base满足条件则进入空域检查
  5. find_alternative_base不满足则查询其他机场和候选设备
  6. validate_alternative检查替代机场的设备能力、可达性和使用权限
  7. alternative_available判断是否找到满足约束的替代方案
  8. replan_route有可用替代方案则重新规划航线
  9. revalidate_plan重新校验航线、空域和任务约束
  10. human_approval生成新版本草稿,等待人工确认
  11. manual_intervention无可用替代方案则暂停并请求用户调整条件

这里有几个容易出错的地方。

换机场后必须重新规划。 机场位置发生变化,航程、续航消耗、航线覆盖和返航条件都可能变化。不能复用原航线后只修改一个机场字段。

替代方案需要有明确的评分和约束。 Agent 可以提出候选机场,但最终选择要受设备能力、空域、距离、权限等业务规则限制。硬约束不满足的方案必须排除,不能因为模型觉得合理就放行。

重新规划必须生成新版本。 例如 planVersion=2。原草稿和原航线继续保留,用于审计和问题追踪。新草稿通过完整校验后,才进入人工审批。

找不到替代方案时要停止。 系统返回“当前设备无法满足任务要求”,并说明缺少什么条件。用户可以调整区域、时间或任务范围,不允许无限循环查找。

4.3 执行状态与错误恢复

建议把流程状态和无人机业务状态分开管理。

流程状态含义
RUNNINGGraph 正在执行
WAITING_APPROVAL等待人工审批
WAITING_RESOURCE等待设备或其他资源
PAUSED人工暂停,等待恢复
FAILED执行失败,需要处理
CANCELLED已取消
COMPLETED流程完成

无人机任务本身还需要自己的状态,例如 DRAFT、APPROVED、DISPATCHED、IN_FLIGHT、SUCCEEDED 和 ABORTED。两套状态不能混为一谈:Graph 进入 COMPLETED,可能表示“草稿创建流程完成”,并不代表无人机已经飞行结束。

恢复时,系统从持久化状态读取当前节点和任务版本,确认上一步是否已经产生业务副作用,再决定继续还是补偿。

例如,任务下发接口超时,不代表下发失败。系统应先通过任务 ID 或幂等键查询下发结果,避免重试造成重复任务。

5技术选型与原理

5.1 Agent 与固定工作流怎么选

对比项Agent 动态决策固定 Workflow
执行路径根据上下文和工具结果选择下一步由程序预先定义
适合场景设备替代、告警分析、复杂问题诊断空域检查、审批、下发、报告生成
灵活性高,可以处理未预设的组合情况路径相对固定,行为容易预测
可测试性需要覆盖模型输出和工具组合可以针对节点、分支做确定性测试
主要风险错误选择工具、重复调用、推理偏离目标流程僵化,遇到新情况需要增加分支
故障恢复需要结合工具记录和业务状态判断可以按已保存的节点状态恢复
安全控制必须限制工具权限和调用范围可在节点和边上设置明确约束
本项目用途分析设备不足的原因,提出替代方案控制校验、审批、下发及结束流程

判断标准很直接:结果不确定、需要根据新信息选择路径的步骤,可以使用 Agent;执行顺序明确、涉及安全或不可逆操作的步骤,优先使用固定工作流。

即使采用 Agent,也应该通过工具白名单、参数校验和预算限制约束它。即使采用固定工作流,也可以在某个节点调用大模型完成意图识别或告警解释。

5.2 Graph 为什么适合长流程

普通方法调用通常运行在一次请求里。无人机任务却可能跨越多分钟甚至更长时间,还可能等待用户审批、等待设备恢复或接收异步告警。

Graph 可以把任务拆成多个有输入、有输出的节点,通过条件边控制后续路径。配合持久化检查点,可以把“执行到了哪里”从进程内存转为可恢复的状态。

建议将节点设计成小而明确的业务步骤:

  • 输入:从共享状态中读取必要字段。
  • 执行:调用模型或业务服务。
  • 输出:返回本节点产生的状态变更。
  • 异常:区分可重试错误、业务拒绝和需要人工处理的错误。

节点不要直接修改一大堆无关字段,也不要把整段历史对话传给每一个节点。

5.3 MCP 解决什么问题

MCP(Model Context Protocol)是一种让 AI 应用以统一协议发现和调用外部工具、资源的方式。

在本项目中,如果无人机平台需要接入多个独立系统,例如第三方空域查询服务、企业知识库或外部资产平台,可以考虑把相应能力封装为 MCP Server,让支持 MCP 的客户端按统一方式发现和调用工具。

但 MCP 不是所有工具的必经通道。

工具类型建议方式原因
本服务内部的设备查询直接调用 Java Service调用简单,类型约束清晰
本平台的航线规划接口业务工具封装便于做权限、事务和参数校验
独立部署的外部系统视集成需求使用 MCP统一跨进程工具接入方式
企业知识库检索直接集成或 MCP 均可取决于现有检索服务和复用范围
起飞、返航等高风险操作受控业务服务必须有独立的权限和安全校验,不能因采用 MCP 而放宽限制

MCP 解决的是工具接入协议问题,不会自动解决 Agent 的规划、Graph 的状态持久化、权限审批或错误恢复。无论工具通过哪种方式接入,真正执行操作的服务都必须执行自己的安全校验。

6关键数据结构与代码

6.1 Graph 状态定义

Graph 的状态只保存执行过程中需要共享的信息。设备列表、航线版本、审批结果等结构化数据应有明确字段,不能全部塞进一个 String context。

下面使用 Java 21 风格展示业务状态模型。InspectionState 和节点类是目标设计中的应用层类型,并非框架内置类。

java
public enum FlowStatus {
    RUNNING, WAITING_APPROVAL, WAITING_RESOURCE,
    PAUSED, FAILED, CANCELLED, COMPLETED
}

public record InspectionRequest(
    String missionType,
    String areaId,
    String userId,
    String requestedAt
) {}

public record DeviceCandidate(
    String deviceId,
    String airportId,
    boolean online,
    double batteryPercent
) {}

public record RoutePlan(
    String routeId,
    int planVersion,
    String deviceId,
    String airportId
) {}

public record InspectionState(
    String executionId,
    String conversationId,
    InspectionRequest request,
    FlowStatus status,
    String currentNode,
    int iteration,
    int planVersion,
    List<DeviceCandidate> candidates,
    RoutePlan routePlan,
    String approvalId,
    String errorCode,
    String errorMessage
) {}

生产实现还要增加空域校验结果、租户标识、状态版本、幂等键和时间戳。电量等字段也应采用平台实际的单位和数据类型,不能仅靠模型提供的数值判断设备是否可用。

6.2 节点定义

节点的核心职责是读取状态、执行单一业务步骤、返回状态变更。以下为简化的 Java 伪实现,便于说明结构,不是可直接编译的 Spring AI Alibaba Graph API 示例。

java
public interface InspectionNode {
    String name();

    InspectionState execute(InspectionState state);
}

public class QueryDevicesNode implements InspectionNode {

    private final DeviceService deviceService;

    public QueryDevicesNode(DeviceService deviceService) {
        this.deviceService = deviceService;
    }

    @Override
    public String name() {
        return "query_devices";
    }

    @Override
    public InspectionState execute(InspectionState state) {
        var candidates = deviceService.findCandidates(
            state.request().areaId()
        );

        return new InspectionState(
            state.executionId(),
            state.conversationId(),
            state.request(),
            FlowStatus.RUNNING,
            name(),
            state.iteration(),
            state.planVersion(),
            candidates,
            state.routePlan(),
            state.approvalId(),
            null,
            null
        );
    }
}

实际项目可以用不可变状态对象或框架支持的状态更新机制,避免节点之间无约束地修改共享数据。

条件分支应该由程序根据明确的业务结果判断。例如:

text
public String nextNode(InspectionState state) {
    if (state.status() == FlowStatus.CANCELLED) {
        return "end";
    }

    if (state.candidates().isEmpty()) {
        return "find_alternative_base";
    }

    if (state.routePlan() == null) {
        return "plan_route";
    }

    return "validate_plan";
}

这段代码只展示分支思想。正式实现还要检查候选设备是否满足续航和载荷要求,不能把“候选列表非空”等同于“设备足够”。

Graph 的节点和条件边应在图构建阶段注册。运行时由 Graph 执行节点、更新状态、选择下一条边。Agent 可以在 find_alternative_base 等节点内进行有限的动态决策。

6.3 记忆与任务状态分别存在哪里

这里有三类信息,不能混在一起。

类型保存位置内容与用途
短期上下文Redis、当前会话存储最近几轮对话、当前轮次引用、临时摘要
长期偏好PostgreSQL用户明确设置的常用巡检方式、默认报告格式、偏好机场等
任务状态PostgreSQL执行 ID、当前节点、任务状态、航线版本、审批记录和错误信息

Milvus 可以存储文档向量,用于检索操作手册、设备说明和处置规程。它不是用户偏好或任务状态的权威存储。

MinIO 可以保存航线文件、图片、视频和巡检报告等对象。数据库保存对象引用、文件校验信息和关联任务 ID。

建议至少设计以下表:

  • copilot_conversation:会话基本信息。
  • copilot_message:对话消息及必要的结构化元数据。
  • copilot_user_preference:用户长期偏好。
  • copilot_execution:Graph 执行实例及当前状态。
  • copilot_execution_checkpoint:可恢复的执行检查点。
  • copilot_execution_event:节点执行、工具调用、审批及错误事件。
  • copilot_approval:人工审批记录。

Redis 中的缓存可以过期,但不能因为缓存过期就丢失尚未完成的任务。任务恢复必须以持久化状态为依据。

6.4 超长上下文怎么处理

大模型的上下文窗口不是越大越好。对话越来越长时,应按用途筛选信息,而不是把全部聊天记录持续追加进去。

建议采用三步处理。

第一步:保留当前任务必需字段。 任务 ID、区域几何范围、设备约束、当前航线版本、空域校验结果、审批状态和待执行节点,都应从结构化状态重新加载。

第二步:对历史对话做摘要。 保留用户目标、已确认条件、已经尝试的方案、失败原因和未解决的问题。普通闲聊可以压缩,不能把尚未确认的推测写成用户已经确认的事实。

第三步:按节点构造上下文。 设备查询节点只需要区域、设备能力要求和当前任务状态;告警分析节点需要告警信息、相关遥测和适用的操作规程。无需每次把所有历史消息都传给模型。

可以给每次模型调用配置上下文预算:

java
public record ContextBudget(
    int maxInputTokens,
    int maxOutputTokens,
    int maxIterations,
    int maxToolCalls
) {}

这些字段表示设计上的预算参数,不代表当前模型的实际限制。具体值应根据所选模型的上下文窗口、工具数量和业务测试结果确定。

需要注意,摘要不能代替原始证据。关键操作记录、审批人、审批时间、工具返回的业务 ID、空域检查结果及原始告警引用必须保留。模型生成的摘要只能帮助理解,不能作为任务是否执行成功的唯一依据。

6.5 暂停、恢复与取消

人工介入不能只在前端隐藏一个“继续”按钮。后端必须有可持久化的状态转换。

  • 暂停: 将执行实例转为 PAUSED,记录当前节点和检查点。若任务已在飞行,暂停 Graph 不等于暂停无人机,飞行安全由独立业务控制流程负责。
  • 恢复: 根据 executionId 加载检查点,验证任务未取消、状态版本匹配、依赖资源仍可用,再从安全恢复点继续。
  • 取消: 记录取消请求,阻止后续普通节点继续执行。若无人机已经起飞,还必须走现有任务服务的取消或安全处置流程,不能只把数据库状态改成 CANCELLED。
  • 审批: 持久化审批人、审批时间、审批结果和对应的 planVersion。审批旧版本不能自动授权新版本。

推荐的持久化顺序是:先记录节点开始事件,再执行可重试的查询或规划操作,获得结果后保存状态和检查点。对于下发任务等有外部副作用的操作,还需要幂等键和结果核对。

PostgreSQL 事务只能保证本地数据库操作的一致性,不能把数据库事务和无人机远程执行合并为一个原子事务。因此,任务下发和取消需要结合业务状态查询、幂等处理及必要的补偿机制。

7异常、安全与边界条件

7.1 错误恢复策略

不同错误不能统一处理成“再问一次模型”。

错误类型处理方式
网络超时查询原操作结果,确认安全后有限重试
参数校验失败返回明确错误,要求修正参数,不盲目重试
设备不足进入替代设备分支,重新规划并重新校验
空域检查不通过阻止下发,尝试合规替代方案或请求人工调整
模型调用失败使用持久化状态暂停或失败,避免重复执行业务副作用
审批超时保持等待状态,按业务规则提醒或终止待审批流程
状态版本冲突重新加载最新状态,拒绝基于旧版本继续操作

重试次数、超时时间和替代方案数量都应有限制。重试前必须判断操作是否幂等,尤其是创建任务、下发任务和取消任务等操作。

7.2 模型不能突破的边界

第一,工具必须按用户权限和任务权限授权。模型不能通过生成不同的工具参数绕过权限检查。

第二,空域、设备能力和飞行安全属于硬约束。模型可以解释校验结果,但不能覆盖校验结果。

第三,审批绑定具体任务草稿和航线版本。任何实质性修改都要重新校验,并按规则重新审批。

第四,Agent 的每次工具调用都要记录调用方、参数摘要、结果、耗时和错误。敏感信息应按最小必要原则保存,避免把完整凭据写入模型上下文或日志。

第五,遥测和视觉告警属于业务数据,不是模型指令。图片、告警文本或检索文档中即使包含要求执行其他操作的文字,也不能据此改变系统权限和工作流规则。

8测试与验证

本章方案要验证的重点不是模型能否说出一段正确答案,而是复杂流程能否在异常情况下保持正确状态。

建议按以下场景设计测试:

测试场景验证点
正常巡检按既定顺序执行,生成草稿并等待审批
设备不足进入替代机场分支,重新规划并重新校验
无替代设备停止自动尝试,返回明确原因
空域校验失败不得进入任务下发节点
模型连续选错工具触发预算或迭代上限,安全终止
工具超时不重复产生任务,能够核对操作结果
审批期间修改航线拒绝使用旧版本审批结果
执行中服务重启从持久化检查点恢复,不重复执行已完成的副作用
用户取消任务阻止后续节点,并核实实际飞行任务的处置结果
上下文超长压缩普通历史,但保留任务约束和关键证据

还要为 Graph 节点和分支编写单元测试,为工具适配层编写集成测试,为完整巡检链路编写端到端测试。

模型相关测试应固定输入条件,记录模型版本、提示词版本、工具调用序列和最终业务状态。对 Agent 可以采用一组设备不足、设备离线、替代机场不可用等案例,检查它是否始终遵守约束,而不是只评估回答是否流畅。

以上均为目标测试设计。当前没有实际执行数据,因此不能宣称流程已经通过测试或达到某个准确率。

9面试表达与追问

问答一:为什么不让 Agent 从头到尾自己决定?

答:因为无人机任务有明确的安全约束和不可逆操作。Agent 适合处理需要根据结果动态选择路径的问题,比如设备不足时寻找替代机场。但空域检查、人工审批和任务下发必须由固定流程控制。这样既保留模型的灵活性,也能明确知道每一步为什么执行、失败后从哪里恢复。

问答二:Spring AI Alibaba 的 Agent Framework 和 Graph 有什么区别?

答:我把 Agent Framework 理解为 Agent 的构建与运行能力,重点是模型决策和工具使用;Graph 重点是把多个步骤组织成有状态的流程,定义节点、分支和恢复点。实际项目中,我会用 Agent 处理局部的动态决策,用 Graph 控制整条巡检链路。具体 API 要根据项目采用的框架版本确定。

问答三:用户在审批节点关闭页面,系统怎么继续执行?

答:我不会把执行状态只放在前端或 Java 进程内存里。后端会持久化执行实例、当前节点、检查点和审批状态。用户重新进入页面时,根据 executionId 查询真实状态。用户恢复操作时,后端会重新检查任务是否取消、航线版本是否变化、设备和空域条件是否仍有效。只有满足恢复条件,才继续执行。审批等待期间不会因为页面关闭就自动下发任务。