diff --git a/README.md b/README.md index d8542dc..1bb4113 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ Pytest plugin for automatically mocking OpenAI requests. Powered by [RESPX](http [![sdk support](https://img.shields.io/badge/SDK_Support-v1.32+-white?logo=openai&logoColor=black&labelColor=white)](https://github.com/openai/openai-python) -> [!NOTE] -> For working with OpenAI Python SDK versions `>=1.25,<1.32` please use version [`v0.6`](https://github.com/mharrisb1/openai-responses-python/releases/tag/v0.6.1) - ## Supported Endpoints - [Chat](https://platform.openai.com/docs/api-reference/chat) diff --git a/examples/test_langchain_openai.py b/examples/test_langchain_openai.py new file mode 100644 index 0000000..16f466b --- /dev/null +++ b/examples/test_langchain_openai.py @@ -0,0 +1,41 @@ +from langchain_openai import ChatOpenAI +from pydantic.v1 import SecretStr + +import openai_responses +from openai_responses import OpenAIMock + + +@openai_responses.mock() +def test_langchain_chat_openai_invoke(openai_mock: OpenAIMock): + openai_mock.chat.completions.create.response = { + "choices": [ + { + "index": 0, + "finish_reason": "stop", + "message": { + "content": "J'adore la programmation.", + "role": "assistant", + }, + } + ] + } + + llm = ChatOpenAI( + name="My Custom Chatbot", + model="gpt-4o", + temperature=0, + max_tokens=None, + timeout=None, + max_retries=2, + api_key=SecretStr("sk-fake123"), + ) + + messages = [ + ( + "system", + "You are a helpful assistant that translates English to French. Translate the user sentence.", + ), + ("human", "I love programming."), + ] + ai_msg = llm.invoke(messages) + assert ai_msg.content == "J'adore la programmation." # type: ignore diff --git a/examples/test_parsed_chat_completion.py b/examples/test_parsed_chat_completion.py new file mode 100644 index 0000000..2ce9c86 --- /dev/null +++ b/examples/test_parsed_chat_completion.py @@ -0,0 +1,112 @@ +from typing import Optional +from datetime import datetime + +import openai +from pydantic import BaseModel + +import openai_responses +from openai_responses import OpenAIMock + + +class CalendarEvent(BaseModel): + name: str + date: str + participants: list[str] + + +@openai_responses.mock() +def test_create_parsed_chat_completion_with_response_format(openai_mock: OpenAIMock): + openai_mock.beta.chat.completions.create.response = { + "choices": [ + { + "index": 0, + "finish_reason": "stop", + "message": { + "content": CalendarEvent( + name="Example Event", + date=datetime.now().strftime("%Y-%m-%d"), + participants=[ + "Alice", + "Bob", + ], + ).model_dump_json(), + "role": "assistant", + }, + } + ] + } + + client = openai.Client(api_key="sk-fake123") + + completion = client.beta.chat.completions.parse( + model="gpt-4o-2024-08-06", + messages=[ + {"role": "system", "content": "Extract the event information."}, + { + "role": "user", + "content": "Alice and Bob are going to a science fair on today.", + }, + ], + response_format=CalendarEvent, + ) + + event = completion.choices[0].message.parsed + assert event + assert event.name == "Example Event" + assert datetime.strptime(event.date, "%Y-%m-%d").date() == datetime.now().date() + assert len(event.participants) == 2 + + +@openai_responses.mock() +def test_create_parsed_chat_completion_with_tools(openai_mock: OpenAIMock): + openai_mock.beta.chat.completions.create.response = { + "choices": [ + { + "index": 0, + "finish_reason": "stop", + "message": { + "role": "assistant", + "tool_calls": [ + { + "id": "call_abc123", + "type": "function", + "function": { + "arguments": CalendarEvent( + name="Example Event", + date=datetime.now().strftime("%Y-%m-%d"), + participants=[ + "Alice", + "Bob", + ], + ).model_dump_json(), + "name": "CalendarEvent", + }, + } + ], + }, + } + ] + } + + client = openai.Client(api_key="sk-fake123") + + completion = client.beta.chat.completions.parse( + model="gpt-4o-2024-08-06", + messages=[ + {"role": "system", "content": "Extract the event information."}, + { + "role": "user", + "content": "Alice and Bob are going to a science fair on today.", + }, + ], + tools=[openai.pydantic_function_tool(CalendarEvent)], + ) + + event: Optional[CalendarEvent] = ( + completion.choices[0].message.tool_calls[0].function.parsed_arguments # type: ignore + ) + + assert event + assert event.name == "Example Event" + assert datetime.strptime(event.date, "%Y-%m-%d").date() == datetime.now().date() + assert len(event.participants) == 2 diff --git a/pyproject.toml b/pyproject.toml index 6136db8..2bdf406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ pytest = "^8.1.1" pytest-asyncio = "^0.23.6" mkdocs-material = "^9.5.18" tox = "^4.14.2" +langchain-openai = "^0.1.20" [build-system] requires = ["poetry-core"] diff --git a/src/openai_responses/_routes/__init__.py b/src/openai_responses/_routes/__init__.py index 4c05583..cfeb5b6 100644 --- a/src/openai_responses/_routes/__init__.py +++ b/src/openai_responses/_routes/__init__.py @@ -12,62 +12,10 @@ FileRetrieveContentRoute, ) from .models import ModelListRoute, ModelRetrieveRoute -from .assistants import ( - AssistantCreateRoute, - AssistantListRoute, - AssistantRetrieveRoute, - AssistantUpdateRoute, - AssistantDeleteRoute, -) -from .threads import ( - ThreadCreateRoute, - ThreadRetrieveRoute, - ThreadUpdateRoute, - ThreadDeleteRoute, -) -from .messages import ( - MessageCreateRoute, - MessageListRoute, - MessageRetrieveRoute, - MessageUpdateRoute, - MessageDeleteRoute, -) -from .runs import ( - RunCreateRoute, - ThreadCreateAndRun, - RunListRoute, - RunRetrieveRoute, - RunUpdateRoute, - RunSubmitToolOutputsRoute, - RunCancelRoute, -) -from .run_steps import RunStepListRoute, RunStepRetrieveRoute -from .vector_stores import ( - VectorStoreCreateRoute, - VectorStoreListRoute, - VectorStoreRetrieveRoute, - VectorStoreUpdateRoute, - VectorStoreDeleteRoute, -) -from .vector_store_files import ( - VectorStoreFileCreateRoute, - VectorStoreFileListRoute, - VectorStoreFileRetrieveRoute, - VectorStoreFileDeleteRoute, -) -from .vector_store_file_batches import ( - VectorStoreFileBatchCreateRoute, - VectorStoreFileBatchRetrieveRoute, - VectorStoreFileBatchCancelRoute, - VectorStoreFileBatchListFilesRoute, -) -__all__ = [ - "BetaRoutes", - "ChatRoutes", - "EmbeddingsRoutes", - "FileRoutes", -] +from .beta import BetaRoutes + +__all__ = ["BetaRoutes", "ChatRoutes", "EmbeddingsRoutes", "FileRoutes", "ModelRoutes"] class ChatRoutes: @@ -98,86 +46,3 @@ class ModelRoutes: def __init__(self, router: respx.MockRouter, state: StateStore) -> None: self.list = ModelListRoute(router, state) self.retrieve = ModelRetrieveRoute(router, state) - - -class BetaRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.assistants = AssistantsRoutes(router, state) - self.threads = ThreadRoutes(router, state) - self.vector_stores = VectorStoreRoutes(router, state) - - -class AssistantsRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = AssistantCreateRoute(router, state) - self.list = AssistantListRoute(router, state) - self.retrieve = AssistantRetrieveRoute(router, state) - self.update = AssistantUpdateRoute(router, state) - self.delete = AssistantDeleteRoute(router, state) - - -class ThreadRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = ThreadCreateRoute(router, state) - self.retrieve = ThreadRetrieveRoute(router, state) - self.update = ThreadUpdateRoute(router, state) - self.delete = ThreadDeleteRoute(router, state) - self.create_and_run = ThreadCreateAndRun(router, state) - - self.messages = MessageRoutes(router, state) - self.runs = RunRoutes(router, state) - - -class MessageRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = MessageCreateRoute(router, state) - self.list = MessageListRoute(router, state) - self.retrieve = MessageRetrieveRoute(router, state) - self.update = MessageUpdateRoute(router, state) - self.delete = MessageDeleteRoute(router, state) - - -class RunRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = RunCreateRoute(router, state) - self.list = RunListRoute(router, state) - self.retrieve = RunRetrieveRoute(router, state) - self.update = RunUpdateRoute(router, state) - self.submit_tool_outputs = RunSubmitToolOutputsRoute(router, state) - self.cancel = RunCancelRoute(router, state) - - self.steps = RunStepRoutes(router, state) - - -class RunStepRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.list = RunStepListRoute(router, state) - self.retrieve = RunStepRetrieveRoute(router, state) - - -class VectorStoreRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = VectorStoreCreateRoute(router, state) - self.list = VectorStoreListRoute(router, state) - self.retrieve = VectorStoreRetrieveRoute(router, state) - self.update = VectorStoreUpdateRoute(router, state) - self.delete = VectorStoreDeleteRoute(router, state) - - self.files = VectorStoreFileRoutes(router, state) - self.file_batches = VectorStoreFileBatchRoutes(router, state) - - -class VectorStoreFileRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = VectorStoreFileCreateRoute(router, state) - self.list = VectorStoreFileListRoute(router, state) - self.retrieve = VectorStoreFileRetrieveRoute(router, state) - self.delete = VectorStoreFileDeleteRoute(router, state) - - -class VectorStoreFileBatchRoutes: - def __init__(self, router: respx.MockRouter, state: StateStore) -> None: - self.create = VectorStoreFileBatchCreateRoute(router, state) - self.retrieve = VectorStoreFileBatchRetrieveRoute(router, state) - self.cancel = VectorStoreFileBatchCancelRoute(router, state) - self.list_files = VectorStoreFileBatchListFilesRoute(router, state) diff --git a/src/openai_responses/_routes/beta/__init__.py b/src/openai_responses/_routes/beta/__init__.py new file mode 100644 index 0000000..f34ecfa --- /dev/null +++ b/src/openai_responses/_routes/beta/__init__.py @@ -0,0 +1,154 @@ +import respx + +from ...stores import StateStore + + +from .assistants import ( + AssistantCreateRoute, + AssistantListRoute, + AssistantRetrieveRoute, + AssistantUpdateRoute, + AssistantDeleteRoute, +) +from .threads import ( + ThreadCreateRoute, + ThreadRetrieveRoute, + ThreadUpdateRoute, + ThreadDeleteRoute, +) +from .messages import ( + MessageCreateRoute, + MessageListRoute, + MessageRetrieveRoute, + MessageUpdateRoute, + MessageDeleteRoute, +) +from .runs import ( + RunCreateRoute, + ThreadCreateAndRun, + RunListRoute, + RunRetrieveRoute, + RunUpdateRoute, + RunSubmitToolOutputsRoute, + RunCancelRoute, +) +from .run_steps import RunStepListRoute, RunStepRetrieveRoute +from .vector_stores import ( + VectorStoreCreateRoute, + VectorStoreListRoute, + VectorStoreRetrieveRoute, + VectorStoreUpdateRoute, + VectorStoreDeleteRoute, +) +from .vector_store_files import ( + VectorStoreFileCreateRoute, + VectorStoreFileListRoute, + VectorStoreFileRetrieveRoute, + VectorStoreFileDeleteRoute, +) +from .vector_store_file_batches import ( + VectorStoreFileBatchCreateRoute, + VectorStoreFileBatchRetrieveRoute, + VectorStoreFileBatchCancelRoute, + VectorStoreFileBatchListFilesRoute, +) + +from .chat import ParsedChatCompletionsCreateRoute + + +__all__ = ["BetaRoutes"] + + +class BetaRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.assistants = AssistantsRoutes(router, state) + self.threads = ThreadRoutes(router, state) + self.vector_stores = VectorStoreRoutes(router, state) + + self.chat = ChatRoutes(router) + + +class AssistantsRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = AssistantCreateRoute(router, state) + self.list = AssistantListRoute(router, state) + self.retrieve = AssistantRetrieveRoute(router, state) + self.update = AssistantUpdateRoute(router, state) + self.delete = AssistantDeleteRoute(router, state) + + +class ThreadRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = ThreadCreateRoute(router, state) + self.retrieve = ThreadRetrieveRoute(router, state) + self.update = ThreadUpdateRoute(router, state) + self.delete = ThreadDeleteRoute(router, state) + self.create_and_run = ThreadCreateAndRun(router, state) + + self.messages = MessageRoutes(router, state) + self.runs = RunRoutes(router, state) + + +class MessageRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = MessageCreateRoute(router, state) + self.list = MessageListRoute(router, state) + self.retrieve = MessageRetrieveRoute(router, state) + self.update = MessageUpdateRoute(router, state) + self.delete = MessageDeleteRoute(router, state) + + +class RunRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = RunCreateRoute(router, state) + self.list = RunListRoute(router, state) + self.retrieve = RunRetrieveRoute(router, state) + self.update = RunUpdateRoute(router, state) + self.submit_tool_outputs = RunSubmitToolOutputsRoute(router, state) + self.cancel = RunCancelRoute(router, state) + + self.steps = RunStepRoutes(router, state) + + +class RunStepRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.list = RunStepListRoute(router, state) + self.retrieve = RunStepRetrieveRoute(router, state) + + +class VectorStoreRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = VectorStoreCreateRoute(router, state) + self.list = VectorStoreListRoute(router, state) + self.retrieve = VectorStoreRetrieveRoute(router, state) + self.update = VectorStoreUpdateRoute(router, state) + self.delete = VectorStoreDeleteRoute(router, state) + + self.files = VectorStoreFileRoutes(router, state) + self.file_batches = VectorStoreFileBatchRoutes(router, state) + + +class VectorStoreFileRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = VectorStoreFileCreateRoute(router, state) + self.list = VectorStoreFileListRoute(router, state) + self.retrieve = VectorStoreFileRetrieveRoute(router, state) + self.delete = VectorStoreFileDeleteRoute(router, state) + + +class VectorStoreFileBatchRoutes: + def __init__(self, router: respx.MockRouter, state: StateStore) -> None: + self.create = VectorStoreFileBatchCreateRoute(router, state) + self.retrieve = VectorStoreFileBatchRetrieveRoute(router, state) + self.cancel = VectorStoreFileBatchCancelRoute(router, state) + self.list_files = VectorStoreFileBatchListFilesRoute(router, state) + + +class ChatRoutes: + def __init__(self, router: respx.MockRouter) -> None: + self.completions = ParsedChatCompletionRoutes(router) + + +class ParsedChatCompletionRoutes: + def __init__(self, router: respx.MockRouter) -> None: + self.create = ParsedChatCompletionsCreateRoute(router) diff --git a/src/openai_responses/_routes/assistants.py b/src/openai_responses/_routes/beta/assistants.py similarity index 92% rename from src/openai_responses/_routes/assistants.py rename to src/openai_responses/_routes/beta/assistants.py index eb363fc..a80e440 100644 --- a/src/openai_responses/_routes/assistants.py +++ b/src/openai_responses/_routes/beta/assistants.py @@ -11,21 +11,21 @@ from openai.types.beta.assistant_create_params import AssistantCreateParams from openai.types.beta.assistant_update_params import AssistantUpdateParams -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..helpers.builders.vector_stores import vector_store_from_create_request -from ..helpers.builders.vector_store_files import vector_store_file_from_create_request -from ..helpers.mergers.assistants import merge_assistant_with_partial +from ...helpers.builders.vector_stores import vector_store_from_create_request +from ...helpers.builders.vector_store_files import vector_store_file_from_create_request +from ...helpers.mergers.assistants import merge_assistant_with_partial -from ..stores import StateStore +from ...stores import StateStore -from .._types.partials.deleted import PartialResourceDeleted -from .._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._types.partials.assistants import PartialAssistant +from ..._types.partials.deleted import PartialResourceDeleted +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage +from ..._types.partials.assistants import PartialAssistant -from .._utils.faker import faker -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ "AssistantCreateRoute", diff --git a/src/openai_responses/_routes/beta/chat.py b/src/openai_responses/_routes/beta/chat.py new file mode 100644 index 0000000..0c0197b --- /dev/null +++ b/src/openai_responses/_routes/beta/chat.py @@ -0,0 +1,39 @@ +from typing import Any, Dict + +import httpx +import respx + +from openai.types.chat.parsed_chat_completion import ParsedChatCompletion + +from .._base import StatelessRoute + +from ..._types.partials.chat import PartialParsedChatCompletion + +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_parse +from ..._utils.time import utcnow_unix_timestamp_s + +__all__ = ["ParsedChatCompletionsCreateRoute"] + + +class ParsedChatCompletionsCreateRoute( + StatelessRoute[ParsedChatCompletion[Dict[str, Any]], PartialParsedChatCompletion] +): + def __init__(self, router: respx.MockRouter) -> None: + super().__init__( + route=router.post(url__regex="/chat/completions"), + status_code=201, + ) + + @staticmethod + def _build( + partial: PartialParsedChatCompletion, + request: httpx.Request, + ) -> ParsedChatCompletion[Dict[str, Any]]: + content = json_loads(request.content) + defaults: PartialParsedChatCompletion = { + "id": partial.get("id", faker.chat.completion.id()), + "created": partial.get("created", utcnow_unix_timestamp_s()), + "object": "chat.completion", + } + return model_parse(ParsedChatCompletion[Any], defaults | partial | content) diff --git a/src/openai_responses/_routes/messages.py b/src/openai_responses/_routes/beta/messages.py similarity index 95% rename from src/openai_responses/_routes/messages.py rename to src/openai_responses/_routes/beta/messages.py index 07d6b68..c90ed2d 100644 --- a/src/openai_responses/_routes/messages.py +++ b/src/openai_responses/_routes/beta/messages.py @@ -9,15 +9,15 @@ from openai.types.beta.threads.message_deleted import MessageDeleted from openai.types.beta.threads.message_update_params import MessageUpdateParams -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..stores import StateStore -from .._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._types.partials.messages import PartialMessage, PartialMessageDeleted +from ...stores import StateStore +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage +from ..._types.partials.messages import PartialMessage, PartialMessageDeleted -from .._utils.faker import faker -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ "MessageCreateRoute", diff --git a/src/openai_responses/_routes/run_steps.py b/src/openai_responses/_routes/beta/run_steps.py similarity index 94% rename from src/openai_responses/_routes/run_steps.py rename to src/openai_responses/_routes/beta/run_steps.py index b827cd2..4fc83f6 100644 --- a/src/openai_responses/_routes/run_steps.py +++ b/src/openai_responses/_routes/beta/run_steps.py @@ -7,13 +7,13 @@ from openai.pagination import SyncCursorPage from openai.types.beta.threads.runs.run_step import RunStep -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..stores import StateStore -from .._types.partials.run_steps import PartialRunStep -from .._types.partials.sync_cursor_page import PartialSyncCursorPage +from ...stores import StateStore +from ..._types.partials.run_steps import PartialRunStep +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._utils.serde import model_dict +from ..._utils.serde import model_dict __all__ = ["RunStepListRoute", "RunStepRetrieveRoute"] diff --git a/src/openai_responses/_routes/runs.py b/src/openai_responses/_routes/beta/runs.py similarity index 95% rename from src/openai_responses/_routes/runs.py rename to src/openai_responses/_routes/beta/runs.py index 9bf95e6..55c2add 100644 --- a/src/openai_responses/_routes/runs.py +++ b/src/openai_responses/_routes/beta/runs.py @@ -11,19 +11,19 @@ from openai.types.beta.threads.run_update_params import RunUpdateParams from openai.types.beta.thread_create_and_run_params import ThreadCreateAndRunParams -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..helpers.builders.messages import message_from_create_request -from ..helpers.builders.threads import thread_from_create_request +from ...helpers.builders.messages import message_from_create_request +from ...helpers.builders.threads import thread_from_create_request -from ..stores import StateStore -from .._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._types.partials.runs import PartialRun +from ...stores import StateStore +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage +from ..._types.partials.runs import PartialRun -from .._utils.copy import model_copy -from .._utils.faker import faker -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.copy import model_copy +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ diff --git a/src/openai_responses/_routes/threads.py b/src/openai_responses/_routes/beta/threads.py similarity index 91% rename from src/openai_responses/_routes/threads.py rename to src/openai_responses/_routes/beta/threads.py index 443a605..08b7cab 100644 --- a/src/openai_responses/_routes/threads.py +++ b/src/openai_responses/_routes/beta/threads.py @@ -10,20 +10,20 @@ from openai.types.beta.thread_update_params import ThreadUpdateParams from openai.types.beta.thread_deleted import ThreadDeleted -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..helpers.builders.messages import message_from_create_request -from ..helpers.builders.vector_stores import vector_store_from_create_request -from ..helpers.builders.vector_store_files import vector_store_file_from_create_request -from ..helpers.mergers.threads import merge_thread_with_partial +from ...helpers.builders.messages import message_from_create_request +from ...helpers.builders.vector_stores import vector_store_from_create_request +from ...helpers.builders.vector_store_files import vector_store_file_from_create_request +from ...helpers.mergers.threads import merge_thread_with_partial -from ..stores import StateStore -from .._types.partials.deleted import PartialResourceDeleted -from .._types.partials.threads import PartialThread +from ...stores import StateStore +from ..._types.partials.deleted import PartialResourceDeleted +from ..._types.partials.threads import PartialThread -from .._utils.faker import faker -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ diff --git a/src/openai_responses/_routes/vector_store_file_batches.py b/src/openai_responses/_routes/beta/vector_store_file_batches.py similarity index 93% rename from src/openai_responses/_routes/vector_store_file_batches.py rename to src/openai_responses/_routes/beta/vector_store_file_batches.py index 9b14a7f..5683d87 100644 --- a/src/openai_responses/_routes/vector_store_file_batches.py +++ b/src/openai_responses/_routes/beta/vector_store_file_batches.py @@ -13,18 +13,18 @@ ) -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..helpers.builders.vector_store_files import vector_store_file_from_create_request +from ...helpers.builders.vector_store_files import vector_store_file_from_create_request -from ..stores import StateStore -from .._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._types.partials.vector_store_files import PartialVectorStoreFile -from .._types.partials.vector_store_file_batches import PartialVectorStoreFileBatch +from ...stores import StateStore +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage +from ..._types.partials.vector_store_files import PartialVectorStoreFile +from ..._types.partials.vector_store_file_batches import PartialVectorStoreFileBatch -from .._utils.faker import faker -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ "VectorStoreFileBatchCreateRoute", diff --git a/src/openai_responses/_routes/vector_store_files.py b/src/openai_responses/_routes/beta/vector_store_files.py similarity index 94% rename from src/openai_responses/_routes/vector_store_files.py rename to src/openai_responses/_routes/beta/vector_store_files.py index bb37e07..741dd83 100644 --- a/src/openai_responses/_routes/vector_store_files.py +++ b/src/openai_responses/_routes/beta/vector_store_files.py @@ -10,15 +10,15 @@ VectorStoreFileDeleted, ) -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..stores import StateStore -from .._types.partials.deleted import PartialResourceDeleted -from .._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._types.partials.vector_store_files import PartialVectorStoreFile +from ...stores import StateStore +from ..._types.partials.deleted import PartialResourceDeleted +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage +from ..._types.partials.vector_store_files import PartialVectorStoreFile -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ "VectorStoreFileCreateRoute", diff --git a/src/openai_responses/_routes/vector_stores.py b/src/openai_responses/_routes/beta/vector_stores.py similarity index 94% rename from src/openai_responses/_routes/vector_stores.py rename to src/openai_responses/_routes/beta/vector_stores.py index 2298ddf..e592fda 100644 --- a/src/openai_responses/_routes/vector_stores.py +++ b/src/openai_responses/_routes/beta/vector_stores.py @@ -11,20 +11,20 @@ from openai.types.beta.vector_store_update_params import VectorStoreUpdateParams from openai.types.beta.vector_store_deleted import VectorStoreDeleted -from ._base import StatefulRoute +from .._base import StatefulRoute -from ..helpers.builders.vector_store_files import vector_store_file_from_create_request +from ...helpers.builders.vector_store_files import vector_store_file_from_create_request -from ..stores import StateStore -from .._types.partials.sync_cursor_page import PartialSyncCursorPage -from .._types.partials.vector_stores import ( +from ...stores import StateStore +from ..._types.partials.sync_cursor_page import PartialSyncCursorPage +from ..._types.partials.vector_stores import ( PartialVectorStore, PartialVectorStoreDeleted, ) -from .._utils.faker import faker -from .._utils.serde import json_loads, model_dict, model_parse -from .._utils.time import utcnow_unix_timestamp_s +from ..._utils.faker import faker +from ..._utils.serde import json_loads, model_dict, model_parse +from ..._utils.time import utcnow_unix_timestamp_s __all__ = [ "VectorStoreCreateRoute", diff --git a/src/openai_responses/_types/partials/assistants.py b/src/openai_responses/_types/partials/assistants.py index 80b97b3..0f51c76 100644 --- a/src/openai_responses/_types/partials/assistants.py +++ b/src/openai_responses/_types/partials/assistants.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Dict, Literal, List, TypedDict, Union +from typing import Annotated, Any, Dict, Literal, List, Optional, TypedDict, Union from typing_extensions import NotRequired from openai._utils._transform import PropertyInfo @@ -6,22 +6,24 @@ __all__ = ["PartialAssistant"] -class PartialAssistantResponseFormat(TypedDict): - type: NotRequired[Literal["text", "json_object"]] - - class PartialFunctionDefinition(TypedDict): name: str description: NotRequired[str] parameters: NotRequired[Dict[str, Any]] + strict: NotRequired[bool] class PartialCodeInterpreterTool(TypedDict): type: Literal["code_interpreter"] +class PartialFileSearch(TypedDict): + max_num_results: NotRequired[int] + + class PartialFileSearchTool(TypedDict): type: Literal["file_search"] + file_search: Optional[PartialFileSearch] class PartialFunctionTool(TypedDict): @@ -29,6 +31,26 @@ class PartialFunctionTool(TypedDict): function: PartialFunctionDefinition +class PartialResponseFormatText(TypedDict): + type: Literal["text"] + + +class PartialResponseFormatJSONObject(TypedDict): + type: Literal["json_object"] + + +class PartialJSONSchema(TypedDict): + name: str + description: NotRequired[str] + schema: NotRequired[Dict[str, Any]] + strict: Optional[bool] + + +class PartialResponseFormatJSONSchema(TypedDict): + type: Literal["json_schema"] + json_schema: PartialJSONSchema + + class PartialToolResourcesCodeInterpreter(TypedDict): file_ids: NotRequired[List[str]] @@ -42,6 +64,23 @@ class PartialToolResources(TypedDict): file_search: NotRequired[PartialToolResourcesFileSearch] +PartialAssistantResponseFormat = Union[ + Literal["auto"], + PartialResponseFormatText, + PartialResponseFormatJSONObject, + PartialResponseFormatJSONSchema, +] + +PartialAssistantTool = Annotated[ + Union[ + PartialCodeInterpreterTool, + PartialFileSearchTool, + PartialFunctionTool, + ], + PropertyInfo(discriminator="type"), +] + + class PartialAssistant(TypedDict): id: NotRequired[str] created_at: NotRequired[int] @@ -51,21 +90,8 @@ class PartialAssistant(TypedDict): model: NotRequired[str] name: NotRequired[str] object: NotRequired[Literal["assistant"]] - tools: NotRequired[ - List[ - Annotated[ - Union[ - PartialCodeInterpreterTool, - PartialFileSearchTool, - PartialFunctionTool, - ], - PropertyInfo(discriminator="type"), - ] - ] - ] - response_format: NotRequired[ - Union[Literal["none", "auto"], PartialAssistantResponseFormat] - ] + tools: NotRequired[List[PartialAssistantTool]] + response_format: NotRequired[PartialAssistantResponseFormat] temperature: NotRequired[float] tool_resources: NotRequired[PartialToolResources] top_p: NotRequired[float] diff --git a/src/openai_responses/_types/partials/chat.py b/src/openai_responses/_types/partials/chat.py index 008a877..f7730ee 100644 --- a/src/openai_responses/_types/partials/chat.py +++ b/src/openai_responses/_types/partials/chat.py @@ -1,7 +1,7 @@ -from typing import List, Literal, TypedDict +from typing import Any, Dict, List, Literal, TypedDict from typing_extensions import NotRequired -__all__ = ["PartialChatCompletion"] +__all__ = ["PartialChatCompletion", "PartialParsedChatCompletion"] class PartialFunctionCall(TypedDict): @@ -15,8 +15,9 @@ class PartialToolCall(TypedDict): type: Literal["function"] -class PartialMessage(TypedDict): +class PartialChatCompletionMessage(TypedDict): content: NotRequired[str] + refusal: NotRequired[str] role: Literal["assistant"] function_call: NotRequired[PartialFunctionCall] tool_calls: NotRequired[List[PartialToolCall]] @@ -49,7 +50,7 @@ class PartialChoice(TypedDict): ] index: int logprops: NotRequired[PartialChatCompletionTokenLogprob] - message: PartialMessage + message: PartialChatCompletionMessage class PartialCompletionUsage(TypedDict): @@ -64,5 +65,18 @@ class PartialChatCompletion(TypedDict): created: NotRequired[int] model: NotRequired[str] object: NotRequired[Literal["chat.completion"]] + service_tier: NotRequired[Literal["scale", "default"]] system_fingerprint: NotRequired[str] usage: NotRequired[PartialCompletionUsage] + + +class PartialParsedChatCompletionMessage(PartialChatCompletionMessage): + parsed: NotRequired[Dict[str, Any]] + + +class PartialParsedChoice(PartialChoice): + message: PartialParsedChatCompletionMessage # type: ignore + + +class PartialParsedChatCompletion(PartialChatCompletion): + choices: NotRequired[List[PartialParsedChoice]] # type: ignore diff --git a/src/openai_responses/_types/partials/messages.py b/src/openai_responses/_types/partials/messages.py index 4601d43..b290a83 100644 --- a/src/openai_responses/_types/partials/messages.py +++ b/src/openai_responses/_types/partials/messages.py @@ -28,6 +28,16 @@ class PartialImageFileContentBlock(TypedDict): image_file: PartialImageFile +class PartialImageURL(TypedDict): + url: str + detail: NotRequired[Literal["auto", "low", "high"]] + + +class PartialImageURLContentBlock(TypedDict): + type: Literal["image_url"] + image_url: PartialImageURL + + class PartialFileCitation(TypedDict): file_id: str quote: str @@ -68,6 +78,22 @@ class PartialTextContentBlock(TypedDict): text: PartialText +class PartialRefusalContentBlock(TypedDict): + type: Literal["refusal"] + refusal: str + + +PartialMessageContent = Annotated[ + Union[ + PartialImageFileContentBlock, + PartialImageURLContentBlock, + PartialTextContentBlock, + PartialRefusalContentBlock, + ], + PropertyInfo(discriminator="type"), +] + + class PartialMessage(TypedDict): id: NotRequired[str] assistant_id: NotRequired[str] diff --git a/src/openai_responses/_types/partials/runs.py b/src/openai_responses/_types/partials/runs.py index 2af6d26..5f26a9c 100644 --- a/src/openai_responses/_types/partials/runs.py +++ b/src/openai_responses/_types/partials/runs.py @@ -1,7 +1,7 @@ -from typing import Annotated, Any, Dict, List, Literal, TypedDict, Union +from typing import Any, Dict, List, Literal, TypedDict, Union from typing_extensions import NotRequired -from openai._utils import PropertyInfo +from .assistants import PartialAssistantResponseFormat, PartialAssistantTool __all__ = ["PartialRun"] @@ -35,10 +35,6 @@ class PartialRequiredAction(TypedDict): type: Literal["submit_tool_outputs"] -class PartialAssistantResponseFormat(TypedDict): - type: NotRequired[Literal["text", "json_object"]] - - class PartialAssistantToolChoiceFunction(TypedDict): name: str @@ -96,9 +92,7 @@ class PartialRun(TypedDict): object: NotRequired[Literal["thread.run"]] parallel_tool_calls: NotRequired[bool] required_action: NotRequired[PartialRequiredAction] - response_format: NotRequired[ - Union[Literal["none", "auto"], PartialAssistantResponseFormat] - ] + response_format: NotRequired[PartialAssistantResponseFormat] started_at: NotRequired[int] status: NotRequired[ Literal[ @@ -116,18 +110,7 @@ class PartialRun(TypedDict): tool_choice: NotRequired[ Union[Literal["none", "auto", "required"], PartialAssistantToolChoice] ] - tools: NotRequired[ - List[ - Annotated[ - Union[ - PartialCodeInterpreterTool, - PartialFileSearchTool, - PartialFunctionTool, - ], - PropertyInfo(discriminator="type"), - ] - ] - ] + tools: NotRequired[List[PartialAssistantTool]] truncation_strategy: NotRequired[PartialTruncationStrategy] usage: NotRequired[PartialUsage] temperature: NotRequired[float] diff --git a/src/openai_responses/helpers/builders/messages.py b/src/openai_responses/helpers/builders/messages.py index fdda967..db23ea5 100644 --- a/src/openai_responses/helpers/builders/messages.py +++ b/src/openai_responses/helpers/builders/messages.py @@ -5,7 +5,7 @@ from openai.types.beta.threads.message import Message from ._base import _generic_builder -from ..._routes.messages import MessageCreateRoute +from ..._routes.beta.messages import MessageCreateRoute from ..._types.partials.messages import PartialMessage from ..._utils.faker import faker diff --git a/src/openai_responses/helpers/builders/runs.py b/src/openai_responses/helpers/builders/runs.py index 5fc0a71..b810659 100644 --- a/src/openai_responses/helpers/builders/runs.py +++ b/src/openai_responses/helpers/builders/runs.py @@ -5,7 +5,7 @@ from openai.types.beta.threads.run import Run from ._base import _generic_builder -from ..._routes.runs import RunCreateRoute +from ..._routes.beta.runs import RunCreateRoute from ..._types.partials.runs import PartialRun __all__ = ["run_from_create_request"] diff --git a/src/openai_responses/helpers/builders/threads.py b/src/openai_responses/helpers/builders/threads.py index 293c79b..a6130bd 100644 --- a/src/openai_responses/helpers/builders/threads.py +++ b/src/openai_responses/helpers/builders/threads.py @@ -5,7 +5,7 @@ from openai.types.beta.thread import Thread from ._base import _generic_builder -from ..._routes.threads import ThreadCreateRoute +from ..._routes.beta.threads import ThreadCreateRoute from ..._types.partials.threads import PartialThread __all__ = ["thread_from_create_request"] diff --git a/src/openai_responses/helpers/builders/vector_store_files.py b/src/openai_responses/helpers/builders/vector_store_files.py index 6004d4f..2bdaa43 100644 --- a/src/openai_responses/helpers/builders/vector_store_files.py +++ b/src/openai_responses/helpers/builders/vector_store_files.py @@ -4,7 +4,7 @@ from openai.types.beta.vector_stores.vector_store_file import VectorStoreFile from ._base import _generic_builder -from ..._routes.vector_store_files import VectorStoreFileCreateRoute +from ..._routes.beta.vector_store_files import VectorStoreFileCreateRoute from ..._types.partials.vector_store_files import PartialVectorStoreFile __all__ = ["vector_store_file_from_create_request"] diff --git a/src/openai_responses/helpers/builders/vector_stores.py b/src/openai_responses/helpers/builders/vector_stores.py index 2c37917..7ca4872 100644 --- a/src/openai_responses/helpers/builders/vector_stores.py +++ b/src/openai_responses/helpers/builders/vector_stores.py @@ -4,7 +4,7 @@ from openai.types.beta.vector_store import VectorStore from ._base import _generic_builder -from ..._routes.vector_stores import VectorStoreCreateRoute +from ..._routes.beta.vector_stores import VectorStoreCreateRoute from ..._types.partials.vector_stores import PartialVectorStore __all__ = ["vector_store_from_create_request"]