mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
修复 Gemini 工具结果透传 Vertex 格式
This commit is contained in:
@@ -345,7 +345,6 @@ fn canonical_block_to_gemini_part(
|
|||||||
..
|
..
|
||||||
} => Some(Some(json!({
|
} => Some(Some(json!({
|
||||||
"functionResponse": {
|
"functionResponse": {
|
||||||
"id": tool_use_id,
|
|
||||||
"name": name.clone()
|
"name": name.clone()
|
||||||
.or_else(|| tool_name_by_id.get(tool_use_id).cloned())
|
.or_else(|| tool_name_by_id.get(tool_use_id).cloned())
|
||||||
.unwrap_or_else(|| tool_use_id.clone()),
|
.unwrap_or_else(|| tool_use_id.clone()),
|
||||||
@@ -687,3 +686,41 @@ fn clean_gemini_schema(value: &mut Value) {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::CanonicalContentBlock;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn canonical_tool_result_to_gemini_request_omits_function_response_id() {
|
||||||
|
let mut tool_name_by_id = BTreeMap::new();
|
||||||
|
tool_name_by_id.insert("call_1".to_string(), "lookup".to_string());
|
||||||
|
|
||||||
|
let part = canonical_block_to_gemini_part(
|
||||||
|
&CanonicalContentBlock::ToolResult {
|
||||||
|
tool_use_id: "call_1".to_string(),
|
||||||
|
name: None,
|
||||||
|
output: Some(serde_json::json!({"ok": true})),
|
||||||
|
content_text: None,
|
||||||
|
is_error: false,
|
||||||
|
extensions: BTreeMap::new(),
|
||||||
|
},
|
||||||
|
&mut tool_name_by_id,
|
||||||
|
)
|
||||||
|
.expect("part should be representable")
|
||||||
|
.expect("part should not be omitted");
|
||||||
|
|
||||||
|
let function_response = part
|
||||||
|
.get("functionResponse")
|
||||||
|
.and_then(Value::as_object)
|
||||||
|
.expect("functionResponse should exist");
|
||||||
|
|
||||||
|
assert!(!function_response.contains_key("id"));
|
||||||
|
assert_eq!(function_response["name"], "lookup");
|
||||||
|
assert_eq!(
|
||||||
|
function_response["response"],
|
||||||
|
serde_json::json!({"result": {"ok": true}})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -208,6 +208,14 @@ pub fn build_same_format_provider_request_body(
|
|||||||
) {
|
) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if matches!(input.family, SameFormatProviderFamily::Gemini)
|
||||||
|
&& aether_ai_formats::api_format_alias_matches(
|
||||||
|
input.provider_api_format,
|
||||||
|
"gemini:generate_content",
|
||||||
|
)
|
||||||
|
{
|
||||||
|
strip_gemini_function_response_ids(&mut provider_request_body);
|
||||||
|
}
|
||||||
let require_body_stream_field = input.force_body_stream_field
|
let require_body_stream_field = input.force_body_stream_field
|
||||||
|| input
|
|| input
|
||||||
.body_json
|
.body_json
|
||||||
@@ -222,6 +230,28 @@ pub fn build_same_format_provider_request_body(
|
|||||||
Some(provider_request_body)
|
Some(provider_request_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn strip_gemini_function_response_ids(value: &mut Value) {
|
||||||
|
match value {
|
||||||
|
Value::Object(object) => {
|
||||||
|
for key in ["functionResponse", "function_response"] {
|
||||||
|
if let Some(function_response) = object.get_mut(key).and_then(Value::as_object_mut)
|
||||||
|
{
|
||||||
|
function_response.remove("id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for child in object.values_mut() {
|
||||||
|
strip_gemini_function_response_ids(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Array(items) => {
|
||||||
|
for item in items {
|
||||||
|
strip_gemini_function_response_ids(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_same_format_provider_upstream_url(
|
pub fn build_same_format_provider_upstream_url(
|
||||||
transport: &GatewayProviderTransportSnapshot,
|
transport: &GatewayProviderTransportSnapshot,
|
||||||
params: SameFormatProviderUpstreamUrlParams<'_>,
|
params: SameFormatProviderUpstreamUrlParams<'_>,
|
||||||
@@ -788,6 +818,66 @@ mod tests {
|
|||||||
assert!(body.get("stream").is_none());
|
assert!(body.get("stream").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn same_format_gemini_body_strips_function_response_id_for_upstream() {
|
||||||
|
let body = build_same_format_provider_request_body(SameFormatProviderRequestBodyInput {
|
||||||
|
body_json: &json!({
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"parts": [
|
||||||
|
{
|
||||||
|
"functionResponse": {
|
||||||
|
"id": "call_123",
|
||||||
|
"name": "lookup",
|
||||||
|
"response": {
|
||||||
|
"result": {
|
||||||
|
"id": "keep_result_id",
|
||||||
|
"ok": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function_response": {
|
||||||
|
"id": "call_456",
|
||||||
|
"name": "lookup_snake",
|
||||||
|
"response": {"ok": true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stream": true
|
||||||
|
}),
|
||||||
|
mapped_model: "gemini-upstream",
|
||||||
|
client_api_format: "gemini:generate_content",
|
||||||
|
provider_api_format: "gemini:generate_content",
|
||||||
|
source_model: None,
|
||||||
|
family: SameFormatProviderFamily::Gemini,
|
||||||
|
body_rules: None,
|
||||||
|
request_headers: None,
|
||||||
|
upstream_is_stream: true,
|
||||||
|
force_body_stream_field: false,
|
||||||
|
kiro_auth_config: None,
|
||||||
|
is_claude_code: false,
|
||||||
|
enable_model_directives: false,
|
||||||
|
})
|
||||||
|
.expect("body should build");
|
||||||
|
|
||||||
|
let function_response = &body["contents"][0]["parts"][0]["functionResponse"];
|
||||||
|
assert!(function_response.get("id").is_none());
|
||||||
|
assert_eq!(function_response["name"], "lookup");
|
||||||
|
assert_eq!(
|
||||||
|
function_response["response"]["result"]["id"],
|
||||||
|
"keep_result_id"
|
||||||
|
);
|
||||||
|
|
||||||
|
let function_response = &body["contents"][0]["parts"][1]["function_response"];
|
||||||
|
assert!(function_response.get("id").is_none());
|
||||||
|
assert_eq!(function_response["name"], "lookup_snake");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn same_format_stream_policy_wins_after_body_rules() {
|
fn same_format_stream_policy_wins_after_body_rules() {
|
||||||
let body_rules = json!([
|
let body_rules = json!([
|
||||||
|
|||||||
Reference in New Issue
Block a user