hopefully fix repetition with higher temperature and frequency penalty

This commit is contained in:
2026-03-03 21:30:57 -05:00
parent c547edc44b
commit a6ab9708a0
4 changed files with 271 additions and 26 deletions

View File

@@ -4,6 +4,14 @@ from typing import Optional, List, Tuple
from datetime import datetime from datetime import datetime
import numpy as np import numpy as np
from openai import OpenAI from openai import OpenAI
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Database configuration # Database configuration
DB_PATH = os.getenv("DB_PATH", "chat_history.db") DB_PATH = os.getenv("DB_PATH", "chat_history.db")
@@ -24,16 +32,20 @@ class ChatDatabase:
"""SQLite database with RAG support for storing chat history using OpenAI embeddings.""" """SQLite database with RAG support for storing chat history using OpenAI embeddings."""
def __init__(self, db_path: str = DB_PATH): def __init__(self, db_path: str = DB_PATH):
logger.info(f"Initializing ChatDatabase with path: {db_path}")
self.db_path = db_path self.db_path = db_path
self.client = OpenAI(base_url=OPENAI_API_EMBED_ENDPOINT, api_key=OPENAI_API_KEY) self.client = OpenAI(base_url=OPENAI_API_EMBED_ENDPOINT, api_key=OPENAI_API_KEY)
logger.info("Connecting to OpenAI API for embeddings")
self._initialize_database() self._initialize_database()
def _initialize_database(self): def _initialize_database(self):
"""Initialize the SQLite database with required tables.""" """Initialize the SQLite database with required tables."""
logger.info(f"Initializing SQLite database at {self.db_path}")
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
cursor = conn.cursor() cursor = conn.cursor()
# Create messages table # Create messages table
logger.info("Creating chat_messages table if not exists")
cursor.execute( cursor.execute(
""" """
CREATE TABLE IF NOT EXISTS chat_messages ( CREATE TABLE IF NOT EXISTS chat_messages (
@@ -48,8 +60,10 @@ class ChatDatabase:
) )
""" """
) )
logger.info("chat_messages table initialized successfully")
# Create embeddings table for RAG # Create embeddings table for RAG
logger.info("Creating message_embeddings table if not exists")
cursor.execute( cursor.execute(
""" """
CREATE TABLE IF NOT EXISTS message_embeddings ( CREATE TABLE IF NOT EXISTS message_embeddings (
@@ -59,28 +73,39 @@ class ChatDatabase:
) )
""" """
) )
logger.info("message_embeddings table initialized successfully")
# Create index for faster lookups # Create index for faster lookups
logger.info("Creating idx_timestamp index if not exists")
cursor.execute( cursor.execute(
""" """
CREATE INDEX IF NOT EXISTS idx_timestamp ON chat_messages(timestamp) CREATE INDEX IF NOT EXISTS idx_timestamp ON chat_messages(timestamp)
""" """
) )
logger.info("idx_timestamp index created successfully")
logger.info("Creating idx_user_id index if not exists")
cursor.execute( cursor.execute(
""" """
CREATE INDEX IF NOT EXISTS idx_user_id ON chat_messages(user_id) CREATE INDEX IF NOT EXISTS idx_user_id ON chat_messages(user_id)
""" """
) )
logger.info("idx_user_id index created successfully")
conn.commit() conn.commit()
logger.info("Database initialization completed successfully")
conn.close() conn.close()
def _generate_embedding(self, text: str) -> List[float]: def _generate_embedding(self, text: str) -> List[float]:
"""Generate embedding for text using OpenAI API.""" """Generate embedding for text using OpenAI API."""
logger.debug(f"Generating embedding for text (length: {len(text)})")
try: try:
logger.info(f"Calling OpenAI API to generate embedding with model: {EMBEDDING_MODEL}")
response = self.client.embeddings.create( response = self.client.embeddings.create(
model=EMBEDDING_MODEL, input=text, encoding_format="float" model=EMBEDDING_MODEL, input=text, encoding_format="float"
) )
logger.debug("OpenAI API response received successfully")
# The embedding is returned as a nested list: [[embedding_values]] # The embedding is returned as a nested list: [[embedding_values]]
# We need to extract the inner list # We need to extract the inner list
embedding_data = response[0].embedding embedding_data = response[0].embedding
@@ -89,26 +114,38 @@ class ChatDatabase:
first_item = embedding_data[0] first_item = embedding_data[0]
if isinstance(first_item, list): if isinstance(first_item, list):
# Handle nested structure: [[values]] -> [values] # Handle nested structure: [[values]] -> [values]
logger.debug("Extracted embedding from nested structure [[values]]")
return first_item return first_item
else: else:
# Handle direct structure: [values] # Handle direct structure: [values]
logger.debug("Extracted embedding from direct structure [values]")
return embedding_data return embedding_data
logger.warning("Embedding data is empty or invalid")
return [] return []
except Exception as e: except Exception as e:
print(f"Error generating embedding: {e}") logger.error(f"Error generating embedding: {e}")
return None return None
def _vector_to_bytes(self, vector: List[float]) -> bytes: def _vector_to_bytes(self, vector: List[float]) -> bytes:
"""Convert vector to bytes for SQLite storage.""" """Convert vector to bytes for SQLite storage."""
return np.array(vector, dtype=np.float32).tobytes() logger.debug(f"Converting vector (length: {len(vector)}) to bytes")
result = np.array(vector, dtype=np.float32).tobytes()
logger.debug(f"Vector converted to {len(result)} bytes")
return result
def _bytes_to_vector(self, blob: bytes) -> np.ndarray: def _bytes_to_vector(self, blob: bytes) -> np.ndarray:
"""Convert bytes back to vector.""" """Convert bytes back to vector."""
return np.frombuffer(blob, dtype=np.float32) logger.debug(f"Converting {len(blob)} bytes back to vector")
result = np.frombuffer(blob, dtype=np.float32)
logger.debug(f"Vector reconstructed with {len(result)} dimensions")
return result
def _calculate_similarity(self, vec1: np.ndarray, vec2: np.ndarray) -> float: def _calculate_similarity(self, vec1: np.ndarray, vec2: np.ndarray) -> float:
"""Calculate cosine similarity between two vectors.""" """Calculate cosine similarity between two vectors."""
return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)) logger.debug(f"Calculating cosine similarity between vectors of dimension {len(vec1)}")
result = np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))
logger.debug(f"Similarity calculated: {result:.4f}")
return result
def add_message( def add_message(
self, self,
@@ -120,11 +157,13 @@ class ChatDatabase:
guild_id: Optional[str] = None, guild_id: Optional[str] = None,
) -> bool: ) -> bool:
"""Add a message to the database and generate its embedding.""" """Add a message to the database and generate its embedding."""
logger.info(f"Adding message {message_id} from user {username}")
conn = sqlite3.connect(self.db_path) conn = sqlite3.connect(self.db_path)
cursor = conn.cursor() cursor = conn.cursor()
try: try:
# Insert message # Insert message
logger.debug(f"Inserting message into chat_messages table: message_id={message_id}")
cursor.execute( cursor.execute(
""" """
INSERT OR REPLACE INTO chat_messages INSERT OR REPLACE INTO chat_messages
@@ -133,10 +172,13 @@ class ChatDatabase:
""", """,
(message_id, user_id, username, content, channel_id, guild_id), (message_id, user_id, username, content, channel_id, guild_id),
) )
logger.debug(f"Message {message_id} inserted into chat_messages table")
# Generate and store embedding # Generate and store embedding
logger.info(f"Generating embedding for message {message_id}")
embedding = self._generate_embedding(content) embedding = self._generate_embedding(content)
if embedding: if embedding:
logger.debug(f"Embedding generated successfully for message {message_id}, storing in database")
cursor.execute( cursor.execute(
""" """
INSERT OR REPLACE INTO message_embeddings INSERT OR REPLACE INTO message_embeddings
@@ -145,15 +187,20 @@ class ChatDatabase:
""", """,
(message_id, self._vector_to_bytes(embedding)), (message_id, self._vector_to_bytes(embedding)),
) )
logger.debug(f"Embedding stored in message_embeddings table for message {message_id}")
else:
logger.warning(f"Failed to generate embedding for message {message_id}, skipping embedding storage")
# Clean up old messages if exceeding limit # Clean up old messages if exceeding limit
logger.info("Checking if cleanup of old messages is needed")
self._cleanup_old_messages(cursor) self._cleanup_old_messages(cursor)
conn.commit() conn.commit()
logger.info(f"Successfully added message {message_id} to database")
return True return True
except Exception as e: except Exception as e:
print(f"Error adding message: {e}") logger.error(f"Error adding message {message_id}: {e}")
conn.rollback() conn.rollback()
return False return False
finally: finally:

129
main.py
View File

@@ -1,12 +1,18 @@
import discord import discord
from discord.ext import commands from discord.ext import commands
import requests
import os import os
import base64 import base64
from io import BytesIO from io import BytesIO
from openai import OpenAI from openai import OpenAI
import logging
from database import get_database, CustomBotManager from database import get_database, CustomBotManager
# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN", "placeholder") DISCORD_TOKEN = os.getenv("DISCORD_TOKEN", "placeholder")
OPENAI_API_ENDPOINT = os.getenv("OPENAI_API_ENDPOINT") OPENAI_API_ENDPOINT = os.getenv("OPENAI_API_ENDPOINT")
@@ -35,7 +41,9 @@ OPENAI_COMPLETIONS_URL = f"{OPENAI_API_ENDPOINT}/chat/completions"
@bot.event @bot.event
async def on_ready(): async def on_ready():
logger.info("Bot is starting up...")
print(f"Bot logged in as {bot.user}") print(f"Bot logged in as {bot.user}")
logger.info(f"Bot logged in as {bot.user}")
@bot.command(name="custom-bot") @bot.command(name="custom-bot")
@@ -45,51 +53,86 @@ async def custom_bot(ctx, bot_name: str, *, personality: str):
Usage: !custom-bot <bot_name> <personality_description> Usage: !custom-bot <bot_name> <personality_description>
Example: !custom-bot alfred you are a proper british butler Example: !custom-bot alfred you are a proper british butler
""" """
logger.info(
f"Custom bot command initiated by {ctx.author.name}: name='{bot_name}', personality length={len(personality)}"
)
# Validate bot name # Validate bot name
if not bot_name or len(bot_name) < 2 or len(bot_name) > 50: if not bot_name or len(bot_name) < 2 or len(bot_name) > 50:
logger.warning(
f"Invalid bot name from {ctx.author.name}: '{bot_name}' (length: {len(bot_name) if bot_name else 0})"
)
await ctx.send("❌ Invalid bot name. Name must be between 2 and 50 characters.") await ctx.send("❌ Invalid bot name. Name must be between 2 and 50 characters.")
return return
logger.info(f"Bot name validation passed for '{bot_name}'")
# Validate personality # Validate personality
if not personality or len(personality) < 10: if not personality or len(personality) < 10:
logger.warning(
f"Invalid personality from {ctx.author.name}: length={len(personality) if personality else 0}"
)
await ctx.send( await ctx.send(
"❌ Invalid personality. Description must be at least 10 characters." "❌ Invalid personality. Description must be at least 10 characters."
) )
return return
logger.info(f"Personality validation passed for bot '{bot_name}'")
# Create custom bot manager # Create custom bot manager
logger.info(f"Initializing CustomBotManager for user {ctx.author.name}")
custom_bot_manager = CustomBotManager() custom_bot_manager = CustomBotManager()
# Create the custom bot # Create the custom bot
logger.info(
f"Attempting to create custom bot '{bot_name}' for user {ctx.author.name}"
)
success = custom_bot_manager.create_custom_bot( success = custom_bot_manager.create_custom_bot(
bot_name=bot_name, system_prompt=personality, created_by=str(ctx.author.id) bot_name=bot_name, system_prompt=personality, created_by=str(ctx.author.id)
) )
if success: if success:
logger.info(
f"Successfully created custom bot '{bot_name}' for user {ctx.author.name}"
)
await ctx.send( await ctx.send(
f"✅ Custom bot **'{bot_name}'** has been created with personality: *{personality}*" f"✅ Custom bot **'{bot_name}'** has been created with personality: *{personality}*"
) )
await ctx.send(f"\nYou can now use this bot with: `!{bot_name} <your message>`") await ctx.send(f"\nYou can now use this bot with: `!{bot_name} <your message>`")
else: else:
logger.warning(
f"Failed to create custom bot '{bot_name}' for user {ctx.author.name}"
)
await ctx.send("❌ Failed to create custom bot. It may already exist.") await ctx.send("❌ Failed to create custom bot. It may already exist.")
@bot.command(name="list-custom-bots") @bot.command(name="list-custom-bots")
async def list_custom_bots(ctx): async def list_custom_bots(ctx):
"""List all custom bots available in the server""" """List all custom bots available in the server"""
logger.info(f"Listing custom bots requested by {ctx.author.name}")
# Create custom bot manager
logger.info("Initializing CustomBotManager to list custom bots")
custom_bot_manager = CustomBotManager() custom_bot_manager = CustomBotManager()
logger.info("Fetching list of custom bots from database")
bots = custom_bot_manager.list_custom_bots() bots = custom_bot_manager.list_custom_bots()
if not bots: if not bots:
logger.info(f"No custom bots found for user {ctx.author.name}")
await ctx.send( await ctx.send(
"No custom bots have been created yet. Use `!custom-bot <name> <personality>` to create one." "No custom bots have been created yet. Use `!custom-bot <name> <personality>` to create one."
) )
return return
logger.info(
f"Found {len(bots)} custom bots, displaying top 10 for {ctx.author.name}"
)
bot_list = "🤖 **Available Custom Bots**:\n\n" bot_list = "🤖 **Available Custom Bots**:\n\n"
for name, prompt, creator in bots[:10]: # Limit to 10 bots for name, prompt, creator in bots[:10]: # Limit to 10 bots
bot_list += f"• **{name}** (created by {creator})\n" bot_list += f"• **{name}** (created by {creator})\n"
logger.info(f"Sending bot list response to {ctx.author.name}")
await ctx.send(bot_list) await ctx.send(bot_list)
@@ -99,22 +142,48 @@ async def delete_custom_bot(ctx, bot_name: str):
Usage: !delete-custom-bot <bot_name> Usage: !delete-custom-bot <bot_name>
""" """
logger.info(
f"Delete custom bot command initiated by {ctx.author.name}: bot_name='{bot_name}'"
)
# Create custom bot manager
logger.info("Initializing CustomBotManager for delete operation")
custom_bot_manager = CustomBotManager() custom_bot_manager = CustomBotManager()
# Get bot info
logger.info(f"Looking up custom bot '{bot_name}' in database")
bot_info = custom_bot_manager.get_custom_bot(bot_name) bot_info = custom_bot_manager.get_custom_bot(bot_name)
if not bot_info: if not bot_info:
logger.warning(f"Custom bot '{bot_name}' not found by user {ctx.author.name}")
await ctx.send(f"❌ Custom bot '{bot_name}' not found.") await ctx.send(f"❌ Custom bot '{bot_name}' not found.")
return return
logger.info(f"Custom bot '{bot_name}' found, owned by user {bot_info[2]}")
# Check ownership
if bot_info[2] != str(ctx.author.id): if bot_info[2] != str(ctx.author.id):
logger.warning(
f"User {ctx.author.name} attempted to delete bot '{bot_name}' they don't own"
)
await ctx.send("❌ You can only delete your own custom bots.") await ctx.send("❌ You can only delete your own custom bots.")
return return
logger.info(f"User {ctx.author.name} is authorized to delete bot '{bot_name}'")
# Delete the bot
logger.info(f"Deleting custom bot '{bot_name}' from database")
success = custom_bot_manager.delete_custom_bot(bot_name) success = custom_bot_manager.delete_custom_bot(bot_name)
if success: if success:
logger.info(
f"Successfully deleted custom bot '{bot_name}' by user {ctx.author.name}"
)
await ctx.send(f"✅ Custom bot '{bot_name}' has been deleted.") await ctx.send(f"✅ Custom bot '{bot_name}' has been deleted.")
else: else:
logger.warning(
f"Failed to delete custom bot '{bot_name}' by user {ctx.author.name}"
)
await ctx.send("❌ Failed to delete custom bot.") await ctx.send("❌ Failed to delete custom bot.")
@@ -125,19 +194,34 @@ async def on_message(message):
if message.author == bot.user: if message.author == bot.user:
return return
logger.debug(
f"Processing message from {message.author.name}: '{message.content[:50]}...'"
)
ctx = await bot.get_context(message) ctx = await bot.get_context(message)
# Check if the message starts with a custom bot command # Check if the message starts with a custom bot command
content = message.content.lower() content = message.content.lower()
logger.info(f"Initializing CustomBotManager to check for custom bot commands")
custom_bot_manager = CustomBotManager() custom_bot_manager = CustomBotManager()
logger.info("Fetching list of custom bots to check for matching commands")
custom_bots = custom_bot_manager.list_custom_bots() custom_bots = custom_bot_manager.list_custom_bots()
logger.info(f"Checking {len(custom_bots)} custom bots for command match")
for bot_name, system_prompt, _ in custom_bots: for bot_name, system_prompt, _ in custom_bots:
# Check if message starts with the custom bot name followed by a space # Check if message starts with the custom bot name followed by a space
if content.startswith(f"!{bot_name} "): if content.startswith(f"!{bot_name} "):
logger.info(
f"Custom bot command detected: '{bot_name}' triggered by {message.author.name}"
)
# Extract the actual message (remove the bot name prefix) # Extract the actual message (remove the bot name prefix)
user_message = message.content[len(f"!{bot_name} ") :] user_message = message.content[len(f"!{bot_name} ") :]
logger.debug(
f"Extracted user message for bot '{bot_name}': '{user_message[:50]}...'"
)
# Prepare the payload with custom personality # Prepare the payload with custom personality
payload = { payload = {
@@ -154,6 +238,7 @@ async def on_message(message):
response_prefix = f"**{bot_name} response**" response_prefix = f"**{bot_name} response**"
logger.info(f"Sending request to OpenAI API for bot '{bot_name}'")
await handle_chat( await handle_chat(
ctx=ctx, ctx=ctx,
message=user_message, message=user_message,
@@ -259,12 +344,6 @@ async def handle_chat(ctx, *, message: str, payload: dict, response_prefix: str)
) )
return return
# Set headers
headers = {
"Authorization": f"Bearer {OPENAI_API_KEY}",
"Content-Type": "application/json",
}
# Get database instance # Get database instance
db = get_database() db = get_database()
@@ -283,16 +362,22 @@ async def handle_chat(ctx, *, message: str, payload: dict, response_prefix: str)
print(payload) print(payload)
try: try:
# Send request to OpenAI API # Initialize OpenAI client
response = requests.post( client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_ENDPOINT)
OPENAI_COMPLETIONS_URL, json=payload, headers=headers, timeout=300
)
response.raise_for_status()
result = response.json() # Call OpenAI API
response = client.chat.completions.create(
model=payload["model"],
messages=payload["messages"],
max_completion_tokens=MAX_COMPLETION_TOKENS,
frequency_penalty=1.5,
presence_penalty=1.5,
temperature=1,
seed=-1,
)
# Extract the generated text # Extract the generated text
generated_text = result["choices"][0]["message"]["content"].strip() generated_text = response.choices[0].message.content.strip()
# Store both user message and bot response in the database # Store both user message and bot response in the database
db.add_message( db.add_message(
@@ -343,16 +428,18 @@ async def call_llm(ctx, payload: dict) -> str:
} }
try: try:
# Send request to OpenAI API # Initialize OpenAI client
response = requests.post( client = OpenAI(api_key=OPENAI_API_KEY, base_url=OPENAI_API_ENDPOINT)
OPENAI_COMPLETIONS_URL, json=payload, headers=headers, timeout=300
)
response.raise_for_status()
result = response.json() # Call OpenAI API
response = client.chat.completions.create(
model=payload["model"],
messages=payload["messages"],
max_tokens=MAX_COMPLETION_TOKENS,
)
# Extract the generated text # Extract the generated text
generated_text = result["choices"][0]["message"]["content"].strip() generated_text = response.choices[0].message.content.strip()
print(generated_text) print(generated_text)
return generated_text return generated_text

View File

@@ -10,4 +10,5 @@ dependencies = [
"requests>=2.32.5", "requests>=2.32.5",
"types-requests>=2.32.4.20260107", "types-requests>=2.32.4.20260107",
"numpy>=1.24.0", "numpy>=1.24.0",
"pytest>=9.0.2",
] ]

110
uv.lock generated
View File

@@ -566,6 +566,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
] ]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]] [[package]]
name = "jiter" name = "jiter"
version = "0.13.0" version = "0.13.0"
@@ -968,6 +977,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" },
] ]
[[package]]
name = "packaging"
version = "26.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]] [[package]]
name = "propcache" name = "propcache"
version = "0.4.1" version = "0.4.1"
@@ -1215,6 +1242,33 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
] ]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pytest"
version = "9.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.5" version = "2.32.5"
@@ -1239,6 +1293,60 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
] ]
[[package]]
name = "tomli"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
{ url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
{ url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
{ url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
{ url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
{ url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
{ url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
{ url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
{ url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
{ url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
{ url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
{ url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
{ url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
{ url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
{ url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
{ url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
{ url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
{ url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
{ url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
{ url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
{ url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
{ url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
{ url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
{ url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
{ url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
{ url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
{ url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
{ url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
{ url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
{ url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
{ url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
{ url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
{ url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
{ url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
{ url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
{ url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
{ url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
{ url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
{ url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
{ url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
{ url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
{ url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
{ url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
{ url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
{ url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
{ url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
]
[[package]] [[package]]
name = "tqdm" name = "tqdm"
version = "4.67.3" version = "4.67.3"
@@ -1302,6 +1410,7 @@ dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "openai" }, { name = "openai" },
{ name = "pytest" },
{ name = "requests" }, { name = "requests" },
{ name = "types-requests" }, { name = "types-requests" },
] ]
@@ -1311,6 +1420,7 @@ requires-dist = [
{ name = "discord", specifier = ">=2.3.2" }, { name = "discord", specifier = ">=2.3.2" },
{ name = "numpy", specifier = ">=1.24.0" }, { name = "numpy", specifier = ">=1.24.0" },
{ name = "openai", specifier = ">=2.24.0" }, { name = "openai", specifier = ">=2.24.0" },
{ name = "pytest", specifier = ">=9.0.2" },
{ name = "requests", specifier = ">=2.32.5" }, { name = "requests", specifier = ">=2.32.5" },
{ name = "types-requests", specifier = ">=2.32.4.20260107" }, { name = "types-requests", specifier = ">=2.32.4.20260107" },
] ]