apache/shenyu · error · ShenyuException

Failed to parse request body into ChatCompletionRequest

Error message

Failed to parse request body into ChatCompletionRequest

What it means

OpenAiProtocolAdapter.toChatCompletionRequest mutates the client's raw JSON tree (injecting stream flag etc.) and then binds it to the ChatCompletionRequest DTO with Jackson. If the body cannot be converted — wrong types, unknown/invalid fields with strict parsing, or malformed structure — a ShenyuException wrapping the Jackson failure is thrown and the request is rejected before proxying to the AI upstream.

Solutions

  1. Log/inspect the wrapped Jackson cause to find the exact offending field, then fix the client request body field types/structure.
  2. Validate the JSON body against the ChatCompletionRequest schema before sending it through the AI proxy.
  3. If a legitimate field is rejected, relax the adapter (configure ObjectMapper deserialization failure handlers) or extend the ChatCompletionRequest DTO.
  4. Confirm the Content-Type is application/json and the body is complete (not truncated by an upstream proxy).

Example fix

// before
curl -X POST gateway/chat -d '{"model": 1, "messages":"hi"}'
// after
curl -X POST gateway/chat -H 'Content-Type: application/json' \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'
Defensive patterns

Strategy: validation

Validate before calling

const errs = [];
if (typeof body.model !== 'string') errs.push('model must be a string');
if (!Array.isArray(body.messages)) errs.push('messages must be an array');
if (errs.length) throw new Error('invalid ChatCompletionRequest: ' + errs.join('; '));

Type guard

function isChatCompletionRequest(b) {
  return !!b && typeof b === 'object' && typeof b.model === 'string' && Array.isArray(b.messages);
}

Try / catch

try {
  adapter.toChatCompletionRequest(rawBody);
} catch (ShenyuException e) {
  log.error("bad AI request body: {}", e.getCause(), e);
  return ResponseEntity.badRequest().body("invalid request body");
}

Prevention

When it happens

Trigger: Calling toChatCompletionRequest with a JSON body whose fields don't match ChatCompletionRequest (e.g. "model": 123, "messages" as a non-array, or a structure that fails Jackson treeToValue after MAPPER.treeToValue).

Common situations: Clients posting OpenAI-compatible requests with subtly wrong field types, extra nested fields that fail strict binding, non-JSON or partially valid payloads, or a gateway version whose ChatCompletionRequest DTO is narrower than the fields the client sends.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/dc25f8d8d2a221f3. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-common/src/main/java/org/apache/shenyu/plugin/ai/common/protocol/OpenAiProtocolAdapter.java:146

            if (Objects.nonNull(fallbackConfig.getModel()) && !fallbackConfig.getModel().isEmpty()) {
                mutableRoot.put(FIELD_MODEL, fallbackConfig.getModel());
            }
            if (Objects.nonNull(fallbackConfig.getTemperature())) {
                mutableRoot.put(FIELD_TEMPERATURE, fallbackConfig.getTemperature());
            }
            if (Objects.nonNull(fallbackConfig.getMaxTokens())) {
                mutableRoot.put(FIELD_MAX_TOKENS, fallbackConfig.getMaxTokens());
                mutableRoot.put(FIELD_MAX_COMPLETION_TOKENS, fallbackConfig.getMaxTokens());
            }
        }

        mutableRoot.put(FIELD_STREAM, stream);

        try {
            return MAPPER.treeToValue(mutableRoot, ChatCompletionRequest.class);
        } catch (Exception e) {
            LOG.error("[AiProxy] Failed to parse request body into ChatCompletionRequest", e);
            throw new ShenyuException("Failed to parse request body into ChatCompletionRequest", e);
        }
    }

    private static JsonNode parseStrict(final String json) {
        try {
            return MAPPER.readTree(json);
        } catch (JsonProcessingException e) {
            return null;
        }
    }
}

View on GitHub (pinned to 567142e072)