Skip to content

feat(openai_like): add Responses API support to JSON providers - #21398

Merged
Chesars merged 13 commits into
BerriAI:litellm_oss_staging_03_10_2026from
Chesars:feat/openai-like-responses-api
Mar 11, 2026
Merged

feat(openai_like): add Responses API support to JSON providers#21398
Chesars merged 13 commits into
BerriAI:litellm_oss_staging_03_10_2026from
Chesars:feat/openai-like-responses-api

Conversation

@Chesars

@Chesars Chesars commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Enables JSON-declared providers to support /v1/responses without Python code. Simplifies Perplexity responses config (410 → 40 lines).

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have Added testing in the tests/litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

🆕 New Feature
🧹 Refactoring

Changes

Responses API infrastructure for JSON providers

  • Add supported_endpoints field to SimpleProviderConfig (default: [])
  • Add supports_responses_api() to JSONProviderRegistry
  • Create OpenAILikeResponsesConfig base class in litellm/llms/openai_like/responses/
  • Add create_responses_config_class() for dynamic config generation from JSON
  • ProviderConfigManager.get_provider_responses_api_config() now accepts Union[LlmProviders, str] and checks JSON providers first

Perplexity (410 → 40 lines)

  • Move cost dict→float parsing to generic validators (ResponseAPIUsage.parse_cost + Usage.__init__)
  • Simplify PerplexityResponsesConfig: only preset/ model handling remains, everything else inherited from OpenAIResponsesAPIConfig

How to use

Add supported_endpoints to any JSON provider in providers.json:

{
  "your_provider": {
    "base_url": "https://api.provider.com/v1",
    "api_key_env": "PROVIDER_API_KEY",
    "supported_endpoints": ["/v1/chat/completions", "/v1/responses"]
  }
}

Tests

  • 19 unit tests covering: supported_endpoints defaults, supports_responses_api(), dynamic class generation (URL, headers, inheritance), ProviderConfigManager integration

@vercel

vercel Bot commented Feb 17, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
litellm Error Error Mar 11, 2026 3:14am

Request Review

@greptile-apps

greptile-apps Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces Responses API (/v1/responses) support for JSON-declared OpenAI-compatible providers, enabling zero-code configuration via a supported_endpoints field in providers.json. It also simplifies PerplexityResponsesConfig from ~410 to ~40 lines by inheriting from OpenAIResponsesAPIConfig, with restored status error checking and proper message type handling for Perplexity's list-input requirements.

Key changes:

  • SimpleProviderConfig gains supported_endpoints: list (default [])
  • JSONProviderRegistry.supports_responses_api() checks for "/v1/responses" in that list
  • create_responses_config_class() dynamically generates a JSONProviderResponsesConfig class (cached per slug)
  • ProviderConfigManager.get_provider_responses_api_config() now accepts Union[LlmProviders, str] and checks Python classes first, then falls back to JSON providers
  • ResponseAPIUsage.parse_cost field validator added to handle providers (like Perplexity) that return cost as a dict instead of a float
  • All six responses/main.py call sites updated to pass raw provider string directly, enabling JSON-only providers

Issue found:

  • The dynamic get_complete_url appends only /responses to base_url, meaning providers must have base_url ending in /v1 for the generated URL to be correct — this convention is documented but not enforced in code, creating risk of silent configuration errors.

Confidence Score: 4/5

  • The PR introduces well-tested Responses API support for JSON providers with one actionable issue about URL validation that should be addressed before merge.
  • The core infrastructure is sound with comprehensive test coverage. The architecture correctly prioritizes Python-class providers over JSON configs, and the simplified Perplexity config properly delegates to the base class while adding necessary overrides. One verified issue remains: the URL construction for the Responses API endpoint assumes base_url ends with /v1 but doesn't enforce this at runtime, creating risk of silent configuration errors. This should be addressed with validation. The docstring concerning provider-specific references was already corrected, and the edge case about pydantic model inputs is too theoretical to warrant blocking. Overall quality is good with proper test coverage for the happy path.
  • litellm/llms/openai_like/dynamic_config.py — needs URL validation to prevent silent configuration errors

Last reviewed commit: da76e17

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread litellm/utils.py
Comment thread litellm/llms/perplexity/responses/transformation.py
Comment thread litellm/types/utils.py Outdated
@Chesars
Chesars force-pushed the feat/openai-like-responses-api branch from d4599f7 to 21af7df Compare February 17, 2026 19:28
@Chesars

Chesars commented Feb 17, 2026

Copy link
Copy Markdown
Contributor Author

@greptile, could u re-review please?

@greptile-apps

greptile-apps Bot commented Feb 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds Responses API (/v1/responses) support to the JSON provider system, allowing OpenAI-compatible providers declared in providers.json to support the Responses API without writing Python code. It also simplifies the Perplexity responses config from ~410 to ~40 lines by inheriting from OpenAIResponsesAPIConfig.

  • Adds supported_endpoints field to SimpleProviderConfig and supports_responses_api() to JSONProviderRegistry for declaring endpoint support in JSON
  • Creates OpenAILikeResponsesConfig base class and create_responses_config_class() for dynamic config generation, with class-level caching
  • Refactors ProviderConfigManager.get_provider_responses_api_config() to accept Union[LlmProviders, str] and check JSON providers as fallback after Python classes
  • Moves Perplexity's dict-to-float cost parsing into generic validators (ResponseAPIUsage.parse_cost and Usage.__init__), benefiting any future provider with similar behavior
  • Simplifies PerplexityResponsesConfig to only retain preset model handling and restricted param list, delegating everything else to the OpenAI base
  • Includes 19 mock-only unit tests covering the full feature surface

Confidence Score: 4/5

  • This PR is safe to merge with one minor issue in the base class URL handling that should be addressed.
  • The architecture is well-designed with proper separation of concerns, class caching, and Python-class-priority over JSON fallback. The Perplexity simplification is a good refactor that moves provider-specific cost parsing into generic validators. One logic issue exists: the base class OpenAILikeResponsesConfig.get_complete_url silently produces an invalid URL when no base URL is configured, though this is mitigated by the dynamic config's override that raises a ValueError. Tests are comprehensive and mock-only.
  • litellm/llms/openai_like/responses/transformation.py - get_complete_url can produce invalid URL '/responses' when api_base is not configured

Important Files Changed

Filename Overview
litellm/llms/openai_like/dynamic_config.py Adds create_responses_config_class() with proper caching (addresses the prior review concern). Minor: sends empty bearer token when no API key is configured.
litellm/llms/openai_like/json_loader.py Adds supported_endpoints field and supports_responses_api() registry method. Clean, minimal additions with sensible defaults.
litellm/llms/openai_like/responses/transformation.py New base class for OpenAI-like responses. get_complete_url falls back to empty string when no base URL is configured, producing an invalid /responses URL instead of raising an error.
litellm/llms/perplexity/responses/transformation.py Significantly simplified from ~410 to ~40 lines. Custom tool transformation, response parsing, and streaming logic removed in favor of OpenAI base class inheritance. Only preset model handling and supported params remain.
litellm/responses/main.py Removes LlmProviders() enum wrapping from custom_llm_provider before passing to get_provider_responses_api_config, allowing string-based JSON providers to be resolved. Clean, minimal change across 6 call sites.
litellm/utils.py Refactors get_provider_responses_api_config to accept Union[LlmProviders, str], check Python classes first (priority), then fall back to JSON providers. Extracts existing logic into _get_python_responses_api_config. Well-structured with clear priority ordering.
litellm/types/llms/openai.py Adds parse_cost field validator to ResponseAPIUsage to handle dict-format cost (e.g. Perplexity's {total_cost: 0.01}). Clean Pydantic validator approach.
litellm/types/utils.py Adds dict-to-float cost parsing in Usage.__init__. Handles the case where dict lacks total_cost key by deleting the attribute (consistent with the else branch).
tests/test_litellm/llms/openai_like/responses/test_openai_like_responses.py 19 unit tests covering config defaults, registry methods, dynamic class generation, URL building, header validation, inheritance, and ProviderConfigManager integration. All mock-based, no network calls.

Flowchart

flowchart TD
    A["litellm.responses(model='provider/model')"] --> B["responses/main.py\nResolve custom_llm_provider"]
    B --> C["ProviderConfigManager\n.get_provider_responses_api_config()"]
    C --> D{"Is provider a\nknown LlmProviders enum?"}
    D -->|Yes| E["_get_python_responses_api_config()\nCheck Python class registry"]
    D -->|No| F["JSONProviderRegistry\n.exists() + .supports_responses_api()"]
    E -->|Found| G["Return Python config\ne.g. PerplexityResponsesConfig"]
    E -->|Not found| F
    F -->|Supported| H["create_responses_config_class()\nGenerate from JSON + cache"]
    H --> I["Return JSONProviderResponsesConfig\ninherits OpenAILikeResponsesConfig"]
    F -->|Not supported| J["Return None\nFallback to chat-based handler"]
    G --> K["Execute Responses API request"]
    I --> K
Loading

Last reviewed commit: df40a19

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread litellm/llms/openai_like/responses/transformation.py Outdated
Comment thread litellm/llms/openai_like/dynamic_config.py
@Chesars Chesars changed the title feat(openai_like): add Responses API support to JSON provider system feat(openai_like): add Responses API support to JSON providers Feb 17, 2026
Add infrastructure for JSON-declared providers to support /v1/responses
via `supported_endpoints` field in providers.json. Simplify Perplexity
responses config from 410 to 40 lines by moving cost dict→float parsing
to generic validators in ResponseAPIUsage and Usage.

- Add `supported_endpoints` field to SimpleProviderConfig (default: [])
- Add `supports_responses_api()` to JSONProviderRegistry
- Create OpenAILikeResponsesConfig base class for responses API
- Add `create_responses_config_class()` with class caching
- ProviderConfigManager: Python classes take priority over JSON fallback
- Fix ResponseAPIUsage.cost field_validator to handle dict cost objects
- Fix Usage.__init__ to handle dict cost from chat completions
- Simplify PerplexityResponsesConfig with get_supported_openai_params guard
- Add 20 unit tests including Python-over-JSON priority test
@Chesars
Chesars force-pushed the feat/openai-like-responses-api branch from df40a19 to 0c27f20 Compare February 17, 2026 19:37
@Chesars
Chesars changed the base branch from main to litellm_oss_staging_03_10_2026 March 10, 2026 20:02
Resolve conflict in perplexity/responses/transformation.py by keeping
the simplified ~50 line version (PR's goal) instead of main's ~410 line
version. Added supports_native_websocket() -> False from main.
Tested and confirmed both o4-mini and o4-mini-2025-04-16 support
web_search_preview via the Responses API.
Comment thread litellm/llms/openai_like/providers.json Outdated
Comment thread litellm/llms/perplexity/responses/transformation.py
Comment thread litellm/llms/perplexity/responses/transformation.py
…hat_config

- Invert lookup order in get_provider_chat_config so Python classes
  (with custom overrides) are checked before JSON providers, matching
  the pattern already used in get_provider_responses_api_config.
  Prevents regression where providers like Perplexity (declared in
  providers.json) would silently lose their custom chat config.
- Add 'models' to Perplexity Responses API supported params (fallback
  chain feature documented in Perplexity API).
Comment thread litellm/llms/openai_like/dynamic_config.py
Comment thread litellm/llms/perplexity/responses/transformation.py
Comment thread litellm/llms/openai_like/providers.json Outdated
- Add 'models' field to ResponsesAPIOptionalRequestParams TypedDict so
  the Perplexity fallback chain param is not silently filtered out by
  get_requested_response_api_optional_param(). Verified with curl that
  Perplexity's models param works (tries models in order until one
  succeeds).
- Fix test_python_class_takes_priority_over_json: save and restore the
  original JSONProviderRegistry entry instead of deleting it, preventing
  state corruption for subsequent tests.
Comment thread litellm/llms/openai_like/dynamic_config.py
Comment thread litellm/llms/perplexity/responses/transformation.py
Chesars added 2 commits March 10, 2026 19:29
- Update test_all_supported_params_declared to match the simplified
  8-param list (was 24 params from old 410-line implementation)
- Replace test_cost_transformation tests: use ResponseAPIUsage
  validator directly instead of removed _transform_usage method
  (cost parsing moved to generic parse_cost validator)
The dynamic responses config appends /responses to base_url, so it
must end with /v1 to produce the correct endpoint URL.
@Chesars

Chesars commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

@greptile preset removal is a false positive — it was never a kwarg. It's handled via model name prefix: perplexity/preset/pro-searchtransform_responses_api_request sends {"preset": "pro-search"}. No breaking change.

@greptile-apps

greptile-apps Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Got it - thanks for the clarification. If preset is parsed from the model name (e.g., perplexity/preset/pro-search) and never exposed as a kwarg, then there's no breaking change. That's consistent with how LiteLLM handles provider-specific routing through model name prefixes.

Comment thread litellm/llms/perplexity/responses/transformation.py
…r handling

- Remove Perplexity-specific `models` field from ResponsesAPIOptionalRequestParams
  TypedDict to avoid polluting the shared OpenAI type. The param is still passed
  through via get_supported_openai_params + map_openai_params at runtime.
- Add transform_response_api_response override to catch Perplexity's HTTP 200
  with status:"failed" and raise BaseLLMException instead of silently succeeding.
Comment thread litellm/types/utils.py Outdated
Comment thread litellm/llms/perplexity/responses/transformation.py
…reaming cost test

Remove the isinstance(cost, dict) guard from Usage.__init__ — ResponseAPIUsage.parse_cost
validator already converts Perplexity's cost dict to float before it reaches Usage.

Add test_streaming_cost_dict_to_float_via_validator to verify the dict→float conversion
works end-to-end through transform_streaming_response (pydantic recursive construction).
@Chesars

Chesars commented Mar 11, 2026

Copy link
Copy Markdown
Contributor Author

@greptile. preset and models do not work in main as explicit parameters — they were filtered out by ResponsesAPIOptionalRequestParams TypedDict in get_requested_response_api_optional_param. The refactoring doesn't introduce a regression. Verified by running the filtering logic with main.

Perplexity has a dedicated Python class (PerplexityResponsesConfig) that
always takes priority over JSON config. The JSON entry was dead code.
Comment thread litellm/llms/perplexity/responses/transformation.py
Comment thread litellm/types/llms/openai.py
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Perplexity requires type='message' on input items. Add _ensure_message_type
to automatically set it when missing, matching the old _format_input behavior.
@Chesars
Chesars merged commit 260c37d into BerriAI:litellm_oss_staging_03_10_2026 Mar 11, 2026
3 of 5 checks passed
@Chesars
Chesars deleted the feat/openai-like-responses-api branch March 11, 2026 03:15
Comment on lines +208 to +225
def get_complete_url(
self,
api_base: Optional[str],
litellm_params: dict,
) -> str:
if not api_base:
if provider.api_base_env:
api_base = get_secret_str(provider.api_base_env)
if not api_base:
api_base = provider.base_url

if api_base is None:
raise ValueError(
f"api_base is required for provider {provider.slug}"
)

api_base = api_base.rstrip("/")
return f"{api_base}/responses"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated URL appends only /responses to api_base. This silently produces an incorrect endpoint for any provider whose base_url does NOT already end with /v1 (e.g., "https://api.provider.com"). The documentation examples show base_url ending in /v1 (e.g., "https://api.yourprovider.com/v1"), but nothing in the code validates or enforces this convention.

If a provider sets base_url: "https://api.provider.com" (without /v1) and supported_endpoints: ["/v1/responses"], the generated URL becomes https://api.provider.com/responses — which fails at runtime with a confusing 404.

Consider adding a validation check to enforce the convention and provide a clear error message:

Suggested change
def get_complete_url(
self,
api_base: Optional[str],
litellm_params: dict,
) -> str:
if not api_base:
if provider.api_base_env:
api_base = get_secret_str(provider.api_base_env)
if not api_base:
api_base = provider.base_url
if api_base is None:
raise ValueError(
f"api_base is required for provider {provider.slug}"
)
api_base = api_base.rstrip("/")
return f"{api_base}/responses"
if api_base is None:
raise ValueError(
f"api_base is required for provider {provider.slug}"
)
api_base = api_base.rstrip("/")
# Validate that base_url ends with /v1 for responses endpoint
if not api_base.endswith("/v1"):
raise ValueError(
f"base_url for provider '{provider.slug}' must end with '/v1' to use the Responses API "
f"(got '{api_base}'). Example: 'https://api.yourprovider.com/v1'"
)
return f"{api_base}/responses"

@yuneng-jiang yuneng-jiang mentioned this pull request Mar 13, 2026
7 tasks
fzowl pushed a commit to fzowl/litellm that referenced this pull request Jun 24, 2026
…ses-api

feat(openai_like): add Responses API support to JSON providers
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.

1 participant