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
|
# Signature: (script: str, result: str) -> None
|
||||||
tool_log_callback = 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.
|
# Maximum characters per text chunk sent to Anthropic.
|
||||||
# Kept well under the ~200k token API limit.
|
# 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 = []
|
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)
|
response = _gemini_chat.send_message(payload_to_send)
|
||||||
|
|
||||||
text_parts_raw = [
|
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:
|
if not tool_calls:
|
||||||
break
|
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
|
break
|
||||||
|
|
||||||
function_responses = []
|
function_responses = []
|
||||||
sent_results_log = []
|
sent_results_log = []
|
||||||
for fc in tool_calls:
|
|
||||||
|
for i, fc in enumerate(tool_calls):
|
||||||
fc_name = fc.name
|
fc_name = fc.name
|
||||||
fc_args = dict(fc.args)
|
fc_args = dict(fc.args)
|
||||||
|
|
||||||
if fc_name in mcp_client.TOOL_NAMES:
|
if fc_name in mcp_client.TOOL_NAMES:
|
||||||
_append_comms("OUT", "tool_call", {"name": fc_name, "args": fc_args})
|
_append_comms("OUT", "tool_call", {"name": fc_name, "args": fc_args})
|
||||||
output = mcp_client.dispatch(fc_name, fc_args)
|
output = mcp_client.dispatch(fc_name, fc_args)
|
||||||
_append_comms("IN", "tool_result", {"name": fc_name, "output": output})
|
_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:
|
elif fc_name == TOOL_NAME:
|
||||||
script = fc_args.get("script", "")
|
script = fc_args.get("script", "")
|
||||||
_append_comms("OUT", "tool_call", {
|
_append_comms("OUT", "tool_call", {"name": TOOL_NAME, "script": script})
|
||||||
"name": TOOL_NAME,
|
|
||||||
"script": script,
|
|
||||||
})
|
|
||||||
output = _run_script(script, base_dir)
|
output = _run_script(script, base_dir)
|
||||||
_append_comms("IN", "tool_result", {
|
_append_comms("IN", "tool_result", {"name": TOOL_NAME, "output": output})
|
||||||
"name": TOOL_NAME,
|
else:
|
||||||
"output": output,
|
output = f"ERROR: unknown tool '{fc_name}'"
|
||||||
})
|
|
||||||
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})
|
|
||||||
|
|
||||||
# Refresh file context after tool calls locally, but DO NOT inject as text part into Gemini.
|
# Inject dynamic updates directly into the LAST tool's output string.
|
||||||
# Gemini strictly expects only function_responses in this array.
|
# Gemini strictly expects function_responses only, so we piggyback on the string.
|
||||||
if file_items:
|
if i == len(tool_calls) - 1:
|
||||||
file_items = _reread_file_items(file_items)
|
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", {
|
function_responses.append(
|
||||||
"results": sent_results_log
|
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
|
payload_to_send = function_responses
|
||||||
|
|
||||||
final_text = "\n\n".join(all_text_parts)
|
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 = []
|
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(
|
response = _anthropic_client.messages.create(
|
||||||
model=_model,
|
model=_model,
|
||||||
max_tokens=8096,
|
max_tokens=8096,
|
||||||
@@ -687,10 +685,12 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
|||||||
"usage": usage_dict,
|
"usage": usage_dict,
|
||||||
})
|
})
|
||||||
|
|
||||||
if response.stop_reason != "tool_use":
|
if response.stop_reason != "tool_use" or not tool_use_blocks:
|
||||||
break
|
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
|
break
|
||||||
|
|
||||||
tool_results = []
|
tool_results = []
|
||||||
@@ -728,9 +728,6 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
|||||||
"content": output,
|
"content": output,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not tool_results:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Refresh file context after tool calls and inject into tool result message
|
# Refresh file context after tool calls and inject into tool result message
|
||||||
if file_items:
|
if file_items:
|
||||||
file_items = _reread_file_items(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({
|
_anthropic_history.append({
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": tool_results,
|
"content": tool_results,
|
||||||
|
|||||||
Reference in New Issue
Block a user