1. 从 Yu-AI-Agent 的对话主链路说起:AI 恋爱大师智能体到底跑通了什么
Yu-AI-Agent 这个项目,我第一次翻它代码的时候,最直观的感受是:它把「一个能聊天的 AI 恋爱大师」拆成了一条特别清晰的链路。你如果只盯着前端页面看,会觉得它就是个聊天框;但把后端yu-ai-agent-admin模块打开,会发现真正干活的是 Spring AI 的ChatClient加上一层自定义 Advisor,再叠一个文件持久化的ChatMemory。这条链路跑通之后,多轮对话、上下文记忆、RAG 知识库检索、工具调用都能挂上去,AI 恋爱大师只是它最外层的一个业务壳。
所以这篇学习笔记不打算泛泛讲「AI Agent 是什么」,而是聚焦一件事:怎么在本地把 Yu-AI-Agent 的对话主链路复现出来,并且把模型调用端点切到 TaoToken 的统一 Key/API 通道,完成一次真实问答回环。适合谁看?适合已经会写 Spring Boot、想照着跑通一个 AI 恋爱大师 Demo 的 Java 开发者。你不需要先懂 RAG、不需要先配 PGVector,只要 JDK 21 + Maven 能跑起来,就能先把「对话」这一层跑通。
项目本身基于 Spring Boot 3.5 + Java 21 + Spring AI 1.0 构建,后端分了几个模块:yu-ai-agent-admin是核心应用,里面有advisor/、agent/、chatmemory/、config/、controller/、rag/、service/、tools/这些包;yu-image-search-mcp-server是独立的 MCP 图片搜索服务模块。对话主链路的核心在service/下的LoveAppService,它用ChatClient发起调用,用自定义 Advisor 做日志和 Re-Reading,用FileBasedChatMemory+ Kryo 序列化把会话状态落盘。理解这条链路,比一上来就啃 ReActAgent 要实际得多。
我试过先把 RAG 和 MCP 全部注释掉,只留LoveAppService和一个内存版 ChatMemory,结果发现连「你好,我最近失恋了」这种最简单的多轮对话都跑不通——问题不在业务代码,而在模型端点配置。默认配置指向的是 DashScope 的qwen-plus,如果你手头没有对应 Key,或者想统一走一个兼容 OpenAI 协议的通道,就得改application.yml里的 base-url 和 api-key。这也是为什么这篇笔记会把「改端点」当成一个独立章节来写,而不是塞在快速开始里一笔带过。
再补一句场景上的判断:AI 恋爱大师这个 Demo 的价值不在于「恋爱」两个字,而在于它把多轮会话记忆这件事做成了可复现的骨架。你把这套骨架换成客服、换成学习助手、换成任何需要记住上下文的场景,代码结构几乎不用动。所以下面我会按「工程结构 → 依赖 → 配置 → 验证 → 排障」的顺序走,每一步都给可复制的片段。
2. TaoToken 前置准备:统一 Key/API 通道怎么接进 Spring AI
在改application.yml之前,先把 TaoToken 这一侧准备好。TaoToken 在这里扮演的角色是统一的模型调用端点:你不需要为每个模型厂商单独维护一套 Key 和 base-url,而是拿一个统一 Key,通过兼容 OpenAI 协议的 API 通道去调不同模型。对 Yu-AI-Agent 这种用 Spring AI 的项目来说,好处是spring.ai.openai那一套配置可以直接复用,不用改业务代码。
第一步是拿 Key。打开官网https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,注册后在控制台里创建 API Key。控制台地址是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite,API Keys 管理页在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite。Key 拿到后先别急着写进代码,建议放到环境变量里,避免提交到 Git。
第二步是确认 API 端点。TaoToken 的 API 根地址是https://taotoken.net/api,注意这个地址后面不加 UTM 参数,直接作为 base-url 使用。Spring AI 的 OpenAI starter 默认会拼/v1/chat/completions,所以你在配置里填的 base-url 应该是https://taotoken.net/api,让它自己去拼路径。如果你填成带/v1的地址,可能会出现路径重复导致 404。
第三步是选模型。TaoToken 支持多种模型,你在application.yml里通过spring.ai.openai.chat.options.model指定 Model ID。比如你想用 Claude 系列做对话,就填对应的模型标识;想用 GPT 系列,就换另一个标识。这里的关键是:Base URL + Key + Model ID 三件套必须一致,缺一个都会在启动或首次请求时报错。我见过最常见的坑是 Key 填对了、base-url 填对了,但 model 还留着默认的gpt-3.5-turbo,结果请求发出去返回模型不存在。
如果你后面要接 Claude Code 或者做 Agent 类长期编码任务,TaoToken 也有对应的 Coding Plan 入口:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。不过对 Yu-AI-Agent 这个 Demo 来说,先用按量计费的 API Key 就够了,没必要一上来就上套餐。
还有一点要提醒:Spring AI 1.0 对 OpenAI 兼容端点的支持是通过spring.ai.openai这组配置项完成的,但 Yu-AI-Agent 原始代码里用的是spring.ai.dashscope。你要么把 dashscope 的配置替换成 openai 的,要么在代码里显式构造一个指向 TaoToken 的OpenAiChatModelBean。前者改动小,后者更灵活。下面第 3 节我会给两种方式的配置片段,你按自己项目的实际情况选。
3. 可复制配置:把 Yu-AI-Agent 的模型端点改到 TaoToken
这一节是整篇的核心,直接给可复制的配置。先看目录:Yu-AI-Agent 的主配置文件在yu-ai-agent-admin/src/main/resources/application.yml,数据源配置在application-druid.yml,MCP 服务端配置在mcp-servers.json。我们只动application.yml。
3.1 方式一:直接替换 spring.ai 配置块
原始配置大概是这样的:
spring: ai: dashscope: api-key: 替换为您的API_KEY chat: options: model: qwen-plus改成走 TaoToken 的 OpenAI 兼容通道:
spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: claude-3-5-sonnet-20241022 temperature: 0.7这里api-key用了环境变量占位符${TAOTOKEN_API_KEY},启动前在 shell 里export TAOTOKEN_API_KEY=你的Key即可。model那一行换成你在 TaoToken 控制台里确认可用的 Model ID。temperature是可选参数,恋爱大师这种场景 0.7 左右比较自然。
注意:如果你同时保留了spring.ai.dashscope和spring.ai.openai两个块,Spring AI 可能会因为多个 ChatModel Bean 冲突而启动失败。要么删掉 dashscope 块,要么用@Primary指定。最省事的做法是只留 openai 一块。
3.2 方式二:显式声明 OpenAiChatModel Bean
如果你不想动application.yml里的 dashscope 配置(比如项目里其他地方还在用),可以在config/包下加一个配置类:
@Configuration public class TaoTokenChatModelConfig { @Bean @Primary public OpenAiChatModel taoTokenChatModel( @Value("${taotoken.base-url}") String baseUrl, @Value("${taotoken.api-key}") String apiKey, @Value("${taotoken.model}") String model) { OpenAiApi api = OpenAiApi.builder() .baseUrl(baseUrl) .apiKey(apiKey) .build(); return OpenAiChatModel.builder() .openAiApi(api) .defaultOptions(OpenAiChatOptions.builder() .model(model) .temperature(0.7) .build()) .build(); } }然后在application.yml里加:
taotoken: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model: claude-3-5-sonnet-20241022这种方式的好处是配置和代码分离,切换模型只改 yml,不用重新编译。坏处是多了一个 Bean,要确保@Primary生效,否则LoveAppService注入的ChatClient可能还是指向 dashscope。
3.3 ChatMemory 与 Advisor 的配置
对话主链路里,ChatMemory决定了多轮会话能不能记住上下文。Yu-AI-Agent 用的是FileBasedChatMemory+ Kryo 序列化,配置在chatmemory/包下。核心是给ChatClient挂上 memory advisor:
ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors( new MessageChatMemoryAdvisor(chatMemory), new SimpleLoggerAdvisor() ) .build();MessageChatMemoryAdvisor负责把历史消息塞进 prompt,SimpleLoggerAdvisor负责打日志方便排障。如果你只想先跑通,可以把FileBasedChatMemory换成InMemoryChatMemory,省掉 Kryo 序列化的坑。等对话跑通了再换回文件持久化。
3.4 关键参数对照表
| 配置项 | 原始值 | TaoToken 值 | 说明 |
|---|---|---|---|
| base-url | DashScope 默认 | https://taotoken.net/api | 不加/v1,让 starter 自己拼 |
| api-key | DashScope Key | TaoToken Key | 建议走环境变量 |
| model | qwen-plus | 控制台确认的 Model ID | 三件套必须一致 |
| temperature | 默认 | 0.7 | 恋爱场景偏自然 |
| memory | 无 | MessageChatMemoryAdvisor | 多轮对话必需 |
注意:改完配置后,先别急着启动整个应用。建议先写一个最小的
@SpringBootTest或者CommandLineRunner,只调一次chatClient.prompt().user("你好").call().content(),确认端点通了再跑完整业务。
4. 启动验证:一次真实问答回环怎么跑
配置改完,接下来是验证。Yu-AI-Agent 的启动命令在 README 里写得很清楚:
mvn clean package -DskipTests java -jar yu-ai-agent-admin/target/yu-ai-agent-admin-0.0.1-SNAPSHOT.jar启动前确认环境变量已导出:
export TAOTOKEN_API_KEY=你的Key启动成功后,控制台会打印 Tomcat 端口,默认 8123。接口文档在http://localhost:8123/api/doc.html,这是 Knife4j 的页面。你可以直接在页面上找到 AI 对话接口,也可以先用 curl 验证。
4.1 用 curl 打一次对话接口
假设对话接口路径是/ai/love_app/chat/sync(具体以你项目 controller 为准),请求体:
curl -X POST http://localhost:8123/api/ai/love_app/chat/sync \ -H "Content-Type: application/json" \ -d '{"message":"我最近和对象总是吵架,怎么办?","chatId":"test-001"}'如果返回里带了模型生成的建议文本,说明整条链路通了:Controller → LoveAppService → ChatClient → TaoToken 端点 → 模型 → 返回。chatId是多轮会话的标识,同一个 chatId 连续发两次,第二次应该能感知到第一次的上下文。
4.2 验证多轮记忆
第一次请求:
curl -X POST http://localhost:8123/api/ai/love_app/chat/sync \ -H "Content-Type: application/json" \ -d '{"message":"我叫小明,我女朋友叫小红","chatId":"test-002"}'第二次请求,同一个 chatId:
curl -X POST http://localhost:8123/api/ai/love_app/chat/sync \ -H "Content-Type: application/json" \ -d '{"message":"你还记得我女朋友叫什么吗?","chatId":"test-002"}'如果第二次回答里出现了「小红」,说明MessageChatMemoryAdvisor+FileBasedChatMemory生效了。如果第二次回答完全不知道小红是谁,那就是 memory 没挂上,或者 chatId 没传对。
4.3 看日志确认端点
SimpleLoggerAdvisor会把请求和响应打到日志里。你可以在日志里搜taotoken.net,确认请求确实发到了 TaoToken 而不是默认端点。如果日志里出现的是dashscope.aliyuncs.com,说明配置没生效,Bean 冲突了。
4.4 结构化输出验证
Yu-AI-Agent 支持生成恋爱报告这类结构化 JSON 输出。你可以调对应的接口,看返回是不是合法 JSON。如果模型返回的是带 markdown 代码块的 JSON,需要在 prompt 里明确要求「只返回 JSON,不要包裹代码块」,或者在代码里做一次清洗。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节按真实报错来。下面这几个是我在接 TaoToken 时实际遇到过的,按出现频率排序。
5.1 401 Unauthorized
报错长这样:
401 Unauthorized: {"error":{"message":"Invalid API key","type":"invalid_request_error"}}原因基本是 Key 没传对。检查三处:环境变量TAOTOKEN_API_KEY是否导出成功(echo $TAOTOKEN_API_KEY);application.yml里是否写成了${TAOTOKEN_API_KEY}而不是硬编码了一个旧 Key;Key 本身是否在控制台被删除或过期。还有一种情况是 Key 前后带了空格,YAML 里不容易看出来,建议用引号包起来。
5.2 local proxy failed
这个报错通常出现在你本地配了某些网络工具,或者 Spring AI 的 HTTP 客户端走了系统代理。报错信息类似:
java.net.ConnectException: local proxy failed处理方式是检查 JVM 启动参数里有没有-Dhttp.proxyHost之类的设置,以及系统环境变量HTTP_PROXY/HTTPS_PROXY是否指向了一个不可用的地址。把代理相关配置清掉,让请求直连taotoken.net即可。注意这里说的是清掉本地代理配置,不是让你去配代理。
5.3 reading choices 相关报错
报错类似:
Cannot deserialize value of type ... from Array value (token `JsonToken.START_ARRAY`) ... reading choices这是响应体结构和 Spring AI 期望的不一致。常见原因是 base-url 填错了,比如填成了https://taotoken.net/api/v1,导致实际请求路径变成/api/v1/v1/chat/completions,返回了一个非标准结构的错误页。把 base-url 改回https://taotoken.net/api即可。另一个原因是 model 填了一个不存在的 ID,端点返回了错误 JSON,反序列化时炸在choices字段上。
5.4 OAuth 相关报错
如果你在配置里误用了某些需要 OAuth 的端点,或者 Key 类型选错了,可能会看到:
OAuth token is missing or invalidTaoToken 的 API Key 是直接放在Authorization: Bearer头里的,不需要走 OAuth 流程。检查你的OpenAiApi构造是不是用了apiKey而不是credential。Spring AI 的OpenAiApi.builder().apiKey(...)会自动拼 Bearer 头,不用手动加。
5.5 三件套自查清单
出现任何连接类或鉴权类报错,先按这个清单过一遍:
| 检查项 | 正确值 | 常见错误 |
|---|---|---|
| Base URL | https://taotoken.net/api | 多写/v1 |
| API Key | 控制台创建的 Key | 用了旧 Key 或带空格 |
| Model ID | 控制台确认可用 | 留了默认gpt-3.5-turbo |
| 环境变量 | 已 export | 只在 IDE 里配了没生效 |
提示:如果你用的是 Cline MCP 或者 Claude Code 这类工具接 TaoToken,配置项名称可能不同,但 Base URL + Key + Model ID 这三件套的逻辑是一样的。Claude Code 的配置入口在
https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite,里面有对应的接入说明。
6. 把对话骨架用起来:从恋爱大师到你的 Agent
跑通一次问答回环之后,Yu-AI-Agent 的对话骨架其实已经在你手里了。LoveAppService里那套ChatClient+ Advisor + ChatMemory 的组合,换成任何业务场景都成立。你想做客服助手,就把 system prompt 换掉;想做学习助手,就把 RAG 知识库换成你的文档;想做代码助手,就把工具集换成文件读写和终端执行。
下一步可以做的几件事:把InMemoryChatMemory换回FileBasedChatMemory,验证 Kryo 序列化在重启后还能恢复会话;把 RAG 链路接上,用 PGVector 存你的领域文档;把mcp-servers.json里的图片搜索服务启起来,试试工具调用。这些都在同一个工程结构里,不用重新搭架子。
如果你后面要长期跑 Agent 类任务,比如让智能体自己规划多步操作,可以看看 TaoToken 的 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。模型对话调试可以用https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。API Key 管理还是那个地址:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite。
最后留一个我踩过的坑:改完application.yml后一定要mvn clean package重新打包,因为target/里可能还留着旧的application.yml。直接java -jar跑旧包,配置改了也不生效,你会以为是端点问题,其实是打包问题。