feat(ai-pipeline): 支持 reasoning signature 及媒体内容块的跨格式流式转换

- 在 CanonicalStreamEvent 中新增 ReasoningSignature 和 ContentPart 变体,CanonicalContentPart 枚举覆盖图片/文件/音频
- Gemini 流解析器提取 thoughtSignature,并对 inlineData/fileData 等非文本 part 生成 ContentPart 事件
- Claude 流聚合支持 thinking_delta / signature_delta,输出 thinking block 携带 signature 字段
- Gemini 同步响应聚合重写,支持媒体 part 和 reasoning signature 的完整还原
- 跨格式矩阵(gemini↔claude↔openai)补全图片块双向转换及 reasoning signature 传递
- 请求转换层(to/from openai_chat)补全 Claude/Gemini 的 thinking、图片、工具调用字段映射
- 新增/扩展测试:inline image 双向重写、thinking signature 聚合、跨格式 sync product 媒体字段
This commit is contained in:
fawney19
2026-04-24 02:18:34 +08:00
parent 0f94f92c37
commit 581bc03d4e
28 changed files with 4155 additions and 499 deletions
@@ -888,6 +888,7 @@ fn converts_claude_cli_tool_use_to_openai_cli_function_call() {
},
{
"type": "function_call",
"id": "tool_123",
"call_id": "tool_123",
"name": "read_file",
"arguments": "{\"path\":\"/tmp/test.txt\"}"
@@ -952,7 +953,10 @@ fn converts_gemini_cli_response_to_openai_cli_response() {
"usage": {
"input_tokens": 3,
"output_tokens": 7,
"total_tokens": 10
"total_tokens": 10,
"output_tokens_details": {
"reasoning_tokens": 2
}
}
})
);
@@ -1011,6 +1015,7 @@ fn converts_gemini_cli_function_call_to_openai_cli_function_call() {
},
{
"type": "function_call",
"id": "call_auto_1",
"call_id": "call_auto_1",
"name": "get_weather",
"arguments": "{\"location\":\"Tokyo\"}"
@@ -1019,7 +1024,10 @@ fn converts_gemini_cli_function_call_to_openai_cli_function_call() {
"usage": {
"input_tokens": 3,
"output_tokens": 7,
"total_tokens": 10
"total_tokens": 10,
"output_tokens_details": {
"reasoning_tokens": 2
}
}
})
);
@@ -886,7 +886,7 @@ mod tests {
record_admin_provider_pool_stream_timeout(&runner, "provider-1", "key-4", &pool_config)
.await;
let runtime = read_admin_provider_pool_runtime_state(
let mut runtime = read_admin_provider_pool_runtime_state(
&runner,
"provider-1",
&key_ids,
@@ -894,6 +894,25 @@ mod tests {
None,
)
.await;
for _ in 0..20 {
if runtime
.cooldown_reason_by_key
.get("key-4")
.map(String::as_str)
== Some("stream_timeout_x2")
{
break;
}
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
runtime = read_admin_provider_pool_runtime_state(
&runner,
"provider-1",
&key_ids,
&pool_config,
None,
)
.await;
}
assert_eq!(
runtime
@@ -877,7 +877,7 @@ async fn gateway_executes_openai_chat_cross_format_upstream_stream_via_local_fin
"index": 0,
"message": {
"role": "assistant",
"content": "Gemini Chat"
"content": "Hello Gemini Chat"
},
"finish_reason": "stop"
}],
@@ -860,6 +860,7 @@ async fn gateway_executes_openai_cli_cross_format_function_call_upstream_stream_
},
{
"type": "function_call",
"id": "call_auto_1",
"call_id": "call_auto_1",
"name": "get_weather",
"arguments": "{\"location\":\"Tokyo\"}"
@@ -1410,13 +1411,13 @@ async fn gateway_executes_openai_cli_antigravity_cross_format_upstream_stream_vi
assert_eq!(
response_json,
json!({
"id": "resp_antigravity_cli_xfmt_123",
"id": "resp-local-stream",
"object": "response",
"status": "completed",
"model": "claude-sonnet-4-5",
"output": [{
"type": "message",
"id": "resp_antigravity_cli_xfmt_123_msg",
"id": "resp-local-stream_msg",
"role": "assistant",
"status": "completed",
"content": [{
@@ -912,6 +912,7 @@ async fn gateway_executes_gemini_chat_sync_upstream_stream_via_local_finalize_re
assert_eq!(
response_json,
json!({
"responseId": "resp-local-stream",
"candidates": [{
"content": {
"parts": [{"text": "Hello Gemini"}],
@@ -1972,10 +1973,18 @@ async fn gateway_executes_antigravity_gemini_cli_sync_upstream_stream_via_local_
assert_eq!(
response_json,
json!({
"responseId": "resp-local-stream",
"_v1internal_response_id": "resp_antigravity_cli_sync_123",
"candidates": [{
"content": {
"parts": [
{
"functionCall": {
"name": "get_weather",
"args": {"city": "SF"},
"id": "call_get_weather_0"
}
},
{"text": "Hello Antigravity CLI"},
{
"functionCall": {
@@ -2115,6 +2115,7 @@ async fn gateway_executes_antigravity_gemini_cli_sync_via_local_decision_gate_af
assert_eq!(
response_json,
json!({
"responseId": "resp-local-stream",
"_v1internal_response_id": "resp_antigravity_cli_local_sync_123",
"candidates": [{
"content": {
+1 -1
View File
@@ -36,7 +36,7 @@ where
let mut stored = None;
// Usage terminal events are written on a shared background runtime; under full-suite parallel
// load they can lag noticeably behind the request/response assertion path.
let timeout = std::time::Duration::from_secs(30);
let timeout = std::time::Duration::from_secs(60);
let deadline = tokio::time::Instant::now() + timeout;
loop {
stored = repository