190 lines
4.1 KiB
Markdown
190 lines
4.1 KiB
Markdown
---
|
||
title: 流式输出(SSE)接入
|
||
description: 使用 Server-Sent Events 实现 AI 流式响应,提升用户体验。
|
||
category: api
|
||
order: 2
|
||
---
|
||
|
||
# 流式输出(SSE)接入
|
||
|
||
> 使用 Server-Sent Events 实现 AI 流式响应,提升用户体验。
|
||
|
||
## 🌊 什么是流式输出?
|
||
|
||
流式输出(Streaming)是一种数据传输方式,服务器将数据「分块」发送给客户端,而不是等待全部完成后再返回。
|
||
|
||
### 对比
|
||
|
||
| 方式 | 等待时间 | 体验 | 适用场景 |
|
||
|------|----------|------|----------|
|
||
| 普通请求 | 等待完整响应 | 一般 | 简单、快速的请求 |
|
||
| 流式输出 | 实时看到生成过程 | ⭐ 优秀 | AI 生成、长文本 |
|
||
|
||
## 🚀 开始使用
|
||
|
||
### 前端:接收流式响应
|
||
|
||
```typescript
|
||
import { WebsopyClient } from '@websopy/sdk'
|
||
|
||
const client = new WebsopyClient({
|
||
apiKey: process.env.WEBSOPY_API_KEY
|
||
})
|
||
|
||
async function streamChat() {
|
||
const stream = await client.ai.chatStream({
|
||
messages: [
|
||
{ role: 'user', content: '写一篇关于 AI 的文章' }
|
||
]
|
||
})
|
||
|
||
// 方法 1:遍历数据块
|
||
for await (const chunk of stream) {
|
||
if (chunk.type === 'content') {
|
||
process.stdout.write(chunk.content)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 响应数据结构
|
||
|
||
```typescript
|
||
interface StreamChunk {
|
||
type: 'content' | 'tool_call' | 'done' | 'error'
|
||
content?: string
|
||
tool?: {
|
||
name: string
|
||
arguments: string
|
||
}
|
||
usage?: {
|
||
promptTokens: number
|
||
completionTokens: number
|
||
}
|
||
}
|
||
|
||
// 示例数据块
|
||
{ type: 'content', content: 'AI' }
|
||
{ type: 'content', content: ' 正在' }
|
||
{ type: 'content', content: '改变' }
|
||
{ type: 'content', content: '世界...' }
|
||
{ type: 'done', usage: { promptTokens: 20, completionTokens: 150 } }
|
||
```
|
||
|
||
## 🌐 原生 SSE 实现
|
||
|
||
如果不用 SDK,直接使用 Fetch API:
|
||
|
||
```typescript
|
||
async function nativeStream() {
|
||
const response = await fetch(
|
||
'https://api.websopy.com/v1/ai/chat?message=Hello',
|
||
{
|
||
headers: {
|
||
'Authorization': `Bearer ${apiKey}`,
|
||
'Accept': 'text/event-stream'
|
||
}
|
||
}
|
||
)
|
||
|
||
const reader = response.body.getReader()
|
||
const decoder = new TextDecoder()
|
||
|
||
while (true) {
|
||
const { done, value } = await reader.read()
|
||
if (done) break
|
||
|
||
const chunk = decoder.decode(value)
|
||
const lines = chunk.split('\n')
|
||
for (const line of lines) {
|
||
if (line.startsWith('data: ')) {
|
||
const data = JSON.parse(line.slice(6))
|
||
console.log('收到:', data)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 🎨 前端组件示例
|
||
|
||
### Vue Composition API
|
||
|
||
```typescript
|
||
import { ref } from 'vue'
|
||
|
||
export function useStreamingChat() {
|
||
const messages = ref([])
|
||
const streaming = ref(false)
|
||
const currentContent = ref('')
|
||
|
||
const sendMessage = async (content) => {
|
||
streaming.value = true
|
||
currentContent.value = ''
|
||
|
||
messages.value.push({ role: 'user', content })
|
||
|
||
const stream = await client.ai.chatStream({
|
||
messages: messages.value
|
||
})
|
||
|
||
for await (const chunk of stream) {
|
||
if (chunk.type === 'content') {
|
||
currentContent.value += chunk.content
|
||
}
|
||
}
|
||
|
||
messages.value.push({ role: 'assistant', content: currentContent.value })
|
||
streaming.value = false
|
||
}
|
||
|
||
return { messages, streaming, currentContent, sendMessage }
|
||
}
|
||
```
|
||
|
||
## ❓ 常见问题
|
||
|
||
### Q: 流式中断怎么办?
|
||
|
||
```typescript
|
||
async function streamWithReconnect() {
|
||
const maxRetries = 3
|
||
let retries = 0
|
||
|
||
while (retries < maxRetries) {
|
||
try {
|
||
const stream = await client.ai.chatStream({...})
|
||
return stream
|
||
} catch (error) {
|
||
retries++
|
||
await sleep(1000 * retries) // 指数退避
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Q: 如何在流式过程中取消请求?
|
||
|
||
```typescript
|
||
const controller = new AbortController()
|
||
|
||
// 发送请求
|
||
const stream = await client.ai.chatStream({
|
||
messages: [...],
|
||
signal: controller.signal
|
||
})
|
||
|
||
// 取消请求
|
||
controller.abort()
|
||
```
|
||
|
||
### Q: SSE 和 WebSocket 区别?
|
||
|
||
| 特性 | SSE | WebSocket |
|
||
|------|-----|-----------|
|
||
| 方向 | 单向(服务端→客户端) | 双向 |
|
||
| 协议 | HTTP | ws:// |
|
||
| 重连 | 自动 | 需手动处理 |
|
||
| 复杂度 | 简单 | 复杂 |
|
||
| 适用 | AI 流式输出 | 实时聊天 |
|