Files
server/docs/GROK_API_DEBUG_GUIDE.md
2025-12-06 22:41:44 +08:00

312 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔍 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. 检查网络连接
---
**需要更多帮助?** 提供完整的错误日志和配置信息。