diff --git a/.env-example b/.env-example
new file mode 100644
index 0000000..82b581a
--- /dev/null
+++ b/.env-example
@@ -0,0 +1,17 @@
+DATABASE_URL=
+SECRET_KEY=
+
+AWS_ACCESS_KEY_ID=
+AWS_ENDPOINT_URL_S3=
+AWS_REGION=
+AWS_SECRET_ACCESS_KEY=
+BUCKET_NAME=
+
+CORS_ORIGINS=
+FORWARDED_ALLOW_IPS=
+
+GOOGLE_API_KEY=
+TOGETHER_API_KEY=
+HYPERBOLIC_API_KEY=
+
+AUTH_TOKEN=
\ No newline at end of file
diff --git a/README.md b/README.md
index 2c50d74..bfd28cf 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,123 @@
+# VisuLinx API
+
+A robust FastAPI-based backend service for managing organizations, projects, and file processing with secure authentication.
+
+## 🚀 Features
+
+- **User Management**: Secure user authentication and authorization
+- **Organization Management**: Create and manage organizations with multiple users
+- **Project Management**: Organize work into projects within organizations
+- **File Processing**: Handle file uploads with S3 integration
+- **Preferences System**: Flexible system-wide preferences management
+- **RESTful API**: Modern, fast, and well-documented API endpoints
+- **CORS Support**: Configured for cross-origin resource sharing
+
+## 🏗 Project Structure
+
+```
+app/
+├── api.py # FastAPI application configuration
+├── database.py # Database connection and settings
+├── models.py # SQLAlchemy ORM models
+├── routers/ # API route handlers
+├── schemas.py # Pydantic models for request/response
+├── security.py # Authentication and authorization
+├── services/ # Business logic services
+└── settings.py # Application settings
+```
+
+## 🔧 Technical Stack
+
+- **Framework**: FastAPI
+- **Database**: SQLAlchemy ORM
+- **Authentication**: JWT-based authentication
+- **Storage**: AWS S3 integration
+- **Data Validation**: Pydantic
+- **API Documentation**: Automatic OpenAPI/Swagger docs
+
+## 💻 Key Components
+
+### Models
+- **User**: Manages user accounts and authentication
+- **Organization**: Handles multi-user organizations
+- **Project**: Organizes work within organizations
+- **File**: Manages file uploads and processing
+- **Preference**: Stores system-wide preferences
+
+### API Endpoints
+- `/users`: User management endpoints
+- `/auth`: Authentication endpoints
+- `/organizations`: Organization management
+- `/projects`: Project-related operations
+- `/preferences`: System preferences management
+
+### 🤖 AI Services
+
+The project integrates with multiple AI providers for advanced image analysis and object detection:
+
+#### Supported AI Providers
+- **Together AI**: Uses the Qwen2-VL-72B-Instruct model for high-quality visual analysis
+- **Hyperbolic AI**: Implements the same Qwen model through a different API endpoint
+- **Google Gemini**: Utilizes Gemini 1.5 Pro for advanced image processing
+
+#### Features
+- **Object Detection**: Extract bounding boxes for objects in images
+- **Image Processing**: Automatic image resizing and compression for optimal AI processing
+- **Multi-Provider Support**: Fallback options across different AI services
+- **Standardized Interface**: Consistent API across all providers through the `AiService` abstract base class
+
+#### Configuration
+The AI services require the following environment variables:
+```
+GOOGLE_API_KEY= # For Gemini AI
+TOGETHER_API_KEY= # For Together AI
+HYPERBOLIC_API_KEY= # For Hyperbolic AI
+```
+
+## 🔒 Security Features
+
+- Secure password hashing
+- JWT token-based authentication
+- Role-based access control
+- CORS middleware configuration
+
+## 🚀 Getting Started
+
+### Prerequisites
+
+- Python >= 3.13
+- uv (Python package installer)
+
+### Installation
+
+1. Clone the repository
+2. Install dependencies using uv:
+ ```bash
+ uv sync
+ ```
+3. Configure environment variables
+4. Run the application:
+ ```bash
+ uv run fastapi dev
+ ```
+
+## 📝 API Documentation
+
+Once the server is running, access the API documentation at:
+- Swagger UI: `http://localhost:8000/docs`
+
+## 🤝 Contributing
+
+1. Fork the repository
+2. Create a feature branch
+3. Commit your changes
+4. Push to the branch
+5. Create a Pull Request
+
+## 📄 License
+
+MIT License
+
## Autogenerate migration
```sh
@@ -8,4 +128,3 @@ uv run alembic revision --autogenerate -m "add ... table"
```sh
python -c "import secrets; print(secrets.token_urlsafe(32))"
-```
diff --git a/app/routers/projects.py b/app/routers/projects.py
index d9891b2..68a03d7 100644
--- a/app/routers/projects.py
+++ b/app/routers/projects.py
@@ -428,7 +428,9 @@ async def extract_bounding_boxes(
if document.contents is not None
}
- bounding_boxes = ai_service.extract_bounding_boxes(
+ ai = ai_service.GeminiAiService()
+
+ bounding_boxes = ai.extract_bounding_boxes(
image_url=download_url,
document_contents=document_contents,
system_prompt=preferences['system_prompt'],
diff --git a/app/services/ai_service.py b/app/services/ai_service.py
index 701563e..87ab857 100644
--- a/app/services/ai_service.py
+++ b/app/services/ai_service.py
@@ -1,15 +1,20 @@
import base64
import json
import os
+from abc import ABC, abstractmethod
from io import BytesIO
import google.generativeai as genai
+import openai
import requests
from dotenv import load_dotenv
-from PIL import Image
+from openai.types.chat import ChatCompletion
+from together import Together
+from together.types import ChatCompletionResponse
from typing_extensions import TypedDict
-load_dotenv()
+# Ensure environment variables are loaded at the very beginning
+load_dotenv(override=True)
class DetectedObjectSchema(TypedDict):
@@ -21,86 +26,306 @@ class DetectedObjectListSchema(TypedDict):
objects: list[DetectedObjectSchema]
-def resize_and_compress_image(image_path, max_width=1000, quality=95):
- """Resize and compress the input image."""
- with Image.open(image_path) as img:
- image = img
+class AiService(ABC):
+ @abstractmethod
+ def extract_bounding_boxes(
+ self,
+ image_url: str,
+ document_contents: dict[str, str],
+ system_prompt: str,
+ assistant_prompt: str,
+ ) -> DetectedObjectListSchema:
+ pass
- # Convert to RGB if necessary
- if image.mode != 'RGB':
- image = image.convert('RGB')
+ @staticmethod
+ def resize_and_compress_image(
+ image_path: str,
+ max_width: int = 1000,
+ quality: int = 95,
+ ) -> BytesIO:
+ from PIL import Image # noqa: PLC0415
- # Calculate new dimensions
- width, height = image.size
- if width > max_width:
- height = int((height * max_width) / width)
- width = max_width
+ """Resize and compress the input image."""
+ with Image.open(image_path) as img:
+ image = img
- # Resize image
- image = image.resize((width, height), Image.Resampling.LANCZOS)
+ # Convert to RGB if necessary
+ if image.mode != 'RGB':
+ image = image.convert('RGB')
- # Save compressed image to BytesIO
- buffer = BytesIO()
- image.save(buffer, format='JPEG', quality=quality)
- buffer.seek(0)
+ # Calculate new dimensions
+ width, height = image.size
+ if width > max_width:
+ height = int((height * max_width) / width)
+ width = max_width
- return buffer
+ # Resize image
+ image = image.resize((width, height), Image.Resampling.LANCZOS)
+ # Save compressed image to BytesIO
+ buffer = BytesIO()
+ image.save(buffer, format='JPEG', quality=quality)
+ buffer.seek(0)
-def extract_bounding_boxes(
- image_url: str,
- document_contents: dict[str, str],
- system_prompt: str,
- assistant_prompt: str,
-) -> DetectedObjectListSchema:
- gemini_api_key = os.getenv('GOOGLE_API_KEY')
- model_name = 'gemini-1.5-pro-latest'
+ return buffer
- if not gemini_api_key:
- raise ValueError('Please set GOOGLE_API_KEY in the .env file')
- image_path = 'image.jpg'
- with open(image_path, 'wb') as f:
- response = requests.get(image_url)
- f.write(response.content)
+class TogetherAiService(AiService):
+ def __init__(self) -> None:
+ self.together_api_key = os.getenv('TOGETHER_API_KEY')
+ self.model = 'Qwen/Qwen2-VL-72B-Instruct'
+ self.client = Together(api_key=self.together_api_key)
- image_buffer = resize_and_compress_image(image_path)
- image_data = base64.b64encode(image_buffer.getvalue()).decode('utf-8')
+ if not self.together_api_key:
+ raise ValueError('Please set TOGETHER_API_KEY in the .env file')
- # Create the image part for gemini request
- image_part = {'mime_type': 'image/jpeg', 'data': image_data}
+ def extract_bounding_boxes(
+ self,
+ image_url: str,
+ document_contents: dict[str, str],
+ system_prompt: str,
+ assistant_prompt: str,
+ ) -> DetectedObjectListSchema:
+ image_path = 'image.jpg'
+ with open(image_path, 'wb') as f:
+ response = requests.get(image_url)
+ f.write(response.content)
- prompt = assistant_prompt
+ image_buffer = self.resize_and_compress_image(image_path)
+ image_data = base64.b64encode(image_buffer.getvalue()).decode('utf-8')
- # Add the document contents to the prompt
- for key, value in document_contents.items():
- prompt += (
- '\n\n'
- f'{key}'
- f'{value}'
- ''
+ modified_system_prompt = (
+ """You are a helpful assistant to detect objects in images. When
+ asked to detect elements you return bounding boxes in the form of
+ [xmin, ymin, xmax, ymax] with the values being scaled to match the
+ 1024x1024 size. """
+ # + system_prompt
+ + """Always respond in JSON format with an object with a key
+ 'objects' that contains a list of objects where each object has the
+ following keys: 'bounding_boxes' and 'name'. Here's an example of
+ what the object must look like:
+ {
+ "objects": [
+ {
+ "bounding_boxes": [435, 595, 704, 710],
+ "name": "electric car"
+ },
+ {
+ "bounding_boxes": [300, 450, 665, 610],
+ "name": "house"
+ }
+ ]
+ }
+ """
)
- contents = [
- {'inline_data': image_part},
- {'text': prompt},
- ]
-
- # Initialize the model
- model = genai.GenerativeModel(
- model_name=model_name,
- system_instruction=system_prompt,
- )
-
- # Generate response
- response = model.generate_content(
- contents,
- generation_config=genai.GenerationConfig(
- response_mime_type='application/json',
- response_schema=DetectedObjectListSchema,
- temperature=0.05,
- ),
- )
-
- # Return decoded json
- return json.loads(response.text)
+ prompt = assistant_prompt
+
+ # Add the document contents to the prompt
+ for key, value in document_contents.items():
+ prompt += (
+ '\n\n'
+ f'{key}'
+ f'{value}'
+ ''
+ )
+
+ response = self.client.chat.completions.create(
+ model=self.model,
+ messages=[
+ {
+ 'role': 'system',
+ 'content': modified_system_prompt,
+ },
+ {
+ 'role': 'user',
+ 'content': [
+ {'type': 'text', 'text': prompt},
+ {
+ 'type': 'image_url',
+ 'image_url': {
+ 'url': f'data:image/jpeg;base64,{image_data}',
+ },
+ },
+ ],
+ },
+ ],
+ )
+
+ if (
+ type(response) is not ChatCompletionResponse
+ or response.choices is None
+ or len(response.choices) <= 0
+ or response.choices[0].message is None
+ or type(response.choices[0].message.content) is not str
+ ):
+ raise ValueError('Invalid response from Together API')
+
+ info = response.choices[0].message.content
+ print(info)
+
+ # Remove markdown code block delimiters if present
+ info = info.replace('```json', '').replace('```', '').strip()
+ return json.loads(info)
+
+
+class HyperbolicAiService(AiService):
+ def __init__(self) -> None:
+ self.hyperbolic_api_key = os.getenv('HYPERBOLIC_API_KEY')
+ self.model = 'Qwen/Qwen2-VL-72B-Instruct'
+ self.client = openai.OpenAI(
+ api_key=self.hyperbolic_api_key,
+ base_url='https://api.hyperbolic.xyz/v1',
+ )
+ if not self.hyperbolic_api_key:
+ raise ValueError('Please set HYPERBOLIC_API_KEY in the .env file')
+
+ def extract_bounding_boxes(
+ self,
+ image_url: str,
+ document_contents: dict[str, str],
+ system_prompt: str,
+ assistant_prompt: str,
+ ) -> DetectedObjectListSchema:
+ image_path = 'image.jpg'
+ with open(image_path, 'wb') as f:
+ img_response = requests.get(image_url)
+ f.write(img_response.content)
+
+ image_buffer = self.resize_and_compress_image(image_path)
+ image_data = base64.b64encode(image_buffer.getvalue()).decode('utf-8')
+
+ modified_system_prompt = """You are a helpful assistant that precisely
+ detects objects in images. When asked to detect objects, you return
+ bounding boxes in the form of [xmin, ymin, xmax, ymax] with the
+ values being scaled to match the 1024x1024 size.
+ Always respond in JSON format with an object with a key
+ 'objects' that contains a list of objects where each object has the
+ following keys: 'bounding_boxes' and 'name'. Here's an example of
+ what the object must look like:
+ {
+ "objects": [
+ {
+ "bounding_boxes": [xmin, ymin, xmax, ymax],
+ "name": "object1"
+ },
+ {
+ "bounding_boxes": [xmin, ymin, xmax, ymax],
+ "name": "object2"
+ }
+ ]
+ }
+ """
+
+ prompt = """Detect all objects in this image and provide
+ bounding boxes for each of them.
+ """
+
+ # Add the document contents to the prompt
+ # for key, value in document_contents.items():
+ # prompt += (
+ # '\n\n'
+ # f'{key}'
+ # f'{value}'
+ # ''
+ # )
+
+ response = self.client.chat.completions.create(
+ model=self.model,
+ messages=[
+ {
+ 'role': 'system',
+ 'content': modified_system_prompt,
+ },
+ {
+ 'role': 'user',
+ 'content': [
+ {'type': 'text', 'text': prompt},
+ {
+ 'type': 'image_url',
+ 'image_url': {
+ 'url': f'data:image/jpeg;base64,{image_data}',
+ },
+ },
+ ],
+ },
+ ],
+ )
+
+ if (
+ type(response) is not ChatCompletion
+ or response.choices is None
+ or len(response.choices) <= 0
+ or response.choices[0].message is None
+ or type(response.choices[0].message.content) is not str
+ ):
+ raise ValueError('Invalid response from Hyperbolic API')
+
+ info = response.choices[0].message.content
+ print(info)
+
+ # Remove markdown code block delimiters if present
+ info = info.replace('```json', '').replace('```', '').strip()
+ return json.loads(info)
+
+
+class GeminiAiService(AiService):
+ def __init__(self) -> None:
+ self.gemini_api_key = os.getenv('GOOGLE_API_KEY')
+ self.model_name = 'gemini-1.5-pro-latest'
+
+ if not self.gemini_api_key:
+ raise ValueError('Please set GOOGLE_API_KEY in the .env file')
+
+ def extract_bounding_boxes(
+ self,
+ image_url: str,
+ document_contents: dict[str, str],
+ system_prompt: str,
+ assistant_prompt: str,
+ ) -> DetectedObjectListSchema:
+ image_path = 'image.jpg'
+ with open(image_path, 'wb') as f:
+ response = requests.get(image_url)
+ f.write(response.content)
+
+ image_buffer = self.resize_and_compress_image(image_path)
+ image_data = base64.b64encode(image_buffer.getvalue()).decode('utf-8')
+
+ # Create the image part for gemini request
+ image_part = {'mime_type': 'image/jpeg', 'data': image_data}
+
+ prompt = assistant_prompt
+
+ # Add the document contents to the prompt
+ for key, value in document_contents.items():
+ prompt += (
+ '\n\n'
+ f'{key}'
+ f'{value}'
+ ''
+ )
+
+ contents = [
+ {'inline_data': image_part},
+ {'text': prompt},
+ ]
+
+ # Initialize the model
+ model = genai.GenerativeModel(
+ model_name=self.model_name,
+ system_instruction=system_prompt,
+ )
+
+ # Generate response
+ response = model.generate_content(
+ contents,
+ generation_config=genai.GenerationConfig(
+ response_mime_type='application/json',
+ response_schema=DetectedObjectListSchema,
+ temperature=0.05,
+ ),
+ )
+
+ # Return decoded json
+ return json.loads(response.text)
diff --git a/pyproject.toml b/pyproject.toml
index eee2bc8..5d161f5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,10 +20,11 @@ dependencies = [
"testcontainers>=4.8.2",
"python-magic>=0.4.27",
"pytest-asyncio>=0.24.0",
- "modal>=0.67.9",
"google-generativeai>=0.8.3",
- "pillow>=11.0.0",
+ "pillow<11.0.0",
"types-requests>=2.32.0.20241016",
+ "together>=1.3.14",
+ "openai>=1.61.0",
]
[dependency-groups]
diff --git a/uv.lock b/uv.lock
index 5e862ed..4da861d 100644
--- a/uv.lock
+++ b/uv.lock
@@ -125,11 +125,11 @@ wheels = [
[[package]]
name = "attrs"
-version = "24.3.0"
+version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 }
+sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 },
+ { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 },
]
[[package]]
@@ -301,6 +301,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 },
]
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 },
+]
+
[[package]]
name = "dnspython"
version = "2.7.0"
@@ -337,6 +346,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521 },
]
+[[package]]
+name = "eval-type-backport"
+version = "0.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830 },
+]
+
[[package]]
name = "factory-boy"
version = "3.3.1"
@@ -405,6 +423,15 @@ standard = [
{ name = "uvicorn", extra = ["standard"] },
]
+[[package]]
+name = "filelock"
+version = "3.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
+]
+
[[package]]
name = "freezegun"
version = "1.5.1"
@@ -582,16 +609,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/86/1c/59dfc81f27f252bef2cd52c57157bf381cb3738185d3087ac4c9ff3376b0/grpcio_status-1.68.1-py3-none-any.whl", hash = "sha256:66f3d8847f665acfd56221333d66f7ad8927903d87242a482996bdb45e8d28fd", size = 14427 },
]
-[[package]]
-name = "grpclib"
-version = "0.4.7"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "h2" },
- { name = "multidict" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/79/b9/55936e462a5925190d7427e880b3033601d1effd13809b483d13a926061a/grpclib-0.4.7.tar.gz", hash = "sha256:2988ef57c02b22b7a2e8e961792c41ccf97efc2ace91ae7a5b0de03c363823c3", size = 61254 }
-
[[package]]
name = "h11"
version = "0.14.0"
@@ -601,28 +618,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
]
-[[package]]
-name = "h2"
-version = "4.1.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "hpack" },
- { name = "hyperframe" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/2a/32/fec683ddd10629ea4ea46d206752a95a2d8a48c22521edd70b142488efe1/h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb", size = 2145593 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2a/e5/db6d438da759efbb488c4f3fbdab7764492ff3c3f953132efa6b9f0e9e53/h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d", size = 57488 },
-]
-
-[[package]]
-name = "hpack"
-version = "4.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/3e/9b/fda93fb4d957db19b0f6b370e79d586b3e8528b20252c729c476a2c02954/hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095", size = 49117 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d5/34/e8b383f35b77c402d28563d2b8f83159319b509bc5f760b15d60b0abf165/hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c", size = 32611 },
-]
-
[[package]]
name = "httpcore"
version = "1.0.7"
@@ -678,15 +673,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
]
-[[package]]
-name = "hyperframe"
-version = "6.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5a/2a/4747bff0a17f7281abe73e955d60d80aae537a5d203f417fa1c2e7578ebb/hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914", size = 25008 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/de/85a784bcc4a3779d1753a7ec2dee5de90e18c7bcf402e71b51fcf150b129/hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15", size = 12389 },
-]
-
[[package]]
name = "idna"
version = "3.10"
@@ -717,6 +703,29 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 },
]
+[[package]]
+name = "jiter"
+version = "0.8.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 },
+ { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 },
+ { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 },
+ { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 },
+ { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 },
+ { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 },
+ { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 },
+ { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 },
+ { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 },
+ { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 },
+ { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 },
+ { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 },
+ { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 },
+ { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 },
+ { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 },
+]
+
[[package]]
name = "jmespath"
version = "1.0.1"
@@ -787,30 +796,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
]
-[[package]]
-name = "modal"
-version = "0.70.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "aiohttp" },
- { name = "certifi" },
- { name = "click" },
- { name = "fastapi" },
- { name = "grpclib" },
- { name = "protobuf" },
- { name = "rich" },
- { name = "synchronicity" },
- { name = "toml" },
- { name = "typer" },
- { name = "types-certifi" },
- { name = "types-toml" },
- { name = "typing-extensions" },
- { name = "watchfiles" },
-]
-wheels = [
- { url = "https://files.pythonhosted.org/packages/61/5c/08d6e04773eab6048a56f0b130765e343e7827071bdef3835bb6de49b099/modal-0.70.1-py3-none-any.whl", hash = "sha256:827b4fed8162c7f7efa2a49b67392b86bfeac3b2b81d1d3ab4a6cdc19352bb8b", size = 509321 },
-]
-
[[package]]
name = "mslex"
version = "1.3.0"
@@ -880,6 +865,53 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
]
+[[package]]
+name = "numpy"
+version = "2.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/d0/c12ddfd3a02274be06ffc71f3efc6d0e457b0409c4481596881e748cb264/numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f", size = 20233295 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e1/fe/df5624001f4f5c3e0b78e9017bfab7fdc18a8d3b3d3161da3d64924dd659/numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc", size = 20899188 },
+ { url = "https://files.pythonhosted.org/packages/a9/80/d349c3b5ed66bd3cb0214be60c27e32b90a506946857b866838adbe84040/numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369", size = 14113972 },
+ { url = "https://files.pythonhosted.org/packages/9d/50/949ec9cbb28c4b751edfa64503f0913cbfa8d795b4a251e7980f13a8a655/numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd", size = 5114294 },
+ { url = "https://files.pythonhosted.org/packages/8d/f3/399c15629d5a0c68ef2aa7621d430b2be22034f01dd7f3c65a9c9666c445/numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be", size = 6648426 },
+ { url = "https://files.pythonhosted.org/packages/2c/03/c72474c13772e30e1bc2e558cdffd9123c7872b731263d5648b5c49dd459/numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84", size = 14045990 },
+ { url = "https://files.pythonhosted.org/packages/83/9c/96a9ab62274ffafb023f8ee08c88d3d31ee74ca58869f859db6845494fa6/numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff", size = 16096614 },
+ { url = "https://files.pythonhosted.org/packages/d5/34/cd0a735534c29bec7093544b3a509febc9b0df77718a9b41ffb0809c9f46/numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0", size = 15242123 },
+ { url = "https://files.pythonhosted.org/packages/5e/6d/541717a554a8f56fa75e91886d9b79ade2e595918690eb5d0d3dbd3accb9/numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de", size = 17859160 },
+ { url = "https://files.pythonhosted.org/packages/b9/a5/fbf1f2b54adab31510728edd06a05c1b30839f37cf8c9747cb85831aaf1b/numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9", size = 6273337 },
+ { url = "https://files.pythonhosted.org/packages/56/e5/01106b9291ef1d680f82bc47d0c5b5e26dfed15b0754928e8f856c82c881/numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369", size = 12609010 },
+ { url = "https://files.pythonhosted.org/packages/9f/30/f23d9876de0f08dceb707c4dcf7f8dd7588266745029debb12a3cdd40be6/numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391", size = 20924451 },
+ { url = "https://files.pythonhosted.org/packages/6a/ec/6ea85b2da9d5dfa1dbb4cb3c76587fc8ddcae580cb1262303ab21c0926c4/numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39", size = 14122390 },
+ { url = "https://files.pythonhosted.org/packages/68/05/bfbdf490414a7dbaf65b10c78bc243f312c4553234b6d91c94eb7c4b53c2/numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317", size = 5156590 },
+ { url = "https://files.pythonhosted.org/packages/f7/ec/fe2e91b2642b9d6544518388a441bcd65c904cea38d9ff998e2e8ebf808e/numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49", size = 6671958 },
+ { url = "https://files.pythonhosted.org/packages/b1/6f/6531a78e182f194d33ee17e59d67d03d0d5a1ce7f6be7343787828d1bd4a/numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2", size = 14019950 },
+ { url = "https://files.pythonhosted.org/packages/e1/fb/13c58591d0b6294a08cc40fcc6b9552d239d773d520858ae27f39997f2ae/numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7", size = 16079759 },
+ { url = "https://files.pythonhosted.org/packages/2c/f2/f2f8edd62abb4b289f65a7f6d1f3650273af00b91b7267a2431be7f1aec6/numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb", size = 15226139 },
+ { url = "https://files.pythonhosted.org/packages/aa/29/14a177f1a90b8ad8a592ca32124ac06af5eff32889874e53a308f850290f/numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648", size = 17856316 },
+ { url = "https://files.pythonhosted.org/packages/95/03/242ae8d7b97f4e0e4ab8dd51231465fb23ed5e802680d629149722e3faf1/numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4", size = 6329134 },
+ { url = "https://files.pythonhosted.org/packages/80/94/cd9e9b04012c015cb6320ab3bf43bc615e248dddfeb163728e800a5d96f0/numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576", size = 12696208 },
+]
+
+[[package]]
+name = "openai"
+version = "1.61.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/32/2a/b3fa8790be17d632f59d4f50257b909a3f669036e5195c1ae55737274620/openai-1.61.0.tar.gz", hash = "sha256:216f325a24ed8578e929b0f1b3fb2052165f3b04b0461818adaa51aa29c71f8a", size = 350174 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/93/76/70c5ad6612b3e4c89fa520266bbf2430a89cae8bd87c1e2284698af5927e/openai-1.61.0-py3-none-any.whl", hash = "sha256:e8c512c0743accbdbe77f3429a1490d862f8352045de8dc81969301eb4a4f666", size = 460623 },
+]
+
[[package]]
name = "packaging"
version = "24.2"
@@ -891,29 +923,21 @@ wheels = [
[[package]]
name = "pillow"
-version = "11.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 },
- { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 },
- { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 },
- { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 },
- { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 },
- { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 },
- { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 },
- { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 },
- { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 },
- { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 },
- { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 },
- { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 },
- { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 },
- { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 },
- { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 },
- { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 },
- { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 },
- { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 },
- { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 },
+version = "10.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c3/00/706cebe7c2c12a6318aabe5d354836f54adff7156fd9e1bd6c89f4ba0e98/pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", size = 3525685 },
+ { url = "https://files.pythonhosted.org/packages/cf/76/f658cbfa49405e5ecbfb9ba42d07074ad9792031267e782d409fd8fe7c69/pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", size = 3374883 },
+ { url = "https://files.pythonhosted.org/packages/46/2b/99c28c4379a85e65378211971c0b430d9c7234b1ec4d59b2668f6299e011/pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", size = 4339837 },
+ { url = "https://files.pythonhosted.org/packages/f1/74/b1ec314f624c0c43711fdf0d8076f82d9d802afd58f1d62c2a86878e8615/pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", size = 4455562 },
+ { url = "https://files.pythonhosted.org/packages/4a/2a/4b04157cb7b9c74372fa867096a1607e6fedad93a44deeff553ccd307868/pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", size = 4366761 },
+ { url = "https://files.pythonhosted.org/packages/ac/7b/8f1d815c1a6a268fe90481232c98dd0e5fa8c75e341a75f060037bd5ceae/pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", size = 4536767 },
+ { url = "https://files.pythonhosted.org/packages/e5/77/05fa64d1f45d12c22c314e7b97398ffb28ef2813a485465017b7978b3ce7/pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", size = 4477989 },
+ { url = "https://files.pythonhosted.org/packages/12/63/b0397cfc2caae05c3fb2f4ed1b4fc4fc878f0243510a7a6034ca59726494/pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", size = 4610255 },
+ { url = "https://files.pythonhosted.org/packages/7b/f9/cfaa5082ca9bc4a6de66ffe1c12c2d90bf09c309a5f52b27759a596900e7/pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", size = 2235603 },
+ { url = "https://files.pythonhosted.org/packages/01/6a/30ff0eef6e0c0e71e55ded56a38d4859bf9d3634a94a88743897b5f96936/pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", size = 2554972 },
+ { url = "https://files.pythonhosted.org/packages/48/2c/2e0a52890f269435eee38b21c8218e102c621fe8d8df8b9dd06fabf879ba/pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", size = 2243375 },
]
[[package]]
@@ -1007,6 +1031,7 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 },
{ url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 },
{ url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 },
+ { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 },
]
[[package]]
@@ -1023,6 +1048,27 @@ argon2 = [
{ name = "argon2-cffi" },
]
+[[package]]
+name = "pyarrow"
+version = "19.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/01/fe1fd04744c2aa038e5a11c7a4adb3d62bce09798695e54f7274b5977134/pyarrow-19.0.0.tar.gz", hash = "sha256:8d47c691765cf497aaeed4954d226568563f1b3b74ff61139f2d77876717084b", size = 1129096 }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f5/b9/ba07ed3dd6b6e4f379b78e9c47c50c8886e07862ab7fa6339ac38622d755/pyarrow-19.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:cf3bf0ce511b833f7bc5f5bb3127ba731e97222023a444b7359f3a22e2a3b463", size = 30651291 },
+ { url = "https://files.pythonhosted.org/packages/ad/10/0d304243c8277035298a68a70807efb76199c6c929bb3363c92ac9be6a0d/pyarrow-19.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:4d8b0c0de0a73df1f1bf439af1b60f273d719d70648e898bc077547649bb8352", size = 32100461 },
+ { url = "https://files.pythonhosted.org/packages/8a/61/bcfc5182e11831bca3f849945b9b106e09fd10ded773dff466658e972a45/pyarrow-19.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92aff08e23d281c69835e4a47b80569242a504095ef6a6223c1f6bb8883431d", size = 41132491 },
+ { url = "https://files.pythonhosted.org/packages/8e/87/2915a29049ec352dc69a967fbcbd76b0180319233de0daf8bd368df37099/pyarrow-19.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b78eff5968a1889a0f3bc81ca57e1e19b75f664d9c61a42a604bf9d8402aae", size = 42192529 },
+ { url = "https://files.pythonhosted.org/packages/48/18/44e5542b2707a8afaf78b5b88c608f261871ae77787eac07b7c679ca6f0f/pyarrow-19.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:b34d3bde38eba66190b215bae441646330f8e9da05c29e4b5dd3e41bde701098", size = 40495363 },
+ { url = "https://files.pythonhosted.org/packages/ba/d6/5096deb7599bbd20bc2768058fe23bc725b88eb41bee58303293583a2935/pyarrow-19.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5418d4d0fab3a0ed497bad21d17a7973aad336d66ad4932a3f5f7480d4ca0c04", size = 42074075 },
+ { url = "https://files.pythonhosted.org/packages/2c/df/e3c839c04c284c9ec3d62b02a8c452b795d9b07b04079ab91ce33484d4c5/pyarrow-19.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e82c3d5e44e969c217827b780ed8faf7ac4c53f934ae9238872e749fa531f7c9", size = 25239803 },
+ { url = "https://files.pythonhosted.org/packages/6a/d3/a6d4088e906c7b5d47792256212606d2ae679046dc750eee0ae167338e5c/pyarrow-19.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:f208c3b58a6df3b239e0bb130e13bc7487ed14f39a9ff357b6415e3f6339b560", size = 30695401 },
+ { url = "https://files.pythonhosted.org/packages/94/25/70040fd0e397dd1b937f459eaeeec942a76027357491dca0ada09d1322af/pyarrow-19.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:c751c1c93955b7a84c06794df46f1cec93e18610dcd5ab7d08e89a81df70a849", size = 32104680 },
+ { url = "https://files.pythonhosted.org/packages/4e/f9/92783290cc0d80ca16d34b0c126305bfacca4b87dd889c8f16c6ef2a8fd7/pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b903afaa5df66d50fc38672ad095806443b05f202c792694f3a604ead7c6ea6e", size = 41076754 },
+ { url = "https://files.pythonhosted.org/packages/05/46/2c9870f50a495c72e2b8982ae29a9b1680707ea936edc0de444cec48f875/pyarrow-19.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22a4bc0937856263df8b94f2f2781b33dd7f876f787ed746608e06902d691a5", size = 42163133 },
+ { url = "https://files.pythonhosted.org/packages/7b/2f/437922b902549228fb15814e8a26105bff2787ece466a8d886eb6699efad/pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5e8a28b918e2e878c918f6d89137386c06fe577cd08d73a6be8dafb317dc2d73", size = 40452210 },
+ { url = "https://files.pythonhosted.org/packages/36/ef/1d7975053af9d106da973bac142d0d4da71b7550a3576cc3e0b3f444d21a/pyarrow-19.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:29cd86c8001a94f768f79440bf83fee23963af5e7bc68ce3a7e5f120e17edf89", size = 42077618 },
+]
+
[[package]]
name = "pyasn1"
version = "0.6.1"
@@ -1343,18 +1389,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
]
-[[package]]
-name = "sigtools"
-version = "4.0.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "attrs" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/5f/db/669ca14166814da187b3087b908ca924cf83f5b504fe23b3859a3ef67d4f/sigtools-4.0.1.tar.gz", hash = "sha256:4b8e135a9cd4d2ea00da670c093372d74e672ba3abb87f4c98d8e73dea54445c", size = 71910 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/1f/91/853dbf6ec096197dba9cd5fd0c836c5fc19142038b7db60ebe6332b1bab1/sigtools-4.0.1-py2.py3-none-any.whl", hash = "sha256:d216b4cf920bbab0fce636ddc429ed8463a5b533d9e1492acb45a2a1bc36ac6c", size = 76419 },
-]
-
[[package]]
name = "six"
version = "1.17.0"
@@ -1406,16 +1440,12 @@ wheels = [
]
[[package]]
-name = "synchronicity"
-version = "0.9.8"
+name = "tabulate"
+version = "0.9.0"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "sigtools" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/5d/67/78c5fa3d30502a88cc2eef9c672d64274c81d98aef58e814d36cf44f5039/synchronicity-0.9.8.tar.gz", hash = "sha256:f8246a2cd0c2658e260234be27eaaf3c461e11e2a80ab60b698a43c078359471", size = 47574 }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6b/89/544bfc8de1304d46d289581dd4623f318e6edf78c16370ae77a1a095e95b/synchronicity-0.9.8-py3-none-any.whl", hash = "sha256:ff1c1bec769ba8a6ded8298a30282fd32992e900a3270837d9256fd60e61862b", size = 34634 },
+ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 },
]
[[package]]
@@ -1450,12 +1480,27 @@ wheels = [
]
[[package]]
-name = "toml"
-version = "0.10.2"
+name = "together"
+version = "1.3.14"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 }
+dependencies = [
+ { name = "aiohttp" },
+ { name = "click" },
+ { name = "eval-type-backport" },
+ { name = "filelock" },
+ { name = "numpy" },
+ { name = "pillow" },
+ { name = "pyarrow" },
+ { name = "pydantic" },
+ { name = "requests" },
+ { name = "rich" },
+ { name = "tabulate" },
+ { name = "tqdm" },
+ { name = "typer" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/ea/a6bea1e3b4233924aac740e9963631334ed1134e7375ed1e4ed5d997d3e7/together-1.3.14.tar.gz", hash = "sha256:9ce173fd11253dfa1a34dfcd48f77be7ed82ac643b07d96157f0f65356b9c76d", size = 52400 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 },
+ { url = "https://files.pythonhosted.org/packages/85/e4/88dceb35080b1e7a8901f0e943d0d32326a926a74f2ccd5880ccbd7a8ebb/together-1.3.14-py3-none-any.whl", hash = "sha256:da3ca1247072878728b7fba69c23a7852cc32d4eec89d14c79505c0a97b9ae1c", size = 73823 },
]
[[package]]
@@ -1513,15 +1558,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/21/f1/0f0869d35c1b746df98d60016f898eb49db208747a4ed2de81b58f48ecd8/types_awscrt-0.23.6-py3-none-any.whl", hash = "sha256:fbf9c221af5607b24bf17f8431217ce8b9a27917139edbc984891eb63fd5a593", size = 19025 },
]
-[[package]]
-name = "types-certifi"
-version = "2021.10.8.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/52/68/943c3aeaf14624712a0357c4a67814dba5cea36d194f5c764dad7959a00c/types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", size = 2095 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b5/63/2463d89481e811f007b0e1cd0a91e52e141b47f9de724d20db7b861dcfec/types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a", size = 2136 },
-]
-
[[package]]
name = "types-requests"
version = "2.32.0.20241016"
@@ -1543,15 +1579,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/73/de/38872bc9414018e223a4c7193bc2f7ed5ef8ab7a01ab3bb8d7de4f3c2720/types_s3transfer-0.10.4-py3-none-any.whl", hash = "sha256:22ac1aabc98f9d7f2928eb3fb4d5c02bf7435687f0913345a97dd3b84d0c217d", size = 18744 },
]
-[[package]]
-name = "types-toml"
-version = "0.10.8.20240310"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/86/47/3e4c75042792bff8e90d7991aa5c51812cc668828cc6cce711e97f63a607/types-toml-0.10.8.20240310.tar.gz", hash = "sha256:3d41501302972436a6b8b239c850b26689657e25281b48ff0ec06345b8830331", size = 4392 }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/da/a2/d32ab58c0b216912638b140ab2170ee4b8644067c293b170e19fba340ccc/types_toml-0.10.8.20240310-py3-none-any.whl", hash = "sha256:627b47775d25fa29977d9c70dc0cbab3f314f32c8d8d0c012f2ef5de7aaec05d", size = 4777 },
-]
-
[[package]]
name = "typing-extensions"
version = "4.12.2"
@@ -1627,8 +1654,8 @@ dependencies = [
{ name = "boto3-stubs", extra = ["s3"] },
{ name = "fastapi", extra = ["standard"] },
{ name = "google-generativeai" },
- { name = "modal" },
{ name = "mypy" },
+ { name = "openai" },
{ name = "pillow" },
{ name = "psycopg2-binary" },
{ name = "pwdlib", extra = ["argon2"] },
@@ -1640,6 +1667,7 @@ dependencies = [
{ name = "python-multipart" },
{ name = "sqlalchemy" },
{ name = "testcontainers" },
+ { name = "together" },
{ name = "types-requests" },
]
@@ -1661,9 +1689,9 @@ requires-dist = [
{ name = "boto3-stubs", extras = ["s3"], specifier = ">=1.35.68" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.5" },
{ name = "google-generativeai", specifier = ">=0.8.3" },
- { name = "modal", specifier = ">=0.67.9" },
{ name = "mypy", specifier = ">=1.13.0" },
- { name = "pillow", specifier = ">=11.0.0" },
+ { name = "openai", specifier = ">=1.61.0" },
+ { name = "pillow", specifier = "<11.0.0" },
{ name = "psycopg2-binary", specifier = ">=2.9.10" },
{ name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.10.0" },
@@ -1674,6 +1702,7 @@ requires-dist = [
{ name = "python-multipart", specifier = ">=0.0.17" },
{ name = "sqlalchemy", specifier = ">=2.0.36" },
{ name = "testcontainers", specifier = ">=4.8.2" },
+ { name = "together", specifier = ">=1.3.14" },
{ name = "types-requests", specifier = ">=2.32.0.20241016" },
]