Add trace arg to chat endpoint

This commit is contained in:
Daniel Balcells 2025-06-04 15:39:09 -04:00
parent c22cc507b0
commit b4b8e8b0c3
7 changed files with 590 additions and 23 deletions

188
monitor_deriver_queue.py Normal file
View File

@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""
Monitor the Honcho deriver queue and show progress as messages are processed.
This is a read-only monitor that does not modify the queue in any way.
"""
import asyncio
import os
import sys
from datetime import datetime
from pathlib import Path
# Add src to path so we can import Honcho modules
sys.path.insert(0, str(Path(__file__).parent))
from tqdm import tqdm
from sqlalchemy import select, func, case
from sqlalchemy.ext.asyncio import AsyncSession
from dotenv import load_dotenv
# Import Honcho's actual models and dependencies
from src import models
from src.dependencies import tracked_db
load_dotenv()
async def get_queue_stats(db: AsyncSession):
"""Get current queue statistics (read-only)"""
# Count unprocessed messages
unprocessed_result = await db.execute(
select(func.count()).select_from(models.QueueItem).where(models.QueueItem.processed == False)
)
unprocessed_count = unprocessed_result.scalar() or 0
# Count total messages
total_result = await db.execute(
select(func.count()).select_from(models.QueueItem)
)
total_count = total_result.scalar() or 0
# Count messages by session
session_stats_result = await db.execute(
select(models.QueueItem.session_id, func.count())
.where(models.QueueItem.processed == False)
.group_by(models.QueueItem.session_id)
)
session_stats = list(session_stats_result)
sessions_with_messages = len(session_stats)
# Get active processing sessions
active_sessions_result = await db.execute(
select(func.count()).select_from(models.ActiveQueueSession)
)
active_processing = active_sessions_result.scalar() or 0
return unprocessed_count, total_count, sessions_with_messages, active_processing
async def get_session_details(db: AsyncSession):
"""Get details about sessions in the queue (read-only)"""
result = await db.execute(
select(
models.QueueItem.session_id,
func.count(models.QueueItem.id).label('message_count'),
func.sum(case((models.QueueItem.processed == True, 1), else_=0)).label('processed_count')
)
.group_by(models.QueueItem.session_id)
.order_by(func.count(models.QueueItem.id).desc())
.limit(10)
)
return result.all()
async def monitor_queue(poll_interval=2.0):
"""Monitor the queue and display progress (read-only)"""
try:
print("🔍 Honcho Deriver Queue Monitor")
print("================================\n")
print("Connecting to database...")
# Use Honcho's tracked_db for read-only access
async with tracked_db("queue_monitor_readonly") as db:
# Get initial stats
unprocessed, total, sessions_with_msgs, active_processing = await get_queue_stats(db)
if total == 0:
print("📭 Queue is empty! No messages in the queue.")
return
if unprocessed == 0:
print("✅ All messages have been processed!")
print(f" Total processed: {total}")
return
print(f"\n📊 Initial Queue Status:")
print(f" • Total messages: {total:,}")
print(f" • Unprocessed: {unprocessed:,}")
print(f" • Processed: {total - unprocessed:,}")
print(f" • Sessions with messages: {sessions_with_msgs}")
print(f" • Active processing sessions: {active_processing}")
# Show top sessions
print(f"\n📋 Top Sessions by Message Count:")
session_details = await get_session_details(db)
for session_id, msg_count, processed in session_details:
unproc = msg_count - (processed or 0)
print(f" • Session {session_id}: {msg_count} messages ({unproc} unprocessed)")
print(f"\n⏳ Monitoring queue (polling every {poll_interval}s)...")
print(" Press Ctrl+C to stop\n")
# Create progress bar
with tqdm(total=unprocessed, desc="Processing messages", unit="msg",
bar_format='{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}]') as pbar:
initial_unprocessed = unprocessed
last_unprocessed = unprocessed
start_time = asyncio.get_event_loop().time()
while unprocessed > 0:
await asyncio.sleep(poll_interval)
# Get updated stats (read-only)
async with tracked_db("queue_monitor_readonly") as db:
unprocessed, total, sessions_with_msgs, active_processing = await get_queue_stats(db)
# Update progress
processed_since_last = last_unprocessed - unprocessed
if processed_since_last > 0:
pbar.update(processed_since_last)
last_unprocessed = unprocessed
elif processed_since_last < 0:
# Handle case where new messages were added
print(f"\n⚠️ {-processed_since_last} new messages added to queue")
pbar.total = pbar.n + unprocessed
pbar.refresh()
last_unprocessed = unprocessed
# Calculate rate
elapsed = asyncio.get_event_loop().time() - start_time
processed_total = initial_unprocessed - unprocessed
rate = processed_total / elapsed if elapsed > 0 else 0
# Update description with current stats
pbar.set_description(
f"Processing (Active: {active_processing}, Sessions: {sessions_with_msgs})"
)
# Show additional stats
pbar.set_postfix_str(
f"Remaining: {unprocessed:,} | Rate: {rate:.1f} msg/s"
)
print(f"\n✅ Queue processing complete!")
print(f" • Total processed: {initial_unprocessed:,} messages")
print(f" • Time taken: {elapsed:.1f} seconds")
print(f" • Average rate: {rate:.1f} messages/second")
except KeyboardInterrupt:
print("\n\n⚠️ Monitoring stopped by user.")
except Exception as e:
print(f"\n❌ Error: {e}")
import traceback
traceback.print_exc()
async def main():
"""Main entry point"""
# Check if we can connect to the database
try:
async with tracked_db("queue_monitor_test") as db:
# Just test the connection
await db.execute(select(1))
except Exception as e:
print(f"❌ Failed to connect to database: {e}")
print("\nMake sure:")
print("1. PostgreSQL is running")
print("2. CONNECTION_URI environment variable is set correctly")
print("3. The database and tables exist (run migrations if needed)")
return
# Run the monitor
await monitor_queue(poll_interval=2.0)
if __name__ == "__main__":
# Run the async main function
asyncio.run(main())

View File

@ -25,6 +25,9 @@ dependencies = [
"langfuse>=2.57.1",
"pyjwt>=2.10.0",
"google-genai>=1.10.0",
"ipython>=8.18.1",
"asyncpg>=0.30.0",
"tqdm>=4.67.1",
]
[tool.uv]
dev-dependencies = [

View File

@ -51,7 +51,7 @@ class Dialectic:
self.client = ModelClient(
provider=DEF_DIALECTIC_PROVIDER, model=DEF_DIALECTIC_MODEL
)
self.system_prompt = """You are operating as a context service that helps maintain psychological understanding of users across applications. Alongside a query, you'll receive: 1) previously collected psychological context about the user that I've maintained, 2) a series of long-term facts about the user, and 3) their current conversation/interaction from the requesting application. Your goal is to analyze this information and provide theory-of-mind insights that help applications personalize their responses. Please respond in a brief, matter-of-fact, and appropriate manner to convey as much relevant information to the application based on its query and the user's most recent message. You are encouraged to provide any context from the provided resources that helps provide a more complete or nuanced understanding of the user, as long as it is somewhat relevant to the query. If the context provided doesn't help address the query, write absolutely NOTHING but "None"."""
self.system_prompt = """You are operating as a context service that helps maintain psychological understanding of users across applications. Alongside a query, you'll receive: 1) previously collected psychological context about the user that I've maintained, 2) a series of long-term facts about the user, and 3) their current conversation/interaction from the requesting application. Your goal is to analyze this information and provide theory-of-mind insights that help applications personalize their responses. Please respond in a brief, matter-of-fact, and appropriate manner to convey as much relevant information to the application based on its query and the user's most recent message. You are encouraged to provide any context from the provided resources that helps provide a more complete or nuanced understanding of the user, as long as it is somewhat relevant to the query. If the context provided doesn't help address the query, write absolutely NOTHING but "No information available". If you are provided in the query with an alternative way to indicate that there is no information available, please use that instead."""
@ai_track("Dialectic Call")
@observe()
@ -136,6 +136,7 @@ async def chat(
session_id: str,
queries: str | list[str],
stream: bool = False,
include_trace: bool = False,
) -> schemas.DialecticResponse | MessageStreamManager:
"""
Chat with the Dialectic API using on-demand user representation generation.
@ -195,26 +196,29 @@ async def chat(
collection_id = (
collection.public_id
) # Extract the ID while session is active
facts = await get_long_term_facts(final_query, app_id, user_id, collection_id)
return facts
fact_docs = await get_long_term_facts(final_query, app_id, user_id, collection_id)
return fact_docs
long_term_task = asyncio.create_task(fetch_long_term())
short_term_task = asyncio.create_task(run_tom_inference(chat_history, session_id))
facts, tom_inference = await asyncio.gather(long_term_task, short_term_task)
logger.debug(f"Retrieved {len(facts)} facts from long-term memory")
fact_docs, tom_inference = await asyncio.gather(long_term_task, short_term_task)
logger.debug(f"Facts docs: {fact_docs}")
logger.debug(f"Tom inference: {tom_inference}")
logger.debug(f"Retrieved {len(fact_docs)} facts from long-term memory")
logger.debug(f"TOM inference completed with {len(tom_inference)} characters")
# Generate a fresh user representation
logger.debug("Generating user representation")
async with tracked_db("chat.generate_user_representation") as db_rep:
fact_strings = [doc.content for doc in fact_docs]
user_representation = await generate_user_representation(
app_id=app_id,
user_id=user_id,
session_id=session_id,
chat_history=chat_history,
tom_inference=tom_inference,
facts=facts,
facts=fact_strings,
db=db_rep,
message_id=latest_message_id,
with_inference=False,
@ -256,12 +260,46 @@ async def chat(
logger.debug(
f"Dialectic response received in {query_time:.2f}s (total: {total_time:.2f}s)"
)
return schemas.DialecticResponse(content=response[0]["text"])
trace_obj = None
if include_trace:
# Build FactWithSource list and fetch source messages in batch
message_ids = [doc.h_metadata.get("message_id") for doc in fact_docs if doc.h_metadata.get("message_id")]
message_contents: dict[str, str] = {}
if message_ids:
async with tracked_db("chat.load_source_messages") as db_src:
stmt = select(models.Message).where(models.Message.public_id.in_(message_ids))
result = await db_src.execute(stmt)
for msg in result.scalars().all():
message_contents[msg.public_id] = msg.content
fact_items: list[schemas.FactWithSource] = []
for doc in fact_docs:
msg_id = doc.h_metadata.get("message_id")
msg_content = message_contents.get(msg_id) if isinstance(msg_id, str) else None
fact_items.append(
schemas.FactWithSource(
fact=doc.content,
message_id=msg_id if isinstance(msg_id, str) else None,
message_content=msg_content,
)
)
trace_obj = schemas.ChatTrace(
queries=questions,
facts=fact_items,
tom_inference=tom_inference,
user_representation=user_representation,
latency_ms=total_time * 1000,
)
return schemas.DialecticResponse(content=response[0]["text"], trace=trace_obj)
async def get_long_term_facts(
query: str, app_id: str, user_id: str, collection_id: str
) -> list[str]:
) -> list[schemas.Document]:
"""
Generate queries based on the dialectic query and retrieve relevant facts.
@ -281,7 +319,7 @@ async def get_long_term_facts(
logger.debug(f"Generated {len(search_queries)} semantic queries: {search_queries}")
# Create a list of coroutines, one for each query
async def execute_query(i: int, search_query: str) -> list[str]:
async def execute_query(i: int, search_query: str) -> list[schemas.Document]:
logger.debug(f"Starting query {i + 1}/{len(search_queries)}: {search_query}")
query_start = asyncio.get_event_loop().time()
query_embedding_store = CollectionEmbeddingStore(
@ -289,12 +327,12 @@ async def get_long_term_facts(
user_id=user_id,
collection_id=collection_id,
)
facts = await query_embedding_store.get_relevant_facts(
docs = await query_embedding_store.get_relevant_facts(
search_query, top_k=10, max_distance=0.85
)
query_time = asyncio.get_event_loop().time() - query_start
logger.debug(f"Query {i + 1} retrieved {len(facts)} facts in {query_time:.2f}s")
return facts
logger.debug(f"Query {i + 1} retrieved {len(docs)} facts in {query_time:.2f}s")
return docs
# Execute all queries in parallel
query_tasks = [
@ -303,15 +341,16 @@ async def get_long_term_facts(
all_facts_lists = await asyncio.gather(*query_tasks)
# Combine all facts into a single set to remove duplicates
retrieved_facts = set()
for facts in all_facts_lists:
retrieved_facts.update(facts)
retrieved_docs: dict[str, schemas.Document] = {}
for docs in all_facts_lists:
for doc in docs:
retrieved_docs[doc.public_id] = doc # deduplicate by document ID
total_time = asyncio.get_event_loop().time() - fact_start_time
logger.debug(
f"Total fact retrieval completed in {total_time:.2f}s with {len(retrieved_facts)} unique facts"
f"Total fact retrieval completed in {total_time:.2f}s with {len(retrieved_docs)} unique facts"
)
return list(retrieved_facts)
return list(retrieved_docs.values())
async def run_tom_inference(chat_history: str, session_id: str) -> str:

View File

@ -19,7 +19,7 @@ class CollectionEmbeddingStore:
facts: list[str],
replace_duplicates: bool = True,
similarity_threshold: float = 0.85,
message_id: str = None,
message_id: str | None = None,
) -> None:
"""Save facts to the collection.
@ -50,7 +50,7 @@ class CollectionEmbeddingStore:
async def get_relevant_facts(
self, query: str, top_k: int = 5, max_distance: float = 0.3
) -> list[str]:
) -> list[schemas.Document]:
"""Retrieve the most relevant facts for a given query.
Args:
@ -62,7 +62,7 @@ class CollectionEmbeddingStore:
List of facts sorted by relevance
"""
async with tracked_db("embedding_store.get_relevant_facts") as db:
documents = await crud.query_documents(
documents_seq = await crud.query_documents(
db,
app_id=self.app_id,
user_id=self.user_id,
@ -72,7 +72,12 @@ class CollectionEmbeddingStore:
top_k=top_k,
)
return [doc.content for doc in documents]
# Convert ORM objects to Pydantic models while the session is still open
documents: list[schemas.Document] = [
schemas.Document.model_validate(doc) for doc in documents_seq
]
return documents
async def remove_duplicates(
self, facts: list[str], similarity_threshold: float = 0.85

View File

@ -219,12 +219,25 @@ async def chat(
):
"""Chat with the Dialectic API"""
if options.include_trace:
# Force non-streaming when trace requested
return await agent.chat(
app_id=app_id,
user_id=user_id,
session_id=session_id,
queries=options.queries,
stream=False,
include_trace=True,
)
if not options.stream:
return await agent.chat(
app_id=app_id,
user_id=user_id,
session_id=session_id,
queries=options.queries,
stream=False,
include_trace=False,
)
else:
@ -236,6 +249,7 @@ async def chat(
session_id=session_id,
queries=options.queries,
stream=True,
include_trace=False,
)
if type(stream) is AsyncMessageStreamManager:
async with stream as stream_manager:

View File

@ -268,9 +268,28 @@ class Document(DocumentBase):
)
class FactWithSource(BaseModel):
"""A retrieved fact along with a snippet of its source message."""
fact: str
message_id: str | None = None
message_content: str | None = None
class ChatTrace(BaseModel):
"""Debug payload returned when include_trace=True."""
queries: list[str]
facts: list[FactWithSource]
tom_inference: str
user_representation: str | None = None
latency_ms: float | None = None
class DialecticOptions(BaseModel):
queries: str | list[str]
stream: bool = False
include_trace: bool = False
@field_validator("queries")
def validate_queries(cls, v):
@ -286,9 +305,17 @@ class DialecticOptions(BaseModel):
raise ValueError("One or more queries too long")
return v
@field_validator("include_trace")
def validate_stream_and_trace(cls, v, values):
# Ensure stream is False when include_trace is True
if v and values.get("stream", False):
raise ValueError("Streaming is not supported when include_trace=True")
return v
class DialecticResponse(BaseModel):
content: str
trace: ChatTrace | None = None
class MessageBatchCreate(BaseModel):

295
uv.lock
View File

@ -2,7 +2,8 @@ version = 1
revision = 1
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version >= '3.11'",
"python_full_version == '3.10.*'",
"python_full_version < '3.10'",
]
@ -62,6 +63,75 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
]
[[package]]
name = "asttokens"
version = "3.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 },
]
[[package]]
name = "async-timeout"
version = "5.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 },
]
[[package]]
name = "asyncpg"
version = "0.30.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "async-timeout", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2f/4c/7c991e080e106d854809030d8584e15b2e996e26f16aee6d757e387bc17d/asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851", size = 957746 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bb/07/1650a8c30e3a5c625478fa8aafd89a8dd7d85999bf7169b16f54973ebf2c/asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e", size = 673143 },
{ url = "https://files.pythonhosted.org/packages/a0/9a/568ff9b590d0954553c56806766914c149609b828c426c5118d4869111d3/asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0", size = 645035 },
{ url = "https://files.pythonhosted.org/packages/de/11/6f2fa6c902f341ca10403743701ea952bca896fc5b07cc1f4705d2bb0593/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f", size = 2912384 },
{ url = "https://files.pythonhosted.org/packages/83/83/44bd393919c504ffe4a82d0aed8ea0e55eb1571a1dea6a4922b723f0a03b/asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af", size = 2947526 },
{ url = "https://files.pythonhosted.org/packages/08/85/e23dd3a2b55536eb0ded80c457b0693352262dc70426ef4d4a6fc994fa51/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75", size = 2895390 },
{ url = "https://files.pythonhosted.org/packages/9b/26/fa96c8f4877d47dc6c1864fef5500b446522365da3d3d0ee89a5cce71a3f/asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f", size = 3015630 },
{ url = "https://files.pythonhosted.org/packages/34/00/814514eb9287614188a5179a8b6e588a3611ca47d41937af0f3a844b1b4b/asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf", size = 568760 },
{ url = "https://files.pythonhosted.org/packages/f0/28/869a7a279400f8b06dd237266fdd7220bc5f7c975348fea5d1e6909588e9/asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50", size = 625764 },
{ url = "https://files.pythonhosted.org/packages/4c/0e/f5d708add0d0b97446c402db7e8dd4c4183c13edaabe8a8500b411e7b495/asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a", size = 674506 },
{ url = "https://files.pythonhosted.org/packages/6a/a0/67ec9a75cb24a1d99f97b8437c8d56da40e6f6bd23b04e2f4ea5d5ad82ac/asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed", size = 645922 },
{ url = "https://files.pythonhosted.org/packages/5c/d9/a7584f24174bd86ff1053b14bb841f9e714380c672f61c906eb01d8ec433/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a", size = 3079565 },
{ url = "https://files.pythonhosted.org/packages/a0/d7/a4c0f9660e333114bdb04d1a9ac70db690dd4ae003f34f691139a5cbdae3/asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956", size = 3109962 },
{ url = "https://files.pythonhosted.org/packages/3c/21/199fd16b5a981b1575923cbb5d9cf916fdc936b377e0423099f209e7e73d/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056", size = 3064791 },
{ url = "https://files.pythonhosted.org/packages/77/52/0004809b3427534a0c9139c08c87b515f1c77a8376a50ae29f001e53962f/asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454", size = 3188696 },
{ url = "https://files.pythonhosted.org/packages/52/cb/fbad941cd466117be58b774a3f1cc9ecc659af625f028b163b1e646a55fe/asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d", size = 567358 },
{ url = "https://files.pythonhosted.org/packages/3c/0a/0a32307cf166d50e1ad120d9b81a33a948a1a5463ebfa5a96cc5606c0863/asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f", size = 629375 },
{ url = "https://files.pythonhosted.org/packages/4b/64/9d3e887bb7b01535fdbc45fbd5f0a8447539833b97ee69ecdbb7a79d0cb4/asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e", size = 673162 },
{ url = "https://files.pythonhosted.org/packages/6e/eb/8b236663f06984f212a087b3e849731f917ab80f84450e943900e8ca4052/asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a", size = 637025 },
{ url = "https://files.pythonhosted.org/packages/cc/57/2dc240bb263d58786cfaa60920779af6e8d32da63ab9ffc09f8312bd7a14/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3", size = 3496243 },
{ url = "https://files.pythonhosted.org/packages/f4/40/0ae9d061d278b10713ea9021ef6b703ec44698fe32178715a501ac696c6b/asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737", size = 3575059 },
{ url = "https://files.pythonhosted.org/packages/c3/75/d6b895a35a2c6506952247640178e5f768eeb28b2e20299b6a6f1d743ba0/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a", size = 3473596 },
{ url = "https://files.pythonhosted.org/packages/c8/e7/3693392d3e168ab0aebb2d361431375bd22ffc7b4a586a0fc060d519fae7/asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af", size = 3641632 },
{ url = "https://files.pythonhosted.org/packages/32/ea/15670cea95745bba3f0352341db55f506a820b21c619ee66b7d12ea7867d/asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e", size = 560186 },
{ url = "https://files.pythonhosted.org/packages/7e/6b/fe1fad5cee79ca5f5c27aed7bd95baee529c1bf8a387435c8ba4fe53d5c1/asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305", size = 621064 },
{ url = "https://files.pythonhosted.org/packages/3a/22/e20602e1218dc07692acf70d5b902be820168d6282e69ef0d3cb920dc36f/asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70", size = 670373 },
{ url = "https://files.pythonhosted.org/packages/3d/b3/0cf269a9d647852a95c06eb00b815d0b95a4eb4b55aa2d6ba680971733b9/asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3", size = 634745 },
{ url = "https://files.pythonhosted.org/packages/8e/6d/a4f31bf358ce8491d2a31bfe0d7bcf25269e80481e49de4d8616c4295a34/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33", size = 3512103 },
{ url = "https://files.pythonhosted.org/packages/96/19/139227a6e67f407b9c386cb594d9628c6c78c9024f26df87c912fabd4368/asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4", size = 3592471 },
{ url = "https://files.pythonhosted.org/packages/67/e4/ab3ca38f628f53f0fd28d3ff20edff1c975dd1cb22482e0061916b4b9a74/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4", size = 3496253 },
{ url = "https://files.pythonhosted.org/packages/ef/5f/0bf65511d4eeac3a1f41c54034a492515a707c6edbc642174ae79034d3ba/asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba", size = 3662720 },
{ url = "https://files.pythonhosted.org/packages/e7/31/1513d5a6412b98052c3ed9158d783b1e09d0910f51fbe0e05f56cc370bc4/asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590", size = 560404 },
{ url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623 },
{ url = "https://files.pythonhosted.org/packages/b4/82/d94f3ed6921136a0ef40a825740eda19437ccdad7d92d924302dca1d5c9e/asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad", size = 673026 },
{ url = "https://files.pythonhosted.org/packages/4e/db/7db8b73c5d86ec9a21807f405e0698f8f637a8a3ca14b7b6fd4259b66bcf/asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff", size = 644732 },
{ url = "https://files.pythonhosted.org/packages/eb/a0/1f1910659d08050cb3e8f7d82b32983974798d7fd4ddf7620b8e2023d4ac/asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708", size = 2911761 },
{ url = "https://files.pythonhosted.org/packages/4d/53/5aa0d92488ded50bab2b6626430ed9743b0b7e2d864a2b435af1ccbf219a/asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144", size = 2946595 },
{ url = "https://files.pythonhosted.org/packages/c5/cd/d6d548d8ee721f4e0f7fbbe509bbac140d556c2e45814d945540c96cf7d4/asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb", size = 2890135 },
{ url = "https://files.pythonhosted.org/packages/46/f0/28df398b685dabee20235e24880e1f6486d84ae7e6b0d11bdebc17740e7a/asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547", size = 3011889 },
{ url = "https://files.pythonhosted.org/packages/c8/07/8c7ffe6fe8bccff9b12fcb6410b1b2fa74b917fd8b837806a40217d5228b/asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a", size = 569406 },
{ url = "https://files.pythonhosted.org/packages/05/51/f59e4df6d9b8937530d4b9fdee1598b93db40c631fe94ff3ce64207b7a95/asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773", size = 626581 },
]
[[package]]
name = "attrs"
version = "25.3.0"
@ -263,6 +333,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435 },
]
[[package]]
name = "decorator"
version = "5.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 },
]
[[package]]
name = "distro"
version = "1.9.0"
@ -303,6 +382,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 },
]
[[package]]
name = "executing"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 },
]
[[package]]
name = "fastapi"
version = "0.115.12"
@ -468,11 +556,15 @@ source = { virtual = "." }
dependencies = [
{ name = "alembic" },
{ name = "anthropic" },
{ name = "asyncpg" },
{ name = "fastapi", extra = ["standard"] },
{ name = "fastapi-pagination" },
{ name = "google-genai" },
{ name = "greenlet" },
{ name = "httpx" },
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
{ name = "ipython", version = "8.36.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
{ name = "ipython", version = "9.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "langfuse" },
{ name = "nanoid" },
{ name = "openai" },
@ -483,6 +575,7 @@ dependencies = [
{ name = "rich" },
{ name = "sentry-sdk", extra = ["anthropic", "fastapi", "sqlalchemy"] },
{ name = "sqlalchemy" },
{ name = "tqdm" },
]
[package.dev-dependencies]
@ -500,11 +593,13 @@ dev = [
requires-dist = [
{ name = "alembic", specifier = ">=1.14.0" },
{ name = "anthropic", specifier = ">=0.36.0" },
{ name = "asyncpg", specifier = ">=0.30.0" },
{ name = "fastapi", extras = ["standard"], specifier = ">=0.111.0" },
{ name = "fastapi-pagination", specifier = ">=0.12.24" },
{ name = "google-genai", specifier = ">=1.10.0" },
{ name = "greenlet", specifier = ">=3.0.3" },
{ name = "httpx", specifier = ">=0.27.0" },
{ name = "ipython", specifier = ">=8.18.1" },
{ name = "langfuse", specifier = ">=2.57.1" },
{ name = "nanoid", specifier = ">=2.0.0" },
{ name = "openai", specifier = ">=1.43.0" },
@ -515,6 +610,7 @@ requires-dist = [
{ name = "rich", specifier = ">=13.7.1" },
{ name = "sentry-sdk", extras = ["anthropic", "fastapi", "sqlalchemy"], specifier = ">=2.3.1" },
{ name = "sqlalchemy", specifier = ">=2.0.30" },
{ name = "tqdm", specifier = ">=4.67.1" },
]
[package.metadata.requires-dev]
@ -634,6 +730,105 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/12/c9/6869a1dcf4aaf309b9543ec070be3ec3adebee7c9bec9af8c230494134b9/interrogate-1.7.0-py3-none-any.whl", hash = "sha256:b13ff4dd8403369670e2efe684066de9fcb868ad9d7f2b4095d8112142dc9d12", size = 46982 },
]
[[package]]
name = "ipython"
version = "8.18.1"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.10'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
{ name = "decorator", marker = "python_full_version < '3.10'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.10'" },
{ name = "jedi", marker = "python_full_version < '3.10'" },
{ name = "matplotlib-inline", marker = "python_full_version < '3.10'" },
{ name = "pexpect", marker = "python_full_version < '3.10' and sys_platform != 'win32'" },
{ name = "prompt-toolkit", marker = "python_full_version < '3.10'" },
{ name = "pygments", marker = "python_full_version < '3.10'" },
{ name = "stack-data", marker = "python_full_version < '3.10'" },
{ name = "traitlets", marker = "python_full_version < '3.10'" },
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161 },
]
[[package]]
name = "ipython"
version = "8.36.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version == '3.10.*'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version == '3.10.*' and sys_platform == 'win32'" },
{ name = "decorator", marker = "python_full_version == '3.10.*'" },
{ name = "exceptiongroup", marker = "python_full_version == '3.10.*'" },
{ name = "jedi", marker = "python_full_version == '3.10.*'" },
{ name = "matplotlib-inline", marker = "python_full_version == '3.10.*'" },
{ name = "pexpect", marker = "python_full_version == '3.10.*' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "prompt-toolkit", marker = "python_full_version == '3.10.*'" },
{ name = "pygments", marker = "python_full_version == '3.10.*'" },
{ name = "stack-data", marker = "python_full_version == '3.10.*'" },
{ name = "traitlets", marker = "python_full_version == '3.10.*'" },
{ name = "typing-extensions", marker = "python_full_version == '3.10.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a2/9f/d9a73710df947b7804bd9d93509463fb3a89e0ddc99c9fcc67279cddbeb6/ipython-8.36.0.tar.gz", hash = "sha256:24658e9fe5c5c819455043235ba59cfffded4a35936eefceceab6b192f7092ff", size = 5604997 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/d7/c1c9f371790b3a181e343c4815a361e5a0cc7d90ef6642d64ba5d05de289/ipython-8.36.0-py3-none-any.whl", hash = "sha256:12b913914d010dcffa2711505ec8be4bf0180742d97f1e5175e51f22086428c1", size = 831074 },
]
[[package]]
name = "ipython"
version = "9.2.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11'",
]
dependencies = [
{ name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" },
{ name = "decorator", marker = "python_full_version >= '3.11'" },
{ name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" },
{ name = "jedi", marker = "python_full_version >= '3.11'" },
{ name = "matplotlib-inline", marker = "python_full_version >= '3.11'" },
{ name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
{ name = "prompt-toolkit", marker = "python_full_version >= '3.11'" },
{ name = "pygments", marker = "python_full_version >= '3.11'" },
{ name = "stack-data", marker = "python_full_version >= '3.11'" },
{ name = "traitlets", marker = "python_full_version >= '3.11'" },
{ name = "typing-extensions", marker = "python_full_version == '3.11.*'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/02/63a84444a7409b3c0acd1de9ffe524660e0e5d82ee473e78b45e5bfb64a4/ipython-9.2.0.tar.gz", hash = "sha256:62a9373dbc12f28f9feaf4700d052195bf89806279fc8ca11f3f54017d04751b", size = 4424394 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/ce/5e897ee51b7d26ab4e47e5105e7368d40ce6cfae2367acdf3165396d50be/ipython-9.2.0-py3-none-any.whl", hash = "sha256:fef5e33c4a1ae0759e0bba5917c9db4eb8c53fee917b6a526bd973e1ca5159f6", size = 604277 },
]
[[package]]
name = "ipython-pygments-lexers"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pygments", marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 },
]
[[package]]
name = "jedi"
version = "0.19.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "parso" },
]
sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 },
]
[[package]]
name = "jinja2"
version = "3.1.6"
@ -828,6 +1023,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/73/085399401383ce949f727afec55ec3abd76648d04b9f22e1c0e99cb4bec3/MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", size = 15506 },
]
[[package]]
name = "matplotlib-inline"
version = "0.1.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "traitlets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 },
]
[[package]]
name = "mdurl"
version = "0.1.2"
@ -906,7 +1113,8 @@ name = "numpy"
version = "2.2.4"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.10'",
"python_full_version >= '3.11'",
"python_full_version == '3.10.*'",
]
sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 }
wheels = [
@ -994,6 +1202,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
]
[[package]]
name = "parso"
version = "0.8.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 },
]
[[package]]
name = "pexpect"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ptyprocess" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 },
]
[[package]]
name = "pgvector"
version = "0.4.0"
@ -1016,6 +1245,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
]
[[package]]
name = "prompt-toolkit"
version = "3.0.51"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
]
[[package]]
name = "psycopg"
version = "3.2.6"
@ -1096,6 +1337,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/27/b6/e48cafe90e46b82b1393276ce6d92ddb7650c39aba8aa0256affbaa94c36/psycopg_binary-3.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:ea158665676f42b19585dfe948071d3c5f28276f84a97522fb2e82c1d9194563", size = 2793902 },
]
[[package]]
name = "ptyprocess"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 },
]
[[package]]
name = "pure-eval"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 },
]
[[package]]
name = "py"
version = "1.11.0"
@ -1571,6 +1830,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d5/f0/dc4757b83ac1ab853cf222df8535ed73973e0c203d983982ba7b8bc60508/SQLAlchemy_Utils-0.41.2-py3-none-any.whl", hash = "sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e", size = 93083 },
]
[[package]]
name = "stack-data"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asttokens" },
{ name = "executing" },
{ name = "pure-eval" },
]
sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 },
]
[[package]]
name = "starlette"
version = "0.46.1"
@ -1644,6 +1917,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
]
[[package]]
name = "traitlets"
version = "5.14.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 },
]
[[package]]
name = "typer"
version = "0.15.2"
@ -1842,6 +2124,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/e9/3cbcf4d70cd0b6d3f30631deae1bf37cc0be39887ca327a44462fe546bf5/watchfiles-1.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e0227b8ed9074c6172cf55d85b5670199c99ab11fd27d2c473aa30aec67ee42", size = 452488 },
]
[[package]]
name = "wcwidth"
version = "0.2.13"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 },
]
[[package]]
name = "websockets"
version = "15.0.1"