GPT-5 (API) outputs garbled arguments when streaming tool calls

I have a python script to reproduce:

import os
import json
import requests


def main():
    api_base = "https://api.openai.com"
    endpoint = "/v1/chat/completions"
    url = api_base.rstrip("/") + endpoint

    api_key = "<REDACTED>"

    outfile = os.getenv("SSE_OUTPUT_FILE", "sse_output.txt")

    headers = {
        "Accept-Encoding": "gzip, deflate",
        "Connection": "keep-alive",
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",

    }

    payload = {
        "messages": [
            {
                "role": "user",
                "content": "Help me find an anime. 2010s, sea-related. OP: a red umbrella fly to the sky and finally caught by someone. Search by Japanese, Chinese"
            }
        ],
        "model": "gpt-5",
        "stream": True,
        "tools": [
            {
                "type": "function",
                "function": {
                    "name": "web_search",
                    "strict": True,
                    "description": "This tool allows you to search the web by the given search queries.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "q": {
                                            "type": "string",
                                            "description": "the search query to run"
                                        }
                                    },
                                    "required": ["q"],
                                    "additionalProperties": False
                                },
                                "minItems": 1,
                                "maxItems": 6
                            }
                        },
                        "required": ["query"],
                        "additionalProperties": False
                    }
                }
            },
        ]
    }

    timeout = (30, 610)

    with requests.post(
        url,
        headers=headers,
        data=json.dumps(payload, ensure_ascii=False),
        stream=True,
        timeout=timeout,
    ) as resp:
        resp.encoding = 'utf-8'
        resp.raise_for_status()

        output = ""

        for line in resp.iter_lines(decode_unicode=True):
            if line is None or line.strip() == "":
                continue
            if "[DONE]" in line:
                continue

            raw_str = line.strip("data: ")
            data = json.loads(raw_str)
            print(data)

            output += line + "\n\n"

        bad_patterns = ["00", "\\u", "\\x"]

        if any(pattern in output for pattern in bad_patterns):
            return "BAD"
        
        return "OK"

if __name__ == "__main__":
    for i in range(5):
        result = main()
        print(f"Attempt {i+1}: {result}")

The q field in the tool call output often looks like this: (after aggregated)

[..., {"q": "\x7f\x006b01\x006f\x006e\x006f\x0061\x0073\x0075\x006b\x0061\x0072\x0061 OP \x006f\x0070\x0065\x006e\x0069\x006e\x0067 \x0072\x0065\x0064 \x0075\x006d\x0062\x0072\x0065\x006c\x006c\x0061"}, ...]

or

[..., {"q": "\x0041\x006e\x0069\x006d\x0065 OP red umbrella sea 2-cour"}, ...]

Raw SSE outputs look like:

{..., 'choices': [{'index': 0, 'delta': {'tool_calls': [{'index': 2, 'function': {'arguments': '07\\u0'}}]}, 'finish_reason': None}], ...}
{..., 'choices': [{'index': 0, 'delta': {'tool_calls': [{'index': 2, 'function': {'arguments': '007\\u0'}}]}, 'finish_reason': None}], ...}
{..., 'choices': [{'index': 0, 'delta': {'tool_calls': [{'index': 2, 'function': {'arguments': '007\\'}}]}, 'finish_reason': None}], ...}
{..., 'choices': [{'index': 0, 'delta': {'tool_calls': [{'index': 2, 'function': {'arguments': 'u0007'}}]}, 'finish_reason': None}], ...}
{..., 'choices': [{'index': 0, 'delta': {'tool_calls': [{'index': 2, 'function': {'arguments': '\\u0007'}}]}, 'finish_reason': None}], ...}
{..., 'choices': [{'index': 0, 'delta': {'tool_calls': [{'index': 2, 'function': {'arguments': '\\u00'}}]}, 'finish_reason': None}], ...}

What I have tried

  • Use OpenAI Python SDK / Responses API: the same problem.
  • No streaming: No problem in most cases. But I still see about 1%(?) requests have bad tools arguments like this.

The model gpt-5 is stinky for sure.

The AI occasionally emitted parallel tool calls with this input. Using “parallel_tool_calls”: False didn’t improve the query parameters attempted.

“reasoning_effort”: “low” because it was taking too long - no improvement.

Demanding a preamble to the user first made no real difference in ensuring success, nor turning off the obfuscation, etc.

Perhaps subtle improvement, a description for the “q” property: “description”: “the multilingual utf-8 search query string to run on a search engine”. Failures were still massive, the output producing nonsense “escaping” tokens, here in the transition from the first to second query “q” object:

{"arguments":"s"}}]}
{"arguments":"\"},{\""}}]}
{"arguments":"q"}}]}
{"arguments":"\":\""}}]}
{"arguments":"\\"}}]}
{"arguments":"u"}}]}
{"arguments":"00"}}]}

Most importantly:

The AI is aware that it is outputting garbage - THE MODEL IS DAMAGED:

— Reconstructed tool calls —

Tool #0 :: web_search
{
“query”: [
{
“q”: “anime opening red umbrella flying caught by someone sea related 2010s”
},
{
“q”: “\u000e30\t0\u000210\u00015 V OP \u00066\u0002c\u00073 \u000e55 \u0007e\u00064 \t7 \bb (Japanese search) \t9 \be\bb \t1 \bd \t0 \bf \t0 \t2 \ba \t0 (This is garbled)”
}
]
}
Attempt 1: BAD

Log of that trial (that had to be limited here because of max forum message characters):

...
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" caught"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" by"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" someone"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" sea"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" related"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" "}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"201"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"0"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"s"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"},{\""}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"q"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"0"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"e"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"30"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"090"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"021"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"0"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"015"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" V"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" OP"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \\"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"066"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"02"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"c"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"073"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \\"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"0"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"e"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"55"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \\"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"07"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"e"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"064"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \\"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"u"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"00"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"097"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" \\"}}]},"finish_reason":null}],"usage":null}
data: {"id":"chatcmpl-CX3M7Co5pPXEVmxCybWisfgZ6hFnf","object":"chat.completion.chunk","created":1761993759,"model":"gpt-5-2025-08-07","service_tier":"default","system_fingerprint":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"u"}}]},"finish_reason":null}],"usage":null}
...
...

More fluent code that can provide display of content and reassembled tool calls, logs to non-clobbering file names (stick this in its own directory to run), context manager, more parameters to no avail.

import os
import json
import httpx
from typing import Dict, Any, List


def api_headers() -> dict[str, str]:
    """
    Build only the Bearer Authorization header using OPENAI_API_KEY
    from the environment. Raises if the variable is missing.
    """
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        raise RuntimeError("OPENAI_API_KEY is not set in the environment.")
    return {"Authorization": f"Bearer {api_key}"}


def pretty_print_tool_calls(assembled_tools: Dict[int, Dict[str, Any]]) -> None:
    """
    Pretty-print reconstructed tool calls whose function.arguments arrived as fragmented deltas.
    Attempts to JSON-decode arguments for a nicer render; falls back to raw text.
    """
    if not assembled_tools:
        return
    print("\n--- Reconstructed tool calls ---")
    for idx in sorted(assembled_tools.keys()):
        tc = assembled_tools[idx]
        fn_name = tc.get("name") or "(unknown)"
        args_str = tc.get("arguments") or ""
        print(f"\nTool #{idx} :: {fn_name}")
        pretty_args = None
        try:
            parsed = json.loads(args_str)
            pretty_args = json.dumps(parsed, ensure_ascii=False, indent=2)
        except Exception:
            pass
        print(pretty_args if pretty_args is not None else args_str)


def main() -> str:
    """
    Stream from the OpenAI Chat Completions API using httpx.
    - Single total timeout of 120 seconds.
    - Streams SSE lines, collects raw text, and prints message/tool deltas.
    - Raises for HTTP errors and prints response body for diagnostics.
    - Writes the final raw SSE to a *non-clobbering* file AFTER leaving the network context.
    Returns "BAD" if suspicious byte/escape patterns appear, else "OK".
    """
    payload: Dict[str, Any] = {
        "messages": [
            {
                "role": "user",
                "content": (
                    "Help me find an anime. 2010s, sea-related. "
                    "OP: a red umbrella fly to the sky and finally caught by someone. "
                    "Search by Japanese, Chinese"
                ),
            }
        ],
        "model": "gpt-5",
        "parallel_tool_calls": False,
        "reasoning_effort": "low",
        "tools": [
            {
                "type": "function",
                "function": {
                    "name": "web_search",
                    "strict": True,
                    "description": "This tool allows you to search the web by the given search queries.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "query": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "q": {
                                            "type": "string",
                                            "description": "the multilingual utf-8 search query string to run on a search engine",
                                        }
                                    },
                                    "required": ["q"],
                                    "additionalProperties": False,
                                },
                                "minItems": 1,
                                "maxItems": 6,
                            }
                        },
                        "required": ["query"],
                        "additionalProperties": False,
                    },
                },
            },
        ],
    }

    payload.update({
        "stream": True,
        "stream_options": {                # Valid ONLY when "stream": True
            "include_obfuscation": False,  # If True, emits an extra key that normalizes traffic
            "include_usage": True,         # If True, final SSE chunk includes a token usage object
        },
    })
    raw_output_lines: List[str] = []
    assembled_tools: Dict[int, Dict[str, Any]] = {}
    bad_patterns = ["00", "\\u", "\\x"]
    timeout = httpx.Timeout(120.0)

    with httpx.Client(headers=api_headers(), timeout=timeout) as client:
        try:
            with client.stream(
                "POST",
                "https://api.openai.com/v1/chat/completions",
                json=payload,
            ) as resp:
                try:
                    resp.raise_for_status()
                except httpx.HTTPStatusError:
                    err_body = resp.read()
                    url = str(resp.request.url) if resp.request else "unknown-url"
                    print(
                        f"HTTP error {resp.status_code} while calling {url}:\n"
                        f"{err_body.decode('utf-8', errors='replace')}"
                    )
                    raise

                for line in resp.iter_lines():
                    if not line:
                        continue
                    # httpx may yield str (when charset known) or bytes; normalize to str.
                    s = line if isinstance(line, str) else line.decode("utf-8", errors="replace")
                    s = s.strip()
                    if not s or s == "data:":
                        continue
                    data_str = s[len("data:") :].strip() if s.startswith("data:") else s
                    if data_str == "[DONE]":
                        continue

                    raw_output_lines.append(s)

                    try:
                        event = json.loads(data_str)
                    except json.JSONDecodeError:
                        continue

                    choices = event.get("choices") or []
                    if not choices:
                        continue

                    delta = choices[0].get("delta") or {}
                    finish_reason = choices[0].get("finish_reason")

                    if delta.get("content"):
                        print(delta["content"], end="", flush=True)

                    for td in delta.get("tool_calls") or []:
                        t_index = td.get("index")
                        fn = td.get("function") or {}
                        if t_index is None:
                            continue
                        slot = assembled_tools.setdefault(t_index, {"name": None, "arguments": ""})
                        if fn.get("name"):
                            slot["name"] = fn["name"]
                        if fn.get("arguments"):
                            slot["arguments"] += fn["arguments"]

                    if finish_reason:
                        print("\n\n--- finish_reason:", finish_reason, "---")

        except httpx.HTTPError as e:
            print(f"\nRequest failed: {e!r}")

    # After network context ends, write the full raw SSE to a non-clobbering file
    raw_output = "\n".join(raw_output_lines) + ("\n" if raw_output_lines else "")

    # Simple heuristic to flag abnormal encodings/escapes in the collected stream
    status = "BAD" if any(p in raw_output for p in bad_patterns) else "OK"

    # Find the next available sse_outputNNN.txt filename (001..999)
    base, ext = "sse_output", ".txt"
    idx = 1
    out_path = os.path.abspath(f"{base}{idx:03d}{ext}")
    while os.path.exists(out_path) and idx < 1000:
        idx += 1
        out_path = os.path.abspath(f"{base}{idx:03d}{ext}")

    try:
        with open(out_path, "w", encoding="utf-8") as f:
            f.write(raw_output)
        print(f"\n\n[SSE raw saved to {out_path}]")
    except Exception as e:
        print(f"\n\n[Could not save SSE raw output: {e!r}]")

    # Show the reconstructed tool calls for inspection
    pretty_print_tool_calls(assembled_tools)

    return status


if __name__ == "__main__":
    for i in range(2):
        result = main()
        print(f"Attempt {i+1}: {result}")

I didn’t try non-streaming or on Responses, but can replicate the concern easily with input variations and moderate attempts at remediation.
.
gpt-5-mini and o4-mini seems OK. OpenAI screwed up the model.

Seems the best you might do is to re-write your function as an attempt - make it an array of strings instead of objects.

1 Like