diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..4b41e7b7e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,90 @@ +# AGENTS.md + +## Project overview + +This repository contains a course-materials RAG chatbot: + +- FastAPI serves the API and static frontend. +- OpenAI's Responses API generates answers and calls the local course-search tool. +- ChromaDB stores course metadata and embedded transcript chunks. +- Sentence Transformers generates local embeddings. +- The frontend is plain HTML, CSS, and JavaScript. + +## Important paths + +- `backend/app.py`: FastAPI application and HTTP endpoints. +- `backend/rag_system.py`: orchestration for ingestion, retrieval, generation, and sessions. +- `backend/ai_generator.py`: OpenAI Responses API integration and tool-call handling. +- `backend/document_processor.py`: transcript parsing and chunking. +- `backend/vector_store.py`: ChromaDB collections and semantic search. +- `backend/search_tools.py`: model-facing course-search tool. +- `frontend/`: browser interface. +- `docs/`: source course transcripts. + +## Setup and run + +Use `uv` for Python dependencies. The application requires Python 3.13 or newer. + +```bash +uv sync +./run.sh +``` + +The application runs at `http://localhost:8000`; FastAPI documentation is at +`http://localhost:8000/docs`. + +The required environment variable is: + +```dotenv +OPENAI_API_KEY=your_key +``` + +Never print, log, hard-code, or commit API keys. Do not read or modify `.env` +unless the user explicitly asks for environment configuration. + +## Architecture and behavior + +- Preserve the public API contracts for `POST /api/query` and `GET /api/courses`. +- Keep retrieval source labels available to the frontend. +- Course-specific questions should use semantic retrieval; general questions may + be answered without retrieval. +- The model may perform at most one course search for each user query. +- Conversation sessions are intentionally in-memory and retain a small history. +- ChromaDB uses separate `course_catalog` and `course_content` collections. +- Start the server through `run.sh`; current imports and data paths assume Uvicorn + runs from the `backend` directory. + +## Development conventions + +- Keep changes small and consistent with the current straightforward architecture. +- Prefer type hints for new Python functions and Pydantic models for API schemas. +- Keep provider-specific API handling inside `backend/ai_generator.py`. +- Do not replace the local embedding or ChromaDB stack unless explicitly requested. +- Do not edit the course transcripts or generated `chroma_db` data unless the task + concerns ingestion or the knowledge base. +- Avoid adding frontend frameworks for small UI changes. +- Update `README.md` when setup, configuration, endpoints, or runtime behavior changes. +- Update both `pyproject.toml` and `uv.lock` when dependencies change. + +## Verification + +There is currently no automated test suite. At minimum, after backend changes run: + +```bash +uv run python -m compileall -q backend +git diff --check +``` + +For changes to retrieval or generation, add an API-free focused test or smoke check +when practical. Do not make a live OpenAI request unless the user explicitly asks +for live integration testing and a valid key is configured. + +For API or frontend behavior changes, start the app and verify the affected endpoint +or browser flow. Report any verification that could not be run. + +## Security and repository hygiene + +- Treat model output and retrieved documents as untrusted input. +- Do not expose stack traces, secrets, or environment values through API responses. +- Preserve unrelated user changes in the working tree. +- Do not commit `.env`, `.venv`, ChromaDB data, caches, or generated artifacts. diff --git a/README.md b/README.md index e5420d50a..6ee4a906b 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ A Retrieval-Augmented Generation (RAG) system designed to answer questions about ## Overview -This application is a full-stack web application that enables users to query course materials and receive intelligent, context-aware responses. It uses ChromaDB for vector storage, Anthropic's Claude for AI generation, and provides a web interface for interaction. +This application is a full-stack web application that enables users to query course materials and receive intelligent, context-aware responses. It uses ChromaDB for vector storage, OpenAI for AI generation, and provides a web interface for interaction. ## Prerequisites - Python 3.13 or higher - uv (Python package manager) -- An Anthropic API key (for Claude AI) +- An OpenAI API key - **For Windows**: Use Git Bash to run the application commands - [Download Git for Windows](https://git-scm.com/downloads/win) ## Installation @@ -30,7 +30,7 @@ This application is a full-stack web application that enables users to query cou Create a `.env` file in the root directory: ```bash - ANTHROPIC_API_KEY=your_anthropic_api_key_here + OPENAI_API_KEY=your_openai_api_key_here ``` ## Running the Application diff --git a/backend/ai_generator.py b/backend/ai_generator.py index 0363ca90c..64102dc83 100644 --- a/backend/ai_generator.py +++ b/backend/ai_generator.py @@ -1,135 +1,94 @@ -import anthropic -from typing import List, Optional, Dict, Any - -class AIGenerator: - """Handles interactions with Anthropic's Claude API for generating responses""" - - # Static system prompt to avoid rebuilding on each call - SYSTEM_PROMPT = """ You are an AI assistant specialized in course materials and educational content with access to a comprehensive search tool for course information. - -Search Tool Usage: -- Use the search tool **only** for questions about specific course content or detailed educational materials -- **One search per query maximum** -- Synthesize search results into accurate, fact-based responses -- If search yields no results, state this clearly without offering alternatives - -Response Protocol: -- **General knowledge questions**: Answer using existing knowledge without searching -- **Course-specific questions**: Search first, then answer -- **No meta-commentary**: - - Provide direct answers only — no reasoning process, search explanations, or question-type analysis - - Do not mention "based on the search results" - - -All responses must be: -1. **Brief, Concise and focused** - Get to the point quickly -2. **Educational** - Maintain instructional value -3. **Clear** - Use accessible language -4. **Example-supported** - Include relevant examples when they aid understanding -Provide only the direct answer to what was asked. -""" - - def __init__(self, api_key: str, model: str): - self.client = anthropic.Anthropic(api_key=api_key) - self.model = model - - # Pre-build base API parameters - self.base_params = { - "model": self.model, - "temperature": 0, - "max_tokens": 800 - } - - def generate_response(self, query: str, - conversation_history: Optional[str] = None, - tools: Optional[List] = None, - tool_manager=None) -> str: - """ - Generate AI response with optional tool usage and conversation context. - - Args: - query: The user's question or request - conversation_history: Previous messages for context - tools: Available tools the AI can use - tool_manager: Manager to execute tools - - Returns: - Generated response as string - """ - - # Build system content efficiently - avoid string ops when possible - system_content = ( - f"{self.SYSTEM_PROMPT}\n\nPrevious conversation:\n{conversation_history}" - if conversation_history - else self.SYSTEM_PROMPT - ) - - # Prepare API call parameters efficiently - api_params = { - **self.base_params, - "messages": [{"role": "user", "content": query}], - "system": system_content - } - - # Add tools if available - if tools: - api_params["tools"] = tools - api_params["tool_choice"] = {"type": "auto"} - - # Get response from Claude - response = self.client.messages.create(**api_params) - - # Handle tool execution if needed - if response.stop_reason == "tool_use" and tool_manager: - return self._handle_tool_execution(response, api_params, tool_manager) - - # Return direct response - return response.content[0].text - - def _handle_tool_execution(self, initial_response, base_params: Dict[str, Any], tool_manager): - """ - Handle execution of tool calls and get follow-up response. - - Args: - initial_response: The response containing tool use requests - base_params: Base API parameters - tool_manager: Manager to execute tools - - Returns: - Final response text after tool execution - """ - # Start with existing messages - messages = base_params["messages"].copy() - - # Add AI's tool use response - messages.append({"role": "assistant", "content": initial_response.content}) - - # Execute all tool calls and collect results - tool_results = [] - for content_block in initial_response.content: - if content_block.type == "tool_use": - tool_result = tool_manager.execute_tool( - content_block.name, - **content_block.input - ) - - tool_results.append({ - "type": "tool_result", - "tool_use_id": content_block.id, - "content": tool_result - }) - - # Add tool results as single message - if tool_results: - messages.append({"role": "user", "content": tool_results}) - - # Prepare final API call without tools - final_params = { - **self.base_params, - "messages": messages, - "system": base_params["system"] - } - - # Get final response - final_response = self.client.messages.create(**final_params) - return final_response.content[0].text \ No newline at end of file +import json +from typing import Any, Dict, List, Optional + +from openai import OpenAI + + +class AIGenerator: + """Generate answers with OpenAI and execute local course-search tools.""" + + SYSTEM_PROMPT = """You are an AI assistant specialized in course materials and educational content with access to a comprehensive search tool for course information. + +Search Tool Usage: +- Use the search tool only for questions about specific course content or detailed educational materials. +- Use at most one search per query. +- Synthesize search results into accurate, fact-based responses. +- If search yields no results, state this clearly without offering alternatives. + +Response Protocol: +- General knowledge questions: answer using existing knowledge without searching. +- Course-specific questions: search first, then answer. +- Provide direct answers only. Do not describe your reasoning or search process. +- Do not say "based on the search results." + +Keep responses brief, educational, clear, and focused. Include an example when it materially improves understanding. +""" + + def __init__(self, api_key: str, model: str): + self.client = OpenAI(api_key=api_key) + self.model = model + + def generate_response( + self, + query: str, + conversation_history: Optional[str] = None, + tools: Optional[List[Dict[str, Any]]] = None, + tool_manager=None, + ) -> str: + """Generate a response, executing at most one round of tool calls.""" + instructions = self.SYSTEM_PROMPT + if conversation_history: + instructions += f"\n\nPrevious conversation:\n{conversation_history}" + + openai_tools = self._convert_tools(tools or []) + input_items: List[Any] = [{"role": "user", "content": query}] + request: Dict[str, Any] = { + "model": self.model, + "instructions": instructions, + "input": input_items, + "reasoning": {"effort": "low"}, + "max_output_tokens": 800, + } + if openai_tools: + request["tools"] = openai_tools + request["tool_choice"] = "auto" + + response = self.client.responses.create(**request) + function_calls = [item for item in response.output if item.type == "function_call"] + if not function_calls or not tool_manager: + return response.output_text + + input_items.extend(response.output) + for function_call in function_calls: + arguments = json.loads(function_call.arguments) + result = tool_manager.execute_tool(function_call.name, **arguments) + input_items.append( + { + "type": "function_call_output", + "call_id": function_call.call_id, + "output": result, + } + ) + + final_response = self.client.responses.create( + model=self.model, + instructions=instructions, + input=input_items, + tools=openai_tools, + reasoning={"effort": "low"}, + max_output_tokens=800, + ) + return final_response.output_text + + @staticmethod + def _convert_tools(tools: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Convert the existing tool definitions to OpenAI function tools.""" + return [ + { + "type": "function", + "name": tool["name"], + "description": tool.get("description", ""), + "parameters": tool["input_schema"], + } + for tool in tools + ] diff --git a/backend/app.py b/backend/app.py index 5a69d741d..6bf38cf47 100644 --- a/backend/app.py +++ b/backend/app.py @@ -40,10 +40,15 @@ class QueryRequest(BaseModel): query: str session_id: Optional[str] = None +class SourceCitation(BaseModel): + """A display label and optional destination for a retrieved source.""" + text: str + link: Optional[str] = None + class QueryResponse(BaseModel): """Response model for course queries""" answer: str - sources: List[str] + sources: List[SourceCitation] session_id: str class CourseStats(BaseModel): diff --git a/backend/config.py b/backend/config.py index d9f6392ef..836271997 100644 --- a/backend/config.py +++ b/backend/config.py @@ -8,9 +8,9 @@ @dataclass class Config: """Configuration settings for the RAG system""" - # Anthropic API settings - ANTHROPIC_API_KEY: str = os.getenv("ANTHROPIC_API_KEY", "") - ANTHROPIC_MODEL: str = "claude-sonnet-4-20250514" + # OpenAI API settings + OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "") + OPENAI_MODEL: str = "gpt-5.6" # Embedding model settings EMBEDDING_MODEL: str = "all-MiniLM-L6-v2" @@ -26,4 +26,3 @@ class Config: config = Config() - diff --git a/backend/rag_system.py b/backend/rag_system.py index 50d848c8e..5fba93533 100644 --- a/backend/rag_system.py +++ b/backend/rag_system.py @@ -4,7 +4,7 @@ from vector_store import VectorStore from ai_generator import AIGenerator from session_manager import SessionManager -from search_tools import ToolManager, CourseSearchTool +from search_tools import ToolManager, CourseSearchTool, SourceCitation from models import Course, Lesson, CourseChunk class RAGSystem: @@ -16,7 +16,7 @@ def __init__(self, config): # Initialize core components self.document_processor = DocumentProcessor(config.CHUNK_SIZE, config.CHUNK_OVERLAP) self.vector_store = VectorStore(config.CHROMA_PATH, config.EMBEDDING_MODEL, config.MAX_RESULTS) - self.ai_generator = AIGenerator(config.ANTHROPIC_API_KEY, config.ANTHROPIC_MODEL) + self.ai_generator = AIGenerator(config.OPENAI_API_KEY, config.OPENAI_MODEL) self.session_manager = SessionManager(config.MAX_HISTORY) # Initialize search tools @@ -99,7 +99,7 @@ def add_course_folder(self, folder_path: str, clear_existing: bool = False) -> T return total_courses, total_chunks - def query(self, query: str, session_id: Optional[str] = None) -> Tuple[str, List[str]]: + def query(self, query: str, session_id: Optional[str] = None) -> Tuple[str, List[SourceCitation]]: """ Process a user query using the RAG system with tool-based search. @@ -144,4 +144,4 @@ def get_course_analytics(self) -> Dict: return { "total_courses": self.vector_store.get_course_count(), "course_titles": self.vector_store.get_existing_course_titles() - } \ No newline at end of file + } diff --git a/backend/search_tools.py b/backend/search_tools.py index adfe82352..89e61c370 100644 --- a/backend/search_tools.py +++ b/backend/search_tools.py @@ -1,8 +1,15 @@ -from typing import Dict, Any, Optional, Protocol +from typing import Dict, Any, Optional, TypedDict from abc import ABC, abstractmethod from vector_store import VectorStore, SearchResults +class SourceCitation(TypedDict): + """Structured source metadata returned to API consumers.""" + + text: str + link: Optional[str] + + class Tool(ABC): """Abstract base class for all tools""" @@ -22,7 +29,7 @@ class CourseSearchTool(Tool): def __init__(self, vector_store: VectorStore): self.store = vector_store - self.last_sources = [] # Track sources from last search + self.last_sources: list[SourceCitation] = [] # Track sources from last search def get_tool_definition(self) -> Dict[str, Any]: """Return Anthropic tool definition for this tool""" @@ -88,7 +95,8 @@ def execute(self, query: str, course_name: Optional[str] = None, lesson_number: def _format_results(self, results: SearchResults) -> str: """Format search results with course and lesson context""" formatted = [] - sources = [] # Track sources for the UI + sources: list[SourceCitation] = [] # Track sources for the UI + seen_sources: set[str] = set() for doc, meta in zip(results.documents, results.metadata): course_title = meta.get('course_title', 'unknown') @@ -100,11 +108,18 @@ def _format_results(self, results: SearchResults) -> str: header += f" - Lesson {lesson_num}" header += "]" - # Track source for the UI - source = course_title + # Track each lesson once for the UI, even when several chunks match. + source_text = course_title if lesson_num is not None: - source += f" - Lesson {lesson_num}" - sources.append(source) + source_text += f" - Lesson {lesson_num}" + if source_text not in seen_sources: + lesson_link = ( + self.store.get_lesson_link(course_title, lesson_num) + if lesson_num is not None + else None + ) + sources.append({"text": source_text, "link": lesson_link}) + seen_sources.add(source_text) formatted.append(f"{header}\n{doc}") @@ -139,7 +154,7 @@ def execute_tool(self, tool_name: str, **kwargs) -> str: return self.tools[tool_name].execute(**kwargs) - def get_last_sources(self) -> list: + def get_last_sources(self) -> list[SourceCitation]: """Get sources from the last search operation""" # Check all tools for last_sources attribute for tool in self.tools.values(): @@ -151,4 +166,4 @@ def reset_sources(self): """Reset sources from all tools that track sources""" for tool in self.tools.values(): if hasattr(tool, 'last_sources'): - tool.last_sources = [] \ No newline at end of file + tool.last_sources = [] diff --git a/frontend/script.js b/frontend/script.js index 562a8a363..bc59c0aef 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -119,24 +119,67 @@ function addMessage(content, type, sources = null, isWelcome = false) { // Convert markdown to HTML for assistant messages const displayContent = type === 'assistant' ? marked.parse(content) : escapeHtml(content); - let html = `
${displayContent}
`; - + messageDiv.innerHTML = `
${displayContent}
`; + if (sources && sources.length > 0) { - html += ` -
- Sources -
${sources.join(', ')}
-
- `; + messageDiv.appendChild(createSourcesSection(sources)); } - - messageDiv.innerHTML = html; + chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; return messageId; } +function createSourcesSection(sources) { + const details = document.createElement('details'); + details.className = 'sources-collapsible'; + + const summary = document.createElement('summary'); + summary.className = 'sources-header'; + summary.textContent = 'Sources'; + details.appendChild(summary); + + const content = document.createElement('div'); + content.className = 'sources-content'; + + sources.forEach((source, index) => { + if (index > 0) { + content.appendChild(document.createTextNode(', ')); + } + + const label = typeof source === 'string' ? source : source.text; + const safeUrl = typeof source === 'object' ? getSafeHttpUrl(source.link) : null; + + if (safeUrl) { + const link = document.createElement('a'); + link.href = safeUrl; + link.target = '_blank'; + link.rel = 'noopener noreferrer'; + link.textContent = label; + content.appendChild(link); + } else { + const text = document.createElement('span'); + text.textContent = label; + content.appendChild(text); + } + }); + + details.appendChild(content); + return details; +} + +function getSafeHttpUrl(value) { + if (!value) return null; + + try { + const url = new URL(value); + return ['http:', 'https:'].includes(url.protocol) ? url.href : null; + } catch { + return null; + } +} + // Helper function to escape HTML for user messages function escapeHtml(text) { const div = document.createElement('div'); @@ -188,4 +231,4 @@ async function loadCourseStats() { courseTitles.innerHTML = 'Failed to load courses'; } } -} \ No newline at end of file +} diff --git a/frontend/style.css b/frontend/style.css index 825d03675..86d746bcc 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -245,6 +245,22 @@ header h1 { color: var(--text-secondary); } +.sources-content a { + color: #60a5fa; + text-decoration: underline; + text-underline-offset: 2px; +} + +.sources-content a:hover { + color: #93c5fd; +} + +.sources-content a:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; + border-radius: 2px; +} + /* Markdown formatting styles */ .message-content h1, .message-content h2, diff --git a/pyproject.toml b/pyproject.toml index 3f05e2de0..a84e7a0dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,10 @@ readme = "README.md" requires-python = ">=3.13" dependencies = [ "chromadb==1.0.15", - "anthropic==0.58.2", "sentence-transformers==5.0.0", "fastapi==0.116.1", "uvicorn==0.35.0", "python-multipart==0.0.20", "python-dotenv==1.1.1", + "openai>=2.53.0", ] diff --git a/run.sh b/run.sh index 80e3853d8..b05485af5 100755 --- a/run.sh +++ b/run.sh @@ -10,7 +10,7 @@ if [ ! -d "backend" ]; then fi echo "Starting Course Materials RAG System..." -echo "Make sure you have set your ANTHROPIC_API_KEY in .env" +echo "Make sure you have set your OPENAI_API_KEY in .env" # Change to backend directory and start the server -cd backend && uv run uvicorn app:app --reload --port 8000 \ No newline at end of file +cd backend && uv run uvicorn app:app --reload --port 8000 diff --git a/tests/test_search_tools.py b/tests/test_search_tools.py new file mode 100644 index 000000000..fdbe4fa15 --- /dev/null +++ b/tests/test_search_tools.py @@ -0,0 +1,82 @@ +import sys +import unittest +from pathlib import Path + + +BACKEND_DIR = Path(__file__).resolve().parents[1] / "backend" +sys.path.insert(0, str(BACKEND_DIR)) + +from search_tools import CourseSearchTool +from vector_store import SearchResults + + +class FakeVectorStore: + def __init__(self, lesson_links=None): + self.lesson_links = lesson_links or {} + self.lesson_link_calls = [] + + def get_lesson_link(self, course_title, lesson_number): + self.lesson_link_calls.append((course_title, lesson_number)) + return self.lesson_links.get((course_title, lesson_number)) + + +class CourseSearchToolFormattingTests(unittest.TestCase): + def test_returns_structured_deduplicated_sources_without_changing_model_text(self): + store = FakeVectorStore({("RAG Course", 2): "https://example.com/lesson-2"}) + tool = CourseSearchTool(store) + results = SearchResults( + documents=["First chunk", "Second chunk"], + metadata=[ + {"course_title": "RAG Course", "lesson_number": 2}, + {"course_title": "RAG Course", "lesson_number": 2}, + ], + distances=[0.1, 0.2], + ) + + formatted = tool._format_results(results) + + self.assertEqual( + formatted, + "[RAG Course - Lesson 2]\nFirst chunk\n\n" + "[RAG Course - Lesson 2]\nSecond chunk", + ) + self.assertEqual( + tool.last_sources, + [{"text": "RAG Course - Lesson 2", "link": "https://example.com/lesson-2"}], + ) + self.assertEqual(store.lesson_link_calls, [("RAG Course", 2)]) + + def test_missing_lesson_link_is_returned_as_null(self): + tool = CourseSearchTool(FakeVectorStore()) + results = SearchResults( + documents=["Chunk"], + metadata=[{"course_title": "RAG Course", "lesson_number": 3}], + distances=[0.1], + ) + + tool._format_results(results) + + self.assertEqual( + tool.last_sources, + [{"text": "RAG Course - Lesson 3", "link": None}], + ) + + def test_lesson_zero_uses_its_lesson_link(self): + store = FakeVectorStore({("RAG Course", 0): "https://example.com/lesson-0"}) + tool = CourseSearchTool(store) + results = SearchResults( + documents=["Introduction"], + metadata=[{"course_title": "RAG Course", "lesson_number": 0}], + distances=[0.1], + ) + + tool._format_results(results) + + self.assertEqual( + tool.last_sources, + [{"text": "RAG Course - Lesson 0", "link": "https://example.com/lesson-0"}], + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/uv.lock b/uv.lock index 9ae65c557..c8fe9de88 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13" [[package]] @@ -11,24 +11,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] -[[package]] -name = "anthropic" -version = "0.58.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/b9/ab06c586aa5a5e7499017cee5ebab94ee260e75975c45395f32b8592abdd/anthropic-0.58.2.tar.gz", hash = "sha256:86396cc45530a83acea25ae6bca9f86656af81e3d598b4d22a1300e0e4cf8df8", size = 425125, upload-time = "2025-07-18T13:38:55.94Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/f2/68d908ff308c9a65af5749ec31952e01d32f19ea073b0268affc616e6ebc/anthropic-0.58.2-py3-none-any.whl", hash = "sha256:3742181c634c725f337b71096839b6404145e33a8e190c75387c4028b825864d", size = 292896, upload-time = "2025-07-18T13:38:54.782Z" }, -] - [[package]] name = "anyio" version = "4.9.0" @@ -484,38 +466,52 @@ wheels = [ [[package]] name = "jiter" -version = "0.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/9d/ae7ddb4b8ab3fb1b51faf4deb36cb48a4fbbd7cb36bad6a5fca4741306f7/jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500", size = 162759, upload-time = "2025-05-18T19:04:59.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/b0/279597e7a270e8d22623fea6c5d4eeac328e7d95c236ed51a2b884c54f70/jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644", size = 311617, upload-time = "2025-05-18T19:04:02.078Z" }, - { url = "https://files.pythonhosted.org/packages/91/e3/0916334936f356d605f54cc164af4060e3e7094364add445a3bc79335d46/jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a", size = 318947, upload-time = "2025-05-18T19:04:03.347Z" }, - { url = "https://files.pythonhosted.org/packages/6a/8e/fd94e8c02d0e94539b7d669a7ebbd2776e51f329bb2c84d4385e8063a2ad/jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6", size = 344618, upload-time = "2025-05-18T19:04:04.709Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b0/f9f0a2ec42c6e9c2e61c327824687f1e2415b767e1089c1d9135f43816bd/jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3", size = 368829, upload-time = "2025-05-18T19:04:06.912Z" }, - { url = "https://files.pythonhosted.org/packages/e8/57/5bbcd5331910595ad53b9fd0c610392ac68692176f05ae48d6ce5c852967/jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2", size = 491034, upload-time = "2025-05-18T19:04:08.222Z" }, - { url = "https://files.pythonhosted.org/packages/9b/be/c393df00e6e6e9e623a73551774449f2f23b6ec6a502a3297aeeece2c65a/jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25", size = 388529, upload-time = "2025-05-18T19:04:09.566Z" }, - { url = "https://files.pythonhosted.org/packages/42/3e/df2235c54d365434c7f150b986a6e35f41ebdc2f95acea3036d99613025d/jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041", size = 350671, upload-time = "2025-05-18T19:04:10.98Z" }, - { url = "https://files.pythonhosted.org/packages/c6/77/71b0b24cbcc28f55ab4dbfe029f9a5b73aeadaba677843fc6dc9ed2b1d0a/jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca", size = 390864, upload-time = "2025-05-18T19:04:12.722Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d3/ef774b6969b9b6178e1d1e7a89a3bd37d241f3d3ec5f8deb37bbd203714a/jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4", size = 522989, upload-time = "2025-05-18T19:04:14.261Z" }, - { url = "https://files.pythonhosted.org/packages/0c/41/9becdb1d8dd5d854142f45a9d71949ed7e87a8e312b0bede2de849388cb9/jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e", size = 513495, upload-time = "2025-05-18T19:04:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/9c/36/3468e5a18238bdedae7c4d19461265b5e9b8e288d3f86cd89d00cbb48686/jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d", size = 211289, upload-time = "2025-05-18T19:04:17.541Z" }, - { url = "https://files.pythonhosted.org/packages/7e/07/1c96b623128bcb913706e294adb5f768fb7baf8db5e1338ce7b4ee8c78ef/jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4", size = 205074, upload-time = "2025-05-18T19:04:19.21Z" }, - { url = "https://files.pythonhosted.org/packages/54/46/caa2c1342655f57d8f0f2519774c6d67132205909c65e9aa8255e1d7b4f4/jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca", size = 318225, upload-time = "2025-05-18T19:04:20.583Z" }, - { url = "https://files.pythonhosted.org/packages/43/84/c7d44c75767e18946219ba2d703a5a32ab37b0bc21886a97bc6062e4da42/jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070", size = 350235, upload-time = "2025-05-18T19:04:22.363Z" }, - { url = "https://files.pythonhosted.org/packages/01/16/f5a0135ccd968b480daad0e6ab34b0c7c5ba3bc447e5088152696140dcb3/jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca", size = 207278, upload-time = "2025-05-18T19:04:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9b/1d646da42c3de6c2188fdaa15bce8ecb22b635904fc68be025e21249ba44/jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522", size = 310866, upload-time = "2025-05-18T19:04:24.891Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0e/26538b158e8a7c7987e94e7aeb2999e2e82b1f9d2e1f6e9874ddf71ebda0/jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8", size = 318772, upload-time = "2025-05-18T19:04:26.161Z" }, - { url = "https://files.pythonhosted.org/packages/7b/fb/d302893151caa1c2636d6574d213e4b34e31fd077af6050a9c5cbb42f6fb/jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216", size = 344534, upload-time = "2025-05-18T19:04:27.495Z" }, - { url = "https://files.pythonhosted.org/packages/01/d8/5780b64a149d74e347c5128d82176eb1e3241b1391ac07935693466d6219/jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4", size = 369087, upload-time = "2025-05-18T19:04:28.896Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5b/f235a1437445160e777544f3ade57544daf96ba7e96c1a5b24a6f7ac7004/jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426", size = 490694, upload-time = "2025-05-18T19:04:30.183Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/9c3d4617caa2ff89cf61b41e83820c27ebb3f7b5fae8a72901e8cd6ff9be/jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12", size = 388992, upload-time = "2025-05-18T19:04:32.028Z" }, - { url = "https://files.pythonhosted.org/packages/68/b1/344fd14049ba5c94526540af7eb661871f9c54d5f5601ff41a959b9a0bbd/jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9", size = 351723, upload-time = "2025-05-18T19:04:33.467Z" }, - { url = "https://files.pythonhosted.org/packages/41/89/4c0e345041186f82a31aee7b9d4219a910df672b9fef26f129f0cda07a29/jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a", size = 392215, upload-time = "2025-05-18T19:04:34.827Z" }, - { url = "https://files.pythonhosted.org/packages/55/58/ee607863e18d3f895feb802154a2177d7e823a7103f000df182e0f718b38/jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853", size = 522762, upload-time = "2025-05-18T19:04:36.19Z" }, - { url = "https://files.pythonhosted.org/packages/15/d0/9123fb41825490d16929e73c212de9a42913d68324a8ce3c8476cae7ac9d/jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86", size = 513427, upload-time = "2025-05-18T19:04:37.544Z" }, - { url = "https://files.pythonhosted.org/packages/d8/b3/2bd02071c5a2430d0b70403a34411fc519c2f227da7b03da9ba6a956f931/jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357", size = 210127, upload-time = "2025-05-18T19:04:38.837Z" }, - { url = "https://files.pythonhosted.org/packages/03/0c/5fe86614ea050c3ecd728ab4035534387cd41e7c1855ef6c031f1ca93e3f/jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00", size = 318527, upload-time = "2025-05-18T19:04:40.612Z" }, - { 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" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203, upload-time = "2026-06-29T13:03:36.243Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489, upload-time = "2026-06-29T13:03:37.846Z" }, + { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453, upload-time = "2026-06-29T13:03:39.221Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625, upload-time = "2026-06-29T13:03:40.597Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958, upload-time = "2026-06-29T13:03:42.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017, upload-time = "2026-06-29T13:03:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320, upload-time = "2026-06-29T13:03:45.226Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520, upload-time = "2026-06-29T13:03:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550, upload-time = "2026-06-29T13:03:48.361Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424, upload-time = "2026-06-29T13:03:49.881Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981, upload-time = "2026-06-29T13:03:51.363Z" }, + { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853, upload-time = "2026-06-29T13:03:53.015Z" }, + { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160, upload-time = "2026-06-29T13:03:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862, upload-time = "2026-06-29T13:03:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/a7/89/bc4f1b57d5da938fd344a466396541e586d161320d70bffd929aaafcd8f4/jiter-0.16.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b2c61484666ad42726029af0c00ef4541f0f3b5cdc550221f56c2343208018ee", size = 308239, upload-time = "2026-06-29T13:03:57.205Z" }, + { url = "https://files.pythonhosted.org/packages/65/7a/c415453e5213001bf3b411ff65dec3d303b0e76a4a2cfea9768cd4960994/jiter-0.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:63efadc657488f45db1c676d81e704cac2abf3fdb892def1faea61db053127e2", size = 308928, upload-time = "2026-06-29T13:03:58.643Z" }, + { url = "https://files.pythonhosted.org/packages/11/fc/1f4fb7ebf9a724c7741994f4aae18fba1e2f3133df14521a79194952c34a/jiter-0.16.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf0d73f50e7b6935677854f6e8e31d499ca7064dd24734f703e060f5b237d883", size = 336998, upload-time = "2026-06-29T13:04:00.071Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8d/72cadaac05ccfa7cc3a0a2232862e6c72443ca40cf300ba8b57f9f18b69b/jiter-0.16.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf3ea07d9bc8e7d03a9fbc051295462e6dbc295b894fd72457c3136e3e43d898", size = 362112, upload-time = "2026-06-29T13:04:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/58/4a/c4b0d5f651fda90a24ffce9f8d56cde462a2e09d31ae3de3c68cef34c04e/jiter-0.16.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26798522707abb47d767db536e4148ceac1b14446bf028ee85e579a2e043cfe5", size = 459807, upload-time = "2026-06-29T13:04:03.214Z" }, + { url = "https://files.pythonhosted.org/packages/80/58/ef77879ea9aa56b50824edc5a445e226422c7a8d211f3fd2a56bcb9493cf/jiter-0.16.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc837c1b9631be10abfe0191537fe8009838204cec7e44827401ace390ddb567", size = 373181, upload-time = "2026-06-29T13:04:04.629Z" }, + { url = "https://files.pythonhosted.org/packages/49/2e/ffbc3f254e4d8a66da3062c624a7df4b7c2b2cf9e1fe43cf394b3e104041/jiter-0.16.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49060fd70737fad59d33ba9dcc0d83247dc9e77187de26053a19c16c9f32bd69", size = 344927, upload-time = "2026-06-29T13:04:06.067Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f6/0be5dc6d64a89f80aa8fec984f94dedb2973e251edcae55841d60786d578/jiter-0.16.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:adbb8edeadd431bc4477879d5d371ece7cb1334486584e0f252656dd7ffada29", size = 352754, upload-time = "2026-06-29T13:04:07.477Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/7d31243b3b91cd261dd19e9d3557fc3251a80883d3d8049c86174e7ab7af/jiter-0.16.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:31aaee5b80f672c1dc21272bcfb9cbdcfc1ea04ff50f00ed5af500b80c44fa93", size = 390553, upload-time = "2026-06-29T13:04:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/25/33/51ae371fde3c88897520f62b4d5f8b27ad7103e2bb10812ff52195609853/jiter-0.16.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6722bcef4ffc86c835574b1b2fac6b33b9fb4a889c781e67950e891591f3c55a", size = 516900, upload-time = "2026-06-29T13:04:10.407Z" }, + { url = "https://files.pythonhosted.org/packages/a0/45/6449b3d123ea439ba79507c657288f461d55049e7bcbdc2cf8eb8210f491/jiter-0.16.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:5ab4f50ff971b611d656554ea10b75f80097392c827bc32923c6eeb6386c8b00", size = 548754, upload-time = "2026-06-29T13:04:12.046Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/fd2fb11ae3e2649333da3aa170d04d7b3000bbdc3b270f6513382fdf4e04/jiter-0.16.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:710cc51d4ebdcd3c1f70b232c1db1ea1344a075770422bbd4bede5708335acbe", size = 122381, upload-time = "2026-06-29T13:04:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/26/80/f0b147a62c315a164ed2168908286ca302310824c218d3aae52b06c0c9a9/jiter-0.16.0-cp314-cp314-win32.whl", hash = "sha256:57b37fc887a32d44798e4d8ebfa7c9683ff3da1d5bf38f08d1bb3573ccb39106", size = 204578, upload-time = "2026-06-29T13:04:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/4758a14304b4523a6f5adb2419340086aa3593bd4327c2b25b5948a90548/jiter-0.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbd18dd5e2df96b580487b5745adf57ef64ad89ba2d9662fc3c19386acce7db8", size = 198154, upload-time = "2026-06-29T13:04:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/26/be/41fa54a2e7ea41d6c99f1dc5b1f0fd4cb474680304b5d268dd518e81da3a/jiter-0.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:a32d2027a9fa67f109ff245a3252ece3ccc32cc56703e1deab6cc846a59e0585", size = 191458, upload-time = "2026-06-29T13:04:17.707Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/59127338b86d9fe4d99418f5a15118bea778103ee0fe9d9dd7e0af174e95/jiter-0.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2577196f4474ef3fc4779a088a23b0897bbf86f9ea3679c372d45b8383b43207", size = 316739, upload-time = "2026-06-29T13:04:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/2d/95/49461034d5388196d3dabf98748935f017b7785d8f3f5349f834bcc4ed0d/jiter-0.16.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e89e008a93c01104161c75b4988e58716b01d62307ebfe161e52a56d2a818", size = 340911, upload-time = "2026-06-29T13:04:21.257Z" }, + { url = "https://files.pythonhosted.org/packages/cd/97/a4369f2fb82cb3dda13b98622f31249b2e014b223fe64ee534413ad72294/jiter-0.16.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e2e9efbe042210df657bade597f66d6d75723e3d8f45a12ea6d8167ff8bbce3", size = 361747, upload-time = "2026-06-29T13:04:22.677Z" }, + { url = "https://files.pythonhosted.org/packages/28/51/49b6ed456261646e1906016a6760367a28aacd3c24805e4e5fe64116c1db/jiter-0.16.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f4d9e473a5ce7d27fef8b848df4dc16e283893d3f53b4a585e72c9595f3c284", size = 460225, upload-time = "2026-06-29T13:04:24.441Z" }, + { url = "https://files.pythonhosted.org/packages/33/b5/5689aff4f66c5b60be63106e591dbfcba2190df97d2c9c7cf052361ddb98/jiter-0.16.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d30a4a1c87713060c8d1cc59a7b6c8fb6b8ef0a6900368014c76c87922a2929", size = 373169, upload-time = "2026-06-29T13:04:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/a2/96/3ae1b85ee0d6d6cab254fb7f8da018272b932bbf2d69b07e98aa2a96c746/jiter-0.16.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae96332410f866e5900d809298b1ed82735932986c672495f9701daacd80620", size = 350332, upload-time = "2026-06-29T13:04:27.302Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/c99d7bafd78986556c95bf60ce84c6cc98786eac56066c12d7f828bb6747/jiter-0.16.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:da3d7ec75dc83bb18bca888b5edfae0656a26849056c59e05a7728badd17e7af", size = 353377, upload-time = "2026-06-29T13:04:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/f99a8e571287c3dec766bcc18528bbe8e8fb5365522ab5e6d64c93e87066/jiter-0.16.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ee6162b77d49a9939229df666dfa8af3e656b6701b54c4c84966d740e189264e", size = 387746, upload-time = "2026-06-29T13:04:30.319Z" }, + { url = "https://files.pythonhosted.org/packages/75/69/c78a5b3f71040e34eb5917df26fb7ae9a2174cad1ccbf277512507c53a6e/jiter-0.16.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:63ffdbdae7d4499f4cda14eadc12ddcabef0fc0c081191bdc2247489cb698077", size = 517292, upload-time = "2026-06-29T13:04:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f7/095b38eda4c70d03651c403f29a5590f16d12ddc5d544aac9f9cddf72277/jiter-0.16.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a111256a7193bea0759267b10385e5870949c239ed7b6ddbaaf57573edb38734", size = 549259, upload-time = "2026-06-29T13:04:33.721Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c5/6a0207d90e5f656d95af98ebd0934f382d37674416f215aeda2ff8063e51/jiter-0.16.0-cp314-cp314t-win32.whl", hash = "sha256:de5ba8763e56b793561f43bed197c9ea55776daa5e9a6b91eed68a909bc9cdbf", size = 206523, upload-time = "2026-06-29T13:04:35.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/31/c757d5f30a8980fd945ce7b98be10be9e4ff59c7c42f5fd86804c2e87db8/jiter-0.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b8a3f9a6008048fe9def7bf465180564a6e458047d2ce499149cfbe73c3ae9db", size = 200366, upload-time = "2026-06-29T13:04:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a2/d88de6d313d734a544a7901353ad5db67cb38dcfcd91713b7979dafc345d/jiter-0.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0fa25b09b13075c46f5bc174f2690525a925a4fc2f7c82969a2bbabff22386ce", size = 190516, upload-time = "2026-06-29T13:04:38.004Z" }, ] [[package]] @@ -860,6 +856,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/15/d75fd66aba116ce3732bb1050401394c5ec52074c4f7ee18db8838dd4667/onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7e823624b015ea879d976cbef8bfaed2f7e2cc233d7506860a76dd37f8f381", size = 16477261, upload-time = "2025-07-10T19:16:03.226Z" }, ] +[[package]] +name = "openai" +version = "2.53.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/cf/36e3e7235fdf6d125c052acc0970924611b17a20a4fe580596faf4566a65/openai-2.53.0.tar.gz", hash = "sha256:baf5802ad08980e1d9d561e1b996e800c8bcd14af5847c6d0e7a5cc59e4d4116", size = 1099435, upload-time = "2026-08-03T21:42:01.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/0f/cc6afea3542a5142c5d8fc8211c5e059a8375105d004a41dfa2c7948dbb0/openai-2.53.0-py3-none-any.whl", hash = "sha256:c694ffc747a3c4d1663ef2b07b811315a476164ee5efa3a993967349ebca7618", size = 1659829, upload-time = "2026-08-03T21:41:59.581Z" }, +] + [[package]] name = "opentelemetry-api" version = "1.35.0" @@ -1552,9 +1567,9 @@ name = "starting-codebase" version = "0.1.0" source = { virtual = "." } dependencies = [ - { name = "anthropic" }, { name = "chromadb" }, { name = "fastapi" }, + { name = "openai" }, { name = "python-dotenv" }, { name = "python-multipart" }, { name = "sentence-transformers" }, @@ -1563,9 +1578,9 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "anthropic", specifier = "==0.58.2" }, { name = "chromadb", specifier = "==1.0.15" }, { name = "fastapi", specifier = "==0.116.1" }, + { name = "openai", specifier = ">=2.53.0" }, { name = "python-dotenv", specifier = "==1.1.1" }, { name = "python-multipart", specifier = "==0.0.20" }, { name = "sentence-transformers", specifier = "==5.0.0" },