trustgraph/docs/tech-specs/streaming-llm-responses.zh-cn.md

579 lines
18 KiB
Markdown
Raw Normal View History

---
layout: default
title: "流式LLM响应技术规范"
parent: "Chinese (Beta)"
---
# 流式LLM响应技术规范
> **Beta Translation:** This document was translated via Machine Learning and as such may not be 100% accurate. All non-English languages are currently classified as Beta.
## 概述
本规范描述了在TrustGraph中实现LLM响应的流式支持。流式传输允许实时交付由LLM生成的token而不是等待完整的响应生成。
流式传输允许实时交付由LLM生成的token而不是等待完整的响应生成。
流式传输允许实时交付由LLM生成的token而不是等待完整的响应生成。
流式传输允许实时交付由LLM生成的token而不是等待完整的响应生成。
此实现支持以下用例:
1. **实时用户界面**: 将生成的令牌流式传输到用户界面,
从而提供即时的视觉反馈。
2. **减少首次令牌时间**: 用户立即看到输出,
而不是等待完整生成。
3. **处理长响应**: 处理可能导致超时或超出内存限制的非常长的输出。
4. **交互式应用程序**: 启用响应迅速的聊天和代理界面。
## 目标
**向后兼容性:** 现有的非流式客户端继续工作
无需修改。
**一致的 API 设计:** 流式和非流式使用相同的模式,差异最小。
**提供商的灵活性:** 在可用时支持流式传输,在不可用时提供优雅的
回退方案。
**分阶段推出:** 逐步实施以降低风险。
**端到端支持:** 从 LLM 提供商到客户端
应用程序,通过 Pulsar、Gateway API 和 Python API 提供支持。
## 背景
### 当前架构
当前 LLM 文本补全流程如下:
1. 客户端发送 `TextCompletionRequest`,包含 `system``prompt` 字段。
2. LLM 服务处理请求并等待完整生成。
3. 返回单个 `TextCompletionResponse`,包含完整的 `response` 字符串。
当前模式 (`trustgraph-base/trustgraph/schema/services/llm.py`)
```python
class TextCompletionRequest(Record):
system = String()
prompt = String()
class TextCompletionResponse(Record):
error = Error()
response = String()
in_token = Integer()
out_token = Integer()
model = String()
```
### 当前限制
**延迟:** 用户必须等待完整生成后才能看到任何输出。
**超时风险:** 较长的生成过程可能超出客户端超时阈值。
**糟糕的用户体验:** 在生成过程中没有反馈,会让人产生缓慢的感觉。
**资源使用:** 完整的响应必须缓存在内存中。
本规范通过启用增量响应来解决这些限制,同时保持完全的向后兼容性。
## 技术设计
### 第一阶段:基础设施
第一阶段通过修改模式、API 和 CLI 工具,为流式传输奠定基础。
#### 模式更改
##### LLM 模式 (`trustgraph-base/trustgraph/schema/services/llm.py`)
**请求更改:**
```python
class TextCompletionRequest(Record):
system = String()
prompt = String()
streaming = Boolean() # NEW: Default false for backward compatibility
```
`streaming`:当 `true` 时,请求流式响应传输。
默认:`false`(保留现有行为)。
**响应变更:**
```python
class TextCompletionResponse(Record):
error = Error()
response = String()
in_token = Integer()
out_token = Integer()
model = String()
end_of_stream = Boolean() # NEW: Indicates final message
```
`end_of_stream`:当 `true` 时,表示这是最终(或唯一的)响应。
对于非流式请求:单个响应,包含 `end_of_stream=true`
对于流式请求:多个响应,全部包含 `end_of_stream=false`
除了最后一个响应。
##### 提示模式 (`trustgraph-base/trustgraph/schema/services/prompt.py`)
提示服务包装了文本补全功能,因此它遵循相同的模式:
**请求更改:**
```python
class PromptRequest(Record):
id = String()
terms = Map(String())
streaming = Boolean() # NEW: Default false
```
**响应变更:**
```python
class PromptResponse(Record):
error = Error()
text = String()
object = String()
end_of_stream = Boolean() # NEW: Indicates final message
```
#### 网关 API 变更
网关 API 必须向 HTTP/WebSocket 客户端暴露流式传输功能。
**REST API 更新:**
`POST /api/v1/text-completion`: 接受请求体中的 `streaming` 参数
响应行为取决于流式传输标志:
`streaming=false`: 单个 JSON 响应(当前行为)
`streaming=true`: 服务器发送事件 (SSE) 流或 WebSocket 消息
**响应格式(流式传输):**
每个流式传输的数据块都遵循相同的模式结构:
```json
{
"response": "partial text...",
"end_of_stream": false,
"model": "model-name"
}
```
最终部分:
```json
{
"response": "final text chunk",
"end_of_stream": true,
"in_token": 150,
"out_token": 500,
"model": "model-name"
}
```
#### Python API 变更
Python 客户端 API 必须同时支持流式和非流式模式,
同时保持向后兼容性。
**LlmClient 更新** (`trustgraph-base/trustgraph/clients/llm_client.py`):
```python
class LlmClient(BaseClient):
def request(self, system, prompt, timeout=300, streaming=False):
"""
Non-streaming request (backward compatible).
Returns complete response string.
"""
# Existing behavior when streaming=False
async def request_stream(self, system, prompt, timeout=300):
"""
Streaming request.
Yields response chunks as they arrive.
"""
# New async generator method
```
**PromptClient 更新** (`trustgraph-base/trustgraph/base/prompt_client.py`):
类似于使用 `streaming` 参数和异步生成器的变体。
#### CLI 工具更改
**tg-invoke-llm** (`trustgraph-cli/trustgraph/cli/invoke_llm.py`):
```
tg-invoke-llm [system] [prompt] [--no-streaming] [-u URL] [-f flow-id]
```
默认启用流式传输,以获得更好的交互式用户体验。
`--no-streaming` 标志禁用流式传输。
当启用流式传输时:将生成的 token 输出到标准输出,并在它们到达时立即输出。
当禁用流式传输时:等待完整的响应,然后输出。
**tg-invoke-prompt** (`trustgraph-cli/trustgraph/cli/invoke_prompt.py`):
```
tg-invoke-prompt [template-id] [var=value...] [--no-streaming] [-u URL] [-f flow-id]
```
`tg-invoke-llm` 相同的模式。
#### LLM 服务基础类更改
**LlmService** (`trustgraph-base/trustgraph/base/llm_service.py`):
```python
class LlmService(FlowProcessor):
async def on_request(self, msg, consumer, flow):
request = msg.value()
streaming = getattr(request, 'streaming', False)
if streaming and self.supports_streaming():
async for chunk in self.generate_content_stream(...):
await self.send_response(chunk, end_of_stream=False)
await self.send_response(final_chunk, end_of_stream=True)
else:
response = await self.generate_content(...)
await self.send_response(response, end_of_stream=True)
def supports_streaming(self):
"""Override in subclass to indicate streaming support."""
return False
async def generate_content_stream(self, system, prompt, model, temperature):
"""Override in subclass to implement streaming."""
raise NotImplementedError()
```
--
### 第二阶段VertexAI 概念验证
第二阶段在单个提供商VertexAI中实现流式传输以验证
基础设施并实现端到端测试。
#### VertexAI 实施
**模块:** `trustgraph-vertexai/trustgraph/model/text_completion/vertexai/llm.py`
**变更:**
1. 覆盖 `supports_streaming()` 以返回 `True`
2. 实现 `generate_content_stream()` 异步生成器
3. 处理 Gemini 和 Claude 模型(通过 VertexAI Anthropic API
**Gemini 流式传输:**
```python
async def generate_content_stream(self, system, prompt, model, temperature):
model_instance = self.get_model(model, temperature)
response = model_instance.generate_content(
[system, prompt],
stream=True # Enable streaming
)
for chunk in response:
yield LlmChunk(
text=chunk.text,
in_token=None, # Available only in final chunk
out_token=None,
)
# Final chunk includes token counts from response.usage_metadata
```
**Claude (通过 VertexAI Anthropic) 流式传输:**
```python
async def generate_content_stream(self, system, prompt, model, temperature):
with self.anthropic_client.messages.stream(...) as stream:
for text in stream.text_stream:
yield LlmChunk(text=text)
# Token counts from stream.get_final_message()
```
#### 测试
流式响应组装的单元测试
与 VertexAI (Gemini 和 Claude) 的集成测试
端到端测试CLI -> 网关 -> Pulsar -> VertexAI -> 回调
向后兼容性测试:非流式请求仍然有效
--
### 第三阶段:所有 LLM 提供商
第三阶段将流式支持扩展到系统中的所有 LLM 提供商。
#### 提供商实施状态
每个提供商必须:
1. **完全流式支持**: 实施 `generate_content_stream()`
2. **兼容模式**: 正确处理 `end_of_stream` 标志
(返回单个响应,包含 `end_of_stream=true`)
| 提供商 | 包 | 流式支持 |
|----------|---------|-------------------|
| OpenAI | trustgraph-flow | 完全 (原生流式 API) |
| Claude/Anthropic | trustgraph-flow | 完全 (原生流式 API) |
| Ollama | trustgraph-flow | 完全 (原生流式 API) |
| Cohere | trustgraph-flow | 完全 (原生流式 API) |
| Mistral | trustgraph-flow | 完全 (原生流式 API) |
| Azure OpenAI | trustgraph-flow | 完全 (原生流式 API) |
| Google AI Studio | trustgraph-flow | 完全 (原生流式 API) |
| VertexAI | trustgraph-vertexai | 完全 (第二阶段) |
| Bedrock | trustgraph-bedrock | 完全 (原生流式 API) |
| LM Studio | trustgraph-flow | 完全 (与 OpenAI 兼容) |
| LlamaFile | trustgraph-flow | 完全 (与 OpenAI 兼容) |
| vLLM | trustgraph-flow | 完全 (与 OpenAI 兼容) |
| TGI | trustgraph-flow | 待定 |
| Azure | trustgraph-flow | 待定 |
#### 实施模式
对于与 OpenAI 兼容的提供商 (OpenAI, LM Studio, LlamaFile, vLLM)
```python
async def generate_content_stream(self, system, prompt, model, temperature):
response = await self.client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system},
{"role": "user", "content": prompt}
],
temperature=temperature,
stream=True
)
async for chunk in response:
if chunk.choices[0].delta.content:
yield LlmChunk(text=chunk.choices[0].delta.content)
```
--
### 第四阶段:代理 API
第四阶段将流式传输扩展到代理 API。 这更加复杂,因为
代理 API 本身就是多消息的(思考 → 行动 → 观察
→ 重复 → 最终答案)。
#### 当前代理模式
```python
class AgentStep(Record):
thought = String()
action = String()
arguments = Map(String())
observation = String()
user = String()
class AgentRequest(Record):
question = String()
state = String()
group = Array(String())
history = Array(AgentStep())
user = String()
class AgentResponse(Record):
answer = String()
error = Error()
thought = String()
observation = String()
```
#### 建议的代理模式变更
**请求变更:**
```python
class AgentRequest(Record):
question = String()
state = String()
group = Array(String())
history = Array(AgentStep())
user = String()
streaming = Boolean() # NEW: Default false
```
**响应变化:**
代理在推理过程中会产生多种类型的输出:
想法(推理)
动作(工具调用)
观察结果(工具结果)
答案(最终回复)
错误
由于 `chunk_type` 标识了正在发送的内容类型,因此可以合并单独的
`answer``error``thought``observation` 字段,
从而将其合并到一个 `content` 字段中:
```python
class AgentResponse(Record):
chunk_type = String() # "thought", "action", "observation", "answer", "error"
content = String() # The actual content (interpretation depends on chunk_type)
end_of_message = Boolean() # Current thought/action/observation/answer is complete
end_of_dialog = Boolean() # Entire agent dialog is complete
```
**字段语义:**
`chunk_type`: 指示 `content` 字段中包含的内容类型。
`"thought"`: 代理的推理/思考。
`"action"`: 正在调用的工具/动作。
`"observation"`: 来自工具执行的结果。
`"answer"`: 对用户问题的最终答案。
`"error"`: 错误消息。
`content`: 实际的流式内容,根据 `chunk_type` 进行解释。
`end_of_message`: 当 `true` 时,当前块类型已完成。
示例:当前思考的所有 token 都已发送。
允许客户端知道何时进入下一个阶段。
`end_of_dialog`: 当 `true` 时,整个代理交互已完成。
这是一个流中的最终消息。
#### 代理流式行为
`streaming=true` 时:
1. **思考流式传输:**
具有 `chunk_type="thought"``end_of_message=false` 的多个块。
最终的思考块具有 `end_of_message=true`
2. **动作通知:**
具有 `chunk_type="action"``end_of_message=true` 的单个块。
3. **观察:**
具有 `chunk_type="observation"` 的块(块),最终块具有 `end_of_message=true`
4. **重复** 1-3 步骤,直到代理完成推理。
5. **最终答案:**
`chunk_type="answer"` 包含最终的响应,位于 `content` 中。
最后一个块具有 `end_of_message=true``end_of_dialog=true`
**示例流序列:**
```
{chunk_type: "thought", content: "I need to", end_of_message: false, end_of_dialog: false}
{chunk_type: "thought", content: " search for...", end_of_message: true, end_of_dialog: false}
{chunk_type: "action", content: "search", end_of_message: true, end_of_dialog: false}
{chunk_type: "observation", content: "Found: ...", end_of_message: true, end_of_dialog: false}
{chunk_type: "thought", content: "Based on this", end_of_message: false, end_of_dialog: false}
{chunk_type: "thought", content: " I can answer...", end_of_message: true, end_of_dialog: false}
{chunk_type: "answer", content: "The answer is...", end_of_message: true, end_of_dialog: true}
```
`streaming=false`:
当前行为保持不变
提供完整的答案的单个响应
`end_of_message=true`, `end_of_dialog=true`
#### 网关和 Python API
网关:用于代理流的新 SSE/WebSocket 端点
Python API新的 `agent_stream()` 异步生成器方法
--
## 安全注意事项
**没有新的攻击面**:流式传输使用相同的身份验证/授权机制
**速率限制**:如果需要,请应用每个令牌或每个分块的速率限制
**连接处理**:在客户端断开连接时,正确终止流
**超时管理**:流式传输请求需要适当的超时处理
## 性能注意事项
**内存**:流式传输减少了峰值内存使用量(没有完整的响应缓冲)
**延迟**:首次令牌的时间显著减少
**连接开销**SSE/WebSocket 连接具有保活开销
**Pulsar 吞吐量**:多个小消息与单个大消息之间的权衡
tradeoff
## 测试策略
### 单元测试
使用新字段的模式序列化/反序列化
向后兼容性(缺少字段使用默认值)
分块组装逻辑
### 集成测试
每个 LLM 提供商的流式传输实现
网关 API 流式传输端点
Python 客户端流式传输方法
### 端到端测试
CLI 工具的流式传输输出
完整流程:客户端 → 网关 → Pulsar → LLM → 返回
混合流式传输/非流式传输工作负载
### 向后兼容性测试
现有客户端无需修改即可工作
非流式传输请求的行为与之前相同
## 迁移计划
### 第一阶段:基础设施
部署模式更改(向后兼容)
部署网关 API 更新
部署 Python API 更新
发布 CLI 工具更新
### 第二阶段VertexAI
部署 VertexAI 流式实现。
使用测试工作负载进行验证。
### 第三阶段:所有提供商
逐步推广提供商更新。
监控问题。
### 第四阶段Agent API
部署 Agent 模式变更。
部署 Agent 流式实现。
更新文档。
## 时间线
| 阶段 | 描述 | 依赖项 |
|-------|-------------|--------------|
| 第一阶段 | 基础设施 | 无 |
| 第二阶段 | VertexAI 概念验证 | 第一阶段 |
| 第三阶段 | 所有提供商 | 第二阶段 |
| 第四阶段 | Agent API | 第三阶段 |
## 设计决策
在规范过程中,解决了以下问题:
1. **流式传输中的 Token 计数**: Token 计数是增量值,而不是累计总数。
消费者可以根据需要对其进行求和。这与大多数提供商报告的使用方式一致,并简化了实现。
2. **流中的错误处理:** 如果发生错误,则会填充 `error` 字段,并且不需要填充其他字段。 错误始终是最终的
通信 - 在发生错误后,不允许也不期望发送任何后续消息。
通信 - 在此之后,不允许也不需要发送任何后续消息。
一个错误。对于 LLM/Prompt 流,`end_of_stream=true`。对于 Agent 流,
`chunk_type="error"``end_of_dialog=true`
3. **部分响应恢复:** 消息协议Pulsar具有容错性
因此不需要消息级别的重试。如果客户端丢失了流的信息
或断开连接,则必须从头开始重试整个请求。
4. **快速响应流式传输**: 流式传输仅支持文本 (`text`)
响应,不支持结构化 (`object`) 响应。 提示服务在开始时就知道输出将是 JSON 还是文本,这取决于提示
模板。 如果对 JSON 输出提示执行流式传输请求,则
会出现问题。
服务应该要么:
返回完整的 JSON 数据,以单个响应的形式,包含 `end_of_stream=true`,或者
拒绝流式传输请求,并返回错误。
## 开放问题
目前没有。
## 参考文献
当前 LLM 模式:`trustgraph-base/trustgraph/schema/services/llm.py`
当前提示词模式:`trustgraph-base/trustgraph/schema/services/prompt.py`
当前代理模式:`trustgraph-base/trustgraph/schema/services/agent.py`
LLM 服务基础地址:`trustgraph-base/trustgraph/base/llm_service.py`
VertexAI 提供商:`trustgraph-vertexai/trustgraph/model/text_completion/vertexai/llm.py`
网关 API`trustgraph-base/trustgraph/api/`
CLI 工具:`trustgraph-cli/trustgraph/cli/`