定位:这是一份全量字典——§1~§12 按模块收录实战项目用到的(和它依赖的)全部 Spring AI API:方法签名、传参、返回值、真实用例。
用法:写代码卡住 → Ctrl+F12 看类全貌 → 翻本文对签名;每条尽量附实战用例(以类名标注),没标的是官方通用 API。
版本:Spring AI 1.1.0 + Spring AI Alibaba 1.1.2.0 + Boot 3.5.6 + Java 21;模型接入走 spring-ai-starter-model-openai(DashScope 兼容模式)。


§1 模型接入:ChatModel 层

配置入口(application.yml):spring.ai.openai.api-key / base-url / chat.options.model → starter 自动装配 ChatModel Bean,任何组件 @Autowired 直用。

ChatModel 方法传什么返回什么实战用例
call(String message)一句话String 纯文本PlanExecuteAgent
call(Message... messages)若干条消息String—(少用)
call(Prompt prompt)Prompt 对象ChatResponseContextCompactor
stream(Prompt prompt)PromptFlux<ChatResponse>—
stream(String message)一句话Flux<ChatResponse>—

ChatResponse 内部字段解剖(响应侧其实只有两层包装):

ChatResponse
├─ generations : List<Generation>      一般只取第一个 → getResult()
│    └─ Generation
│         ├─ output  : AssistantMessage   ← 模型本轮说的话
│         │    ├─ text      : String            正文(getText())
│         │    └─ toolCalls : List<ToolCall>    想调的工具getToolCalls(),可为 null/空
│         └─ metadata : ChatGenerationMetadata
└─ metadata : ChatResponseMetadata      token 用量(usage)、id 等
  • 快捷链:response.getResult().getOutput().getText()(见 TemplateStrategy)
  • getResults() 返回 List<Generation>(多候选时用,一般只用 getResult)
  • 判空三连:chunk / getResult() / getOutput() 都可能为 null(WebSearchReactAgent 里就是这么防的;流式里另加 .filter(Objects::nonNull))

§2 Prompt 与 Message 家族

请求侧内部字段解剖——先纠正一个常见误解:Prompt 没有单独的 system/user/assistant 字段。它只有两个字段:一个消息列表 + 一个参数对象;System/User/Assistant 是列表里每个元素的角色类型:

Prompt
├─ instructions : List<Message>     ← 字段1:消息列表,顺序 = 模型看到的顺序
│    ├─ [0] SystemMessage        (MessageType.SYSTEM)    人设/规则 —— 通常一条,放最前
│    ├─ [1] UserMessage          (MessageType.USER)      用户说的话
│    ├─ [2] AssistantMessage     (MessageType.ASSISTANT) 模型上一轮的回答(多轮时)
│    └─ [3] ToolResponseMessage  (MessageType.TOOL)      工具执行结果(ReAct 时)
└─ options : ChatOptions            ← 字段2:参数,可为 null(温度/工具都藏在这)
  • Message 是接口,只有 4 个实现类;每个实现用 getMessageType() 报告自己的角色,枚举 MessageType = SYSTEM / USER / ASSISTANT / TOOL
  • 判断一条消息是谁的:instanceof 或 getMessageType()(TokenEstimator 里用的是 instanceof)
  • 发给模型时,每条消息序列化成 {"role":"system|user|assistant|tool", "content":"..."}——curl/F12 里看到的 JSON 就是这么来的;"角色"只是消息上的一个标注字段
  • 多轮对话的真相:所谓记忆,就是每次把 [0]~[n] 全量重放、只追加本轮新消息,[2] 那种 AssistantMessage 就是从 ChatMemory 里回灌的历史回答
类怎么创建角色实战用例
SystemMessagenew SystemMessage(String)人设与规则,每轮单独加、不进记忆WebSearchReactAgent
UserMessagenew UserMessage(String) 或 builder().text(...).build()用户输入FileReactAgent
AssistantMessagenew AssistantMessage(String);带工具时 builder().toolCalls(...).build()模型回复(读取或回填)SimpleReactAgent / WebSearchReactAgent
ToolResponseMessagebuilder().responses(List<ToolResponse>).build()工具执行结果回填WebSearchReactAgent
Prompt见下方构造器全表消息+选项的打包PlanExecuteAgent

Prompt 构造器全表(5 个重载)——它内部只有两个字段:instructions(消息列表)和 options(参数,可为 null),所以所有写法殊途同归:

构造器说明
new Prompt(String contents)一句话,内部自动包成单条 UserMessage
new Prompt(String contents, ChatOptions options)一句话 + 参数
new Prompt(Message... messages)可变参数 ≡ new Prompt(List.of(m1, m2))
new Prompt(List<Message> messages)消息列表(实战项目最常用,见 PlanExecuteAgent)
new Prompt(List<Message> messages, ChatOptions options)列表 + 参数——工具、温度都从这进

读回:prompt.getInstructions() → List<Message>;prompt.getOptions() → ChatOptions(可能为 null)。

三种"发消息"最终汇成同一个 Prompt:
chatModel.call("你好") ≡ chatModel.call(new Prompt("你好")) ≡ chatClient.prompt().user("你好").call().content()。
区别只在谁帮你包装:ChatClient 帮你包消息;要塞 options 就走 Prompt 双参构造或 .options()。

两个关键 record(不可变数据载体):

AssistantMessage.ToolCall(id, type, name, arguments)   // 访问器 id() / name() / arguments()
// arguments 是 JSON 字符串,流式下会跨 chunk 分片(见 §5)
ToolResponseMessage.ToolResponse(id, name, responseData) // id 必须原样回流配对(WebSearchReactAgent)
  • 多模态:UserMessage.builder() 挂 Media(图片理解,见 FileManageService)
  • 实战惯例:XML 风格标签包裹字段,帮模型识别边界:"<question>" + q + "</question>"、<fileid>(见 FileReactAgent)

§3 ChatOptions:每次调用的旋钮

类型适用备注
ChatOptions通用参数model / temperature / topP / maxTokens / stopSequences
ToolCallingChatOptions通用参数 + 工具四件套实战项目全 Agent 用它(见 WebSearchReactAgent)
OpenAiChatOptions 等厂商专用参数示例项目未直接用

ToolCallingChatOptions.builder() 全方法:

工具相关(放链式前面)通用参数(放后面)
toolCallbacks(ToolCallback...)model(String)
toolNames(String...)temperature(Double)
internalToolExecutionEnabled(Boolean)maxTokens(Integer)
toolContext(Map)topP / topK / stopSequences
  • 链式顺序坑:工具方法返回工具 Builder,通用方法返回通用 Builder——先工具后通用怎么写都不断,反过来可能断
  • 唯一传入口:new Prompt(messages, options) 第二参数——忘传 = 全白设
  • internalToolExecutionEnabled(false) 是实战项目的灵魂开关:关掉框架自动执行工具,自己接管循环(§5)

§4 ChatClient:高层流式 API

获取 ChatClient 的三种方式:

方式签名场景
快速创建ChatClient.create(chatModel)不需要预设任何默认值
Builder(实战项目主用)ChatClient.builder(chatModel) → 返回 ChatClient.Builder预设默认 system/选项/工具后 build()
派生existing.mutate() → 返回 ChatClient.Builder保留现有默认值、微调后重建(见 EmbeddingService)

ChatClient.Builder 全方法(每个都返回 Builder 本身,可任意链式,build() 收口):

方法作用
defaultSystem(String)全局系统提示(另有 Consumer 重载:s -> s.text(...).param(...))
defaultUser(String)全局用户消息模板
defaultOptions(ChatOptions)默认参数——实战项目在这塞了 ToolCallingChatOptions
defaultToolCallbacks(ToolCallback...) / (List<ToolCallback>) / (ToolCallbackProvider)默认工具
defaultToolNames(String...)只放行指定名字的工具
defaultAdvisors(Advisor...) / (List<Advisor>)默认拦截器
build()产出 ChatClient 实例

prompt() 返回的请求 spec——每次请求可覆盖默认值,不传就用 Builder 预设(WebSearchReactAgent 用了 .messages):

方法作用
.messages(List<Message>)直接塞消息列表(实战项目主链路)
.system(String) / .user(String)临时设一条 system / user 消息
.options(ChatOptions)本次请求参数(覆盖 defaultOptions)
.toolCallbacks(...) / .toolNames(...)本次请求的工具
.toolContext(Map<String, Object>)给工具透传上下文
.advisors(Advisor...)本次请求的拦截器

请求-终端两段式:

chatClient.prompt()                 // 开始一次请求(进入请求 spec)
    .messages(messages)             // 组装本次请求
    .stream()                       // 或 .call() —— 进入终端 spec
    .chatResponse()                 // → Flux<ChatResponse>
终端调用返回场景
.call().content()String一次性拿文本
.call().entity(Class)Java 对象结构化输出一步到位(§7)
.call().chatResponse()ChatResponse要元数据时
.stream().content()Flux<String>流式纯文本
.stream().chatResponse()Flux<ChatResponse>实战主链路:要拿工具调用(见 WebSearchReactAgent)
  • chatClient.mutate():从现有 client 派生新 client,不重建默认配置(见 EmbeddingService)
  • 两套怎么选:一次性调用用 ChatModel 直调(简单);Agent 主链路用 ChatClient(可挂默认选项/工具/Advisor)

§5 工具调用(Tool Calling)

四步全链路:

步骤API实战用例
① 声明工具方法上 @Tool(description="...") + 参数 @ToolParam(description="...")—
② 注册ToolCallbacks.from(Object... sources) → ToolCallback[](扫 @Tool 方法)AgentController
③ 交给模型ToolCallingChatOptions.builder().toolCallbacks(tools).internalToolExecutionEnabled(false)WebSearchReactAgent
④ 手动执行callback.call(argsJson) → String(重载带 ToolContext)WebSearchReactAgent

手动执行全套 API(以下用例均出自 WebSearchReactAgent 的手动 ReAct 循环):

API作用
output.getToolCalls()模型要用的工具列表
AssistantMessage.builder().toolCalls(list).build()先回填模型意图
callback.call(tc.arguments())执行(参数就是 JSON 串,别自己再解析)
new ToolResponse(id, name, result)结果按 id 配对
ToolResponseMessage.builder().responses(list).build()再回填结果 → 下一轮
callback.getToolDefinition().name()按名字找工具

流式特有坑:工具参数 JSON 会跨 chunk 分片,必须按 id() 合并拼接(WebSearchReactAgent 的 mergeToolCall);阻塞版循环用 call() 没这个问题。

错误处理铁律:工具抛异常也要回填同 id 的错误 ToolResponse,否则下一轮可能被模型接口拒绝。

外部工具(MCP):Tavily 搜索经 MCP 协议接入,转换成同一套 ToolCallback(§10),与本地工具无差别合并(示例项目自写 ToolMergeUtils.mergeTools,见 AgentController)。

§6 ChatMemory:跨请求记忆

API作用实战用例
MessageWindowChatMemory.builder().maxMessages(n).build()滑动窗口记忆BaseAgent
add(String conversationId, Message)追加一条BaseAgent
add(String, List<Message>)批量追加—
get(String conversationId)取窗口内全部历史BaseAgent
clear(String conversationId)清空会话—

MessageWindowChatMemory 内部结构:一个 ChatMemoryRepository(默认 InMemoryChatMemoryRepository——本质是个内存 Map,存所有会话的全部消息)+ maxMessages(读取时只裁最近 n 条返回)。这解释了"跨请求但不跨重启"这个结论:会话数据住在内存 Map 里,应用一停就没了。想要跨重启:换 JdbcChatMemoryRepository,或像示例项目一样自己从 MySQL 回灌(见 BaseAgent)。Builder 链只有两步:MessageWindowChatMemory.builder().maxMessages(30).build()。

  • conversationId 是隔离键:不同会话互不可见
  • 两层分工:内存 ChatMemory 管"喂模型的上下文",跨重启靠示例项目手写的 MySQL 回灌——createPersistentChatMemory 从会话表把历史 add 回去(BaseAgent 实现,AgentController 调用)

§7 结构化输出

API作用实战用例
new BeanOutputConverter<T>(Class)简单类型PptIntentRecognizer
new BeanOutputConverter<T>(new ParameterizedTypeReference<List<T>>() {})泛型集合必须用这个PlanExecuteAgent / BaseAgent
.getFormat()生成 JSON 格式约束,拼进提示词PlanExecuteAgent
.convert(String json)模型输出 → Java 对象PptIntentRecognizer
MapOutputConverter / ListOutputConverter简单结构—
chatClient.call().entity(Class)ChatClient 一步到位(内部就是 BeanOutputConverter)—

实战项目用到的转换类型:List<PlanTask>(计划)、CritiqueResult(自评)、PptIntentResult(意图)、PptSchema(PPT 结构)。

§8 流式三件套:Flux / Sinks / Schedulers

Flux 操作符(按实战项目出场频率):

操作符一句话实战用例
map逐元素转换取文本
filter丢弃 null 块—
doOnNext旁路观察(累积/解析)WebSearchReactAgent
doOnComplete正常结束收尾(存记忆在这)WebSearchReactAgent
doOnError / doOnCancel出错 / 用户点停止WebSearchReactAgent
doFinally无论怎么结束都执行(存库)WebSearchReactAgent
subscribeOn(Schedulers.boundedElastic())切换执行线程——阻塞代码必须挪到弹性线程—
publishOn(Schedulers.boundedElastic())切换下游处理线程WebSearchReactAgent
collectList() / block()收齐/阻塞——只允许在 boundedElastic 上—

Sinks(手动往流里塞数据):

Sinks.Many<String> sink = Sinks.many().unicast().onBackpressureBuffer();
sink.tryEmitNext(event);    // 逐条塞事件
sink.tryEmitComplete();     // 收口
return sink.asFlux();       // 交给 controller

SSE 产出口:@GetMapping(produces = "text/event-stream;charset=UTF-8") 返回 Flux<String> → Spring 自动把每个元素包成一条 data:... 事件(见 AgentController)。忘写 produces = 不再是流。

§9 RAG 向量库(PGVector)

建库(按表名动态建库的工厂):

PgVectorStore.builder(jdbcTemplate, embeddingModel)
    .distanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE)   // 余弦距离
    .indexType(PgVectorStore.PgIndexType.HNSW)                    // 索引类型
    .removeExistingVectorStoreTable(false)
    .build()
    .afterPropertiesSet();   // 触发表/索引初始化(不存在则自动建)

检索四件套(EmbeddingService,文件问答的向量检索路径):

API作用
vectorStore.similaritySearch(SearchRequest)相似检索 → List<Document>
SearchRequest.builder().query(q).topK(5).filterExpression(f).build()查询条件;SearchRequest 只有 4 个字段:query / topK / similarityThreshold(0~1,越低召回越多) / filterExpression,示例项目用了其中 3 个
new FilterExpressionBuilder().eq("fileid", fileId).build()元数据过滤(只搜本文件)
Document.getId() / getText() / getScore()取内容(id 去重)

写入:vectorStore.add(List<Document>)(上传文件时入库)。

查询扩展(进阶,Multi-Query):chatClientBuilder(chatClient.mutate()).numberOfQueries(3).includeOriginal(true).build() → expand(query) → List<Query>,一个问题变三个检索视角(见 EmbeddingService)。

EmbeddingModel:embed(String) / embed(Document)——通常被 VectorStore 内部自动调用,业务代码不直调。

配置键:embeddings.store.host / port / database / user / password(application.yml)。

§10 MCP 客户端(接外部工具)

Tavily 搜索接入三件套(AgentController,MCP 客户端侧):

步骤API
传输层HttpClientStreamableHttpTransport.builder(url).requestBuilder(带 Authorization 头).build()
客户端McpClient.sync(transport).requestTimeout(Duration.ofSeconds(300)).build() → McpSyncClient
握手client.initialize()
变成本地工具SyncMcpToolCallbackProvider.builder().mcpClients(List.of(tavilyMcp)).build().getToolCallbacks() → ToolCallback[]

转换后与本地工具完全同构——这就是"工具统一抽象"的价值。MCP 服务端的搭建不在本文范围(常见传输方式:stdio / SSE / Streamable HTTP)。

§11 Advisor 扩展点

  • 概念:挂在 ChatClient 调用链上的拦截器,可改请求、改响应、加记忆、打日志
  • 接口:org.springframework.ai.chat.client.advisor.api.Advisor
  • 挂载:ChatClient.Builder.defaultAdvisors(advisors)——示例项目当前传空列表,纯扩展位;细粒度挂载用 .prompt().advisors(...)
  • 常见内置:SimpleLoggerAdvisor(请求日志)、MessageChatMemoryAdvisor(记忆托管给 ChatClient)——示例项目选择手动管理记忆,没有启用

§12 application.yml 配置键总表(示例项目)

键管什么
spring.ai.openai.api-key / base-url / chat.options.model模型接入(DashScope 兼容模式)
tavily.api-key / tavily.mcp-url搜索工具(MCP)
grsai.nanobanana.api-key图像生成
embeddings.store.*PGVector 向量库连接
minio.*文件存储
skills.directory技能目录
spring.datasource.* / spring.data.redis.*MySQL / Redis 基础设施
Logo

更多推荐