Response Streaming梳理

下面我按这篇文档的结构,结合你是 Spring Boot 开发者的视角,把 LangChain4j Response Streaming 讲清楚。

这篇文档讲的是:如何让大模型的回答像 ChatGPT 那样一个字、一段一段地流式返回,而不是等整段回答生成完再一次性返回。


1. Response Streaming 是什么?

大模型生成文本时,本质上不是一次性生成完整答案,而是:

一个 token 一个 token 地生成。

这里的 token 可以简单理解为:

  • 一个字
  • 一个词
  • 一个词的一部分
  • 一个标点
  • 一小段文本

不同模型、不同服务商对 token 的切分方式不同。

普通非流式调用是这样:

用户提问
   ↓
等待模型完整生成
   ↓
一次性返回完整答案

流式调用是这样:

用户提问
   ↓
模型生成一点,就返回一点
   ↓
前端马上显示
   ↓
继续生成,继续显示

类似 ChatGPT 的打字机效果。


2. 为什么要用 Response Streaming?

文档里说它可以显著改善用户体验。

原因很简单:

如果用户问:

请帮我写一篇 1000 字的文章

非流式模式下,用户可能要等 5 秒、10 秒甚至更久,页面上什么都没有。

而流式模式下,可能 0.5 秒后就开始看到内容:

当然可以,下面是一篇关于...

这样用户会觉得:

  • 系统响应更快
  • 页面没有卡住
  • 更像真实 AI 助手
  • 适合聊天机器人、客服、写作助手等场景

3. 这篇文档讲的是 Low-level LLM API

文档开头有一句说明:

This page describes response streaming with a low-level LLM API.
See AI Services for a high-level LLM API.

意思是:

这篇文档讲的是 底层 API 的流式响应。

LangChain4j 里大致有两种使用方式:

方式一:Low-level API

你直接操作模型对象,比如:

StreamingChatModel
ChatModel
LanguageModel

这种方式比较底层、灵活,但是需要自己处理:

  • 消息
  • 回调
  • token
  • 错误
  • 流式事件
  • 工具调用等

方式二:AI Services

这是更高级的封装。

类似这样:

interface Assistant {
    String chat(String userMessage);
}

或者流式:

interface Assistant {
    TokenStream chat(String userMessage);
}

AI Services 更适合业务开发,因为它可以帮你自动处理很多东西,比如:

  • Prompt 模板
  • Chat Memory
  • Tools
  • RAG
  • Structured Output

如果你是 Spring Boot 开发者,刚开始学习,我建议:

先理解 Low-level API 的原理,再在项目里优先考虑 AI Services。


4. ChatModel 和 StreamingChatModel 的关系

文档里提到:

For the ChatModel and LanguageModel interfaces, there are corresponding StreamingChatModel and StreamingLanguageModel interfaces.

意思是 LangChain4j 有普通模型接口,也有对应的流式模型接口。


4.1 ChatModel

ChatModel 是普通的聊天模型接口。

它的特点是:

等模型完整生成后,一次性返回完整结果。

大概类似这样:

ChatModel model = ...;

ChatResponse response = model.chat("你好,介绍一下 LangChain4j");
System.out.println(response.aiMessage().text());

结果是完整返回:

LangChain4j 是一个用于 Java 应用集成大语言模型的框架...

4.2 StreamingChatModel

StreamingChatModel 是流式聊天模型接口。

它的特点是:

模型生成一部分,就回调一部分。

类似这样:

StreamingChatModel model = ...;

model.chat("你好,介绍一下 LangChain4j", new StreamingChatResponseHandler() {
    @Override
    public void onPartialResponse(String partialResponse) {
        System.out.print(partialResponse);
    }

    @Override
    public void onCompleteResponse(ChatResponse completeResponse) {
        System.out.println("\n生成完成");
    }

    @Override
    public void onError(Throwable error) {
        error.printStackTrace();
    }
});

这时候控制台可能会逐步输出:

LangChain4j 是
一个用于 Java
应用集成大语言模型
的框架...

5. LanguageModel 和 StreamingLanguageModel

文档还提到:

LanguageModel
StreamingLanguageModel

这两个和 ChatModel 类似,但概念上有些区别。


5.1 LanguageModel

更偏向传统文本生成。

例如:

输入一个 prompt,输出一段文本

适合这类场景:

请续写下面这段话:
从前有一座山...

5.2 ChatModel

更偏向聊天场景。

它通常支持多轮消息,例如:

UserMessage
AiMessage
SystemMessage
ToolExecutionResultMessage

也就是说它知道:

  • 哪些话是用户说的
  • 哪些话是 AI 说的
  • 哪些话是系统指令
  • 哪些话是工具调用结果

现在大多数大模型应用,尤其是聊天机器人、AI 助手,都会优先使用 ChatModelStreamingChatModel


6. StreamingChatResponseHandler 是核心

文档中的核心接口是:

public interface StreamingChatResponseHandler {

    default void onPartialResponse(String partialResponse) {}

    default void onPartialResponse(
        PartialResponse partialResponse,
        PartialResponseContext context
    ) {}

    default void onPartialThinking(PartialThinking partialThinking) {}

    default void onPartialThinking(
        PartialThinking partialThinking,
        PartialThinkingContext context
    ) {}

    default void onPartialToolCall(PartialToolCall partialToolCall) {}

    default void onPartialToolCall(
        PartialToolCall partialToolCall,
        PartialToolCallContext context
    ) {}

    default void onCompleteToolCall(CompleteToolCall completeToolCall) {}

    default void onUnmappedRawEvent(Object rawEvent) {}

    void onCompleteResponse(ChatResponse completeResponse);

    void onError(Throwable error);
}

这个接口的作用是:

你告诉 LangChain4j:当模型流式返回不同类型的数据时,应该怎么处理。

它类似 Spring 里的回调接口,或者事件监听器。

你可以理解成:

模型开始生成
   ↓
每生成一点文本,调用 onPartialResponse
   ↓
如果生成思考过程,调用 onPartialThinking
   ↓
如果生成工具调用,调用 onPartialToolCall
   ↓
如果工具调用完整了,调用 onCompleteToolCall
   ↓
最终完整回答生成完,调用 onCompleteResponse
   ↓
如果出错,调用 onError

7. onPartialResponse(String partialResponse)

这是最常用的方法。

default void onPartialResponse(String partialResponse) {}

它表示:

当模型生成了一小段文本时,就会调用这个方法。

例如用户问:

介绍一下 Spring Boot

模型可能分多次返回:

Spring
 Boot
 是一个
用于简化
 Spring 应用开发

每次返回一小段,就调用一次:

@Override
public void onPartialResponse(String partialResponse) {
    System.out.print(partialResponse);
}

在 Spring Boot 里,这个方法通常用来:

  • 推送给前端 SSE
  • 推送给 WebSocket
  • 写入响应流
  • 实现打字机效果

作用总结

方法 作用
onPartialResponse(String partialResponse) 接收模型生成的文本片段
常见用途 实时推送到前端
类似场景 ChatGPT 边生成边显示

8. onPartialResponse(PartialResponse, PartialResponseContext)

文档里还提到另一个重载方法:

default void onPartialResponse(
    PartialResponse partialResponse,
    PartialResponseContext context
) {}

它和 onPartialResponse(String) 的区别是:

String 版本只给你文本内容。

而这个版本给你的信息更多。

可以简单理解为:

PartialResponse partialResponse

代表这次流式返回的内容对象。

PartialResponseContext context

代表这次返回时的一些上下文信息。

可能包括服务商相关信息、响应上下文、事件信息等。

如果你只是做普通聊天页面,通常用这个就够了:

onPartialResponse(String partialResponse)

如果你需要更精细控制,比如:

  • 区分模型响应的不同事件
  • 获取更完整的上下文
  • 兼容不同模型服务商的特殊返回
  • 做日志、监控、调试

可以使用带 context 的版本。


9. partial response 不一定是一个 token

文档强调:

Depending on the LLM provider, partial response text can consist of a single or more tokens.

意思是:

每次回调返回的内容,不一定刚好是一个 token。

有些服务商一次返回一个 token:

你
好
,
我
是
AI

有些服务商一次返回一小段:

你好,
我是
AI 助手。

所以开发时不要假设:

  • 一次回调就是一个字
  • 一次回调就是一个完整词
  • 一次回调就是一句话

正确做法是:

收到什么就追加什么。

例如:

StringBuilder builder = new StringBuilder();

@Override
public void onPartialResponse(String partialResponse) {
    builder.append(partialResponse);
}

10. onPartialThinking:接收模型的思考过程

文档里提到:

default void onPartialThinking(PartialThinking partialThinking) {}

default void onPartialThinking(
    PartialThinking partialThinking,
    PartialThinkingContext context
) {}

这个方法表示:

当模型流式输出 reasoning / thinking 内容时,会调用这个方法。

现在有些模型支持“推理过程”或者“思考过程”。

比如用户问:

小明有 3 个苹果,又买了 5 个,一共有几个?

模型可能内部会有思考:

用户问的是加法问题,3 + 5 = 8...

然后最终回答:

一共有 8 个苹果。

部分模型可能把这类“思考过程”也作为流式事件返回。


普通回答 vs Thinking

可以这样理解:

类型 说明 是否展示给用户
PartialResponse 最终回答的一部分 一般展示
PartialThinking 模型思考/推理过程 看业务决定

是否应该展示 Thinking?

这个要看你的业务。

如果你做的是:

  • 编程助手
  • 数学解题
  • 推理演示
  • AI 调试工具

可以考虑展示部分 reasoning。

如果你做的是:

  • 客服机器人
  • 普通问答助手
  • 企业内部助手

一般不建议直接展示模型思考过程。

因为它可能:

  • 让用户困惑
  • 暴露不必要的中间内容
  • 包含不稳定推理
  • 不同模型支持程度不同

11. onPartialToolCall:接收工具调用片段

文档里提到:

default void onPartialToolCall(PartialToolCall partialToolCall) {}

default void onPartialToolCall(
    PartialToolCall partialToolCall,
    PartialToolCallContext context
) {}

这个和 LangChain4j 的 Tools / Function Calling 有关。


11.1 什么是 Tool Call?

大模型本身不会真正查数据库、查天气、调用接口。

但是它可以决定:

我需要调用某个工具来完成任务。

比如你定义了一个工具:

public class WeatherTool {

    @Tool
    public String getWeather(String city) {
        return "北京今天晴,25 度";
    }
}

用户问:

北京今天天气怎么样?

模型可能不会直接回答,而是生成一个工具调用:

{
  "name": "getWeather",
  "arguments": {
    "city": "北京"
  }
}

这就是 tool call。


11.2 为什么 Tool Call 也需要流式?

因为工具调用的参数可能也是一点一点生成的。

比如模型生成这个 JSON:

{
  "name": "getWeather",
  "arguments": {
    "city": "北京"
  }
}

流式过程中可能分几段返回:

{
  "name": "get
Weather",
  "arguments":
{
  "city": "北京"
}

所以 LangChain4j 提供了:

onPartialToolCall(...)

用来接收尚未完整的工具调用片段。


11.3 实际开发中怎么用?

如果你是初学者,大多数情况下不用自己处理 onPartialToolCall

因为:

  • 使用 AI Services 时,LangChain4j 可以帮你处理 tools
  • 你更常关心最终结果
  • 手动处理 tool call 复杂度较高

但如果你在做底层框架、调试工具调用、实现自定义 agent,那么这个方法就很重要。


12. onCompleteToolCall:工具调用完整生成

文档里提到:

default void onCompleteToolCall(CompleteToolCall completeToolCall) {}

它表示:

当模型完整生成了一个工具调用时,会调用这个方法。

也就是说:

onPartialToolCall 是片段。

onCompleteToolCall 是完整结果。

比如最终完整的工具调用是:

{
  "name": "getWeather",
  "arguments": {
    "city": "北京"
  }
}

这时你就可以在 onCompleteToolCall 里拿到完整工具名和参数。


简单理解

方法 时机 用途
onPartialToolCall 工具调用生成中 看中间片段、做调试
onCompleteToolCall 工具调用生成完 可以执行工具调用

13. onUnmappedRawEvent:未映射的原始事件

文档里提到:

default void onUnmappedRawEvent(Object rawEvent) {}

这个方法的意思是:

如果模型服务商返回了某些 LangChain4j 还没有标准化映射的原始流式事件,会调用这个方法。

不同大模型服务商的流式协议不完全一样。

比如:

  • OpenAI
  • Anthropic Claude
  • Google Gemini
  • Ollama
  • DashScope
  • Azure OpenAI

它们返回的流式事件格式可能不同。

LangChain4j 会尽量把它们统一抽象成:

  • PartialResponse
  • PartialThinking
  • PartialToolCall
  • CompleteToolCall
  • CompleteResponse

但是有时候服务商会返回一些特殊事件,LangChain4j 没有对应的统一对象。

这时就会进入:

onUnmappedRawEvent(Object rawEvent)

这个方法有什么用?

主要用于:

  • 调试
  • 日志记录
  • 兼容某个服务商的特殊功能
  • 排查为什么某些事件没有被标准处理

例如:

@Override
public void onUnmappedRawEvent(Object rawEvent) {
    log.info("收到未映射的原始事件: {}", rawEvent);
}

对于普通业务开发,这个不是必须实现。


14. onCompleteResponse:完整响应结束

文档中的必实现方法之一:

void onCompleteResponse(ChatResponse completeResponse);

它表示:

模型本次完整回答已经生成结束。

这个方法很重要。

因为在流式过程中,你会不断收到片段:

onPartialResponse("Spring")
onPartialResponse(" Boot")
onPartialResponse(" 是一个")
onPartialResponse("框架")

等全部结束后,会调用:

onCompleteResponse(...)

这里可以拿到完整的 ChatResponse


completeResponse 里面通常有什么?

一般可以包含:

  • AI 最终消息
  • 完整文本
  • token 使用情况
  • finish reason
  • metadata
  • 工具调用信息

具体字段取决于 LangChain4j 版本和模型服务商。


常见用途

@Override
public void onCompleteResponse(ChatResponse completeResponse) {
    log.info("AI 完整响应: {}", completeResponse.aiMessage().text());
}

可以在这里做:

  • 保存聊天记录
  • 统计 token 用量
  • 记录日志
  • 关闭 SSE 连接
  • 通知前端回答结束
  • 做后续业务处理

15. onError:异常处理

另一个必实现方法:

void onError(Throwable error);

它表示:

流式调用过程中发生异常。

可能的原因包括:

  • API Key 错误
  • 网络超时
  • 模型服务不可用
  • 请求参数错误
  • 触发模型服务商限流
  • 响应解析失败
  • 用户中途断开连接

实际开发中一定要实现这个方法。

例如:

@Override
public void onError(Throwable error) {
    log.error("AI 流式响应失败", error);
}

如果你使用 SSE,需要在这里通知前端:

emitter.completeWithError(error);

16. 完整执行流程

你可以把整个流式响应过程理解成这样:

用户发送问题
   ↓
调用 StreamingChatModel.chat(...)
   ↓
LangChain4j 请求大模型
   ↓
大模型开始生成
   ↓
onPartialResponse:收到文本片段
   ↓
onPartialThinking:收到思考片段,可选
   ↓
onPartialToolCall:收到工具调用片段,可选
   ↓
onCompleteToolCall:工具调用完整,可选
   ↓
onCompleteResponse:完整响应结束
   ↓
结束

如果中途失败:

用户发送问题
   ↓
调用模型
   ↓
发生异常
   ↓
onError

17. Spring Boot 中怎么理解这个东西?

如果你是 Spring Boot 开发者,可以把它类比成:

普通接口

@GetMapping("/chat")
public String chat(String message) {
    return aiService.chat(message);
}

特点:

  • 后端等 AI 完整生成
  • 一次性返回给前端
  • 简单,但用户体验一般

流式接口

@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter chatStream(String message) {
    SseEmitter emitter = new SseEmitter();

    streamingChatModel.chat(message, new StreamingChatResponseHandler() {
        @Override
        public void onPartialResponse(String partialResponse) {
            try {
                emitter.send(partialResponse);
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
        }

        @Override
        public void onCompleteResponse(ChatResponse completeResponse) {
            emitter.complete();
        }

        @Override
        public void onError(Throwable error) {
            emitter.completeWithError(error);
        }
    });

    return emitter;
}

这样前端就能不断收到后端推过来的内容。


18. 一个更贴近 Spring Boot 的示例

假设你用 SseEmitter 实现流式返回:

@RestController
@RequestMapping("/ai")
public class AiController {

    private final StreamingChatModel streamingChatModel;

    public AiController(StreamingChatModel streamingChatModel) {
        this.streamingChatModel = streamingChatModel;
    }

    @GetMapping(value = "/chat-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chatStream(@RequestParam String message) {
        SseEmitter emitter = new SseEmitter(0L);

        streamingChatModel.chat(message, new StreamingChatResponseHandler() {

            @Override
            public void onPartialResponse(String partialResponse) {
                try {
                    emitter.send(SseEmitter.event()
                            .name("message")
                            .data(partialResponse));
                } catch (IOException e) {
                    emitter.completeWithError(e);
                }
            }

            @Override
            public void onCompleteResponse(ChatResponse completeResponse) {
                try {
                    emitter.send(SseEmitter.event()
                            .name("done")
                            .data("[DONE]"));
                } catch (IOException e) {
                    emitter.completeWithError(e);
                    return;
                }

                emitter.complete();
            }

            @Override
            public void onError(Throwable error) {
                emitter.completeWithError(error);
            }
        });

        return emitter;
    }
}

前端可以用:

const eventSource = new EventSource("/ai/chat-stream?message=介绍一下Spring Boot");

eventSource.addEventListener("message", event => {
  console.log("收到片段:", event.data);
});

eventSource.addEventListener("done", event => {
  console.log("完成");
  eventSource.close();
});

19. 什么时候用 Response Streaming?

推荐使用流式响应的场景:

场景 是否推荐
AI 聊天机器人 强烈推荐
AI 客服 强烈推荐
文章生成 推荐
代码生成 推荐
长文本总结 推荐
简短分类任务 不一定需要
后台批处理 不一定需要
JSON 结构化输出 视情况而定

20. 流式响应和普通响应的对比

对比项 普通响应 流式响应
返回方式 一次性返回 分段返回
用户等待时间 较长 较短
体验 像普通接口 像 ChatGPT
开发复杂度 稍高
前端处理 简单 需要 SSE/WebSocket
后端处理 普通 Controller 需要流式推送
适合场景 短任务 长文本、聊天、生成类任务

21. 初学者最该掌握哪些点?

如果你刚开始学 LangChain4j 的流式响应,优先掌握这些:

第一,知道为什么要流式

因为用户不想一直等。


第二,知道核心接口

StreamingChatModel

负责发起流式调用。

StreamingChatResponseHandler

负责接收流式事件。


第三,重点掌握三个方法

onPartialResponse
onCompleteResponse
onError

这三个最常用。


第四,Spring Boot 中一般配合 SSE 或 WebSocket

常见方式:

  • SSE:简单,适合服务端单向推送
  • WebSocket:适合双向实时通信

AI 聊天场景下,SSE 用得非常多。


22. 这篇文档的知识点总表

知识点 是干什么的 作用
Response Streaming 流式响应 边生成边返回
token 模型生成的最小片段 流式输出的基础
Low-level LLM API 底层模型 API 灵活,但需要自己处理回调
AI Services 高级封装 API 更适合业务开发
ChatModel 普通聊天模型 一次性返回完整回答
StreamingChatModel 流式聊天模型 分段返回回答
LanguageModel 普通文本模型 文本生成
StreamingLanguageModel 流式文本模型 流式文本生成
StreamingChatResponseHandler 流式响应处理器 接收各种流式事件
onPartialResponse 文本片段回调 实时展示给用户
PartialResponseContext 文本片段上下文 获取更详细信息
onPartialThinking 思考过程回调 接收 reasoning 内容
onPartialToolCall 工具调用片段 处理 function calling 流式片段
onCompleteToolCall 完整工具调用 工具调用生成完成
onUnmappedRawEvent 未映射原始事件 调试服务商特殊事件
onCompleteResponse 完整响应结束 保存结果、关闭流
onError 异常回调 错误处理

23. 你可以这样记

最简单的理解方式:

StreamingChatModel 是流式模型

StreamingChatResponseHandler 是监听器

onPartialResponse 是每次收到一点内容

onCompleteResponse 是全部结束

onError 是出错

如果你只是做一个 Spring Boot + LangChain4j 的 AI 聊天接口,最开始只需要关心:

onPartialResponse(String partialResponse)
onCompleteResponse(ChatResponse completeResponse)
onError(Throwable error)

其他的:

onPartialThinking
onPartialToolCall
onCompleteToolCall
onUnmappedRawEvent

可以等你学习 Tools、Agent、Reasoning 模型时再深入。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐