7.3 KiB
7.3 KiB
🔍 Grok API 调试指南
数据格式说明
WebClient 处理后的数据格式
Spring WebFlux 的 WebClient.bodyToFlux(String.class) 会自动处理 SSE 流,每个元素直接就是一个完整的 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 中配置:
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 配置错误
检查步骤:
- 查看日志是否有
INFO - 开始调用Grok API - 检查是否有错误日志
- 验证
application.yml中的配置:xiaozhi: voice-stream: grok: api-key: ${GROK_API_KEY} api-url: https://api.x.ai/v1/chat/completions
问题2: token乱码或格式错误
检查点:
- 启用 TRACE 日志查看原始数据
- 确认 JSON 格式是否正确
- 查看是否有
ERROR - JSON解析失败日志
问题3: 回复被截断
日志特征:
INFO - 流结束原因: length
解决方案:
增加 DEFAULT_MAX_TOKENS 常量:
private static final int DEFAULT_MAX_TOKENS = 4000; // 或更大
问题4: WebClient 连接超时
错误日志:
ERROR - LLM调用失败: Connection timeout
解决方案:
在 initWebClient() 中增加超时配置:
this.webClient = WebClient.builder()
.baseUrl(apiUrl)
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
.defaultHeader(HttpHeaders.ACCEPT, "text/event-stream")
.build();
测试建议
1. 使用 curl 测试 API
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. 检查配置
// 在 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. 单元测试
@Test
public void testParseSSE() {
String testJson = """
{
"id": "test",
"choices": [{
"delta": {
"content": "测试"
}
}]
}
""";
// 测试解析逻辑
}
性能监控
关键指标
- 首 token 延迟:从发送请求到收到第一个 token 的时间
- token 吞吐率:每秒收到的 token 数量
- 总响应时间:从开始到收到 [DONE] 的时间
添加监控日志
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. 保存原始响应
// 在parseSSE中
logger.trace("原始响应: {}", line);
2. 使用断点调试
在以下位置设置断点:
parseSSE()方法入口sink.next(content)之前callback.onToken()调用处
3. 模拟测试
创建测试类:
@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);
}
总结
正常工作流程:
- ✅ 看到
INFO - 开始调用Grok API - ✅ 看到多个
TRACE - 提取到token: xxx - ✅ 看到
INFO - 流结束原因: stop(或 length) - ✅ 看到
INFO - Grok API流式调用完成
如果出现问题:
- 检查日志级别是否足够详细
- 查找 ERROR 级别的日志
- 验证配置是否正确
- 使用 curl 直接测试 API
- 检查网络连接
需要更多帮助? 提供完整的错误日志和配置信息。