jd-opensource/joyagent-jdgenie · error · IllegalArgumentException

Invalid or empty response from LLM

Error message

Invalid or empty response from LLM

What it means

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.

Solutions

  1. Log the raw responseJson included in the error path to diagnose
  2. Verify tool schemas and model support for function calling
  3. Check HTTP status and error body before parsing choices
  4. Retry transient failures with backoff

Example fix

// before
LLMToolResponse r = llm.askTool(msgs, "auto", tools);
// after
try {
    LLMToolResponse r = llm.askTool(msgs, "auto", tools);
} catch (IllegalArgumentException e) {
    log.error("invalid llm tool response, check model/endpoint");
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check HTTP status and that body parses as JSON before parsing choices

Try / catch

try {
    return llm.askTool(msgs, choice, tools);
} catch (IllegalArgumentException e) {
    log.error("invalid llm tool response, retrying", e);
    return retryWithBackoff(() -> llm.askTool(msgs, choice, tools));
}

Prevention

When it happens

Trigger: Calling askTool when the endpoint returns an error payload, empty body, truncated stream, or a choices[0] without a message field.

Common situations: 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.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of jd-opensource/joyagent-jdgenie@2417e0b8b6 (2026-09-08). Data as JSON: /api/errors/7c54d63dd95b4e3a. Report an issue: GitHub.

Appendix: source

Thrown at genie-backend/src/main/java/com/jd/genie/agent/llm/LLM.java:480

            if (Objects.nonNull(extParams)) {
                params.putAll(extParams);
            }

            log.info("{} call llm request {}", context.getRequestId(), JSONObject.toJSONString(params));
            if (!stream) {
                params.put("stream", false);
                // 调用 API
                CompletableFuture<String> future = callOpenAI(params, timeout);
                return future.thenApply(responseJson -> {
                    try {
                        // 解析响应
                        log.info("{} call llm response {}", context.getRequestId(), responseJson);
                        JsonNode jsonResponse = objectMapper.readTree(responseJson);
                        JsonNode choices = jsonResponse.get("choices");

                        if (choices == null || choices.isEmpty() || choices.get(0).get("message") == null) {
                            log.error("{} Invalid response: {}", context.getRequestId(), responseJson);
                            throw new IllegalArgumentException("Invalid or empty response from LLM");
                        }

                        // 提取响应内容
                        JsonNode message = choices.get(0).get("message");
                        String content = message.has("content") && !"null".equals(message.get("content").asText()) ? message.get("content").asText() : null;

                        // 提取工具调用
                        List<ToolCall> toolCalls = new ArrayList<>();
                        if ("struct_parse".equals(functionCallType)) {
                            // 匹配方式: 直接匹配 ```json ... ``` 代码块
                            String pattern = "```json\\s*([\\s\\S]*?)\\s*```";
                            List<String> matches = findMatches(content, pattern);
                            if (!matches.isEmpty()) {
                                for (String match : matches) {
                                    ToolCall oneToolCall = parseToolCall(context, match);
                                    if (Objects.nonNull(oneToolCall)) {
                                        toolCalls.add(oneToolCall);
                                    }

View on GitHub (pinned to 2417e0b8b6)