diff --git a/app/azure/request_adapter.py b/app/azure/request_adapter.py index 697db1e..ac2f849 100644 --- a/app/azure/request_adapter.py +++ b/app/azure/request_adapter.py @@ -92,6 +92,13 @@ class RequestAdapter: def _transform_tools_for_responses(self, tools: Any) -> Any: out: List[Dict[str, Any]] = [] + if not isinstance(tools, list): + current_app.logger.debug( + "Skipping tool transformation because tools payload is not a list: %r", + tools, + ) + return out + for tool in tools: function = tool.get("function") transformed: Dict[str, Any] = { diff --git a/app/common/logging.py b/app/common/logging.py index dbb6aaf..021b203 100644 --- a/app/common/logging.py +++ b/app/common/logging.py @@ -205,7 +205,7 @@ def log_request(req: Request) -> str: ) messages = json_payload.get("messages", []) - tools = json_payload.get("tools", []) + tools = json_payload.get("tools", []) or [] # Render tools section once (no duplicate panels) table = Table( diff --git a/tests/recordings/empty_tools/downstream_request.json b/tests/recordings/empty_tools/downstream_request.json new file mode 100644 index 0000000..b5a92c1 --- /dev/null +++ b/tests/recordings/empty_tools/downstream_request.json @@ -0,0 +1,22 @@ +{ + "model": "gpt-minimal", + "temperature": 0, + "user": "REDACTED", + "messages": [ + { + "role": "system", + "content": "REDACTED" + }, + { + "role": "user", + "content": "REDACTED" + }, + { + "role": "user", + "content": "REDACTED" + } + ], + "tools": null, + "tool_choice": "auto", + "stream": true +} \ No newline at end of file diff --git a/tests/recordings/empty_tools/downstream_response.sse b/tests/recordings/empty_tools/downstream_response.sse new file mode 100644 index 0000000..e459820 --- /dev/null +++ b/tests/recordings/empty_tools/downstream_response.sse @@ -0,0 +1,10 @@ +data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093803,"model":"gpt-minimal","choices":[{"index":0,"delta":{"role":"assistant","content":"\n\n"},"finish_reason":null}]} + +data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093804,"model":"gpt-minimal","choices":[{"index":0,"delta":{"role":"assistant","content":"\n\n"},"finish_reason":null}]} + +data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093804,"model":"gpt-minimal","choices":[{"index":0,"delta":{"role":"assistant","content":"pong"},"finish_reason":null}]} + +data: {"id":"chatcmpl-ERO7qkefORSgjuOmbThzZoG8","object":"chat.completion.chunk","created":1758093806,"model":"gpt-minimal","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} + +data: [DONE] + diff --git a/tests/recordings/empty_tools/upstream_request.json b/tests/recordings/empty_tools/upstream_request.json new file mode 100644 index 0000000..2dea819 --- /dev/null +++ b/tests/recordings/empty_tools/upstream_request.json @@ -0,0 +1,37 @@ +{ + "instructions": "REDACTED", + "input": [ + { + "role": "user", + "content": [ + { + "type": "input_text", + "text": "REDACTED" + } + ] + }, + { + "role": "user", + "content": [ + { + "type": "input_text", + "text": "REDACTED" + } + ] + } + ], + "model": "gpt-5", + "tools": [], + "tool_choice": "auto", + "prompt_cache_key": "REDACTED", + "stream": true, + "reasoning": { + "effort": "minimal", + "summary": "detailed" + }, + "store": false, + "stream_options": { + "include_obfuscation": false + }, + "truncation": "auto" +} \ No newline at end of file diff --git a/tests/recordings/empty_tools/upstream_response.sse b/tests/recordings/empty_tools/upstream_response.sse new file mode 100644 index 0000000..03731da --- /dev/null +++ b/tests/recordings/empty_tools/upstream_response.sse @@ -0,0 +1,33 @@ +event: response.created +data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_68ca61ea133c8190a25851b67c2dc1fa000638fabbdbe241","object":"response","created_at":1758093802,"status":"in_progress","background":false,"content_filters":null,"error":null,"incomplete_details":null,"instructions":"REDACTED","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":"REDACTED","reasoning":{"effort":"minimal","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"explanation":{"type":"string","description":"REDACTED"},"query":{"type":"string","description":"REDACTED"},"target_directories":{"type":"array","items":{"type":"string"},"description":"REDACTED"},"search_only_prs":{"type":"boolean","description":"REDACTED"}},"required":["explanation","query","target_directories"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"command":{"type":"string","description":"REDACTED"},"is_background":{"type":"boolean","description":"REDACTED"},"explanation":{"type":"string","description":"REDACTED"}},"required":["command","is_background"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"pattern":{"type":"string","description":"REDACTED"},"path":{"type":"string","description":"REDACTED"},"glob":{"type":"string","description":"REDACTED"},"output_mode":{"type":"string","description":"REDACTED","enum":["content","files_with_matches","count"]},"-B":{"type":"number","description":"REDACTED"},"-A":{"type":"number","description":"REDACTED"},"-C":{"type":"number","description":"REDACTED"},"-i":{"type":"boolean","description":"REDACTED"},"type":{"type":"string","description":"REDACTED"},"head_limit":{"type":"number","description":"REDACTED"},"multiline":{"type":"boolean","description":"REDACTED"}},"required":["pattern"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"explanation":{"type":"string","description":"REDACTED"}},"required":["target_file"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"paths":{"description":"REDACTED","type":"array","items":{"type":"string"}}},"required":[]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_notebook":{"type":"string","description":"REDACTED"},"cell_idx":{"type":"number","description":"REDACTED"},"is_new_cell":{"type":"boolean","description":"REDACTED"},"cell_language":{"type":"string","description":"REDACTED"},"old_string":{"type":"string","description":"REDACTED"},"new_string":{"type":"string","description":"REDACTED"}},"required":["target_notebook","cell_idx","is_new_cell","cell_language","old_string","new_string"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"merge":{"type":"boolean","description":"REDACTED"},"todos":{"type":"array","description":"REDACTED","items":{"type":"object","properties":{"content":{"type":"string","description":"REDACTED"},"status":{"type":"string","enum":["pending","in_progress","completed","cancelled"],"description":"REDACTED"},"id":{"type":"string","description":"REDACTED"}},"required":["content","status","id"]},"minItems":2}},"required":["merge","todos"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"instructions":{"type":"string","description":"REDACTED"},"code_edit":{"type":"string","description":"REDACTED"}},"required":["target_file","instructions","code_edit"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"offset":{"type":"integer","description":"REDACTED"},"limit":{"type":"integer","description":"REDACTED"}},"required":["target_file"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_directory":{"type":"string","description":"REDACTED"},"ignore_globs":{"type":"array","items":{"type":"string"},"description":"REDACTED"}},"required":["target_directory"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_directory":{"type":"string","description":"REDACTED"},"glob_pattern":{"type":"string","description":"REDACTED"}},"required":["glob_pattern"]},"strict":false}],"top_p":1.0,"truncation":"auto","usage":null,"user":null,"metadata":{}}} + +event: response.in_progress +data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_68ca61ea133c8190a25851b67c2dc1fa000638fabbdbe241","object":"response","created_at":1758093802,"status":"in_progress","background":false,"content_filters":null,"error":null,"incomplete_details":null,"instructions":"REDACTED","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5","output":[],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":"REDACTED","reasoning":{"effort":"minimal","summary":"detailed"},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"explanation":{"type":"string","description":"REDACTED"},"query":{"type":"string","description":"REDACTED"},"target_directories":{"type":"array","items":{"type":"string"},"description":"REDACTED"},"search_only_prs":{"type":"boolean","description":"REDACTED"}},"required":["explanation","query","target_directories"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"command":{"type":"string","description":"REDACTED"},"is_background":{"type":"boolean","description":"REDACTED"},"explanation":{"type":"string","description":"REDACTED"}},"required":["command","is_background"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"pattern":{"type":"string","description":"REDACTED"},"path":{"type":"string","description":"REDACTED"},"glob":{"type":"string","description":"REDACTED"},"output_mode":{"type":"string","description":"REDACTED","enum":["content","files_with_matches","count"]},"-B":{"type":"number","description":"REDACTED"},"-A":{"type":"number","description":"REDACTED"},"-C":{"type":"number","description":"REDACTED"},"-i":{"type":"boolean","description":"REDACTED"},"type":{"type":"string","description":"REDACTED"},"head_limit":{"type":"number","description":"REDACTED"},"multiline":{"type":"boolean","description":"REDACTED"}},"required":["pattern"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"explanation":{"type":"string","description":"REDACTED"}},"required":["target_file"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"paths":{"description":"REDACTED","type":"array","items":{"type":"string"}}},"required":[]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_notebook":{"type":"string","description":"REDACTED"},"cell_idx":{"type":"number","description":"REDACTED"},"is_new_cell":{"type":"boolean","description":"REDACTED"},"cell_language":{"type":"string","description":"REDACTED"},"old_string":{"type":"string","description":"REDACTED"},"new_string":{"type":"string","description":"REDACTED"}},"required":["target_notebook","cell_idx","is_new_cell","cell_language","old_string","new_string"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"merge":{"type":"boolean","description":"REDACTED"},"todos":{"type":"array","description":"REDACTED","items":{"type":"object","properties":{"content":{"type":"string","description":"REDACTED"},"status":{"type":"string","enum":["pending","in_progress","completed","cancelled"],"description":"REDACTED"},"id":{"type":"string","description":"REDACTED"}},"required":["content","status","id"]},"minItems":2}},"required":["merge","todos"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"instructions":{"type":"string","description":"REDACTED"},"code_edit":{"type":"string","description":"REDACTED"}},"required":["target_file","instructions","code_edit"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"offset":{"type":"integer","description":"REDACTED"},"limit":{"type":"integer","description":"REDACTED"}},"required":["target_file"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_directory":{"type":"string","description":"REDACTED"},"ignore_globs":{"type":"array","items":{"type":"string"},"description":"REDACTED"}},"required":["target_directory"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_directory":{"type":"string","description":"REDACTED"},"glob_pattern":{"type":"string","description":"REDACTED"}},"required":["glob_pattern"]},"strict":false}],"top_p":1.0,"truncation":"auto","usage":null,"user":null,"metadata":{}}} + +event: response.output_item.added +data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_68ca61eb7cf481909660df840351bf9d000638fabbdbe241","type":"reasoning","summary":[]}} + +event: response.output_item.done +data: {"type":"response.output_item.done","sequence_number":3,"output_index":0,"item":{"id":"rs_68ca61eb7cf481909660df840351bf9d000638fabbdbe241","type":"reasoning","summary":[]}} + +event: response.output_item.added +data: {"type":"response.output_item.added","sequence_number":4,"output_index":1,"item":{"id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","type":"message","status":"in_progress","content":[],"role":"assistant"}} + +event: response.content_part.added +data: {"type":"response.content_part.added","sequence_number":5,"item_id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","output_index":1,"content_index":0,"part":{"type":"output_text","annotations":[],"text":""}} + +event: response.output_text.delta +data: {"type":"response.output_text.delta","sequence_number":6,"item_id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","output_index":1,"content_index":0,"delta":"pong"} + +event: response.output_text.done +data: {"type":"response.output_text.done","sequence_number":7,"item_id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","output_index":1,"content_index":0,"text":"REDACTED"} + +event: response.content_part.done +data: {"type":"response.content_part.done","sequence_number":8,"item_id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","output_index":1,"content_index":0,"part":{"type":"output_text","annotations":[],"text":"REDACTED"}} + +event: response.output_item.done +data: {"type":"response.output_item.done","sequence_number":9,"output_index":1,"item":{"id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"REDACTED"}],"role":"assistant"}} + +event: response.completed +data: {"type":"response.completed","sequence_number":10,"response":{"id":"resp_68ca61ea133c8190a25851b67c2dc1fa000638fabbdbe241","object":"response","created_at":1758093802,"status":"completed","background":false,"content_filters":null,"error":null,"incomplete_details":null,"instructions":"REDACTED","max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5","output":[{"id":"rs_68ca61eb7cf481909660df840351bf9d000638fabbdbe241","type":"reasoning","summary":[]},{"id":"msg_68ca61ecbbe881909418900fa3ecd3e7000638fabbdbe241","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"text":"REDACTED"}],"role":"assistant"}],"parallel_tool_calls":true,"previous_response_id":null,"prompt_cache_key":"REDACTED","reasoning":{"effort":"minimal","summary":"detailed"},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"explanation":{"type":"string","description":"REDACTED"},"query":{"type":"string","description":"REDACTED"},"target_directories":{"type":"array","items":{"type":"string"},"description":"REDACTED"},"search_only_prs":{"type":"boolean","description":"REDACTED"}},"required":["explanation","query","target_directories"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"command":{"type":"string","description":"REDACTED"},"is_background":{"type":"boolean","description":"REDACTED"},"explanation":{"type":"string","description":"REDACTED"}},"required":["command","is_background"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"pattern":{"type":"string","description":"REDACTED"},"path":{"type":"string","description":"REDACTED"},"glob":{"type":"string","description":"REDACTED"},"output_mode":{"type":"string","description":"REDACTED","enum":["content","files_with_matches","count"]},"-B":{"type":"number","description":"REDACTED"},"-A":{"type":"number","description":"REDACTED"},"-C":{"type":"number","description":"REDACTED"},"-i":{"type":"boolean","description":"REDACTED"},"type":{"type":"string","description":"REDACTED"},"head_limit":{"type":"number","description":"REDACTED"},"multiline":{"type":"boolean","description":"REDACTED"}},"required":["pattern"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"explanation":{"type":"string","description":"REDACTED"}},"required":["target_file"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"paths":{"description":"REDACTED","type":"array","items":{"type":"string"}}},"required":[]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_notebook":{"type":"string","description":"REDACTED"},"cell_idx":{"type":"number","description":"REDACTED"},"is_new_cell":{"type":"boolean","description":"REDACTED"},"cell_language":{"type":"string","description":"REDACTED"},"old_string":{"type":"string","description":"REDACTED"},"new_string":{"type":"string","description":"REDACTED"}},"required":["target_notebook","cell_idx","is_new_cell","cell_language","old_string","new_string"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"merge":{"type":"boolean","description":"REDACTED"},"todos":{"type":"array","description":"REDACTED","items":{"type":"object","properties":{"content":{"type":"string","description":"REDACTED"},"status":{"type":"string","enum":["pending","in_progress","completed","cancelled"],"description":"REDACTED"},"id":{"type":"string","description":"REDACTED"}},"required":["content","status","id"]},"minItems":2}},"required":["merge","todos"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"instructions":{"type":"string","description":"REDACTED"},"code_edit":{"type":"string","description":"REDACTED"}},"required":["target_file","instructions","code_edit"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_file":{"type":"string","description":"REDACTED"},"offset":{"type":"integer","description":"REDACTED"},"limit":{"type":"integer","description":"REDACTED"}},"required":["target_file"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_directory":{"type":"string","description":"REDACTED"},"ignore_globs":{"type":"array","items":{"type":"string"},"description":"REDACTED"}},"required":["target_directory"]},"strict":false},{"type":"function","description":"REDACTED","name":"REDACTED","parameters":{"type":"object","properties":{"target_directory":{"type":"string","description":"REDACTED"},"glob_pattern":{"type":"string","description":"REDACTED"}},"required":["glob_pattern"]},"strict":false}],"top_p":1.0,"truncation":"auto","usage":{"input_tokens":8510,"input_tokens_details":{"cached_tokens":0},"output_tokens":7,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":8517},"user":null,"metadata":{}}} + diff --git a/tests/test_replays.py b/tests/test_replays.py index a26ee7f..92aa5fd 100644 --- a/tests/test_replays.py +++ b/tests/test_replays.py @@ -50,3 +50,9 @@ class TestReplyVerbosityLevelHigh(ReplyBase): app.config["AZURE_VERBOSITY_LEVEL"] = "high" recording = "verbosity_level" + + +class TestReplyWithEmptyTools(ReplyBase): + """Reply with the 'tools' key not present in the downstream request.""" + + recording = "empty_tools"