搭建AI核心工作流程
1、流式传输,实现AI打字机效果
1、首先进行流式调用大模型,参考相关api。
Flux<ChatResponse> streamResponse = chatModel.stream(new Prompt(new UserMessage(prompt)));
java
/**
* 调用 LLM(流式输出)
*/
private String callLlmWithStreaming(String prompt, Consumer<String> streamHandler, SseMessageTypeEnum messageType) {
StringBuilder contentBuilder = new StringBuilder();
Flux<ChatResponse> streamResponse = chatModel.stream(new Prompt(new UserMessage(prompt)));
streamResponse
.doOnNext(response -> {
String chunk = response.getResult().getOutput().getText();
if (chunk != null && !chunk.isEmpty()) {
contentBuilder.append(chunk);
streamHandler.accept(messageType.getStreamingPrefix() + chunk);
}
})
.doOnError(error -> log.error("LLM 流式调用失败, messageType={}", messageType, error))
.blockLast();
return contentBuilder.toString();
}
2、在接收流式调用时,传递一个回调函数对象。【Consumer<String> streamHandler】
1、该对象accept信息时,会触发回调。
2、回调一直往上传,传递到初始的定义方
【import java.util.function.Consumer;】
【public void executeArticleGeneration(ArticleState state, Consumer<String> streamHandler) {】
【agent2GenerateOutline(state, streamHandler);】
3、在往上传,就是调用方。再调用方产生了回调。
【// 执行智能体编排,并通过 SSE 推送进度
articleAgentService.executeArticleGeneration(state, message -> {
handleAgentMessage(taskId, message, state);
});】
4、handleAgentMessage回调函数会调用SSE进行流式推送到前端页面。
3、SSE流式调用管理代码
java
package com.panda.multiagent.manager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static com.panda.multiagent.constant.ArticleConstant.SSE_RECONNECT_TIME_MS;
import static com.panda.multiagent.constant.ArticleConstant.SSE_TIMEOUT_MS;
/**
* @Author panda
* @Date 2026-04-02
* @Des
*/
@Component
@Slf4j
public class SseEmitterManager {
/**
* 存储所有的 SseEmitter
*/
private final Map<String, SseEmitter> emitterMap = new ConcurrentHashMap<>();
/**
* 创建 SseEmitter
*
* @param taskId 任务ID
* @return SseEmitter
*/
public SseEmitter createEmitter(String taskId) {
SseEmitter emitter = new SseEmitter(SSE_TIMEOUT_MS);
// 设置超时回调
emitter.onTimeout(() -> {
log.warn("SSE 连接超时, taskId={}", taskId);
emitterMap.remove(taskId);
});
// 设置完成回调
emitter.onCompletion(() -> {
log.info("SSE 连接完成, taskId={}", taskId);
emitterMap.remove(taskId);
});
// 设置错误回调
emitter.onError((e) -> {
log.error("SSE 连接错误, taskId={}", taskId, e);
emitterMap.remove(taskId);
});
emitterMap.put(taskId, emitter);
log.info("SSE 连接已创建, taskId={}", taskId);
return emitter;
}
/**
* 发送消息
*
* @param taskId 任务ID
* @param message 消息内容
*/
public void send(String taskId, String message) {
SseEmitter emitter = emitterMap.get(taskId);
if (emitter == null) {
log.warn("SSE Emitter 不存在, taskId={}", taskId);
return;
}
try {
SseEmitter.SseEventBuilder builder = SseEmitter.event();
builder.data(message).reconnectTime(SSE_RECONNECT_TIME_MS);
emitter.send(builder);
log.debug("SSE 消息发送成功, taskId={}, message={}", taskId, message);
} catch (IOException e) {
log.error("SSE 消息发送失败, taskId={}", taskId, e);
emitterMap.remove(taskId);
}
}
/**
* 完成连接
*
* @param taskId 任务ID
*/
public void complete(String taskId) {
SseEmitter emitter = emitterMap.get(taskId);
if (emitter == null) {
log.warn("SSE Emitter 不存在, taskId={}", taskId);
return;
}
try {
emitter.complete();
log.info("SSE 连接已完成, taskId={}", taskId);
} catch (Exception e) {
log.error("SSE 连接完成失败, taskId={}", taskId, e);
} finally {
emitterMap.remove(taskId);
}
}
/**
* 检查 Emitter 是否存在
*
* @param taskId 任务ID
* @return 是否存在
*/
public boolean exists(String taskId) {
return emitterMap.containsKey(taskId);
}
}
4、前端使用
前端(EventSource) ←── SSE 推送 ←── 后端(SseEmitter)
↓
解析消息类型
↓
更新页面状态
sse.ts
javascript
export interface SSEMessage {
type: string
data?: any
[key: string]: any
}
export interface SSEOptions {
onMessage: (message: SSEMessage) => void
onError?: (error: Event) => void
onComplete?: () => void
}
/**
* 建立 SSE 连接
*/
export const connectSSE = (taskId: string, options: SSEOptions): EventSource => {
const { onMessage, onError, onComplete } = options
const eventSource = new EventSource(`/api/article/progress/${taskId}`, { withCredentials: true })
eventSource.onmessage = (event) => {
try {
const message: SSEMessage = JSON.parse(event.data)
onMessage(message)
// 检查是否完成
if (message.type === 'ALL_COMPLETE' || message.type === 'ERROR') {
eventSource.close()
onComplete?.()
}
} catch (error) {
console.error('SSE 消息解析失败:', error)
}
}
eventSource.onerror = (error) => {
console.error('SSE 连接错误:', error)
onError?.(error)
eventSource.close()
}
return eventSource
}
/**
* 关闭 SSE 连接
*/
export const closeSSE = (eventSource: EventSource | null) => {
if (eventSource) {
eventSource.close()
}
}
在业务中直接使用就可以。
javascript
try {
// 创建任务
const res = await createArticle({ topic: topic.value })
taskId.value = res.data.data as string
// 建立 SSE 连接
eventSource = connectSSE(taskId.value, {
onMessage: handleSSEMessage,
onError: handleSSEError,
onComplete: handleSSEComplete,
})
} catch (error: any) {
message.error(error.message || '创建任务失败')
isCreating.value = false
}