AgentScope Java 实战 05:Spring Boot 整合——11 个官方 starter 的自动装配路线

本篇是系列的收官加餐。上一篇《互通层》把自研装配的 web 模块收了尾,这个仓库里其实还藏着第二个入口------dream-scope-boot:它把 AgentScope Java 2.0.3 的 11 个官方 Spring Boot starter 全部拉进依赖,验证另一条"引入即用"的路线。两条路线同一框架、同一套工具能力,装配方式完全不同,正好构成一组对照实验。环境:AgentScope Java 2.0.3 + Spring Boot 3.5.x + JDK 21。

前面四篇的 dream-scope-web 走的是自研装配:手写 PortsConfig、手动构建 HarnessAgent、自己接 Redis 和 RAG。这篇的 dream-scope-boot 反其道而行------官方 starter 自动配置,不依赖 dream-scope-domain 和 dream-scope-adapter,两个模块互不替代。README 里写得很直白:boot 是"可选入口"。

一、两条装配路线,差在哪

维度 dream-scope-web(自研装配) dream-scope-boot(官方 starter)
Agent 构建 手写 @Bean 构建 HarnessAgent 核心 starter 自动创建 ReActAgent
模型管理 自研 ChatHarnessOptions agentscope.model.provider 属性绑定
RAG 自研检索端口体系(关键词+pgvector+RRF+精排) 官方 agentscope-extensions-rag-simple
HTTP 契约 自定义 invoke/stream 自定义 + OpenAI 兼容 + AG-UI + A2A
依赖方向 domain / adapter 全家桶 不依赖 domain / adapter

一句话概括:web 模块控制力优先,每一层都能定制,代价是装配代码量;boot 模块效率优先,starter 引入即用,代价是定制空间受限。两个模块并存,本身就是对这个问题的回答------什么时候该用官方 starter,什么时候该自研,文章最后一节展开。

二、11 个 starter 全景

boot 模块的 pom.xml 把官方 starter 拉满,按职能分四类:

类别 Starter 状态 入口
核心 agentscope-spring-boot-starter 开 装配 Memory / Toolkit / ReActAgent
模型 agentscope-dashscope-spring-boot-starter 开 agentscope.model.provider=dashscope
模型 openai / anthropic / gemini / ollama 关 改 provider + 各自 enabled
协议 agentscope-chat-completions-web-starter 开 POST /v1/chat/completions
协议 agentscope-agui-spring-boot-starter 开 /agui 事件流
协议 agentscope-admin-spring-boot-starter 开 GET /v1/admin/sessions,写操作关
协议 agentscope-a2a-spring-boot-starter 开 GET /.well-known/agent-card.json
基础设施 agentscope-nacos-spring-boot-starter 部分开 prompt 关;A2A 注册开;技能库开

对应的 application.yml 核心段(dream-scope-boot/src/main/resources/application.yml):

复制代码
agentscope:
  agent:
    enabled: true        # 核心 starter 的总开关
    name: chat
    sys-prompt: 你是一个有帮助的助手。工具表里有 mcp__boot__echo,需要把原文回声时就调用它。回答产品或调用方式前先调用 retrieve,再按 [1][2] 引用。
    max-iters: 10
  model:
    provider: dashscope  # 切厂商只改这一处
  dashscope:
    enabled: true
    api-key: ${DASHSCOPE_API_KEY:}
    model-name: qwen-plus
    stream: true
  chat-completions:
    enabled: true
    base-path: /v1/chat/completions
  agui:
    path-prefix: /agui
    default-agent-id: agentscopeReActAgent
  admin:
    enabled: true
    write-enabled: false
  a2a:
    server:
      enabled: true
      card:
        name: dream-scope-boot

六家模型 Provider 的 starter 全在依赖里,但同一时间只启用一家:切换厂商改 agentscope.model.provider,再把对应家的 enabled 置 true、其余置 false。密钥各走各的环境变量(OPENAI_API_KEY、ANTHROPIC_API_KEY、GEMINI_API_KEY,Ollama 用 OLLAMA_BASE_URL)。

三、自动配置的三个条件注解

核心 starter 的自动配置类逻辑不复杂,但三个注解的组合值得细看(AgentScope 官方 starter 源码):

复制代码
@AutoConfiguration
@EnableConfigurationProperties(AgentscopeProperties.class)
@ConditionalOnClass(ReActAgent.class)
public class AgentscopeAutoConfiguration {

    @Bean
    @ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
    @ConditionalOnMissingBean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Memory agentscopeMemory() { return new InMemoryMemory(); }

    @Bean
    @ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
    @ConditionalOnMissingBean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Toolkit agentscopeToolkit() { return new Toolkit(); }
}

三个注解各管一件事:

注解 作用
@ConditionalOnProperty agentscope.agent.enabled=true 才装配,一行配置整体关停
@ConditionalOnMissingBean 用户自定义 Bean 优先,starter 不抢
@Scope(SCOPE_PROTOTYPE) Memory / Toolkit 每次注入都创建新实例

第三个最容易被忽视,也最关键。Memory 持对话历史,Toolkit 持工具注册,都是有状态且非线程安全的对象------在 Web 环境里如果做成单例,并发请求会互相踩状态。所以官方把它声明成原型作用域,每次注入拿到独立副本。代价是:想让两个 Agent 用同一份工具,直接 @Autowired 注入是行不通的(它们会拿到两个不同的 Toolkit 实例),必须在装配层显式传递。第五节会看到 boot 模块怎么处理这件事。

自动配置的触发链是:agentscope.agent.enabled=true 且容器里已有 Model Bean → starter 创建 ReActAgent。而 Model 由厂商 starter 提供------这就是为什么没有配 DashScope 密钥时进程直接起不来:Model Bean 创建失败,整条链断在源头。

四、两个工程细节

排除 spring-boot-autoconfigure。 boot 模块的 pom.xml 里,每个 AgentScope starter 都带一组排除(dream-scope-boot/pom.xml):

复制代码
<exclusions>
    <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </exclusion>
    <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
    </exclusion>
</exclusions>

原因:AgentScope starter 自身依赖 spring-boot-autoconfigure,而 boot 模块已经通过 spring-boot-starter-web 引入了它。11 个 starter 各带一份传递依赖,轻则版本漂移重则重复类。排除后由父 POM 的 spring-boot-dependencies BOM 统一管理。这个做法有个隐含前提:AgentScope 2.0.3 的自动配置与 Spring Boot 3.5.x 兼容,否则会报 NoClassDefFoundError。细节是核心 starter 与 Provider starter 的排除列表不一致(后者多排了 autoconfigure-processor),如果照抄这套排除方案,建议统一。

六家 Provider 共存的代价。 依赖全拉、只启用一家,带来三个隐形成本:所有 Provider 的自动配置类都会被 Spring Boot 处理(启动开销);每个 starter 传递引入对应 SDK(JAR 膨胀);理论上多 Provider 都创建 Model Bean 会有冲突风险,靠各自的 @ConditionalOnMissingBean 和 enabled 开关兜底。生产项目更稳妥的做法是用 Maven profile 控制 Provider 依赖,只装实际使用的那家。

五、两条对话路径怎么装配

starter 搞定了 ReActAgent 这条"官方链",boot 模块自己的活是把 HarnessAgent 这条"自研链"也装配进来,两链并存、互不干扰。装配核心在 StarterPortsConfig(com.zhu.scope.boot.config):

复制代码
@Bean(destroyMethod = "close")
@ConditionalOnProperty(prefix = "agentscope.agent", name = "enabled", havingValue = "true")
BootHarness bootHarness(Model model, Toolkit toolkit, BootRetrieveTool retrieve,
        BootScopeProperties props, Environment env,
        ObjectProvider<NacosSkillRepository> nacosSkills) {
    JedisPooled jedis = BootRedis.open(props.getRedis().getUri());
    DistributedStore store = BootRedis.store(jedis, props.getRedis().getKeyPrefix());
    HarnessAgent agent = BootPlanAgent.create(
            model, toolkit, workspace(props),
            props.getPlan().getDirectory(), store,
            props.getCompaction().getTriggerMessages(),
            props.getCompaction().getKeepMessages(),
            props.getSkills().getDirectory(),
            BootFallback.open(env, props, model),
            props.getEviction().getMaxResultChars(),
            props.getEviction().getPreviewChars(),
            props.getEviction().getPath(),
            /* 采样参数与 plan 迭代上限 */ ...,
            nacosSkills.getIfAvailable());
    return new BootHarness(agent, jedis);
}

几个值得注意的点:

  • Model 来自 starter :bootHarness 的入参直接注入 Model,这就是第三节说的"厂商 starter 提供 Model Bean,自研装配复用它"。HarnessAgent 和 ReActAgent 底层用的是同一个模型实例,切厂商时两边一起切。
  • Redis 是硬依赖 :BootRedis.open 连不上就抛异常,带对话能力的 Agent 起不来。会话存储用官方 DistributedStore,键前缀 dream-scope-boot:,userId 与 sessionId 成对出现才写入。
  • Nacos 技能库是显式失败 :agentscope.nacos.skill.enabled=true 时创建 NacosSkillRepository,连不上直接抛 IllegalStateException------宁可起不来也不静默降级。Nacos 没打算启时,把 A2A 注册和技能库两个开关关掉,进程才能启动。
  • 知识索引无条件创建 :knowledgeIndex 不挂条件注解,配了 DREAM_SCOPE_RAG_PG_JDBC_URL 走 PostgreSQL(表 dream_scope_boot_rag),否则退回内存索引;配了 DASHSCOPE_API_KEY 才有向量补召回(text-embedding-v4),关键词命中不受 0.3 分数阈值限制。

chat 路径的调用入口 StarterChatAgent 把 HarnessAgent 包成同步/流式两种调用(com.zhu.scope.boot.agent.impl):

复制代码
public StarterResult handle(StarterRequest request) {
    Msg inbound = StarterMessages.toUserMessage(request.input(), request.imageUrls());
    RuntimeContext context = contextOf(request);
    Msg outbound = agent.call(List.of(inbound), context).block(callTimeout);
    return new StarterResult(id(), StarterMessages.textOf(outbound), ...);
}

public void streamHandle(StarterRequest request, Sink sink) {
    Disposable disposable = agent.streamEvents(List.of(inbound), context)
            .timeout(callTimeout)
            .subscribe(event -> codec.toEvent(event).ifPresent(sink::onEvent),
                    error -> sink.onError(mapError(error)), sink::onComplete);
    sink.bindCancel(() -> { disposable.dispose(); agent.interrupt(context); });
}

同步路径 block(120s) 兜底超时;流式路径超时用 Reactor 的 timeout,客户端断开时 dispose 订阅再 interrupt Agent,取消信号传导到模型调用层。错误统一映射成 StarterTimeoutException / StarterProviderException 两类,Controller 层不用感知 Reactor 的异常细节。

请求怎么找到 Agent?一个 25 行的极简注册表(StarterHandlerRegistry):

复制代码
public final class StarterHandlerRegistry {
    private final Map<String, StarterHandler> handlers = new ConcurrentHashMap<>();

    public StarterHandlerRegistry(List<StarterHandler> agents) {
        if (agents == null) return;
        for (StarterHandler handler : agents) {
            if (handler == null || handler.id() == null || handler.id().isBlank())
                throw new IllegalArgumentException("agent handler id required");
            handlers.put(handler.id(), handler);
        }
    }

    public Optional<StarterHandler> find(String agentId) {
        if (agentId == null || agentId.isBlank()) return Optional.empty();
        return Optional.ofNullable(handlers.get(agentId));
    }
}

构造时从 Spring 收集所有 StarterHandler Bean(chat 和 knowledge 两个实现),id 空白直接 fail-fast,查找返回 Optional,未知 agentId 由 Controller 层翻译成 404。没有注册中心的花哨设计------两个 Handler 的规模用不上。

六、协议出口与 Nacos 的三组开关

starter 自动装配的协议能力,boot 一个没落下:

入口 提供方 说明
POST /v1/chat/completions chat-completions-web-starter OpenAI 兼容,ChatUI 等前端直连
POST /agui agui-starter AG-UI 协议事件流,default-agent-id 指向 starter 的 ReActAgent
GET /.well-known/agent-card.json a2a-starter Agent Card 自描述
GET /v1/admin/sessions admin-starter 会话管理,write-enabled: false 只读

AG-UI starter 做的事是把 AgentScope 事件流翻译成 AG-UI Protocol 事件,映射关系由 starter 内置:

AgentScope 事件 AG-UI 事件
AgentStartEvent / AgentEndEvent RUN_STARTED / RUN_FINISHED
文本增量 TEXT_MESSAGE_START / CONTENT / END
推理内容(enableReasoning) REASONING_MESSAGE_*
工具调用 / 结果 TOOL_CALL_START / ARGS / END / RESULT
未映射事件 RAW(原样透传)

boot 模块用的是 MVC(spring-boot-starter-web),AG-UI starter 走 MVC 适配路径;官方示例更推荐搭配 WebFlux。这条路径不经过计划模式、Redis 会话和工作区------它和 invoke 端点共用工具表,但会话能力是 HarnessAgent 那条链的专属。A2A 客户端扩展(agentscope-extensions-a2a-client,非 starter)配的 url 指向自己的 8092,创建时不访问远端,调用时才去拉对方的 agent-card------一个自调用演示位。

Nacos 这块在 boot 模块里出现了三组开关,配置前缀互不相干,实测最容易混淆:

开关组 前缀 boot 的状态
AgentScope Prompt 热加载 agentscope.nacos.prompt.enabled 关
AgentScope A2A 注册 agentscope.a2a.nacos.enabled 开
AgentScope 技能库 agentscope.nacos.skill.enabled 开(技能 boot-echo)
Spring Cloud 配置中心 spring.cloud.nacos.config.enabled 默认关(环境变量控制)
Spring Cloud 服务发现 spring.cloud.nacos.discovery.enabled 默认关

AgentScope 自己的 Nacos 集成(prompt / A2A / skill 三子模块)和 Spring Cloud 的 Nacos 集成(config / discovery)是两套完全独立的体系。boot 还加了 spring.config.import: optional:nacos:dream-scope-boot.yaml,配 optional: 前缀,Nacos 不在也不阻断启动------和技能库的 fail-fast 形成对比,一个可选一个必需,按业务影响分级。

fig2 已在第五节画出两条路径的全景。到这里,boot 模块的完整画面是:11 个 starter 负责官方链和协议出口,StarterPortsConfig 一个配置类负责自研链,两条链共享同一个 Model 和知识索引。

七、官方 starter 与自研装配的选型判断

跑完两个模块,取舍其实很清晰:

  • 官方 starter 赢在起步速度。配好 provider 和密钥,ReActAgent + OpenAI 兼容接口 + AG-UI 就位,半天能跑通一条完整链路。快速原型、内部 demo、给前端团队提供标准协议出口,starter 是正解。
  • 自研装配赢在控制力。web 模块里检索的降级链、事件编解码、SSE 取消传导、大结果截断回读,这些深度定制点 starter 都没留口子。生产级 Agent 一旦涉及复杂会话策略和可观测性,自研是绕不开的。
  • 两条路线可以渐进过渡 。boot 模块验证了一个重要事实:starter 的 Model Bean 和原型 Toolkit 可以被自研装配复用(@ConditionalOnMissingBean 保证了用户 Bean 优先)。先 starter 跑通,再逐个替换成自研 Bean,迁移路径是平滑的,不需要一步到位。

官方 starter 把"开箱一条链"做得很扎实,深度定制上留白较多------这些留白正是 web 模块那种自研装配的存在价值。两个模块并存不是冗余,是同一框架下两种工程策略的完整对照。

本篇学到什么

  1. 条件装配三件套 :@ConditionalOnProperty 控制总开关、@ConditionalOnMissingBean 让用户 Bean 优先、SCOPE_PROTOTYPE 处理有状态对象的线程安全------starter 设计的标准范式。
  2. 原型作用域的坑 :Memory / Toolkit 每次注入都是新实例,跨 Agent 共享工具必须在装配层显式传递,直接 @Autowired 共享是常见误区。
  3. starter 传递依赖治理 :多 starter 场景统一排除 spring-boot-autoconfigure,交给 BOM 管,前提是版本兼容性已验证。
  4. 多 Provider 共存的代价:依赖全拉只开一家,换来切换便利,付出启动开销与 JAR 膨胀,生产建议 Maven profile 裁剪。
  5. 依赖分级策略:Redis、技能库连不上就 fail-fast,知识索引退内存、配置中心 optional------按业务影响决定硬失败还是降级。

系列五篇到此完结:01 起步与 Agent 核心、02 会话与工具、03 RAG、04 互通层、05 Spring Boot 整合。dream-scope 一个仓库、两条路线,从自研装配到官方 starter 全部踩了一遍。

有问题评论区见,欢迎交流~

相关推荐
汤姆yu1 小时前
Claude Opus 5.5深度解析
ai·大模型·opus5.5
I Am a robert girl1 小时前
从一张图到碎裂瞬间:FracGen 如何用物理信号“导演“物体撕裂
人工智能·深度学习·计算机视觉·生成模型·视频生成·物理仿真·断裂模拟
能源革命1 小时前
AI 日报 · 2026-10-01
人工智能
量子-Alex1 小时前
【大模型强化学习后训练】强化学习后训练算力应投向何处?模型规模、搜索、学习与反馈
人工智能·学习
u1301301 小时前
GitHub 热榜项目:日榜(2026-10-01)
人工智能·github
lisw051 小时前
AI如何助力网络安全合规性?
人工智能·安全·可信计算技术
Experience-摆渡1 小时前
EasySpider:把网页采集做成可视化拖拽的免费开源工具(附命令行用法)
人工智能
档案宝档案管理1 小时前
WorkBuddy+档案管理系统:归档、查档、提醒,哪些工作可以自动跑?
人工智能
Dawson Zhu1 小时前
几何深度学习:原理解析与工程实践
人工智能·语言模型·架构·aigc·agi