From 00d14131a9fedf31768dda3bb803f62fdb743545 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 21 Feb 2026 22:40:50 -0500 Subject: [PATCH] fixes --- ai_client.py | 81 +++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/ai_client.py b/ai_client.py index 16233b2..d3f1828 100644 --- a/ai_client.py +++ b/ai_client.py @@ -27,7 +27,8 @@ comms_log_callback = None # Signature: (script: str, result: str) -> None tool_log_callback = None -MAX_TOOL_ROUNDS = 5 +# Increased to allow thorough code exploration before forcing a summary +MAX_TOOL_ROUNDS = 10 # Maximum characters per text chunk sent to Anthropic. # Kept well under the ~200k token API limit. @@ -438,7 +439,8 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items: all_text_parts = [] - for round_idx in range(MAX_TOOL_ROUNDS + 1): + # We allow MAX_TOOL_ROUNDS, plus 1 final loop to get the text synthesis + for round_idx in range(MAX_TOOL_ROUNDS + 2): response = _gemini_chat.send_message(payload_to_send) text_parts_raw = [ @@ -483,53 +485,48 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items: if not tool_calls: break - if round_idx >= MAX_TOOL_ROUNDS: + if round_idx > MAX_TOOL_ROUNDS: + # The model ignored the MAX ROUNDS warning and kept calling tools. + # Force abort to prevent infinite loop. break function_responses = [] sent_results_log = [] - for fc in tool_calls: + + for i, fc in enumerate(tool_calls): fc_name = fc.name fc_args = dict(fc.args) + if fc_name in mcp_client.TOOL_NAMES: _append_comms("OUT", "tool_call", {"name": fc_name, "args": fc_args}) output = mcp_client.dispatch(fc_name, fc_args) _append_comms("IN", "tool_result", {"name": fc_name, "output": output}) - function_responses.append( - types.Part.from_function_response( - name=fc_name, - response={"output": output} - ) - ) - sent_results_log.append({"tool_use_id": fc_name, "content": output}) elif fc_name == TOOL_NAME: script = fc_args.get("script", "") - _append_comms("OUT", "tool_call", { - "name": TOOL_NAME, - "script": script, - }) + _append_comms("OUT", "tool_call", {"name": TOOL_NAME, "script": script}) output = _run_script(script, base_dir) - _append_comms("IN", "tool_result", { - "name": TOOL_NAME, - "output": output, - }) - function_responses.append( - types.Part.from_function_response( - name=TOOL_NAME, - response={"output": output} - ) - ) - sent_results_log.append({"tool_use_id": TOOL_NAME, "content": output}) + _append_comms("IN", "tool_result", {"name": TOOL_NAME, "output": output}) + else: + output = f"ERROR: unknown tool '{fc_name}'" - # Refresh file context after tool calls locally, but DO NOT inject as text part into Gemini. - # Gemini strictly expects only function_responses in this array. - if file_items: - file_items = _reread_file_items(file_items) + # Inject dynamic updates directly into the LAST tool's output string. + # Gemini strictly expects function_responses only, so we piggyback on the string. + if i == len(tool_calls) - 1: + if file_items: + file_items = _reread_file_items(file_items) + refreshed_ctx = _build_file_context_text(file_items) + if refreshed_ctx: + output += f"\n\n[SYSTEM: FILES UPDATED — current contents below. Do NOT re-read these files.]\n\n{refreshed_ctx}" + + if round_idx == MAX_TOOL_ROUNDS: + output += "\n\n[SYSTEM WARNING: MAX TOOL ROUNDS REACHED. YOU MUST PROVIDE YOUR FINAL ANSWER NOW WITHOUT CALLING ANY MORE TOOLS.]" - _append_comms("OUT", "tool_result_send", { - "results": sent_results_log - }) + function_responses.append( + types.Part.from_function_response(name=fc_name, response={"output": output}) + ) + sent_results_log.append({"tool_use_id": fc_name, "content": output}) + _append_comms("OUT", "tool_result_send", {"results": sent_results_log}) payload_to_send = function_responses final_text = "\n\n".join(all_text_parts) @@ -641,7 +638,8 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item all_text_parts = [] - for round_idx in range(MAX_TOOL_ROUNDS + 1): + # We allow MAX_TOOL_ROUNDS, plus 1 final loop to get the text synthesis + for round_idx in range(MAX_TOOL_ROUNDS + 2): response = _anthropic_client.messages.create( model=_model, max_tokens=8096, @@ -687,10 +685,12 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item "usage": usage_dict, }) - if response.stop_reason != "tool_use": + if response.stop_reason != "tool_use" or not tool_use_blocks: break - if round_idx >= MAX_TOOL_ROUNDS: + if round_idx > MAX_TOOL_ROUNDS: + # The model ignored the MAX ROUNDS warning and kept calling tools. + # Force abort to prevent infinite loop. break tool_results = [] @@ -728,9 +728,6 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item "content": output, }) - if not tool_results: - break - # Refresh file context after tool calls and inject into tool result message if file_items: file_items = _reread_file_items(file_items) @@ -745,6 +742,12 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item ), }) + if round_idx == MAX_TOOL_ROUNDS: + tool_results.append({ + "type": "text", + "text": "SYSTEM WARNING: MAX TOOL ROUNDS REACHED. YOU MUST PROVIDE YOUR FINAL ANSWER NOW WITHOUT CALLING ANY MORE TOOLS." + }) + _anthropic_history.append({ "role": "user", "content": tool_results,