fixes
This commit is contained in:
81
ai_client.py
81
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,
|
||||
|
||||
Reference in New Issue
Block a user