v3.11.10: fix Antigravity — interleave function_call/output pairs, trim Gemini turns (PR #11)

This commit is contained in:
Roman | RyzenAdvanced
2026-05-26 21:47:32 +04:00
Unverified
parent f6827f6c84
commit aa7b9e8280
6 changed files with 51 additions and 6 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## v3.11.10 (2026-05-26)
**Antigravity Fix: Interleave function_call/output Pairs, Gemini Turn Trimming (PR #11)**
### Bug Fixes
- **Fix Antigravity function_call/output ordering**: Tool calls and their responses are now properly interleaved in sequence (`function_call``function_call_output``function_call` → ...) instead of being grouped separately
- **Gemini sanitizer trimming**: Leading/trailing non-user turns removed for Google API compliance (Google requires conversation to start and end with user turn)
- **Stricter role boundary enforcement**: `functionCall` (model) and `functionResponse` (user) never merged across role boundaries
- **Merge PR #11**: Fix by qwen-chat coder
## v3.11.9 (2026-05-26)
## v3.11.9 (2026-05-26) ## v3.11.9 (2026-05-26)
**Antigravity Fix: Preserve functionCall/functionResponse in Gemini Sanitizer (PR #10)** **Antigravity Fix: Preserve functionCall/functionResponse in Gemini Sanitizer (PR #10)**

Binary file not shown.

Binary file not shown.

View File

@@ -27,6 +27,10 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("3.11.10", "2026-05-26", [
"Fix Antigravity: interleave function_call/output pairs (PR #11)",
"Gemini sanitizer: trim non-user turns for Google API compliance",
]),
("3.11.9", "2026-05-26", [ ("3.11.9", "2026-05-26", [
"Fix Antigravity: preserve functionCall/functionResponse (PR #10)", "Fix Antigravity: preserve functionCall/functionResponse (PR #10)",
"Prevents tool responses from being dropped in multi-turn sessions", "Prevents tool responses from being dropped in multi-turn sessions",

View File

@@ -83,6 +83,11 @@ model_catalog_json = ""
""" """
CHANGELOG = [ CHANGELOG = [
("3.11.10", "2026-05-26", [
"Fix Antigravity: interleave function_call/output pairs in correct sequence (PR #11)",
"Fix Gemini sanitizer: trim leading/trailing non-user turns for Google API compliance",
"Stricter function call/response isolation — no merging across role boundaries",
]),
("3.11.9", "2026-05-26", [ ("3.11.9", "2026-05-26", [
"Fix Antigravity: preserve functionCall/functionResponse in Gemini sanitizer (PR #10)", "Fix Antigravity: preserve functionCall/functionResponse in Gemini sanitizer (PR #10)",
"Prevents tool responses from being merged/dropped in multi-turn Antigravity sessions", "Prevents tool responses from being merged/dropped in multi-turn Antigravity sessions",

View File

@@ -4958,6 +4958,7 @@ def _antigravity_normalize_context(input_data, model=""):
if cid in tool_call_ids: if cid in tool_call_ids:
paired_calls.append((idx, item)) paired_calls.append((idx, item))
# Build result maintaining proper function_call -> function_call_output pairing
result = list(dev_messages) result = list(dev_messages)
compaction_summaries = [] compaction_summaries = []
@@ -4973,10 +4974,29 @@ def _antigravity_normalize_context(input_data, model=""):
summary_text = f"[Tool history summary: {n_summarized} older tool outputs omitted. {n_tool_calls} prior function calls were made for file inspection/editing.]" summary_text = f"[Tool history summary: {n_summarized} older tool outputs omitted. {n_tool_calls} prior function calls were made for file inspection/editing.]"
result.append({"type": "message", "role": "user", "content": [{"type": "input_text", "text": summary_text}]}) result.append({"type": "message", "role": "user", "content": [{"type": "input_text", "text": summary_text}]})
for _, call_item in paired_calls: # CRITICAL FIX: Interleave function_calls with their corresponding function_call_outputs
result.append(call_item) # to maintain the required sequence: function_call -> function_call_output -> function_call -> ...
# Build a lookup map: call_id -> function_call_output item
tool_output_map = {}
for _, tool_item in kept_tools: for _, tool_item in kept_tools:
cid = tool_item.get("call_id", tool_item.get("id", ""))
if cid:
tool_output_map[cid] = tool_item
# First, add all paired function_calls followed immediately by their responses
added_call_ids = set()
for _, call_item in paired_calls:
cid = call_item.get("call_id", call_item.get("id", ""))
result.append(call_item)
added_call_ids.add(cid)
# Immediately append the corresponding function_call_output if available
if cid in tool_output_map:
result.append(tool_output_map[cid])
# Add any remaining tool outputs that weren't paired (orphans)
for _, tool_item in kept_tools:
cid = tool_item.get("call_id", tool_item.get("id", ""))
if cid not in added_call_ids:
result.append(tool_item) result.append(tool_item)
for cs_item in compaction_summaries: for cs_item in compaction_summaries:
@@ -5569,17 +5589,21 @@ class Handler(http.server.BaseHTTPRequestHandler):
# Skip duplicate user text messages, but NEVER skip function responses # Skip duplicate user text messages, but NEVER skip function responses
if role == "user" and text_key and text_key == last_user_text and not has_function_response: if role == "user" and text_key and text_key == last_user_text and not has_function_response:
continue continue
# Only merge same-role messages if they don't contain function calls/responses # CRITICAL FIX: Function calls (model role) and function responses (user role) MUST NOT be merged
# Function calls and responses must remain as separate turns # Google's API requires strict alternation: functionCall (model) -> functionResponse (user)
# Never merge across role boundaries when function calls/responses are involved
if role == last_role and role in ("user", "model") and sanitized and not has_function_call and not has_function_response: if role == last_role and role in ("user", "model") and sanitized and not has_function_call and not has_function_response:
# Only merge same-role consecutive text messages without tool content
sanitized[-1].setdefault("parts", []).extend(parts) sanitized[-1].setdefault("parts", []).extend(parts)
else: else:
sanitized.append({"role": role, "parts": parts}) sanitized.append({"role": role, "parts": parts})
if role == "user" and text_key: if role == "user" and text_key:
last_user_text = text_key last_user_text = text_key
last_role = role last_role = role
# Trim leading non-user messages (Google expects conversation to start with user)
while sanitized and sanitized[0].get("role") != "user": while sanitized and sanitized[0].get("role") != "user":
sanitized.pop(0) sanitized.pop(0)
# Trim trailing non-user messages (must end with user turn for continuation)
while sanitized and sanitized[-1].get("role") != "user": while sanitized and sanitized[-1].get("role") != "user":
sanitized.pop() sanitized.pop()
contents = sanitized contents = sanitized