diff --git a/docs/_static/llama-stack-spec.html b/docs/_static/llama-stack-spec.html index 775eb93b3..4f0dbe612 100644 --- a/docs/_static/llama-stack-spec.html +++ b/docs/_static/llama-stack-spec.html @@ -817,6 +817,90 @@ ] } }, + "/v1/openai/v1/responses/{response_id}": { + "get": { + "responses": { + "200": { + "description": "An OpenAIResponseObject.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenAIResponseObject" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest400" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests429" + }, + "500": { + "$ref": "#/components/responses/InternalServerError500" + }, + "default": { + "$ref": "#/components/responses/DefaultError" + } + }, + "tags": [ + "Agents" + ], + "description": "Retrieve an OpenAI response by its ID.", + "parameters": [ + { + "name": "response_id", + "in": "path", + "description": "The ID of the OpenAI response to retrieve.", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "delete": { + "responses": { + "200": { + "description": "An OpenAIDeleteResponseObject", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenAIDeleteResponseObject" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest400" + }, + "429": { + "$ref": "#/components/responses/TooManyRequests429" + }, + "500": { + "$ref": "#/components/responses/InternalServerError500" + }, + "default": { + "$ref": "#/components/responses/DefaultError" + } + }, + "tags": [ + "Agents" + ], + "description": "Delete an OpenAI response by its ID.", + "parameters": [ + { + "name": "response_id", + "in": "path", + "description": "The ID of the OpenAI response to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ] + } + }, "/v1/inference/embeddings": { "post": { "responses": { @@ -1284,49 +1368,6 @@ ] } }, - "/v1/openai/v1/responses/{response_id}": { - "get": { - "responses": { - "200": { - "description": "An OpenAIResponseObject.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OpenAIResponseObject" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest400" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests429" - }, - "500": { - "$ref": "#/components/responses/InternalServerError500" - }, - "default": { - "$ref": "#/components/responses/DefaultError" - } - }, - "tags": [ - "Agents" - ], - "description": "Retrieve an OpenAI response by its ID.", - "parameters": [ - { - "name": "response_id", - "in": "path", - "description": "The ID of the OpenAI response to retrieve.", - "required": true, - "schema": { - "type": "string" - } - } - ] - } - }, "/v1/scoring-functions/{scoring_fn_id}": { "get": { "responses": { @@ -8170,6 +8211,30 @@ ], "title": "OpenAIResponseObjectStreamResponseWebSearchCallSearching" }, + "OpenAIDeleteResponseObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "object": { + "type": "string", + "const": "response", + "default": "response" + }, + "deleted": { + "type": "boolean", + "default": true + } + }, + "additionalProperties": false, + "required": [ + "id", + "object", + "deleted" + ], + "title": "OpenAIDeleteResponseObject" + }, "EmbeddingsRequest": { "type": "object", "properties": { diff --git a/docs/_static/llama-stack-spec.yaml b/docs/_static/llama-stack-spec.yaml index ef6b5d70a..389ddb212 100644 --- a/docs/_static/llama-stack-spec.yaml +++ b/docs/_static/llama-stack-spec.yaml @@ -558,6 +558,64 @@ paths: required: true schema: type: string + /v1/openai/v1/responses/{response_id}: + get: + responses: + '200': + description: An OpenAIResponseObject. + content: + application/json: + schema: + $ref: '#/components/schemas/OpenAIResponseObject' + '400': + $ref: '#/components/responses/BadRequest400' + '429': + $ref: >- + #/components/responses/TooManyRequests429 + '500': + $ref: >- + #/components/responses/InternalServerError500 + default: + $ref: '#/components/responses/DefaultError' + tags: + - Agents + description: Retrieve an OpenAI response by its ID. + parameters: + - name: response_id + in: path + description: >- + The ID of the OpenAI response to retrieve. + required: true + schema: + type: string + delete: + responses: + '200': + description: An OpenAIDeleteResponseObject + content: + application/json: + schema: + $ref: '#/components/schemas/OpenAIDeleteResponseObject' + '400': + $ref: '#/components/responses/BadRequest400' + '429': + $ref: >- + #/components/responses/TooManyRequests429 + '500': + $ref: >- + #/components/responses/InternalServerError500 + default: + $ref: '#/components/responses/DefaultError' + tags: + - Agents + description: Delete an OpenAI response by its ID. + parameters: + - name: response_id + in: path + description: The ID of the OpenAI response to delete. + required: true + schema: + type: string /v1/inference/embeddings: post: responses: @@ -883,36 +941,6 @@ paths: required: true schema: type: string - /v1/openai/v1/responses/{response_id}: - get: - responses: - '200': - description: An OpenAIResponseObject. - content: - application/json: - schema: - $ref: '#/components/schemas/OpenAIResponseObject' - '400': - $ref: '#/components/responses/BadRequest400' - '429': - $ref: >- - #/components/responses/TooManyRequests429 - '500': - $ref: >- - #/components/responses/InternalServerError500 - default: - $ref: '#/components/responses/DefaultError' - tags: - - Agents - description: Retrieve an OpenAI response by its ID. - parameters: - - name: response_id - in: path - description: >- - The ID of the OpenAI response to retrieve. - required: true - schema: - type: string /v1/scoring-functions/{scoring_fn_id}: get: responses: @@ -5782,6 +5810,24 @@ components: - type title: >- OpenAIResponseObjectStreamResponseWebSearchCallSearching + OpenAIDeleteResponseObject: + type: object + properties: + id: + type: string + object: + type: string + const: response + default: response + deleted: + type: boolean + default: true + additionalProperties: false + required: + - id + - object + - deleted + title: OpenAIDeleteResponseObject EmbeddingsRequest: type: object properties: diff --git a/docs/openapi_generator/pyopenapi/utility.py b/docs/openapi_generator/pyopenapi/utility.py index 7e54c6fbb..57f92403d 100644 --- a/docs/openapi_generator/pyopenapi/utility.py +++ b/docs/openapi_generator/pyopenapi/utility.py @@ -156,7 +156,7 @@ def _validate_api_delete_method_returns_none(method) -> str | None: # Allow OpenAI endpoints to return response objects since they follow OpenAI specification method_name = getattr(method, '__name__', '') - if method_name.startswith('openai_'): + if method_name.__contains__('openai_'): return None if return_type is not None and return_type is not type(None): diff --git a/llama_stack/apis/agents/agents.py b/llama_stack/apis/agents/agents.py index cc4ee0648..4de953a7c 100644 --- a/llama_stack/apis/agents/agents.py +++ b/llama_stack/apis/agents/agents.py @@ -33,6 +33,7 @@ from llama_stack.schema_utils import json_schema_type, register_schema, webmetho from .openai_responses import ( ListOpenAIResponseInputItem, ListOpenAIResponseObject, + OpenAIDeleteResponseObject, OpenAIResponseInput, OpenAIResponseInputTool, OpenAIResponseObject, @@ -656,3 +657,12 @@ class Agents(Protocol): :returns: An ListOpenAIResponseInputItem. """ ... + + @webmethod(route="/openai/v1/responses/{response_id}", method="DELETE") + async def delete_openai_response(self, response_id: str) -> OpenAIDeleteResponseObject: + """Delete an OpenAI response by its ID. + + :param response_id: The ID of the OpenAI response to delete. + :returns: An OpenAIDeleteResponseObject + """ + ... diff --git a/llama_stack/apis/agents/openai_responses.py b/llama_stack/apis/agents/openai_responses.py index 35b3d5ace..8f8d7ffa7 100644 --- a/llama_stack/apis/agents/openai_responses.py +++ b/llama_stack/apis/agents/openai_responses.py @@ -173,6 +173,13 @@ class OpenAIResponseObject(BaseModel): user: str | None = None +@json_schema_type +class OpenAIDeleteResponseObject(BaseModel): + id: str + object: Literal["response"] = "response" + deleted: bool = True + + @json_schema_type class OpenAIResponseObjectStreamResponseCreated(BaseModel): response: OpenAIResponseObject diff --git a/llama_stack/providers/inline/agents/meta_reference/agents.py b/llama_stack/providers/inline/agents/meta_reference/agents.py index ea3c5da97..e510a1e2b 100644 --- a/llama_stack/providers/inline/agents/meta_reference/agents.py +++ b/llama_stack/providers/inline/agents/meta_reference/agents.py @@ -358,3 +358,6 @@ class MetaReferenceAgentsImpl(Agents): return await self.openai_responses_impl.list_openai_response_input_items( response_id, after, before, include, limit, order ) + + async def delete_openai_response(self, response_id: str) -> None: + return await self.openai_responses_impl.delete_openai_response(response_id) diff --git a/llama_stack/providers/inline/agents/meta_reference/openai_responses.py b/llama_stack/providers/inline/agents/meta_reference/openai_responses.py index 0ff6dc2c5..9a4e165b1 100644 --- a/llama_stack/providers/inline/agents/meta_reference/openai_responses.py +++ b/llama_stack/providers/inline/agents/meta_reference/openai_responses.py @@ -18,6 +18,7 @@ from llama_stack.apis.agents.openai_responses import ( AllowedToolsFilter, ListOpenAIResponseInputItem, ListOpenAIResponseObject, + OpenAIDeleteResponseObject, OpenAIResponseInput, OpenAIResponseInputFunctionToolCallOutput, OpenAIResponseInputMessageContent, @@ -564,6 +565,9 @@ class OpenAIResponsesImpl: input=input, ) + async def delete_openai_response(self, response_id: str) -> OpenAIDeleteResponseObject: + return await self.responses_store.delete_response_object(response_id) + async def _convert_response_tools_to_chat_tools( self, tools: list[OpenAIResponseInputTool] ) -> tuple[ diff --git a/llama_stack/providers/utils/responses/responses_store.py b/llama_stack/providers/utils/responses/responses_store.py index 15354e3e2..13d5bf33b 100644 --- a/llama_stack/providers/utils/responses/responses_store.py +++ b/llama_stack/providers/utils/responses/responses_store.py @@ -9,6 +9,7 @@ from llama_stack.apis.agents import ( from llama_stack.apis.agents.openai_responses import ( ListOpenAIResponseInputItem, ListOpenAIResponseObject, + OpenAIDeleteResponseObject, OpenAIResponseInput, OpenAIResponseObject, OpenAIResponseObjectWithInput, @@ -98,6 +99,13 @@ class ResponsesStore: raise ValueError(f"Response with id {response_id} not found") from None return OpenAIResponseObjectWithInput(**row["response_object"]) + async def delete_response_object(self, response_id: str) -> OpenAIDeleteResponseObject: + row = await self.sql_store.fetch_one("openai_responses", where={"id": response_id}) + if not row: + raise ValueError(f"Response with id {response_id} not found") + await self.sql_store.delete("openai_responses", where={"id": response_id}) + return OpenAIDeleteResponseObject(id=response_id) + async def list_response_input_items( self, response_id: str, diff --git a/tests/integration/agents/test_openai_responses.py b/tests/integration/agents/test_openai_responses.py index 26eac527b..41359ce89 100644 --- a/tests/integration/agents/test_openai_responses.py +++ b/tests/integration/agents/test_openai_responses.py @@ -4,7 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. import pytest -from openai import OpenAI +from openai import BadRequestError, OpenAI from llama_stack.distribution.library_client import LlamaStackAsLibraryClient @@ -92,6 +92,13 @@ def test_responses_store(openai_client, client_with_models, text_model_id, strea if output_type == "message": assert retrieved_response.output[0].content[0].text == content + # Delete the response + delete_response = client.responses.delete(response_id) + assert delete_response is None + + with pytest.raises(BadRequestError): + client.responses.retrieve(response_id) + def test_list_response_input_items(openai_client, client_with_models, text_model_id): """Test the new list_openai_response_input_items endpoint."""