From ee2bbdd9ead19a8f79919553bddd3e28f0d14405 Mon Sep 17 00:00:00 2001 From: h Date: Wed, 3 Jun 2026 23:55:50 +0200 Subject: [PATCH] feat(bot): add backup models --- backend/.env.example | 4 + backend/pyproject.toml | 2 +- backend/src/bot/handlers/message/handler.py | 80 ++++++++----- backend/src/bot/modules/ai/agent.py | 119 +++++++++++++++++++- backend/src/utils/env.py | 7 ++ backend/uv.lock | 89 ++++++++++++++- 6 files changed, 264 insertions(+), 37 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 638f9b2..21064e9 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -2,6 +2,10 @@ BOT__TOKEN= SITE__URL= +# Leave ANTHROPIC_API_KEY empty to disable the Anthropic backup. +ANTHROPIC_API_KEY= +ANTHROPIC_MODEL=claude-opus-4-8 + LOG__LEVEL=INFO LOG__LEVEL_EXTERNAL=WARNING LOG__SHOW_TIME=false diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 44a8c4f..5167e8f 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -10,7 +10,7 @@ dependencies = [ "aiogram>=3.24.0", "convex>=0.7.0", "httpx>=0.27", - "pydantic-ai-slim[google]>=1.44.0", + "pydantic-ai-slim[google,anthropic]>=1.44.0", "pydantic-settings>=2.12.0", "rich>=14.2.0", "xkcdpass>=1.19.0", diff --git a/backend/src/bot/handlers/message/handler.py b/backend/src/bot/handlers/message/handler.py index 6d5c784..35436b0 100644 --- a/backend/src/bot/handlers/message/handler.py +++ b/backend/src/bot/handlers/message/handler.py @@ -31,6 +31,7 @@ from bot.modules.ai import ( from bot.user_lock import get_user_lock from utils import env from utils.convex import ConvexClient +from utils.logging import logger router = Router() convex = ConvexClient(env.convex_url) @@ -54,9 +55,7 @@ async def _enqueue_room_upload( }, ) except Exception as e: # noqa: BLE001 - from utils.logging import logger as _logger # noqa: PLC0415 - - _logger.warning(f"Failed to enqueue room upload: {e}") + logger.warning(f"Failed to enqueue room upload: {e}") def schedule_room_uploads( @@ -236,7 +235,10 @@ class ProxyStreamingState: async def send_long_message( - bot: Bot, chat_id: int, text: str, reply_markup: ReplyKeyboardMarkup | None = None + bot: Bot, + chat_id: int, + text: str, + reply_markup: ReplyKeyboardMarkup | ReplyKeyboardRemove | None = None, ) -> int | None: parts = split_message(text) first_id: int | None = None @@ -359,7 +361,7 @@ async def process_message_from_web( # noqa: C901, PLR0912, PLR0913, PLR0915 system_prompt = SUMMARIZE_PROMPT if is_summarize else user.get("systemPrompt") if system_prompt and inject_content: system_prompt = system_prompt.replace("{theory_database}", inject_content) - text_agent = create_text_agent( + text_agent = await create_text_agent( api_key=api_key, model_name=model_name, system_prompt=system_prompt, @@ -406,13 +408,21 @@ async def process_message_from_web( # noqa: C901, PLR0912, PLR0913, PLR0915 if state: await state.flush() - full_history = [*history, {"role": "assistant", "content": final_answer}] - follow_up_model = user.get("followUpModel", "gemini-3.1-flash-lite-preview") - follow_up_prompt = user.get("followUpPrompt") - follow_up_agent = create_follow_up_agent( - api_key=api_key, model_name=follow_up_model, system_prompt=follow_up_prompt - ) - follow_ups = await get_follow_ups(follow_up_agent, full_history, chat_images) + follow_ups: list[str] = [] + try: + full_history = [*history, {"role": "assistant", "content": final_answer}] + follow_up_model = user.get("followUpModel", "gemini-3.1-flash-lite") + follow_up_prompt = user.get("followUpPrompt") + follow_up_agent = create_follow_up_agent( + api_key=api_key, + model_name=follow_up_model, + system_prompt=follow_up_prompt, + ) + follow_ups = await get_follow_ups( + follow_up_agent, full_history, chat_images + ) + except Exception as e: # noqa: BLE001 + logger.warning(f"Follow-up generation failed: {e}") if state: await state.stop_typing() @@ -445,7 +455,11 @@ async def process_message_from_web( # noqa: C901, PLR0912, PLR0913, PLR0915 if tg_chat_id and processing_msg: with contextlib.suppress(Exception): await processing_msg.delete() - keyboard = make_follow_up_keyboard(follow_ups) + keyboard = ( + make_follow_up_keyboard(follow_ups) + if follow_ups + else ReplyKeyboardRemove() + ) sent_id = await send_long_message(bot, tg_chat_id, final_answer, keyboard) if sent_id is not None: with contextlib.suppress(Exception): @@ -572,7 +586,7 @@ async def process_message( # noqa: C901, PLR0912, PLR0913, PLR0915 system_prompt = user.get("systemPrompt") if system_prompt and inject_content: system_prompt = system_prompt.replace("{theory_database}", inject_content) - text_agent = create_text_agent( + text_agent = await create_text_agent( api_key=api_key, model_name=model_name, system_prompt=system_prompt, @@ -627,20 +641,24 @@ async def process_message( # noqa: C901, PLR0912, PLR0913, PLR0915 if proxy_state: await proxy_state.flush() - full_history = [ - *history[:-1], - {"role": "assistant", "content": final_answer}, - ] - follow_up_model = user.get("followUpModel", "gemini-3.1-flash-lite-preview") - follow_up_prompt = user.get("followUpPrompt") - follow_up_agent = create_follow_up_agent( - api_key=api_key, - model_name=follow_up_model, - system_prompt=follow_up_prompt, - ) - follow_ups = await get_follow_ups( - follow_up_agent, full_history, chat_images - ) + follow_ups: list[str] = [] + try: + full_history = [ + *history[:-1], + {"role": "assistant", "content": final_answer}, + ] + follow_up_model = user.get("followUpModel", "gemini-3.1-flash-lite") + follow_up_prompt = user.get("followUpPrompt") + follow_up_agent = create_follow_up_agent( + api_key=api_key, + model_name=follow_up_model, + system_prompt=follow_up_prompt, + ) + follow_ups = await get_follow_ups( + follow_up_agent, full_history, chat_images + ) + except Exception as e: # noqa: BLE001 + logger.warning(f"Follow-up generation failed: {e}") await state.stop_typing() @@ -657,7 +675,11 @@ async def process_message( # noqa: C901, PLR0912, PLR0913, PLR0915 with contextlib.suppress(Exception): await processing_msg.delete() - keyboard = make_follow_up_keyboard(follow_ups) + keyboard = ( + make_follow_up_keyboard(follow_ups) + if follow_ups + else ReplyKeyboardRemove() + ) await send_long_message(bot, chat_id, final_answer, keyboard) if proxy_state and proxy_config: diff --git a/backend/src/bot/modules/ai/agent.py b/backend/src/bot/modules/ai/agent.py index 6b79137..4b4f352 100644 --- a/backend/src/bot/modules/ai/agent.py +++ b/backend/src/bot/modules/ai/agent.py @@ -1,3 +1,5 @@ +import asyncio +import time from collections.abc import Awaitable, Callable from dataclasses import dataclass @@ -11,6 +13,8 @@ from pydantic_ai import ( TextPart, UserPromptPart, ) +from pydantic_ai.models import Model +from pydantic_ai.models.fallback import FallbackModel from pydantic_ai.models.google import GoogleModel from pydantic_ai.providers.google import GoogleProvider @@ -24,6 +28,113 @@ from .prompts import DEFAULT_FOLLOW_UP StreamCallback = Callable[[str], Awaitable[None]] convex = ConvexClient(env.convex_url) +CONTENT_FALLBACK_CHAIN = ["gemini-3.5-pro", "gemini-3.1-pro", "gemini-3.5-flash"] +FOLLOW_UP_FALLBACK_CHAIN = [ + "gemini-3.5-flash", + "gemini-3.1-flash-lite", + "gemini-3.5-flash-lite", +] +MODELS_CACHE_TTL_SECONDS = 600.0 + +_models_cache: dict[str, tuple[float, set[str]]] = {} + + +def _strip_preview(name: str) -> str: + return ( + name.removesuffix("-preview").rstrip("-") if name.endswith("-preview") else name + ) + + +def _dedup(names: list[str]) -> list[str]: + seen: set[str] = set() + result: list[str] = [] + for name in names: + if name and name not in seen: + seen.add(name) + result.append(name) + return result + + +def _fetch_google_models(api_key: str) -> set[str]: + names: set[str] = set() + try: + from google import genai # noqa: PLC0415 + + client = genai.Client(api_key=api_key) + for model in client.models.list(): + name = (model.name or "").removeprefix("models/") + actions = getattr(model, "supported_actions", None) or getattr( + model, "supported_generation_methods", None + ) + if actions and "generateContent" not in actions: + continue + if name: + names.add(name) + except Exception as e: # noqa: BLE001 + logger.warning(f"Failed to list Google models: {e}") + return names + + +async def _available_google_models(api_key: str) -> set[str]: + cached = _models_cache.get(api_key) + now = time.monotonic() + if cached and (now - cached[0]) < MODELS_CACHE_TTL_SECONDS: + return cached[1] + names = await asyncio.to_thread(_fetch_google_models, api_key) + if names: + _models_cache[api_key] = (now, names) + return names + + +def _pick_from_available(available: set[str]) -> list[str]: + gemini = sorted( + n for n in available if n.startswith("gemini-") and "preview" not in n + ) + pros = [n for n in gemini if "pro" in n] + flashes = [n for n in gemini if "flash" in n] + picked = _dedup([*pros[:2], *flashes[:2]]) + return picked or sorted(available)[:3] + + +def _anthropic_model() -> Model | None: + key = env.anthropic_api_key.get_secret_value() + if not key: + return None + from pydantic_ai.models.anthropic import AnthropicModel # noqa: PLC0415 + from pydantic_ai.providers.anthropic import AnthropicProvider # noqa: PLC0415 + + return AnthropicModel(env.anthropic_model, provider=AnthropicProvider(api_key=key)) + + +def _as_model(models: list[Model]) -> Model: + return models[0] if len(models) == 1 else FallbackModel(*models) + + +async def build_content_model(api_key: str, requested_model: str) -> Model: + candidates = _dedup( + [requested_model, _strip_preview(requested_model), *CONTENT_FALLBACK_CHAIN] + ) + available = await _available_google_models(api_key) + if available: + live = [name for name in candidates if name in available] + candidates = live or _pick_from_available(available) or candidates + + provider = GoogleProvider(api_key=api_key) + models: list[Model] = [GoogleModel(name, provider=provider) for name in candidates] + anthropic = _anthropic_model() + if anthropic: + models.append(anthropic) + return _as_model(models) + + +def build_follow_up_model(api_key: str, requested_model: str) -> Model: + candidates = _dedup( + [requested_model, _strip_preview(requested_model), *FOLLOW_UP_FALLBACK_CHAIN] + ) + provider = GoogleProvider(api_key=api_key) + models: list[Model] = [GoogleModel(name, provider=provider) for name in candidates] + return _as_model(models) + @dataclass class ImageData: @@ -51,14 +162,13 @@ RAG_SYSTEM_ADDITION = ( ) -def create_text_agent( +async def create_text_agent( api_key: str, model_name: str = "gemini-3-pro-preview", system_prompt: str | None = None, rag_db_names: list[str] | None = None, ) -> Agent[AgentDeps, str] | Agent[None, str]: - provider = GoogleProvider(api_key=api_key) - model = GoogleModel(model_name, provider=provider) + model = await build_content_model(api_key, model_name) base_prompt = system_prompt or DEFAULT_SYSTEM_PROMPT if rag_db_names: @@ -104,8 +214,7 @@ def create_follow_up_agent( model_name: str = "gemini-3.1-flash-lite-preview", system_prompt: str | None = None, ) -> Agent[None, FollowUpOptions]: - provider = GoogleProvider(api_key=api_key) - model = GoogleModel(model_name, provider=provider) + model = build_follow_up_model(api_key, model_name) prompt = system_prompt or DEFAULT_FOLLOW_UP agent: Agent[None, FollowUpOptions] = Agent( # ty: ignore[invalid-assignment] model, output_type=FollowUpOptions, instructions=prompt diff --git a/backend/src/utils/env.py b/backend/src/utils/env.py index 28867e5..3bfd06d 100644 --- a/backend/src/utils/env.py +++ b/backend/src/utils/env.py @@ -36,6 +36,13 @@ class Settings(BaseSettings): log: LogSettings collaborative: CollaborativeSettings + anthropic_api_key: SecretStr = Field( + default=SecretStr(""), validation_alias=AliasChoices("ANTHROPIC_API_KEY") + ) + anthropic_model: str = Field( + default="claude-sonnet-4-5", validation_alias=AliasChoices("ANTHROPIC_MODEL") + ) + convex_url: str = Field(validation_alias=AliasChoices("CONVEX_SELF_HOSTED_URL")) convex_http_url: str = Field( default="", validation_alias=AliasChoices("CONVEX_HTTP_URL") diff --git a/backend/uv.lock b/backend/uv.lock index 9472221..4ed191e 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -126,6 +126,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anthropic" +version = "0.105.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/46/47581b8c689c743ceabf6a0f9ff48472160900ce802d26c0fb50423997b3/anthropic-0.105.2.tar.gz", hash = "sha256:0e26b90841c2dced7cc6e98d21d5517d0be33f1876b8e779f478202e28bcaa07", size = 853789, upload-time = "2026-05-29T00:21:14.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/75/be0c357e33a5a56c8f9db5b4212f886138d2bf59c0952d858f6b75d710ef/anthropic-0.105.2-py3-none-any.whl", hash = "sha256:e53ed5f6bf36fb1ecb9b25d8634cfd30e02fab9fb3374a0c2d5c585874757230", size = 837507, upload-time = "2026-05-29T00:21:15.528Z" }, +] + [[package]] name = "anyio" version = "4.12.1" @@ -155,7 +174,7 @@ dependencies = [ { name = "aiogram" }, { name = "convex" }, { name = "httpx" }, - { name = "pydantic-ai-slim", extra = ["google"] }, + { name = "pydantic-ai-slim", extra = ["anthropic", "google"] }, { name = "pydantic-settings" }, { name = "rich" }, { name = "xkcdpass" }, @@ -166,7 +185,7 @@ requires-dist = [ { name = "aiogram", specifier = ">=3.24.0" }, { name = "convex", specifier = ">=0.7.0" }, { name = "httpx", specifier = ">=0.27" }, - { name = "pydantic-ai-slim", extras = ["google"], specifier = ">=1.44.0" }, + { name = "pydantic-ai-slim", extras = ["google", "anthropic"], specifier = ">=1.44.0" }, { name = "pydantic-settings", specifier = ">=2.12.0" }, { name = "rich", specifier = ">=14.2.0" }, { name = "xkcdpass", specifier = ">=1.19.0" }, @@ -363,6 +382,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "docstring-parser" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, +] + [[package]] name = "frozenlist" version = "1.8.0" @@ -555,6 +583,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] +[[package]] +name = "jiter" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/b5/55f06bb281d92fb3cc86d14e1def2bd908bb77693183e7cb1f5a3c388b0c/jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76", size = 166640, upload-time = "2026-05-19T10:09:48.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/f4/f708c900ecee41b2025ef8413d5351e5649eb2125c506f6720cc69b06f5c/jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3", size = 307829, upload-time = "2026-05-19T10:07:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/59/db537c0949e83668c38481d426b9f2fd5ab758c4ee53a811dd0a510626a0/jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5", size = 308445, upload-time = "2026-05-19T10:08:01.184Z" }, + { url = "https://files.pythonhosted.org/packages/37/38/ea0e13b18c30ef951da0d47d39e7fa9edb82a93a62990ffbd7cea9b622d4/jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279", size = 336181, upload-time = "2026-05-19T10:08:02.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/fc/2303901b16c4ba05865588990a420c0b4156270b44379c20931544a1d962/jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4", size = 362985, upload-time = "2026-05-19T10:08:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/11bace093c52e7d4d26c8e606ccd7ae8c972189622469ec0d9e28161e28b/jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258", size = 453292, upload-time = "2026-05-19T10:08:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/987f2f086ca4d7a6582eb4ccd513f9b26b42d9e4243a087609a3137a8fc7/jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894", size = 373501, upload-time = "2026-05-19T10:08:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/89fbcabb2739b7a5b8dc959a1b6c5761f6484f5fed3486854b3c789bb1de/jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45", size = 344683, upload-time = "2026-05-19T10:08:09.431Z" }, + { url = "https://files.pythonhosted.org/packages/30/6f/6cca7692e7dddfec6d8d76c54dc97f2af2a41df4ac0674b999df1f09a5f3/jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29", size = 350892, upload-time = "2026-05-19T10:08:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/39/14/0338d6190cb8e6d22e677ab1d4eabd4117f67cca70c54cd04b82ff64e068/jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b", size = 388723, upload-time = "2026-05-19T10:08:12.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/cc19f4a1bdb6afb09ce6a2f2615aa8d44d994eba0d8e6105ed1af920e736/jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7", size = 516648, upload-time = "2026-05-19T10:08:14.808Z" }, + { url = "https://files.pythonhosted.org/packages/49/9f/833c541512cd091b63c10c0381973dfe11bc7a503a818c16384417e0c81e/jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712", size = 547382, upload-time = "2026-05-19T10:08:16.927Z" }, + { url = "https://files.pythonhosted.org/packages/d2/11/e7b70e91f90bc4477e8eee9e8a5f7cf3cb41b4525d6394dc98a714eb8f7f/jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c", size = 205845, upload-time = "2026-05-19T10:08:18.401Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/5c20d9ad6f02c493e4023e5d2d09e1c1f15fe2753c9102c544aff068a88e/jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0", size = 196842, upload-time = "2026-05-19T10:08:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/1eb400ef248e8c925fd883fbe325daf5e42cd1b0d308539dd332bd4f7ffc/jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba", size = 192212, upload-time = "2026-05-19T10:08:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/8a/60/2fd8d7c79da8acf9b7b277c7616847773779356b92acfc9bb158452174da/jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8", size = 315065, upload-time = "2026-05-19T10:08:23.218Z" }, + { url = "https://files.pythonhosted.org/packages/46/f4/008fb7d65e8ac2abf00811651a661e025c4ba80bbc6f378450384ddd3aed/jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c", size = 339444, upload-time = "2026-05-19T10:08:24.701Z" }, + { url = "https://files.pythonhosted.org/packages/00/55/90b0c7b9c6896c0f2a591dd36d36b71d22e09674bfef178fa03ba3f81499/jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4", size = 347779, upload-time = "2026-05-19T10:08:26.408Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/69666cec5000fd57734c118437394516c749ae8dbeea9fb66d6fef9c4775/jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b", size = 200395, upload-time = "2026-05-19T10:08:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/39/04/a6aa62cd27e8149b0d28df5561f10f6cceaf7935a9ccf3f1c5a05f9a0cd8/jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7", size = 190516, upload-time = "2026-05-19T10:08:29.35Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/079f350ebf7859d081de30aa890f9e3be68516f754f3ba32366ffff4dcee/jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49", size = 308884, upload-time = "2026-05-19T10:08:31.667Z" }, + { url = "https://files.pythonhosted.org/packages/04/4e/a2c30a7f69b48c03b20935d647479106fe932f6e63f75faf53937197e05d/jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86", size = 310028, upload-time = "2026-05-19T10:08:33.304Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/2e7cdfd3cf8ca967be38c48f5cf474d79f089efaf559a40f15984a77ae69/jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f", size = 337485, upload-time = "2026-05-19T10:08:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/9b/11/15a1aa28b120b8ee5b4f1fb894c125046225f09847738bd64233d3b84883/jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e", size = 364223, upload-time = "2026-05-19T10:08:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/f442e8af5f3d0dcf47b39e83a0efd9ee45ea946aa6d04625dc3181eae3b6/jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6", size = 456387, upload-time = "2026-05-19T10:08:38.143Z" }, + { url = "https://files.pythonhosted.org/packages/da/f4/37f2d2c9f64f49af7da652ed7532bb5a2372e588e6927c3fdd76f911db65/jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9", size = 374461, upload-time = "2026-05-19T10:08:39.869Z" }, + { url = "https://files.pythonhosted.org/packages/60/28/edcfbbbf0cb15436f36664a8908a0df47ab9006298d4cd937dc08ea932d6/jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c", size = 345924, upload-time = "2026-05-19T10:08:41.668Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/89fba6398dab7f202b7278c4b4aac122399d2c0183971c4a57a3b7088df5/jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd", size = 352283, upload-time = "2026-05-19T10:08:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/1b/da/0f6af8cef2c565a1ab44d970f268c43ccaa72707386ea6388e6fe2b6cd26/jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89", size = 389985, upload-time = "2026-05-19T10:08:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ec/b9cb7d6d29e24ee14910266157d2a279d7a8f60ee0df7fa840882976ba64/jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554", size = 517695, upload-time = "2026-05-19T10:08:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/6d1bda880723aae0ad86b4b763f044362448efe31e3e819635d41cb03451/jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a", size = 548868, upload-time = "2026-05-19T10:08:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/72/7de501cf38dcacaf35098796f3a50e0f2e338baba18a58946c618544b809/jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec", size = 206380, upload-time = "2026-05-19T10:08:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a9/e19addf4b0c1bdce52c6da12351e6bc42c340c45e7c09e2158e46d293ccc/jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558", size = 197687, upload-time = "2026-05-19T10:08:51.088Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c9/776b1db01db25fc6c1d58d1979a37b0a9fe787e5f5b1d062d2eaacb77923/jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866", size = 192571, upload-time = "2026-05-19T10:08:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f6/45bb4670bacf300fd2c7abadbfb3af376e5f1b6ae75fd9bc069891d15870/jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d", size = 317151, upload-time = "2026-05-19T10:08:53.867Z" }, + { url = "https://files.pythonhosted.org/packages/d7/68/ed635ad5acd7b73e454283083bbb7c8205ad10e88b0d9d7d793b09fe8226/jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6", size = 341243, upload-time = "2026-05-19T10:08:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/3ff4176b817b8ea33879e71e13d8bc2b0d481a7ed3fe9e080f333d415c16/jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995", size = 363629, upload-time = "2026-05-19T10:08:56.928Z" }, + { url = "https://files.pythonhosted.org/packages/ab/24/5f8270e0ba9c883582f96f722f8a0b58015c7ce1f8c6d4571cf394e99b6b/jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8", size = 456198, upload-time = "2026-05-19T10:08:58.618Z" }, + { url = "https://files.pythonhosted.org/packages/45/5b/76fc02b0b5c54c3d18c60653156e2f76fde1816f9b4722db68d6ee2c897e/jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5", size = 373710, upload-time = "2026-05-19T10:09:00.151Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/4310821b0ea9277994d3e1f49fc6a4b34e4800caebacb2c0af81da59a454/jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b", size = 349901, upload-time = "2026-05-19T10:09:01.621Z" }, + { url = "https://files.pythonhosted.org/packages/93/fe/67648c35b3594fba8854ac64cc8a826d8bcd18324bbdb53d77697c60b6ef/jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8", size = 352438, upload-time = "2026-05-19T10:09:03.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/0a1879d07ad6b3e025a2750027363452ced93c2d16d1c9d4b153ffd51c91/jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec", size = 388152, upload-time = "2026-05-19T10:09:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/46c6f6b56ba85c90021f4afd72ed42f691f8f84daacb5fe27277070e3858/jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e", size = 517707, upload-time = "2026-05-19T10:09:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cb/720662d4c88fcad606e826fef5424365527ba43ce4868a479aed8f8c507e/jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5", size = 548241, upload-time = "2026-05-19T10:09:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/935b8034fd143f21125c87d51404a9e0e1449186a494405721ff5d1d695e/jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52", size = 207950, upload-time = "2026-05-19T10:09:09.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/59/984fd9ece895953dad3e0880a650e766f5a2da2c5514f0eafdaaabbeb5f9/jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854", size = 200055, upload-time = "2026-05-19T10:09:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/cf8d779feb133a27a2e3bc833bccb9e13aa332cdf820497ebf72c10ce8c3/jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0", size = 191244, upload-time = "2026-05-19T10:09:12.74Z" }, +] + [[package]] name = "logfire-api" version = "4.19.0" @@ -821,6 +903,9 @@ wheels = [ ] [package.optional-dependencies] +anthropic = [ + { name = "anthropic" }, +] google = [ { name = "google-genai" }, ]