Chat Client APIs and Streaming

 Chat Client APIs and Streaming

call(). content() (Most Common)

Returns only the generated text.

@GetMapping("/content")
public String content(@RequestParam String question) {

return chatClient.prompt()
.user(question)
.call()
.content();
}

Flow:

User
   │
   ▼
ChatClient
   │
   ▼
LLM
   │
   ▼
String


Use when you only need the answer.

call().chatResponse()

Returns the complete response object, not just the text.

@GetMapping("/response")
public ChatResponse response(@RequestParam String question) {

return chatClient.prompt()
.user(question)
.call()
.chatResponse();
}


Chat Response contains much more than the content.

http://localhost:8080/api/response?question=Explain Kafka

output are in json format .


 Streaming

Instead of waiting for the complete answer, stream it token by token.

@GetMapping(value="/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String question) {

return chatClient.prompt()
.user(question)
.stream()
.content();
}

Now the browser receives:

data:Certainly

data:!

data: Here's

data: an

data: explanation


instead of waiting for the full response.

Why "data:"?

SSE (Server-Sent Events) has a standard format.

One message looks like

data: Kafka

Notice the blank line afterward.

Another message

data: is

Another

data: a

The browser understands this format automatically.


Streaming ChatResponse

Instead of streaming plain text:

Flux<String>

you can stream complete response objects.

@GetMapping(value="/stream-response",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ChatResponse> streamResponse(@RequestParam String question){

return chatClient.prompt()
.user(question)
.stream()
.chatResponse();
}

Useful when you want metadata while streaming.


Mapping to a Java Object

Suppose you ask:

Give employee details.

Create a record:

public record Employee(
String name,
int age,
String city
) {}
Then:

Employee employee =
chatClient.prompt()
.user("Generate employee details")
.call()
.entity(Employee.class);


Spring AI asks the model for structured output and maps it into your Java type.

List Mapping

public record Employee(
String name,
int age
) {}
Then:

List<Employee> employees =
chatClient.prompt()
.user("Generate 5 employees")
.call()
.entity(new ParameterizedTypeReference<List<Employee>>() {});

Bean Mapping

Bean Mapping


public class Student {

private String name;
private int age;

// getters/setters
}

Then:

Student student =
chatClient.prompt()
.user("Generate student")
.call()
.entity(Student.class);

JavaScript Frontend (Production)

<!DOCTYPE html>
<html>
<body>

<button onclick="start()">Ask AI</button>

<pre id="output"></pre>

<script>

function start(){

const eventSource = new EventSource(
"http://localhost:8080/stream?question=Explain Kafka"
);

eventSource.onmessage = function(event){

document.getElementById("output").textContent += event.data;

};

}

</script>

</body>
</html>

This is how many AI chat applications display responses in real time.


What Happens Internally

User
   │
   ▼
ChatClient
   │
   ▼
OpenAI Streaming API
   │
   ▼
Token 1
   │
   ▼
Flux<String>
   │
   ▼
Spring WebFlux
   │
   ▼
Browser / Postman / curl

Flux<String> is a Publisher from Project Reactor. Instead of waiting for the complete response, it emits each chunk as soon as it arrives, which is why streaming feels much more responsive in AI chat applications.


Production Flow

User
   │
   ▼
ChatClient
   │
   ▼
OpenAI Streaming API
   │
   ▼
Chunk 1 → "Certainly"
   │
Chunk 2 → "!"
   │
Chunk 3 → "Here"
   │
Chunk 4 → "'s"
   │
Chunk 5 → "an"
   │
Chunk 6 → "explanation"
   │
Chunk 7 → "of"
   │
Chunk 8 → "Kafka"

Spring AI wraps these chunks in a Flux<String> and Spring WebFlux sends each one as an SSE event.


Interview Question

Q: Why does stream().content() return Flux<String> instead of String?

Answer:

  • String waits until the entire response is generated.
  • Flux<String> is a reactive stream from Project Reactor.
  • As soon as the LLM generates a chunk/token, Spring AI emits it.
  • This reduces perceived latency and enables ChatGPT-like typing effects.


Spring AI receives each chunk.

Instead of storing everything,

it immediately sends each chunk into a Flux.


Think of Flux as a pipe.


What is Flux?

Imagine a water pipe.

Water Tank

↓

Pipe

↓

Tap

Water comes continuously.

Not all at once.

Flux is exactly the same idea.

Instead of water,

it carries data.


Flux means:

"I don't have one value."

"I have many values coming over time."


Why not List<String>?

Suppose the AI generates:

Kafka

is

a

distributed

event

streaming

platform

List would wait until everything is finished.

Wait...

Wait...

Wait...

Return List

Flux returns immediately.

Kafka

↓

is

↓

a

↓

distributed

↓

...

What does Spring WebFlux do?

Spring AI only creates the Flux.

Someone has to send it to the browser.

That is Spring WebFlux's job.

OpenAI ↓ Spring AI ↓ Flux<String> ↓ Spring WebFlux ↓ Browser

Why do you see

data: Kafka

data: is

data: a

Because Spring WebFlux converts every Flux element into an SSE message.



Complete Flow

User

↓

Controller

↓

ChatClient

↓

OpenAI



"Kafka"

↓

Flux emits

↓

Spring WebFlux

↓

data: Kafka

↓

Browser

What is Flux<String>?

Flux<String> is a reactive stream from Project Reactor that emits multiple values over time.


In Spring AI, each value is typically a chunk of the LLM's generated response, allowing the client to receive text progressively instead of waiting for the entire response.




What Spring WebFlux Does

Spring WebFlux takes each emitted chunk from the Flux<String> and sends it to the client using Server-Sent Events (SSE)


Q: Why do we use stream() in Spring AI?

Answer:stream() enables real-time AI responses.

Instead of waiting for the complete response from the LLM,

Spring AI receives generated chunks/tokens continuously and exposes them as a Flux<String>.

Spring WebFlux then streams these chunks to the client using Server-Sent Events (SSE),

improving responsiveness and user experience for AI chat applications.




Comments

Popular posts from this blog

Async/await

Two Sum II - Input Array Is Sorted

Comparable Vs. Comparator in Java