# 🔍 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. 检查网络连接 --- **需要更多帮助?** 提供完整的错误日志和配置信息。