refactor: replace mirascope with handrolled client (#202)
* feat: add optional JWT and webhook secrets to honcho instance creation * chore: ignore spurious warnings * feat: add response format if using gpt-5 model family * feat: add response models to all apis except anthropic * fix: raise NotImplementedError for response models in AsyncAnthropic client * chore: address review * chore: add tests, cleanup * fix: use tenacity instead of custom fns * fix: pass all params to streaming, nonblocking streaming * chore: fix test mock
This commit is contained in:
parent
3f47866ae0
commit
5d88c459b8
|
|
@ -9,6 +9,7 @@ readme = "README.md"
|
|||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"fastapi[standard]>=0.111.0",
|
||||
"groq>=0.31.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"sqlalchemy>=2.0.30",
|
||||
"fastapi-pagination>=0.12.24",
|
||||
|
|
@ -21,12 +22,13 @@ dependencies = [
|
|||
"nanoid>=2.0.0",
|
||||
"alembic>=1.14.0",
|
||||
"pyjwt>=2.10.0",
|
||||
"tenacity>=9.1.2",
|
||||
"tiktoken>=0.9.0",
|
||||
"mirascope[anthropic,google,groq,langfuse]>=1.25.5",
|
||||
"langfuse>=3.3.2",
|
||||
"openai>=1.99.7",
|
||||
"pydantic>=2.11.7",
|
||||
"pydantic-settings>=2.10.1",
|
||||
"google-generativeai>=0.8.5",
|
||||
"google-genai>=1.32.0",
|
||||
"pdfplumber>=0.11.7",
|
||||
"typing-extensions>=4.11.0",
|
||||
]
|
||||
|
|
@ -93,6 +95,6 @@ reportUnusedCallResult = false
|
|||
reportCallInDefaultInitializer = false
|
||||
reportAny = false
|
||||
reportExplicitAny = false
|
||||
allowedUntypedLibraries = ["langfuse", "langfuse.decorators", "mirascope"]
|
||||
allowedUntypedLibraries = ["langfuse"]
|
||||
reportImplicitOverride = false
|
||||
reportImportCycles = false
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from pydantic_settings import (
|
|||
SettingsConfigDict,
|
||||
)
|
||||
|
||||
from src.utils.types import Providers
|
||||
from src.utils.types import SupportedProviders
|
||||
|
||||
# Load .env file for local development.
|
||||
# Make sure this is called before AppSettings is instantiated if you rely on .env for AppSettings construction.
|
||||
|
|
@ -187,14 +187,14 @@ class DeriverSettings(HonchoSettings):
|
|||
] = 1.0
|
||||
STALE_SESSION_TIMEOUT_MINUTES: Annotated[int, Field(default=5, gt=0, le=1440)] = 5
|
||||
|
||||
PROVIDER: Providers = "google"
|
||||
PROVIDER: SupportedProviders = "google"
|
||||
MODEL: str = "gemini-2.5-flash"
|
||||
|
||||
MAX_OUTPUT_TOKENS: Annotated[int, Field(default=2500, gt=0, le=100_000)] = 2500
|
||||
# Thinking budget tokens are only applied when using Anthropic as provider
|
||||
THINKING_BUDGET_TOKENS: Annotated[int, Field(default=1024, gt=0, le=5000)] = 1024
|
||||
|
||||
PEER_CARD_PROVIDER: Providers = "openai"
|
||||
PEER_CARD_PROVIDER: SupportedProviders = "openai"
|
||||
PEER_CARD_MODEL: str = "gpt-5-nano-2025-08-07"
|
||||
# Note: peer cards should be very short, but GPT-5 models need output tokens for thinking which cannot be turned off...
|
||||
PEER_CARD_MAX_OUTPUT_TOKENS: Annotated[
|
||||
|
|
@ -216,11 +216,11 @@ class DeriverSettings(HonchoSettings):
|
|||
class DialecticSettings(HonchoSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="DIALECTIC_", extra="ignore") # pyright: ignore
|
||||
|
||||
PROVIDER: Providers = "anthropic"
|
||||
PROVIDER: SupportedProviders = "anthropic"
|
||||
MODEL: str = "claude-sonnet-4-20250514"
|
||||
|
||||
PERFORM_QUERY_GENERATION: bool = False
|
||||
QUERY_GENERATION_PROVIDER: Providers = "groq"
|
||||
QUERY_GENERATION_PROVIDER: SupportedProviders = "groq"
|
||||
QUERY_GENERATION_MODEL: str = "llama-3.1-8b-instant"
|
||||
|
||||
MAX_OUTPUT_TOKENS: Annotated[int, Field(default=2500, gt=0, le=100_000)] = 2500
|
||||
|
|
@ -243,7 +243,7 @@ class SummarySettings(HonchoSettings):
|
|||
MESSAGES_PER_SHORT_SUMMARY: Annotated[int, Field(default=20, gt=0, le=100)] = 20
|
||||
MESSAGES_PER_LONG_SUMMARY: Annotated[int, Field(default=60, gt=0, le=500)] = 60
|
||||
|
||||
PROVIDER: Providers = "openai"
|
||||
PROVIDER: SupportedProviders = "openai"
|
||||
MODEL: str = "gpt-4o-mini-2024-07-18"
|
||||
MAX_TOKENS_SHORT: Annotated[int, Field(default=1000, gt=0, le=10_000)] = 1000
|
||||
MAX_TOKENS_LONG: Annotated[int, Field(default=4000, gt=0, le=20_000)] = 4000
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from logging import getLogger
|
||||
from typing import Any, cast
|
||||
from typing import Any, Final, cast
|
||||
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
|
@ -12,7 +12,7 @@ from src.utils.shared_models import ObservationDict
|
|||
logger = getLogger(__name__)
|
||||
|
||||
# The collection name for documents that make up a peer's global representation
|
||||
GLOBAL_REPRESENTATION_COLLECTION_NAME = "global_representation"
|
||||
GLOBAL_REPRESENTATION_COLLECTION_NAME: Final[str] = "global_representation"
|
||||
|
||||
# The key for the working representation in the session peer's internal_metadata
|
||||
WORKING_REPRESENTATION_METADATA_KEY = "working_representation"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import logging
|
|||
from typing import Any
|
||||
|
||||
import sentry_sdk
|
||||
from langfuse.decorators import langfuse_context
|
||||
from langfuse import get_client
|
||||
from pydantic import ValidationError
|
||||
from rich.console import Console
|
||||
|
||||
|
|
@ -24,6 +24,8 @@ logging.getLogger("sqlalchemy.engine.Engine").disabled = True
|
|||
|
||||
console = Console(markup=True)
|
||||
|
||||
lf = get_client()
|
||||
|
||||
|
||||
async def process_item(task_type: str, payload: dict[str, Any]) -> None:
|
||||
"""Validate an incoming queue payload and dispatch it to the appropriate handler.
|
||||
|
|
@ -46,7 +48,7 @@ async def process_item(task_type: str, payload: dict[str, Any]) -> None:
|
|||
logger.debug("Finished processing webhook %s", validated.event_type)
|
||||
elif task_type == "summary":
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_trace( # type: ignore
|
||||
lf.update_current_trace( # type: ignore
|
||||
metadata={
|
||||
"critical_analysis_model": settings.DERIVER.MODEL,
|
||||
}
|
||||
|
|
@ -61,7 +63,7 @@ async def process_item(task_type: str, payload: dict[str, Any]) -> None:
|
|||
await process_summary_task(validated)
|
||||
elif task_type == "representation":
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_trace(
|
||||
lf.update_current_trace(
|
||||
metadata={
|
||||
"critical_analysis_model": settings.DERIVER.MODEL,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import time
|
|||
from typing import Any
|
||||
|
||||
import sentry_sdk
|
||||
from langfuse.decorators import langfuse_context
|
||||
from langfuse import get_client
|
||||
|
||||
from src import crud, exceptions
|
||||
from src.config import settings
|
||||
|
|
@ -48,20 +48,9 @@ from .queue_payload import (
|
|||
logger = logging.getLogger(__name__)
|
||||
logging.getLogger("sqlalchemy.engine.Engine").disabled = True
|
||||
|
||||
lf = get_client()
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.DERIVER.PROVIDER,
|
||||
model=settings.DERIVER.MODEL,
|
||||
track_name="Critical Analysis Call",
|
||||
response_model=ReasoningResponse,
|
||||
json_mode=True,
|
||||
max_tokens=settings.DERIVER.MAX_OUTPUT_TOKENS or settings.LLM.DEFAULT_MAX_TOKENS,
|
||||
thinking_budget_tokens=settings.DERIVER.THINKING_BUDGET_TOKENS
|
||||
if settings.DERIVER.PROVIDER == "anthropic"
|
||||
else None,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
async def critical_analysis_call(
|
||||
peer_id: str,
|
||||
peer_card: list[str] | None,
|
||||
|
|
@ -69,8 +58,8 @@ async def critical_analysis_call(
|
|||
working_representation: str | None,
|
||||
history: str,
|
||||
new_turn: str,
|
||||
):
|
||||
return critical_analysis_prompt(
|
||||
) -> ReasoningResponse:
|
||||
prompt = critical_analysis_prompt(
|
||||
peer_id=peer_id,
|
||||
peer_card=peer_card,
|
||||
message_created_at=message_created_at,
|
||||
|
|
@ -79,28 +68,51 @@ async def critical_analysis_call(
|
|||
new_turn=new_turn,
|
||||
)
|
||||
|
||||
response = await honcho_llm_call(
|
||||
provider=settings.DERIVER.PROVIDER,
|
||||
model=settings.DERIVER.MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.DERIVER.MAX_OUTPUT_TOKENS
|
||||
or settings.LLM.DEFAULT_MAX_TOKENS,
|
||||
track_name="Critical Analysis Call",
|
||||
response_model=ReasoningResponse,
|
||||
json_mode=True,
|
||||
thinking_budget_tokens=settings.DERIVER.THINKING_BUDGET_TOKENS,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.DERIVER.PEER_CARD_PROVIDER,
|
||||
model=settings.DERIVER.PEER_CARD_MODEL,
|
||||
track_name="Peer Card Call",
|
||||
response_model=PeerCardQuery,
|
||||
json_mode=True,
|
||||
max_tokens=settings.DERIVER.PEER_CARD_MAX_OUTPUT_TOKENS
|
||||
or settings.LLM.DEFAULT_MAX_TOKENS,
|
||||
reasoning_effort="minimal",
|
||||
enable_retry=True,
|
||||
retry_attempts=1, # unstructured output means we shouldn't need to retry, 1 just in case
|
||||
)
|
||||
async def peer_card_call(
|
||||
old_peer_card: list[str] | None,
|
||||
new_observations: list[str],
|
||||
):
|
||||
return peer_card_prompt(
|
||||
) -> PeerCardQuery:
|
||||
"""
|
||||
Generate peer card prompt, call LLM with response model.
|
||||
"""
|
||||
prompt = peer_card_prompt(
|
||||
old_peer_card=old_peer_card,
|
||||
new_observations=new_observations,
|
||||
)
|
||||
|
||||
response = await honcho_llm_call(
|
||||
provider=settings.DERIVER.PEER_CARD_PROVIDER,
|
||||
model=settings.DERIVER.PEER_CARD_MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.DERIVER.PEER_CARD_MAX_OUTPUT_TOKENS
|
||||
or settings.LLM.DEFAULT_MAX_TOKENS,
|
||||
track_name="Peer Card Call",
|
||||
response_model=PeerCardQuery,
|
||||
json_mode=True,
|
||||
reasoning_effort="minimal",
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
|
||||
return response.content
|
||||
|
||||
|
||||
@conditional_observe
|
||||
@sentry_sdk.trace
|
||||
|
|
@ -272,7 +284,7 @@ async def process_representation_task(
|
|||
)
|
||||
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_trace(
|
||||
lf.update_current_trace(
|
||||
output=format_reasoning_response_as_markdown(final_observations)
|
||||
)
|
||||
|
||||
|
|
@ -302,7 +314,7 @@ class CertaintyReasoner:
|
|||
"""
|
||||
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_observation(
|
||||
lf.update_current_generation(
|
||||
input=format_reasoning_inputs_as_markdown(
|
||||
working_representation,
|
||||
history,
|
||||
|
|
@ -392,7 +404,7 @@ class CertaintyReasoner:
|
|||
)
|
||||
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_observation(
|
||||
lf.update_current_generation(
|
||||
output=format_reasoning_response_as_markdown(response),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ and reasoning tasks.
|
|||
import datetime
|
||||
from inspect import cleandoc as c
|
||||
|
||||
from mirascope import prompt_template
|
||||
|
||||
|
||||
@prompt_template()
|
||||
def critical_analysis_prompt(
|
||||
peer_id: str,
|
||||
peer_card: list[str] | None,
|
||||
|
|
@ -102,7 +99,6 @@ New conversation turn to analyze:
|
|||
)
|
||||
|
||||
|
||||
@prompt_template()
|
||||
def peer_card_prompt(
|
||||
old_peer_card: list[str] | None,
|
||||
new_observations: list[str],
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@ historical observations.
|
|||
import asyncio
|
||||
import logging
|
||||
import uuid
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
import tiktoken
|
||||
from dotenv import load_dotenv
|
||||
from langfuse.decorators import langfuse_context
|
||||
from mirascope.llm import Stream
|
||||
from langfuse import get_client
|
||||
|
||||
from src import crud
|
||||
from src.config import settings
|
||||
from src.crud.representation import GLOBAL_REPRESENTATION_COLLECTION_NAME
|
||||
from src.dependencies import tracked_db
|
||||
from src.utils import summarizer
|
||||
from src.utils.clients import honcho_llm_call
|
||||
from src.utils.clients import HonchoLLMCallStreamChunk, honcho_llm_call
|
||||
from src.utils.embedding_store import EmbeddingStore
|
||||
from src.utils.logging import (
|
||||
accumulate_metric,
|
||||
|
|
@ -36,18 +36,10 @@ logger = logging.getLogger(__name__)
|
|||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Create langfuse client
|
||||
lf = get_client()
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.DIALECTIC.PROVIDER,
|
||||
model=settings.DIALECTIC.MODEL,
|
||||
track_name="Dialectic Call",
|
||||
max_tokens=settings.DIALECTIC.MAX_OUTPUT_TOKENS,
|
||||
thinking_budget_tokens=settings.DIALECTIC.THINKING_BUDGET_TOKENS
|
||||
if settings.DIALECTIC.PROVIDER == "anthropic"
|
||||
else None,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
async def dialectic_call(
|
||||
query: str,
|
||||
working_representation: str | None,
|
||||
|
|
@ -71,7 +63,7 @@ async def dialectic_call(
|
|||
Model response
|
||||
"""
|
||||
# Generate the prompt and log it
|
||||
prompt_result = dialectic_prompt(
|
||||
prompt = dialectic_prompt(
|
||||
query,
|
||||
working_representation,
|
||||
recent_conversation_history,
|
||||
|
|
@ -82,32 +74,26 @@ async def dialectic_call(
|
|||
target_peer_card,
|
||||
)
|
||||
|
||||
# Pretty print the prompt content
|
||||
if len(prompt_result) > 0:
|
||||
# Extract content from the first BaseMessageParam
|
||||
prompt_content = prompt_result[0].content
|
||||
else:
|
||||
prompt_content = str(prompt_result)
|
||||
response = await honcho_llm_call(
|
||||
provider=settings.DIALECTIC.PROVIDER,
|
||||
model=settings.DIALECTIC.MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.DIALECTIC.MAX_OUTPUT_TOKENS,
|
||||
track_name="Dialectic Call",
|
||||
thinking_budget_tokens=settings.DIALECTIC.THINKING_BUDGET_TOKENS
|
||||
if settings.DIALECTIC.PROVIDER == "anthropic"
|
||||
else None,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
|
||||
logger.debug("=== DIALECTIC PROMPT ===")
|
||||
logger.debug(prompt_content)
|
||||
logger.debug(prompt)
|
||||
logger.debug("=== END DIALECTIC PROMPT ===")
|
||||
|
||||
return prompt_result
|
||||
return response.content
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.DIALECTIC.PROVIDER,
|
||||
model=settings.DIALECTIC.MODEL,
|
||||
track_name="Dialectic Stream",
|
||||
max_tokens=settings.DIALECTIC.MAX_OUTPUT_TOKENS,
|
||||
thinking_budget_tokens=settings.DIALECTIC.THINKING_BUDGET_TOKENS
|
||||
if settings.DIALECTIC.PROVIDER == "anthropic"
|
||||
else None,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
stream=True,
|
||||
)
|
||||
async def dialectic_stream(
|
||||
query: str,
|
||||
working_representation: str | None,
|
||||
|
|
@ -131,7 +117,7 @@ async def dialectic_stream(
|
|||
Streaming model response
|
||||
"""
|
||||
# Generate the prompt and log it
|
||||
prompt_result = dialectic_prompt(
|
||||
prompt = dialectic_prompt(
|
||||
query,
|
||||
working_representation,
|
||||
recent_conversation_history,
|
||||
|
|
@ -142,18 +128,25 @@ async def dialectic_stream(
|
|||
target_peer_card,
|
||||
)
|
||||
|
||||
# Pretty print the prompt content
|
||||
if len(prompt_result) > 0:
|
||||
# Extract content from the first BaseMessageParam
|
||||
prompt_content = prompt_result[0].content
|
||||
else:
|
||||
prompt_content = str(prompt_result)
|
||||
response = await honcho_llm_call(
|
||||
provider=settings.DIALECTIC.PROVIDER,
|
||||
model=settings.DIALECTIC.MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.DIALECTIC.MAX_OUTPUT_TOKENS,
|
||||
track_name="Dialectic Stream",
|
||||
thinking_budget_tokens=settings.DIALECTIC.THINKING_BUDGET_TOKENS
|
||||
if settings.DIALECTIC.PROVIDER == "anthropic"
|
||||
else None,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
stream=True,
|
||||
)
|
||||
|
||||
logger.debug("=== DIALECTIC PROMPT (STREAM) ===")
|
||||
logger.debug(prompt_content)
|
||||
logger.debug(prompt)
|
||||
logger.debug("=== END DIALECTIC PROMPT ===")
|
||||
|
||||
return prompt_result
|
||||
return response
|
||||
|
||||
|
||||
async def chat(
|
||||
|
|
@ -164,7 +157,7 @@ async def chat(
|
|||
query: str,
|
||||
*,
|
||||
stream: bool = False,
|
||||
) -> Stream | str:
|
||||
) -> str | AsyncIterator[HonchoLLMCallStreamChunk]:
|
||||
"""
|
||||
Chat with the Dialectic API that builds on-demand user representations.
|
||||
|
||||
|
|
@ -197,7 +190,7 @@ async def chat(
|
|||
context_window_size -= len(tokenizer.encode(query))
|
||||
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_trace(
|
||||
lf.update_current_trace(
|
||||
metadata={
|
||||
"query_generation_model": settings.DIALECTIC.QUERY_GENERATION_MODEL,
|
||||
"query_generation_provider": settings.DIALECTIC.QUERY_GENERATION_PROVIDER,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
from inspect import cleandoc as c
|
||||
|
||||
from mirascope import prompt_template
|
||||
|
||||
|
||||
@prompt_template()
|
||||
def dialectic_prompt(
|
||||
query: str,
|
||||
working_representation: str | None,
|
||||
|
|
@ -86,7 +83,6 @@ Provide a natural language response that:
|
|||
)
|
||||
|
||||
|
||||
@prompt_template()
|
||||
def query_generation_prompt(query: str, target_peer_name: str) -> str:
|
||||
"""
|
||||
Generate the prompt for semantic query expansion.
|
||||
|
|
@ -101,7 +97,7 @@ def query_generation_prompt(query: str, target_peer_name: str) -> str:
|
|||
"""
|
||||
return c(
|
||||
f"""
|
||||
You are a query expansion agent helping AI applications understand their users. The user's name is {target_peer_name}. Your job is to take application queries about this user and generate targeted search queries that will retrieve the most relevant observations using semantic search over an embedding store containing observations about the user.
|
||||
You are a query expansion agent helping AI applications understand their users. The user's name is {target_peer_name}. Your job is to take application queries about this user and generate targeted search queries that will retrieve the most relevant observations using semantic search over an embedding store containing observations about the user.
|
||||
|
||||
## QUERY EXPANSION STRATEGY FOR SEMANTIC SIMILARITY
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import json
|
|||
import logging
|
||||
from typing import Any
|
||||
|
||||
from langfuse.decorators import langfuse_context
|
||||
from langfuse import get_client
|
||||
|
||||
from src.config import settings
|
||||
from src.models import Document
|
||||
|
|
@ -21,6 +21,8 @@ from .prompts import query_generation_prompt
|
|||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
lf = get_client()
|
||||
|
||||
|
||||
@conditional_observe
|
||||
async def get_observations(
|
||||
|
|
@ -75,7 +77,7 @@ async def get_observations(
|
|||
unique_observations = _deduplicate_observations(all_results)
|
||||
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_observation(
|
||||
lf.update_current_generation(
|
||||
input={
|
||||
"query": query,
|
||||
"include_premises": include_premises,
|
||||
|
|
@ -87,7 +89,7 @@ async def get_observations(
|
|||
},
|
||||
)
|
||||
|
||||
langfuse_context.update_current_trace(
|
||||
lf.update_current_trace(
|
||||
metadata={
|
||||
"search_queries": search_queries,
|
||||
"observations_retrieved": unique_observations,
|
||||
|
|
@ -227,13 +229,18 @@ def _format_observations(
|
|||
return "\n".join(parts).strip()
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.DIALECTIC.QUERY_GENERATION_PROVIDER,
|
||||
model=settings.DIALECTIC.QUERY_GENERATION_MODEL,
|
||||
response_model=SemanticQueries,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
async def generate_semantic_queries(query: str, target_peer_name: str):
|
||||
async def generate_semantic_queries(
|
||||
query: str, target_peer_name: str
|
||||
) -> SemanticQueries:
|
||||
"""Generate semantic search queries for observation retrieval."""
|
||||
return query_generation_prompt(query, target_peer_name)
|
||||
prompt = query_generation_prompt(query, target_peer_name)
|
||||
response = await honcho_llm_call(
|
||||
provider=settings.DIALECTIC.QUERY_GENERATION_PROVIDER,
|
||||
model=settings.DIALECTIC.QUERY_GENERATION_MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.LLM.DEFAULT_MAX_TOKENS,
|
||||
response_model=SemanticQueries,
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
return response.content
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import logging
|
||||
from collections.abc import AsyncGenerator
|
||||
from collections.abc import AsyncGenerator, AsyncIterator
|
||||
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
|
|
@ -11,7 +11,6 @@ from fastapi.exceptions import HTTPException
|
|||
from fastapi.responses import StreamingResponse
|
||||
from fastapi_pagination import Page
|
||||
from fastapi_pagination.ext.sqlalchemy import apaginate
|
||||
from mirascope.llm import Stream
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from src import crud, schemas
|
||||
|
|
@ -188,8 +187,8 @@ async def chat(
|
|||
query=options.query,
|
||||
stream=options.stream,
|
||||
)
|
||||
if isinstance(stream, Stream):
|
||||
async for chunk, _ in stream:
|
||||
if isinstance(stream, AsyncIterator):
|
||||
async for chunk in stream:
|
||||
yield chunk.content
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Invalid stream type")
|
||||
|
|
|
|||
|
|
@ -1,392 +1,657 @@
|
|||
from collections.abc import Awaitable, Callable
|
||||
from typing import (
|
||||
Any,
|
||||
Literal,
|
||||
ParamSpec,
|
||||
Protocol,
|
||||
TypeVar,
|
||||
overload,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
# --- OpenAI compatibility shim must run BEFORE importing mirascope ---
|
||||
# Some versions of the OpenAI SDK do not expose ChatCompletionMessageToolCall
|
||||
# at openai.types.chat, but some integrations import it from there at runtime.
|
||||
# We defensively define it if missing to avoid import-time failures.
|
||||
#
|
||||
# We can get rid of this by getting rid of mirascope...
|
||||
from openai.types import chat as _openai_chat_types # type: ignore
|
||||
|
||||
if not hasattr(_openai_chat_types, "ChatCompletionMessageToolCall"):
|
||||
_openai_chat_types.ChatCompletionMessageToolCall = object # pyright: ignore
|
||||
from collections.abc import AsyncIterator, Callable
|
||||
from functools import wraps
|
||||
from typing import Any, Generic, Literal, TypeVar, cast, overload
|
||||
|
||||
from anthropic import AsyncAnthropic
|
||||
from anthropic.types import TextBlock
|
||||
from anthropic.types.message import Message as AnthropicMessage
|
||||
from google import genai
|
||||
from google.genai.types import GenerateContentResponse
|
||||
from groq import AsyncGroq
|
||||
from mirascope import llm
|
||||
from mirascope.core import ResponseModelConfigDict
|
||||
from mirascope.integrations.langfuse import with_langfuse
|
||||
from mirascope.llm import Stream
|
||||
from langfuse import get_client
|
||||
from openai import AsyncOpenAI
|
||||
from pydantic import BaseModel
|
||||
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
||||
from pydantic import BaseModel, Field
|
||||
from sentry_sdk.ai.monitoring import ai_track
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
from src.config import settings
|
||||
from src.utils.types import Providers
|
||||
from src.utils.types import SupportedProviders
|
||||
|
||||
clients: dict[Providers, AsyncAnthropic | AsyncOpenAI | genai.Client | AsyncGroq] = {}
|
||||
T = TypeVar("T")
|
||||
M = TypeVar("M", bound=BaseModel)
|
||||
|
||||
lf = get_client()
|
||||
|
||||
CLIENTS: dict[
|
||||
SupportedProviders,
|
||||
AsyncAnthropic | AsyncOpenAI | genai.Client | AsyncGroq,
|
||||
] = {}
|
||||
|
||||
if settings.LLM.ANTHROPIC_API_KEY:
|
||||
anthropic = AsyncAnthropic(api_key=settings.LLM.ANTHROPIC_API_KEY)
|
||||
clients["anthropic"] = anthropic
|
||||
CLIENTS["anthropic"] = anthropic
|
||||
|
||||
if settings.LLM.OPENAI_API_KEY:
|
||||
openai_client = AsyncOpenAI(
|
||||
api_key=settings.LLM.OPENAI_API_KEY,
|
||||
)
|
||||
clients["openai"] = openai_client
|
||||
CLIENTS["openai"] = openai_client
|
||||
|
||||
if settings.LLM.OPENAI_COMPATIBLE_BASE_URL:
|
||||
clients["custom"] = AsyncOpenAI(
|
||||
CLIENTS["custom"] = AsyncOpenAI(
|
||||
api_key=settings.LLM.OPENAI_COMPATIBLE_API_KEY,
|
||||
base_url=settings.LLM.OPENAI_COMPATIBLE_BASE_URL,
|
||||
)
|
||||
|
||||
if settings.LLM.GEMINI_API_KEY:
|
||||
google = genai.Client(api_key=settings.LLM.GEMINI_API_KEY)
|
||||
clients["google"] = google
|
||||
google = genai.client.Client(api_key=settings.LLM.GEMINI_API_KEY)
|
||||
CLIENTS["google"] = google
|
||||
|
||||
if settings.LLM.GROQ_API_KEY:
|
||||
groq = AsyncGroq(api_key=settings.LLM.GROQ_API_KEY)
|
||||
clients["groq"] = groq
|
||||
CLIENTS["groq"] = groq
|
||||
|
||||
providers = [
|
||||
SELECTED_PROVIDERS = [
|
||||
("Dialectic", settings.DIALECTIC.PROVIDER),
|
||||
("Summary", settings.SUMMARY.PROVIDER),
|
||||
("Deriver", settings.DERIVER.PROVIDER),
|
||||
("Query Generation Provider", settings.DIALECTIC.QUERY_GENERATION_PROVIDER),
|
||||
]
|
||||
|
||||
for provider_name, provider_value in providers:
|
||||
if provider_value not in clients:
|
||||
for provider_name, provider_value in SELECTED_PROVIDERS:
|
||||
if provider_value not in CLIENTS:
|
||||
raise ValueError(f"Missing client for {provider_name}: {provider_value}")
|
||||
|
||||
P = ParamSpec("P")
|
||||
T = TypeVar("T", bound=BaseModel)
|
||||
T_co = TypeVar("T_co", bound=BaseModel, covariant=True)
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
|
||||
|
||||
# Define protocols for different return types
|
||||
@runtime_checkable
|
||||
class AsyncResponseModelCallable(Protocol[P, T_co]):
|
||||
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T_co: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SyncResponseModelCallable(Protocol[P, T_co]):
|
||||
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T_co: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AsyncStreamCallable(Protocol[P]):
|
||||
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Stream: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SyncStreamCallable(Protocol[P]):
|
||||
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Stream: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AsyncStringCallable(Protocol[P]):
|
||||
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> str: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SyncStringCallable(Protocol[P]):
|
||||
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> str: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AsyncCallResponseCallable(Protocol[P]):
|
||||
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> llm.CallResponse: ...
|
||||
|
||||
|
||||
# Overload for stream=True with async function
|
||||
@overload
|
||||
def honcho_llm_call(
|
||||
*,
|
||||
provider: Providers | None = None,
|
||||
model: str | None = None,
|
||||
track_name: str | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
max_tokens: int | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[True],
|
||||
**extra_call_params: Any,
|
||||
) -> Callable[[Callable[P, Awaitable[Any]]], AsyncStreamCallable[P]]: ...
|
||||
|
||||
|
||||
# Overload for response_model with async function
|
||||
@overload
|
||||
def honcho_llm_call(
|
||||
*,
|
||||
provider: Providers | None = None,
|
||||
model: str | None = None,
|
||||
track_name: str | None = None,
|
||||
response_model: type[T],
|
||||
json_mode: bool = False,
|
||||
max_tokens: int | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[False] = False,
|
||||
**extra_call_params: Any,
|
||||
) -> Callable[[Callable[P, Awaitable[Any]]], AsyncResponseModelCallable[P, T]]: ...
|
||||
|
||||
|
||||
# Overload for return_call_response=True with async function
|
||||
@overload
|
||||
def honcho_llm_call(
|
||||
*,
|
||||
provider: Providers | None = None,
|
||||
model: str | None = None,
|
||||
track_name: str | None = None,
|
||||
response_model: None = None,
|
||||
json_mode: bool = False,
|
||||
max_tokens: int | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[False] = False,
|
||||
return_call_response: Literal[True],
|
||||
**extra_call_params: Any,
|
||||
) -> Callable[[Callable[P, Awaitable[Any]]], AsyncCallResponseCallable[P]]: ...
|
||||
|
||||
|
||||
# Overload for no response_model with async function (string return)
|
||||
@overload
|
||||
def honcho_llm_call(
|
||||
*,
|
||||
provider: Providers | None = None,
|
||||
model: str | None = None,
|
||||
track_name: str | None = None,
|
||||
response_model: None = None,
|
||||
json_mode: bool = False,
|
||||
max_tokens: int | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[False] = False,
|
||||
return_call_response: Literal[False],
|
||||
**extra_call_params: Any,
|
||||
) -> Callable[[Callable[P, Awaitable[Any]]], AsyncStringCallable[P]]: ...
|
||||
|
||||
|
||||
# Generic overload for sync functions (fallback)
|
||||
@overload
|
||||
def honcho_llm_call(
|
||||
*,
|
||||
provider: Providers | None = None,
|
||||
model: str | None = None,
|
||||
track_name: str | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
max_tokens: int | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: bool = False,
|
||||
**extra_call_params: Any,
|
||||
) -> Callable[[Callable[P, Any]], Callable[P, Any]]: ...
|
||||
|
||||
|
||||
def honcho_llm_call(
|
||||
provider: Providers | None = None,
|
||||
model: str | None = None,
|
||||
track_name: str | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
max_tokens: int | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: bool = False,
|
||||
return_call_response: bool = False, # pyright: ignore
|
||||
**extra_call_params: Any,
|
||||
) -> Any:
|
||||
class HonchoLLMCallResponse(BaseModel, Generic[T]):
|
||||
"""
|
||||
Consolidated decorator for LLM calls that handles provider-specific configurations.
|
||||
|
||||
This decorator automatically:
|
||||
- Handles both sync and async functions seamlessly
|
||||
- Applies retry logic with exponential backoff
|
||||
- Adds AI tracking for Sentry
|
||||
- Integrates with Langfuse for observability
|
||||
- Builds provider-specific call parameters
|
||||
- Handles client selection from the global clients dict
|
||||
Response object for LLM calls.
|
||||
|
||||
Args:
|
||||
provider: The LLM provider to use (e.g., "anthropic", "google", "openai")
|
||||
model: The model to use
|
||||
track_name: Name for AI tracking (e.g., "Critical Analysis Call")
|
||||
response_model: Optional Pydantic model for structured responses
|
||||
json_mode: Whether to enable JSON mode (for providers that support it)
|
||||
max_tokens: Maximum tokens for the response
|
||||
reasoning_effort: Optional reasoning effort hint passed to OpenAI GPT-5 models only
|
||||
verbosity: Optional verbosity hint passed to OpenAI GPT-5 models only
|
||||
thinking_budget_tokens: Budget for thinking tokens (Anthropic only)
|
||||
enable_retry: Whether to enable retry logic (default: True)
|
||||
retry_attempts: Number of retry attempts (default: 3)
|
||||
stream: Whether to enable streaming responses (default: False)
|
||||
_return_call_response: Whether to return the full CallResponse object (default: False)
|
||||
**extra_call_params: Additional provider-specific parameters
|
||||
|
||||
Returns:
|
||||
A decorator that returns:
|
||||
- For async functions: Callable[P, Awaitable[T]] where T is Stream, response_model, CallResponse, or str
|
||||
- For sync functions: Callable[P, T] where T is Stream, response_model, CallResponse, or str
|
||||
|
||||
Note: Type annotations may be needed at the call site for proper type checking.
|
||||
|
||||
Example (async function):
|
||||
@honcho_llm_call(
|
||||
provider=settings.DERIVER.PROVIDER,
|
||||
model=settings.DERIVER.MODEL,
|
||||
track_name="Critical Analysis Call",
|
||||
response_model=ReasoningResponse,
|
||||
json_mode=True,
|
||||
max_tokens=settings.DERIVER.MAX_OUTPUT_TOKENS,
|
||||
)
|
||||
async def analyze(context: str, query: str):
|
||||
return prompt_template(context, query)
|
||||
|
||||
Example (sync function):
|
||||
@honcho_llm_call(
|
||||
provider="openai",
|
||||
model="gpt-4",
|
||||
max_tokens=1000,
|
||||
)
|
||||
def generate_summary(text: str) -> str:
|
||||
return f"Summarize: {text}"
|
||||
|
||||
# Call synchronously
|
||||
result = generate_summary("Long text here...")
|
||||
content: The response content. When a response_model is provided, this will be
|
||||
the parsed object of that type. Otherwise, it will be a string.
|
||||
output_tokens: Number of tokens generated in the response.
|
||||
finish_reasons: List of finish reasons for the response.
|
||||
"""
|
||||
|
||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
# Handle special case for custom provider
|
||||
# Custom providers use OpenAI-compatible endpoints, so we resolve to "openai" for the provider name
|
||||
# but keep the original "custom" for client lookup
|
||||
resolved_provider = "openai" if provider == "custom" else provider
|
||||
content: T
|
||||
output_tokens: int
|
||||
finish_reasons: list[str]
|
||||
|
||||
# Build provider-specific call params
|
||||
call_params: dict[str, Any] = {}
|
||||
|
||||
if resolved_provider == "google":
|
||||
# Google uses 'config' parameter
|
||||
config: dict[str, Any] = {}
|
||||
if max_tokens:
|
||||
config["max_output_tokens"] = max_tokens
|
||||
class HonchoLLMCallStreamChunk(BaseModel):
|
||||
"""
|
||||
A single chunk in a streaming LLM response.
|
||||
|
||||
Args:
|
||||
content: The text content for this chunk. Empty for chunks that only contain metadata.
|
||||
is_done: Whether this is the final chunk in the stream.
|
||||
finish_reasons: List of finish reasons if the stream is complete.
|
||||
"""
|
||||
|
||||
content: str
|
||||
is_done: bool = False
|
||||
finish_reasons: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
@overload
|
||||
async def honcho_llm_call(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
track_name: str | None = None,
|
||||
*,
|
||||
response_model: type[M],
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[False] = False,
|
||||
) -> HonchoLLMCallResponse[M]: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def honcho_llm_call(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
track_name: str | None = None,
|
||||
response_model: None = None,
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[False] = False,
|
||||
) -> HonchoLLMCallResponse[str]: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def honcho_llm_call(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
track_name: str | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: Literal[True] = ...,
|
||||
) -> AsyncIterator[HonchoLLMCallStreamChunk]: ...
|
||||
|
||||
|
||||
async def honcho_llm_call(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
track_name: str | None = None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None,
|
||||
enable_retry: bool = True,
|
||||
retry_attempts: int = 3,
|
||||
stream: bool = False,
|
||||
) -> HonchoLLMCallResponse[Any] | AsyncIterator[HonchoLLMCallStreamChunk]:
|
||||
client = CLIENTS.get(provider)
|
||||
if not client:
|
||||
raise ValueError(f"Missing client for {provider}")
|
||||
|
||||
decorated = honcho_llm_call_inner
|
||||
|
||||
# apply langfuse if enabled
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
decorated = with_langfuse(decorated)
|
||||
|
||||
# apply tracking
|
||||
if track_name:
|
||||
decorated = ai_track(track_name)(decorated)
|
||||
|
||||
# apply retry logic
|
||||
if enable_retry:
|
||||
decorated = retry(
|
||||
stop=stop_after_attempt(retry_attempts),
|
||||
wait=wait_exponential(multiplier=1, min=4, max=10),
|
||||
)(decorated)
|
||||
|
||||
if stream:
|
||||
return await decorated(
|
||||
provider,
|
||||
model,
|
||||
prompt,
|
||||
max_tokens,
|
||||
response_model,
|
||||
json_mode,
|
||||
reasoning_effort,
|
||||
verbosity,
|
||||
thinking_budget_tokens,
|
||||
True,
|
||||
)
|
||||
else:
|
||||
return await decorated(
|
||||
provider,
|
||||
model,
|
||||
prompt,
|
||||
max_tokens,
|
||||
response_model,
|
||||
json_mode,
|
||||
reasoning_effort,
|
||||
verbosity,
|
||||
thinking_budget_tokens,
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
async def honcho_llm_call_inner(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
response_model: type[M],
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None, # Anthropic only
|
||||
stream: Literal[False] = False,
|
||||
) -> HonchoLLMCallResponse[M]: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def honcho_llm_call_inner(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
response_model: None = None,
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None, # Anthropic only
|
||||
stream: Literal[False] = False,
|
||||
) -> HonchoLLMCallResponse[str]: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def honcho_llm_call_inner(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None, # Anthropic only
|
||||
stream: Literal[True] = ...,
|
||||
) -> AsyncIterator[HonchoLLMCallStreamChunk]: ...
|
||||
|
||||
|
||||
async def honcho_llm_call_inner(
|
||||
provider: SupportedProviders,
|
||||
model: str,
|
||||
prompt: str,
|
||||
max_tokens: int,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
json_mode: bool = False,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"]
|
||||
| None = None, # OpenAI only
|
||||
verbosity: Literal["low", "medium", "high"] | None = None, # OpenAI only
|
||||
thinking_budget_tokens: int | None = None, # Anthropic only
|
||||
stream: bool = False,
|
||||
) -> HonchoLLMCallResponse[Any] | AsyncIterator[HonchoLLMCallStreamChunk]:
|
||||
# has already been validated by honcho_llm_call
|
||||
client = CLIENTS[provider]
|
||||
|
||||
params: dict[str, Any] = {
|
||||
"model": model,
|
||||
"max_tokens": max_tokens,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"stream": stream,
|
||||
}
|
||||
|
||||
if stream:
|
||||
# Return async generator for streaming responses
|
||||
return handle_streaming_response(
|
||||
client,
|
||||
params,
|
||||
json_mode,
|
||||
thinking_budget_tokens,
|
||||
response_model,
|
||||
reasoning_effort,
|
||||
verbosity,
|
||||
)
|
||||
|
||||
# Remove stream parameter for non-streaming calls as some providers don't accept it
|
||||
params.pop("stream", None)
|
||||
|
||||
match client:
|
||||
case AsyncAnthropic():
|
||||
if response_model:
|
||||
config["response_schema"] = response_model
|
||||
|
||||
raise NotImplementedError(
|
||||
"Response model is not supported for Anthropic"
|
||||
)
|
||||
anthropic_params: dict[str, Any] = {
|
||||
"model": params["model"],
|
||||
"max_tokens": params["max_tokens"],
|
||||
"messages": list(params["messages"]),
|
||||
}
|
||||
if json_mode:
|
||||
config["response_mime_type"] = "application/json"
|
||||
|
||||
if config:
|
||||
call_params["config"] = config
|
||||
elif resolved_provider == "anthropic":
|
||||
# Anthropic uses thinking params and max_tokens
|
||||
anthropic_params["messages"].append(
|
||||
{"role": "assistant", "content": "{"}
|
||||
)
|
||||
if thinking_budget_tokens:
|
||||
call_params["thinking"] = {
|
||||
anthropic_params["thinking"] = {
|
||||
"type": "enabled",
|
||||
"budget_tokens": thinking_budget_tokens,
|
||||
}
|
||||
if max_tokens:
|
||||
call_params["max_tokens"] = max_tokens
|
||||
elif resolved_provider == "openai" and model and "gpt-5" in model:
|
||||
call_params["max_completion_tokens"] = max_tokens
|
||||
if reasoning_effort is not None:
|
||||
call_params["reasoning_effort"] = reasoning_effort
|
||||
if verbosity is not None:
|
||||
call_params["verbosity"] = verbosity
|
||||
else:
|
||||
# Other providers just use max_tokens
|
||||
if max_tokens:
|
||||
call_params["max_tokens"] = max_tokens
|
||||
anthropic_response: AnthropicMessage = await client.messages.create( # pyright: ignore
|
||||
**anthropic_params
|
||||
)
|
||||
# Extract text content from content blocks
|
||||
text_blocks: list[str] = []
|
||||
for block in anthropic_response.content: # pyright: ignore
|
||||
if isinstance(block, TextBlock):
|
||||
text_blocks.append(block.text)
|
||||
|
||||
# Merge with any user-supplied provider call params
|
||||
# Accept an explicit "extra_call_params" dict kwarg and merge its contents
|
||||
# Do NOT forward the key itself into provider params.
|
||||
# Also drop any wrapper-only flags.
|
||||
user_extra_call_params: dict[str, Any] | None = extra_call_params.pop(
|
||||
"extra_call_params", None
|
||||
)
|
||||
extra_call_params.pop("return_call_response", None)
|
||||
if isinstance(user_extra_call_params, dict):
|
||||
call_params.update(user_extra_call_params)
|
||||
# Safely extract usage and stop_reason
|
||||
usage = anthropic_response.usage # pyright: ignore
|
||||
stop_reason = anthropic_response.stop_reason # pyright: ignore
|
||||
|
||||
# Build kwargs for llm.call
|
||||
llm_kwargs: dict[str, Any] = {}
|
||||
if resolved_provider and provider:
|
||||
llm_kwargs["provider"] = resolved_provider
|
||||
llm_kwargs["client"] = clients[
|
||||
provider
|
||||
] # Use original provider for client lookup
|
||||
if model:
|
||||
llm_kwargs["model"] = model
|
||||
if response_model:
|
||||
# https://mirascope.com/docs/mirascope/learn/provider-specific/openai#response-models
|
||||
if resolved_provider == "openai":
|
||||
response_model.model_config = ResponseModelConfigDict(strict=True)
|
||||
llm_kwargs["response_model"] = response_model
|
||||
if json_mode:
|
||||
llm_kwargs["json_mode"] = json_mode
|
||||
if stream:
|
||||
llm_kwargs["stream"] = stream
|
||||
if call_params:
|
||||
llm_kwargs["call_params"] = call_params
|
||||
return HonchoLLMCallResponse(
|
||||
content="\n".join(text_blocks),
|
||||
output_tokens=usage.output_tokens if usage else 0, # pyright: ignore
|
||||
finish_reasons=[stop_reason] if stop_reason else [],
|
||||
)
|
||||
case AsyncOpenAI():
|
||||
openai_params: dict[str, Any] = {
|
||||
"model": params["model"],
|
||||
"messages": params["messages"],
|
||||
}
|
||||
if "gpt-5" in model:
|
||||
openai_params["max_completion_tokens"] = params["max_tokens"]
|
||||
if reasoning_effort:
|
||||
openai_params["reasoning_effort"] = reasoning_effort
|
||||
if verbosity:
|
||||
openai_params["verbosity"] = verbosity
|
||||
else:
|
||||
openai_params["max_tokens"] = params["max_tokens"]
|
||||
if json_mode:
|
||||
openai_params["response_format"] = {"type": "json_object"}
|
||||
if response_model:
|
||||
openai_params["response_format"] = response_model
|
||||
response: ChatCompletion = await client.chat.completions.parse( # pyright: ignore
|
||||
**openai_params
|
||||
)
|
||||
# Extract the parsed object for structured output
|
||||
parsed_content = response.choices[0].message.parsed
|
||||
if parsed_content is None:
|
||||
raise ValueError("No parsed content in structured response")
|
||||
|
||||
# Apply decorators in order
|
||||
decorated: Any = func
|
||||
# Safely extract usage and finish_reason
|
||||
usage = response.usage
|
||||
finish_reason = response.choices[0].finish_reason
|
||||
|
||||
# Apply llm.call
|
||||
decorated = llm.call(**llm_kwargs)(decorated) # pyright: ignore
|
||||
return HonchoLLMCallResponse(
|
||||
content=parsed_content,
|
||||
output_tokens=usage.completion_tokens if usage else 0,
|
||||
finish_reasons=[finish_reason] if finish_reason else [],
|
||||
)
|
||||
else:
|
||||
response: ChatCompletion = await client.chat.completions.create( # pyright: ignore
|
||||
**openai_params
|
||||
)
|
||||
|
||||
# Apply langfuse if enabled
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
decorated = with_langfuse()(decorated) # pyright: ignore
|
||||
# Safely extract usage and finish_reason
|
||||
usage = response.usage # pyright: ignore
|
||||
finish_reason = response.choices[0].finish_reason # pyright: ignore
|
||||
|
||||
# Apply AI tracking if name provided
|
||||
if track_name:
|
||||
decorated = ai_track(track_name)(decorated)
|
||||
return HonchoLLMCallResponse(
|
||||
content=response.choices[0].message.content or "", # pyright: ignore
|
||||
output_tokens=usage.completion_tokens if usage else 0, # pyright: ignore
|
||||
finish_reasons=[finish_reason] if finish_reason else [],
|
||||
)
|
||||
case genai.Client():
|
||||
if response_model is None:
|
||||
gemini_response: GenerateContentResponse = (
|
||||
client.models.generate_content(
|
||||
model=model,
|
||||
contents=prompt,
|
||||
config={
|
||||
"response_mime_type": "application/json"
|
||||
if json_mode
|
||||
else None,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
# Apply retry logic if enabled
|
||||
if enable_retry:
|
||||
decorated = retry( # pyright: ignore
|
||||
stop=stop_after_attempt(retry_attempts),
|
||||
wait=wait_exponential(multiplier=1, min=4, max=10),
|
||||
)(decorated) # pyright: ignore
|
||||
# Safely extract response data
|
||||
text_content = gemini_response.text if gemini_response.text else ""
|
||||
token_count = (
|
||||
gemini_response.candidates[0].token_count or 0
|
||||
if gemini_response.candidates
|
||||
else 0
|
||||
)
|
||||
finish_reason = (
|
||||
gemini_response.candidates[0].finish_reason.name
|
||||
if gemini_response.candidates
|
||||
and gemini_response.candidates[0].finish_reason
|
||||
else "stop"
|
||||
)
|
||||
|
||||
return decorated # pyright: ignore
|
||||
return HonchoLLMCallResponse(
|
||||
content=text_content,
|
||||
output_tokens=token_count,
|
||||
finish_reasons=[finish_reason],
|
||||
)
|
||||
|
||||
return decorator
|
||||
else:
|
||||
gemini_response = client.models.generate_content(
|
||||
model=model,
|
||||
contents=prompt,
|
||||
config={
|
||||
"response_mime_type": "application/json",
|
||||
"response_schema": response_model,
|
||||
},
|
||||
)
|
||||
|
||||
token_count = (
|
||||
gemini_response.candidates[0].token_count or 0
|
||||
if gemini_response.candidates
|
||||
else 0
|
||||
)
|
||||
finish_reason = (
|
||||
gemini_response.candidates[0].finish_reason.name
|
||||
if gemini_response.candidates
|
||||
and gemini_response.candidates[0].finish_reason
|
||||
else "stop"
|
||||
)
|
||||
|
||||
return HonchoLLMCallResponse(
|
||||
content=gemini_response.parsed,
|
||||
output_tokens=token_count,
|
||||
finish_reasons=[finish_reason],
|
||||
)
|
||||
|
||||
case AsyncGroq():
|
||||
groq_params: dict[str, Any] = {
|
||||
"model": params["model"],
|
||||
"max_tokens": params["max_tokens"],
|
||||
"messages": params["messages"],
|
||||
}
|
||||
|
||||
if response_model:
|
||||
groq_params["response_format"] = response_model
|
||||
elif json_mode:
|
||||
groq_params["response_format"] = {"type": "json_object"}
|
||||
|
||||
response: ChatCompletion = await client.chat.completions.create( # pyright: ignore
|
||||
**groq_params
|
||||
)
|
||||
if response.choices[0].message.content is None: # pyright: ignore
|
||||
raise ValueError("No content in response")
|
||||
|
||||
# Safely extract usage and finish_reason
|
||||
usage = response.usage # pyright: ignore
|
||||
finish_reason = response.choices[0].finish_reason # pyright: ignore
|
||||
|
||||
return HonchoLLMCallResponse(
|
||||
content=response.choices[0].message.content, # pyright: ignore
|
||||
output_tokens=usage.completion_tokens if usage else 0, # pyright: ignore
|
||||
finish_reasons=[finish_reason] if finish_reason else [],
|
||||
)
|
||||
|
||||
|
||||
async def handle_streaming_response(
|
||||
client: AsyncAnthropic | AsyncOpenAI | genai.Client | AsyncGroq,
|
||||
params: dict[str, Any],
|
||||
json_mode: bool,
|
||||
thinking_budget_tokens: int | None,
|
||||
response_model: type[BaseModel] | None = None,
|
||||
reasoning_effort: Literal["low", "medium", "high", "minimal"] | None = None,
|
||||
verbosity: Literal["low", "medium", "high"] | None = None,
|
||||
) -> AsyncIterator[HonchoLLMCallStreamChunk]:
|
||||
"""
|
||||
Handle streaming responses for all supported providers.
|
||||
|
||||
Args:
|
||||
client: The LLM client instance
|
||||
params: Request parameters including stream=True
|
||||
json_mode: Whether to use JSON mode
|
||||
thinking_budget_tokens: Anthropic thinking budget tokens
|
||||
response_model: Pydantic model for structured output
|
||||
reasoning_effort: OpenAI reasoning effort level (GPT-5 only)
|
||||
verbosity: OpenAI verbosity level (GPT-5 only)
|
||||
|
||||
Yields:
|
||||
HonchoLLMCallStreamChunk: Individual chunks of the streaming response
|
||||
"""
|
||||
match client:
|
||||
case AsyncAnthropic():
|
||||
if response_model:
|
||||
raise NotImplementedError(
|
||||
"Response model is not supported for Anthropic"
|
||||
)
|
||||
anthropic_params: dict[str, Any] = {
|
||||
"model": params["model"],
|
||||
"max_tokens": params["max_tokens"],
|
||||
"messages": list(params["messages"]),
|
||||
}
|
||||
if json_mode:
|
||||
anthropic_params["messages"].append(
|
||||
{"role": "assistant", "content": "{"}
|
||||
)
|
||||
if thinking_budget_tokens:
|
||||
anthropic_params["thinking"] = {
|
||||
"type": "enabled",
|
||||
"budget_tokens": thinking_budget_tokens,
|
||||
}
|
||||
async with client.messages.stream(**anthropic_params) as anthropic_stream:
|
||||
async for chunk in anthropic_stream:
|
||||
if (
|
||||
chunk.type == "content_block_delta"
|
||||
and hasattr(chunk, "delta")
|
||||
and hasattr(chunk.delta, "text")
|
||||
):
|
||||
text_content = getattr(chunk.delta, "text", "")
|
||||
yield HonchoLLMCallStreamChunk(content=text_content)
|
||||
final_message = await anthropic_stream.get_final_message()
|
||||
yield HonchoLLMCallStreamChunk(
|
||||
content="",
|
||||
is_done=True,
|
||||
finish_reasons=[final_message.stop_reason]
|
||||
if final_message.stop_reason
|
||||
else [],
|
||||
)
|
||||
|
||||
case AsyncOpenAI():
|
||||
openai_params: dict[str, Any] = {
|
||||
"model": params["model"],
|
||||
"messages": params["messages"],
|
||||
"stream": True,
|
||||
}
|
||||
|
||||
model_name = params["model"]
|
||||
if "gpt-5" in model_name:
|
||||
openai_params["max_completion_tokens"] = params["max_tokens"]
|
||||
if reasoning_effort:
|
||||
openai_params["reasoning_effort"] = reasoning_effort
|
||||
if verbosity:
|
||||
openai_params["verbosity"] = verbosity
|
||||
else:
|
||||
openai_params["max_tokens"] = params["max_tokens"]
|
||||
|
||||
if response_model:
|
||||
openai_params["response_format"] = response_model
|
||||
elif json_mode:
|
||||
openai_params["response_format"] = {"type": "json_object"}
|
||||
|
||||
openai_stream = await client.chat.completions.create(**openai_params) # pyright: ignore
|
||||
async for chunk in openai_stream: # pyright: ignore
|
||||
chunk = cast(ChatCompletionChunk, chunk)
|
||||
if chunk.choices and chunk.choices[0].delta.content:
|
||||
yield HonchoLLMCallStreamChunk(
|
||||
content=chunk.choices[0].delta.content
|
||||
)
|
||||
if chunk.choices and chunk.choices[0].finish_reason:
|
||||
yield HonchoLLMCallStreamChunk(
|
||||
content="",
|
||||
is_done=True,
|
||||
finish_reasons=[chunk.choices[0].finish_reason],
|
||||
)
|
||||
|
||||
case genai.Client():
|
||||
prompt_text = params["messages"][0]["content"] if params["messages"] else ""
|
||||
|
||||
if response_model is not None:
|
||||
response_stream = await client.aio.models.generate_content_stream(
|
||||
model=params["model"],
|
||||
contents=prompt_text,
|
||||
config={
|
||||
"response_mime_type": "application/json",
|
||||
"response_schema": response_model,
|
||||
},
|
||||
)
|
||||
else:
|
||||
response_stream = await client.aio.models.generate_content_stream(
|
||||
model=params["model"],
|
||||
contents=prompt_text,
|
||||
config={
|
||||
"response_mime_type": "application/json" if json_mode else None,
|
||||
},
|
||||
)
|
||||
|
||||
final_chunk = None
|
||||
async for chunk in response_stream:
|
||||
if chunk.text:
|
||||
yield HonchoLLMCallStreamChunk(content=chunk.text)
|
||||
final_chunk = chunk
|
||||
|
||||
finish_reason = "stop" # Default fallback
|
||||
if (
|
||||
final_chunk
|
||||
and hasattr(final_chunk, "candidates")
|
||||
and final_chunk.candidates
|
||||
and hasattr(final_chunk.candidates[0], "finish_reason")
|
||||
and final_chunk.candidates[0].finish_reason
|
||||
):
|
||||
finish_reason = final_chunk.candidates[0].finish_reason.name
|
||||
|
||||
yield HonchoLLMCallStreamChunk(
|
||||
content="", is_done=True, finish_reasons=[finish_reason]
|
||||
)
|
||||
|
||||
case AsyncGroq():
|
||||
groq_params: dict[str, Any] = {
|
||||
"model": params["model"],
|
||||
"max_tokens": params["max_tokens"],
|
||||
"messages": params["messages"],
|
||||
"stream": True,
|
||||
}
|
||||
|
||||
if response_model:
|
||||
groq_params["response_format"] = response_model
|
||||
elif json_mode:
|
||||
groq_params["response_format"] = {"type": "json_object"}
|
||||
|
||||
groq_stream = await client.chat.completions.create(**groq_params) # pyright: ignore
|
||||
async for chunk in groq_stream: # pyright: ignore
|
||||
chunk = cast(ChatCompletionChunk, chunk)
|
||||
if chunk.choices and chunk.choices[0].delta.content:
|
||||
yield HonchoLLMCallStreamChunk(
|
||||
content=chunk.choices[0].delta.content
|
||||
)
|
||||
if chunk.choices and chunk.choices[0].finish_reason:
|
||||
yield HonchoLLMCallStreamChunk(
|
||||
content="",
|
||||
is_done=True,
|
||||
finish_reasons=[chunk.choices[0].finish_reason],
|
||||
)
|
||||
|
||||
|
||||
def with_langfuse(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@wraps(func)
|
||||
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
lf.start_as_current_generation(name="LLM Call")
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import datetime
|
|||
import logging
|
||||
from typing import Any, Literal, overload
|
||||
|
||||
from langfuse.decorators import langfuse_context
|
||||
from langfuse import get_client
|
||||
from openai.types import CreateEmbeddingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
|
@ -24,6 +24,8 @@ from src.utils.shared_models import (
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
lf = get_client()
|
||||
|
||||
|
||||
class EmbeddingStore:
|
||||
"""Embedding store specialized for observation-based reasoning with structured metadata."""
|
||||
|
|
@ -68,7 +70,7 @@ class EmbeddingStore:
|
|||
conclusions, similarity_threshold=similarity_threshold
|
||||
)
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
langfuse_context.update_current_observation(
|
||||
lf.update_current_generation(
|
||||
input={"observations": [obs.model_dump() for obs in observations]},
|
||||
output={"unique_conclusions": unique_conclusions},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def conditional_observe(func: Callable[..., Any]) -> Callable[..., Any]:
|
|||
"""
|
||||
if settings.LANGFUSE_PUBLIC_KEY:
|
||||
# Import here to avoid circular imports and only import when needed
|
||||
from langfuse.decorators import observe # pyright: ignore
|
||||
from langfuse import observe # pyright: ignore
|
||||
|
||||
return observe()(func)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import asyncio
|
|||
import logging
|
||||
import time
|
||||
from enum import Enum
|
||||
from inspect import cleandoc as c
|
||||
|
||||
from mirascope import llm
|
||||
from sqlalchemy import update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing_extensions import TypedDict
|
||||
|
|
@ -12,7 +12,7 @@ from src import schemas
|
|||
from src.config import settings
|
||||
from src.dependencies import tracked_db
|
||||
from src.exceptions import ResourceNotFoundException
|
||||
from src.utils.clients import honcho_llm_call
|
||||
from src.utils.clients import HonchoLLMCallResponse, honcho_llm_call
|
||||
from src.utils.formatting import utc_now_iso
|
||||
from src.utils.logging import accumulate_metric
|
||||
|
||||
|
|
@ -77,17 +77,11 @@ class SummaryType(Enum):
|
|||
LONG = "honcho_chat_summary_long"
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.SUMMARY.PROVIDER,
|
||||
model=settings.SUMMARY.MODEL,
|
||||
max_tokens=settings.SUMMARY.MAX_TOKENS_SHORT,
|
||||
return_call_response=True,
|
||||
)
|
||||
async def create_short_summary(
|
||||
messages: list[models.Message],
|
||||
input_tokens: int,
|
||||
previous_summary: str | None = None,
|
||||
):
|
||||
) -> HonchoLLMCallResponse[str]:
|
||||
# input_tokens indicates how many tokens the message list + previous summary take up
|
||||
# we want to optimize short summaries to be smaller than the actual content being summarized
|
||||
# so we ask the agent to produce a word count roughly equal to either the input, or the max
|
||||
|
|
@ -100,7 +94,7 @@ async def create_short_summary(
|
|||
else:
|
||||
previous_summary_text = "There is no previous summary -- the messages are the beginning of the conversation."
|
||||
|
||||
return f"""
|
||||
prompt = c(f"""
|
||||
You are a system that summarizes parts of a conversation to create a concise and accurate summary. Focus on capturing:
|
||||
|
||||
1. Key facts and information shared (**Capture as many explicit facts as possible**)
|
||||
|
|
@ -123,19 +117,20 @@ Return only the summary without any explanation or meta-commentary.
|
|||
</conversation>
|
||||
|
||||
Produce as thorough a summary as possible in {output_words} words or less.
|
||||
"""
|
||||
""")
|
||||
|
||||
return await honcho_llm_call(
|
||||
provider=settings.SUMMARY.PROVIDER,
|
||||
model=settings.SUMMARY.MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.SUMMARY.MAX_TOKENS_SHORT,
|
||||
)
|
||||
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=settings.SUMMARY.PROVIDER,
|
||||
model=settings.SUMMARY.MODEL,
|
||||
max_tokens=settings.SUMMARY.MAX_TOKENS_LONG,
|
||||
return_call_response=True,
|
||||
)
|
||||
async def create_long_summary(
|
||||
messages: list[models.Message],
|
||||
previous_summary: str | None = None,
|
||||
):
|
||||
) -> HonchoLLMCallResponse[str]:
|
||||
# the word/token ratio is roughly 4:3 so we multiply by 0.75.
|
||||
# LLMs *seem* to respond better to getting asked for a word count but should workshop this.
|
||||
output_words = int(settings.SUMMARY.MAX_TOKENS_LONG * 0.75)
|
||||
|
|
@ -145,7 +140,7 @@ async def create_long_summary(
|
|||
else:
|
||||
previous_summary_text = "There is no previous summary -- the messages are the beginning of the conversation."
|
||||
|
||||
return f"""
|
||||
prompt = c(f"""
|
||||
You are a system that creates thorough, comprehensive summaries of conversations. Focus on capturing:
|
||||
|
||||
1. Key facts and information shared (**Capture as many explicit facts as possible**)
|
||||
|
|
@ -170,7 +165,14 @@ Return only the summary without any explanation or meta-commentary.
|
|||
</conversation>
|
||||
|
||||
Produce as thorough a summary as possible in {output_words} words or less.
|
||||
"""
|
||||
""")
|
||||
|
||||
return await honcho_llm_call(
|
||||
provider=settings.SUMMARY.PROVIDER,
|
||||
model=settings.SUMMARY.MODEL,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.SUMMARY.MAX_TOKENS_LONG,
|
||||
)
|
||||
|
||||
|
||||
async def summarize_if_needed(
|
||||
|
|
@ -345,7 +347,7 @@ async def _create_summary(
|
|||
A full summary of the conversation up to the last message
|
||||
"""
|
||||
|
||||
response: llm.CallResponse | None = None
|
||||
response: HonchoLLMCallResponse[str] | None = None
|
||||
try:
|
||||
if summary_type == SummaryType.SHORT:
|
||||
response = await create_short_summary(
|
||||
|
|
@ -355,11 +357,7 @@ async def _create_summary(
|
|||
response = await create_long_summary(messages, previous_summary_text)
|
||||
|
||||
summary_text = response.content
|
||||
summary_tokens = (
|
||||
response.usage.output_tokens
|
||||
if response.usage
|
||||
else len(response.content) // 4
|
||||
)
|
||||
summary_tokens = response.output_tokens
|
||||
|
||||
# Detect potential issues with the summary
|
||||
if not summary_text.strip():
|
||||
|
|
@ -379,19 +377,10 @@ async def _create_summary(
|
|||
)
|
||||
summary_tokens = 50
|
||||
|
||||
accumulate_metric(
|
||||
f"summary_{messages[-1].workspace_name}_{messages[-1].id}",
|
||||
f"{summary_type.name}_summary_input",
|
||||
response.usage.input_tokens if response and response.usage else "unknown",
|
||||
"tokens",
|
||||
)
|
||||
|
||||
accumulate_metric(
|
||||
f"summary_{messages[-1].workspace_name}_{messages[-1].id}",
|
||||
f"{summary_type.name}_summary_size",
|
||||
response.usage.output_tokens
|
||||
if response and response.usage
|
||||
else f"{summary_tokens} (est.)",
|
||||
response.output_tokens if response else f"{summary_tokens} (est.)",
|
||||
"tokens",
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Literal
|
||||
|
||||
from mirascope import Provider
|
||||
|
||||
Providers = Provider | Literal["custom"]
|
||||
SupportedProviders = Literal["anthropic", "openai", "google", "groq", "custom"]
|
||||
|
|
|
|||
|
|
@ -328,6 +328,7 @@ class HonchoHarness:
|
|||
# "src.routers.",
|
||||
# "src.crud.",
|
||||
"google_genai.models",
|
||||
"google.genai.models",
|
||||
]
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -141,24 +141,25 @@ def build_peer_card_caller(
|
|||
"openai" if candidate.provider == "custom" else candidate.provider
|
||||
)
|
||||
|
||||
@honcho_llm_call(
|
||||
provider=cast(Any, resolved_provider),
|
||||
model=candidate.model,
|
||||
track_name="Peer Card Call",
|
||||
response_model=PeerCardQuery,
|
||||
json_mode=True,
|
||||
max_tokens=settings.DERIVER.PEER_CARD_MAX_OUTPUT_TOKENS,
|
||||
reasoning_effort="minimal",
|
||||
enable_retry=True,
|
||||
retry_attempts=1, # unstructured output means we shouldn't need to retry, 1 just in case
|
||||
)
|
||||
async def call(old_peer_card: list[str] | None, new_observations: list[str]) -> Any:
|
||||
"""Return the prompt content for Mirascope to execute as a model call."""
|
||||
|
||||
return peer_card_prompt(
|
||||
prompt = peer_card_prompt(
|
||||
old_peer_card=old_peer_card, new_observations=new_observations
|
||||
)
|
||||
|
||||
response = await honcho_llm_call(
|
||||
provider=cast(Any, resolved_provider),
|
||||
model=candidate.model,
|
||||
prompt=prompt,
|
||||
max_tokens=settings.DERIVER.PEER_CARD_MAX_OUTPUT_TOKENS,
|
||||
response_model=PeerCardQuery,
|
||||
json_mode=True,
|
||||
reasoning_effort="minimal",
|
||||
enable_retry=True,
|
||||
retry_attempts=3,
|
||||
)
|
||||
|
||||
return response.content
|
||||
|
||||
return call
|
||||
|
||||
|
||||
|
|
@ -323,7 +324,7 @@ async def run_benchmark(candidates: list[Candidate], cases: list[Case]) -> int:
|
|||
case.old_peer_card, case.new_observations
|
||||
)
|
||||
new_card = card.card
|
||||
if new_card is None:
|
||||
if new_card is None or new_card == []:
|
||||
new_card = case.old_peer_card or []
|
||||
judgment = await judge_response(anthropic, case, new_card)
|
||||
return case, {"card": card, "judgment": judgment}
|
||||
|
|
|
|||
|
|
@ -257,8 +257,7 @@ async def sample_data(
|
|||
def mock_langfuse():
|
||||
"""Mock Langfuse decorator and context during tests"""
|
||||
with (
|
||||
patch("langfuse.decorators.observe") as mock_observe,
|
||||
patch("langfuse.decorators.langfuse_context") as mock_context,
|
||||
patch("langfuse.observe") as mock_observe,
|
||||
):
|
||||
# Mock the decorator to just return the function
|
||||
def return_value(func: Callable[..., Any]):
|
||||
|
|
@ -266,12 +265,6 @@ def mock_langfuse():
|
|||
|
||||
mock_observe.return_value = return_value
|
||||
|
||||
# Mock the context object
|
||||
mock_context_obj = MagicMock()
|
||||
mock_context_obj.update_current_observation = MagicMock()
|
||||
mock_context_obj.update_current_trace = MagicMock()
|
||||
mock_context.return_value = mock_context_obj
|
||||
|
||||
# Disable httpx logging during tests
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
|
|
@ -309,8 +302,8 @@ def mock_openai_embeddings():
|
|||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_mirascope_functions():
|
||||
"""Mock Mirascope LLM functions to avoid needing API keys during tests"""
|
||||
def mock_llm_call_functions():
|
||||
"""Mock LLM functions to avoid needing API keys during tests"""
|
||||
|
||||
# Create mock responses for different function types
|
||||
with (
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
# Test utilities package
|
||||
File diff suppressed because it is too large
Load Diff
489
uv.lock
489
uv.lock
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.10"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.13'",
|
||||
|
|
@ -439,15 +439,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docstring-parser"
|
||||
version = "0.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "email-validator"
|
||||
version = "2.2.0"
|
||||
|
|
@ -558,59 +549,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-ai-generativelanguage"
|
||||
version = "0.6.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core", extra = ["grpc"] },
|
||||
{ name = "google-auth" },
|
||||
{ name = "proto-plus" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/d1/48fe5d7a43d278e9f6b5ada810b0a3530bbeac7ed7fcbcd366f932f05316/google_ai_generativelanguage-0.6.15.tar.gz", hash = "sha256:8f6d9dc4c12b065fe2d0289026171acea5183ebf2d0b11cefe12f3821e159ec3", size = 1375443, upload-time = "2025-01-13T21:50:47.459Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/a3/67b8a6ff5001a1d8864922f2d6488dc2a14367ceb651bc3f09a947f2f306/google_ai_generativelanguage-0.6.15-py3-none-any.whl", hash = "sha256:5a03ef86377aa184ffef3662ca28f19eeee158733e45d7947982eb953c6ebb6c", size = 1327356, upload-time = "2025-01-13T21:50:44.174Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-core"
|
||||
version = "2.25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-auth" },
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "proto-plus" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/21/e9d043e88222317afdbdb567165fdbc3b0aad90064c7e0c9eb0ad9955ad8/google_api_core-2.25.1.tar.gz", hash = "sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8", size = 165443, upload-time = "2025-06-12T20:52:20.439Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/4b/ead00905132820b623732b175d66354e9d3e69fcf2a5dcdab780664e7896/google_api_core-2.25.1-py3-none-any.whl", hash = "sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7", size = 160807, upload-time = "2025-06-12T20:52:19.334Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
grpc = [
|
||||
{ name = "grpcio" },
|
||||
{ name = "grpcio-status" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-api-python-client"
|
||||
version = "2.178.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-api-core" },
|
||||
{ name = "google-auth" },
|
||||
{ name = "google-auth-httplib2" },
|
||||
{ name = "httplib2" },
|
||||
{ name = "uritemplate" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/99/98/916385a87d145a27661b630c480fadf9db32bb1ad9fb1b13e8dbcbe2af70/google_api_python_client-2.178.0.tar.gz", hash = "sha256:99cba921eb471bb5973b780c653ac54d96eef8a42f1b7375b7ab98f257a4414c", size = 13282628, upload-time = "2025-08-06T14:04:51.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/34/8ae31410a2d3f28b16b7135931133caf759d3aa0653f8397e344acec5a88/google_api_python_client-2.178.0-py3-none-any.whl", hash = "sha256:f420adcd050150ff1baefa817e96e1ffa16872744f53471cd34096612e580c34", size = 13809959, upload-time = "2025-08-06T14:04:47.94Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.40.3"
|
||||
|
|
@ -625,22 +563,9 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-auth-httplib2"
|
||||
version = "0.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-auth" },
|
||||
{ name = "httplib2" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/56/be/217a598a818567b28e859ff087f347475c807a5649296fb5a817c58dacef/google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05", size = 10842, upload-time = "2023-12-12T17:40:30.722Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/8a/fe34d2f3f9470a27b01c9e76226965863f153d5fbe276f83608562e49c04/google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d", size = 9253, upload-time = "2023-12-12T17:40:13.055Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-genai"
|
||||
version = "1.28.0"
|
||||
version = "1.32.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -652,27 +577,9 @@ dependencies = [
|
|||
{ name = "typing-extensions" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/f1/039bb08df4670e204c55b5da0b2fa5228dff3346bda01389a86b300f6f58/google_genai-1.28.0.tar.gz", hash = "sha256:e93053c02e616842679ba5ecce5b99db8c0ca6310623c55ff6245b5b1d293138", size = 221029, upload-time = "2025-07-30T21:39:57.002Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/03/ab/e6cdd8fa957c647ef00c4da7c59d0e734354bd49ed8d98c860732d8e1944/google_genai-1.32.0.tar.gz", hash = "sha256:349da3f5ff0e981066bd508585fcdd308d28fc4646f318c8f6d1aa6041f4c7e3", size = 240802, upload-time = "2025-08-27T22:16:32.781Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/ea/b704df3b348d3ae3572b0db5b52438fa426900b0830cff664107abfdba69/google_genai-1.28.0-py3-none-any.whl", hash = "sha256:7fd506799005cc87d3c5704a2eb5a2cb020d45b4d216a802e606700308f7f2f3", size = 219384, upload-time = "2025-07-30T21:39:55.652Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-generativeai"
|
||||
version = "0.8.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "google-ai-generativelanguage" },
|
||||
{ name = "google-api-core" },
|
||||
{ name = "google-api-python-client" },
|
||||
{ name = "google-auth" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/40/c42ff9ded9f09ec9392879a8e6538a00b2dc185e834a3392917626255419/google_generativeai-0.8.5-py3-none-any.whl", hash = "sha256:22b420817fb263f8ed520b33285f45976d5b21e904da32b80d4fd20c055123a2", size = 155427, upload-time = "2025-04-17T00:40:00.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/55/be09472f7a656af1208196d2ef9a3d2710f3cbcf695f51acbcbe28b9472b/google_genai-1.32.0-py3-none-any.whl", hash = "sha256:c0c4b1d45adf3aa99501050dd73da2f0dea09374002231052d81a6765d15e7f6", size = 241680, upload-time = "2025-08-27T22:16:31.409Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -755,68 +662,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/ab/f8/14672d69a91495f43462c5490067eeafc30346e81bda1a62848e897f9bc3/groq-0.31.0-py3-none-any.whl", hash = "sha256:5e3c7ec9728b7cccf913da982a9b5ebb46dc18a070b35e12a3d6a1e12d6b0f7f", size = 131365, upload-time = "2025-08-05T23:13:59.768Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio"
|
||||
version = "1.74.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/b4/35feb8f7cab7239c5b94bd2db71abb3d6adb5f335ad8f131abb6060840b6/grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1", size = 12756048, upload-time = "2025-07-24T18:54:23.039Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/66/54/68e51a90797ad7afc5b0a7881426c337f6a9168ebab73c3210b76aa7c90d/grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907", size = 5481935, upload-time = "2025-07-24T18:52:43.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/2a/af817c7e9843929e93e54d09c9aee2555c2e8d81b93102a9426b36e91833/grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb", size = 10986796, upload-time = "2025-07-24T18:52:47.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/94/d67756638d7bb07750b07d0826c68e414124574b53840ba1ff777abcd388/grpcio-1.74.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486", size = 5983663, upload-time = "2025-07-24T18:52:49.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/f5/c5e4853bf42148fea8532d49e919426585b73eafcf379a712934652a8de9/grpcio-1.74.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11", size = 6653765, upload-time = "2025-07-24T18:52:51.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/75/a1991dd64b331d199935e096cc9daa3415ee5ccbe9f909aa48eded7bba34/grpcio-1.74.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9", size = 6215172, upload-time = "2025-07-24T18:52:53.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/a4/7cef3dbb3b073d0ce34fd507efc44ac4c9442a0ef9fba4fb3f5c551efef5/grpcio-1.74.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc", size = 6329142, upload-time = "2025-07-24T18:52:54.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/d3/587920f882b46e835ad96014087054655312400e2f1f1446419e5179a383/grpcio-1.74.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e", size = 7018632, upload-time = "2025-07-24T18:52:56.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/95/c70a3b15a0bc83334b507e3d2ae20ee8fa38d419b8758a4d838f5c2a7d32/grpcio-1.74.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82", size = 6509641, upload-time = "2025-07-24T18:52:58.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/06/2e7042d06247d668ae69ea6998eca33f475fd4e2855f94dcb2aa5daef334/grpcio-1.74.0-cp310-cp310-win32.whl", hash = "sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7", size = 3817478, upload-time = "2025-07-24T18:53:00.128Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/20/e02b9dcca3ee91124060b65bbf5b8e1af80b3b76a30f694b44b964ab4d71/grpcio-1.74.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5", size = 4493971, upload-time = "2025-07-24T18:53:02.068Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/77/b2f06db9f240a5abeddd23a0e49eae2b6ac54d85f0e5267784ce02269c3b/grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31", size = 5487368, upload-time = "2025-07-24T18:53:03.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/99/0ac8678a819c28d9a370a663007581744a9f2a844e32f0fa95e1ddda5b9e/grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4", size = 10999804, upload-time = "2025-07-24T18:53:05.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/c6/a2d586300d9e14ad72e8dc211c7aecb45fe9846a51e558c5bca0c9102c7f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce", size = 5987667, upload-time = "2025-07-24T18:53:07.157Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/57/5f338bf56a7f22584e68d669632e521f0de460bb3749d54533fc3d0fca4f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3", size = 6655612, upload-time = "2025-07-24T18:53:09.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/ea/a4820c4c44c8b35b1903a6c72a5bdccec92d0840cf5c858c498c66786ba5/grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182", size = 6219544, upload-time = "2025-07-24T18:53:11.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/17/0537630a921365928f5abb6d14c79ba4dcb3e662e0dbeede8af4138d9dcf/grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d", size = 6334863, upload-time = "2025-07-24T18:53:12.925Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a6/85ca6cb9af3f13e1320d0a806658dca432ff88149d5972df1f7b51e87127/grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f", size = 7019320, upload-time = "2025-07-24T18:53:15.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/a7/fe2beab970a1e25d2eff108b3cf4f7d9a53c185106377a3d1989216eba45/grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4", size = 6514228, upload-time = "2025-07-24T18:53:16.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/c2/2f9c945c8a248cebc3ccda1b7a1bf1775b9d7d59e444dbb18c0014e23da6/grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b", size = 3817216, upload-time = "2025-07-24T18:53:20.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/d1/a9cf9c94b55becda2199299a12b9feef0c79946b0d9d34c989de6d12d05d/grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11", size = 4495380, upload-time = "2025-07-24T18:53:22.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/5d/e504d5d5c4469823504f65687d6c8fb97b7f7bf0b34873b7598f1df24630/grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8", size = 5445551, upload-time = "2025-07-24T18:53:23.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/01/730e37056f96f2f6ce9f17999af1556df62ee8dab7fa48bceeaab5fd3008/grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6", size = 10979810, upload-time = "2025-07-24T18:53:25.349Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/3d/09fd100473ea5c47083889ca47ffd356576173ec134312f6aa0e13111dee/grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5", size = 5941946, upload-time = "2025-07-24T18:53:27.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/99/12d2cca0a63c874c6d3d195629dcd85cdf5d6f98a30d8db44271f8a97b93/grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49", size = 6621763, upload-time = "2025-07-24T18:53:29.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/2c/930b0e7a2f1029bbc193443c7bc4dc2a46fedb0203c8793dcd97081f1520/grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7", size = 6180664, upload-time = "2025-07-24T18:53:30.823Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/d5/ff8a2442180ad0867717e670f5ec42bfd8d38b92158ad6bcd864e6d4b1ed/grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3", size = 6301083, upload-time = "2025-07-24T18:53:32.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/ba/b361d390451a37ca118e4ec7dccec690422e05bc85fba2ec72b06cefec9f/grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707", size = 6994132, upload-time = "2025-07-24T18:53:34.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/0c/3a5fa47d2437a44ced74141795ac0251bbddeae74bf81df3447edd767d27/grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b", size = 6489616, upload-time = "2025-07-24T18:53:36.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/95/ab64703b436d99dc5217228babc76047d60e9ad14df129e307b5fec81fd0/grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c", size = 3807083, upload-time = "2025-07-24T18:53:37.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/59/900aa2445891fc47a33f7d2f76e00ca5d6ae6584b20d19af9c06fa09bf9a/grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc", size = 4490123, upload-time = "2025-07-24T18:53:39.528Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/d8/1004a5f468715221450e66b051c839c2ce9a985aa3ee427422061fcbb6aa/grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89", size = 5449488, upload-time = "2025-07-24T18:53:41.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/0e/33731a03f63740d7743dced423846c831d8e6da808fcd02821a4416df7fa/grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01", size = 10974059, upload-time = "2025-07-24T18:53:43.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/c6/3d2c14d87771a421205bdca991467cfe473ee4c6a1231c1ede5248c62ab8/grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e", size = 5945647, upload-time = "2025-07-24T18:53:45.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/83/5a354c8aaff58594eef7fffebae41a0f8995a6258bbc6809b800c33d4c13/grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91", size = 6626101, upload-time = "2025-07-24T18:53:47.015Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/ca/4fdc7bf59bf6994aa45cbd4ef1055cd65e2884de6113dbd49f75498ddb08/grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249", size = 6182562, upload-time = "2025-07-24T18:53:48.967Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/48/2869e5b2c1922583686f7ae674937986807c2f676d08be70d0a541316270/grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362", size = 6303425, upload-time = "2025-07-24T18:53:50.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/0e/bac93147b9a164f759497bc6913e74af1cb632c733c7af62c0336782bd38/grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f", size = 6996533, upload-time = "2025-07-24T18:53:52.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/35/9f6b2503c1fd86d068b46818bbd7329db26a87cdd8c01e0d1a9abea1104c/grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20", size = 6491489, upload-time = "2025-07-24T18:53:55.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/33/a04e99be2a82c4cbc4039eb3a76f6c3632932b9d5d295221389d10ac9ca7/grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa", size = 3805811, upload-time = "2025-07-24T18:53:56.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grpcio-status"
|
||||
version = "1.71.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "grpcio" },
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/d1/b6e9877fedae3add1afdeae1f89d1927d296da9cf977eca0eb08fb8a460e/grpcio_status-1.71.2.tar.gz", hash = "sha256:c7a97e176df71cdc2c179cd1847d7fc86cca5832ad12e9798d7fed6b7a1aab50", size = 13677, upload-time = "2025-06-28T04:24:05.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/67/58/317b0134129b556a93a3b0afe00ee675b5657f0155509e22fcb853bafe2d/grpcio_status-1.71.2-py3-none-any.whl", hash = "sha256:803c98cb6a8b7dc6dbb785b1111aed739f241ab5e9da0bba96888aa74704cfd3", size = 14424, upload-time = "2025-06-28T04:23:42.136Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.16.0"
|
||||
|
|
@ -834,10 +679,11 @@ dependencies = [
|
|||
{ name = "alembic" },
|
||||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "fastapi-pagination" },
|
||||
{ name = "google-generativeai" },
|
||||
{ name = "google-genai" },
|
||||
{ name = "greenlet" },
|
||||
{ name = "groq" },
|
||||
{ name = "httpx" },
|
||||
{ name = "mirascope", extra = ["anthropic", "google", "groq", "langfuse"] },
|
||||
{ name = "langfuse" },
|
||||
{ name = "nanoid" },
|
||||
{ name = "openai" },
|
||||
{ name = "pdfplumber" },
|
||||
|
|
@ -850,6 +696,7 @@ dependencies = [
|
|||
{ name = "rich" },
|
||||
{ name = "sentry-sdk", extra = ["anthropic", "fastapi", "sqlalchemy"] },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "tiktoken" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
|
|
@ -874,10 +721,11 @@ requires-dist = [
|
|||
{ name = "alembic", specifier = ">=1.14.0" },
|
||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.111.0" },
|
||||
{ name = "fastapi-pagination", specifier = ">=0.12.24" },
|
||||
{ name = "google-generativeai", specifier = ">=0.8.5" },
|
||||
{ name = "google-genai", specifier = ">=1.32.0" },
|
||||
{ name = "greenlet", specifier = ">=3.0.3" },
|
||||
{ name = "groq", specifier = ">=0.31.0" },
|
||||
{ name = "httpx", specifier = ">=0.27.0" },
|
||||
{ name = "mirascope", extras = ["anthropic", "google", "groq", "langfuse"], specifier = ">=1.25.5" },
|
||||
{ name = "langfuse", specifier = ">=3.3.2" },
|
||||
{ name = "nanoid", specifier = ">=2.0.0" },
|
||||
{ name = "openai", specifier = ">=1.99.7" },
|
||||
{ name = "pdfplumber", specifier = ">=0.11.7" },
|
||||
|
|
@ -890,6 +738,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 = "tenacity", specifier = ">=9.1.2" },
|
||||
{ name = "tiktoken", specifier = ">=0.9.0" },
|
||||
{ name = "typing-extensions", specifier = ">=4.11.0" },
|
||||
]
|
||||
|
|
@ -964,18 +813,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httplib2"
|
||||
version = "0.22.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyparsing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/ad/2371116b22d616c194aa25ec410c9c6c37f23599dcd590502b74db197584/httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81", size = 351116, upload-time = "2023-03-21T22:29:37.214Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/6c/d2fbdaaa5959339d53ba38e94c123e4e84b8fbc4b84beb0e70d7c1608486/httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc", size = 96854, upload-time = "2023-03-21T22:29:35.683Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.6.4"
|
||||
|
|
@ -1045,6 +882,18 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "8.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "zipp" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
|
|
@ -1157,21 +1006,22 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langfuse"
|
||||
version = "2.60.9"
|
||||
version = "3.3.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "backoff" },
|
||||
{ name = "httpx" },
|
||||
{ name = "idna" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "requests" },
|
||||
{ name = "wrapt" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/1a/2443e3715767f1bf9d8cf32d74ac59cfb60e1d9b84e99df13fd656639eb3/langfuse-2.60.9.tar.gz", hash = "sha256:040753346d7df4a0be6967dfc7efe3de313fee362524fe2f801867fcbbca3c98", size = 152684, upload-time = "2025-06-29T09:39:27.628Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6d/91/b92566f4fd73b607136bcd3ed011af98a571dc4c0f60b95fffb768d3cb5c/langfuse-3.3.2.tar.gz", hash = "sha256:4b029fed675b2b631b96da157459c285930420139667f25a6ee71ed3b3c5a71c", size = 164001, upload-time = "2025-08-27T15:55:11.716Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/50/3aa93fc284ba5f81dcdd00b6414caee338fd45d77fa4959c3e4f838cebc6/langfuse-2.60.9-py3-none-any.whl", hash = "sha256:e4291a66bc579c66d7652da5603ca7f0409536700d7b812e396780b5d9a0685d", size = 275543, upload-time = "2025-06-29T09:39:26.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/ed/c4e914c5362740a40b12eaa8f90205cc05af81bacf983f3e89bf26b1c3cf/langfuse-3.3.2-py3-none-any.whl", hash = "sha256:0a5871501720c362183fbf2970c895fb0d46f4ed121a5f95a1874f9af081cfdd", size = 317629, upload-time = "2025-08-27T15:55:09.435Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1265,37 +1115,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mirascope"
|
||||
version = "1.25.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "jiter" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/bf/7b2441d8740fe82fdd201bcdefacff300ac9f61245169d74d2ebfa607a25/mirascope-1.25.5.tar.gz", hash = "sha256:bcc2734e4f83d6a8d66c7fb1660a12285418ea8f4d52e8f341b8e47ee54c6667", size = 627330, upload-time = "2025-08-05T22:06:41.809Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/2a/b5d57fcdaa7253cbfb6a2b3100d1e08a2133cf9f44964f8774541d09c36e/mirascope-1.25.5-py3-none-any.whl", hash = "sha256:b7f383ff6f4eeecd80f172fae51718103a473f8213859249014a65d30959226a", size = 373303, upload-time = "2025-08-05T22:06:40.077Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
anthropic = [
|
||||
{ name = "anthropic" },
|
||||
]
|
||||
google = [
|
||||
{ name = "google-genai" },
|
||||
{ name = "pillow" },
|
||||
{ name = "proto-plus" },
|
||||
]
|
||||
groq = [
|
||||
{ name = "groq" },
|
||||
]
|
||||
langfuse = [
|
||||
{ name = "langfuse" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoid"
|
||||
version = "2.0.0"
|
||||
|
|
@ -1497,6 +1316,88 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/64/2d/a41c49550a69374111647be22b587e3311a6fc31fc7370e14448e78018bc/openai-1.99.7-py3-none-any.whl", hash = "sha256:ef4165cc4f8872dd4a967d109f12b0b9c98a1e20ae05940c28701729c2883891", size = 786809, upload-time = "2025-08-11T15:13:08.537Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.36.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "importlib-metadata" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-exporter-otlp-proto-common"
|
||||
version = "1.36.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-proto" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/da/7747e57eb341c59886052d733072bc878424bf20f1d8cf203d508bbece5b/opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf", size = 20302, upload-time = "2025-07-29T15:12:07.71Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/ed/22290dca7db78eb32e0101738366b5bbda00d0407f00feffb9bf8c3fdf87/opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840", size = 18349, upload-time = "2025-07-29T15:11:51.327Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-exporter-otlp-proto-http"
|
||||
version = "1.36.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-common" },
|
||||
{ name = "opentelemetry-proto" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "requests" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/85/6632e7e5700ba1ce5b8a065315f92c1e6d787ccc4fb2bdab15139eaefc82/opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e", size = 16213, upload-time = "2025-07-29T15:12:08.932Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/41/a680d38b34f8f5ddbd78ed9f0042e1cc712d58ec7531924d71cb1e6c629d/opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902", size = 18752, upload-time = "2025-07-29T15:11:53.164Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-proto"
|
||||
version = "1.36.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/02/f6556142301d136e3b7e95ab8ea6a5d9dc28d879a99f3dd673b5f97dca06/opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f", size = 46152, upload-time = "2025-07-29T15:12:15.717Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/57/3361e06136225be8180e879199caea520f38026f8071366241ac458beb8d/opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e", size = 72537, upload-time = "2025-07-29T15:12:02.243Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-sdk"
|
||||
version = "1.36.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-semantic-conventions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-semantic-conventions"
|
||||
version = "0.57b0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
|
|
@ -1639,18 +1540,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proto-plus"
|
||||
version = "1.26.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "protobuf" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "5.29.5"
|
||||
|
|
@ -1927,15 +1816,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pypdfium2"
|
||||
version = "4.30.0"
|
||||
|
|
@ -2438,11 +2318,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "8.5.0"
|
||||
version = "9.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2577,15 +2457,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uritemplate"
|
||||
version = "4.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
|
|
@ -2827,64 +2698,78 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.17.2"
|
||||
version = "1.17.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/d1/1daec934997e8b160040c78d7b31789f19b122110a75eca3d4e8da0049e1/wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984", size = 53307, upload-time = "2025-01-14T10:33:13.616Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/7b/13369d42651b809389c1a7153baa01d9700430576c81a2f5c5e460df0ed9/wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22", size = 38486, upload-time = "2025-01-14T10:33:15.947Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/bf/e0105016f907c30b4bd9e377867c48c34dc9c6c0c104556c9c9126bd89ed/wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7", size = 38777, upload-time = "2025-01-14T10:33:17.462Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/70/0f6e0679845cbf8b165e027d43402a55494779295c4b08414097b258ac87/wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c", size = 83314, upload-time = "2025-01-14T10:33:21.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/77/0576d841bf84af8579124a93d216f55d6f74374e4445264cb378a6ed33eb/wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72", size = 74947, upload-time = "2025-01-14T10:33:24.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/ec/00759565518f268ed707dcc40f7eeec38637d46b098a1f5143bff488fe97/wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061", size = 82778, upload-time = "2025-01-14T10:33:26.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/5a/7cffd26b1c607b0b0c8a9ca9d75757ad7620c9c0a9b4a25d3f8a1480fafc/wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2", size = 81716, upload-time = "2025-01-14T10:33:27.372Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/09/dccf68fa98e862df7e6a60a61d43d644b7d095a5fc36dbb591bbd4a1c7b2/wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c", size = 74548, upload-time = "2025-01-14T10:33:28.52Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/8e/067021fa3c8814952c5e228d916963c1115b983e21393289de15128e867e/wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62", size = 81334, upload-time = "2025-01-14T10:33:29.643Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/0d/9d4b5219ae4393f718699ca1c05f5ebc0c40d076f7e65fd48f5f693294fb/wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563", size = 36427, upload-time = "2025-01-14T10:33:30.832Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/6a/c5a83e8f61aec1e1aeef939807602fb880e5872371e95df2137142f5c58e/wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f", size = 38774, upload-time = "2025-01-14T10:33:32.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/f7/a2aab2cbc7a665efab072344a8949a71081eed1d2f451f7f7d2b966594a2/wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58", size = 53308, upload-time = "2025-01-14T10:33:33.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/ff/149aba8365fdacef52b31a258c4dc1c57c79759c335eff0b3316a2664a64/wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda", size = 38488, upload-time = "2025-01-14T10:33:35.264Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/46/5a917ce85b5c3b490d35c02bf71aedaa9f2f63f2d15d9949cc4ba56e8ba9/wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438", size = 38776, upload-time = "2025-01-14T10:33:38.28Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/74/336c918d2915a4943501c77566db41d1bd6e9f4dbc317f356b9a244dfe83/wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a", size = 83776, upload-time = "2025-01-14T10:33:40.678Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/99/c0c844a5ccde0fe5761d4305485297f91d67cf2a1a824c5f282e661ec7ff/wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000", size = 75420, upload-time = "2025-01-14T10:33:41.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/b0/9fc566b0fe08b282c850063591a756057c3247b2362b9286429ec5bf1721/wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6", size = 83199, upload-time = "2025-01-14T10:33:43.598Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/4b/71996e62d543b0a0bd95dda485219856def3347e3e9380cc0d6cf10cfb2f/wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b", size = 82307, upload-time = "2025-01-14T10:33:48.499Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/35/0282c0d8789c0dc9bcc738911776c762a701f95cfe113fb8f0b40e45c2b9/wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662", size = 75025, upload-time = "2025-01-14T10:33:51.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/6d/90c9fd2c3c6fee181feecb620d95105370198b6b98a0770cba090441a828/wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72", size = 81879, upload-time = "2025-01-14T10:33:52.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/fa/9fb6e594f2ce03ef03eddbdb5f4f90acb1452221a5351116c7c4708ac865/wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317", size = 36419, upload-time = "2025-01-14T10:33:53.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/f8/fb1773491a253cbc123c5d5dc15c86041f746ed30416535f2a8df1f4a392/wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3", size = 38773, upload-time = "2025-01-14T10:33:56.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/b9/0ffd557a92f3b11d4c5d5e0c5e4ad057bd9eb8586615cdaf901409920b14/wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125", size = 53800, upload-time = "2025-01-14T10:34:21.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ef/8be90a0b7e73c32e550c73cfb2fa09db62234227ece47b0e80a05073b375/wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998", size = 38824, upload-time = "2025-01-14T10:34:22.999Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/89/0aae34c10fe524cce30fe5fc433210376bce94cf74d05b0d68344c8ba46e/wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5", size = 38920, upload-time = "2025-01-14T10:34:25.386Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/24/11c4510de906d77e0cfb5197f1b1445d4fec42c9a39ea853d482698ac681/wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8", size = 88690, upload-time = "2025-01-14T10:34:28.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/d7/cfcf842291267bf455b3e266c0c29dcb675b5540ee8b50ba1699abf3af45/wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6", size = 80861, upload-time = "2025-01-14T10:34:29.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/66/5d973e9f3e7370fd686fb47a9af3319418ed925c27d72ce16b791231576d/wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc", size = 89174, upload-time = "2025-01-14T10:34:31.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/d3/8e17bb70f6ae25dabc1aaf990f86824e4fd98ee9cadf197054e068500d27/wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2", size = 86721, upload-time = "2025-01-14T10:34:32.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/54/f170dfb278fe1c30d0ff864513cff526d624ab8de3254b20abb9cffedc24/wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b", size = 79763, upload-time = "2025-01-14T10:34:34.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/98/de07243751f1c4a9b15c76019250210dd3486ce098c3d80d5f729cba029c/wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504", size = 87585, upload-time = "2025-01-14T10:34:36.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/f0/13925f4bd6548013038cdeb11ee2cbd4e37c30f8bfd5db9e5a2a370d6e20/wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a", size = 36676, upload-time = "2025-01-14T10:34:37.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/ae/743f16ef8c2e3628df3ddfd652b7d4c555d12c84b53f3d8218498f4ade9b/wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845", size = 38871, upload-time = "2025-01-14T10:34:39.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/bc/30f903f891a82d402ffb5fda27ec1d621cc97cb74c16fea0b6141f1d4e87/wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192", size = 56312, upload-time = "2025-01-14T10:34:40.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/04/c97273eb491b5f1c918857cd26f314b74fc9b29224521f5b83f872253725/wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b", size = 40062, upload-time = "2025-01-14T10:34:45.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/ca/3b7afa1eae3a9e7fefe499db9b96813f41828b9fdb016ee836c4c379dadb/wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0", size = 40155, upload-time = "2025-01-14T10:34:47.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/be/7c1baed43290775cb9030c774bc53c860db140397047cc49aedaf0a15477/wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306", size = 113471, upload-time = "2025-01-14T10:34:50.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/98/4ed894cf012b6d6aae5f5cc974006bdeb92f0241775addad3f8cd6ab71c8/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb", size = 101208, upload-time = "2025-01-14T10:34:52.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/fd/0c30f2301ca94e655e5e057012e83284ce8c545df7661a78d8bfca2fac7a/wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681", size = 109339, upload-time = "2025-01-14T10:34:53.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/56/05d000de894c4cfcb84bcd6b1df6214297b8089a7bd324c21a4765e49b14/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6", size = 110232, upload-time = "2025-01-14T10:34:55.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/f8/c3f6b2cf9b9277fb0813418e1503e68414cd036b3b099c823379c9575e6d/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6", size = 100476, upload-time = "2025-01-14T10:34:58.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/b1/0bb11e29aa5139d90b770ebbfa167267b1fc548d2302c30c8f7572851738/wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f", size = 106377, upload-time = "2025-01-14T10:34:59.3Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/e1/0122853035b40b3f333bbb25f1939fc1045e21dd518f7f0922b60c156f7c/wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555", size = 37986, upload-time = "2025-01-14T10:35:00.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/5e/1655cf481e079c1f22d0cabdd4e51733679932718dc23bf2db175f329b76/wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c", size = 40750, upload-time = "2025-01-14T10:35:03.378Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.23.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue