{"record":{"id":"940847bf56ab8d0f","repo":"FoundationAgents/MetaGPT","slug":"failed-to-parse-rsp","errorCode":null,"errorMessage":"Failed to parse \n {rsp}\n","messagePattern":"Failed to parse \n (.+?)\n","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"metagpt/provider/openai_api.py","lineNumber":263,"sourceCode":"                return json.loads(message.tool_calls[0].function.arguments, strict=False)\n            except json.decoder.JSONDecodeError as e:\n                error_msg = (\n                    f\"Got JSONDecodeError for \\n{'--'*40} \\n{message.tool_calls[0].function.arguments}, {str(e)}\"\n                )\n                logger.error(error_msg)\n                return self._parse_arguments(message.tool_calls[0].function.arguments)\n        elif message.tool_calls is None and message.content is not None:\n            # reponse is code, fix openai tools_call respond bug,\n            # The response content is `code``, but it appears in the content instead of the arguments.\n            code_formats = \"```\"\n            if message.content.startswith(code_formats) and message.content.endswith(code_formats):\n                code = CodeParser.parse_code(text=message.content)\n                return {\"language\": \"python\", \"code\": code}\n            # reponse is message\n            return {\"language\": \"markdown\", \"code\": self.get_choice_text(rsp)}\n        else:\n            logger.error(f\"Failed to parse \\n {rsp}\\n\")\n            raise Exception(f\"Failed to parse \\n {rsp}\\n\")\n\n    def get_choice_text(self, rsp: ChatCompletion) -> str:\n        \"\"\"Required to provide the first text of choice\"\"\"\n        return rsp.choices[0].message.content if rsp.choices else \"\"\n\n    def _calc_usage(self, messages: list[dict], rsp: str) -> CompletionUsage:\n        usage = CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0)\n        if not self.config.calc_usage:\n            return usage\n\n        try:\n            usage.prompt_tokens = count_message_tokens(messages, self.pricing_plan)\n            usage.completion_tokens = count_output_tokens(rsp, self.pricing_plan)\n        except Exception as e:\n            logger.warning(f\"usage calculation failed: {e}\")\n\n        return usage\n","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/FoundationAgents/MetaGPT/blob/11cdf466d042aece04fc6cfd13b28e1a70341b1f/metagpt/provider/openai_api.py#L245-L281","documentation":"The terminal else-branch in the tool-call response parser: the message has tool_calls present but no usable first tool call, AND content is None (or tool_calls is non-None with content None), so none of the recognized branches match. MetaGPT logs the full response and raises a generic Exception with 'Failed to parse', meaning the ChatCompletion shape was unexpected.","triggerScenarios":"get_choice_function_arguments on a response where message.tool_calls is an empty list (not None) while message.content is None, or tool_calls exists but tool_calls[0].function.arguments is missing; also finish_reason='length' responses truncated before any content or call.","commonSituations":"Truncated responses (max tokens hit during tool-call emission), provider quirks returning empty tool_calls arrays, or non-OpenAI backends whose message objects behave differently around None vs empty.","solutions":["Check rsp.choices[0].finish_reason; if 'length', raise max tokens and retry.","Retry the call — transient malformed completions often resolve on resampling.","If the backend is an OpenAI-compatible proxy, verify it faithfully nulls (not empties) tool_calls when unused.","Guard before parsing: fall back to text handling when message.content is None and not message.tool_calls."],"exampleFix":"# before\nmsg = rsp.choices[0].message\nif msg.tool_calls:  # empty list [] is truthy-checked incorrectly upstream\n    ...\n\n# after\nmsg = rsp.choices[0].message\nif msg.content is None and not msg.tool_calls:\n    raise RuntimeError(f\"empty completion, finish_reason={rsp.choices[0].finish_reason}\")","handlingStrategy":"try-catch","validationCode":"msg = rsp.choices[0].message if rsp.choices else None\nif msg is None or (msg.content is None and not msg.tool_calls):\n    finish = rsp.choices[0].finish_reason if rsp.choices else None\n    raise RuntimeError(f\"unparseable completion, finish_reason={finish}\")","typeGuard":null,"tryCatchPattern":"try:\n    result = provider.get_choice_function_arguments(rsp)\nexcept Exception as e:\n    if \"Failed to parse\" in str(e):\n        rsp = provider.completion(messages)  # resample once\n        result = provider.get_choice_function_arguments(rsp)\n    else:\n        raise","preventionTips":["Check finish_reason for 'length' before parsing tool calls","Resample once on malformed completions before failing hard","Validate OpenAI-compatible proxies null tool_calls instead of returning []"],"tags":["openai","tool-calls","parsing","truncation"],"backgroundTag":null,"analyzedSha":"11cdf466d042aece04fc6cfd13b28e1a70341b1f","analyzedAt":"2026-08-14T23:20:02.994Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}