feat: 优化 语音速度
This commit is contained in:
311
docs/GROK_API_DEBUG_GUIDE.md
Normal file
311
docs/GROK_API_DEBUG_GUIDE.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# 🔍 Grok API 调试指南
|
||||
|
||||
## 数据格式说明
|
||||
|
||||
### WebClient 处理后的数据格式
|
||||
|
||||
Spring WebFlux 的 `WebClient.bodyToFlux(String.class)` 会自动处理 SSE 流,每个元素直接就是一个完整的 JSON 字符串:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "8670de6a-75b3-97e2-fa5a-c2e3d7f1d0f2",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 1764858564,
|
||||
"model": "grok-4-1-fast-non-reasoning",
|
||||
"choices": [{
|
||||
"index": 0,
|
||||
"delta": {
|
||||
"content": "你",
|
||||
"role": "assistant"
|
||||
},
|
||||
"finish_reason": null
|
||||
}],
|
||||
"system_fingerprint": "fp_174298dd8e"
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 说明 | 示例值 |
|
||||
|------|------|--------|
|
||||
| `id` | 请求唯一ID | `"8670de6a-75b3-97e2-fa5a-c2e3d7f1d0f2"` |
|
||||
| `object` | 对象类型 | `"chat.completion.chunk"` |
|
||||
| `created` | 创建时间戳 | `1764858564` |
|
||||
| `model` | 使用的模型 | `"grok-4-1-fast-non-reasoning"` |
|
||||
| `choices[0].index` | 选择索引 | `0` |
|
||||
| `choices[0].delta.content` | 当前token内容 | `"你"` |
|
||||
| `choices[0].delta.role` | 角色(首次出现) | `"assistant"` |
|
||||
| `choices[0].finish_reason` | 结束原因 | `null`, `"stop"`, `"length"`, `"content_filter"` |
|
||||
| `system_fingerprint` | 系统指纹 | `"fp_174298dd8e"` |
|
||||
|
||||
## finish_reason 说明
|
||||
|
||||
| 值 | 含义 | 处理建议 |
|
||||
|----|------|----------|
|
||||
| `null` | 流还在继续 | 继续接收token |
|
||||
| `"stop"` | 正常结束 | ✅ 回复完整 |
|
||||
| `"length"` | 达到最大token限制 | ⚠️ 回复可能被截断,建议增加 `max_tokens` |
|
||||
| `"content_filter"` | 内容被过滤 | ⚠️ 回复包含敏感内容 |
|
||||
|
||||
## 解析流程
|
||||
|
||||
```
|
||||
收到数据 → 跳过空行 → 检查 [DONE] → 去除 "data: " 前缀(如果有)
|
||||
↓
|
||||
解析 JSON → 验证 choices 字段 → 获取 choices[0]
|
||||
↓
|
||||
检查 delta 字段 → 提取 content → 回调 onToken(content)
|
||||
↓
|
||||
检查 finish_reason → 记录日志 → 完成
|
||||
```
|
||||
|
||||
## 日志级别配置
|
||||
|
||||
在 `application.yml` 中配置:
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
level:
|
||||
com.xiaozhi.dialogue.llm.GrokStreamService: DEBUG # 或 TRACE
|
||||
```
|
||||
|
||||
### 各级别输出内容
|
||||
|
||||
#### TRACE(最详细)
|
||||
```
|
||||
TRACE - 收到原始SSE数据: {"id":"8670de6a...
|
||||
TRACE - 提取到token: 你
|
||||
TRACE - 提取到token: 好
|
||||
```
|
||||
|
||||
#### DEBUG
|
||||
```
|
||||
DEBUG - 请求体: {"model":"grok-4-1-fast-non-reasoning",...}
|
||||
DEBUG - 收到角色信息: assistant
|
||||
DEBUG - JSON中缺少choices字段(如果有问题)
|
||||
```
|
||||
|
||||
#### INFO
|
||||
```
|
||||
INFO - 开始调用Grok API - Model: grok-4-1-fast-non-reasoning, UserMessage: 你好...
|
||||
INFO - 流结束原因: length
|
||||
INFO - Grok API流式调用完成
|
||||
```
|
||||
|
||||
#### ERROR
|
||||
```
|
||||
ERROR - JSON解析失败 - 原始数据: xxx
|
||||
ERROR - LLM调用失败: Connection refused
|
||||
```
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 问题1: 没有收到任何token
|
||||
|
||||
**可能原因**:
|
||||
- API Key 错误
|
||||
- 网络连接问题
|
||||
- API URL 配置错误
|
||||
|
||||
**检查步骤**:
|
||||
1. 查看日志是否有 `INFO - 开始调用Grok API`
|
||||
2. 检查是否有错误日志
|
||||
3. 验证 `application.yml` 中的配置:
|
||||
```yaml
|
||||
xiaozhi:
|
||||
voice-stream:
|
||||
grok:
|
||||
api-key: ${GROK_API_KEY}
|
||||
api-url: https://api.x.ai/v1/chat/completions
|
||||
```
|
||||
|
||||
### 问题2: token乱码或格式错误
|
||||
|
||||
**检查点**:
|
||||
1. 启用 TRACE 日志查看原始数据
|
||||
2. 确认 JSON 格式是否正确
|
||||
3. 查看是否有 `ERROR - JSON解析失败` 日志
|
||||
|
||||
### 问题3: 回复被截断
|
||||
|
||||
**日志特征**:
|
||||
```
|
||||
INFO - 流结束原因: length
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
增加 `DEFAULT_MAX_TOKENS` 常量:
|
||||
```java
|
||||
private static final int DEFAULT_MAX_TOKENS = 4000; // 或更大
|
||||
```
|
||||
|
||||
### 问题4: WebClient 连接超时
|
||||
|
||||
**错误日志**:
|
||||
```
|
||||
ERROR - LLM调用失败: Connection timeout
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
在 `initWebClient()` 中增加超时配置:
|
||||
```java
|
||||
this.webClient = WebClient.builder()
|
||||
.baseUrl(apiUrl)
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
|
||||
.defaultHeader(HttpHeaders.ACCEPT, "text/event-stream")
|
||||
.build();
|
||||
```
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 使用 curl 测试 API
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.x.ai/v1/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "grok-4-1-fast-non-reasoning",
|
||||
"messages": [{"role": "user", "content": "你好"}],
|
||||
"stream": true
|
||||
}'
|
||||
```
|
||||
|
||||
预期输出:
|
||||
```
|
||||
data: {"id":"xxx","object":"chat.completion.chunk",...}
|
||||
data: {"id":"xxx","object":"chat.completion.chunk",...}
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
### 2. 检查配置
|
||||
|
||||
```java
|
||||
// 在 GrokStreamService 中添加测试方法
|
||||
@PostConstruct
|
||||
public void testConfig() {
|
||||
logger.info("Grok配置:");
|
||||
logger.info(" API URL: {}", voiceStreamConfig.getGrok().getApiUrl());
|
||||
logger.info(" Model: {}", voiceStreamConfig.getGrok().getModel());
|
||||
logger.info(" API Key配置: {}", voiceStreamConfig.getGrok().getApiKey() != null ? "已配置" : "未配置");
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 单元测试
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void testParseSSE() {
|
||||
String testJson = """
|
||||
{
|
||||
"id": "test",
|
||||
"choices": [{
|
||||
"delta": {
|
||||
"content": "测试"
|
||||
}
|
||||
}]
|
||||
}
|
||||
""";
|
||||
|
||||
// 测试解析逻辑
|
||||
}
|
||||
```
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 关键指标
|
||||
|
||||
1. **首 token 延迟**:从发送请求到收到第一个 token 的时间
|
||||
2. **token 吞吐率**:每秒收到的 token 数量
|
||||
3. **总响应时间**:从开始到收到 [DONE] 的时间
|
||||
|
||||
### 添加监控日志
|
||||
|
||||
```java
|
||||
private long startTime;
|
||||
private int tokenCount = 0;
|
||||
|
||||
public void streamChat(...) {
|
||||
startTime = System.currentTimeMillis();
|
||||
// ...
|
||||
.doOnNext(token -> {
|
||||
tokenCount++;
|
||||
if (tokenCount == 1) {
|
||||
long firstTokenLatency = System.currentTimeMillis() - startTime;
|
||||
logger.info("首token延迟: {}ms", firstTokenLatency);
|
||||
}
|
||||
})
|
||||
.doOnComplete(() -> {
|
||||
long totalTime = System.currentTimeMillis() - startTime;
|
||||
double tps = tokenCount / (totalTime / 1000.0);
|
||||
logger.info("总计: {}个token, 耗时: {}ms, 吞吐率: {:.2f} tokens/s",
|
||||
tokenCount, totalTime, tps);
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 保存原始响应
|
||||
|
||||
```java
|
||||
// 在parseSSE中
|
||||
logger.trace("原始响应: {}", line);
|
||||
```
|
||||
|
||||
### 2. 使用断点调试
|
||||
|
||||
在以下位置设置断点:
|
||||
- `parseSSE()` 方法入口
|
||||
- `sink.next(content)` 之前
|
||||
- `callback.onToken()` 调用处
|
||||
|
||||
### 3. 模拟测试
|
||||
|
||||
创建测试类:
|
||||
```java
|
||||
@Test
|
||||
public void testGrokStreamService() {
|
||||
GrokStreamService service = new GrokStreamService();
|
||||
service.streamChat("你好", null, new TokenCallback() {
|
||||
@Override
|
||||
public void onToken(String token) {
|
||||
System.out.println("Token: " + token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
System.out.println("完成");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
System.err.println("错误: " + error);
|
||||
}
|
||||
});
|
||||
|
||||
// 等待完成
|
||||
Thread.sleep(10000);
|
||||
}
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
正常工作流程:
|
||||
1. ✅ 看到 `INFO - 开始调用Grok API`
|
||||
2. ✅ 看到多个 `TRACE - 提取到token: xxx`
|
||||
3. ✅ 看到 `INFO - 流结束原因: stop`(或 length)
|
||||
4. ✅ 看到 `INFO - Grok API流式调用完成`
|
||||
|
||||
如果出现问题:
|
||||
1. 检查日志级别是否足够详细
|
||||
2. 查找 ERROR 级别的日志
|
||||
3. 验证配置是否正确
|
||||
4. 使用 curl 直接测试 API
|
||||
5. 检查网络连接
|
||||
|
||||
---
|
||||
|
||||
**需要更多帮助?** 提供完整的错误日志和配置信息。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user