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

7.3 KiB
Raw Blame History

🔍 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 配置错误

检查步骤

  1. 查看日志是否有 INFO - 开始调用Grok API
  2. 检查是否有错误日志
  3. 验证 application.yml 中的配置:
    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 常量:

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": "测试"
            }
          }]
        }
        """;
    
    // 测试解析逻辑
}

性能监控

关键指标

  1. 首 token 延迟:从发送请求到收到第一个 token 的时间
  2. token 吞吐率:每秒收到的 token 数量
  3. 总响应时间:从开始到收到 [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);
}

总结

正常工作流程:

  1. 看到 INFO - 开始调用Grok API
  2. 看到多个 TRACE - 提取到token: xxx
  3. 看到 INFO - 流结束原因: stop(或 length
  4. 看到 INFO - Grok API流式调用完成

如果出现问题:

  1. 检查日志级别是否足够详细
  2. 查找 ERROR 级别的日志
  3. 验证配置是否正确
  4. 使用 curl 直接测试 API
  5. 检查网络连接

需要更多帮助? 提供完整的错误日志和配置信息。