mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 00:17:45 +08:00
fix(antigravity): send googleSearch instead of the Gemini 1.5 retrieval tool
The transport boundary rewrote `googleSearch` into the Gemini 1.5-era
`googleSearchRetrieval` spelling before every v1internal call, on the stated
grounds that the private backend rejects `googleSearch` when it is combined
with function declarations. That rewrite breaks grounding on Gemini 3.
Observed on stabey-124 against daily-cloudcode-pa.googleapis.com. A controlled
pair, same model and keys, 5 seconds apart:
- no `web_search_options` -> 200
- with `web_search_options` -> 502 on all three candidates
The outgoing body carried `tools: [{"googleSearchRetrieval": {}}]` and no
function declarations at all, so the documented mixed-tool rationale did not
apply. `request_candidates.error_message` holds what the backend actually
said:
Malformed function call: call:google_search{query:current UTC date time}
Malformed function call: call:google:search{query:current UTC date}
Malformed function call: call:google_search{queries:[current UTC date]}
The model reaches for `google_search`, the legacy declaration binds nothing,
and the turn dies unparsed. CLIProxyAPI sends `googleSearch` to this same
v1internal surface, including alongside function declarations.
Keep folding the snake_case `google_search` alias into the canonical
`googleSearch` key, and leave a request that already spells the tool
`googleSearchRetrieval` untouched.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -120,11 +120,18 @@ pub fn build_antigravity_safe_v1internal_request(
|
|||||||
AntigravityRequestEnvelopeSupport::Supported(envelope)
|
AntigravityRequestEnvelopeSupport::Supported(envelope)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Antigravity's private v1internal Gemini surface still uses the legacy
|
/// Antigravity's private v1internal Gemini surface takes the same
|
||||||
/// `googleSearchRetrieval` spelling. The public Gemini converter emits the
|
/// `googleSearch` grounding tool as the public one. Only the snake_case alias
|
||||||
/// newer `googleSearch` spelling, which the private backend rejects when it is
|
/// needs folding into the canonical camelCase key.
|
||||||
/// combined with function declarations. Normalize only at this transport
|
///
|
||||||
/// boundary so public Gemini requests retain their native shape.
|
/// This used to rewrite `googleSearch` into the Gemini 1.5-era
|
||||||
|
/// `googleSearchRetrieval` spelling. Gemini 3 rejects that: the model emits a
|
||||||
|
/// `google_search` call the backend cannot bind to any declared tool, and the
|
||||||
|
/// turn dies with `MALFORMED_FUNCTION_CALL`, e.g.
|
||||||
|
/// `Malformed function call: call:google_search{query:current UTC date}`
|
||||||
|
/// observed against `daily-cloudcode-pa.googleapis.com` with
|
||||||
|
/// `tools: [{"googleSearchRetrieval": {}}]` and no function declarations.
|
||||||
|
/// CLIProxyAPI sends `googleSearch` to the same v1internal surface.
|
||||||
fn normalize_antigravity_builtin_tool_names(request: &mut Map<String, Value>) {
|
fn normalize_antigravity_builtin_tool_names(request: &mut Map<String, Value>) {
|
||||||
let Some(tools) = request.get_mut("tools").and_then(Value::as_array_mut) else {
|
let Some(tools) = request.get_mut("tools").and_then(Value::as_array_mut) else {
|
||||||
return;
|
return;
|
||||||
@@ -135,14 +142,9 @@ fn normalize_antigravity_builtin_tool_names(request: &mut Map<String, Value>) {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(payload) = tool_object.remove("googleSearch") {
|
|
||||||
tool_object
|
|
||||||
.entry("googleSearchRetrieval".to_string())
|
|
||||||
.or_insert(payload);
|
|
||||||
}
|
|
||||||
if let Some(payload) = tool_object.remove("google_search") {
|
if let Some(payload) = tool_object.remove("google_search") {
|
||||||
tool_object
|
tool_object
|
||||||
.entry("googleSearchRetrieval".to_string())
|
.entry("googleSearch".to_string())
|
||||||
.or_insert(payload);
|
.or_insert(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,6 +224,43 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search_only_request_keeps_the_modern_google_search_spelling() {
|
||||||
|
// Reproduces the live failure: a grounding-only request (no function
|
||||||
|
// declarations) that went out as `googleSearchRetrieval` came back as
|
||||||
|
// `Malformed function call: call:google_search{query:current UTC date}`
|
||||||
|
// from daily-cloudcode-pa.googleapis.com.
|
||||||
|
let request_body = json!({
|
||||||
|
"contents": [
|
||||||
|
{ "role": "user", "parts": [{ "text": "today's UTC date?" }] }
|
||||||
|
],
|
||||||
|
"tools": [{ "googleSearch": {} }]
|
||||||
|
});
|
||||||
|
|
||||||
|
let envelope = match build_antigravity_safe_v1internal_request(
|
||||||
|
&sample_auth(),
|
||||||
|
"request-ant-search-1",
|
||||||
|
"gemini-3.8-flash-high",
|
||||||
|
&request_body,
|
||||||
|
AntigravityEnvelopeRequestType::Agent,
|
||||||
|
) {
|
||||||
|
AntigravityRequestEnvelopeSupport::Supported(envelope) => envelope,
|
||||||
|
AntigravityRequestEnvelopeSupport::Unsupported(reason) => {
|
||||||
|
panic!("search-only envelope should be supported: {reason:?}")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tools = envelope["request"]["tools"]
|
||||||
|
.as_array()
|
||||||
|
.expect("tools should survive");
|
||||||
|
assert_eq!(tools.len(), 1, "{tools:?}");
|
||||||
|
assert_eq!(tools[0]["googleSearch"], json!({}));
|
||||||
|
assert!(
|
||||||
|
tools[0].get("googleSearchRetrieval").is_none(),
|
||||||
|
"the Gemini 1.5 spelling must not be reintroduced: {tools:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn real_agent_request_preserves_antigravity_agent_fields() {
|
fn real_agent_request_preserves_antigravity_agent_fields() {
|
||||||
let request_body = json!({
|
let request_body = json!({
|
||||||
@@ -328,12 +367,9 @@ mod tests {
|
|||||||
.get("include_server_side_tool_invocations")
|
.get("include_server_side_tool_invocations")
|
||||||
.is_none());
|
.is_none());
|
||||||
assert!(envelope["request"]["tools"][0]
|
assert!(envelope["request"]["tools"][0]
|
||||||
.get("googleSearch")
|
.get("googleSearchRetrieval")
|
||||||
.is_none());
|
.is_none());
|
||||||
assert_eq!(
|
assert_eq!(envelope["request"]["tools"][0]["googleSearch"], json!({}));
|
||||||
envelope["request"]["tools"][0]["googleSearchRetrieval"],
|
|
||||||
json!({})
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
envelope["request"]["tools"][1]["functionDeclarations"][0]["name"],
|
envelope["request"]["tools"][1]["functionDeclarations"][0]["name"],
|
||||||
"run_command"
|
"run_command"
|
||||||
@@ -471,7 +507,7 @@ mod tests {
|
|||||||
.get("google_search")
|
.get("google_search")
|
||||||
.is_none());
|
.is_none());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
envelope["request"]["tools"][0]["googleSearchRetrieval"],
|
envelope["request"]["tools"][0]["googleSearch"],
|
||||||
json!({
|
json!({
|
||||||
"dynamicRetrievalConfig": {
|
"dynamicRetrievalConfig": {
|
||||||
"mode": "MODE_UNSPECIFIED"
|
"mode": "MODE_UNSPECIFIED"
|
||||||
|
|||||||
Reference in New Issue
Block a user