from dataclasses import dataclass
from pathlib import Path
from typing import Any, Literal, Optional, Type
from uuid import uuid4
from pydantic import BaseModel
[docs]
@dataclass
class Text:
content: str
[docs]
@dataclass
class Image:
source: str
media_type: str = "image/jpeg"
detail: Literal["low", "high", "auto"] = "auto"
[docs]
@classmethod
def file(
cls,
path: str | Path,
media_type: str = "image/jpeg",
detail: Literal["low", "high", "auto"] = "auto",
) -> "Image":
return cls(source=str(path), media_type=media_type, detail=detail)
[docs]
@classmethod
def url(cls, url: str, detail: Literal["low", "high", "auto"] = "auto") -> "Image":
return cls(source=url, detail=detail)
[docs]
@classmethod
def base64(cls, data: str, media_type: str = "image/jpeg") -> "Image":
return cls(source=f"data:{media_type};base64,{data}", media_type=media_type)
[docs]
@dataclass
class Document:
source: str
doc_type: str
extract_text: bool = True
cache: bool = False
[docs]
@classmethod
def from_pdf(
cls, path: str | Path, extract_text: bool = True, cache: bool = False
) -> "Document":
return cls(
source=str(path), doc_type="pdf", extract_text=extract_text, cache=cache
)
[docs]
@classmethod
def from_url(cls, url: str, cache: bool = False) -> "Document":
return cls(source=url, doc_type="url", cache=cache)
[docs]
@dataclass
class User:
content: list[str | Text | Image | Document]
[docs]
def __init__(self, *content: str | Text | Image | Document):
self.content = []
for item in content:
if isinstance(item, str):
self.content.append(Text(item))
else:
self.content.append(item)
[docs]
@dataclass
class Assistant:
content: list[str | Text | Image | Document]
[docs]
def __init__(self, *content: str | Text | Image | Document):
self.content = []
for item in content:
if isinstance(item, str):
self.content.append(Text(item))
else:
self.content.append(item)
[docs]
@dataclass
class System:
content: str
# @dataclass
# class ToolCallResponse:
# call: ToolCall
# response: Any | None = None
# error: str | None = None
Block = Text | Image | Document | User | Assistant | System | ToolCall | ToolUse
[docs]
@dataclass
class Prompt:
[docs]
def __init__(
self,
conversation: Optional[list[Block]] = None,
system: str | None = None,
tools: list[Tool] | None = None,
response_format: Type[BaseModel] | None = None,
tool_choice: Literal["auto", "none", "required"] | str = "auto",
**kwargs,
):
self.conversation = conversation or []
self.system = system
self.tools = tools or []
self.response_format = response_format
self.tool_choice = tool_choice
self.extra = kwargs
[docs]
@classmethod
def simple(cls, system: str, user: str) -> "Prompt":
return cls(system=system, conversation=[User(user)])
[docs]
@classmethod
def with_schema(
cls, schema: Type[BaseModel], user: str, system: str = "Extract structured data"
) -> "Prompt":
return cls(system=system, conversation=[User(user)], response_format=schema)
[docs]
def serialize(self) -> dict[str, Any]:
return {
"system": self.system,
"conversation": [block for block in self.conversation],
"tools": [
{"name": t.name, "description": t.description} for t in self.tools
],
"response_format": (
self.response_format.__name__ if self.response_format else None
),
"tool_choice": self.tool_choice,
**self.extra,
}
[docs]
@classmethod
def deserialize(cls, data: dict[str, Any]) -> "Prompt":
return cls(**data)
[docs]
@dataclass
class Message:
content: str
[docs]
@dataclass
class TextMessage:
content: str
[docs]
@dataclass
class LLMResponse:
raw_response: Any
tools: list[Tool]
_text_content: str
_tool_calls: list[ToolCall]
[docs]
def raise_for_status(self):
pass
[docs]
def text(self) -> str:
return self._text_content
[docs]
def messages(self) -> list[Message]:
return [Message(content=self._text_content)] if self._text_content else []
[docs]
def text_messages(self) -> list[TextMessage]:
return [TextMessage(content=self._text_content)] if self._text_content else []