1. Java Agent 项目从零起步:为什么我建议先用 TaoToken 统一 Key
如果你是一名 Java 后端,最近想动手做一个 AI Agent 项目,大概率会卡在第一步:模型 API 怎么接。LangChain4j 本身是 Java 生态里做 AI 应用最顺手的框架,AiService声明式接口、ChatMemory对话记忆、RAG 检索增强,这几块拼起来就是一个能用的智能体骨架。但真正落地时,你会发现每个模型厂商的 Key、Base URL、模型名都不一样,项目里散落一堆配置,换个模型就要改代码。
我这次的做法是:用 TaoToken 作为统一的 API 通道,把 Key 和 Base URL 收敛成一份配置,LangChain4j 只认 OpenAI 兼容协议,底层换什么模型对业务代码透明。这样AiService和ChatMemory的写法完全不变,RAG 的 Embedding 也能走同一条通道。适合谁?适合已经会 Spring Boot、想用 Java 而不是 Python 做 Agent 的开发者,也适合团队里需要统一模型接入层、不想每个项目重复造轮子的场景。
这篇会交付四样东西:可复制的pom.xml依赖、application.yml配置骨架、最小可运行的 Agent 示例(含AiService+ChatMemory),以及 RAG 检索结果的验证动作。全程按「能跑起来」的标准写,不堆概念。
2. TaoToken 前置准备:拿 Key、认通道、配环境
TaoToken 在这里扮演的角色是「统一入口」:你只需要一个 Key,就能通过 OpenAI 兼容协议访问背后的模型能力。对 LangChain4j 来说,它就是一个标准的 OpenAI 端点,所以langchain4j-open-ai-spring-boot-starter可以直接用,不需要额外的适配器。
第一步,去控制台创建 API Key。地址是https://taotoken.net/console,登录后在 API Keys 页面新建一个,复制出来先存好,后面配置里要用。注意 Key 只在创建时完整显示一次,丢了就重新建。
第二步,确认 API 通道地址。TaoToken 的 API 根地址是https://taotoken.net/api,在 LangChain4j 里配置base-url时填这个。模型名按你实际要用的填,比如gpt-4o-mini这类 OpenAI 兼容命名,具体以控制台模型列表为准。
第三步,环境准备。JDK 17 或以上,Maven 3.8+,IDEA 或 VS Code 都行。我试过用 Spring Initializr 生成骨架,勾选 Spring Web 和 Lombok 就够,剩下的依赖手动加。如果你习惯命令行,也可以直接mvn archetype:generate,但 Initializr 更省事。
注意:Key 不要硬编码进代码提交到仓库,本地用
application-local.yml或者环境变量注入,生产环境走配置中心。
3. 可复制配置:pom 依赖与 application.yml 骨架
3.1 pom.xml 依赖清单
LangChain4j 的版本迭代比较快,这里用0.35.0作为基线,Spring Boot 用3.2.x。核心依赖就四个:Web、LangChain4j 核心 starter、OpenAI 兼容 starter、以及测试。
<properties> <java.version>17</java.version> <langchain4j.version>0.35.0</langchain4j.version> </properties> <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- LangChain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- OpenAI 兼容通道,TaoToken 走这个 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 本地 Embedding 模型,RAG 用,不依赖外部服务 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings-all-minilm-l6-v2</artifactId> <version>${langchain4j.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>这里有个坑:langchain4j-embeddings-all-minilm-l6-v2会下载一个几十 MB 的本地模型文件,第一次构建会慢,但之后离线也能用,适合学习阶段。
3.2 application.yml 配置骨架
配置分三块:服务端口、TaoToken 通道、LangChain4j 的 OpenAI 参数。注意base-url填 TaoToken 的 API 地址,api-key填你控制台拿到的 Key。
server: port: 8080 langchain4j: open-ai: chat-model: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model-name: gpt-4o-mini temperature: 0.7 timeout: PT60S streaming-chat-model: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model-name: gpt-4o-miniTAOTOKEN_API_KEY通过环境变量注入,IDEA 里在 Run Configuration 的 Environment variables 里加一行就行。这样配置的好处是:以后要换模型,只改model-name,AiService和ChatMemory的代码一行不动。
4. 最小可运行 Agent:AiService + ChatMemory + RAG
4.1 定义 AiService 接口
AiService是 LangChain4j 最舒服的地方:你只写接口,框架自动生成实现。加上@SystemMessage设定角色,加上@MemoryId让对话记忆按用户隔离。
package com.example.agent.service; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.spring.AiService; @AiService public interface AgentAssistant { @SystemMessage("你是一个 Java AI 助手,回答简洁、专业,涉及代码时给出可运行示例。") String chat(@MemoryId String userId, @UserMessage String message); }4.2 配置 ChatMemoryProvider
ChatMemory的本质是保存历史对话,下一次提问时一起发给模型。MessageWindowChatMemory.withMaxMessages(10)表示保留最近 10 条消息,超出就丢弃最早的。多用户场景下,用memoryId区分不同会话。
package com.example.agent.config; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MemoryConfig { @Bean public ChatMemoryProvider chatMemoryProvider() { return memoryId -> MessageWindowChatMemory.withMaxMessages(10); } }4.3 挂载 RAG 知识库
RAG 的流程是:文档切块 → 向量化 → 存向量库 → 用户提问时检索最相似片段 → 拼进 Prompt。这里用内存向量库和本地 Embedding 模型,学习阶段够用,不用额外服务。
package com.example.agent.config; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.document.parser.TextDocumentParser; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.embedding.AllMiniLmL6V2EmbeddingModel; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.nio.file.Paths; import java.util.List; @Configuration public class RagConfig { @Bean public EmbeddingStore<TextSegment> embeddingStore() { return new InMemoryEmbeddingStore<>(); } @Bean public EmbeddingModel embeddingModel() { return new AllMiniLmL6V2EmbeddingModel(); } @Bean public EmbeddingStoreContentRetriever contentRetriever( EmbeddingStore<TextSegment> store, EmbeddingModel model) { return EmbeddingStoreContentRetriever.builder() .embeddingStore(store) .embeddingModel(model) .maxResults(3) .minScore(0.6) .build(); } @Bean public Void initRag(EmbeddingStore<TextSegment> store, EmbeddingModel model) { Document doc = FileSystemDocumentLoader.loadDocument( Paths.get("documents/my-info.txt"), new TextDocumentParser()); List<TextSegment> segments = DocumentSplitters.recursive(300, 30).split(doc); List<Embedding> embeddings = model.embedAll(segments).content(); store.addAll(embeddings, segments); System.out.println("RAG 初始化完成,片段数:" + segments.size()); return null; } }documents/my-info.txt放在项目根目录,内容随便写几段你的业务说明,比如产品 FAQ、接口文档摘要。maxResults(3)表示每次检索返回最相似的 3 个片段,minScore(0.6)过滤掉相似度太低的,避免无关内容污染 Prompt。
4.4 Controller 暴露接口
package com.example.agent.controller; import com.example.agent.service.AgentAssistant; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class AgentController { private final AgentAssistant assistant; public AgentController(AgentAssistant assistant) { this.assistant = assistant; } @GetMapping("/chat") public String chat(@RequestParam String userId, @RequestParam String msg) { return assistant.chat(userId, msg); } }5. 验证请求:对话记忆与 RAG 检索是否生效
5.1 验证 ChatMemory
启动项目,先发两条消息,看第二条能不能记住第一条的内容。
curl "http://localhost:8080/chat?userId=user1&msg=我叫张三,在做Java Agent项目" curl "http://localhost:8080/chat?userId=user1&msg=我叫什么?在做什么?"预期第二条返回里包含「张三」和「Java Agent」。如果返回的是「我不知道」,说明ChatMemoryProvider没生效,检查@MemoryId参数名和ChatMemoryProviderBean 是否都被 Spring 扫描到。
再换一个userId=user2问同样的问题,应该答不出来,说明记忆是按用户隔离的。
5.2 验证 RAG 检索
在documents/my-info.txt里写一句独特的内容,比如「本项目的内部代号是 BlueWhale,部署端口是 18080」。然后提问:
curl "http://localhost:8080/chat?userId=user1&msg=本项目的内部代号是什么?"如果返回「BlueWhale」,说明 RAG 链路通了:文档被切块、向量化、存入内存库,提问时检索到了相关片段并拼进了 Prompt。如果答不出来,先看启动日志里「RAG 初始化完成,片段数:N」的 N 是不是 0,是 0 说明文件路径不对或文件为空。
5.3 验证模型通道
单独发一条不依赖记忆和 RAG 的问题,确认 TaoToken 通道本身是通的:
curl "http://localhost:8080/chat?userId=test&msg=用一句话解释什么是向量数据库"能正常返回就说明 Key、Base URL、模型名三者匹配。如果报 401,检查 Key;报 404,检查base-url和model-name。
6. 本篇常见错排查
报错一:No AiService bean found。通常是@AiService注解的包不在 Spring 扫描范围内。把接口放在启动类同级或子包下,或者用@AiService的scan属性指定包名。
报错二:Connection refused或超时。检查base-url是不是https://taotoken.net/api,注意结尾不要多斜杠。如果公司网络有出口限制,确认能访问该域名。
报错三:ChatMemory 不生效,每次都是新对话。最常见的原因是@MemoryId参数没加,或者ChatMemoryProviderBean 没定义。LangChain4j 只有在检测到@MemoryId且存在ChatMemoryProvider时才会启用记忆。
报错四:RAG 检索结果不相关。调minScore,默认 0.6 偏高,可以降到 0.5 试试;或者调maxResults,从 3 加到 5。另外文档切块大小300和重叠30也要根据文档类型调,技术文档可以小一点,FAQ 可以大一点。
报错五:本地 Embedding 模型下载失败。第一次构建需要联网下载模型文件,如果卡住,检查 Maven 仓库配置,或者手动下载后放到本地仓库。这个模型只有几十 MB,正常网络几分钟就好。
报错六:流式输出没反应。如果用Flux<String>做 SSE,Controller 的produces必须是MediaType.TEXT_EVENT_STREAM_VALUE,且前端要用EventSource接收。普通curl看不到流式效果,用浏览器或curl -N才行。
7. 下一步:把 Key 管好,把 Agent 跑远
项目跑通之后,真正要长期维护的是接入层。TaoToken 在这里的价值不是「多一个通道」,而是把 Key 管理、模型切换、用量查看收敛到一个地方。你可以在控制台看到每个 Key 的调用情况,换模型时只改application.yml里的model-name,AiService、ChatMemory、RAG 的代码完全不用动。
如果你准备把这个 Agent 用到长期编码或自动化任务里,可以看看 Coding Plan,它更适合持续性的开发场景;如果只是想先验证模型对话效果,模型对话页面可以直接试;接入过程中遇到 Key 或通道问题,API Keys 页面和接入文档里有完整的参数说明。先把最小闭环跑起来,再逐步加 Function Calling 和 MCP,这条路我走过,顺序对了就不容易卡住。