diff --git a/litellm/llms/cohere/chat/v2_transformation.py b/litellm/llms/cohere/chat/v2_transformation.py index b46a995a736..141a174e558 100644 --- a/litellm/llms/cohere/chat/v2_transformation.py +++ b/litellm/llms/cohere/chat/v2_transformation.py @@ -250,7 +250,7 @@ class CohereV2ChatConfig(OpenAIGPTConfig): completion_tokens: Final = token_usage.get("output_tokens", 0) model_response.choices[0].finish_reason = map_finish_reason( # rebind-ok: ModelResponse is filled in place - cohere_v2_chat_response["finish_reason"] + cohere_v2_chat_response.get("finish_reason", "COMPLETE") ) model_response.created = int(time.time()) model_response.model = model diff --git a/tests/unit/llms/cohere/chat/test_cohere_transformation.py b/tests/unit/llms/cohere/chat/test_cohere_transformation.py index 6b8fd7629dc..fcb3d3f1cc0 100644 --- a/tests/unit/llms/cohere/chat/test_cohere_transformation.py +++ b/tests/unit/llms/cohere/chat/test_cohere_transformation.py @@ -607,6 +607,25 @@ def test_cohere_v2_maps_finish_reason( assert response.choices[0].finish_reason == expected_finish_reason +@pytest.mark.usefixtures("_cohere_httpx_transport") +@pytest.mark.respx(assert_all_called=True) +def test_cohere_v2_missing_finish_reason_defaults_to_stop(respx_mock: respx.MockRouter) -> None: + body_without_finish_reason: Final = { + key: value + for key, value in _cohere_v2_response({"content": [{"type": "text", "text": "Hello"}]}).items() + if key != "finish_reason" + } + respx_mock.post(COHERE_V2_CHAT_URL).mock(return_value=httpx.Response(200, json=body_without_finish_reason)) + + response: Final = litellm.completion( + model="cohere_chat/v2/command-a-03-2025", + messages=[{"role": "user", "content": "Say hello"}], + api_key="cohere-test-key", + ) + + assert (response.choices[0].message.content, response.choices[0].finish_reason) == ("Hello", "stop") + + @pytest.mark.usefixtures("_cohere_httpx_transport") @pytest.mark.respx(assert_all_called=True) async def test_cohere_v2_annotations(respx_mock: respx.MockRouter) -> None: