v10.13.8: hard tool-strip on budget cap, compaction output stripping, null-tool loop detection
This commit is contained in:
@@ -1879,7 +1879,7 @@ def _crof_compact_for_retry(input_data, model, aggression=0):
|
||||
return head + tail
|
||||
|
||||
summary_lines = [f"[Auto-compacted: {len(body)} turns removed (adaptive limit={limit})]"]
|
||||
for item in body[-5:]:
|
||||
for item in body[-3:]:
|
||||
summary_lines.append(_item_summary(item, max_len=120))
|
||||
|
||||
summary_msg = {"type": "message", "role": "user", "content": [{"type": "input_text", "text": "\n".join(summary_lines)}]}
|
||||
@@ -1908,9 +1908,13 @@ def _item_summary(item, max_len=200):
|
||||
return f"[tool call] {name}({args[:max_len]})"
|
||||
elif t == "function_call_output":
|
||||
output = item.get("output", "")
|
||||
if len(output) > max_len:
|
||||
return f"[tool result] {output[:max_len]}..."
|
||||
return f"[tool result] {output}"
|
||||
out_len = len(output) if isinstance(output, str) else len(str(output))
|
||||
exit_match = re.search(r'Process exited with code (\d+)', output if isinstance(output, str) else str(output))
|
||||
exit_code = exit_match.group(1) if exit_match else "?"
|
||||
tok_match = re.search(r'Original token count: (\d+)', output if isinstance(output, str) else str(output))
|
||||
tok_count = tok_match.group(1) if tok_match else ""
|
||||
tok_info = f" ({tok_count} tokens)" if tok_count else ""
|
||||
return f"[tool result] exit={exit_code}{tok_info} {out_len} chars"
|
||||
return f"[{t}]"
|
||||
|
||||
def _extract_files(items):
|
||||
@@ -1998,6 +2002,10 @@ def _compact_input(input_data):
|
||||
tool_summaries.append(_item_summary(item, max_len=150))
|
||||
|
||||
files = _extract_files(body)
|
||||
n_read_tools = sum(1 for it in body if it.get("type") == "function_call"
|
||||
and any(k in it.get("arguments", "") for k in ["cat ", "head ", "tail ", "sed -n", "grep ", "less ", "more ", "python3 -c", ".read()"]))
|
||||
n_write_tools = sum(1 for it in body if it.get("type") == "function_call"
|
||||
and any(k in it.get("arguments", "") for k in ["write(", ".write", " > ", " >> ", "sed -i", "patch "]))
|
||||
|
||||
summary_lines = [f"[Auto-compacted: {len(body)} earlier turns summarized to preserve context]"]
|
||||
if user_queries:
|
||||
@@ -2005,11 +2013,13 @@ def _compact_input(input_data):
|
||||
if assistant_msgs:
|
||||
summary_lines.append(f"Assistant responses: {'; '.join(assistant_msgs[-3:])}")
|
||||
if tool_summaries:
|
||||
summary_lines.append(f"Actions taken ({len(tool_summaries)} steps):")
|
||||
for ts in tool_summaries[-15:]:
|
||||
summary_lines.append(f"Actions taken ({len(tool_summaries)} steps, {n_read_tools} reads, {n_write_tools} writes):")
|
||||
for ts in tool_summaries[-5:]:
|
||||
summary_lines.append(f" {ts}")
|
||||
if files:
|
||||
summary_lines.append(f"Files touched: {', '.join(sorted(files)[-10:])}")
|
||||
if n_read_tools > 10 and n_write_tools == 0:
|
||||
summary_lines.append("⚠ You have already read these files extensively but made NO edits. You MUST write your changes NOW. Do NOT read any more files.")
|
||||
|
||||
summary_text = "\n".join(summary_lines)
|
||||
summary_msg = {
|
||||
@@ -2135,7 +2145,7 @@ def _adaptive_compact(input_data, model, policy=None):
|
||||
if not body:
|
||||
return head + tail, True
|
||||
summary_lines = [f"[Auto-compacted: {len(body)} turns removed (budget={input_budget}tok, model={model})]"]
|
||||
for item in body[-5:]:
|
||||
for item in body[-3:]:
|
||||
summary_lines.append(_item_summary(item, max_len=120))
|
||||
summary_msg = {"type": "message", "role": "user",
|
||||
"content": [{"type": "input_text", "text": "\n".join(summary_lines)}]}
|
||||
@@ -5907,6 +5917,18 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
file=sys.stderr)
|
||||
break
|
||||
|
||||
null_tool_names = {"get_goal", "get_remaining_tokens", "get_completion_budget", "status"}
|
||||
consecutive_null = 0
|
||||
for item in reversed(input_data):
|
||||
if isinstance(item, dict):
|
||||
if item.get("type") == "function_call" and item.get("name") in null_tool_names:
|
||||
consecutive_null += 1
|
||||
elif item.get("type") == "function_call":
|
||||
break
|
||||
if consecutive_null >= 3:
|
||||
ag_state["force_finalize"] = True
|
||||
print(f"[{getattr(self, '_session_id', '?')}] [antigravity-loop] NULL-TOOL LOOP: {consecutive_null} consecutive {null_tool_names} calls, forcing finalize", file=sys.stderr)
|
||||
|
||||
last_tool_key = None
|
||||
for item in reversed(input_data):
|
||||
if isinstance(item, dict) and item.get("type") == "function_call":
|
||||
@@ -5940,9 +5962,12 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
request_body["systemInstruction"] = {"role": "user", "parts": system_parts}
|
||||
if gen_config:
|
||||
request_body["generationConfig"] = gen_config
|
||||
if gemini_tools:
|
||||
_budget_exceeded = ag_state.get("total_tool_calls", 0) > _ANTIGRAVITY_MAX_TOOL_CALLS_PER_TASK
|
||||
if gemini_tools and not _budget_exceeded and not ag_state.get("force_finalize"):
|
||||
request_body["tools"] = gemini_tools
|
||||
if _is_claude_model and gemini_tools:
|
||||
elif _budget_exceeded or ag_state.get("force_finalize"):
|
||||
print(f"[{getattr(self, '_session_id', '?')}] [antigravity-budget] TOOLS STRIPPED from request (budget exceeded or force_finalize)", file=sys.stderr)
|
||||
if _is_claude_model and "tools" in request_body:
|
||||
request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}}
|
||||
|
||||
import platform as _plat
|
||||
@@ -6720,22 +6745,34 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
f"{ft['path_counts'][dp]}x, total={ft['total_reads']}", file=sys.stderr)
|
||||
break
|
||||
|
||||
last_tool_key = None
|
||||
for item in reversed(input_data):
|
||||
if isinstance(item, dict) and item.get("type") == "function_call":
|
||||
fname = item.get("name", "")
|
||||
args_str = json.dumps(item.get("arguments", {}), sort_keys=True)[:100]
|
||||
last_tool_key = f"{fname}:{args_str}"
|
||||
break
|
||||
if last_tool_key:
|
||||
if last_tool_key == ag_state["last_tool"]:
|
||||
ag_state["last_tool_count"] += 1
|
||||
if ag_state["last_tool_count"] >= 5:
|
||||
ag_state["repeated_tool"] = True
|
||||
ag_state["force_finalize"] = True
|
||||
else:
|
||||
ag_state["last_tool"] = last_tool_key
|
||||
ag_state["last_tool_count"] = 1
|
||||
null_tool_names = {"get_goal", "get_remaining_tokens", "get_completion_budget", "status"}
|
||||
consecutive_null = 0
|
||||
for item in reversed(input_data):
|
||||
if isinstance(item, dict):
|
||||
if item.get("type") == "function_call" and item.get("name") in null_tool_names:
|
||||
consecutive_null += 1
|
||||
elif item.get("type") == "function_call":
|
||||
break
|
||||
if consecutive_null >= 3:
|
||||
ag_state["force_finalize"] = True
|
||||
print(f"[{getattr(self, '_session_id', '?')}] [antigravity-loop] NULL-TOOL LOOP: {consecutive_null} consecutive {null_tool_names} calls, forcing finalize", file=sys.stderr)
|
||||
|
||||
last_tool_key = None
|
||||
for item in reversed(input_data):
|
||||
if isinstance(item, dict) and item.get("type") == "function_call":
|
||||
fname = item.get("name", "")
|
||||
args_str = json.dumps(item.get("arguments", {}), sort_keys=True)[:100]
|
||||
last_tool_key = f"{fname}:{args_str}"
|
||||
break
|
||||
if last_tool_key:
|
||||
if last_tool_key == ag_state["last_tool"]:
|
||||
ag_state["last_tool_count"] += 1
|
||||
if ag_state["last_tool_count"] >= 5:
|
||||
ag_state["repeated_tool"] = True
|
||||
ag_state["force_finalize"] = True
|
||||
else:
|
||||
ag_state["last_tool"] = last_tool_key
|
||||
ag_state["last_tool_count"] = 1
|
||||
|
||||
_EDIT_WORDS = ("change", "fix", "update", "redesign", "rewrite", "modify", "improve", "replace", "edit", "make it", "add", "remove", "delete", "rename", "move", "convert")
|
||||
latest_lower = ""
|
||||
@@ -6789,10 +6826,13 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
request_body["systemInstruction"] = {"parts": system_parts}
|
||||
if gen_config:
|
||||
request_body["generationConfig"] = gen_config
|
||||
if gemini_tools:
|
||||
_budget_exceeded_oa = ag_state.get("total_tool_calls", 0) > _ANTIGRAVITY_MAX_TOOL_CALLS_PER_TASK
|
||||
if gemini_tools and not _budget_exceeded_oa and not ag_state.get("force_finalize"):
|
||||
request_body["tools"] = gemini_tools
|
||||
elif _budget_exceeded_oa or ag_state.get("force_finalize"):
|
||||
print(f"[antigravity-budget] TOOLS STRIPPED from OA request (budget exceeded or force_finalize)", file=sys.stderr)
|
||||
|
||||
if OAUTH_PROVIDER == "google-antigravity" and _is_claude_model and gemini_tools:
|
||||
if OAUTH_PROVIDER == "google-antigravity" and _is_claude_model and "tools" in request_body:
|
||||
request_body["toolConfig"] = {"functionCallingConfig": {"mode": "VALIDATED"}}
|
||||
if _is_claude_thinking:
|
||||
print(f"[antigravity-claude] applied VALIDATED toolConfig for thinking model", file=sys.stderr)
|
||||
|
||||
Reference in New Issue
Block a user