litellm - ✅(Solved) Fix [Bug]: Unmapped finish reasons are mapped to "stop" [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#23793Fetched 2026-04-08 00:49:06
View on GitHub
Comments
1
Participants
2
Timeline
15
Reactions
2
Timeline (top)
cross-referenced ×4labeled ×3referenced ×3subscribed ×2

Fix Action

Fixed

PR fix notes

PR #23800: fix(core): preserve unmapped finish_reason and add 'refusal' mapping

Description (problem / solution / changelog)

Summary

Fixes #23793

Problem

map_finish_reason() was changed (in commit a4fc73f8) to default unmapped finish reasons to "stop", which is lossy. Previously, unmapped values were returned as-is, allowing callers to handle provider-specific finish reasons correctly. Additionally, Anthropic's "refusal" stop reason (docs) was missing from the mapping.

Changes

  1. Restore pass-through behavior for unmapped finish reasons — Instead of defaulting to "stop", unmapped values are now returned as-is (with a warning log). This preserves information for callers.

  2. Add "refusal" to the finish reason map — Maps Anthropic's "refusal" stop reason correctly.

  3. Add "refusal" to OpenAIChatCompletionFinishReason type — Ensures the type includes this valid reason.

  4. Update tests — Tests reflect the new pass-through behavior for unknown values and include the new "refusal" mapping.

Files Changed

  • litellm/litellm_core_utils/core_helpers.py — Pass-through unmapped values, add "refusal" to map
  • litellm/types/llms/openai.py — Add "refusal" to finish reason literal type
  • tests/test_litellm/litellm_core_utils/test_core_helpers.py — Update tests

Changed files

  • litellm/litellm_core_utils/core_helpers.py (modified, +5/-3)
  • litellm/types/llms/openai.py (modified, +1/-0)
  • tests/test_litellm/litellm_core_utils/test_core_helpers.py (modified, +6/-5)

PR #23831: fix(core): map Anthropic 'refusal' finish reason to 'content_filter'

Description (problem / solution / changelog)

Problem

Anthropic models can return "refusal" as a finish_reason (documented at https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#refusal) when the model declines to respond to a request. This reason was not handled in map_finish_reason(), so it either passed through unmapped or was previously mapped to "stop" (lossy).

Solution

Add an explicit mapping for "refusal""content_filter", which aligns with the OpenAI convention for model-refused responses and preserves the semantics of the refusal for downstream consumers.

elif finish_reason == "refusal":  # anthropic - model refused to respond
    return "content_filter"

Testing

  • Syntax verified with Python AST parser
  • Aligns with OpenAI's content_filter finish reason used for refusals

Fixes #23793

Changed files

  • litellm/litellm_core_utils/core_helpers.py (modified, +2/-0)
RAW_BUFFERClick to expand / collapse

Check for existing issues

  • I have searched the existing issues and checked that my issue is not a duplicate.

What happened?

Happened in https://github.com/BerriAI/litellm/commit/a4fc73f8#diff-c6fe5154d4522a2d357cde6ac681bb8ad8ac981385885225e28734cceaa72c88L61-L79

Specifically, map_finish_reason used to return the unmapped finish_reason if it wasn't found in the map. Now you map it to "stop", which is lossy. And in particular, your Anthropic section of the new map is missing "refusal": https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#refusal

I would suggest you include "refusal" and bring back the old behavior, letting callers handle unmapped finish reasons.

Steps to Reproduce

Relevant log output

What part of LiteLLM is this about?

SDK (litellm Python package)

What LiteLLM version are you on ?

v1.82.3

Twitter / LinkedIn details

No response

extent analysis

Fix Plan

To address the issue, we need to update the map_finish_reason function to include the missing "refusal" value and revert to its old behavior of returning the unmapped finish_reason if it's not found in the map.

Code Changes

Here are the steps to update the code:

  • Update the map_finish_reason dictionary to include "refusal":
map_finish_reason = {
    # ... existing mappings ...
    "refusal": "refusal",
    # ... existing mappings ...
}
  • Modify the map_finish_reason function to return the original finish_reason if it's not found in the map:
def map_finish_reason(finish_reason):
    return map_finish_reason.get(finish_reason, finish_reason)

Alternatively, you can use the dict.get() method with a default value:

def map_finish_reason(finish_reason):
    return {"refusal": "refusal", "stop": "stop"}.get(finish_reason, finish_reason)

Verification

To verify the fix, you can test the map_finish_reason function with different inputs, including the previously missing "refusal" value:

print(map_finish_reason("refusal"))  # Should print "refusal"
print(map_finish_reason("unknown"))  # Should print "unknown"

Extra Tips

Make sure to update the documentation and tests accordingly to reflect the changes in the map_finish_reason function. Additionally, consider adding more test cases to cover different scenarios and edge cases.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - ✅(Solved) Fix [Bug]: Unmapped finish reasons are mapped to "stop" [2 pull requests, 1 comments, 2 participants]