系列文章目录
第一章 「Java AI实战」LangChain4J - 接入Xinference本地大模型
第二章 「Java AI实战」LangChain4J - ChatAPI 及常用配置
第三章 「Java AI实战」LangChain4J - 向量数据库接入与语义检索
第四章 「Java AI实战」LangChain4J - Agent智能体开发
第五章 「Java AI实战」LangChain4J -记忆缓存
第六章 「Java AI实战」LangChain4J -文本分类器实践
前言:为什么需要「多 Agent 协作」?
在大部分 Java 项目里,“接入大模型” 往往长这样:
- Controller 收到一个 question
- 调用大模型接口拿到 answer
- 把结果原样丢回前端
这种玩法可以叫「高配搜索框模式」——能用,但很快就会遇到瓶颈:
- 一个 Agent 要写 Prompt、要查数据库、还要改写文案,太忙了
- 不同角色(创作者、编辑、审稿人)的职责混在一起,难以维护
- 想复用其中一段能力(比如只要润色)也不方便
更工程化的玩法是:
把大任务拆成多个 “角色清晰的 Agent”,再编排它们像流水线一样协同完成任务。
这一篇,我们就用一个简单的场景——故事创作 + 面向不同读者的改写——来落地一套:
- CreativeWriter:负责创作故事
- AudienceEditor:负责针对目标读者进行润色
一、基于 LangChain4J 的多 Agent 能力
在 LangChain4J 的 Agentic 模块中,有几个核心概念:
- @Agent:把一个 Java 接口方法标记为 Agent 能力
- @UserMessage:定义调用模型时的 Prompt 模板
- @V:把上下文里的变量注入到 Prompt 中
- AgenticServices:
- agentBuilder():把接口实现为一个可调用的 Agent Bean
- sequenceBuilder():把多个 Agent 组装成顺序执行的工作流
在这套 Demo 里,我们做的事情可以抽象成一句话:
用 CreativeWriter 生成 story → 用 AudienceEditor 根据 audience 对 story进行二次创作 → 返回 finalStory。
二、代码实践:从接口到 HTTP 接口的完整闭环
2.1 整体设计思路
整个 Demo 的结构可以概括为:
-
两个 Agent 接口
- CreativeWriter:按主题生成故事初稿
- AudienceEditor:根据读者群体对故事进行改写
-
一个 LLM 配置
- 使用 OpenAiChatModel 协议,对接 Xinference + 本地 Qwen2.5 模型
-
一个 AgentConfig
- 通过 AgenticServices.agentBuilder 把接口变成 Bean
- 通过 AgenticServices.sequenceBuilder 串成一个顺序执行的工作流
-
一个 REST Controller
- 暴露 /story GET 接口,对外提供服务
2.2 定义两个 Agent:创作 & 润色
2.2.1 创作故事的 Agent:CreativeWriter
package org.example.agents;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
public interface CreativeWriter {
@UserMessage("你是一名富有创造力的故事作者。请围绕给定的主题创作一个不超过 3 句话的故事草稿。" +
"只返回故事内容本身,不要包含任何解释或其他文字。主题是:{{topic}}。")
@Agent(description = "根据给定主题生成一个故事")
String generateStory(@V("topic") String topic);
}
这里有几个关键点:
- @UserMessage 里写的是系统 Prompt 模板:
- 限制为「不超过 3 句话」
- 要求「只返回故事内容本身」
- @V(“topic”):把上下文中的 topic 变量注入到 {{topic}} 占位符中
- @Agent:告诉 LangChain4J,generateStory 是一个 Agent 方法
2.2.2 润色故事的 Agent:AudienceEditor
package org.example.agents;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
public interface AudienceEditor {
@UserMessage(" 你是一名专业的文字编辑。请将下面的故事改写得更适合目标读者:{{audience}}。" +
"故事长度请控制在 3 句话以内,要生动、有吸引力,并且通俗易懂。" +
"只返回改写后的故事内容,不要包含任何解释或多余文字。原始故事如下:{{story}}")
@Agent(description = "根据目标读者对故事进行润色与改写")
String editStory(@V("story") String story,
@V("audience") String audience);
}
设计要点:
- 接收两个变量:
- story:上一阶段的创作结果
- audience:目标读者(例如“小学生”“开发者”“职场新人”等)
- Prompt 中明确约束:
- 输出长度(≤ 3 句)
- 风格要求(生动、有吸引力、通俗易懂)
- 不要解释,只给结果文本
💡 小结:到这里,我们只是定义了「能力接口」,真正的 Agent 实例将在配置类里通过AgenticServices 生成。
2.3 配置大模型:对接 Xinference + Qwen2.5
package org.example.config;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class LlmConfig {
@Bean
public ChatModel xinferenceChatModel(
@Value("${xinference.base-url}") String baseUrl,
@Value("${xinference.api-key}") String apiKey,
@Value("${xinference.model-name}") String modelName) {
return OpenAiChatModel.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.modelName(modelName)
.temperature(0.8)
.timeout(Duration.ofSeconds(60))
.maxRetries(2)
.build();
}
}
说明:
- 使用 OpenAiChatModel 兼容 OpenAI 协议,对接 Xinference
- 关键参数:
- baseUrl:Xinference 的 HTTP 地址
- apiKey:本地可写占位,如 not-used
- modelName:例如 qwen2.5-instruct
- temperature:0.8,稍微有点“会写故事”的发挥空间
2.4 组装多 Agent 工作流:AgentConfig
package org.example.config;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.service.tool.ToolProvider;
import org.example.agents.AudienceEditor;
import org.example.agents.CreativeWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
1. 使用 AgenticServices 把接口变成真正的 Agent Bean,并组装成一个顺序工作流
*/
@Configuration
public class AgentConfig {
/**
* CreativeWriter Agent:输出写到 agentic scope 的 "story" key
*/
@Bean
public CreativeWriter creativeWriter(ChatModel xinferenceChatModel, ToolProvider mcpToolProvider) {
return AgenticServices.agentBuilder(CreativeWriter.class)
.chatModel(xinferenceChatModel)
.outputKey("story")
.build();
}
/**
* AudienceEditor Agent:最终结果写到 "finalStory"
*/
@Bean
public AudienceEditor audienceEditor(ChatModel xinferenceChatModel, ToolProvider mcpToolProvider) {
return AgenticServices.agentBuilder(AudienceEditor.class)
.chatModel(xinferenceChatModel)
.outputKey("finalStory")
.build();
}
/**
* 多 Agent 顺序协同:
* 1)CreativeWriter:根据 topic 生成初稿(story)
* 2)AudienceEditor:根据 audience + story 做润色,产出 finalStory
*/
@Bean
public UntypedAgent storyPipeline(CreativeWriter creativeWriter,
AudienceEditor audienceEditor) {
return AgenticServices.sequenceBuilder()
.subAgents(creativeWriter, audienceEditor)
.outputKey("finalStory")
.build();
}
}
这里是整个 Demo 的关键:
-
AgenticServices.agentBuilder:
- 把接口转为可调用的 Agent Bean
- 绑定使用哪个 ChatModel
- 可选绑定 ToolProvider(例如 MCP 工具)
- 通过 outputKey 把输出写入「Agentic 上下文」
-
AgenticServices.sequenceBuilder():
- 用 subAgents(creativeWriter, audienceEditor) 定义执行顺序
- 整个 pipeline 的最终输出使用 outputKey(“finalStory”)
✅ 你可以把这个 pipeline 理解成一个「小型 AI 工作室」: 前半段是「创作部」,后半段是「编辑部」。
2.5 暴露 HTTP 接口:StoryController
package org.example.controller;
import dev.langchain4j.agentic.UntypedAgent;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class StoryController {
@Resource
private UntypedAgent storyPipeline;
@GetMapping("/story")
public String createStory(@RequestParam("topic") String topic,
@RequestParam(name = "audience", defaultValue = "开发") String audience) {
return (String) storyPipeline.invoke(Map.of("topic", topic, "audience", audience));
}
}
说明:
-
注入的是前面组装好的 UntypedAgent storyPipeline
-
接口入参:
- topic:故事主题(必填)
- audience:读者人群(可选,默认 “开发”)
-
核心调用:
- storyPipeline.invoke(input),其中 input 是一个 Map:
- topic 会被 CreativeWriter 的 @V(“topic”) 使用
- audience 会被 AudienceEditor 的 @V(“audience”) 使用
- storyPipeline.invoke(input),其中 input 是一个 Map:
-
返回值直接就是 finalStory,一个已经针对目标读者优化过的故事
2.6 配置文件 application.properties
server.port=8081
xinference.base-url=http://本地ip:9997/v1
xinference.api-key=not-used
xinference.model-name=qwen2.5-instruct
2.7 调用示例
GET http://localhost:8081/story?topic=今天的天气&audience=幼儿园老师
可能得到类似这样的结果(示意):
今天早上,天空像被轻轻洗过一样干净,太阳笑眯眯地从云朵后面探出头来。 小朋友们穿着彩色的小外套,在操场上开心地跑来跑去。
这样的好天气,最适合做游戏、晒太阳和和好朋友一起分享快乐啦!
总结
这一篇,我们用一个非常轻量的 Demo,走通了:
在 Java / Spring Boot 中如何:
- 定义多个职责清晰的 Agent(创作 / 润色)
- 使用 LangChain4J 的
AgenticServices把它们串成一个顺序工作流 - 对接 Xinference + 本地大模型,提供 HTTP 服务
可以看到,多 Agent 协作带来的好处包括:
- 职责解耦:每个 Agent 做一件事,Prompt 更干净、维护更轻松
- 组合灵活:可以很容易新增一个「审稿 Agent」「敏感内容检查 Agent」接到 pipeline 末尾
- 更工程化:业务层可以像调用普通 Service 一样使用 Agent 工作流
接下来,完全可以基于这个 Demo 做更多扩展,比如:
-
在
AudienceEditor中挂上 MCP 工具
让它能先查知识库 / 文档,再结合上下文做更精准的润色。 -
新增一个「风格转换 Agent」
用来控制整体输出风格:是「技术博客风」「科普文风」还是「营销文案风」。 -
把
storyPipeline升级为「可配置的多 Agent 流水线」
做成一个简单的「AI 工作流编排中心」,按业务场景自由组合、调整 Agent 顺序。
更多完整实战案例: https://wx.zsxq.com/group/48885482144828
1311

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



