修复流式测试夹具与自主构建重试
Project CI / Repository checks (pull_request) Successful in 4m10s
Project CI / Frontend tests (pull_request) Successful in 5m5s
Project CI / Backend tests (pull_request) Successful in 6m3s
Project CI / Native shell tests (pull_request) Successful in 14m4s

兼容 stream=true 的 Responses 与 Chat Completions 测试响应

修正截断 scaffold 修复错误前缀匹配

确保最终回复失败审计夹具先完成 planning 阶段
This commit is contained in:
2026-08-27 14:56:29 +08:00
parent a155529861
commit 1926aa4fb5
2 changed files with 162 additions and 61 deletions
@@ -668,7 +668,7 @@ pub(super) const AGENT_RUNTIME_AUTONOMOUS_MANIFEST_DAG_WAIT_LIVENESS_ERROR_PREFI
pub(super) const AGENT_RUNTIME_AUTONOMOUS_RESPONSE_PLAN_LIVENESS_ERROR_PREFIX: &str =
"自主构建专业 Agent 已给出结论但结构化计划仍未完成";
pub(super) const AGENT_RUNTIME_AUTONOMOUS_TRUNCATED_SCAFFOLD_ERROR_PREFIX: &str =
"自主构建完整写入游戏入口(index.html 或 game/index.html时必须提交闭合且可运行的 HTML";
"自主构建完整写入 game/index.html 时必须提交闭合且可运行的 HTML";
pub(super) const AGENT_RUNTIME_QUALITY_REVIEW_AGENT_ID: &str = "quality-review";
pub(super) const AGENT_RUNTIME_SUPERVISOR_INITIAL_COLLABORATION_LIVENESS_ERROR_PREFIX: &str =
"Project Supervisor 首批协作在首个 planning 窗口内未建立";
@@ -1557,17 +1557,35 @@ pub(crate) fn spawn_mock_llm_tool_plan_then_invalid_final_reply(
let base_url = format!("http://{}", listener.local_addr().expect("mock llm addr"));
std::thread::spawn(move || {
let (mut planning_stream, _) = listener.accept().expect("mock tool plan accept");
drop(read_mock_http_request(&mut planning_stream));
let planning_body = serde_json::json!({
"id": "resp_invalid_final_reply_planning",
"model": "mock-game-model",
"output_text": planning_response,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string();
let planning_request = read_mock_http_request(&mut planning_stream);
let planning_streaming = planning_request.contains("\"stream\":true");
let (planning_body, planning_content_type) = if planning_streaming {
(
format!(
"data: {}\n\ndata: {}\n\n",
serde_json::json!({
"type": "response.output_text.delta",
"delta": planning_response
}),
serde_json::json!({ "type": "response.completed" })
),
"text/event-stream; charset=utf-8",
)
} else {
(
serde_json::json!({
"id": "resp_invalid_final_reply_planning",
"model": "mock-game-model",
"output_text": planning_response,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string(),
"application/json",
)
};
let planning_response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
"HTTP/1.1 200 OK\r\nContent-Type: {planning_content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
planning_body.len(),
planning_body
);
@@ -1762,30 +1780,65 @@ fn spawn_mock_llm_server_responses_with_capture(
if let Some(sender) = request_sender.as_ref() {
let _ = sender.send(request_text.clone());
}
let body = if request_text.contains("POST /responses HTTP/1.1") {
serde_json::json!({
"id": "resp_game_creator_mock",
"model": "mock-game-model",
"output_text": response_content,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
let is_responses = request_text.contains("POST /responses HTTP/1.1");
let is_stream = request_text.contains("\"stream\":true");
let (body, content_type) = if is_stream && is_responses {
(
format!(
"data: {}\n\ndata: {}\n\n",
serde_json::json!({
"type": "response.output_text.delta",
"delta": response_content
}),
serde_json::json!({ "type": "response.completed" })
),
"text/event-stream; charset=utf-8",
)
} else if is_stream {
(
format!(
"data: {}\n\ndata: [DONE]\n\n",
serde_json::json!({
"id": "chatcmpl_game_creator_mock",
"object": "chat.completion.chunk",
"choices": [{
"index": 0,
"delta": { "role": "assistant", "content": response_content },
"finish_reason": "stop"
}]
})
),
"text/event-stream; charset=utf-8",
)
} else if is_responses {
(
serde_json::json!({
"id": "resp_game_creator_mock",
"model": "mock-game-model",
"output_text": response_content,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string(),
"application/json",
)
} else {
serde_json::json!({
"id": "chatcmpl_game_creator_mock",
"model": "mock-game-model",
"choices": [
{
(
serde_json::json!({
"id": "chatcmpl_game_creator_mock",
"model": "mock-game-model",
"choices": [{
"message": { "content": response_content },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 11, "completion_tokens": 22, "total_tokens": 33 }
})
}
.to_string();
}],
"usage": { "prompt_tokens": 11, "completion_tokens": 22, "total_tokens": 33 }
})
.to_string(),
"application/json",
)
};
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
"HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
body.len(),
body
);
@@ -2159,18 +2212,34 @@ fn spawn_mock_llm_tool_plan_then_transient_final_reply(
.try_into()
.unwrap_or(u64::MAX);
let planning_request = read_mock_http_request(&mut planning_stream);
let _ = request_capture_sender.send((planning_accepted_at_ms, planning_request));
let _ = request_capture_sender.send((planning_accepted_at_ms, planning_request.clone()));
let _ = request_notice_sender.send(());
let planning_body = serde_json::json!({
"id": "resp_transient_final_reply_planning",
"model": "mock-game-model",
"output_text": planning_response,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string();
let planning_body = if planning_request.contains("\"stream\":true") {
format!(
"data: {}\n\ndata: {}\n\n",
serde_json::json!({
"type": "response.output_text.delta",
"delta": planning_response
}),
serde_json::json!({ "type": "response.completed" })
)
} else {
serde_json::json!({
"id": "resp_transient_final_reply_planning",
"model": "mock-game-model",
"output_text": planning_response,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string()
};
let planning_content_type = if planning_request.contains("\"stream\":true") {
"text/event-stream; charset=utf-8"
} else {
"application/json"
};
let planning_http_response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
"HTTP/1.1 200 OK\r\nContent-Type: {planning_content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
planning_body.len(),
planning_body
);
@@ -2250,7 +2319,7 @@ fn spawn_mock_llm_tool_plan_then_transient_final_compaction(
let (mut planning_stream, _) = listener.accept().expect("mock tool plan accept");
let planning_accepted_at_ms = accepted_at_ms();
let planning_request = read_mock_http_request(&mut planning_stream);
let _ = request_capture_sender.send((planning_accepted_at_ms, planning_request));
let _ = request_capture_sender.send((planning_accepted_at_ms, planning_request.clone()));
replace_test_local_config(
&config_path,
format!(
@@ -2274,16 +2343,32 @@ fn spawn_mock_llm_tool_plan_then_transient_final_compaction(
),
);
let _ = request_notice_sender.send(());
let planning_body = serde_json::json!({
"id": "resp_final_compaction_planning",
"model": "mock-game-model",
"output_text": planning_response,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string();
let planning_body = if planning_request.contains("\"stream\":true") {
format!(
"data: {}\n\ndata: {}\n\n",
serde_json::json!({
"type": "response.output_text.delta",
"delta": planning_response
}),
serde_json::json!({ "type": "response.completed" })
)
} else {
serde_json::json!({
"id": "resp_final_compaction_planning",
"model": "mock-game-model",
"output_text": planning_response,
"status": "completed",
"usage": { "input_tokens": 11, "output_tokens": 22, "total_tokens": 33 }
})
.to_string()
};
let planning_content_type = if planning_request.contains("\"stream\":true") {
"text/event-stream; charset=utf-8"
} else {
"application/json"
};
let planning_http_response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
"HTTP/1.1 200 OK\r\nContent-Type: {planning_content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
planning_body.len(),
planning_body
);
@@ -2308,19 +2393,35 @@ fn spawn_mock_llm_tool_plan_then_transient_final_compaction(
let recovered_compaction_request = read_mock_http_request(&mut recovered_compaction_stream);
let _ = request_capture_sender.send((
recovered_compaction_accepted_at_ms,
recovered_compaction_request,
recovered_compaction_request.clone(),
));
let _ = request_notice_sender.send(());
let compaction_body = serde_json::json!({
"id": "resp_final_compaction_recovered",
"model": "mock-game-model",
"output_text": compaction_response,
"status": "completed",
"usage": { "input_tokens": 44, "output_tokens": 12, "total_tokens": 56 }
})
.to_string();
let compaction_body = if recovered_compaction_request.contains("\"stream\":true") {
format!(
"data: {}\n\ndata: {}\n\n",
serde_json::json!({
"type": "response.output_text.delta",
"delta": compaction_response
}),
serde_json::json!({ "type": "response.completed" })
)
} else {
serde_json::json!({
"id": "resp_final_compaction_recovered",
"model": "mock-game-model",
"output_text": compaction_response,
"status": "completed",
"usage": { "input_tokens": 44, "output_tokens": 12, "total_tokens": 56 }
})
.to_string()
};
let compaction_content_type = if recovered_compaction_request.contains("\"stream\":true") {
"text/event-stream; charset=utf-8"
} else {
"application/json"
};
let compaction_http_response = format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
"HTTP/1.1 200 OK\r\nContent-Type: {compaction_content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
compaction_body.len(),
compaction_body
);