Cyber Catalyst Team commited on
Commit
b708c13
·
1 Parent(s): 24e5442

fix: correctly indent agent tool execution block inside the round loop to prevent duplicate calls and rate limits

Browse files
Files changed (1) hide show
  1. backend.py +82 -82
backend.py CHANGED
@@ -836,92 +836,92 @@ async def chat_completions(request: Request, authorization: str = Header(None)):
836
  if choice.finish_reason == "tool_calls":
837
  break
838
 
839
- # If no tool calls, we're done
840
- if not tool_calls_raw:
841
- await save_message(session_id, "assistant", full_content)
842
- yield make_chunk(request_id, requested_model, finish_reason="stop")
843
- yield "data: [DONE]\n\n"
844
- return
845
-
846
- # Execute tool calls
847
- tool_calls_list = []
848
- for idx in sorted(tool_calls_raw.keys()):
849
- tc = tool_calls_raw[idx]
850
- tool_calls_list.append({
851
- "id": tc["id"],
852
- "type": "function",
853
- "function": {"name": tc["name"], "arguments": tc["arguments"]}
854
- })
855
-
856
- # Add assistant message with tool calls to history
857
- assistant_msg = {"role": "assistant", "content": full_content or None, "tool_calls": tool_calls_list}
858
- final_messages.append(assistant_msg)
859
-
860
- # Execute each tool and add results
861
- for tc in tool_calls_list:
862
- func_name = tc["function"]["name"]
863
- raw_args_str = tc["function"]["arguments"]
864
- try:
865
- func_args = json.loads(raw_args_str)
866
- except json.JSONDecodeError:
867
- # Attempt raw JSON repair
868
- repaired_str = raw_args_str.strip()
869
- if not repaired_str.startswith("{"):
870
- repaired_str = "{" + repaired_str
871
- if not repaired_str.endswith("}"):
872
- repaired_str = repaired_str + "}"
873
  try:
874
- func_args = json.loads(repaired_str)
875
- log_activity(f"Auto-fixed invalid JSON string for tool: {func_name}")
876
- except:
877
- func_args = {}
 
 
 
 
 
 
 
 
 
878
 
879
- # Perform semantic repairs
880
- repaired_args, repair_notes = repair_arguments(func_name, func_args)
881
 
882
- # Log activity
883
- log_activity(f"Tool execution: {func_name} args={repaired_args}")
884
- if repair_notes:
885
- for note in repair_notes:
886
- log_activity(f"[Tool Repair] {note}")
887
-
888
- # Show tool execution to user
889
- yield make_chunk(request_id, requested_model, f"\n\n🔧 **{func_name}**")
890
- if repair_notes:
891
- yield make_chunk(request_id, requested_model, " *(Auto-Repaired)*")
892
 
893
- if func_name == "run_bash" and "command" in repaired_args:
894
- yield make_chunk(request_id, requested_model, f": `{repaired_args['command']}`\n")
895
- elif func_name == "read_file" and "path" in repaired_args:
896
- yield make_chunk(request_id, requested_model, f": `{repaired_args['path']}`\n")
897
- elif func_name == "write_file" and "path" in repaired_args:
898
- yield make_chunk(request_id, requested_model, f": `{repaired_args['path']}`\n")
899
- elif func_name == "list_directory":
900
- yield make_chunk(request_id, requested_model, f": `{repaired_args.get('path', '.')}`\n")
901
- elif func_name == "grep_search":
902
- yield make_chunk(request_id, requested_model, f": `{repaired_args.get('pattern', '')}`\n")
903
- else:
904
- yield make_chunk(request_id, requested_model, "\n")
905
-
906
- # Execute the tool
907
- result = await execute_tool(func_name, repaired_args)
908
-
909
- # Append teaching note if repaired
910
- if repair_notes:
911
- result += f"\n\n[SYSTEM REPAIR NOTE: The harness automatically fixed formatting issues: {', '.join(repair_notes)}. Please strictly follow the tool's JSON schema in subsequent calls without these wrapping/formatting errors.]"
912
-
913
- # Show truncated result to user
914
- preview = result[:500] + ("..." if len(result) > 500 else "")
915
- yield make_chunk(request_id, requested_model, f"```\n{preview}\n```\n")
916
-
917
- # Add tool result to message history
918
- final_messages.append({
919
- "role": "tool",
920
- "tool_call_id": tc["id"],
921
- "content": result,
922
- })
923
-
924
- await save_message(session_id, "tool", result, tool_call_id=tc["id"])
925
 
926
  # Continue the agentic loop (model processes tool results)
927
 
 
836
  if choice.finish_reason == "tool_calls":
837
  break
838
 
839
+ # If no tool calls, we're done
840
+ if not tool_calls_raw:
841
+ await save_message(session_id, "assistant", full_content)
842
+ yield make_chunk(request_id, requested_model, finish_reason="stop")
843
+ yield "data: [DONE]\n\n"
844
+ return
845
+
846
+ # Execute tool calls
847
+ tool_calls_list = []
848
+ for idx in sorted(tool_calls_raw.keys()):
849
+ tc = tool_calls_raw[idx]
850
+ tool_calls_list.append({
851
+ "id": tc["id"],
852
+ "type": "function",
853
+ "function": {"name": tc["name"], "arguments": tc["arguments"]}
854
+ })
855
+
856
+ # Add assistant message with tool calls to history
857
+ assistant_msg = {"role": "assistant", "content": full_content or None, "tool_calls": tool_calls_list}
858
+ final_messages.append(assistant_msg)
859
+
860
+ # Execute each tool and add results
861
+ for tc in tool_calls_list:
862
+ func_name = tc["function"]["name"]
863
+ raw_args_str = tc["function"]["arguments"]
 
 
 
 
 
 
 
 
 
864
  try:
865
+ func_args = json.loads(raw_args_str)
866
+ except json.JSONDecodeError:
867
+ # Attempt raw JSON repair
868
+ repaired_str = raw_args_str.strip()
869
+ if not repaired_str.startswith("{"):
870
+ repaired_str = "{" + repaired_str
871
+ if not repaired_str.endswith("}"):
872
+ repaired_str = repaired_str + "}"
873
+ try:
874
+ func_args = json.loads(repaired_str)
875
+ log_activity(f"Auto-fixed invalid JSON string for tool: {func_name}")
876
+ except:
877
+ func_args = {}
878
 
879
+ # Perform semantic repairs
880
+ repaired_args, repair_notes = repair_arguments(func_name, func_args)
881
 
882
+ # Log activity
883
+ log_activity(f"Tool execution: {func_name} args={repaired_args}")
884
+ if repair_notes:
885
+ for note in repair_notes:
886
+ log_activity(f"[Tool Repair] {note}")
887
+
888
+ # Show tool execution to user
889
+ yield make_chunk(request_id, requested_model, f"\n\n🔧 **{func_name}**")
890
+ if repair_notes:
891
+ yield make_chunk(request_id, requested_model, " *(Auto-Repaired)*")
892
 
893
+ if func_name == "run_bash" and "command" in repaired_args:
894
+ yield make_chunk(request_id, requested_model, f": `{repaired_args['command']}`\n")
895
+ elif func_name == "read_file" and "path" in repaired_args:
896
+ yield make_chunk(request_id, requested_model, f": `{repaired_args['path']}`\n")
897
+ elif func_name == "write_file" and "path" in repaired_args:
898
+ yield make_chunk(request_id, requested_model, f": `{repaired_args['path']}`\n")
899
+ elif func_name == "list_directory":
900
+ yield make_chunk(request_id, requested_model, f": `{repaired_args.get('path', '.')}`\n")
901
+ elif func_name == "grep_search":
902
+ yield make_chunk(request_id, requested_model, f": `{repaired_args.get('pattern', '')}`\n")
903
+ else:
904
+ yield make_chunk(request_id, requested_model, "\n")
905
+
906
+ # Execute the tool
907
+ result = await execute_tool(func_name, repaired_args)
908
+
909
+ # Append teaching note if repaired
910
+ if repair_notes:
911
+ result += f"\n\n[SYSTEM REPAIR NOTE: The harness automatically fixed formatting issues: {', '.join(repair_notes)}. Please strictly follow the tool's JSON schema in subsequent calls without these wrapping/formatting errors.]"
912
+
913
+ # Show truncated result to user
914
+ preview = result[:500] + ("..." if len(result) > 500 else "")
915
+ yield make_chunk(request_id, requested_model, f"```\n{preview}\n```\n")
916
+
917
+ # Add tool result to message history
918
+ final_messages.append({
919
+ "role": "tool",
920
+ "tool_call_id": tc["id"],
921
+ "content": result,
922
+ })
923
+
924
+ await save_message(session_id, "tool", result, tool_call_id=tc["id"])
925
 
926
  # Continue the agentic loop (model processes tool results)
927