fix(runtime): SSE行缓冲 — 修复glm tool call参数截断丢失
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

根因: OpenAI driver的SSE解析直接按TCP chunk分行,
当glm的JSON响应被拆成多个TCP包时,SSE data行被截断,
导致tool call arguments丢失(input={})。

修复:
1. 添加pending_line缓冲区,跨chunk累积不完整的SSE行
2. 只处理完整的行(\n结尾),未完成的保留到下次
3. researcher.infer_action()增加更多字段推断(search/keyword/q等)

验证: 99 tests PASS, 160 hands tests PASS
This commit is contained in:
iven
2026-04-22 15:20:23 +08:00
parent bc9537cd80
commit 3cb9709caf
2 changed files with 100 additions and 28 deletions

View File

@@ -163,6 +163,7 @@ impl LlmDriver for OpenAiDriver {
let mut current_tool_id: Option<String> = None;
let mut sse_event_count: usize = 0;
let mut raw_bytes_total: usize = 0;
let mut pending_line = String::new(); // Buffer for incomplete SSE lines
while let Some(chunk_result) = byte_stream.next().await {
let chunk = match chunk_result {
@@ -180,13 +181,21 @@ impl LlmDriver for OpenAiDriver {
if raw_bytes_total <= 600 {
tracing::debug!("[OpenAI:stream] RAW chunk ({} bytes): {:?}", text.len(), &text[..text.len().min(500)]);
}
for line in text.lines() {
// Accumulate text and split by lines, handling incomplete last line
pending_line.push_str(&text);
// Extract complete lines (ending with \n), keep the rest pending
let mut complete_lines: Vec<String> = Vec::new();
while let Some(pos) = pending_line.find('\n') {
complete_lines.push(pending_line[..pos].to_string());
pending_line = pending_line[pos + 1..].to_string();
}
for line in complete_lines {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with(':') {
continue; // Skip empty lines and SSE comments
}
// Handle both "data: " (standard) and "data:" (no space)
let data = if let Some(d) = trimmed.strip_prefix("data: ") {
let data: Option<&str> = if let Some(d) = trimmed.strip_prefix("data: ") {
Some(d)
} else if let Some(d) = trimmed.strip_prefix("data:") {
Some(d.trim_start())