From 2a67b3bebcb10d0201b858f17942781251e93080 Mon Sep 17 00:00:00 2001 From: assada Date: Thu, 31 Jul 2025 17:38:33 +0200 Subject: [PATCH 1/2] feat: WIP: implement agent framework abstraction. --- agents/config/demo_agent.json | 13 ++ {app/agent => agents}/google_adk/.gitkeep | 0 agents/langgraph/demo/__init__.py | 3 + .../langgraph/demo/demo_graph.py | 9 +- agents/langgraph/demo/tools/__init__.py | 5 + .../langgraph/demo/tools/tools.py | 0 {app/agent => agents}/llamaindex/.gitkeep | 0 agents/prompt/demo_agent.json | 26 +++ app/__init__.py | 7 - app/agent/config/__init__.py | 8 + app/agent/config/loader.py | 50 +++++ app/agent/config/models.py | 31 ++++ app/agent/factory.py | 49 +++++ app/agent/frameworks/__init__.py | 0 app/agent/frameworks/base.py | 48 +++++ app/agent/frameworks/google_adk.py | 73 ++++++++ app/agent/frameworks/langgraph.py | 174 ++++++++++++++++++ .../langgraph_framework}/__init__.py | 0 .../langgraph_framework}/base_state.py | 0 .../checkpoint/__init__.py | 0 .../langgraph_framework}/checkpoint/base.py | 0 .../checkpoint/factory.py | 6 +- .../langgraph_framework}/checkpoint/memory.py | 2 +- .../checkpoint/postgres.py | 2 +- .../langgraph_framework}/graph.py | 3 +- .../langgraph_framework}/utils/__init__.py | 0 .../langgraph_framework}/utils/utils.py | 0 app/agent/frameworks/llamaindex.py | 72 ++++++++ app/agent/langgraph/demo/__init__.py | 3 - app/agent/prompt.py | 25 ++- app/agent/services/agent_service.py | 99 +--------- app/agent/services/stream_processor.py | 10 +- app/bootstrap/__init__.py | 3 +- app/bootstrap/config.py | 7 + app/http/controllers/thread_controller.py | 52 +++--- app/models/thread.py | 5 + app/repositories/thread_repository.py | 1 + 37 files changed, 632 insertions(+), 154 deletions(-) create mode 100644 agents/config/demo_agent.json rename {app/agent => agents}/google_adk/.gitkeep (100%) create mode 100644 agents/langgraph/demo/__init__.py rename {app/agent => agents}/langgraph/demo/demo_graph.py (89%) create mode 100644 agents/langgraph/demo/tools/__init__.py rename {app/agent => agents}/langgraph/demo/tools/tools.py (100%) rename {app/agent => agents}/llamaindex/.gitkeep (100%) create mode 100644 agents/prompt/demo_agent.json create mode 100644 app/agent/config/__init__.py create mode 100644 app/agent/config/loader.py create mode 100644 app/agent/config/models.py create mode 100644 app/agent/factory.py create mode 100644 app/agent/frameworks/__init__.py create mode 100644 app/agent/frameworks/base.py create mode 100644 app/agent/frameworks/google_adk.py create mode 100644 app/agent/frameworks/langgraph.py rename app/agent/{langgraph => frameworks/langgraph_framework}/__init__.py (100%) rename app/agent/{langgraph => frameworks/langgraph_framework}/base_state.py (100%) rename app/agent/{langgraph => frameworks/langgraph_framework}/checkpoint/__init__.py (100%) rename app/agent/{langgraph => frameworks/langgraph_framework}/checkpoint/base.py (100%) rename app/agent/{langgraph => frameworks/langgraph_framework}/checkpoint/factory.py (81%) rename app/agent/{langgraph => frameworks/langgraph_framework}/checkpoint/memory.py (91%) rename app/agent/{langgraph => frameworks/langgraph_framework}/checkpoint/postgres.py (94%) rename app/agent/{langgraph => frameworks/langgraph_framework}/graph.py (98%) rename app/agent/{langgraph => frameworks/langgraph_framework}/utils/__init__.py (100%) rename app/agent/{langgraph => frameworks/langgraph_framework}/utils/utils.py (100%) create mode 100644 app/agent/frameworks/llamaindex.py delete mode 100644 app/agent/langgraph/demo/__init__.py diff --git a/agents/config/demo_agent.json b/agents/config/demo_agent.json new file mode 100644 index 0000000..8af2b0a --- /dev/null +++ b/agents/config/demo_agent.json @@ -0,0 +1,13 @@ +{ + "name": "demo_agent", + "description": "Demo conversational agent with tool support", + "production": { + "framework": "langgraph", + "graph_class": "agents.langgraph.demo.demo_graph.DemoGraph", + "prompt_source": "file", + "custom_settings": { + "default_system_prompt": "You are a helpful AI assistant with access to tools.", + "max_iterations": 10 + } + } +} \ No newline at end of file diff --git a/app/agent/google_adk/.gitkeep b/agents/google_adk/.gitkeep similarity index 100% rename from app/agent/google_adk/.gitkeep rename to agents/google_adk/.gitkeep diff --git a/agents/langgraph/demo/__init__.py b/agents/langgraph/demo/__init__.py new file mode 100644 index 0000000..c094bb1 --- /dev/null +++ b/agents/langgraph/demo/__init__.py @@ -0,0 +1,3 @@ +from .demo_graph import DemoGraph + +__all__ = ["DemoGraph"] diff --git a/app/agent/langgraph/demo/demo_graph.py b/agents/langgraph/demo/demo_graph.py similarity index 89% rename from app/agent/langgraph/demo/demo_graph.py rename to agents/langgraph/demo/demo_graph.py index acc6be8..440b573 100644 --- a/app/agent/langgraph/demo/demo_graph.py +++ b/agents/langgraph/demo/demo_graph.py @@ -7,11 +7,12 @@ from langgraph.graph.state import CompiledStateGraph from langgraph.prebuilt import ToolNode -from app.agent.langgraph import Graph -from app.agent.langgraph.base_state import BaseState, State -from app.agent.langgraph.demo.tools.tools import TOOLS +from app.agent.frameworks.langgraph_framework import Graph +from app.agent.frameworks.langgraph_framework.base_state import BaseState, State from app.agent.prompt import PromptProvider +from .tools import TOOLS + logger = logging.getLogger(__name__) @@ -23,7 +24,7 @@ def __init__( @property def graph_name(self) -> str: - return "demo_graph" + return "demo_agent" def get_tools(self) -> list[Any]: return TOOLS diff --git a/agents/langgraph/demo/tools/__init__.py b/agents/langgraph/demo/tools/__init__.py new file mode 100644 index 0000000..df84792 --- /dev/null +++ b/agents/langgraph/demo/tools/__init__.py @@ -0,0 +1,5 @@ +from .tools import TOOLS + +__all__ = [ + "TOOLS", +] \ No newline at end of file diff --git a/app/agent/langgraph/demo/tools/tools.py b/agents/langgraph/demo/tools/tools.py similarity index 100% rename from app/agent/langgraph/demo/tools/tools.py rename to agents/langgraph/demo/tools/tools.py diff --git a/app/agent/llamaindex/.gitkeep b/agents/llamaindex/.gitkeep similarity index 100% rename from app/agent/llamaindex/.gitkeep rename to agents/llamaindex/.gitkeep diff --git a/agents/prompt/demo_agent.json b/agents/prompt/demo_agent.json new file mode 100644 index 0000000..f66ff86 --- /dev/null +++ b/agents/prompt/demo_agent.json @@ -0,0 +1,26 @@ +{ + "production": { + "content": "You are a helpful AI assistant with access to tools. Use the tools when needed to provide accurate and helpful responses to user questions.", + "config": { + "model": "openai/gpt-4o-mini", + "temperature": 0.7, + "max_tokens": 4096 + } + }, + "development": { + "content": "You are a development AI assistant. You have access to tools and should use them when helpful. Keep responses concise for testing purposes.", + "config": { + "model": "openai/gpt-3.5-turbo", + "temperature": 0.5, + "max_tokens": 2048 + } + }, + "staging": { + "content": "You are a staging AI assistant with tool access. Provide thorough responses while testing new features.", + "config": { + "model": "openai/gpt-4o-mini", + "temperature": 0.6, + "max_tokens": 3072 + } + } +} \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 91c49f5..e69de29 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +0,0 @@ -from .bootstrap import AppConfig, create_app, get_config - -__all__ = [ - "create_app", - "get_config", - "AppConfig", -] diff --git a/app/agent/config/__init__.py b/app/agent/config/__init__.py new file mode 100644 index 0000000..281bf78 --- /dev/null +++ b/app/agent/config/__init__.py @@ -0,0 +1,8 @@ +from .loader import AgentConfigLoader +from .models import AgentConfig, EnvironmentConfig + +__all__ = [ + "AgentConfigLoader", + "AgentConfig", + "EnvironmentConfig" +] \ No newline at end of file diff --git a/app/agent/config/loader.py b/app/agent/config/loader.py new file mode 100644 index 0000000..bf5ed0b --- /dev/null +++ b/app/agent/config/loader.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import json +import logging +from pathlib import Path +from typing import Any + +from app.agent.config.models import AgentConfig + +logger = logging.getLogger(__name__) + + +class AgentConfigLoader: + def __init__(self, config_directory: str = "agents/config"): + self.config_directory = Path(config_directory) + self._configs: dict[str, AgentConfig] = {} + self._load_all_configs() + + def _load_all_configs(self) -> None: + if not self.config_directory.exists(): + logger.warning(f"Agent config directory {self.config_directory} does not exist") + return + + for config_file in self.config_directory.glob("*.json"): + try: + agent_name = config_file.stem + with open(config_file, encoding='utf-8') as f: + config_data = json.load(f) + + self._configs[agent_name] = AgentConfig(**config_data) + logger.debug(f"Loaded agent config: {agent_name}") + + except Exception as e: + logger.error(f"Failed to load agent config {config_file}: {e}") + + def get_agent_config(self, agent_name: str) -> AgentConfig | None: + return self._configs.get(agent_name) + + def list_available_agents(self) -> list[str]: + return list(self._configs.keys()) + + def get_config_for_environment( + self, agent_name: str, environment: str = "production" + ) -> dict[str, Any] | None: + agent_config = self.get_agent_config(agent_name) + if not agent_config: + return None + + env_config = agent_config.get_config_for_environment(environment) + return env_config.model_dump() diff --git a/app/agent/config/models.py b/app/agent/config/models.py new file mode 100644 index 0000000..2a5d98d --- /dev/null +++ b/app/agent/config/models.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +import logging +from typing import Any + +from pydantic import BaseModel + +logger = logging.getLogger(__name__) + +class EnvironmentConfig(BaseModel): + framework: str # Options: "langgraph", "llamaindex", "google_adk" + graph_class: str | None = None + prompt_source: str = "file" # Options: "langfuse", "file" + custom_settings: dict[str, Any] | None = None + + +class AgentConfig(BaseModel): + name: str + description: str | None = None + production: EnvironmentConfig + development: EnvironmentConfig | None = None + + def get_config_for_environment(self, environment: str) -> EnvironmentConfig: + logger.warning(f"get_config_for_environment: {environment}") + if environment == "production": + return self.production + elif environment == "development" and self.development: + return self.development + else: + # Fallback to production if environment not found + return self.production \ No newline at end of file diff --git a/app/agent/factory.py b/app/agent/factory.py new file mode 100644 index 0000000..ecf1961 --- /dev/null +++ b/app/agent/factory.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import logging + +from app.agent.config import AgentConfigLoader +from app.agent.frameworks.base import AgentFramework, AgentInstance +from app.agent.frameworks.google_adk import GoogleADKFramework +from app.agent.frameworks.langgraph import LangGraphFramework +from app.agent.frameworks.llamaindex import LlamaIndexFramework +from app.bootstrap.config import AppConfig + +logger = logging.getLogger(__name__) + + +class AgentFactory: + def __init__(self, global_config: AppConfig): + self.global_config = global_config + self.config_loader = AgentConfigLoader(global_config.agent_config_dir) + logger.info(f"Loading agent configuration from {self.config_loader.config_directory}") + self._frameworks: dict[str, AgentFramework] = {} + self._register_built_in_frameworks() + + def _register_built_in_frameworks(self) -> None: + self.register_framework(LangGraphFramework()) + self.register_framework(LlamaIndexFramework()) + self.register_framework(GoogleADKFramework()) + + def register_framework(self, framework: AgentFramework) -> None: + self._frameworks[framework.framework_name] = framework + logger.info(f"Registered framework: {framework.framework_name}") + + async def create_agent( + self, + agent_id: str, + environment: str | None = None, + ) -> AgentInstance: + agent_config = self.config_loader.get_agent_config(agent_id) + if not agent_config: + raise ValueError(f"Agent configuration not found: {agent_id}") + + env_config = agent_config.get_config_for_environment(environment) + framework_name = env_config.framework + + framework = self._frameworks.get(framework_name) + if not framework: + raise ValueError(f"Framework not supported: {framework_name}") + + logger.info(f"Creating agent '{agent_id}' with framework '{framework_name}' for environment '{environment}'") + return await framework.create_agent(agent_id, env_config, self.global_config) diff --git a/app/agent/frameworks/__init__.py b/app/agent/frameworks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/agent/frameworks/base.py b/app/agent/frameworks/base.py new file mode 100644 index 0000000..60220f0 --- /dev/null +++ b/app/agent/frameworks/base.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import logging +from abc import ABC, abstractmethod +from collections.abc import AsyncGenerator +from typing import Any + +from app.agent.config import EnvironmentConfig +from app.bootstrap.config import AppConfig +from app.models import Thread, User + +logger = logging.getLogger(__name__) + + +class AgentInstance(ABC): + def __init__(self, agent_id: str, config: EnvironmentConfig): + self.agent_id = agent_id + self.config = config + + @abstractmethod + async def stream_response( + self, message: str, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + pass + + @abstractmethod + async def load_history( + self, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + pass + + +class AgentFramework(ABC): + @property + @abstractmethod + def framework_name(self) -> str: + """Name of the framework (e.g., 'langgraph', 'llamaindex', 'google_adk').""" + pass + + @abstractmethod + async def create_agent( + self, + agent_id: str, + agent_config: EnvironmentConfig, + global_config: AppConfig + ) -> AgentInstance: + """Create an agent instance for this framework.""" + pass diff --git a/app/agent/frameworks/google_adk.py b/app/agent/frameworks/google_adk.py new file mode 100644 index 0000000..384cff5 --- /dev/null +++ b/app/agent/frameworks/google_adk.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import json +import logging +from collections.abc import AsyncGenerator +from typing import Any + +from app.agent.config import AgentConfig, EnvironmentConfig +from app.agent.frameworks.base import AgentFramework, AgentInstance +from app.bootstrap.config import AppConfig +from app.models import Thread, User + +logger = logging.getLogger(__name__) + + +class GoogleADKAgentInstance(AgentInstance): + def __init__(self, agent_id: str, config: EnvironmentConfig): + super().__init__(agent_id, config) + # TODO: Initialize Google ADK components here + logger.info(f"Created Google ADK agent instance: {agent_id}") + + async def stream_response( + self, message: str, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + # TODO: Implement Google ADK streaming logic + logger.info(f"Google ADK agent {self.agent_id} processing message: {message[:50]}...") + + # Placeholder implementation + yield { + "event": "message", + "data": json.dumps({ + "type": "ai_message", + "content": f"Google ADK agent '{self.agent_id}' received: {message}", + "metadata": {"agent": self.agent_id, "framework": "google_adk"} + }) + } + + yield { + "event": "end", + "data": json.dumps({"status": "completed"}) + } + + async def load_history( + self, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + # TODO: Implement Google ADK history loading + logger.info(f"Loading history for thread {thread.id} with Google ADK agent {self.agent_id}") + + # Placeholder implementation + yield { + "event": "end", + "data": json.dumps({"status": "completed", "message": "No history available"}) + } + + +class GoogleADKFramework(AgentFramework): + @property + def framework_name(self) -> str: + return "google_adk" + + async def create_agent( + self, + agent_id: str, + agent_config: EnvironmentConfig, + global_config: AppConfig + ) -> AgentInstance: + # TODO: Initialize Google ADK components based on config + logger.info(f"Creating Google ADK agent: {agent_id}") + + return GoogleADKAgentInstance( + agent_id=agent_id, + config=agent_config + ) diff --git a/app/agent/frameworks/langgraph.py b/app/agent/frameworks/langgraph.py new file mode 100644 index 0000000..38f7972 --- /dev/null +++ b/app/agent/frameworks/langgraph.py @@ -0,0 +1,174 @@ +from __future__ import annotations + +import importlib +import json +import logging +from collections.abc import AsyncGenerator +from datetime import UTC, datetime +from typing import Any +from uuid import uuid4 + +from langchain_core.messages import HumanMessage +from langchain_core.runnables import RunnableConfig +from langfuse import Langfuse # type: ignore[attr-defined] +from langfuse.langchain import CallbackHandler +from langgraph.graph.state import CompiledStateGraph + +from app.agent.config import EnvironmentConfig +from app.agent.frameworks.base import AgentFramework, AgentInstance +from app.agent.frameworks.langgraph_framework.checkpoint.factory import ( + CheckpointerFactory, +) +from app.agent.frameworks.langgraph_framework.utils import to_chat_message +from app.agent.prompt import create_prompt_provider +from app.agent.services.events import EndEvent, ErrorEvent +from app.agent.services.events.base_event import BaseEvent +from app.agent.services.stream_processor import StreamProcessor +from app.bootstrap.config import AppConfig +from app.models import Thread, User +from app.models.thread import ThreadStatus + +logger = logging.getLogger(__name__) + + +class LangGraphAgentInstance(AgentInstance): + def __init__( + self, + agent_id: str, + config: EnvironmentConfig, + graph: CompiledStateGraph[Any, Any, Any], + langfuse: Langfuse + ): + super().__init__(agent_id, config) + self.graph = graph + self.langfuse = langfuse + self.stream_processor = StreamProcessor() + + async def stream_response( + self, message: str, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + with self.langfuse.start_as_current_span( + name=self.graph.name, input=message + ) as span: + run_id = uuid4() + + thread.status = ThreadStatus.busy + thread.updated_at = datetime.now(UTC) + + inputs = { + "messages": [HumanMessage(content=message)], + } + + config = RunnableConfig( + configurable={ + "thread_id": thread.id, + "user_id": user.id, + }, + metadata={ + "langfuse_session_id": str(thread.id), + "langfuse_user_id": str(user.id), + "langfuse_tags": ["production", "chat-bot"], + "trace_id": span.trace_id, + }, + run_id=run_id, + callbacks=[CallbackHandler()], + ) + + try: + stream = self.graph.astream( + inputs, stream_mode=["updates", "messages", "custom"], config=config + ) + async for event in self.stream_processor.process_stream( + stream, run_id, span # type: ignore[arg-type] + ): + thread.status = ThreadStatus.idle + yield event.model_dump() + except Exception as e: + thread.status = ThreadStatus.error + yield ErrorEvent( + data=json.dumps({"run_id": str(run_id), "content": str(e)}) + ).model_dump() + + async def load_history( + self, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + try: + state_snapshot = await self.graph.aget_state( + config=RunnableConfig( + configurable={"thread_id": thread.id, "user_id": user.id} + ), + ) + + trace_by_id = { + m["id"]: m["trace_id"] + for m in state_snapshot.values.get("message_trace_map", []) + } + + messages = state_snapshot.values.get("messages", []) + if not messages: + yield EndEvent(data=json.dumps({"status": "completed"})).model_dump() + return + + for m in messages: + chat_msg = to_chat_message(m, trace_id=trace_by_id.get(m.id)) + + event = BaseEvent.from_payload( + event=chat_msg.type, payload=chat_msg.model_dump(), source="history" + ) + + yield event.model_dump() + + yield EndEvent(data=json.dumps({"status": "completed"})).model_dump() + + except Exception as e: + logger.error(f"Error loading history: {e}") + yield ErrorEvent( + data=json.dumps({"content": f"Error loading history: {str(e)}"}) + ).model_dump() + + +class LangGraphFramework(AgentFramework): + @property + def framework_name(self) -> str: + return "langgraph" + + def _load_graph_class(self, graph_class_name: str): + module_name, class_name = graph_class_name.rsplit('.', 1) + + try: + module = importlib.import_module(module_name) + return getattr(module, class_name) + except ImportError as e: + raise ImportError(f"Could not import module {module_name}: {e}") + except AttributeError as e: + raise AttributeError(f"Class {class_name} not found in module {module_name}: {e}") + + async def create_agent( + self, + agent_id: str, + agent_config: EnvironmentConfig, + global_config: AppConfig + ) -> AgentInstance: + langfuse = Langfuse(debug=False) ## TODO: can be configured with agent_config + + checkpointer_provider = await CheckpointerFactory.create(global_config) ## TODO: Agent config + checkpointer = await checkpointer_provider.get_checkpointer() + logger.warning(agent_config.prompt_source) + prompt_provider = create_prompt_provider( + prompt_source=agent_config.prompt_source, + langfuse_client=langfuse if agent_config.prompt_source == "langfuse" else None, + prompt_dir=global_config.prompt_dir + ) + + graph_class_name = agent_config.graph_class + graph_class = self._load_graph_class(graph_class_name) + + graph_instance = graph_class(checkpointer, prompt_provider, agent_config.custom_settings) + compiled_graph = graph_instance.build_graph() + + return LangGraphAgentInstance( + agent_id=agent_id, + config=agent_config, + graph=compiled_graph, + langfuse=langfuse + ) \ No newline at end of file diff --git a/app/agent/langgraph/__init__.py b/app/agent/frameworks/langgraph_framework/__init__.py similarity index 100% rename from app/agent/langgraph/__init__.py rename to app/agent/frameworks/langgraph_framework/__init__.py diff --git a/app/agent/langgraph/base_state.py b/app/agent/frameworks/langgraph_framework/base_state.py similarity index 100% rename from app/agent/langgraph/base_state.py rename to app/agent/frameworks/langgraph_framework/base_state.py diff --git a/app/agent/langgraph/checkpoint/__init__.py b/app/agent/frameworks/langgraph_framework/checkpoint/__init__.py similarity index 100% rename from app/agent/langgraph/checkpoint/__init__.py rename to app/agent/frameworks/langgraph_framework/checkpoint/__init__.py diff --git a/app/agent/langgraph/checkpoint/base.py b/app/agent/frameworks/langgraph_framework/checkpoint/base.py similarity index 100% rename from app/agent/langgraph/checkpoint/base.py rename to app/agent/frameworks/langgraph_framework/checkpoint/base.py diff --git a/app/agent/langgraph/checkpoint/factory.py b/app/agent/frameworks/langgraph_framework/checkpoint/factory.py similarity index 81% rename from app/agent/langgraph/checkpoint/factory.py rename to app/agent/frameworks/langgraph_framework/checkpoint/factory.py index 69bb441..71f3402 100644 --- a/app/agent/langgraph/checkpoint/factory.py +++ b/app/agent/frameworks/langgraph_framework/checkpoint/factory.py @@ -1,8 +1,8 @@ import logging -from app.agent.langgraph.checkpoint.base import BaseCheckpointer -from app.agent.langgraph.checkpoint.memory import MemoryCheckpointer -from app.agent.langgraph.checkpoint.postgres import PostgresCheckpointer +from .base import BaseCheckpointer +from .memory import MemoryCheckpointer +from .postgres import PostgresCheckpointer from app.bootstrap.config import AppConfig from app.infrastructure.database.connection import DatabaseConnectionFactory diff --git a/app/agent/langgraph/checkpoint/memory.py b/app/agent/frameworks/langgraph_framework/checkpoint/memory.py similarity index 91% rename from app/agent/langgraph/checkpoint/memory.py rename to app/agent/frameworks/langgraph_framework/checkpoint/memory.py index 4f7cff6..e484e22 100644 --- a/app/agent/langgraph/checkpoint/memory.py +++ b/app/agent/frameworks/langgraph_framework/checkpoint/memory.py @@ -3,7 +3,7 @@ from langgraph.checkpoint.memory import InMemorySaver -from app.agent.langgraph.checkpoint.base import BaseCheckpointer +from app.agent.frameworks.langgraph_framework.checkpoint.base import BaseCheckpointer logger = logging.getLogger(__name__) diff --git a/app/agent/langgraph/checkpoint/postgres.py b/app/agent/frameworks/langgraph_framework/checkpoint/postgres.py similarity index 94% rename from app/agent/langgraph/checkpoint/postgres.py rename to app/agent/frameworks/langgraph_framework/checkpoint/postgres.py index 5663f9a..9577802 100644 --- a/app/agent/langgraph/checkpoint/postgres.py +++ b/app/agent/frameworks/langgraph_framework/checkpoint/postgres.py @@ -3,7 +3,7 @@ from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver -from app.agent.langgraph.checkpoint.base import BaseCheckpointer +from app.agent.frameworks.langgraph_framework.checkpoint.base import BaseCheckpointer from app.infrastructure.database.connection import DatabaseConnection logger = logging.getLogger(__name__) diff --git a/app/agent/langgraph/graph.py b/app/agent/frameworks/langgraph_framework/graph.py similarity index 98% rename from app/agent/langgraph/graph.py rename to app/agent/frameworks/langgraph_framework/graph.py index 712e22b..cb4addc 100644 --- a/app/agent/langgraph/graph.py +++ b/app/agent/frameworks/langgraph_framework/graph.py @@ -13,7 +13,7 @@ from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.graph.state import CompiledStateGraph -from app.agent.langgraph.base_state import BaseState, State +from .base_state import BaseState, State from app.agent.prompt import Prompt, PromptProvider logger = logging.getLogger(__name__) @@ -29,6 +29,7 @@ def __init__( self, checkpointer: BaseCheckpointSaver[Any], prompt_provider: PromptProvider, + custom_settings: dict[str, Any] | None = None, ): self._checkpointer = checkpointer self._prompt_provider = prompt_provider diff --git a/app/agent/langgraph/utils/__init__.py b/app/agent/frameworks/langgraph_framework/utils/__init__.py similarity index 100% rename from app/agent/langgraph/utils/__init__.py rename to app/agent/frameworks/langgraph_framework/utils/__init__.py diff --git a/app/agent/langgraph/utils/utils.py b/app/agent/frameworks/langgraph_framework/utils/utils.py similarity index 100% rename from app/agent/langgraph/utils/utils.py rename to app/agent/frameworks/langgraph_framework/utils/utils.py diff --git a/app/agent/frameworks/llamaindex.py b/app/agent/frameworks/llamaindex.py new file mode 100644 index 0000000..d47b556 --- /dev/null +++ b/app/agent/frameworks/llamaindex.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +import json +import logging +from collections.abc import AsyncGenerator +from typing import Any + +from app.agent.config import EnvironmentConfig +from app.agent.frameworks.base import AgentFramework, AgentInstance +from app.bootstrap.config import AppConfig +from app.models import Thread, User + +logger = logging.getLogger(__name__) + + +class LlamaIndexAgentInstance(AgentInstance): + def __init__(self, agent_id: str, config: EnvironmentConfig): + super().__init__(agent_id, config) + # TODO: Initialize LlamaIndex components here + logger.debug(f"Created LlamaIndex agent instance: {agent_id}") + + async def stream_response( + self, message: str, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + # TODO: Implement LlamaIndex streaming logic + logger.debug(f"LlamaIndex agent {self.agent_id} processing message: {message[:50]}...") + + yield { + "event": "message", + "data": json.dumps({ + "type": "ai_message", + "content": f"LlamaIndex agent '{self.agent_id}' received: {message}", + "metadata": {"agent": self.agent_id, "framework": "llamaindex"} + }) + } + + yield { + "event": "end", + "data": json.dumps({"status": "completed"}) + } + + async def load_history( + self, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + # TODO: Implement LlamaIndex history loading + logger.info(f"Loading history for thread {thread.id} with LlamaIndex agent {self.agent_id}") + + # Placeholder implementation + yield { + "event": "end", + "data": json.dumps({"status": "completed", "message": "No history available"}) + } + + +class LlamaIndexFramework(AgentFramework): + @property + def framework_name(self) -> str: + return "llamaindex" + + async def create_agent( + self, + agent_id: str, + agent_config: EnvironmentConfig, + global_config: AppConfig + ) -> AgentInstance: + # TODO: Initialize LlamaIndex components based on config + logger.debug(f"Creating LlamaIndex agent: {agent_id}") + + return LlamaIndexAgentInstance( + agent_id=agent_id, + config=agent_config + ) diff --git a/app/agent/langgraph/demo/__init__.py b/app/agent/langgraph/demo/__init__.py deleted file mode 100644 index 39b6e10..0000000 --- a/app/agent/langgraph/demo/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from app.agent.langgraph.demo.demo_graph import DemoGraph - -__all__ = ["DemoGraph"] diff --git a/app/agent/prompt.py b/app/agent/prompt.py index 5926d85..23150ad 100644 --- a/app/agent/prompt.py +++ b/app/agent/prompt.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import logging from abc import ABC, abstractmethod from enum import Enum from pathlib import Path @@ -13,6 +14,7 @@ except ImportError: _LF = None # type: ignore[misc, assignment] +logger = logging.getLogger(__name__) class PromptSource(str, Enum): LANGFUSE = "langfuse" @@ -41,7 +43,7 @@ def __init__(self, client: object): def get_prompt(self, prompt_name: str, label: str, fallback: Prompt) -> Prompt: langfuse_prompt = self._client.get_prompt( - name=prompt_name, label=label, fallback=fallback.content + name=prompt_name, label=label, fallback=fallback.content ## TODO: fallback not working as expected ) return Prompt( @@ -67,8 +69,9 @@ class JsonFilePromptProvider(PromptProvider): If *label* is not found, *fallback* is returned. """ - def __init__(self, root_dir: str | Path = "prompts") -> None: + def __init__(self, root_dir: str | Path = "agents/prompt") -> None: self._root_dir = Path(root_dir) + self._root_dir.mkdir(parents=True, exist_ok=True) def _path_for(self, prompt_name: str) -> Path: return self._root_dir / f"{prompt_name}.json" @@ -79,15 +82,29 @@ def get_prompt(self, prompt_name: str, label: str, fallback: Prompt) -> Prompt: with path.open("r", encoding="utf-8") as f: data = json.load(f) except FileNotFoundError: + logger.error(f"File for {prompt_name} ({path}) not found.") return fallback.model_copy() except Exception as exc: # pragma: no cover raise RuntimeError(f"Error reading prompt file '{path}': {exc}") from exc raw_entry = data.get(label) if raw_entry is None: + logger.warning(f"Prompt '{prompt_name}' with label '{label}' not found in {path}. Using fallback.") return fallback.model_copy() - content = raw_entry.get("prompt", fallback.content) + content = raw_entry.get("content", raw_entry.get("prompt", fallback.content)) config = raw_entry.get("config", fallback.config) - return Prompt(source=PromptSource.FILE, content=content, config=config) + logger.debug(f"Loaded prompt '{prompt_name}' with label '{label}' from {path}") + + return Prompt(source=PromptSource.FILE, content=content, config=config, metadata={"file_path": str(path), "label": label}) + +def create_prompt_provider(prompt_source: str, langfuse_client: object | None = None, prompt_dir: str | Path = "agents/prompt") -> PromptProvider: + if prompt_source.lower() == "langfuse": + if langfuse_client is None or _LF is None or not isinstance(langfuse_client, _LF): + raise ValueError("Langfuse client is required for langfuse prompt type") + return LangfusePromptProvider(langfuse_client) + elif prompt_source.lower() == "file": + return JsonFilePromptProvider(prompt_dir) + else: + raise ValueError(f"Unknown prompt source: {prompt_source}. Supported types: 'langfuse', 'file'") diff --git a/app/agent/services/agent_service.py b/app/agent/services/agent_service.py index aa6e822..37720d3 100644 --- a/app/agent/services/agent_service.py +++ b/app/agent/services/agent_service.py @@ -1,113 +1,16 @@ -import json import logging -from collections.abc import AsyncGenerator from datetime import UTC, datetime -from typing import Any -from uuid import uuid4 -from langchain_core.messages import HumanMessage -from langchain_core.runnables import RunnableConfig from langfuse import Langfuse # type: ignore[attr-defined] -from langfuse.langchain import CallbackHandler -from langgraph.graph.state import CompiledStateGraph -from app.agent.services.events import EndEvent, ErrorEvent -from app.agent.services.events.base_event import BaseEvent -from app.agent.services.stream_processor import StreamProcessor -from app.agent.langgraph.utils import to_chat_message from app.models import Thread, User -from app.models.thread import ThreadStatus logger = logging.getLogger(__name__) class AgentService: - def __init__(self, agent: CompiledStateGraph[Any, Any, Any], langfuse: Langfuse): + def __init__(self, langfuse: Langfuse): self.langfuse = langfuse - self.agent = agent - self.stream_processor = StreamProcessor() - - async def stream_response( - self, message: str, thread: Thread, user: User - ) -> AsyncGenerator[dict[str, Any]]: - with self.langfuse.start_as_current_span( - name=self.agent.name, input=message - ) as span: - run_id = uuid4() - - thread.status = ThreadStatus.busy - thread.updated_at = datetime.now(UTC) - - inputs = { - "messages": [HumanMessage(content=message)], - } - - config = RunnableConfig( - configurable={ - "thread_id": thread.id, - "user_id": user.id, - }, - metadata={ - "langfuse_session_id": str(thread.id), - "langfuse_user_id": str(user.id), - "langfuse_tags": ["production", "chat-bot"], - "trace_id": span.trace_id, - }, - run_id=run_id, - callbacks=[CallbackHandler()], - ) - - try: - stream = self.agent.astream( - inputs, stream_mode=["updates", "messages", "custom"], config=config - ) - async for event in self.stream_processor.process_stream( - stream, run_id, span # type: ignore[arg-type] - ): - thread.status = ThreadStatus.idle - yield event.model_dump() - except Exception as e: - thread.status = ThreadStatus.error - yield ErrorEvent( - data=json.dumps({"run_id": str(run_id), "content": str(e)}) - ).model_dump() - - async def load_history( - self, thread: Thread, user: User - ) -> AsyncGenerator[dict[str, Any]]: - try: - state_snapshot = await self.agent.aget_state( - config=RunnableConfig( - configurable={"thread_id": thread.id, "user_id": user.id} - ), - ) - - trace_by_id = { - m["id"]: m["trace_id"] - for m in state_snapshot.values.get("message_trace_map", []) - } - - messages = state_snapshot.values.get("messages", []) - if not messages: - yield EndEvent(data=json.dumps({"status": "completed"})).model_dump() - return - - for m in messages: - chat_msg = to_chat_message(m, trace_id=trace_by_id.get(m.id)) - - event = BaseEvent.from_payload( - event=chat_msg.type, payload=chat_msg.model_dump(), source="history" - ) - - yield event.model_dump() - - yield EndEvent(data=json.dumps({"status": "completed"})).model_dump() - - except Exception as e: - logger.error(f"Error loading history: {e}") - yield ErrorEvent( - data=json.dumps({"content": f"Error loading history: {str(e)}"}) - ).model_dump() async def add_feedback( self, trace: str, feedback: float, thread: Thread, user: User diff --git a/app/agent/services/stream_processor.py b/app/agent/services/stream_processor.py index d533220..8cbf349 100644 --- a/app/agent/services/stream_processor.py +++ b/app/agent/services/stream_processor.py @@ -11,15 +11,15 @@ from langchain_core.messages import AIMessage, AIMessageChunk from langfuse._client.span import LangfuseSpan -from app.agent.models import AIMessage as CustomAIMessage -from app.agent.models import HumanMessage, Token -from app.agent.services.events import EndEvent, ErrorEvent, TokenEvent -from app.agent.services.events.base_event import BaseEvent -from app.agent.langgraph.utils import ( +from app.agent.frameworks.langgraph_framework.utils import ( concat_text, strip_tool_calls, to_chat_message, ) +from app.agent.models import AIMessage as CustomAIMessage +from app.agent.models import HumanMessage, Token +from app.agent.services.events import EndEvent, ErrorEvent, TokenEvent +from app.agent.services.events.base_event import BaseEvent logger = logging.getLogger(__name__) diff --git a/app/bootstrap/__init__.py b/app/bootstrap/__init__.py index 121c5cb..d07b2cb 100644 --- a/app/bootstrap/__init__.py +++ b/app/bootstrap/__init__.py @@ -1,8 +1,7 @@ from .app_factory import create_app -from .config import AppConfig, get_config +from .config import get_config __all__ = [ "create_app", "get_config", - "AppConfig", ] diff --git a/app/bootstrap/config.py b/app/bootstrap/config.py index 538e1eb..5ea134d 100644 --- a/app/bootstrap/config.py +++ b/app/bootstrap/config.py @@ -21,6 +21,10 @@ class AppConfig(BaseModel): cors_allow_methods: list[str] = ["*"] cors_allow_headers: list[str] = ["*"] + environment: str = "production" + agent_config_dir: str = "agents/config" + prompt_dir: str = "agents/prompt" + database_url: str | None = None checkpoint_type: str = "memory" # Options: memory, postgres @@ -43,4 +47,7 @@ def get_config() -> AppConfig: "postgresql://postgres:postgres@localhost:5432/agent_template", ), checkpoint_type=os.getenv("CHECKPOINT_TYPE", "memory"), + environment=os.getenv("ENVIRONMENT", "production"), + agent_config_dir=os.getenv("AGENT_CONFIG_DIR", "agents/config"), + prompt_dir=os.getenv("AGENT_PROMPT_DIR", "agents/prompt"), ) diff --git a/app/http/controllers/thread_controller.py b/app/http/controllers/thread_controller.py index 83cd06e..01e2edf 100644 --- a/app/http/controllers/thread_controller.py +++ b/app/http/controllers/thread_controller.py @@ -4,12 +4,10 @@ from fastapi import Depends, HTTPException from langfuse import Langfuse # type: ignore[attr-defined] -from langgraph.graph.state import CompiledStateGraph -from sse_starlette.sse import EventSourceResponse +from sse_starlette.sse import EventSourceResponse # type: ignore[attr-defined] -from app.agent.langgraph.checkpoint.factory import CheckpointerFactory -from app.agent.langgraph.demo.demo_graph import DemoGraph -from app.agent.prompt import LangfusePromptProvider +from app.agent.factory import AgentFactory +from app.agent.frameworks.base import AgentInstance from app.agent.services import AgentService from app.bootstrap.config import AppConfig from app.http.requests import FeedbackRequest @@ -22,24 +20,24 @@ class ThreadController: def __init__(self, config: AppConfig): self.config = config - self._checkpointer_provider: CheckpointerFactory | None = None - self._graph: CompiledStateGraph[Any, Any, Any] | None = None - self._agent_service: AgentService | None = None - self._langfuse: Langfuse | None = None + self.agent_factory = AgentFactory(config) + self._agent_instances: dict[str, AgentInstance] = {} + self._agent_service = AgentService(Langfuse(debug=False)) - async def _initialize(self) -> None: - if self._graph is None: - self._langfuse = Langfuse(debug=False) + async def _get_agent_instance(self, agent_id: str | None = None) -> AgentInstance: + environment = self.config.environment + cache_key = f"{agent_id}:{environment}" + if cache_key in self._agent_instances: + return self._agent_instances[cache_key] - if self._checkpointer_provider is None: - self._checkpointer_provider = await CheckpointerFactory.create( - self.config - ) # type: ignore[assignment] + agent_instance = await self.agent_factory.create_agent( + agent_id, environment + ) + + self._agent_instances[cache_key] = agent_instance + logger.debug(f"Created and cached agent instance: {cache_key}") - checkpointer = await self._checkpointer_provider.get_checkpointer() # type: ignore[union-attr] - prompt_provider = LangfusePromptProvider(self._langfuse) - self._graph = DemoGraph(checkpointer, prompt_provider).build_graph() - self._agent_service = AgentService(self._graph, self._langfuse) + return agent_instance async def stream( self, @@ -47,15 +45,20 @@ async def stream( thread_id: UUID | None, metadata: dict[str, Any] | None, user: User, + agent_id: str | None = None, ) -> EventSourceResponse: thread = await ThreadRepository.get_thread_by_id(str(thread_id)) + if agent_id and thread.agent_id != agent_id: + thread.agent_id = agent_id + effective_agent_id = agent_id or thread.agent_id + try: - await self._initialize() + agent_instance = await self._get_agent_instance(effective_agent_id) logger.debug(f"Received chat request: {str(query)[:50]}...") return EventSourceResponse( - self._agent_service.stream_response(str(query), thread, user), # type: ignore[union-attr] + agent_instance.stream_response(str(query), thread, user), # type: ignore[union-attr] headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", @@ -74,9 +77,9 @@ async def get_thread_history( thread: Thread = Depends(ThreadRepository.get_thread_by_id), # noqa: B008 ) -> EventSourceResponse: try: - await self._initialize() + agent_instance = await self._get_agent_instance(thread.agent_id) return EventSourceResponse( - self._agent_service.load_history(thread, user), # type: ignore[union-attr] + agent_instance.load_history(thread, user), # type: ignore[union-attr] headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", @@ -95,7 +98,6 @@ async def feedback( user: User = Depends(UserRepository.get_user_by_id), # noqa: B008 thread: Thread = Depends(ThreadRepository.get_thread_by_id), # noqa: B008 ) -> dict[str, str]: - await self._initialize() return await self._agent_service.add_feedback( # type: ignore[union-attr] trace=request.trace_id, feedback=request.feedback, thread=thread, user=user ) diff --git a/app/models/thread.py b/app/models/thread.py index 143da4c..255516b 100644 --- a/app/models/thread.py +++ b/app/models/thread.py @@ -17,6 +17,11 @@ class Thread(BaseModel): description="Thread ID.", examples=["edd5a53c-da04-4db4-84e0-a9f3592eef45"], ) + agent_id: str | None = Field( + default=None, + description="The ID of the agent associated with this thread.", + examples=["agent-12345"], + ) created_at: AwareDatetime = Field( default_factory=lambda: datetime.now(UTC), description="The time the thread was created.", diff --git a/app/repositories/thread_repository.py b/app/repositories/thread_repository.py index 89a1286..ec7acd8 100644 --- a/app/repositories/thread_repository.py +++ b/app/repositories/thread_repository.py @@ -22,6 +22,7 @@ async def get_thread_by_id( metadata={ "title": "Sample Thread", }, + agent_id="demo_agent" ) if not thread: From 3c97343200029105aef8b9215628b528503b4b98 Mon Sep 17 00:00:00 2001 From: assada Date: Thu, 31 Jul 2025 19:06:00 +0200 Subject: [PATCH 2/2] feat: implement agent framework abstraction. --- README.md | 5 +- agents/config/demo_agent.json | 4 +- agents/config/demo_llamaindex_agent.json | 24 + agents/google_adk/__init__.py | 0 agents/langgraph/__init__.py | 0 agents/langgraph/demo/demo_graph.py | 10 +- agents/langgraph/demo/tools/tools.py | 11 +- agents/llamaindex/__init__.py | 0 agents/llamaindex/demo/__init__.py | 0 agents/llamaindex/demo/demo_agent.py | 203 ++++ agents/llamaindex/demo/tools.py | 32 + agents/prompt/demo_llamaindex_agent.json | 28 + app/agent/config/models.py | 5 +- app/agent/factory.py | 2 +- app/agent/frameworks/base.py | 16 +- app/agent/frameworks/google_adk.py | 24 +- app/agent/frameworks/langgraph.py | 24 +- .../langgraph_framework/checkpoint/factory.py | 5 +- .../frameworks/langgraph_framework/graph.py | 3 +- app/agent/frameworks/llamaindex.py | 87 +- app/http/controllers/thread_controller.py | 14 +- app/models/thread.py | 3 +- pyproject.toml | 1 + tests/agent/services/test_agent_service.py | 99 +- uv.lock | 938 ++++++++++++++++++ 25 files changed, 1357 insertions(+), 181 deletions(-) create mode 100644 agents/config/demo_llamaindex_agent.json create mode 100644 agents/google_adk/__init__.py create mode 100644 agents/langgraph/__init__.py create mode 100644 agents/llamaindex/__init__.py create mode 100644 agents/llamaindex/demo/__init__.py create mode 100644 agents/llamaindex/demo/demo_agent.py create mode 100644 agents/llamaindex/demo/tools.py create mode 100644 agents/prompt/demo_llamaindex_agent.json diff --git a/README.md b/README.md index 1174a92..3357cb9 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,11 @@ PRs welcome for: - [ ] ~~Keep alive SSE connection until the user closes the browser tab (??)~~ - [ ] 🟡 Add a way to validate the user's access token (OAuth2) - [ ] 🟡 Add evaluation metrics -- [ ] 🔴 Add *one more* abstraction layer so the agent can use different frameworks (LangGraph, LlamaIndex, etc.) +- [x] 🔴 Add *one more* abstraction layer so the agent can use different frameworks (LangGraph, LlamaIndex, etc.) - [ ] 🟠 Add even more fucking abstractions to make it independent of observability tools (LangFuse, LangSmith, Grafana Alloy, or whatever the fuck else) -- [ ] ⚪ Long-Term memory for each user. I want to add to chat application for real-time per thread prompt tuning - memory +- [ ] ⚪ Long-Term memory for each user. I want to add to chat application for real-time per thread prompt tuning - + memory insights, response strategies, etc. But this is more about agent implementation not template core. Graph node as " addon package?" LOL! https://i.imgur.com/k1jk3cx.png here we go again! - [ ] ⚪ Guardrails ([LLMGuard implementation](https://github.com/assada/agent_template/tree/feat/guardrails) or handle diff --git a/agents/config/demo_agent.json b/agents/config/demo_agent.json index 8af2b0a..9f830ff 100644 --- a/agents/config/demo_agent.json +++ b/agents/config/demo_agent.json @@ -3,8 +3,8 @@ "description": "Demo conversational agent with tool support", "production": { "framework": "langgraph", - "graph_class": "agents.langgraph.demo.demo_graph.DemoGraph", - "prompt_source": "file", + "class_name": "agents.langgraph.demo.demo_graph.DemoGraph", + "prompt_source": "langfuse", "custom_settings": { "default_system_prompt": "You are a helpful AI assistant with access to tools.", "max_iterations": 10 diff --git a/agents/config/demo_llamaindex_agent.json b/agents/config/demo_llamaindex_agent.json new file mode 100644 index 0000000..84bbef8 --- /dev/null +++ b/agents/config/demo_llamaindex_agent.json @@ -0,0 +1,24 @@ +{ + "name": "demo_llamaindex_agent", + "description": "Demo LlamaIndex agent with weather tool support", + "production": { + "framework": "llamaindex", + "class_name": "agents.llamaindex.demo.demo_agent.DemoLlamaIndexAgent", + "prompt_source": "file", + "custom_settings": { + "default_system_prompt": "You are a helpful AI assistant with access to weather information. When users ask about weather, use the get_weather tool to provide accurate information.", + "max_iterations": 10, + "verbose": false + } + }, + "development": { + "framework": "llamaindex", + "class_name": "agents.llamaindex.demo.demo_agent.DemoLlamaIndexAgent", + "prompt_source": "file", + "custom_settings": { + "default_system_prompt": "You are a helpful AI assistant with access to weather information. When users ask about weather, use the get_weather tool to provide accurate information.", + "max_iterations": 5, + "verbose": false + } + } +} \ No newline at end of file diff --git a/agents/google_adk/__init__.py b/agents/google_adk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/langgraph/__init__.py b/agents/langgraph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/langgraph/demo/demo_graph.py b/agents/langgraph/demo/demo_graph.py index 440b573..cf5b133 100644 --- a/agents/langgraph/demo/demo_graph.py +++ b/agents/langgraph/demo/demo_graph.py @@ -2,14 +2,14 @@ from typing import Any, Literal from langchain_core.messages import AIMessage -from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.graph import START, StateGraph -from langgraph.graph.state import CompiledStateGraph -from langgraph.prebuilt import ToolNode from app.agent.frameworks.langgraph_framework import Graph from app.agent.frameworks.langgraph_framework.base_state import BaseState, State from app.agent.prompt import PromptProvider +from langgraph.checkpoint.base import BaseCheckpointSaver +from langgraph.graph import START, StateGraph +from langgraph.graph.state import CompiledStateGraph +from langgraph.prebuilt import ToolNode from .tools import TOOLS @@ -18,7 +18,7 @@ class DemoGraph(Graph): def __init__( - self, checkpointer: BaseCheckpointSaver[Any], prompt_provider: PromptProvider + self, checkpointer: BaseCheckpointSaver[Any], prompt_provider: PromptProvider, custom_config: dict[str, Any] | None = None ): super().__init__(checkpointer, prompt_provider) diff --git a/agents/langgraph/demo/tools/tools.py b/agents/langgraph/demo/tools/tools.py index 3b7e337..5731912 100644 --- a/agents/langgraph/demo/tools/tools.py +++ b/agents/langgraph/demo/tools/tools.py @@ -1,9 +1,8 @@ from collections.abc import Callable from typing import Any -from langgraph.config import get_stream_writer - from app.agent.models import CustomUIMessage +from langgraph.config import get_stream_writer async def get_weather(city: str) -> str: @@ -24,7 +23,13 @@ async def get_weather(city: str) -> str: ) ) - return f"It's always sunny in {city}!" + import random + + weather_conditions = ["sunny", "cloudy", "rainy", "snowy", "partly cloudy"] + temperature = random.randint(-5, 35) + condition = random.choice(weather_conditions) + + return f"The weather in {city} is {condition} and {temperature}°C!" TOOLS: list[Callable[..., Any]] = [get_weather] diff --git a/agents/llamaindex/__init__.py b/agents/llamaindex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/llamaindex/demo/__init__.py b/agents/llamaindex/demo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/agents/llamaindex/demo/demo_agent.py b/agents/llamaindex/demo/demo_agent.py new file mode 100644 index 0000000..8d2bb10 --- /dev/null +++ b/agents/llamaindex/demo/demo_agent.py @@ -0,0 +1,203 @@ +from __future__ import annotations + +import asyncio +import json +import logging +from collections.abc import AsyncGenerator +from typing import Any + +from llama_index.core.agent.workflow import BaseWorkflowAgent, ReActAgent +from llama_index.core.llms.function_calling import FunctionCallingLLM +from llama_index.core.workflow import Event +from workflows.handler import WorkflowHandler + +from app.agent.prompt import Prompt, PromptProvider +from app.agent.services.events import EndEvent, ErrorEvent, TokenEvent +from app.agent.services.events.base_event import BaseEvent +from app.models import Thread, User + +from .tools import TOOLS + +logger = logging.getLogger(__name__) + + +class DemoLlamaIndexAgent: + def __init__( + self, + prompt_provider: PromptProvider, + custom_settings: dict[str, Any] | None = None + ): + self.prompt_provider = prompt_provider + self.custom_settings = custom_settings or {} + self.agent: BaseWorkflowAgent | None = None + + def _get_model(self, prompt: Prompt) -> FunctionCallingLLM: + from llama_index.llms.openai import OpenAI + + config = getattr(prompt, "config", {}) or {} + model_config = config.get("model", "openai/gpt-4o-mini") + + if "/" in model_config: + provider, model = model_config.split("/", 1) + else: + provider, model = "openai", model_config + + if provider == "openai": + return OpenAI( + model=model, + temperature=config.get("temperature", 0.7), + max_tokens=config.get("max_tokens", 4096), + streaming=True + ) + else: + raise ValueError(f"Unsupported provider: {provider}") + + def _get_prompt_fallback(self) -> Prompt: + return Prompt( + content="You are a helpful AI assistant with access to tools. Use the tools when needed to answer user questions.", + config={ + "model": "openai/gpt-4o-mini", + "temperature": 0.7, + "max_tokens": 4096, + }, + ) + + def _initialize_agent(self) -> BaseWorkflowAgent: + if self.agent is not None: + return self.agent + + prompt = self.prompt_provider.get_prompt( + "demo_llamaindex_agent", "production", self._get_prompt_fallback() + ) + + llm = self._get_model(prompt) + + self.agent = ReActAgent( + tools=TOOLS, + llm=llm, + verbose=self.custom_settings.get("verbose", True), + system_prompt=prompt.content + ) + + return self.agent + + async def stream_response( + self, message: str, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + try: + agent = self._initialize_agent() + handler = agent.run(user_msg=message) + + async def get_result() -> WorkflowHandler: + return await handler + + run_task = asyncio.create_task(get_result()) + + streaming_content = "" + has_content = False + + async for event in agent.stream_events(): + processed_event = self._process_workflow_event(event, thread, user) + if processed_event: + if isinstance(processed_event, TokenEvent): + token_data = json.loads(processed_event.data) + streaming_content += token_data.get("content", "") + has_content = True + + yield processed_event.model_dump() + + final_response = await run_task + + if not has_content and final_response: + message_event = BaseEvent.from_payload( + event="message", + payload={ + "type": "ai_message", + "content": str(final_response), + "metadata": { + "agent": "demo_llamaindex_agent", + "framework": "llamaindex", + "user_id": str(user.id), + "thread_id": str(thread.id) + } + } + ) + yield message_event.model_dump() + + except Exception as e: + logger.error(f"Error in LlamaIndex agent streaming: {e}") + error_event = ErrorEvent( + data=json.dumps({ + "content": f"Error processing request: {str(e)}", + "error_type": type(e).__name__ + }) + ) + yield error_event.model_dump() + finally: + end_event = EndEvent(data=json.dumps({"status": "completed"})) + yield end_event.model_dump() + + def _process_workflow_event(self, event: Event, thread: Thread, user: User) -> BaseEvent | None: + """Process workflow events and convert to our event format.""" + try: + event_type = type(event).__name__ + + if hasattr(event, 'delta') and event.delta: + return TokenEvent( + data=json.dumps({ + "content": str(event.delta), + "metadata": { + "agent": "demo_llamaindex_agent", + "framework": "llamaindex", + "event_type": event_type + } + }) + ) + + elif hasattr(event, 'content') and event.content: + return BaseEvent.from_payload( + event="message", + payload={ + "type": "ai_message", + "content": str(event.content), + "metadata": { + "agent": "demo_llamaindex_agent", + "framework": "llamaindex", + "event_type": event_type, + "user_id": str(user.id), + "thread_id": str(thread.id) + } + } + ) + + elif hasattr(event, 'tool_calls') and event.tool_calls: + return BaseEvent.from_payload( + event="tool_call", + payload={ + "tool_calls": [str(call) for call in event.tool_calls], + "metadata": { + "agent": "demo_llamaindex_agent", + "framework": "llamaindex", + "event_type": event_type + } + } + ) + + else: + logger.debug(f"Workflow event {event_type}: {event}") + return None + + except Exception as e: + logger.warning(f"Error processing workflow event: {e}") + return None + + async def load_history( + self, thread: Thread, user: User + ) -> AsyncGenerator[dict[str, Any]]: + """Load conversation history.""" + # For demo purposes, return empty history + # In real implementation, you'd load from agent's chat history + logger.info(f"Loading history for thread {thread.id} with LlamaIndex agent") + + end_event = EndEvent(data=json.dumps({"status": "completed", "message": "No history available"})) + yield end_event.model_dump() \ No newline at end of file diff --git a/agents/llamaindex/demo/tools.py b/agents/llamaindex/demo/tools.py new file mode 100644 index 0000000..bc70341 --- /dev/null +++ b/agents/llamaindex/demo/tools.py @@ -0,0 +1,32 @@ + +from llama_index.core.tools import FunctionTool + + +async def get_weather(city: str) -> str: + """Get weather for a given city. + + Args: + city: The name of the city to get weather for + + Returns: + Weather information for the city + """ + # For demo purposes, return mock weather data + # In real implementation, this would call a weather API + import random + weather_conditions = ["sunny", "cloudy", "rainy", "snowy", "partly cloudy"] + temperature = random.randint(-5, 35) + condition = random.choice(weather_conditions) + + return f"The weather in {city} is {condition} and {temperature}°C!" + + +def create_weather_tool() -> FunctionTool: + return FunctionTool.from_defaults( + fn=get_weather, + name="get_weather", + description="Get current weather information for a specific city" + ) + + +TOOLS = [create_weather_tool()] \ No newline at end of file diff --git a/agents/prompt/demo_llamaindex_agent.json b/agents/prompt/demo_llamaindex_agent.json new file mode 100644 index 0000000..bbb5154 --- /dev/null +++ b/agents/prompt/demo_llamaindex_agent.json @@ -0,0 +1,28 @@ +{ + "production": { + "content": "You are a helpful AI assistant with access to weather information tools.\n\nWhen users ask about the weather in any city, use the get_weather tool to provide accurate and up-to-date information.\n\nAlways be polite, helpful, and provide clear responses. If you use a tool, explain what you're doing and why.", + "config": { + "model": "openai/gpt-4o-mini", + "temperature": 0.7, + "max_tokens": 2048 + }, + "metadata": { + "version": "1.0", + "author": "demo", + "description": "LlamaIndex weather agent prompt" + } + }, + "development": { + "content": "You are a helpful AI assistant with access to weather information tools.\n\nWhen users ask about the weather in any city, use the get_weather tool to provide accurate and up-to-date information.\n\nAlways be polite, helpful, and provide clear responses. If you use a tool, explain what you're doing and why.\n\n[DEV MODE] You are running in development mode with enhanced debugging.", + "config": { + "model": "openai/gpt-3.5-turbo", + "temperature": 0.5, + "max_tokens": 1024 + }, + "metadata": { + "version": "1.0-dev", + "author": "demo", + "description": "LlamaIndex weather agent prompt (development)" + } + } +} \ No newline at end of file diff --git a/app/agent/config/models.py b/app/agent/config/models.py index 2a5d98d..7816f26 100644 --- a/app/agent/config/models.py +++ b/app/agent/config/models.py @@ -1,15 +1,13 @@ from __future__ import annotations -import logging from typing import Any from pydantic import BaseModel -logger = logging.getLogger(__name__) class EnvironmentConfig(BaseModel): framework: str # Options: "langgraph", "llamaindex", "google_adk" - graph_class: str | None = None + class_name: str prompt_source: str = "file" # Options: "langfuse", "file" custom_settings: dict[str, Any] | None = None @@ -21,7 +19,6 @@ class AgentConfig(BaseModel): development: EnvironmentConfig | None = None def get_config_for_environment(self, environment: str) -> EnvironmentConfig: - logger.warning(f"get_config_for_environment: {environment}") if environment == "production": return self.production elif environment == "development" and self.development: diff --git a/app/agent/factory.py b/app/agent/factory.py index ecf1961..541ae8c 100644 --- a/app/agent/factory.py +++ b/app/agent/factory.py @@ -32,7 +32,7 @@ def register_framework(self, framework: AgentFramework) -> None: async def create_agent( self, agent_id: str, - environment: str | None = None, + environment: str, ) -> AgentInstance: agent_config = self.config_loader.get_agent_config(agent_id) if not agent_config: diff --git a/app/agent/frameworks/base.py b/app/agent/frameworks/base.py index 60220f0..af08ceb 100644 --- a/app/agent/frameworks/base.py +++ b/app/agent/frameworks/base.py @@ -1,5 +1,6 @@ from __future__ import annotations +import importlib import logging from abc import ABC, abstractmethod from collections.abc import AsyncGenerator @@ -37,6 +38,20 @@ def framework_name(self) -> str: """Name of the framework (e.g., 'langgraph', 'llamaindex', 'google_adk').""" pass + def _load_agent_class(self, agent_class_name: str) -> Any: + if not agent_class_name or '.' not in agent_class_name: + raise ValueError(f"Invalid agent class name: {agent_class_name}") + + module_name, class_name = agent_class_name.rsplit('.', 1) + + try: + module = importlib.import_module(module_name) + return getattr(module, class_name) + except ImportError as e: + raise ImportError(f"Could not import module {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Class {class_name} not found in module {module_name}: {e}") from e + @abstractmethod async def create_agent( self, @@ -44,5 +59,4 @@ async def create_agent( agent_config: EnvironmentConfig, global_config: AppConfig ) -> AgentInstance: - """Create an agent instance for this framework.""" pass diff --git a/app/agent/frameworks/google_adk.py b/app/agent/frameworks/google_adk.py index 384cff5..fc33cc1 100644 --- a/app/agent/frameworks/google_adk.py +++ b/app/agent/frameworks/google_adk.py @@ -5,7 +5,7 @@ from collections.abc import AsyncGenerator from typing import Any -from app.agent.config import AgentConfig, EnvironmentConfig +from app.agent.config import EnvironmentConfig from app.agent.frameworks.base import AgentFramework, AgentInstance from app.bootstrap.config import AppConfig from app.models import Thread, User @@ -17,9 +17,9 @@ class GoogleADKAgentInstance(AgentInstance): def __init__(self, agent_id: str, config: EnvironmentConfig): super().__init__(agent_id, config) # TODO: Initialize Google ADK components here - logger.info(f"Created Google ADK agent instance: {agent_id}") + logger.debug(f"Created Google ADK agent instance: {agent_id}") - async def stream_response( + async def stream_response( # type: ignore[override] self, message: str, thread: Thread, user: User ) -> AsyncGenerator[dict[str, Any]]: # TODO: Implement Google ADK streaming logic @@ -40,13 +40,12 @@ async def stream_response( "data": json.dumps({"status": "completed"}) } - async def load_history( + async def load_history( # type: ignore[override] self, thread: Thread, user: User - ) -> AsyncGenerator[dict[str, Any]]: + ) -> AsyncGenerator[dict[str, Any]]: # ignore[override] # TODO: Implement Google ADK history loading logger.info(f"Loading history for thread {thread.id} with Google ADK agent {self.agent_id}") - # Placeholder implementation yield { "event": "end", "data": json.dumps({"status": "completed", "message": "No history available"}) @@ -58,6 +57,10 @@ class GoogleADKFramework(AgentFramework): def framework_name(self) -> str: return "google_adk" + def _load_agent_class(self, agent_class_name: str) -> type[AgentInstance]: + # TODO: Implement dynamic class loading for Google ADK + return GoogleADKAgentInstance + async def create_agent( self, agent_id: str, @@ -65,9 +68,14 @@ async def create_agent( global_config: AppConfig ) -> AgentInstance: # TODO: Initialize Google ADK components based on config - logger.info(f"Creating Google ADK agent: {agent_id}") + logger.debug(f"Creating Google ADK agent: {agent_id}") + + agent_class = self._load_agent_class(agent_config.class_name) + + if not issubclass(agent_class, GoogleADKAgentInstance): + raise TypeError(f"Agent class {agent_class} is not a subclass of GoogleADKAgentInstance") - return GoogleADKAgentInstance( + return agent_class( agent_id=agent_id, config=agent_config ) diff --git a/app/agent/frameworks/langgraph.py b/app/agent/frameworks/langgraph.py index 38f7972..4fb4647 100644 --- a/app/agent/frameworks/langgraph.py +++ b/app/agent/frameworks/langgraph.py @@ -1,6 +1,5 @@ from __future__ import annotations -import importlib import json import logging from collections.abc import AsyncGenerator @@ -44,7 +43,7 @@ def __init__( self.langfuse = langfuse self.stream_processor = StreamProcessor() - async def stream_response( + async def stream_response( # type: ignore[override] self, message: str, thread: Thread, user: User ) -> AsyncGenerator[dict[str, Any]]: with self.langfuse.start_as_current_span( @@ -89,7 +88,7 @@ async def stream_response( data=json.dumps({"run_id": str(run_id), "content": str(e)}) ).model_dump() - async def load_history( + async def load_history( # type: ignore[override] self, thread: Thread, user: User ) -> AsyncGenerator[dict[str, Any]]: try: @@ -132,16 +131,6 @@ class LangGraphFramework(AgentFramework): def framework_name(self) -> str: return "langgraph" - def _load_graph_class(self, graph_class_name: str): - module_name, class_name = graph_class_name.rsplit('.', 1) - - try: - module = importlib.import_module(module_name) - return getattr(module, class_name) - except ImportError as e: - raise ImportError(f"Could not import module {module_name}: {e}") - except AttributeError as e: - raise AttributeError(f"Class {class_name} not found in module {module_name}: {e}") async def create_agent( self, @@ -153,18 +142,17 @@ async def create_agent( checkpointer_provider = await CheckpointerFactory.create(global_config) ## TODO: Agent config checkpointer = await checkpointer_provider.get_checkpointer() - logger.warning(agent_config.prompt_source) + prompt_provider = create_prompt_provider( prompt_source=agent_config.prompt_source, langfuse_client=langfuse if agent_config.prompt_source == "langfuse" else None, prompt_dir=global_config.prompt_dir ) - graph_class_name = agent_config.graph_class - graph_class = self._load_graph_class(graph_class_name) + agent_class = self._load_agent_class(agent_config.class_name) - graph_instance = graph_class(checkpointer, prompt_provider, agent_config.custom_settings) - compiled_graph = graph_instance.build_graph() + agent_instance = agent_class(checkpointer, prompt_provider, agent_config.custom_settings) + compiled_graph = agent_instance.build_graph() return LangGraphAgentInstance( agent_id=agent_id, diff --git a/app/agent/frameworks/langgraph_framework/checkpoint/factory.py b/app/agent/frameworks/langgraph_framework/checkpoint/factory.py index 71f3402..be6ac64 100644 --- a/app/agent/frameworks/langgraph_framework/checkpoint/factory.py +++ b/app/agent/frameworks/langgraph_framework/checkpoint/factory.py @@ -1,10 +1,11 @@ import logging +from app.bootstrap.config import AppConfig +from app.infrastructure.database.connection import DatabaseConnectionFactory + from .base import BaseCheckpointer from .memory import MemoryCheckpointer from .postgres import PostgresCheckpointer -from app.bootstrap.config import AppConfig -from app.infrastructure.database.connection import DatabaseConnectionFactory logger = logging.getLogger(__name__) diff --git a/app/agent/frameworks/langgraph_framework/graph.py b/app/agent/frameworks/langgraph_framework/graph.py index cb4addc..8ed85aa 100644 --- a/app/agent/frameworks/langgraph_framework/graph.py +++ b/app/agent/frameworks/langgraph_framework/graph.py @@ -13,9 +13,10 @@ from langgraph.checkpoint.base import BaseCheckpointSaver from langgraph.graph.state import CompiledStateGraph -from .base_state import BaseState, State from app.agent.prompt import Prompt, PromptProvider +from .base_state import BaseState, State + logger = logging.getLogger(__name__) diff --git a/app/agent/frameworks/llamaindex.py b/app/agent/frameworks/llamaindex.py index d47b556..ed8e790 100644 --- a/app/agent/frameworks/llamaindex.py +++ b/app/agent/frameworks/llamaindex.py @@ -7,6 +7,7 @@ from app.agent.config import EnvironmentConfig from app.agent.frameworks.base import AgentFramework, AgentInstance +from app.agent.prompt import create_prompt_provider from app.bootstrap.config import AppConfig from app.models import Thread, User @@ -14,42 +15,49 @@ class LlamaIndexAgentInstance(AgentInstance): - def __init__(self, agent_id: str, config: EnvironmentConfig): + def __init__( + self, + agent_id: str, + config: EnvironmentConfig, + llamaindex_agent: Any = None + ): super().__init__(agent_id, config) - # TODO: Initialize LlamaIndex components here + self.llamaindex_agent = llamaindex_agent logger.debug(f"Created LlamaIndex agent instance: {agent_id}") - async def stream_response( + async def stream_response( # type: ignore[override] self, message: str, thread: Thread, user: User ) -> AsyncGenerator[dict[str, Any]]: - # TODO: Implement LlamaIndex streaming logic - logger.debug(f"LlamaIndex agent {self.agent_id} processing message: {message[:50]}...") + if self.llamaindex_agent: + async for event in self.llamaindex_agent.stream_response(message, thread, user): + yield event + else: + logger.warning(f"No LlamaIndex agent configured for {self.agent_id}") + yield { + "event": "message", + "data": json.dumps({ + "type": "ai_message", + "content": f"LlamaIndex agent '{self.agent_id}' received: {message}", + "metadata": {"agent": self.agent_id, "framework": "llamaindex"} + }) + } + yield { + "event": "end", + "data": json.dumps({"status": "completed"}) + } - yield { - "event": "message", - "data": json.dumps({ - "type": "ai_message", - "content": f"LlamaIndex agent '{self.agent_id}' received: {message}", - "metadata": {"agent": self.agent_id, "framework": "llamaindex"} - }) - } - - yield { - "event": "end", - "data": json.dumps({"status": "completed"}) - } - - async def load_history( + async def load_history( # type: ignore[override] self, thread: Thread, user: User ) -> AsyncGenerator[dict[str, Any]]: - # TODO: Implement LlamaIndex history loading - logger.info(f"Loading history for thread {thread.id} with LlamaIndex agent {self.agent_id}") - - # Placeholder implementation - yield { - "event": "end", - "data": json.dumps({"status": "completed", "message": "No history available"}) - } + if self.llamaindex_agent: + async for event in self.llamaindex_agent.load_history(thread, user): + yield event + else: + logger.info(f"Loading history for thread {thread.id} with LlamaIndex agent {self.agent_id}") + yield { + "event": "end", + "data": json.dumps({"status": "completed", "message": "No history available"}) + } class LlamaIndexFramework(AgentFramework): @@ -63,10 +71,29 @@ async def create_agent( agent_config: EnvironmentConfig, global_config: AppConfig ) -> AgentInstance: - # TODO: Initialize LlamaIndex components based on config logger.debug(f"Creating LlamaIndex agent: {agent_id}") + prompt_provider = create_prompt_provider( + prompt_source=agent_config.prompt_source, + langfuse_client=None, + prompt_dir=global_config.prompt_dir + ) + + llamaindex_agent = None + + if agent_config.class_name: + try: + agent_class = self._load_agent_class(agent_config.class_name) + llamaindex_agent = agent_class( + prompt_provider=prompt_provider, + custom_settings=agent_config.custom_settings + ) + logger.debug(f"Successfully created {agent_config.class_name} instance") + except Exception as e: + logger.error(f"Failed to create LlamaIndex agent {agent_config.class_name}: {e}") + return LlamaIndexAgentInstance( agent_id=agent_id, - config=agent_config + config=agent_config, + llamaindex_agent=llamaindex_agent ) diff --git a/app/http/controllers/thread_controller.py b/app/http/controllers/thread_controller.py index 01e2edf..dc223bd 100644 --- a/app/http/controllers/thread_controller.py +++ b/app/http/controllers/thread_controller.py @@ -4,7 +4,7 @@ from fastapi import Depends, HTTPException from langfuse import Langfuse # type: ignore[attr-defined] -from sse_starlette.sse import EventSourceResponse # type: ignore[attr-defined] +from sse_starlette.sse import EventSourceResponse from app.agent.factory import AgentFactory from app.agent.frameworks.base import AgentInstance @@ -24,7 +24,7 @@ def __init__(self, config: AppConfig): self._agent_instances: dict[str, AgentInstance] = {} self._agent_service = AgentService(Langfuse(debug=False)) - async def _get_agent_instance(self, agent_id: str | None = None) -> AgentInstance: + async def _get_agent_instance(self, agent_id: str) -> AgentInstance: environment = self.config.environment cache_key = f"{agent_id}:{environment}" if cache_key in self._agent_instances: @@ -51,14 +51,14 @@ async def stream( if agent_id and thread.agent_id != agent_id: thread.agent_id = agent_id - effective_agent_id = agent_id or thread.agent_id + effective_agent_id: str = agent_id or thread.agent_id try: agent_instance = await self._get_agent_instance(effective_agent_id) logger.debug(f"Received chat request: {str(query)[:50]}...") return EventSourceResponse( - agent_instance.stream_response(str(query), thread, user), # type: ignore[union-attr] + agent_instance.stream_response(str(query), thread, user), # type: ignore[arg-type] headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", @@ -79,7 +79,7 @@ async def get_thread_history( try: agent_instance = await self._get_agent_instance(thread.agent_id) return EventSourceResponse( - agent_instance.load_history(thread, user), # type: ignore[union-attr] + agent_instance.load_history(thread, user), # type: ignore[arg-type] headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", @@ -89,6 +89,8 @@ async def get_thread_history( }, ) except Exception as e: + import traceback + logger.error(f"Error fetching thread history: {traceback.format_exc()}") logger.error(f"Error fetching thread history: {str(e)}") raise HTTPException(status_code=500, detail="Internal server error") from e @@ -98,6 +100,6 @@ async def feedback( user: User = Depends(UserRepository.get_user_by_id), # noqa: B008 thread: Thread = Depends(ThreadRepository.get_thread_by_id), # noqa: B008 ) -> dict[str, str]: - return await self._agent_service.add_feedback( # type: ignore[union-attr] + return await self._agent_service.add_feedback( trace=request.trace_id, feedback=request.feedback, thread=thread, user=user ) diff --git a/app/models/thread.py b/app/models/thread.py index 255516b..0f9fb9e 100644 --- a/app/models/thread.py +++ b/app/models/thread.py @@ -17,8 +17,7 @@ class Thread(BaseModel): description="Thread ID.", examples=["edd5a53c-da04-4db4-84e0-a9f3592eef45"], ) - agent_id: str | None = Field( - default=None, + agent_id: str = Field( description="The ID of the agent associated with this thread.", examples=["agent-12345"], ) diff --git a/pyproject.toml b/pyproject.toml index 90a9ea6..48fd892 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "langfuse>=3.1.3", "langgraph==0.5.3", "langgraph-checkpoint-postgres>=2.0.22", + "llama-index>=0.12.52", "mypy>=1.17.0", "opentelemetry-instrumentation-fastapi>=0.55b1", "prometheus-fastapi-instrumentator>=7.1.0", diff --git a/tests/agent/services/test_agent_service.py b/tests/agent/services/test_agent_service.py index 78d9341..964cfd7 100644 --- a/tests/agent/services/test_agent_service.py +++ b/tests/agent/services/test_agent_service.py @@ -1,15 +1,11 @@ -import json import tracemalloc from datetime import UTC, datetime -from unittest.mock import AsyncMock, Mock, patch -from uuid import uuid4 +from unittest.mock import Mock import pytest from langfuse import Langfuse -from langgraph.graph.state import CompiledStateGraph from app.agent.services.agent_service import AgentService -from app.agent.services.events.base_event import BaseEvent from app.models import Thread, User from app.models.thread import ThreadStatus @@ -27,17 +23,8 @@ def mock_langfuse(): @pytest.fixture -def mock_graph(): - g = Mock(spec=CompiledStateGraph) - g.name = "test_graph" - g.astream = AsyncMock() - g.aget_state = AsyncMock() - return g - - -@pytest.fixture -def agent_service(mock_graph, mock_langfuse): - return AgentService(mock_graph, mock_langfuse) +def agent_service(mock_langfuse): + return AgentService(mock_langfuse) @pytest.fixture @@ -57,86 +44,6 @@ def mock_user(): class TestAgentService: - @pytest.mark.asyncio - @patch("app.agent.services.agent_service.uuid4") - async def test_stream_response_success(self, mock_uuid4, agent_service, mock_thread, mock_user): - mock_uuid4.return_value = uuid4() - mock_event = Mock() - mock_event.model_dump.return_value = {"event": "test", "data": "test_data"} - - async def stream(): - yield ("updates", {"t": "d"}) - - agent_service.graph.astream.return_value = stream() - - with patch.object(agent_service.stream_processor, "process_stream") as mproc: - async def proc(*_): - yield mock_event - - mproc.return_value = proc() - - out = [r async for r in agent_service.stream_response("msg", mock_thread, mock_user)] - - assert out == [{"event": "test", "data": "test_data"}] - assert mock_thread.status == ThreadStatus.idle - - cfg = agent_service.graph.astream.call_args.kwargs["config"] - assert cfg["configurable"]["thread_id"] == mock_thread.id - assert cfg["configurable"]["user_id"] == mock_user.id - assert cfg["run_id"] is not None - - @pytest.mark.asyncio - async def test_stream_response_thread_status_updates(self, agent_service, mock_thread, mock_user): - ts0 = mock_thread.updated_at - - async def stream(): - yield ("updates", {"t": "d"}) - - agent_service.graph.astream.return_value = stream() - ev = Mock() - ev.model_dump.return_value = {"e": "d"} - - with patch.object(agent_service.stream_processor, "process_stream") as mproc: - async def proc(*_): - yield ev - - mproc.return_value = proc() - async for _ in agent_service.stream_response("m", mock_thread, mock_user): - pass - - assert mock_thread.status == ThreadStatus.idle - assert mock_thread.updated_at > ts0 - - @pytest.mark.asyncio - @patch("app.agent.services.agent_service.to_chat_message") - async def test_load_history_with_messages(self, m_to, agent_service, mock_thread, mock_user): - chat = Mock() - chat.type = "ai_message" - chat.model_dump.return_value = {"c": "t"} - m_to.return_value = chat - - msg = Mock(id="m1") - st = Mock(values={"messages": [msg], "message_trace_map": [{"id": "m1", "trace_id": "tr"}]}) - agent_service.graph.aget_state.return_value = st - - with patch.object(BaseEvent, "from_payload") as fp: - ev = Mock() - ev.model_dump.return_value = {"e": "ai"} - fp.return_value = ev - - res = [r async for r in agent_service.load_history(mock_thread, mock_user)] - - assert res[0] == {"e": "ai"} - assert json.loads(res[1]["data"])["status"] == "completed" - m_to.assert_called_once_with(msg, trace_id="tr") - - @pytest.mark.asyncio - async def test_load_history_no_messages(self, agent_service, mock_thread, mock_user): - st = Mock(values={"messages": []}) - agent_service.graph.aget_state.return_value = st - res = [r async for r in agent_service.load_history(mock_thread, mock_user)] - assert json.loads(res[0]["data"])["status"] == "completed" - @pytest.mark.asyncio async def test_add_feedback_success(self, agent_service, mock_thread, mock_user): t0 = mock_thread.updated_at diff --git a/uv.lock b/uv.lock index 7647ab3..2604294 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,73 @@ version = 1 revision = 1 requires-python = ">=3.13" +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265 }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741 }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407 }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703 }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532 }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794 }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865 }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238 }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566 }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270 }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294 }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958 }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553 }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688 }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157 }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050 }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647 }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067 }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, +] + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792 }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -62,6 +129,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/a4/cec76b3389c4c5ff66301cd100fe88c318563ec8a520e0b2e792b5b84972/asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e", size = 621623 }, ] +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + [[package]] name = "backoff" version = "2.2.1" @@ -71,6 +147,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148 }, ] +[[package]] +name = "banks" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "griffe" }, + { name = "jinja2" }, + { name = "platformdirs" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/f8/25ef24814f77f3fd7f0fd3bd1ef3749e38a9dbd23502fbb53034de49900c/banks-2.2.0.tar.gz", hash = "sha256:d1446280ce6e00301e3e952dd754fd8cee23ff277d29ed160994a84d0d7ffe62", size = 179052 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/d6/f9168956276934162ec8d48232f9920f2985ee45aa7602e3c6b4bc203613/banks-2.2.0-py3-none-any.whl", hash = "sha256:963cd5c85a587b122abde4f4064078def35c50c688c1b9d36f43c92503854e7d", size = 29244 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 }, +] + [[package]] name = "black" version = "25.1.0" @@ -218,6 +323,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0f/64/922899cff2c0fd3496be83fa8b81230f5a8d82a2ad30f98370b133c2c83b/coverage-7.10.1-py3-none-any.whl", hash = "sha256:fa2a258aa6bf188eb9a8948f7102a83da7c430a0dce918dbd8b60ef8fcb772d7", size = 206597 }, ] +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998 }, +] + +[[package]] +name = "dirtyjson" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/04/d24f6e645ad82ba0ef092fa17d9ef7a21953781663648a01c9371d9e8e98/dirtyjson-1.0.8.tar.gz", hash = "sha256:90ca4a18f3ff30ce849d100dcf4a003953c79d3a2348ef056f1d9c22231a25fd", size = 30782 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/69/1bcf70f81de1b4a9f21b3a62ec0c83bdff991c88d6cc2267d02408457e88/dirtyjson-1.0.8-py3-none-any.whl", hash = "sha256:125e27248435a58acace26d5c2c4c11a1c0de0a9c5124c5a94ba78e517d74f53", size = 25197 }, +] + [[package]] name = "distro" version = "1.9.0" @@ -241,6 +389,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631 }, ] +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970 }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791 }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165 }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881 }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409 }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132 }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638 }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539 }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646 }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233 }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996 }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280 }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717 }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644 }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879 }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502 }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169 }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219 }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880 }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498 }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296 }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103 }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869 }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467 }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028 }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294 }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898 }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465 }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385 }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771 }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206 }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620 }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059 }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516 }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106 }, +] + +[[package]] +name = "fsspec" +version = "2025.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597 }, +] + [[package]] name = "googleapis-common-protos" version = "1.70.0" @@ -277,6 +486,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236 }, ] +[[package]] +name = "griffe" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/42/486d21a6c33ff69a7381511d507b6db7a7b7f4d5bec3279bc0dc45c658a9/griffe-1.9.0.tar.gz", hash = "sha256:b5531cf45e9b73f0842c2121cc4d4bcbb98a55475e191fc9830e7aef87a920a0", size = 409341 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/65/7b3fcef8c9fb6d1023484d9caf87e78450a5c9cd1e191ce9632990b65284/griffe-1.9.0-py3-none-any.whl", hash = "sha256:bcf90ee3ad42bbae70a2a490c782fc8e443de9b84aa089d857c278a4e23215fc", size = 137060 }, +] + [[package]] name = "grpcio" version = "1.73.1" @@ -362,6 +583,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + [[package]] name = "jiter" version = "0.10.0" @@ -398,6 +631,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213 }, ] +[[package]] +name = "joblib" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/fe/0f5a938c54105553436dbff7a61dc4fed4b1b2c98852f8833beaf4d5968f/joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444", size = 330475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746 }, +] + [[package]] name = "jsonpatch" version = "1.33" @@ -590,6 +832,360 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/4f/481324462c44ce21443b833ad73ee51117031d41c16fec06cddbb7495b26/langsmith-0.4.8-py3-none-any.whl", hash = "sha256:ca2f6024ab9d2cd4d091b2e5b58a5d2cb0c354a0c84fe214145a89ad450abae0", size = 367975 }, ] +[[package]] +name = "llama-cloud" +version = "0.1.35" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/72/816e6e900448e1b4a8137d90e65876b296c5264a23db6ae888bd3e6660ba/llama_cloud-0.1.35.tar.gz", hash = "sha256:200349d5d57424d7461f304cdb1355a58eea3e6ca1e6b0d75c66b2e937216983", size = 106403 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/d2/8d18a021ab757cea231428404f21fe3186bf1ebaac3f57a73c379483fd3f/llama_cloud-0.1.35-py3-none-any.whl", hash = "sha256:b7abab4423118e6f638d2f326749e7a07c6426543bea6da99b623c715b22af71", size = 303280 }, +] + +[[package]] +name = "llama-cloud-services" +version = "0.6.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "llama-cloud" }, + { name = "llama-index-core" }, + { name = "platformdirs" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/4d/690279c7fbb376e480f65054a9c87bfc7d913eeb5086e3203cd3898777d4/llama_cloud_services-0.6.53.tar.gz", hash = "sha256:ee3f0df2d46c2a96958adaca6b7407861315709a892158f07c7f1bd34bf788b3", size = 43387 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e2/f30d6e5b8076385ff0116d5ad0711c8fd4e96d3dd772096be2581023ea66/llama_cloud_services-0.6.53-py3-none-any.whl", hash = "sha256:14e53ff1b45c096d9a185623deea5b2b207b2a89462e9816ab4aa6705b5db178", size = 50370 }, +] + +[[package]] +name = "llama-index" +version = "0.12.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-agent-openai" }, + { name = "llama-index-cli" }, + { name = "llama-index-core" }, + { name = "llama-index-embeddings-openai" }, + { name = "llama-index-indices-managed-llama-cloud" }, + { name = "llama-index-llms-openai" }, + { name = "llama-index-multi-modal-llms-openai" }, + { name = "llama-index-program-openai" }, + { name = "llama-index-question-gen-openai" }, + { name = "llama-index-readers-file" }, + { name = "llama-index-readers-llama-parse" }, + { name = "nltk" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/33/496f90bc77536c89b0b1266063977d99cf38e6a3458fd62a88c846f2c4f2/llama_index-0.12.52.tar.gz", hash = "sha256:3a81fa4fbf1a36e30502d2fb7da26d53bc1a1ab02db1db12e62f06bb014d5ad9", size = 8092 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/ca/b1bb3edca7140b8d9e8957c95c6c59f2596071e89d5a10b0814976de9450/llama_index-0.12.52-py3-none-any.whl", hash = "sha256:21e05e5a02b3601e18358eeed8748384eac8d35d384fdcbe16d03f0ffb09ea61", size = 7090 }, +] + +[[package]] +name = "llama-index-agent-openai" +version = "0.4.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "llama-index-llms-openai" }, + { name = "openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/94/69decc46d11e954c6a8c64999cc237af5932d116eeb7a06515856641a6d4/llama_index_agent_openai-0.4.12.tar.gz", hash = "sha256:d2fe53feb69cfe45752edb7328bf0d25f6a9071b3c056787e661b93e5b748a28", size = 12443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/f5/857ea1c136f422234e298e868af74094a71bf98687be40a365ad6551a660/llama_index_agent_openai-0.4.12-py3-none-any.whl", hash = "sha256:6dbb6276b2e5330032a726b28d5eef5140825f36d72d472b231f08ad3af99665", size = 14704 }, +] + +[[package]] +name = "llama-index-cli" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "llama-index-embeddings-openai" }, + { name = "llama-index-llms-openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/44/6acba0b8425d15682def89a4dbba68c782fd74ce6e74a4fa48beb08632f6/llama_index_cli-0.4.4.tar.gz", hash = "sha256:c3af0cf1e2a7e5ef44d0bae5aa8e8872b54c5dd6b731afbae9f13ffeb4997be0", size = 25308 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/21/89989b7fa8ce4b9bc6f0f7326ae7f959a887c1281f007a718eafd6ef614f/llama_index_cli-0.4.4-py3-none-any.whl", hash = "sha256:1070593cf79407054735ab7a23c5a65a26fc18d264661e42ef38fc549b4b7658", size = 28598 }, +] + +[[package]] +name = "llama-index-core" +version = "0.12.52.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aiosqlite" }, + { name = "banks" }, + { name = "dataclasses-json" }, + { name = "deprecated" }, + { name = "dirtyjson" }, + { name = "filetype" }, + { name = "fsspec" }, + { name = "httpx" }, + { name = "llama-index-workflows" }, + { name = "nest-asyncio" }, + { name = "networkx" }, + { name = "nltk" }, + { name = "numpy" }, + { name = "pillow" }, + { name = "platformdirs" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "sqlalchemy", extra = ["asyncio"] }, + { name = "tenacity" }, + { name = "tiktoken" }, + { name = "tqdm" }, + { name = "typing-extensions" }, + { name = "typing-inspect" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/3b/3937a1756a02e549a776272371dd6ec3a4541833b2dbb8ef58e61167f9c9/llama_index_core-0.12.52.post1.tar.gz", hash = "sha256:ac6f447271e5ac4c12e1901373ec4b5ac7814ea33bd1ad3c3c8e9ac9771834ab", size = 7279221 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/48/8f6ea9f2a2f5a080166f0a45a252609df32cc1ad626836aaad2424e2c7ec/llama_index_core-0.12.52.post1-py3-none-any.whl", hash = "sha256:3e28d65d238bad8ec5ce372659ae0a3878851c6ba9c9447d6ddb4de138694b1f", size = 7649855 }, +] + +[[package]] +name = "llama-index-embeddings-openai" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/02/a2604ef3a167131fdd701888f45f16c8efa6d523d02efe8c4e640238f4ea/llama_index_embeddings_openai-0.3.1.tar.gz", hash = "sha256:1368aad3ce24cbaed23d5ad251343cef1eb7b4a06d6563d6606d59cb347fef20", size = 5492 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/45/ca55b91c4ac1b6251d4099fa44121a6c012129822906cadcc27b8cfb33a4/llama_index_embeddings_openai-0.3.1-py3-none-any.whl", hash = "sha256:f15a3d13da9b6b21b8bd51d337197879a453d1605e625a1c6d45e741756c0290", size = 6177 }, +] + +[[package]] +name = "llama-index-indices-managed-llama-cloud" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-cloud" }, + { name = "llama-index-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/0b42b533e6891150eb9d1189492a08be155e9656889178009bbc4cdf44b9/llama_index_indices_managed_llama_cloud-0.8.0.tar.gz", hash = "sha256:762de10d3949e04997766f6a665ed4503394d82ea4b3339139a365edde6e753e", size = 14722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/50/c259cc8b8497ab8f3e245c9bc828e8a269951b222760b5cac072acba3811/llama_index_indices_managed_llama_cloud-0.8.0-py3-none-any.whl", hash = "sha256:817d6bd4715d45522e7165d29208e093d06179cc1bc5f9590382245f73dfd7aa", size = 16451 }, +] + +[[package]] +name = "llama-index-instrumentation" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/ad/8be7010038c12ec9c0ed41a070527fd880e6181d87ae00d00790aefa50ee/llama_index_instrumentation-0.4.0.tar.gz", hash = "sha256:f38ecc1f02b6c1f7ab84263baa6467fac9f86538c0ee25542853de46278abea7", size = 44948 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/36/b85d699d2827464f9ba1c5adb1069cf18af0e3c3e45cfe017142dd85eb7c/llama_index_instrumentation-0.4.0-py3-none-any.whl", hash = "sha256:83f73156be34dd0121dfe9e259883620e19f0162f152ac483e179ad5ad0396ac", size = 14950 }, +] + +[[package]] +name = "llama-index-llms-openai" +version = "0.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/39/a7ce514fb500951e9edb713ed918a9ffe49f1a76fccfc531a4ec5c7fe15a/llama_index_llms_openai-0.4.7.tar.gz", hash = "sha256:564af8ab39fb3f3adfeae73a59c0dca46c099ab844a28e725eee0c551d4869f8", size = 24251 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/e9/391926dad180ced6bb37a62edddb8483fbecde411239bd5e726841bb77b4/llama_index_llms_openai-0.4.7-py3-none-any.whl", hash = "sha256:3b8d9d3c1bcadc2cff09724de70f074f43eafd5b7048a91247c9a41b7cd6216d", size = 25365 }, +] + +[[package]] +name = "llama-index-multi-modal-llms-openai" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "llama-index-llms-openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/5d/8a7ff14f5ac6844722152ba35ee4e1298b4665a3cf70eaeae6d6df938e37/llama_index_multi_modal_llms_openai-0.5.3.tar.gz", hash = "sha256:b755a8b47d8d2f34b5a3d249af81d9bfb69d3d2cf9ab539d3a42f7bfa3e2391a", size = 3760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e5/bc4ec1f373cd2195e625af483eddc5b05a55f6c6db020746f6bb2c0fadde/llama_index_multi_modal_llms_openai-0.5.3-py3-none-any.whl", hash = "sha256:be6237df8f9caaa257f9beda5317287bbd2ec19473d777a30a34e41a7c5bddf8", size = 3434 }, +] + +[[package]] +name = "llama-index-program-openai" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-agent-openai" }, + { name = "llama-index-core" }, + { name = "llama-index-llms-openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/81/9caa34e80adce1adb715ae083a54ad45c8fc0d9aef0f2d80d61c1b805ab6/llama_index_program_openai-0.3.2.tar.gz", hash = "sha256:04c959a2e616489894bd2eeebb99500d6f1c17d588c3da0ddc75ebd3eb7451ee", size = 6301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/80/d6ac8afafdd38115d61214891c36876e64f429809abff873660fe30862fe/llama_index_program_openai-0.3.2-py3-none-any.whl", hash = "sha256:451829ae53e074e7b47dcc60a9dd155fcf9d1dcbc1754074bdadd6aab4ceb9aa", size = 6129 }, +] + +[[package]] +name = "llama-index-question-gen-openai" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "llama-index-llms-openai" }, + { name = "llama-index-program-openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6e/19c5051c81ef5fca597d13c6d41b863535521565b1414ab5ab0e5e8c1297/llama_index_question_gen_openai-0.3.1.tar.gz", hash = "sha256:5e9311b433cc2581ff8a531fa19fb3aa21815baff75aaacdef11760ac9522aa9", size = 4107 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/2a/652593d0bd24f901776db0d1778a42363ea2656530da18215f413ce4f981/llama_index_question_gen_openai-0.3.1-py3-none-any.whl", hash = "sha256:1ce266f6c8373fc8d884ff83a44dfbacecde2301785db7144872db51b8b99429", size = 3733 }, +] + +[[package]] +name = "llama-index-readers-file" +version = "0.4.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "defusedxml" }, + { name = "llama-index-core" }, + { name = "pandas" }, + { name = "pypdf" }, + { name = "striprtf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/13/529412fcd1789607e060168ccac347bc7f012ed98c1e34614d4fb443fe39/llama_index_readers_file-0.4.11.tar.gz", hash = "sha256:1b21cb66d78dd5f60e8716607d9a47ccd81bb39106d459665be1ca7799e9597b", size = 22765 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/b3/4977330c49538b0252ca014c2ef10352ecc8f14c489ca6d95858bcb0cceb/llama_index_readers_file-0.4.11-py3-none-any.whl", hash = "sha256:e71192d8d6d0bf95131762da15fa205cf6e0cc248c90c76ee04d0fbfe160d464", size = 41046 }, +] + +[[package]] +name = "llama-index-readers-llama-parse" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-core" }, + { name = "llama-parse" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/30/4611821286f82ba7b5842295607baa876262db86f88b87d83595eed172bf/llama_index_readers_llama_parse-0.4.0.tar.gz", hash = "sha256:e99ec56f4f8546d7fda1a7c1ae26162fb9acb7ebcac343b5abdb4234b4644e0f", size = 2472 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/4f/e30d4257fe9e4224f5612b77fe99aaceddae411b2e74ca30534491de3e6f/llama_index_readers_llama_parse-0.4.0-py3-none-any.whl", hash = "sha256:574e48386f28d2c86c3f961ca4a4906910312f3400dd0c53014465bfbc6b32bf", size = 2472 }, +] + +[[package]] +name = "llama-index-workflows" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-index-instrumentation" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/9d/9dc7adc10d9976582bf50b074883986cb36b46f2fe45cf60550767300a29/llama_index_workflows-1.2.0.tar.gz", hash = "sha256:f6b19f01a340a1afb1d2fd2285c9dce346e304a3aae519e6103059f5afb2609f", size = 1019113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/c1/5190f102a042d36a6a495de27510c2d6e3aca98f892895bfacdcf9109c1d/llama_index_workflows-1.2.0-py3-none-any.whl", hash = "sha256:5722a7ce137e00361025768789e7e77720cd66f855791050183a3c540b6e5b8c", size = 37463 }, +] + +[[package]] +name = "llama-parse" +version = "0.6.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llama-cloud-services" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/58/12ecaad016ed07579660535633a570b3b4bed0455d803515f2e9645f4590/llama_parse-0.6.53.tar.gz", hash = "sha256:7d4521e08cf04fbfaa352ba0ae62e9a5de26154dbf7faf2e3ff1895749fc3d01", size = 3537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/8d/de8f3ea41403265a02d9277112ed69539081694fdcd364be0549f9793410/llama_parse-0.6.53-py3-none-any.whl", hash = "sha256:9cb2e60da433d05992b0a2ab2228348a1ec7812f321125c93e43a642fb979351", size = 4944 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878 }, +] + +[[package]] +name = "multidict" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843 }, + { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053 }, + { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273 }, + { url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124 }, + { url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892 }, + { url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547 }, + { url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223 }, + { url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262 }, + { url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345 }, + { url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248 }, + { url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115 }, + { url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649 }, + { url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203 }, + { url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051 }, + { url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601 }, + { url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683 }, + { url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811 }, + { url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056 }, + { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811 }, + { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304 }, + { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775 }, + { url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773 }, + { url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083 }, + { url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980 }, + { url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776 }, + { url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882 }, + { url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816 }, + { url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341 }, + { url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854 }, + { url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432 }, + { url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731 }, + { url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086 }, + { url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338 }, + { url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812 }, + { url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011 }, + { url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254 }, + { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313 }, +] + [[package]] name = "mypy" version = "1.17.0" @@ -619,6 +1215,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + +[[package]] +name = "networkx" +version = "3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406 }, +] + +[[package]] +name = "nltk" +version = "3.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "joblib" }, + { name = "regex" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/87/db8be88ad32c2d042420b6fd9ffd4a149f9a0d7f0e86b3f543be2eeeedd2/nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868", size = 2904691 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/66/7d9e26593edda06e8cb531874633f7c2372279c3b0f46235539fe546df8b/nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1", size = 1505442 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -628,6 +1257,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, ] +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074 }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311 }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022 }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135 }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147 }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989 }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052 }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955 }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843 }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876 }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786 }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395 }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374 }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864 }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533 }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007 }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914 }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708 }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678 }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832 }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049 }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935 }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906 }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607 }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110 }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050 }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292 }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913 }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180 }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809 }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410 }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821 }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303 }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524 }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519 }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972 }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439 }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479 }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805 }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830 }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665 }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777 }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856 }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226 }, +] + [[package]] name = "openai" version = "1.97.1" @@ -864,6 +1545,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -873,6 +1581,61 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328 }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652 }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443 }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474 }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038 }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094 }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503 }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574 }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060 }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407 }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841 }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450 }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055 }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110 }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547 }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554 }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132 }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001 }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814 }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124 }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186 }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546 }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102 }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803 }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520 }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116 }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597 }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246 }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699 }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789 }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386 }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911 }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383 }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385 }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129 }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580 }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860 }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694 }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888 }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089 }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206 }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370 }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500 }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835 }, +] + [[package]] name = "platformdirs" version = "4.3.8" @@ -913,6 +1676,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/72/0824c18f3bc75810f55dacc2dd933f6ec829771180245ae3cc976195dec0/prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9", size = 19296 }, ] +[[package]] +name = "propcache" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286 }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425 }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846 }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871 }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720 }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203 }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365 }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016 }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596 }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977 }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220 }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642 }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789 }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880 }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220 }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678 }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560 }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676 }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701 }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934 }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316 }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619 }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896 }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111 }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334 }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026 }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724 }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868 }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322 }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778 }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175 }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857 }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663 }, +] + [[package]] name = "protobuf" version = "6.31.1" @@ -1038,6 +1842,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] +[[package]] +name = "pypdf" +version = "5.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/3a/584b97a228950ed85aec97c811c68473d9b8d149e6a8c155668287cf1a28/pypdf-5.9.0.tar.gz", hash = "sha256:30f67a614d558e495e1fbb157ba58c1de91ffc1718f5e0dfeb82a029233890a1", size = 5035118 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/d9/6cff57c80a6963e7dd183bf09e9f21604a77716644b1e580e97b259f7612/pypdf-5.9.0-py3-none-any.whl", hash = "sha256:be10a4c54202f46d9daceaa8788be07aa8cd5ea8c25c529c50dd509206382c35", size = 313193 }, +] + [[package]] name = "pyright" version = "1.1.403" @@ -1093,6 +1906,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644 }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + [[package]] name = "python-dotenv" version = "1.1.1" @@ -1111,6 +1936,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, ] +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -1143,6 +1977,7 @@ dependencies = [ { name = "langfuse" }, { name = "langgraph" }, { name = "langgraph-checkpoint-postgres" }, + { name = "llama-index" }, { name = "mypy" }, { name = "opentelemetry-instrumentation-fastapi" }, { name = "prometheus-fastapi-instrumentator" }, @@ -1170,6 +2005,7 @@ requires-dist = [ { name = "langfuse", specifier = ">=3.1.3" }, { name = "langgraph", specifier = "==0.5.3" }, { name = "langgraph-checkpoint-postgres", specifier = ">=2.0.22" }, + { name = "llama-index", specifier = ">=0.12.52" }, { name = "mypy", specifier = ">=1.17.0" }, { name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.55b1" }, { name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" }, @@ -1260,6 +2096,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/db/c376b0661c24cf770cb8815268190668ec1330eba8374a126ceef8c72d55/ruff-0.12.5-py3-none-win_arm64.whl", hash = "sha256:48cdbfc633de2c5c37d9f090ba3b352d1576b0015bfc3bc98eaf230275b7e805", size = 11951564 }, ] +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -1269,6 +2123,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "soupsieve" +version = "2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 }, +] + [[package]] name = "sqlalchemy" version = "2.0.41" @@ -1290,6 +2153,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224 }, ] +[package.optional-dependencies] +asyncio = [ + { name = "greenlet" }, +] + [[package]] name = "sse-starlette" version = "2.4.1" @@ -1314,6 +2182,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984 }, ] +[[package]] +name = "striprtf" +version = "0.0.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/20/3d419008265346452d09e5dadfd5d045b64b40d8fc31af40588e6c76997a/striprtf-0.0.26.tar.gz", hash = "sha256:fdb2bba7ac440072d1c41eab50d8d74ae88f60a8b6575c6e2c7805dc462093aa", size = 6258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/cf/0fea4f4ba3fc2772ac2419278aa9f6964124d4302117d61bc055758e000c/striprtf-0.0.26-py3-none-any.whl", hash = "sha256:8c8f9d32083cdc2e8bfb149455aa1cc5a4e0a035893bedc75db8b73becb3a1bb", size = 6914 }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -1362,6 +2239,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827 }, +] + [[package]] name = "typing-inspection" version = "0.4.1" @@ -1459,6 +2349,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/ee/518b72faa2073f5aa8e3262408d284892cb79cf2754ba0c3a5870645ef73/xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b", size = 26801 }, ] +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811 }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078 }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748 }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595 }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616 }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324 }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676 }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614 }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766 }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615 }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982 }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792 }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049 }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774 }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252 }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198 }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346 }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826 }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217 }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700 }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644 }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452 }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378 }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261 }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987 }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361 }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460 }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486 }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219 }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693 }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803 }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709 }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591 }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003 }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542 }, +] + [[package]] name = "zipp" version = "3.23.0"