response-format-parity-across-models · EN · 2026-10-13

Does the Response Come Back in the Same Shape for Every Model? Leaf-by-Leaf Comparison

A practical guide to the fields that vary between model providers behind an OpenAI-compatible API, covering finish_reason, usage, logprobs, and message extras, with strategies to write robust parsers that handle these differences without crashing.

Why Response Shapes Differ Across Models

When you call multiple models (Claude, GPT, DeepSeek, Qwen, GLM, Kimi) through a single API key on an aggregator, the API aims to be OpenAI-compatible. However, each model provider may have its own conventions for certain response fields. These differences can cause your parser to fail if you assume a uniform shape.

Key Fields to Examine

finish_reason

The finishreason indicates why the model stopped generating. Common values include stop, length, contentfilter, and toolcalls. However, some providers may return different strings or omit the field in certain edge cases. For example, a model might return endturn instead of stop.

Mitigation: Treat finish_reason as an open string. Use it for logging or conditional logic only if you have verified the values for your specific models. Never rely on a fixed set of values for control flow unless you handle unknowns gracefully.

usage

The usage object typically contains prompttokens, completiontokens, and totaltokens. Some providers include additional details like prompttokensdetails or completiontokens_details. The presence and naming of these subfields can vary.

Mitigation: Always check for the existence of usage and its subfields before accessing them. Use safe navigation or default values. For billing or analytics, compute totals from the fields that are present.

logprobs

logprobs is often optional and may only be returned when explicitly requested. The structure can differ: some providers return a list of token objects with token, logprob, and top_logprobs, while others may return a simpler format or omit it entirely.

Mitigation: Only request logprobs if you need it. When parsing, handle cases where logprobs is null or absent. Validate the shape before iterating.

message extras

The message object usually contains role and content. Some providers add extra fields like reasoningcontent (for chain-of-thought), toolcalls, or function_call. These extras are not guaranteed across all models.

Mitigation: Extract only the fields you need. If you rely on extras, check for their presence and provide fallbacks. Avoid assuming that every model returns the same set of extras.

Strategies for Robust Parsing

  • Use optional chaining and null checks in your code to avoid crashes when fields are missing.
  • Normalize responses by mapping provider-specific values to your own internal representation.
  • Log unexpected values for finish_reason and other enums to catch new variations.
  • Test with multiple models during development to surface differences early.
  • Avoid strict schemas unless you control the model versions; use permissive parsing.

Example: Handling finish_reason Safely

Instead of a switch statement with exhaustive cases, use a mapping with a default:

reason = response.get('choices', [{}])[0].get('finish_reason')
if reason == 'stop':
    # normal completion
elif reason == 'length':
    # truncated
else:
    # log and handle unknown

Conclusion

By treating the API response as a flexible dictionary and validating each field before use, you can build applications that work across all supported models without crashing when a different vendor answers. Always code defensively and test with the models you plan to use.