Skip to content

fix(openai): filter None values from embedding optional_params (encoding_format=null)#24277

Open
IvoryKir wants to merge 3 commits intoBerriAI:mainfrom
IvoryKir:fix/openai-embedding-encoding-format-null
Open

fix(openai): filter None values from embedding optional_params (encoding_format=null)#24277
IvoryKir wants to merge 3 commits intoBerriAI:mainfrom
IvoryKir:fix/openai-embedding-encoding-format-null

Conversation

@IvoryKir
Copy link

Summary

Filter out None and empty string values from optional_params in the generic OpenAI embedding handler before constructing the request, preventing encoding_format: null from being sent to custom endpoints.

Problem

When using litellm.embedding() with the openai provider and a custom api_base pointing to a local embedding server (llama.cpp, vLLM, TEI, etc.), the request includes encoding_format: null in the JSON body. Strict JSON parsers reject this:

[json.exception.type_error.302] type must be string, but is null

This was already fixed for the openai_like handler (L108-110 in litellm/llms/openai_like/embedding/handler.py) after the vLLM embeddings incident, but the same filter was not applied to the generic openai handler in litellm/llms/openai/openai.py.

Users hitting this issue use the openai provider with api_base to point to local servers — a common pattern for llama.cpp, TEI, LocalAI, etc.

Fix

Apply the exact same filtering pattern from openai_like handler to the generic openai handler:

# Before (L1327):
data = {"model": model, "input": input, **optional_params}

# After:
filtered_optional_params = {
    k: v for k, v in optional_params.items() if v not in (None, "")
}
data = {"model": model, "input": input, **filtered_optional_params}

Impact

  • No behavioral change for standard OpenAI API calls (they don't send None values)
  • Fixes all custom api_base endpoints that reject null JSON values
  • Consistent with the openai_like handler's existing approach

Refs

@vercel
Copy link

vercel bot commented Mar 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Mar 22, 2026 7:55am

Request Review

@CLAassistant
Copy link

CLAassistant commented Mar 21, 2026

CLA assistant check
All committers have signed the CLA.

@codspeed-hq
Copy link
Contributor

codspeed-hq bot commented Mar 21, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing IvoryKir:fix/openai-embedding-encoding-format-null (a2386dd) with main (c89496f)

Open in CodSpeed

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 21, 2026

Greptile Summary

This PR applies the same None/empty-string filtering already present in the openai_like embedding handler to the generic openai embedding handler, preventing encoding_format: null (and any other None-valued optional params) from being serialised into the JSON body sent to strict OpenAI-compatible endpoints such as llama.cpp, vLLM, and TEI.

Changes:

  • litellm/llms/openai/openai.py — one-line dict comprehension filter added before the data dict is constructed; the filtered dict is forwarded correctly to both the sync (make_sync_openai_embedding_request) and async (aembedding) paths.
  • New test file with 6 mock-based cases covering None, "", "float", "base64", mixed params, and empty optional_params. No real network calls are made, satisfying CI requirements.

Minor observation: The filter (v not in (None, "")) silently drops any empty-string optional param, not only encoding_format. This is intentional and exactly mirrors the openai_like handler's behaviour, but it is worth noting that a caller explicitly passing e.g. user="" would have that param silently omitted. This is unlikely to be a practical concern given OpenAI's API contracts.

Confidence Score: 4/5

  • Safe to merge — the change is a minimal, targeted fix that is consistent with the existing openai_like handler pattern and has comprehensive mock-based test coverage.
  • The fix is a single dict comprehension that mirrors an identical, battle-tested pattern already in the codebase. It correctly covers both the sync and async code paths (async aembedding receives the already-filtered data dict). Six mock-based tests cover all meaningful cases. No real network calls in the test suite. Confidence is 4 rather than 5 because the filter is applied to all optional params (not just encoding_format), which is intentional but slightly broader than the stated scope.
  • No files require special attention.

Important Files Changed

Filename Overview
litellm/llms/openai/openai.py Adds a filter to remove None and "" values from optional_params before building the embedding request data dict, mirroring the pattern already in the openai_like handler. The filtered data dict is correctly passed to both the sync and async paths.
tests/test_litellm/llms/openai/embedding/test_openai_embedding_encoding_format.py New mock-only test file covering 6 cases (None, empty string, float, base64, other params, empty dict). Uses patch.object on make_sync_openai_embedding_request and _get_openai_client, and also patches convert_to_model_response_object to prevent false failures on unrelated validation. No real network calls are made.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["OpenAIChatCompletion.embedding()"] --> B["Build filtered_optional_params\n{k: v for k, v in optional_params.items()\n if v not in (None, '')}"]
    B --> C["data = {model, input, **filtered_optional_params}"]
    C --> D{aembedding?}
    D -- "True (async)" --> E["aembedding(data=data, ...)"]
    D -- "False (sync)" --> F["_get_openai_client()"]
    F --> G["make_sync_openai_embedding_request(data=data, ...)"]
    G --> H["convert_to_model_response_object()"]
    E --> H
    H --> I["Return EmbeddingResponse"]

    style B fill:#c8f7c5,stroke:#27ae60
    style C fill:#c8f7c5,stroke:#27ae60
Loading

Last reviewed commit: "fix(tests): mock con..."

Filter out None and empty string values from optional_params before
constructing the embedding request data dictionary.

This prevents sending encoding_format=null to OpenAI-compatible servers
(llama.cpp, vLLM, TEI, LocalAI) that have strict JSON parsers and reject
null values where a string is expected:

  [json.exception.type_error.302] type must be string, but is null

The openai_like embedding handler already applies this filter
(openai_like/embedding/handler.py L108-110), but the generic openai
handler did not, causing failures when users set api_base to point to
local embedding servers.

This is a follow-up to the vLLM embeddings incident where the same
pattern was applied to the openai_like handler.

Refs: https://docs.litellm.ai/blog/vllm-embeddings-incident
      BerriAI#19174
Signed-off-by: IvoryKir <ivory.kir@gmail.com>
@IvoryKir
Copy link
Author

The lint CI failure is unrelated to this PR — Black flags litellm/proxy/management_helpers/audit_logs.py which is not touched by these changes. This is a pre-existing formatting issue in the main branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants