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

AtomGit AI 社区提供模型库、数据集、Agent、Token等资源

更多推荐