跪拜 Guibai
← All articles
Backend · AI Programming · Interview

What Actually Happens When Spring AI's ChatClient Calls a Model

By 一只叫煤球的猫 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Misreading `call()` as the network trigger leads to misplaced breakpoints, wrong latency measurements, and confusion about when Advisors execute. Knowing that `content()` is the real trigger and that the Advisor Chain wraps the model call explains where to inject logging, retries, and tool-calling logic.

Summary

Spring AI 2.0's fluent API looks deceptively simple: four method calls and you get a string back from an LLM. Tracing the source reveals that `call()` does not invoke the model at all. It constructs a `DefaultCallResponseSpec` and assembles an Advisor Chain, appending `ChatModelCallAdvisor` and `ChatModelStreamAdvisor` to the end. The actual network request waits until `content()`, `chatResponse()`, or `entity()` is called.

Before that point, `prompt()` clones a fresh `DefaultChatClientRequestSpec` from the client's defaults, and `user("你好")` merely stores the text string. Message assembly into a `UserMessage` and `Prompt` object happens later inside `DefaultChatClientUtils#toChatClientRequest()`, which packages both the `Prompt` and an Advisor context map into a `ChatClientRequest` record. The Advisor Chain then processes this request in a classic around-chain pattern, with `ChatModelCallAdvisor` at the tail extracting the `Prompt` and delegating to the vendor-agnostic `ChatModel` interface.

`OpenAiChatModel` translates Spring AI's `Prompt` into the OpenAI Java SDK's `ChatCompletionCreateParams`, fires the HTTP request, and maps the response back through `ChatResponse` → `Generation` → `AssistantMessage` → text. The `ChatClient.Builder` itself is a prototype-scoped bean, so every `build()` call produces an independent `DefaultChatClient` with its own default configuration.

Takeaways
`call()` builds the Advisor Chain and returns a `DefaultCallResponseSpec`; it does not send a request to the model.
`content()`, `chatResponse()`, `chatClientResponse()`, and `entity()` are the terminal methods that actually drive the synchronous call through the Advisor Chain.
`prompt()` copies the client's default configuration into a new `DefaultChatClientRequestSpec`, giving each call independent state.
`user("你好")` stores the raw string; the `UserMessage` is not created until `DefaultChatClientUtils#toChatClientRequest()` assembles the `Prompt`.
Spring AI auto-configures a prototype-scoped `ChatClient.Builder`, not a singleton `ChatClient`; business code calls `build()` to get the final client.
`ChatModelCallAdvisor` sits at the end of the synchronous Advisor Chain and is the only place that calls `chatModel.call(prompt)`.
`OpenAiChatModel` converts Spring AI's `Prompt` into the OpenAI Java SDK's `ChatCompletionCreateParams` and maps the response back into a unified `ChatResponse`.
Extracting text traverses `ChatClientResponse` → `ChatResponse` → `Generation` → `AssistantMessage` → text; use `chatResponse()` if you need token usage or finish reason.
Conclusions

Spring AI's decision to name the method `call()` when it does not call the model is a persistent source of confusion that the framework could clarify with a rename like `prepare()` or `build()`.

The prototype-scoped Builder pattern gives flexibility at the cost of debugging indirection: a bean-creation problem now requires tracing through Builder, auto-configuration, and the final `DefaultChatClient`.

Placing `ChatModelCallAdvisor` as a mandatory tail Advisor rather than a direct call inside `ChatClient` is a deliberate design choice that makes every request pass through the same around-chain, enabling uniform cross-cutting concerns but adding a layer of abstraction that obscures the call site.

Concepts & terms
Advisor Chain
An around-interceptor chain in Spring AI that wraps a model call. Each Advisor can modify the request before it reaches the model and the response after it returns. The chain ends with `ChatModelCallAdvisor`, which actually invokes the `ChatModel`.
ChatClientRequest
A record containing a `Prompt` (messages and options for the model) and a `context` map (shared data for Advisors). It is the object that travels through the Advisor Chain, not raw strings.
ChatModel
A vendor-agnostic interface in Spring AI with a single `call(Prompt)` method. Implementations like `OpenAiChatModel` translate Spring AI objects into provider-specific SDK calls.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗