97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
979 lines
28 KiB
Python
979 lines
28 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import (
|
|
TYPE_CHECKING,
|
|
TypeVar,
|
|
Callable,
|
|
Literal,
|
|
Any,
|
|
cast,
|
|
get_origin,
|
|
get_args,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
import openai
|
|
from openai.types.chat import ChatCompletionMessageParam
|
|
|
|
from instructor.v2.core.mode import Mode
|
|
from instructor.v2.core.providers import Provider
|
|
from tenacity import (
|
|
AsyncRetrying,
|
|
Retrying,
|
|
)
|
|
from collections.abc import Generator, Iterable, Awaitable, AsyncGenerator, Coroutine
|
|
from typing_extensions import Self, overload
|
|
from instructor.v2.dsl.partial import Partial
|
|
from instructor.v2.core.hooks import Hooks, HookName
|
|
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class _ResponseBase:
|
|
@staticmethod
|
|
def _normalize_messages(
|
|
messages: str | list[ChatCompletionMessageParam] | None,
|
|
kwargs: dict[str, Any],
|
|
) -> str | list[ChatCompletionMessageParam]:
|
|
if messages is None:
|
|
if "input" not in kwargs:
|
|
raise TypeError("Either 'messages' or 'input' must be provided")
|
|
messages = kwargs.pop("input")
|
|
elif "input" in kwargs:
|
|
raise TypeError("Pass only one of 'messages' or 'input'")
|
|
|
|
if isinstance(messages, str):
|
|
return [{"role": "user", "content": messages}]
|
|
return messages
|
|
|
|
|
|
class Response(_ResponseBase):
|
|
"""Helper for responses API using a patched client."""
|
|
|
|
def __init__(
|
|
self,
|
|
client: Instructor,
|
|
):
|
|
self.client = client
|
|
|
|
@overload
|
|
def create(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
**kwargs: Any,
|
|
) -> T: ...
|
|
|
|
@overload
|
|
def create(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
**kwargs: Any,
|
|
) -> Any: ...
|
|
|
|
def create(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
**kwargs,
|
|
) -> T | Any:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create = cast(Callable[..., Any], self.client.create)
|
|
return create(
|
|
response_model=response_model,
|
|
context=context,
|
|
max_retries=max_retries,
|
|
strict=strict,
|
|
messages=messages,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_with_completion(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs: Any,
|
|
) -> tuple[T, Any]: ...
|
|
|
|
@overload
|
|
def create_with_completion(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs: Any,
|
|
) -> tuple[Any, Any]: ...
|
|
|
|
def create_with_completion(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs,
|
|
) -> tuple[T, Any]:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create_with_completion = cast(
|
|
Callable[..., tuple[T, Any]], self.client.create_with_completion
|
|
)
|
|
return create_with_completion(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_iterable(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs: Any,
|
|
) -> Generator[T, None, None]: ...
|
|
|
|
@overload
|
|
def create_iterable(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs: Any,
|
|
) -> Generator[Any, None, None]: ...
|
|
|
|
def create_iterable(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs,
|
|
) -> Generator[T, None, None]:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create_iterable = cast(
|
|
Callable[..., Generator[T, None, None]], self.client.create_iterable
|
|
)
|
|
return create_iterable(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_partial(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs: Any,
|
|
) -> Generator[T, None, None]: ...
|
|
|
|
@overload
|
|
def create_partial(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs: Any,
|
|
) -> Generator[Any, None, None]: ...
|
|
|
|
def create_partial(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | Retrying = 3,
|
|
**kwargs,
|
|
) -> Generator[T, None, None]:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create_partial = cast(
|
|
Callable[..., Generator[T, None, None]], self.client.create_partial
|
|
)
|
|
return create_partial(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class AsyncResponse(_ResponseBase):
|
|
def __init__(self, client: AsyncInstructor):
|
|
self.client = client
|
|
|
|
@overload
|
|
async def create(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
**kwargs: Any,
|
|
) -> T: ...
|
|
|
|
@overload
|
|
async def create(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
**kwargs: Any,
|
|
) -> Any: ...
|
|
|
|
async def create(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
**kwargs,
|
|
) -> T | Any:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create = cast(Callable[..., Awaitable[Any]], self.client.create)
|
|
return await create(
|
|
response_model=response_model,
|
|
context=context,
|
|
max_retries=max_retries,
|
|
strict=strict,
|
|
messages=messages,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
async def create_with_completion(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> tuple[T, Any]: ...
|
|
|
|
@overload
|
|
async def create_with_completion(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> tuple[Any, Any]: ...
|
|
|
|
async def create_with_completion(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs,
|
|
) -> tuple[T, Any]:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create_with_completion = cast(
|
|
Callable[..., Awaitable[tuple[T, Any]]],
|
|
self.client.create_with_completion,
|
|
)
|
|
return await create_with_completion(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
async def create_iterable(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]: ...
|
|
|
|
@overload
|
|
async def create_iterable(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[Any, None]: ...
|
|
|
|
async def create_iterable(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs,
|
|
) -> AsyncGenerator[T, None]:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create_iterable = cast(
|
|
Callable[..., AsyncGenerator[T, None]], self.client.create_iterable
|
|
)
|
|
return create_iterable(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_partial(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] = ...,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]: ...
|
|
|
|
@overload
|
|
def create_partial(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[Any, None]: ...
|
|
|
|
def create_partial(
|
|
self,
|
|
messages: str | list[ChatCompletionMessageParam] | None = None,
|
|
response_model: type[T] | None = None,
|
|
max_retries: int | AsyncRetrying = 3,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]:
|
|
messages = self._normalize_messages(messages, kwargs)
|
|
|
|
create_partial = cast(
|
|
Callable[..., AsyncGenerator[T, None]], self.client.create_partial
|
|
)
|
|
return create_partial(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
class Instructor:
|
|
"""Sync client wrapper that adds structured output support."""
|
|
|
|
client: Any | None
|
|
create_fn: Callable[..., Any]
|
|
mode: Mode
|
|
default_model: str | None = None
|
|
provider: Provider
|
|
hooks: Hooks
|
|
|
|
def __init__(
|
|
self,
|
|
client: Any | None,
|
|
create: Callable[..., Any],
|
|
mode: Mode = Mode.TOOLS,
|
|
provider: Provider = Provider.OPENAI,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
):
|
|
self.client = client
|
|
self.create_fn = create
|
|
self.mode = mode
|
|
if mode == Mode.FUNCTIONS:
|
|
Mode.warn_mode_functions_deprecation()
|
|
|
|
self.kwargs = kwargs
|
|
self.provider = provider
|
|
self.hooks = hooks or Hooks()
|
|
|
|
if mode in {
|
|
Mode.RESPONSES_TOOLS,
|
|
Mode.RESPONSES_TOOLS_WITH_INBUILT_TOOLS,
|
|
}:
|
|
import openai as _openai
|
|
|
|
assert isinstance(client, (_openai.OpenAI, _openai.AsyncOpenAI))
|
|
self.responses = Response(client=self)
|
|
|
|
def on(
|
|
self,
|
|
hook_name: (
|
|
HookName
|
|
| Literal[
|
|
"completion:kwargs",
|
|
"completion:response",
|
|
"completion:error",
|
|
"completion:last_attempt",
|
|
"parse:error",
|
|
]
|
|
),
|
|
handler: Callable[[Any], None],
|
|
) -> None:
|
|
self.hooks.on(hook_name, handler)
|
|
|
|
def off(
|
|
self,
|
|
hook_name: (
|
|
HookName
|
|
| Literal[
|
|
"completion:kwargs",
|
|
"completion:response",
|
|
"completion:error",
|
|
"completion:last_attempt",
|
|
"parse:error",
|
|
]
|
|
),
|
|
handler: Callable[[Any], None],
|
|
) -> None:
|
|
self.hooks.off(hook_name, handler)
|
|
|
|
def clear(
|
|
self,
|
|
hook_name: (
|
|
HookName
|
|
| Literal[
|
|
"completion:kwargs",
|
|
"completion:response",
|
|
"completion:error",
|
|
"completion:last_attempt",
|
|
"parse:error",
|
|
]
|
|
)
|
|
| None = None,
|
|
) -> None:
|
|
self.hooks.clear(hook_name)
|
|
|
|
@property
|
|
def chat(self) -> Self:
|
|
return self
|
|
|
|
@property
|
|
def completions(self) -> Self:
|
|
return self
|
|
|
|
@property
|
|
def messages(self) -> Self:
|
|
return self
|
|
|
|
@overload
|
|
def create(
|
|
self: AsyncInstructor,
|
|
response_model: type[T],
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None, # {{ edit_1 }}
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Awaitable[T]: ...
|
|
|
|
@overload
|
|
def create(
|
|
self: Self,
|
|
response_model: type[T],
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None, # {{ edit_1 }}
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> T: ...
|
|
|
|
@overload
|
|
def create(
|
|
self: AsyncInstructor,
|
|
response_model: None,
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None, # {{ edit_1 }}
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Awaitable[Any]: ...
|
|
|
|
@overload
|
|
def create(
|
|
self: Self,
|
|
response_model: None,
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None, # {{ edit_1 }}
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Any: ...
|
|
|
|
def create(
|
|
self,
|
|
response_model: type[T] | None,
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | Retrying | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> T | Any | Awaitable[T] | Awaitable[Any]:
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
return self.create_fn(
|
|
response_model=response_model,
|
|
messages=messages,
|
|
max_retries=max_retries,
|
|
context=context,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_partial(
|
|
self: AsyncInstructor,
|
|
response_model: type[T],
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None, # {{ edit_1 }}
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]: ...
|
|
|
|
@overload
|
|
def create_partial(
|
|
self: Self,
|
|
response_model: type[T],
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Generator[T, None, None]: ...
|
|
|
|
def create_partial(
|
|
self,
|
|
response_model: type[T],
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | Retrying | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Generator[T, None, None] | AsyncGenerator[T, None]:
|
|
kwargs["stream"] = True
|
|
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
response_model = Partial[response_model] # type: ignore
|
|
return self.create_fn(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
context=context,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_iterable(
|
|
self: AsyncInstructor,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]: ...
|
|
|
|
@overload
|
|
def create_iterable(
|
|
self: Self,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Generator[T, None, None]: ...
|
|
|
|
def create_iterable(
|
|
self,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | Retrying | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Generator[T, None, None] | AsyncGenerator[T, None]:
|
|
kwargs["stream"] = True
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
response_model = Iterable[response_model] # type: ignore
|
|
return self.create_fn(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
context=context,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
)
|
|
|
|
@overload
|
|
def create_with_completion(
|
|
self: AsyncInstructor,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> Awaitable[tuple[T, Any]]: ...
|
|
|
|
@overload
|
|
def create_with_completion(
|
|
self: Self,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | Retrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> tuple[T, Any]: ...
|
|
|
|
def create_with_completion(
|
|
self,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | Retrying | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> tuple[T, Any] | Awaitable[tuple[T, Any]]:
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
model = self.create_fn(
|
|
messages=messages,
|
|
response_model=response_model,
|
|
max_retries=max_retries,
|
|
context=context,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
)
|
|
return model, model._raw_response
|
|
|
|
def handle_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
"""
|
|
Handle and process keyword arguments for the API call.
|
|
|
|
This method merges the provided kwargs with the default kwargs stored in the instance.
|
|
It ensures that any kwargs passed to the method call take precedence over the default ones.
|
|
"""
|
|
for key, value in self.kwargs.items():
|
|
if key not in kwargs:
|
|
kwargs[key] = value
|
|
return kwargs
|
|
|
|
def __getattr__(self, attr: str) -> Any:
|
|
if attr not in {"create", "chat", "messages"}:
|
|
return getattr(self.client, attr)
|
|
|
|
return getattr(self, attr)
|
|
|
|
|
|
class AsyncInstructor(Instructor):
|
|
"""Async client wrapper that adds structured output support."""
|
|
|
|
client: Any | None
|
|
create_fn: Callable[..., Any]
|
|
mode: Mode
|
|
default_model: str | None = None
|
|
provider: Provider
|
|
hooks: Hooks
|
|
|
|
def __init__(
|
|
self,
|
|
client: Any | None,
|
|
create: Callable[..., Any],
|
|
mode: Mode = Mode.TOOLS,
|
|
provider: Provider = Provider.OPENAI,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
):
|
|
self.client = client
|
|
self.create_fn = create
|
|
self.mode = mode
|
|
self.kwargs = kwargs
|
|
self.provider = provider
|
|
self.hooks = hooks or Hooks()
|
|
|
|
if mode in {
|
|
Mode.RESPONSES_TOOLS,
|
|
Mode.RESPONSES_TOOLS_WITH_INBUILT_TOOLS,
|
|
}:
|
|
import openai as _openai
|
|
|
|
assert isinstance(client, (_openai.OpenAI, _openai.AsyncOpenAI))
|
|
self.responses = AsyncResponse(client=self)
|
|
|
|
async def create( # type: ignore[override] # ty: ignore[invalid-method-override]
|
|
self,
|
|
response_model: type[T] | None,
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> T | Any:
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
# Check if the response model is an iterable type
|
|
if (
|
|
get_origin(response_model) in {Iterable}
|
|
and get_args(response_model)
|
|
and get_args(response_model)[0] is not None
|
|
and self.mode not in Mode.parallel_modes()
|
|
):
|
|
return self.create_iterable(
|
|
messages=messages,
|
|
response_model=get_args(response_model)[0],
|
|
max_retries=max_retries,
|
|
context=context,
|
|
strict=strict,
|
|
hooks=hooks, # Pass the per-call hooks to create_iterable
|
|
**kwargs,
|
|
)
|
|
|
|
return await self.create_fn(
|
|
response_model=response_model,
|
|
context=context,
|
|
max_retries=max_retries,
|
|
messages=messages,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
)
|
|
|
|
async def create_partial( # type: ignore[override] # ty: ignore[invalid-method-override]
|
|
self,
|
|
response_model: type[T],
|
|
messages: list[ChatCompletionMessageParam],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]:
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
kwargs["stream"] = True
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
async for item in await self.create_fn(
|
|
response_model=Partial[response_model], # type: ignore
|
|
context=context,
|
|
max_retries=max_retries,
|
|
messages=messages,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
):
|
|
yield item
|
|
|
|
async def create_iterable( # type: ignore[override] # ty: ignore[invalid-method-override]
|
|
self,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> AsyncGenerator[T, None]:
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
kwargs["stream"] = True
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
iterable_model: Any = Iterable
|
|
async for item in await self.create_fn(
|
|
response_model=iterable_model[response_model],
|
|
context=context,
|
|
max_retries=max_retries,
|
|
messages=messages,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
):
|
|
yield item
|
|
|
|
async def create_with_completion( # type: ignore[override] # ty: ignore[invalid-method-override]
|
|
self,
|
|
messages: list[ChatCompletionMessageParam],
|
|
response_model: type[T],
|
|
max_retries: int | AsyncRetrying = 3,
|
|
context: dict[str, Any] | None = None,
|
|
strict: bool = True,
|
|
hooks: Hooks | None = None,
|
|
**kwargs: Any,
|
|
) -> tuple[T, Any]:
|
|
kwargs = self.handle_kwargs(kwargs)
|
|
|
|
# Combine client hooks with per-call hooks
|
|
combined_hooks = self.hooks
|
|
if hooks is not None:
|
|
combined_hooks = self.hooks + hooks
|
|
|
|
response = await self.create_fn(
|
|
response_model=response_model,
|
|
context=context,
|
|
max_retries=max_retries,
|
|
messages=messages,
|
|
strict=strict,
|
|
hooks=combined_hooks,
|
|
**kwargs,
|
|
)
|
|
return response, response._raw_response
|
|
|
|
|
|
@overload
|
|
def from_openai(
|
|
client: openai.OpenAI,
|
|
mode: Mode = Mode.TOOLS,
|
|
**kwargs: Any,
|
|
) -> Instructor: ...
|
|
|
|
|
|
@overload
|
|
def from_openai(
|
|
client: openai.AsyncOpenAI,
|
|
mode: Mode = Mode.TOOLS,
|
|
**kwargs: Any,
|
|
) -> AsyncInstructor: ...
|
|
|
|
|
|
def from_openai(
|
|
client: openai.OpenAI | openai.AsyncOpenAI,
|
|
mode: Mode = Mode.TOOLS,
|
|
**kwargs: Any,
|
|
) -> Instructor | AsyncInstructor:
|
|
"""Compatibility wrapper for the v2 OpenAI factory."""
|
|
from instructor.v2.providers.openai.client import from_openai as from_openai_v2
|
|
|
|
return from_openai_v2(client=client, mode=mode, **kwargs)
|
|
|
|
|
|
@overload
|
|
def from_litellm(
|
|
completion: Callable[..., Coroutine[Any, Any, T]],
|
|
mode: Mode = Mode.TOOLS,
|
|
*,
|
|
async_client: Literal[True],
|
|
**kwargs: Any,
|
|
) -> AsyncInstructor: ...
|
|
|
|
|
|
@overload
|
|
def from_litellm(
|
|
completion: Callable[..., Awaitable[T]],
|
|
mode: Mode = Mode.TOOLS,
|
|
*,
|
|
async_client: Literal[True],
|
|
**kwargs: Any,
|
|
) -> AsyncInstructor: ...
|
|
|
|
|
|
@overload
|
|
def from_litellm(
|
|
completion: Callable[..., object],
|
|
mode: Mode = Mode.TOOLS,
|
|
*,
|
|
async_client: Literal[False] = False,
|
|
**kwargs: Any,
|
|
) -> Instructor: ...
|
|
|
|
|
|
def from_litellm(
|
|
completion: Callable[..., object] | Callable[..., Awaitable[Any]],
|
|
mode: Mode = Mode.TOOLS,
|
|
*,
|
|
async_client: bool | None = None,
|
|
**kwargs: Any,
|
|
) -> Instructor | AsyncInstructor:
|
|
"""Compatibility wrapper for the v2 LiteLLM factory."""
|
|
from instructor.v2.providers.litellm.client import from_litellm as from_litellm_v2
|
|
|
|
if async_client is True:
|
|
return from_litellm_v2(
|
|
completion=cast(Callable[..., Awaitable[Any]], completion),
|
|
mode=mode,
|
|
async_client=True,
|
|
**kwargs,
|
|
)
|
|
if async_client is False:
|
|
return from_litellm_v2(
|
|
completion=completion,
|
|
mode=mode,
|
|
async_client=False,
|
|
**kwargs,
|
|
)
|
|
return from_litellm_v2(
|
|
completion=completion,
|
|
mode=mode,
|
|
**kwargs,
|
|
)
|