diff --git a/README.md b/README.md index be1a33b2..b7dcc337 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Retrieval-Augmented Generation (RAG) library on SQLite. - **Local SQLite**: No external servers required - **Multiple embedding providers**: Ollama, VoyageAI, OpenAI -- **Multiple QA providers**: Ollama, OpenAI, Anthropic +- **Multiple QA providers**: Any provider/model supported by Pydantic AI - **Hybrid search**: Vector + full-text search with Reciprocal Rank Fusion - **Reranking**: Default search result reranking with MixedBread AI or Cohere - **Question answering**: Built-in QA agents on your documents diff --git a/docs/configuration.md b/docs/configuration.md index 50fdd36d..b76d7ed7 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -44,13 +44,7 @@ VOYAGE_API_KEY="your-api-key" ``` ### OpenAI -If you want to use OpenAI embeddings you will need to install `haiku.rag` with the VoyageAI extras, - -```bash -uv pip install haiku.rag[openai] -``` - -and set environment variables. +OpenAI embeddings are included in the default installation. Simply set environment variables: ```bash EMBEDDINGS_PROVIDER="openai" @@ -61,48 +55,50 @@ OPENAI_API_KEY="your-api-key" ## Question Answering Providers -Configure which LLM provider to use for question answering. +Configure which LLM provider to use for question answering using the `provider:model` format. Any provider and model supported by [Pydantic AI](https://ai.pydantic.dev/models/) can be used. ### Ollama (Default) ```bash -QA_PROVIDER="ollama" -QA_MODEL="qwen3" +QA_PROVIDER="ollama:qwen3" OLLAMA_BASE_URL="http://localhost:11434" ``` ### OpenAI -For OpenAI QA, you need to install haiku.rag with OpenAI extras: +OpenAI QA is included in the default installation. Simply configure: ```bash -uv pip install haiku.rag[openai] -``` - -Then configure: - -```bash -QA_PROVIDER="openai" -QA_MODEL="gpt-4o-mini" # or gpt-4, gpt-3.5-turbo, etc. +QA_PROVIDER="openai:gpt-4o-mini" # or openai:gpt-4, openai:gpt-3.5-turbo, etc. OPENAI_API_KEY="your-api-key" ``` ### Anthropic -For Anthropic QA, you need to install haiku.rag with Anthropic extras: +Anthropic QA is included in the default installation. Simply configure: ```bash -uv pip install haiku.rag[anthropic] -``` - -Then configure: - -```bash -QA_PROVIDER="anthropic" -QA_MODEL="claude-3-5-haiku-20241022" # or claude-3-5-sonnet-20241022, etc. +QA_PROVIDER="anthropic:claude-3-5-haiku-20241022" # or anthropic:claude-3-5-sonnet-20241022, etc. ANTHROPIC_API_KEY="your-api-key" ``` +### Other Providers + +Any provider supported by Pydantic AI can be used. Examples include: + +```bash +# Google Gemini +QA_PROVIDER="gemini:gemini-1.5-flash" + +# Groq +QA_PROVIDER="groq:llama-3.3-70b-versatile" + +# Mistral +QA_PROVIDER="mistral:mistral-small-latest" +``` + +See the [Pydantic AI documentation](https://ai.pydantic.dev/models/) for the complete list of supported providers and models. + ## Reranking Reranking improves search quality by re-ordering the initial search results using specialized models. When enabled, the system retrieves more candidates (3x the requested limit) and then reranks them to return the most relevant results. @@ -144,13 +140,7 @@ RERANK_MODEL="mixedbread-ai/mxbai-rerank-base-v2" ### Cohere -For Cohere reranking, install with Cohere extras: - -```bash -uv pip install haiku.rag[cohere] -``` - -Then configure: +Cohere reranking is included in the default installation. Simply configure: ```bash RERANK_PROVIDER="cohere" diff --git a/docs/installation.md b/docs/installation.md index cd6c21c8..fd27e3a2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -6,11 +6,15 @@ uv pip install haiku.rag ``` -By default, Ollama (with the `mxbai-embed-large` model) is used for embeddings. +This includes support for: +- **Ollama** (default embedding provider using `mxbai-embed-large`) +- **OpenAI** (GPT models for QA and embeddings) +- **Anthropic** (Claude models for QA) +- **Cohere** (reranking models) ## Provider-Specific Installation -For other embedding providers, install with extras: +For additional embedding providers, install with extras: ### VoyageAI @@ -18,16 +22,10 @@ For other embedding providers, install with extras: uv pip install haiku.rag[voyageai] ``` -### OpenAI +### MixedBread AI Reranking ```bash -uv pip install haiku.rag[openai] -``` - -### Anthropic - -```bash -uv pip install haiku.rag[anthropic] +uv pip install haiku.rag[mxbai] ``` ## Requirements diff --git a/pyproject.toml b/pyproject.toml index 45a44e20..23c6b765 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "httpx>=0.28.1", "ollama>=0.5.3", "pydantic>=2.11.7", + "pydantic-ai>=0.7.2", "python-dotenv>=1.1.0", "rich>=14.0.0", "sqlite-vec>=0.1.6", @@ -37,9 +38,6 @@ dependencies = [ [project.optional-dependencies] voyageai = ["voyageai>=0.3.2"] -openai = ["openai>=1.0.0"] -anthropic = ["anthropic>=0.56.0"] -cohere = ["cohere>=5.16.1"] mxbai = ["mxbai-rerank>=0.1.6"] [project.scripts] diff --git a/src/haiku/rag/config.py b/src/haiku/rag/config.py index 78328fe6..478cf6e2 100644 --- a/src/haiku/rag/config.py +++ b/src/haiku/rag/config.py @@ -22,8 +22,7 @@ class AppConfig(BaseModel): RERANK_PROVIDER: str = "ollama" RERANK_MODEL: str = "qwen3" - QA_PROVIDER: str = "ollama" - QA_MODEL: str = "qwen3" + QA_PROVIDER: str = "ollama:qwen3" CHUNK_SIZE: int = 256 CONTEXT_CHUNK_RADIUS: int = 0 diff --git a/src/haiku/rag/embeddings/__init__.py b/src/haiku/rag/embeddings/__init__.py index bb371bd1..c463e2ee 100644 --- a/src/haiku/rag/embeddings/__init__.py +++ b/src/haiku/rag/embeddings/__init__.py @@ -17,20 +17,14 @@ def get_embedder() -> EmbedderBase: except ImportError: raise ImportError( "VoyageAI embedder requires the 'voyageai' package. " - "Please install haiku.rag with the 'voyageai' extra:" + "Please install haiku.rag with the 'voyageai' extra: " "uv pip install haiku.rag[voyageai]" ) return VoyageAIEmbedder(Config.EMBEDDINGS_MODEL, Config.EMBEDDINGS_VECTOR_DIM) if Config.EMBEDDINGS_PROVIDER == "openai": - try: - from haiku.rag.embeddings.openai import Embedder as OpenAIEmbedder - except ImportError: - raise ImportError( - "OpenAI embedder requires the 'openai' package. " - "Please install haiku.rag with the 'openai' extra:" - "uv pip install haiku.rag[openai]" - ) + from haiku.rag.embeddings.openai import Embedder as OpenAIEmbedder + return OpenAIEmbedder(Config.EMBEDDINGS_MODEL, Config.EMBEDDINGS_VECTOR_DIM) raise ValueError(f"Unsupported embedding provider: {Config.EMBEDDINGS_PROVIDER}") diff --git a/src/haiku/rag/embeddings/openai.py b/src/haiku/rag/embeddings/openai.py index 818f0e5b..485c97fe 100644 --- a/src/haiku/rag/embeddings/openai.py +++ b/src/haiku/rag/embeddings/openai.py @@ -1,16 +1,13 @@ -try: - from openai import AsyncOpenAI +from openai import AsyncOpenAI - from haiku.rag.embeddings.base import EmbedderBase +from haiku.rag.embeddings.base import EmbedderBase - class Embedder(EmbedderBase): - async def embed(self, text: str) -> list[float]: - client = AsyncOpenAI() - response = await client.embeddings.create( - model=self._model, - input=text, - ) - return response.data[0].embedding -except ImportError: - pass +class Embedder(EmbedderBase): + async def embed(self, text: str) -> list[float]: + client = AsyncOpenAI() + response = await client.embeddings.create( + model=self._model, + input=text, + ) + return response.data[0].embedding diff --git a/src/haiku/rag/qa/__init__.py b/src/haiku/rag/qa/__init__.py index 0d578cd6..d401597b 100644 --- a/src/haiku/rag/qa/__init__.py +++ b/src/haiku/rag/qa/__init__.py @@ -1,44 +1,13 @@ from haiku.rag.client import HaikuRAG from haiku.rag.config import Config -from haiku.rag.qa.base import QuestionAnswerAgentBase -from haiku.rag.qa.ollama import QuestionAnswerOllamaAgent +from haiku.rag.qa.agent import QuestionAnswerAgent -def get_qa_agent( - client: HaikuRAG, model: str = "", use_citations: bool = False -) -> QuestionAnswerAgentBase: - """ - Factory function to get the appropriate QA agent based on the configuration. - """ - if Config.QA_PROVIDER == "ollama": - return QuestionAnswerOllamaAgent( - client, model or Config.QA_MODEL, use_citations - ) +def get_qa_agent(client: HaikuRAG, use_citations: bool = False) -> QuestionAnswerAgent: + provider_model = Config.QA_PROVIDER - if Config.QA_PROVIDER == "openai": - try: - from haiku.rag.qa.openai import QuestionAnswerOpenAIAgent - except ImportError: - raise ImportError( - "OpenAI QA agent requires the 'openai' package. " - "Please install haiku.rag with the 'openai' extra:" - "uv pip install haiku.rag[openai]" - ) - return QuestionAnswerOpenAIAgent( - client, model or Config.QA_MODEL, use_citations - ) - - if Config.QA_PROVIDER == "anthropic": - try: - from haiku.rag.qa.anthropic import QuestionAnswerAnthropicAgent - except ImportError: - raise ImportError( - "Anthropic QA agent requires the 'anthropic' package. " - "Please install haiku.rag with the 'anthropic' extra:" - "uv pip install haiku.rag[anthropic]" - ) - return QuestionAnswerAnthropicAgent( - client, model or Config.QA_MODEL, use_citations - ) - - raise ValueError(f"Unsupported QA provider: {Config.QA_PROVIDER}") + return QuestionAnswerAgent( + client=client, + provider_model=provider_model, + use_citations=use_citations, + ) diff --git a/src/haiku/rag/qa/agent.py b/src/haiku/rag/qa/agent.py new file mode 100644 index 00000000..bb0028fa --- /dev/null +++ b/src/haiku/rag/qa/agent.py @@ -0,0 +1,80 @@ +from pydantic import BaseModel, Field +from pydantic_ai import Agent, RunContext +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.providers.ollama import OllamaProvider + +from haiku.rag.client import HaikuRAG +from haiku.rag.config import Config +from haiku.rag.qa.prompts import SYSTEM_PROMPT, SYSTEM_PROMPT_WITH_CITATIONS + + +class SearchResult(BaseModel): + content: str = Field(description="The document text content") + score: float = Field(description="Relevance score (higher is more relevant)") + document_uri: str = Field(description="Source URI/path of the document") + + +class Dependencies(BaseModel): + model_config = {"arbitrary_types_allowed": True} + client: HaikuRAG + + +class QuestionAnswerAgent: + def __init__( + self, + client: HaikuRAG, + provider_model: str, + use_citations: bool = False, + q: float = 0.0, + ): + self._client = client + + system_prompt = SYSTEM_PROMPT_WITH_CITATIONS if use_citations else SYSTEM_PROMPT + model_obj = self._get_model(provider_model) + + self._agent = Agent( + model=model_obj, + deps_type=Dependencies, + system_prompt=system_prompt, + ) + + @self._agent.tool + async def search_documents( + ctx: RunContext[Dependencies], + query: str, + limit: int = 3, + ) -> list[SearchResult]: + """Search the knowledge base for relevant documents.""" + search_results = await ctx.deps.client.search(query, limit=limit) + expanded_results = await ctx.deps.client.expand_context(search_results) + + return [ + SearchResult( + content=chunk.content, + score=score, + document_uri=chunk.document_uri or "", + ) + for chunk, score in expanded_results + ] + + def _get_model(self, provider_model: str): + """Get the appropriate model object for the provider:model format.""" + if ":" not in provider_model: + raise ValueError(f"Invalid provider:model format: {provider_model}") + + provider, model = provider_model.split(":", 1) + + if provider == "ollama": + return OpenAIModel( + model_name=model, + provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"), + ) + else: + # For other providers, use the provider:model string directly + return provider_model + + async def answer(self, question: str) -> str: + """Answer a question using the RAG system.""" + deps = Dependencies(client=self._client) + result = await self._agent.run(question, deps=deps) + return result.output diff --git a/src/haiku/rag/qa/anthropic.py b/src/haiku/rag/qa/anthropic.py deleted file mode 100644 index c3138e1f..00000000 --- a/src/haiku/rag/qa/anthropic.py +++ /dev/null @@ -1,108 +0,0 @@ -from collections.abc import Sequence - -try: - from anthropic import AsyncAnthropic # type: ignore - from anthropic.types import ( # type: ignore - MessageParam, - TextBlock, - ToolParam, - ToolUseBlock, - ) - - from haiku.rag.client import HaikuRAG - from haiku.rag.qa.base import QuestionAnswerAgentBase - - class QuestionAnswerAnthropicAgent(QuestionAnswerAgentBase): - def __init__( - self, - client: HaikuRAG, - model: str = "claude-3-5-haiku-20241022", - use_citations: bool = False, - ): - super().__init__(client, model or self._model, use_citations) - self.tools: Sequence[ToolParam] = [ - ToolParam( - name="search_documents", - description="Search the knowledge base for relevant documents. Returns a JSON array with content, score, and document_uri for each result.", - input_schema={ - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The search query to find relevant documents", - }, - "limit": { - "type": "integer", - "description": "Maximum number of results to return", - "default": 3, - }, - }, - "required": ["query"], - }, - ) - ] - - async def answer(self, question: str) -> str: - anthropic_client = AsyncAnthropic() - - messages: list[MessageParam] = [{"role": "user", "content": question}] - - max_rounds = 5 # Prevent infinite loops - - for _ in range(max_rounds): - response = await anthropic_client.messages.create( - model=self._model, - max_tokens=4096, - system=self._system_prompt, - messages=messages, - tools=self.tools, - temperature=0.0, - ) - - if response.stop_reason == "tool_use": - messages.append({"role": "assistant", "content": response.content}) - - # Process tool calls - tool_results = [] - for content_block in response.content: - if isinstance(content_block, ToolUseBlock): - if content_block.name == "search_documents": - args = content_block.input - query = ( - args.get("query", question) - if isinstance(args, dict) - else question - ) - limit = ( - int(args.get("limit", 3)) - if isinstance(args, dict) - else 3 - ) - - context = await self._search_and_expand( - query, limit=limit - ) - - tool_results.append( - { - "type": "tool_result", - "tool_use_id": content_block.id, - "content": context, - } - ) - - if tool_results: - messages.append({"role": "user", "content": tool_results}) - else: - # No tool use, return the response - if response.content: - first_content = response.content[0] - if isinstance(first_content, TextBlock): - return first_content.text - return "" - - # If we've exhausted max rounds, return empty string - return "" - -except ImportError: - pass diff --git a/src/haiku/rag/qa/base.py b/src/haiku/rag/qa/base.py deleted file mode 100644 index 92a53060..00000000 --- a/src/haiku/rag/qa/base.py +++ /dev/null @@ -1,89 +0,0 @@ -import json - -from haiku.rag.client import HaikuRAG -from haiku.rag.qa.prompts import SYSTEM_PROMPT, SYSTEM_PROMPT_WITH_CITATIONS - - -class QuestionAnswerAgentBase: - _model: str = "" - _system_prompt: str = SYSTEM_PROMPT - - def __init__(self, client: HaikuRAG, model: str = "", use_citations: bool = False): - self._model = model - self._client = client - self._system_prompt = ( - SYSTEM_PROMPT_WITH_CITATIONS if use_citations else SYSTEM_PROMPT - ) - - async def answer(self, question: str) -> str: - raise NotImplementedError( - "QABase is an abstract class. Please implement the answer method in a subclass." - ) - - async def _search_and_expand(self, query: str, limit: int = 3) -> str: - """Search for documents and expand context, then format as JSON""" - search_results = await self._client.search(query, limit=limit) - expanded_results = await self._client.expand_context(search_results) - return self._format_search_results(expanded_results) - - def _format_search_results(self, search_results) -> str: - """Format search results as JSON list of {content, score, document_uri}""" - formatted_results = [] - for chunk, score in search_results: - formatted_results.append( - { - "content": chunk.content, - "score": score, - "document_uri": chunk.document_uri, - } - ) - return json.dumps(formatted_results, indent=2) - - tools = [ - { - "type": "function", - "function": { - "name": "search_documents", - "description": "Search the knowledge base for relevant documents. Returns a JSON array of search results.", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The search query to find relevant documents", - }, - "limit": { - "type": "integer", - "description": "Maximum number of results to return", - "default": 3, - }, - }, - "required": ["query"], - }, - "returns": { - "type": "string", - "description": "JSON array of search results", - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "The document text content", - }, - "score": { - "type": "number", - "description": "Relevance score (higher is more relevant)", - }, - "document_uri": { - "type": "string", - "description": "Source URI/path of the document", - }, - }, - }, - }, - }, - }, - } - ] diff --git a/src/haiku/rag/qa/ollama.py b/src/haiku/rag/qa/ollama.py deleted file mode 100644 index 6b363542..00000000 --- a/src/haiku/rag/qa/ollama.py +++ /dev/null @@ -1,60 +0,0 @@ -from ollama import AsyncClient - -from haiku.rag.client import HaikuRAG -from haiku.rag.config import Config -from haiku.rag.qa.base import QuestionAnswerAgentBase - -OLLAMA_OPTIONS = {"temperature": 0.0, "seed": 42, "num_ctx": 16384} - - -class QuestionAnswerOllamaAgent(QuestionAnswerAgentBase): - def __init__( - self, - client: HaikuRAG, - model: str = Config.QA_MODEL, - use_citations: bool = False, - ): - super().__init__(client, model or self._model, use_citations) - - async def answer(self, question: str) -> str: - ollama_client = AsyncClient(host=Config.OLLAMA_BASE_URL) - - messages = [ - {"role": "system", "content": self._system_prompt}, - {"role": "user", "content": question}, - ] - - max_rounds = 5 # Prevent infinite loops - - for _ in range(max_rounds): - response = await ollama_client.chat( - model=self._model, - messages=messages, - tools=self.tools, - options=OLLAMA_OPTIONS, - think=False, - ) - - if response.get("message", {}).get("tool_calls"): - messages.append(response["message"]) - - for tool_call in response["message"]["tool_calls"]: - if tool_call["function"]["name"] == "search_documents": - args = tool_call["function"]["arguments"] - query = args.get("query", question) - limit = int(args.get("limit", 3)) - - context = await self._search_and_expand(query, limit=limit) - messages.append( - { - "role": "tool", - "content": context, - "tool_call_id": tool_call.get("id", "search_tool"), - } - ) - else: - # No tool calls, return the response - return response["message"]["content"] - - # If we've exhausted max rounds, return empty string - return "" diff --git a/src/haiku/rag/qa/openai.py b/src/haiku/rag/qa/openai.py deleted file mode 100644 index 0ab45793..00000000 --- a/src/haiku/rag/qa/openai.py +++ /dev/null @@ -1,97 +0,0 @@ -from collections.abc import Sequence - -try: - from openai import AsyncOpenAI # type: ignore - from openai.types.chat import ( # type: ignore - ChatCompletionAssistantMessageParam, - ChatCompletionMessageParam, - ChatCompletionSystemMessageParam, - ChatCompletionToolMessageParam, - ChatCompletionUserMessageParam, - ) - from openai.types.chat.chat_completion_tool_param import ( # type: ignore - ChatCompletionToolParam, - ) - - from haiku.rag.client import HaikuRAG - from haiku.rag.qa.base import QuestionAnswerAgentBase - - class QuestionAnswerOpenAIAgent(QuestionAnswerAgentBase): - def __init__( - self, - client: HaikuRAG, - model: str = "gpt-4o-mini", - use_citations: bool = False, - ): - super().__init__(client, model or self._model, use_citations) - self.tools: Sequence[ChatCompletionToolParam] = [ - ChatCompletionToolParam(tool) for tool in self.tools - ] - - async def answer(self, question: str) -> str: - openai_client = AsyncOpenAI() - - messages: list[ChatCompletionMessageParam] = [ - ChatCompletionSystemMessageParam( - role="system", content=self._system_prompt - ), - ChatCompletionUserMessageParam(role="user", content=question), - ] - - max_rounds = 5 # Prevent infinite loops - - for _ in range(max_rounds): - response = await openai_client.chat.completions.create( - model=self._model, - messages=messages, - tools=self.tools, - temperature=0.0, - ) - - response_message = response.choices[0].message - - if response_message.tool_calls: - messages.append( - ChatCompletionAssistantMessageParam( - role="assistant", - content=response_message.content, - tool_calls=[ - { - "id": tc.id, - "type": "function", - "function": { - "name": tc.function.name, - "arguments": tc.function.arguments, - }, - } - for tc in response_message.tool_calls - ], - ) - ) - - for tool_call in response_message.tool_calls: - if tool_call.function.name == "search_documents": - import json - - args = json.loads(tool_call.function.arguments) - query = args.get("query", question) - limit = int(args.get("limit", 3)) - - context = await self._search_and_expand(query, limit=limit) - - messages.append( - ChatCompletionToolMessageParam( - role="tool", - content=context, - tool_call_id=tool_call.id, - ) - ) - else: - # No tool calls, return the response - return response_message.content or "" - - # If we've exhausted max rounds, return empty string - return "" - -except ImportError: - pass diff --git a/src/haiku/rag/qa/prompts.py b/src/haiku/rag/qa/prompts.py index d55131be..f865b09f 100644 --- a/src/haiku/rag/qa/prompts.py +++ b/src/haiku/rag/qa/prompts.py @@ -18,6 +18,7 @@ Guidelines: - Stick to the answer, do not ellaborate or provide context unless explicitly asked for it. Be concise, and always maintain accuracy over completeness. Prefer short, direct answers that are well-supported by the documents. +/no_think """ SYSTEM_PROMPT_WITH_CITATIONS = """ @@ -55,4 +56,5 @@ Citations: - /path/to/document2.pdf: "The manual provides guidance on military procedures and..." Be concise, and always maintain accuracy over completeness. Prefer short, direct answers that are well-supported by the documents. +/no_think """ diff --git a/src/haiku/rag/reranking/ollama.py b/src/haiku/rag/reranking/ollama.py index 727c546b..9acba3dd 100644 --- a/src/haiku/rag/reranking/ollama.py +++ b/src/haiku/rag/reranking/ollama.py @@ -1,14 +1,12 @@ -import json - -from ollama import AsyncClient from pydantic import BaseModel +from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.providers.ollama import OllamaProvider from haiku.rag.config import Config from haiku.rag.reranking.base import RerankerBase from haiku.rag.store.models.chunk import Chunk -OLLAMA_OPTIONS = {"temperature": 0.0, "seed": 42, "num_ctx": 16384} - class RerankResult(BaseModel): """Individual rerank result with index and relevance score.""" @@ -26,7 +24,28 @@ class RerankResponse(BaseModel): class OllamaReranker(RerankerBase): def __init__(self, model: str = Config.RERANK_MODEL): self._model = model - self._client = AsyncClient(host=Config.OLLAMA_BASE_URL) + + # Create the reranking prompt + system_prompt = """You are a document reranking assistant. Given a query and a list of document chunks, you must rank them by relevance to the query. + +Return your response as a JSON object with a "results" array. Each result should have: +- "index": the original index of the document (integer) +- "relevance_score": a score between 0.0 and 1.0 indicating relevance (float, where 1.0 is most relevant) + +Only return the top documents up to the requested limit, ordered by decreasing relevance score. +/no_think +""" + + model_obj = OpenAIModel( + model_name=model, + provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"), + ) + + self._agent = Agent( + model=model_obj, + output_type=RerankResponse, + system_prompt=system_prompt, + ) async def rerank( self, query: str, chunks: list[Chunk], top_n: int = 10 @@ -38,15 +57,6 @@ class OllamaReranker(RerankerBase): for i, chunk in enumerate(chunks): documents.append({"index": i, "content": chunk.content}) - # Create the prompt for reranking - system_prompt = """You are a document reranking assistant. Given a query and a list of document chunks, you must rank them by relevance to the query. - -Return your response as a JSON object with a "results" array. Each result should have: -- "index": the original index of the document (integer) -- "relevance_score": a score between 0.0 and 1.0 indicating relevance (float, where 1.0 is most relevant) - -Only return the top documents up to the requested limit, ordered by decreasing relevance score.""" - documents_text = "" for doc in documents: documents_text += f"Index {doc['index']}: {doc['content']}\n\n" @@ -56,27 +66,14 @@ Only return the top documents up to the requested limit, ordered by decreasing r Documents to rerank: {documents_text.strip()} -Please rank these documents by relevance to the query and return the top {top_n} results as JSON.""" - - messages = [ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": user_prompt}, - ] +Rank these documents by relevance to the query and return the top {top_n} results as JSON.""" try: - response = await self._client.chat( - model=self._model, - messages=messages, - format=RerankResponse.model_json_schema(), - options=OLLAMA_OPTIONS, - ) + result = await self._agent.run(user_prompt) - content = response["message"]["content"] - - parsed_response = RerankResponse.model_validate(json.loads(content)) return [ - (chunks[result.index], result.relevance_score) - for result in parsed_response.results[:top_n] + (chunks[result_item.index], result_item.relevance_score) + for result_item in result.output.results[:top_n] ] except Exception: diff --git a/tests/llm_judge.py b/tests/llm_judge.py index 648c8b72..f9233e6b 100644 --- a/tests/llm_judge.py +++ b/tests/llm_judge.py @@ -1,21 +1,67 @@ -import json - -from ollama import AsyncClient from pydantic import BaseModel +from pydantic_ai import Agent +from pydantic_ai.models.openai import OpenAIModel +from pydantic_ai.providers.ollama import OllamaProvider from haiku.rag.config import Config +# Shared rubric/prompt for answer equivalence evaluation +ANSWER_EQUIVALENCE_RUBRIC = """You are evaluating whether two answers to the same question are semantically equivalent. + +EVALUATION CRITERIA: +Rate as EQUIVALENT if: +✓ Both answers contain the same core factual information +✓ Both directly address the question asked +✓ The key claims and conclusions are consistent +✓ Any additional detail in one answer doesn't contradict the other + +Rate as NOT EQUIVALENT if: +✗ Factual contradictions exist between the answers +✗ One answer fails to address the core question +✗ Key information is missing that changes the meaning +✗ The answers lead to different conclusions or implications + +GUIDELINES: +- Ignore minor differences in phrasing, style, or formatting +- Focus on semantic meaning rather than exact wording +- Consider both answers correct if they convey the same essential information +- Be tolerant of different levels of detail if the core answer is preserved +- Evaluate based on what a person asking this question would need to know +/no_think""" + class LLMJudgeResponseSchema(BaseModel): equivalent: bool class LLMJudge: - """LLM-as-judge for evaluating answer equivalence using Ollama.""" + """LLM-as-judge for evaluating answer equivalence using Pydantic AI.""" - def __init__(self, model: str = Config.QA_MODEL): - self.model = model - self.client = AsyncClient(host=Config.OLLAMA_BASE_URL) + def __init__(self, provider_model: str = Config.QA_PROVIDER): + self.provider_model = provider_model + + # Parse provider:model format + if ":" not in provider_model: + raise ValueError(f"Invalid provider:model format: {provider_model}") + + provider, model = provider_model.split(":", 1) + + if provider == "ollama": + # Create Ollama model + ollama_model = OpenAIModel( + model_name=model, + provider=OllamaProvider(base_url=f"{Config.OLLAMA_BASE_URL}/v1"), + ) + else: + # For other providers, use the provider:model string directly + ollama_model = provider_model + + # Create Pydantic AI agent + self._agent = Agent( + model=ollama_model, + output_type=LLMJudgeResponseSchema, + system_prompt=ANSWER_EQUIVALENCE_RUBRIC, + ) async def judge_answers( self, question: str, answer: str, expected_answer: str @@ -29,53 +75,14 @@ class LLMJudge: expected_answer: The reference/expected answer Returns: - Dictionary with judgment result: - - equivalent: bool indicating if answers are equivalent - - explanation: str explaining the reasoning - - score: str rating from 1-5 + bool indicating if answers are equivalent """ - prompt = f"""You are an expert evaluator determining whether two answers to the same question are semantically equivalent. - -QUESTION: {question} + prompt = f"""QUESTION: {question} GENERATED ANSWER: {answer} -EXPECTED ANSWER: {expected_answer} +EXPECTED ANSWER: {expected_answer}""" -EVALUATION CRITERIA: -Rate as EQUIVALENT (true) if: -✓ Both answers contain the same core factual information -✓ Both directly address the question asked -✓ The key claims and conclusions are consistent -✓ Any additional detail in one answer doesn't contradict the other - -Rate as NOT EQUIVALENT (false) if: -✗ Factual contradictions exist between the answers -✗ One answer fails to address the core question -✗ Key information is missing from one answer that changes the meaning -✗ The answers lead to different conclusions or implications - -GUIDELINES: -- Ignore minor differences in phrasing, style, or formatting -- Focus on semantic meaning rather than exact wording -- Consider both answers correct if they convey the same essential information -- Be tolerant of different levels of detail if the core answer is preserved -- Evaluate based on what a person asking this question would need to know - -Respond with JSON containing only: {{"equivalent": true}} or {{"equivalent": false}}""" - - response = await self.client.chat( - model=self.model, - messages=[{"role": "user", "content": prompt}], - format=LLMJudgeResponseSchema.model_json_schema(), - think=False, - ) - - answer = response["message"]["content"].strip() - try: - res = json.loads(answer) - assert "equivalent" in res, "Response must contain 'equivalent' key" - return res["equivalent"] - except json.JSONDecodeError: - assert False, "Response is not valid JSON" + result = await self._agent.run(prompt) + return result.output.equivalent diff --git a/tests/test_embedder.py b/tests/test_embedder.py index f6421631..7d227b1c 100644 --- a/tests/test_embedder.py +++ b/tests/test_embedder.py @@ -1,19 +1,26 @@ import numpy as np import pytest -from haiku.rag.embeddings import get_embedder +from haiku.rag.config import Config +from haiku.rag.embeddings.ollama import Embedder as OllamaEmbedder +from haiku.rag.embeddings.openai import Embedder as OpenAIEmbedder + +OPENAI_AVAILABLE = bool(Config.OPENAI_API_KEY) +VOYAGEAI_AVAILABLE = bool(Config.VOYAGE_API_KEY) + + +# Calculate cosine similarity +def similarities(embeddings, test_embedding): + return [ + np.dot(embedding, test_embedding) + / (np.linalg.norm(embedding) * np.linalg.norm(test_embedding)) + for embedding in embeddings + ] @pytest.mark.asyncio -async def test_embedder(): - embedder = get_embedder() - embedding = await embedder.embed("hello world") - assert len(embedding) == embedder._vector_dim - - -@pytest.mark.asyncio -async def test_similarity(): - embedder = get_embedder() +async def test_ollama_embedder(): + embedder = OllamaEmbedder("mxbai-embed-large", 1024) phrases = [ "I enjoy eating great food.", "Python is my favorite programming language.", @@ -21,14 +28,6 @@ async def test_similarity(): ] embeddings = [np.array(await embedder.embed(phrase)) for phrase in phrases] - # Calculate cosine similarity - def similarities(embeddings, test_embedding): - return [ - np.dot(embedding, test_embedding) - / (np.linalg.norm(embedding) * np.linalg.norm(test_embedding)) - for embedding in embeddings - ] - test_phrase = "I am going for a camping trip." test_embedding = await embedder.embed(test_phrase) @@ -49,80 +48,66 @@ async def test_similarity(): @pytest.mark.asyncio -async def test_openai_embedder(monkeypatch): - monkeypatch.setenv("EMBEDDINGS_PROVIDER", "openai") - monkeypatch.setenv("EMBEDDINGS_MODEL", "text-embedding-3-small") +@pytest.mark.skipif(not OPENAI_AVAILABLE, reason="OpenAI API key not available") +async def test_openai_embedder(): + embedder = OpenAIEmbedder("text-embedding-3-small", 1536) + phrases = [ + "I enjoy eating great food.", + "Python is my favorite programming language.", + "I love to travel and see new places.", + ] + embeddings = [np.array(await embedder.embed(phrase)) for phrase in phrases] - try: - from haiku.rag.embeddings.openai import Embedder as OpenAIEmbedder + test_phrase = "I am going for a camping trip." + test_embedding = await embedder.embed(test_phrase) - embedder = OpenAIEmbedder("text-embedding-3-small", 1536) + sims = similarities(embeddings, test_embedding) + assert max(sims) == sims[2] - # Mock the OpenAI client - class MockEmbeddingData: - def __init__(self, embedding): - self.embedding = embedding + test_phrase = "When is dinner ready?" + test_embedding = await embedder.embed(test_phrase) - class MockResponse: - def __init__(self, embedding): - self.data = [MockEmbeddingData(embedding)] + sims = similarities(embeddings, test_embedding) + assert max(sims) == sims[0] - class MockAsyncOpenAI: - class MockEmbeddings: - async def create(self, model, input): - return MockResponse([0.1] * 1536) + test_phrase = "I work as a software developer." + test_embedding = await embedder.embed(test_phrase) - def __init__(self): - self.embeddings = self.MockEmbeddings() - - # Patch the AsyncOpenAI import - import haiku.rag.embeddings.openai - - original_client = haiku.rag.embeddings.openai.AsyncOpenAI - haiku.rag.embeddings.openai.AsyncOpenAI = MockAsyncOpenAI - - try: - embedding = await embedder.embed("test text") - assert len(embedding) == 1536 - assert all(isinstance(x, float) for x in embedding) - finally: - haiku.rag.embeddings.openai.AsyncOpenAI = original_client - - except ImportError: - pytest.skip("OpenAI package not installed") + sims = similarities(embeddings, test_embedding) + assert max(sims) == sims[1] @pytest.mark.asyncio -async def test_voyageai_embedder(monkeypatch): - monkeypatch.setenv("EMBEDDINGS_PROVIDER", "voyageai") - monkeypatch.setenv("EMBEDDINGS_MODEL", "voyage-3.5") - +@pytest.mark.skipif(not VOYAGEAI_AVAILABLE, reason="VoyageAI API key not available") +async def test_voyageai_embedder(): try: from haiku.rag.embeddings.voyageai import Embedder as VoyageAIEmbedder embedder = VoyageAIEmbedder("voyage-3.5", 1024) + phrases = [ + "I enjoy eating great food.", + "Python is my favorite programming language.", + "I love to travel and see new places.", + ] + embeddings = [np.array(await embedder.embed(phrase)) for phrase in phrases] - # Mock the VoyageAI client - class MockEmbeddings: - def __init__(self, embeddings): - self.embeddings = embeddings + test_phrase = "I am going for a camping trip." + test_embedding = await embedder.embed(test_phrase) - class MockClient: - def embed(self, texts, model, output_dtype): - return MockEmbeddings([[0.1] * 1024]) + sims = similarities(embeddings, test_embedding) + assert max(sims) == sims[2] - # Patch the Client import - import haiku.rag.embeddings.voyageai + test_phrase = "When is dinner ready?" + test_embedding = await embedder.embed(test_phrase) - original_client = haiku.rag.embeddings.voyageai.Client - haiku.rag.embeddings.voyageai.Client = MockClient + sims = similarities(embeddings, test_embedding) + assert max(sims) == sims[0] - try: - embedding = await embedder.embed("test text") - assert len(embedding) == 1024 - assert all(isinstance(x, float) for x in embedding) - finally: - haiku.rag.embeddings.voyageai.Client = original_client + test_phrase = "I work as a software developer." + test_embedding = await embedder.embed(test_phrase) + + sims = similarities(embeddings, test_embedding) + assert max(sims) == sims[1] except ImportError: pytest.skip("VoyageAI package not installed") diff --git a/tests/test_qa.py b/tests/test_qa.py index 686fb53f..49348fdf 100644 --- a/tests/test_qa.py +++ b/tests/test_qa.py @@ -2,32 +2,20 @@ import pytest from datasets import Dataset from haiku.rag.client import HaikuRAG -from haiku.rag.qa.ollama import QuestionAnswerOllamaAgent - -try: - from haiku.rag.qa.openai import QuestionAnswerOpenAIAgent - - OPENAI_AVAILABLE = True -except ImportError: - QuestionAnswerOpenAIAgent = None - OPENAI_AVAILABLE = False - -try: - from haiku.rag.qa.anthropic import QuestionAnswerAnthropicAgent - - ANTHROPIC_AVAILABLE = True -except ImportError: - QuestionAnswerAnthropicAgent = None - ANTHROPIC_AVAILABLE = False +from haiku.rag.config import Config +from haiku.rag.qa.agent import QuestionAnswerAgent from .llm_judge import LLMJudge +OPENAI_AVAILABLE = bool(Config.OPENAI_API_KEY) +ANTHROPIC_AVAILABLE = bool(Config.ANTHROPIC_API_KEY) + @pytest.mark.asyncio async def test_qa_ollama(qa_corpus: Dataset): - """Test QA with actual question from the dataset using LLM judge.""" + """Test Ollama QA with LLM judge.""" client = HaikuRAG(":memory:") - qa = QuestionAnswerOllamaAgent(client) + qa = QuestionAnswerAgent(client, provider_model="ollama:qwen3") llm_judge = LLMJudge() doc = qa_corpus[1] @@ -49,9 +37,9 @@ async def test_qa_ollama(qa_corpus: Dataset): @pytest.mark.asyncio @pytest.mark.skipif(not OPENAI_AVAILABLE, reason="OpenAI not available") async def test_qa_openai(qa_corpus: Dataset): - """Test OpenAI QA basic functionality.""" + """Test OpenAI QA with LLM judge.""" client = HaikuRAG(":memory:") - qa = QuestionAnswerOpenAIAgent(client) # type: ignore + qa = QuestionAnswerAgent(client, provider_model="openai:gpt-4o-mini") llm_judge = LLMJudge() doc = qa_corpus[1] @@ -73,9 +61,11 @@ async def test_qa_openai(qa_corpus: Dataset): @pytest.mark.asyncio @pytest.mark.skipif(not ANTHROPIC_AVAILABLE, reason="Anthropic not available") async def test_qa_anthropic(qa_corpus: Dataset): - """Test Anthropic QA basic functionality.""" + """Test Anthropic QA with LLM judge.""" client = HaikuRAG(":memory:") - qa = QuestionAnswerAnthropicAgent(client) # type: ignore + qa = QuestionAnswerAgent( + client, provider_model="anthropic:claude-3-5-haiku-20241022" + ) llm_judge = LLMJudge() doc = qa_corpus[1] diff --git a/tests/test_reranker.py b/tests/test_reranker.py index 5ce13156..7a86209d 100644 --- a/tests/test_reranker.py +++ b/tests/test_reranker.py @@ -1,8 +1,11 @@ import pytest +from haiku.rag.config import Config from haiku.rag.reranking.base import RerankerBase from haiku.rag.store.models.chunk import Chunk +COHERE_AVAILABLE = bool(Config.COHERE_API_KEY) + chunks = [ Chunk(content=content, document_id=i) for i, content in enumerate( @@ -43,6 +46,7 @@ async def test_mxbai_reranker(): @pytest.mark.asyncio +@pytest.mark.skipif(not COHERE_AVAILABLE, reason="Cohere API key not available") async def test_cohere_reranker(): try: from haiku.rag.reranking.cohere import CohereReranker diff --git a/uv.lock b/uv.lock index 79c63b8f..b267b317 100644 --- a/uv.lock +++ b/uv.lock @@ -28,6 +28,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/1c/a17fb513aeb684fb83bef5f395910f53103ab30308bbdd77fd66d6698c46/accelerate-1.9.0-py3-none-any.whl", hash = "sha256:c24739a97ade1d54af4549a65f8b6b046adc87e2b3e4d6c66516e32c53d5a8f1", size = 367073, upload-time = "2025-07-16T16:24:52.957Z" }, ] +[[package]] +name = "ag-ui-protocol" +version = "0.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/de/0bddf7f26d5f38274c99401735c82ad59df9cead6de42f4bb2ad837286fe/ag_ui_protocol-0.1.8.tar.gz", hash = "sha256:eb745855e9fc30964c77e953890092f8bd7d4bbe6550d6413845428dd0faac0b", size = 5323, upload-time = "2025-07-15T10:55:36.389Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/00/40c6b0313c25d1ab6fac2ecba1cd5b15b1cd3c3a71b3d267ad890e405889/ag_ui_protocol-0.1.8-py3-none-any.whl", hash = "sha256:1567ccb067b7b8158035b941a985e7bb185172d660d4542f3f9c6fff77b55c6e", size = 7066, upload-time = "2025-07-15T10:55:35.075Z" }, +] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -137,7 +149,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.56.0" +version = "0.63.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -148,9 +160,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/40/0c4eb5728466849803782c8a86eb315af1a6eb0efea6a751de120ab845c9/anthropic-0.56.0.tar.gz", hash = "sha256:56fa9eb61afa004a1664bc85eed071e77b96c579b77395e9cc893097e599f72e", size = 421538, upload-time = "2025-07-01T19:39:10.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/6e/3bedbd1c932cce98495e007b6d8007a139cf46adc5c889d700ec75ddd7f3/anthropic-0.63.0.tar.gz", hash = "sha256:d75ecfff17a0b96d845be3cbd93e06a48ea95aaa27add586748772fa5b926994", size = 427391, upload-time = "2025-08-12T16:59:58.079Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/90/7f4d4084f9c35c3ea3e784646ec12f9b2c8cf8743b2bb5489252659b5bda/anthropic-0.56.0-py3-none-any.whl", hash = "sha256:91f1f74abdcf0958d3296b657304588cc244b1107b89f973ff6f511afdacfc56", size = 289603, upload-time = "2025-07-01T19:39:08.794Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a1/83bdb1a8be76fbb3ceedae9dfe1b515cff56dcfbbc388b53070a27ce341f/anthropic-0.63.0-py3-none-any.whl", hash = "sha256:d1849fe1635ae4277f45a0e4365979ed69e6264b73350ce8a99fee701d347745", size = 296637, upload-time = "2025-08-12T16:59:56.841Z" }, ] [[package]] @@ -167,6 +179,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, ] +[[package]] +name = "argcomplete" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, +] + [[package]] name = "attrs" version = "25.3.0" @@ -233,6 +254,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, ] +[[package]] +name = "boto3" +version = "1.40.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/f31556d817e872c2723196a34b197d971d78297b22b8bae0ae6d93f7f9c1/boto3-1.40.7.tar.gz", hash = "sha256:61b15f70761f1eadd721c6ba41a92658f003eaaef09500ca7642f5ae68ec8945", size = 111989, upload-time = "2025-08-11T19:20:45.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/e3/f2a77f4809ffe4e896c2e6186db88333ae980f52a91b28e9fd068d8f5506/boto3-1.40.7-py3-none-any.whl", hash = "sha256:8727cac601a679d2885dc78b8119a0548bbbe04e49b72f7d94021a629154c080", size = 140061, upload-time = "2025-08-11T19:20:43.173Z" }, +] + +[[package]] +name = "botocore" +version = "1.40.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/d7/5e559918410b259c1e54a4646ff39c56433e1c9cefa5e66ab0f06716cee8/botocore-1.40.7.tar.gz", hash = "sha256:33793696680cf3a0c4b5ace4f9070c67c4d4fcb19c999fd85cfee55de3dcf913", size = 14318282, upload-time = "2025-08-11T19:20:33.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/fa/bb7ec68b24d1b4678d341a305cbfed78a593e6383c86a70727410e4d0e11/botocore-1.40.7-py3-none-any.whl", hash = "sha256:a06956f3d7222e80ef6ae193608f358c3b7898e1a2b88553479d8f9737fbb03e", size = 13981488, upload-time = "2025-08-11T19:20:27.303Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + [[package]] name = "certifi" version = "2025.6.15" @@ -686,6 +744,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, ] +[[package]] +name = "eval-type-backport" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/ea/8b0ac4469d4c347c6a385ff09dc3c048c2d021696664e26c7ee6791631b5/eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1", size = 9079, upload-time = "2024-12-21T20:09:46.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/31/55cd413eaccd39125368be33c46de24a1f639f2e12349b0361b4678f3915/eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a", size = 5830, upload-time = "2024-12-21T20:09:44.175Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -869,6 +936,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-genai" +version = "1.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "google-auth" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/9b/a1e31252c4151da9403b357b47ae7ec5fc852eaf3486696eec211794001d/google_genai-1.29.0.tar.gz", hash = "sha256:a6b036ab032830f668d137b198c2a5abd8951a036d7a8480b61ce837c1c7f36b", size = 224207, upload-time = "2025-08-06T23:32:09.708Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/33/9b22b0b3734f93655d0d28cfcd64496ef46dd68efe8ae19278f3b1297998/google_genai-1.29.0-py3-none-any.whl", hash = "sha256:8b64737de008d15ca4737e593913f88f656f0568544ab6901f768f0d1fd69bbf", size = 222591, upload-time = "2025-08-06T23:32:08.133Z" }, +] + +[[package]] +name = "griffe" +version = "1.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/0f/9cbd56eb047de77a4b93d8d4674e70cd19a1ff64d7410651b514a1ed93d5/griffe-1.11.1.tar.gz", hash = "sha256:d54ffad1ec4da9658901eb5521e9cddcdb7a496604f67d8ae71077f03f549b7e", size = 410996, upload-time = "2025-08-11T11:38:35.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/a3/451ffd422ce143758a39c0290aaa7c9727ecc2bcc19debd7a8f3c6075ce9/griffe-1.11.1-py3-none-any.whl", hash = "sha256:5799cf7c513e4b928cfc6107ee6c4bc4a92e001f07022d97fd8dee2f612b6064", size = 138745, upload-time = "2025-08-11T11:38:33.964Z" }, +] + +[[package]] +name = "groq" +version = "0.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a2/77fd1460e7d55859219223719aa44ae8902a3a1ad333cd5faf330eb0b894/groq-0.31.0.tar.gz", hash = "sha256:182252e9bf0d696df607c137cbafa851d2c84aaf94bcfe9165c0bc231043490c", size = 136237, upload-time = "2025-08-05T23:14:01.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/f8/14672d69a91495f43462c5490067eeafc30346e81bda1a62848e897f9bc3/groq-0.31.0-py3-none-any.whl", hash = "sha256:5e3c7ec9728b7cccf913da982a9b5ebb46dc18a070b35e12a3d6a1e12d6b0f7f", size = 131365, upload-time = "2025-08-05T23:13:59.768Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -888,6 +1017,7 @@ dependencies = [ { name = "httpx" }, { name = "ollama" }, { name = "pydantic" }, + { name = "pydantic-ai" }, { name = "python-dotenv" }, { name = "rich" }, { name = "sqlite-vec" }, @@ -897,18 +1027,9 @@ dependencies = [ ] [package.optional-dependencies] -anthropic = [ - { name = "anthropic" }, -] -cohere = [ - { name = "cohere" }, -] mxbai = [ { name = "mxbai-rerank" }, ] -openai = [ - { name = "openai" }, -] voyageai = [ { name = "voyageai" }, ] @@ -928,15 +1049,13 @@ dev = [ [package.metadata] requires-dist = [ - { name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.56.0" }, - { name = "cohere", marker = "extra == 'cohere'", specifier = ">=5.16.1" }, { name = "docling", specifier = ">=2.15.0" }, { name = "fastmcp", specifier = ">=2.8.1" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "mxbai-rerank", marker = "extra == 'mxbai'", specifier = ">=0.1.6" }, { name = "ollama", specifier = ">=0.5.3" }, - { name = "openai", marker = "extra == 'openai'", specifier = ">=1.0.0" }, { name = "pydantic", specifier = ">=2.11.7" }, + { name = "pydantic-ai", specifier = ">=0.7.2" }, { name = "python-dotenv", specifier = ">=1.1.0" }, { name = "rich", specifier = ">=14.0.0" }, { name = "sqlite-vec", specifier = ">=0.1.6" }, @@ -945,7 +1064,7 @@ requires-dist = [ { name = "voyageai", marker = "extra == 'voyageai'", specifier = ">=0.3.2" }, { name = "watchfiles", specifier = ">=1.1.0" }, ] -provides-extras = ["voyageai", "openai", "anthropic", "cohere", "mxbai"] +provides-extras = ["voyageai", "mxbai"] [package.metadata.requires-dev] dev = [ @@ -1014,7 +1133,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "0.33.0" +version = "0.34.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -1026,9 +1145,14 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/8a/1362d565fefabaa4185cf3ae842a98dbc5b35146f5694f7080f043a6952f/huggingface_hub-0.33.0.tar.gz", hash = "sha256:aa31f70d29439d00ff7a33837c03f1f9dd83971ce4e29ad664d63ffb17d3bb97", size = 426179, upload-time = "2025-06-11T17:08:07.913Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/fb/53587a89fbc00799e4179796f51b3ad713c5de6bb680b2becb6d37c94649/huggingface_hub-0.33.0-py3-none-any.whl", hash = "sha256:e8668875b40c68f9929150d99727d39e5ebb8a05a98e4191b908dc7ded9074b3", size = 514799, upload-time = "2025-06-11T17:08:05.757Z" }, + { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, +] + +[package.optional-dependencies] +inference = [ + { name = "aiohttp" }, ] [[package]] @@ -1062,6 +1186,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" }, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -1143,6 +1279,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, upload-time = "2025-05-18T19:04:41.894Z" }, ] +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + [[package]] name = "jsonlines" version = "3.1.0" @@ -1212,6 +1357,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, ] +[[package]] +name = "logfire-api" +version = "4.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/29/8aa07eb5e841aab63ab53bbd251b5b6a1b485f8b42f8076c50e2c8d423c2/logfire_api-4.3.1.tar.gz", hash = "sha256:2e5f81b28406db7ef42152b8e2c616213133f408004fa0b5aa5f995f454e67eb", size = 52791, upload-time = "2025-08-12T12:49:41.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/c1/3a49b8a3b8ce3b8f9d80937a58b817d919de7225fcecbc05b986791929af/logfire_api-4.3.1-py3-none-any.whl", hash = "sha256:98985eee43c356e3af067d9e1f9b2c5cf1cae344fe7259c96309516e85e0af3d", size = 88328, upload-time = "2025-08-12T12:49:39.445Z" }, +] + [[package]] name = "lxml" version = "5.4.0" @@ -1351,22 +1505,24 @@ wheels = [ [[package]] name = "mcp" -version = "1.9.4" +version = "1.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "httpx-sse" }, + { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/f2/dc2450e566eeccf92d89a00c3e813234ad58e2ba1e31d11467a09ac4f3b9/mcp-1.9.4.tar.gz", hash = "sha256:cfb0bcd1a9535b42edaef89947b9e18a8feb49362e1cc059d6e7fc636f2cb09f", size = 333294, upload-time = "2025-06-12T08:20:30.158Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/88/f6cb7e7c260cd4b4ce375f2b1614b33ce401f63af0f49f7141a2e9bf0a45/mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5", size = 431148, upload-time = "2025-08-07T20:31:18.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/97/fc/80e655c955137393c443842ffcc4feccab5b12fa7cb8de9ced90f90e6998/mcp-1.9.4-py3-none-any.whl", hash = "sha256:7fcf36b62936adb8e63f89346bccca1268eeca9bf6dfb562ee10b1dfbda9dac0", size = 130232, upload-time = "2025-06-12T08:20:28.551Z" }, + { url = "https://files.pythonhosted.org/packages/ad/68/316cbc54b7163fa22571dcf42c9cc46562aae0a021b974e0a8141e897200/mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789", size = 160145, upload-time = "2025-08-07T20:31:15.69Z" }, ] [[package]] @@ -1387,6 +1543,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, ] +[[package]] +name = "mistralai" +version = "1.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/1d/280c6582124ff4aab3009f0c0282fd48e7fa3a60457f25e9196dc3cc2b8f/mistralai-1.9.3.tar.gz", hash = "sha256:a69806247ed3a67820ecfc9a68b7dbc0c6120dad5e5c3d507bd57fa388b491b7", size = 197355, upload-time = "2025-07-23T19:12:16.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/9a/0c48706c646b0391b798f8568f2b1545e54d345805e988003c10450b7b4c/mistralai-1.9.3-py3-none-any.whl", hash = "sha256:962445e7cebadcbfbcd1daf973e853a832dcf7aba6320468fcf7e2cf5f943aec", size = 426266, upload-time = "2025-07-23T19:12:15.414Z" }, +] + [[package]] name = "mkdocs" version = "1.6.1" @@ -1603,6 +1775,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, ] +[[package]] +name = "nexus-rpc" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/66/540687556bd28cf1ec370cc6881456203dfddb9dab047b8979c6865b5984/nexus_rpc-1.1.0.tar.gz", hash = "sha256:d65ad6a2f54f14e53ebe39ee30555eaeb894102437125733fb13034a04a44553", size = 77383, upload-time = "2025-07-07T19:03:58.368Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2f/9e9d0dcaa4c6ffa22b7aa31069a8a264c753ff8027b36af602cce038c92f/nexus_rpc-1.1.0-py3-none-any.whl", hash = "sha256:d1b007af2aba186a27e736f8eaae39c03aed05b488084ff6c3d1785c9ba2ad38", size = 27743, upload-time = "2025-07-07T19:03:57.556Z" }, +] + [[package]] name = "ninja" version = "1.11.1.4" @@ -1842,7 +2026,7 @@ wheels = [ [[package]] name = "openai" -version = "1.88.0" +version = "1.99.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1854,9 +2038,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/ea/bbeef604d1fe0f7e9111745bb8a81362973a95713b28855beb9a9832ab12/openai-1.88.0.tar.gz", hash = "sha256:122d35e42998255cf1fc84560f6ee49a844e65c054cd05d3e42fda506b832bb1", size = 470963, upload-time = "2025-06-17T05:04:45.856Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/d2/ef89c6f3f36b13b06e271d3cc984ddd2f62508a0972c1cbcc8485a6644ff/openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92", size = 506992, upload-time = "2025-08-12T02:31:10.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/03/ef68d77a38dd383cbed7fc898857d394d5a8b0520a35f054e7fe05dc3ac1/openai-1.88.0-py3-none-any.whl", hash = "sha256:7edd7826b3b83f5846562a6f310f040c79576278bf8e3687b30ba05bb5dff978", size = 734293, upload-time = "2025-06-17T05:04:43.858Z" }, + { url = "https://files.pythonhosted.org/packages/e8/fb/df274ca10698ee77b07bff952f302ea627cc12dac6b85289485dd77db6de/openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a", size = 786816, upload-time = "2025-08-12T02:31:08.34Z" }, ] [[package]] @@ -1900,6 +2084,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -2061,6 +2258,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + [[package]] name = "propcache" version = "0.3.2" @@ -2134,6 +2343,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, ] +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226, upload-time = "2025-05-28T23:51:59.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963, upload-time = "2025-05-28T23:51:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818, upload-time = "2025-05-28T23:51:44.297Z" }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091, upload-time = "2025-05-28T23:51:45.907Z" }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824, upload-time = "2025-05-28T23:51:47.545Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942, upload-time = "2025-05-28T23:51:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" }, +] + [[package]] name = "psutil" version = "7.0.0" @@ -2193,6 +2416,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/40/ad395740cd641869a13bcf60851296c89624662575621968dcfafabaa7f6/pyarrow-20.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:82f1ee5133bd8f49d31be1299dc07f585136679666b502540db854968576faf9", size = 25944982, upload-time = "2025-04-27T12:33:04.72Z" }, ] +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pyclipper" version = "1.3.0.post6" @@ -2243,6 +2487,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/c0/ec2b1c8712ca690e5d61979dee872603e92b8a32f94cc1b72d53beab008a/pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b", size = 444782, upload-time = "2025-06-14T08:33:14.905Z" }, ] +[[package]] +name = "pydantic-ai" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "google", "groq", "huggingface", "mcp", "mistral", "openai", "retries", "temporal", "vertexai"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/d0/ca0dbea87aa677192fa4b663532bd37ae8273e883c55b661b786dbb52731/pydantic_ai-0.7.2.tar.gz", hash = "sha256:d215c323741d47ff13c6b48aa75aedfb8b6b5f9da553af709675c3078a4be4fc", size = 43763306, upload-time = "2025-08-14T22:59:58.912Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/77/402a278b9694cdfaeb5bf0ed4e0fee447de624aa67126ddcce8d98dc6062/pydantic_ai-0.7.2-py3-none-any.whl", hash = "sha256:a6e5d0994aa87385a05fdfdad7fda1fd14576f623635e4000883c4c7856eba13", size = 10188, upload-time = "2025-08-14T22:59:50.653Z" }, +] + +[[package]] +name = "pydantic-ai-slim" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "eval-type-backport" }, + { name = "griffe" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pydantic-graph" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/39/87500c5e038296fe1becf62ac24f7e62dd5a1fb7fe63a9e29c58a2898b1a/pydantic_ai_slim-0.7.2.tar.gz", hash = "sha256:636ca32c8928048ba1173963aab6b7eb33b71174bbc371ad3f2096fee4c48dfe", size = 211787, upload-time = "2025-08-14T23:00:02.67Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/93/fc3723a7cde4a8edb2d060fb8abeba22270ae61984796ab653fdd05baca0/pydantic_ai_slim-0.7.2-py3-none-any.whl", hash = "sha256:f5749d63bf4c2deac45371874df30d1d76a1572ce9467f6505926ecb835da583", size = 289755, upload-time = "2025-08-14T22:59:53.346Z" }, +] + +[package.optional-dependencies] +ag-ui = [ + { name = "ag-ui-protocol" }, + { name = "starlette" }, +] +anthropic = [ + { name = "anthropic" }, +] +bedrock = [ + { name = "boto3" }, +] +cli = [ + { name = "argcomplete" }, + { name = "prompt-toolkit" }, + { name = "rich" }, +] +cohere = [ + { name = "cohere", marker = "sys_platform != 'emscripten'" }, +] +evals = [ + { name = "pydantic-evals" }, +] +google = [ + { name = "google-genai" }, +] +groq = [ + { name = "groq" }, +] +huggingface = [ + { name = "huggingface-hub", extra = ["inference"] }, +] +mcp = [ + { name = "mcp" }, +] +mistral = [ + { name = "mistralai" }, +] +openai = [ + { name = "openai" }, +] +retries = [ + { name = "tenacity" }, +] +temporal = [ + { name = "temporalio" }, +] +vertexai = [ + { name = "google-auth" }, + { name = "requests" }, +] + [[package]] name = "pydantic-core" version = "2.33.2" @@ -2308,6 +2633,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, ] +[[package]] +name = "pydantic-evals" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "pydantic-ai-slim" }, + { name = "pyyaml" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/b7/005b1b23b96abf2bce880a4c10496c00f8ebd67690f6888e576269059f54/pydantic_evals-0.7.2.tar.gz", hash = "sha256:0cf7adee67b8a12ea0b41e5162c7256ae0f6a237acb1eea161a74ed6cf61615a", size = 44086, upload-time = "2025-08-14T23:00:03.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/6f/3b844991fc1223f9c3b201f222397b0d115e236389bd90ced406ebc478ea/pydantic_evals-0.7.2-py3-none-any.whl", hash = "sha256:c7497d89659c35fbcaefbeb6f457ae09d62e36e161c4b25a462808178b7cfa92", size = 52753, upload-time = "2025-08-14T22:59:55.018Z" }, +] + +[[package]] +name = "pydantic-graph" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "logfire-api" }, + { name = "pydantic" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/a9/8a918b4dc2cd55775d854e076823fa9b60a390e4fbec5283916346556754/pydantic_graph-0.7.2.tar.gz", hash = "sha256:f90e4ec6f02b899bf6f88cc026dafa119ea5041ab4c62ba81497717c003a946e", size = 21804, upload-time = "2025-08-14T23:00:04.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/d7/639c69dda9e4b4cf376c9f45e5eae96721f2dc2f2dc618fb63142876dce4/pydantic_graph-0.7.2-py3-none-any.whl", hash = "sha256:b6189500a465ce1bce4bbc65ac5871149af8e0f81a15d54540d3dfc0cc9b2502", size = 27392, upload-time = "2025-08-14T22:59:56.564Z" }, +] + [[package]] name = "pydantic-settings" version = "2.9.1" @@ -2804,6 +3161,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, ] +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + [[package]] name = "rtree" version = "1.4.0" @@ -2846,6 +3215,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" }, ] +[[package]] +name = "s3transfer" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589, upload-time = "2025-07-18T19:22:42.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, +] + [[package]] name = "safetensors" version = "0.5.3" @@ -3131,6 +3512,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] +[[package]] +name = "temporalio" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nexus-rpc" }, + { name = "protobuf" }, + { name = "types-protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/af/1a3619fc62333d0acbdf90cfc5ada97e68e8c0f79610363b2dbb30871d83/temporalio-1.15.0.tar.gz", hash = "sha256:a4bc6ca01717880112caab75d041713aacc8263dc66e41f5019caef68b344fa0", size = 1684485, upload-time = "2025-07-29T03:44:09.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/2d/0153f2bc459e0cb59d41d4dd71da46bf9a98ca98bc37237576c258d6696b/temporalio-1.15.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:74bc5cc0e6bdc161a43015538b0821b8713f5faa716c4209971c274b528e0d47", size = 12703607, upload-time = "2025-07-29T03:43:30.083Z" }, + { url = "https://files.pythonhosted.org/packages/e4/39/1b867ec698c8987aef3b7a7024b5c0c732841112fa88d021303d0fc69bea/temporalio-1.15.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee8001304dae5723d79797516cfeebe04b966fdbdf348e658fce3b43afdda3cd", size = 12232853, upload-time = "2025-07-29T03:43:38.909Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3e/647d9a7c8b2f638f639717404c0bcbdd7d54fddd7844fdb802e3f40dc55f/temporalio-1.15.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8febd1ac36720817e69c2176aa4aca14a97fe0b83f0d2449c0c730b8f0174d02", size = 12636700, upload-time = "2025-07-29T03:43:49.066Z" }, + { url = "https://files.pythonhosted.org/packages/9a/13/7aa9ec694fec9fba39efdbf61d892bccf7d2b1aa3d9bd359544534c1d309/temporalio-1.15.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202d81a42cafaed9ccc7ccbea0898838e3b8bf92fee65394f8790f37eafbaa63", size = 12860186, upload-time = "2025-07-29T03:43:57.644Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2b/ba962401324892236148046dbffd805d4443d6df7a7dc33cc7964b566bf9/temporalio-1.15.0-cp39-abi3-win_amd64.whl", hash = "sha256:aae5b18d7c9960238af0f3ebf6b7e5959e05f452106fc0d21a8278d78724f780", size = 12932800, upload-time = "2025-07-29T03:44:06.271Z" }, +] + [[package]] name = "tenacity" version = "9.1.2" @@ -3383,6 +3783,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317, upload-time = "2025-05-26T14:30:30.523Z" }, ] +[[package]] +name = "types-protobuf" +version = "6.30.2.20250809" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/9e/8777c578b5b66f6ef99ce9dac4865b51016a52b1d681942fbf75ac35d60f/types_protobuf-6.30.2.20250809.tar.gz", hash = "sha256:b04f2998edf0d81bd8600bbd5db0b2adf547837eef6362ba364925cee21a33b4", size = 62204, upload-time = "2025-08-09T03:14:07.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/9a/43daca708592570539888d80d6b708dff0b1795218aaf6b13057cc2e2c18/types_protobuf-6.30.2.20250809-py3-none-any.whl", hash = "sha256:7afc2d3f569d281dd22f339179577243be60bf7d1dfb4bc13d0109859fb1f1be", size = 76389, upload-time = "2025-08-09T03:14:06.531Z" }, +] + [[package]] name = "types-requests" version = "2.32.4.20250611" @@ -3591,6 +4000,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, ] +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + [[package]] name = "xlsxwriter" version = "3.2.3" @@ -3734,3 +4194,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, ] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +]