mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
[Feat] Day-0 Support for OpenAI Re-usable prompts Responses API (#11782)
* add prompt to responses params * add OpenAI PromptObject * add prompt param to responses api * test_get_optional_params_responses_api * test_openai_responses_litellm_router_with_prompt * docs Reusable Prompts
This commit is contained in:
parent
99c2a7fb70
commit
80501b8268
6 changed files with 166 additions and 21 deletions
|
|
@ -207,6 +207,50 @@ print(delete_response)
|
|||
|----------|---------------------|
|
||||
| `openai` | [All Responses API parameters are supported](https://github.com/BerriAI/litellm/blob/7c3df984da8e4dff9201e4c5353fdc7a2b441831/litellm/llms/openai/responses/transformation.py#L23) |
|
||||
|
||||
### Reusable Prompts
|
||||
|
||||
Use the `prompt` parameter to reference a stored prompt template and optionally supply variables.
|
||||
|
||||
```python showLineNumbers title="Stored Prompt"
|
||||
import litellm
|
||||
|
||||
response = litellm.responses(
|
||||
model="openai/o1-pro",
|
||||
prompt={
|
||||
"id": "pmpt_abc123",
|
||||
"version": "2",
|
||||
"variables": {
|
||||
"customer_name": "Jane Doe",
|
||||
"product": "40oz juice box",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
The same parameter is supported when calling the LiteLLM proxy with the OpenAI SDK:
|
||||
|
||||
```python showLineNumbers title="Stored Prompt via Proxy"
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(base_url="http://localhost:4000", api_key="your-api-key")
|
||||
|
||||
response = client.responses.create(
|
||||
model="openai/o1-pro",
|
||||
prompt={
|
||||
"id": "pmpt_abc123",
|
||||
"version": "2",
|
||||
"variables": {
|
||||
"customer_name": "Jane Doe",
|
||||
"product": "40oz juice box",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
print(response)
|
||||
```
|
||||
|
||||
## Computer Use
|
||||
|
||||
<Tabs>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
"store",
|
||||
"background",
|
||||
"stream",
|
||||
"prompt",
|
||||
"temperature",
|
||||
"text",
|
||||
"tool_choice",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from litellm.responses.litellm_completion_transformation.handler import (
|
|||
)
|
||||
from litellm.responses.utils import ResponsesAPIRequestUtils
|
||||
from litellm.types.llms.openai import (
|
||||
PromptObject,
|
||||
Reasoning,
|
||||
ResponseIncludable,
|
||||
ResponseInputParam,
|
||||
|
|
@ -96,6 +97,7 @@ async def aresponses(
|
|||
include: Optional[List[ResponseIncludable]] = None,
|
||||
instructions: Optional[str] = None,
|
||||
max_output_tokens: Optional[int] = None,
|
||||
prompt: Optional[PromptObject] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
parallel_tool_calls: Optional[bool] = None,
|
||||
previous_response_id: Optional[str] = None,
|
||||
|
|
@ -141,6 +143,7 @@ async def aresponses(
|
|||
include=include,
|
||||
instructions=instructions,
|
||||
max_output_tokens=max_output_tokens,
|
||||
prompt=prompt,
|
||||
metadata=metadata,
|
||||
parallel_tool_calls=parallel_tool_calls,
|
||||
previous_response_id=previous_response_id,
|
||||
|
|
@ -197,6 +200,7 @@ def responses(
|
|||
include: Optional[List[ResponseIncludable]] = None,
|
||||
instructions: Optional[str] = None,
|
||||
max_output_tokens: Optional[int] = None,
|
||||
prompt: Optional[PromptObject] = None,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
parallel_tool_calls: Optional[bool] = None,
|
||||
previous_response_id: Optional[str] = None,
|
||||
|
|
@ -255,11 +259,11 @@ def responses(
|
|||
)
|
||||
|
||||
# get provider config
|
||||
responses_api_provider_config: Optional[
|
||||
BaseResponsesAPIConfig
|
||||
] = ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=model,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
responses_api_provider_config: Optional[BaseResponsesAPIConfig] = (
|
||||
ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=model,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
)
|
||||
|
||||
local_vars.update(kwargs)
|
||||
|
|
@ -449,11 +453,11 @@ def delete_responses(
|
|||
raise ValueError("custom_llm_provider is required but passed as None")
|
||||
|
||||
# get provider config
|
||||
responses_api_provider_config: Optional[
|
||||
BaseResponsesAPIConfig
|
||||
] = ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=None,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
responses_api_provider_config: Optional[BaseResponsesAPIConfig] = (
|
||||
ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=None,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
)
|
||||
|
||||
if responses_api_provider_config is None:
|
||||
|
|
@ -628,11 +632,11 @@ def get_responses(
|
|||
raise ValueError("custom_llm_provider is required but passed as None")
|
||||
|
||||
# get provider config
|
||||
responses_api_provider_config: Optional[
|
||||
BaseResponsesAPIConfig
|
||||
] = ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=None,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
responses_api_provider_config: Optional[BaseResponsesAPIConfig] = (
|
||||
ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=None,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
)
|
||||
|
||||
if responses_api_provider_config is None:
|
||||
|
|
@ -784,11 +788,11 @@ def list_input_items(
|
|||
if custom_llm_provider is None:
|
||||
raise ValueError("custom_llm_provider is required but passed as None")
|
||||
|
||||
responses_api_provider_config: Optional[
|
||||
BaseResponsesAPIConfig
|
||||
] = ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=None,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
responses_api_provider_config: Optional[BaseResponsesAPIConfig] = (
|
||||
ProviderConfigManager.get_provider_responses_api_config(
|
||||
model=None,
|
||||
provider=litellm.LlmProviders(custom_llm_provider),
|
||||
)
|
||||
)
|
||||
|
||||
if responses_api_provider_config is None:
|
||||
|
|
|
|||
|
|
@ -930,6 +930,19 @@ class ComputerToolParam(TypedDict, total=False):
|
|||
ALL_RESPONSES_API_TOOL_PARAMS = Union[ToolParam, ComputerToolParam]
|
||||
|
||||
|
||||
class PromptObject(TypedDict, total=False):
|
||||
"""Reference to a stored prompt template."""
|
||||
|
||||
id: Required[str]
|
||||
"""The unique identifier of the prompt template to use."""
|
||||
|
||||
variables: Optional[Dict]
|
||||
"""Variables to substitute into the prompt template."""
|
||||
|
||||
version: Optional[str]
|
||||
"""Optional version of the prompt template."""
|
||||
|
||||
|
||||
class ResponsesAPIOptionalRequestParams(TypedDict, total=False):
|
||||
"""TypedDict for Optional parameters supported by the responses API."""
|
||||
|
||||
|
|
@ -950,6 +963,7 @@ class ResponsesAPIOptionalRequestParams(TypedDict, total=False):
|
|||
top_p: Optional[float]
|
||||
truncation: Optional[Literal["auto", "disabled"]]
|
||||
user: Optional[str]
|
||||
prompt: Optional[PromptObject]
|
||||
|
||||
|
||||
class ResponsesAPIRequestParams(ResponsesAPIOptionalRequestParams, total=False):
|
||||
|
|
|
|||
|
|
@ -689,6 +689,80 @@ async def test_openai_responses_litellm_router_with_metadata():
|
|||
mock_post.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_openai_responses_litellm_router_with_prompt():
|
||||
"""Test that prompt object is passed through the Router for responses API"""
|
||||
|
||||
prompt_obj = {
|
||||
"id": "pmpt_abc123",
|
||||
"version": "2",
|
||||
"variables": {"random_variable": "ishaan_from_litellm"},
|
||||
}
|
||||
|
||||
mock_response = {
|
||||
"id": "resp_123",
|
||||
"object": "response",
|
||||
"created_at": 1741476542,
|
||||
"status": "completed",
|
||||
"model": "gpt-4o",
|
||||
"output": [],
|
||||
"parallel_tool_calls": True,
|
||||
"usage": {"input_tokens": 0, "output_tokens": 0, "total_tokens": 0},
|
||||
"text": {"format": {"type": "text"}},
|
||||
"error": None,
|
||||
"incomplete_details": None,
|
||||
"instructions": None,
|
||||
"metadata": {},
|
||||
"temperature": 1.0,
|
||||
"tool_choice": "auto",
|
||||
"tools": [],
|
||||
"top_p": 1.0,
|
||||
"max_output_tokens": None,
|
||||
"previous_response_id": None,
|
||||
"reasoning": {"effort": None, "summary": None},
|
||||
"truncation": "disabled",
|
||||
"user": None,
|
||||
}
|
||||
|
||||
class MockResponse:
|
||||
def __init__(self, json_data, status_code):
|
||||
self._json_data = json_data
|
||||
self.status_code = status_code
|
||||
self.text = str(json_data)
|
||||
|
||||
def json(self):
|
||||
return self._json_data
|
||||
|
||||
with patch(
|
||||
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.post",
|
||||
new_callable=AsyncMock,
|
||||
) as mock_post:
|
||||
mock_post.return_value = MockResponse(mock_response, 200)
|
||||
|
||||
litellm._turn_on_debug()
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt4o-special-alias",
|
||||
"litellm_params": {
|
||||
"model": "gpt-4o",
|
||||
"api_key": "fake-key",
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
await router.aresponses(
|
||||
model="gpt4o-special-alias",
|
||||
input="Hello",
|
||||
prompt=prompt_obj,
|
||||
)
|
||||
|
||||
request_body = mock_post.call_args.kwargs["json"]
|
||||
assert request_body["prompt"] == prompt_obj
|
||||
mock_post.assert_called_once()
|
||||
|
||||
|
||||
def test_bad_request_bad_param_error():
|
||||
"""Raise a BadRequestError when an invalid parameter value is provided"""
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ class TestResponsesAPIRequestUtils:
|
|||
model = "gpt-4o"
|
||||
config = OpenAIResponsesAPIConfig()
|
||||
optional_params = ResponsesAPIOptionalRequestParams(
|
||||
{"temperature": 0.7, "max_output_tokens": 100}
|
||||
{
|
||||
"temperature": 0.7,
|
||||
"max_output_tokens": 100,
|
||||
"prompt": {"id": "pmpt_123"},
|
||||
}
|
||||
)
|
||||
|
||||
# Execute
|
||||
|
|
@ -41,6 +45,8 @@ class TestResponsesAPIRequestUtils:
|
|||
assert result["temperature"] == 0.7
|
||||
assert "max_output_tokens" in result
|
||||
assert result["max_output_tokens"] == 100
|
||||
assert "prompt" in result
|
||||
assert result["prompt"] == {"id": "pmpt_123"}
|
||||
|
||||
def test_get_optional_params_responses_api_unsupported_param(self):
|
||||
"""Test that unsupported parameters raise an error"""
|
||||
|
|
@ -68,6 +74,7 @@ class TestResponsesAPIRequestUtils:
|
|||
params = {
|
||||
"temperature": 0.7,
|
||||
"max_output_tokens": 100,
|
||||
"prompt": {"id": "pmpt_456"},
|
||||
"invalid_param": "value",
|
||||
"model": "gpt-4o", # This is not in ResponsesAPIOptionalRequestParams
|
||||
}
|
||||
|
|
@ -84,6 +91,7 @@ class TestResponsesAPIRequestUtils:
|
|||
assert "model" not in result
|
||||
assert result["temperature"] == 0.7
|
||||
assert result["max_output_tokens"] == 100
|
||||
assert result["prompt"] == {"id": "pmpt_456"}
|
||||
|
||||
def test_decode_previous_response_id_to_original_previous_response_id(self):
|
||||
"""Test decoding a LiteLLM encoded previous_response_id to the original previous_response_id"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue