fix(oci,httpx): normalize finish_reason, preserve response_format, fix sync embed JSON content-type

- cohere.py / generic.py: normalize unknown OCI finishReason values (ERROR,
  ERROR_TOXIC, CONTENT_FILTERED, USER_CANCEL, ...) to 'stop' in non-streaming
  and streaming generic handlers, matching the streaming Cohere handler so
  downstream consumers switching on finish_reason aren't broken by raw OCI
  values.
- transformation.py: restore the dual-key alias so optional_params still
  carries the original 'response_format' key alongside the OCI-mapped
  'responseFormat'. Downstream litellm framework code (json_mode detection,
  logging) inspects 'response_format' after map_openai_params runs.
- llm_http_handler.py: make the sync embedding path mirror the async path —
  when sign_request returns no signed_body, send via json=data (which sets
  Content-Type: application/json) instead of data=json.dumps(data) which
  doesn't. Removes a sync/async behavioural asymmetry for non-OCI providers
  that adopt the sign_request pattern.

Co-authored-by: Yassin Kortam <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-19 09:03:11 +00:00
parent b295514489
commit 489f7c146b
No known key found for this signature in database
4 changed files with 39 additions and 9 deletions

View file

@ -939,12 +939,20 @@ class BaseLLMHTTPHandler:
sync_httpx_client = client
try:
response = sync_httpx_client.post(
url=api_base,
headers=headers,
data=signed_body if signed_body is not None else json.dumps(data),
timeout=timeout,
)
if signed_body is not None:
response = sync_httpx_client.post(
url=api_base,
headers=headers,
data=signed_body,
timeout=timeout,
)
else:
response = sync_httpx_client.post(
url=api_base,
headers=headers,
json=data,
timeout=timeout,
)
except Exception as e:
raise self._handle_error(
e=e,

View file

@ -216,8 +216,15 @@ def handle_cohere_response(
finish_reason = "length"
elif oci_finish_reason == "TOOL_CALL":
finish_reason = "tool_calls"
elif oci_finish_reason is not None:
# OCI Cohere can emit error/cancel finish reasons (e.g. ``ERROR``,
# ``ERROR_TOXIC``, ``ERROR_LIMIT``, ``USER_CANCEL``) that aren't part
# of OpenAI's standard set. Normalize them to ``"stop"`` so downstream
# consumers switching on ``finish_reason`` keep working — matches the
# streaming handler below.
finish_reason = "stop"
else:
finish_reason = oci_finish_reason
finish_reason = None
tool_calls: Optional[List[Dict[str, Any]]] = None
if cohere_response.chatResponse.toolCalls:

View file

@ -332,7 +332,11 @@ def handle_generic_response(
elif oci_finish_reason == "TOOL_CALLS":
model_response.choices[0].finish_reason = "tool_calls" # type: ignore[union-attr]
elif oci_finish_reason is not None:
model_response.choices[0].finish_reason = oci_finish_reason # type: ignore[union-attr,assignment]
# OCI GENERIC can emit non-OpenAI finish reasons (e.g. ``ERROR``,
# ``CONTENT_FILTERED``, ``CANCELLED``). Normalize to ``"stop"`` so
# downstream consumers switching on ``finish_reason`` keep working —
# matches the streaming handler.
model_response.choices[0].finish_reason = "stop" # type: ignore[union-attr]
oci_usage = completion_response.chatResponse.usage
reasoning_tokens: Optional[int] = None
@ -398,8 +402,13 @@ def handle_generic_stream_chunk(dict_chunk: dict) -> ModelResponseStream:
finish_reason = "length"
elif oci_finish_reason == "TOOL_CALLS":
finish_reason = "tool_calls"
elif oci_finish_reason is not None:
# OCI GENERIC can emit non-OpenAI finish reasons (e.g. ``ERROR``,
# ``CONTENT_FILTERED``, ``CANCELLED``). Normalize to ``"stop"`` so
# downstream consumers switching on ``finish_reason`` keep working.
finish_reason = "stop"
else:
finish_reason = oci_finish_reason
finish_reason = None
return ModelResponseStream(
choices=[

View file

@ -252,6 +252,12 @@ class OCIChatConfig(BaseConfig):
adapted_params[key] = value
continue
adapted_params[alias] = value
# Preserve the original OpenAI ``response_format`` key alongside the
# OCI-mapped ``responseFormat`` so downstream litellm framework code
# (e.g. ``json_mode`` detection, logging) that inspects
# ``optional_params["response_format"]`` continues to work.
if alias == "responseFormat":
adapted_params["response_format"] = value
return adapted_params