{"record":{"id":"7c54d63dd95b4e3a","repo":"jd-opensource/joyagent-jdgenie","slug":"invalid-or-empty-response-from-llm","errorCode":null,"errorMessage":"Invalid or empty response from LLM","messagePattern":"Invalid or empty response from LLM","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"genie-backend/src/main/java/com/jd/genie/agent/llm/LLM.java","lineNumber":480,"sourceCode":"            if (Objects.nonNull(extParams)) {\n                params.putAll(extParams);\n            }\n\n            log.info(\"{} call llm request {}\", context.getRequestId(), JSONObject.toJSONString(params));\n            if (!stream) {\n                params.put(\"stream\", false);\n                // 调用 API\n                CompletableFuture<String> future = callOpenAI(params, timeout);\n                return future.thenApply(responseJson -> {\n                    try {\n                        // 解析响应\n                        log.info(\"{} call llm response {}\", context.getRequestId(), responseJson);\n                        JsonNode jsonResponse = objectMapper.readTree(responseJson);\n                        JsonNode choices = jsonResponse.get(\"choices\");\n\n                        if (choices == null || choices.isEmpty() || choices.get(0).get(\"message\") == null) {\n                            log.error(\"{} Invalid response: {}\", context.getRequestId(), responseJson);\n                            throw new IllegalArgumentException(\"Invalid or empty response from LLM\");\n                        }\n\n                        // 提取响应内容\n                        JsonNode message = choices.get(0).get(\"message\");\n                        String content = message.has(\"content\") && !\"null\".equals(message.get(\"content\").asText()) ? message.get(\"content\").asText() : null;\n\n                        // 提取工具调用\n                        List<ToolCall> toolCalls = new ArrayList<>();\n                        if (\"struct_parse\".equals(functionCallType)) {\n                            // 匹配方式: 直接匹配 ```json ... ``` 代码块\n                            String pattern = \"```json\\\\s*([\\\\s\\\\S]*?)\\\\s*```\";\n                            List<String> matches = findMatches(content, pattern);\n                            if (!matches.isEmpty()) {\n                                for (String match : matches) {\n                                    ToolCall oneToolCall = parseToolCall(context, match);\n                                    if (Objects.nonNull(oneToolCall)) {\n                                        toolCalls.add(oneToolCall);\n                                    }","sourceCodeStart":462,"sourceCodeEnd":498,"githubUrl":"https://github.com/jd-opensource/joyagent-jdgenie/blob/2417e0b8b636d941ad5fb14c59b20dddfef5375d/genie-backend/src/main/java/com/jd/genie/agent/llm/LLM.java#L462-L498","documentation":"LLM.askTool validates the completion response and throws IllegalArgumentException when choices is null/empty or the first choice has no message object. Unlike ask, it tolerates null content (tool calls may have no text) but requires a message node.","triggerScenarios":"Calling askTool when the endpoint returns an error payload, empty body, truncated stream, or a choices[0] without a message field.","commonSituations":"Model/provider rejects the tool schema and returns an error JSON, quota/rate-limit responses, wrong endpoint path, or incompatible API version that reshapes the response.","solutions":["Log the raw responseJson included in the error path to diagnose","Verify tool schemas and model support for function calling","Check HTTP status and error body before parsing choices","Retry transient failures with backoff"],"exampleFix":"// before\nLLMToolResponse r = llm.askTool(msgs, \"auto\", tools);\n// after\ntry {\n    LLMToolResponse r = llm.askTool(msgs, \"auto\", tools);\n} catch (IllegalArgumentException e) {\n    log.error(\"invalid llm tool response, check model/endpoint\");\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":"// check HTTP status and that body parses as JSON before parsing choices","typeGuard":null,"tryCatchPattern":"try {\n    return llm.askTool(msgs, choice, tools);\n} catch (IllegalArgumentException e) {\n    log.error(\"invalid llm tool response, retrying\", e);\n    return retryWithBackoff(() -> llm.askTool(msgs, choice, tools));\n}","preventionTips":["Confirm the model supports function calling","Validate tool JSON schemas against provider spec","Monitor rate limits and error payloads","Log responseJson for diagnosis"],"tags":["llm","json","response-parsing"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"2417e0b8b636d941ad5fb14c59b20dddfef5375d","analyzedAt":"2026-09-08T11:28:19.414Z","contentChangedAt":"2026-09-08T11:28:19.414Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}