Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb2bfd53cc |
+2
-2
@@ -3,8 +3,8 @@ __pycache__
|
||||
*.pyc
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
**/venv
|
||||
**/.venv
|
||||
venv
|
||||
.venv
|
||||
|
||||
# System-specific files
|
||||
.DS_Store
|
||||
|
||||
@@ -16,7 +16,6 @@ and this project adheres to
|
||||
### Changed
|
||||
|
||||
- ⬆️(dependencies) upgrade Next.js 15 to 16, upgrade python dependencies
|
||||
- ⬆️(dependencies) switch base docker image to upgrade markitdown
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
+24
-29
@@ -1,12 +1,11 @@
|
||||
# Django conversations
|
||||
|
||||
# ---- base image to inherit from ----
|
||||
FROM python:3.13.3-slim AS base
|
||||
FROM python:3.13.3-alpine AS base
|
||||
|
||||
# Upgrade system packages to install security updates
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN apk update && \
|
||||
apk upgrade
|
||||
|
||||
# ---- Back-end builder image ----
|
||||
FROM base AS back-builder
|
||||
@@ -18,13 +17,14 @@ ENV UV_PYTHON_DOWNLOADS=0
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.9.26 /uv /uvx /bin/
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
# Install Rust and Cargo using Alpine's package manager
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
libffi-dev \
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
libxslt-dev \
|
||||
rust \
|
||||
cargo
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -52,10 +52,9 @@ FROM base AS link-collector
|
||||
ARG CONVERSATIONS_STATIC_ROOT=/data/static
|
||||
|
||||
# Install pango & rdfind
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libpango-1.0-0 \
|
||||
rdfind \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add \
|
||||
pango \
|
||||
rdfind
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -81,20 +80,18 @@ FROM base AS core
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
# Install required system libs
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libcairo2 \
|
||||
RUN apk add \
|
||||
cairo \
|
||||
file \
|
||||
fonts-noto-core \
|
||||
fonts-noto-color-emoji \
|
||||
font-noto \
|
||||
font-noto-emoji \
|
||||
gettext \
|
||||
libgdk-pixbuf-2.0-0 \
|
||||
libffi8 \
|
||||
gdk-pixbuf \
|
||||
libffi-dev \
|
||||
libxml2 \
|
||||
libxslt1.1 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
shared-mime-info \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
libxslt \
|
||||
pango \
|
||||
shared-mime-info
|
||||
|
||||
COPY ./docker/files/etc/mime.types /etc/mime.types
|
||||
|
||||
@@ -131,9 +128,7 @@ FROM core AS backend-development
|
||||
USER root:root
|
||||
|
||||
# Install psql
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
postgresql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add postgresql-client
|
||||
|
||||
# Install development dependencies
|
||||
RUN --mount=from=ghcr.io/astral-sh/uv:0.9.26,source=/uv,target=/bin/uv \
|
||||
@@ -154,8 +149,8 @@ CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
# ---- Production image ----
|
||||
FROM core AS backend-production
|
||||
|
||||
# Remove apt lists, we don't need them anymore
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
# Remove apk cache, we don't need it anymore
|
||||
RUN rm -rf /var/cache/apk/*
|
||||
|
||||
ARG CONVERSATIONS_STATIC_ROOT=/data/static
|
||||
|
||||
|
||||
@@ -117,20 +117,14 @@ class ConversationAgent(BaseAgent):
|
||||
"""Dynamic instruction function to set the expected language to use."""
|
||||
return f"Answer in {get_language_name(language).lower()}." if language else ""
|
||||
|
||||
def get_web_search_tool_name(self) -> str | None:
|
||||
def is_web_search_configured(self) -> bool:
|
||||
"""
|
||||
Get the name of the web search tool if available.
|
||||
Return True when a web search backend is configured on this model.
|
||||
|
||||
If several are available, return the first one found.
|
||||
|
||||
Warning, this says the tool is available, not that
|
||||
it (the tool/feature) is enabled for the current conversation.
|
||||
This does not mean web search is enabled for the current conversation
|
||||
(feature flags and runtime deps still apply).
|
||||
"""
|
||||
for toolset in self.toolsets:
|
||||
for tool in toolset.tools.values():
|
||||
if tool.name.startswith("web_search_"):
|
||||
return tool.name
|
||||
return None
|
||||
return bool(getattr(self.configuration, "web_search", None))
|
||||
|
||||
|
||||
@dataclasses.dataclass(init=False)
|
||||
|
||||
@@ -251,6 +251,7 @@ class AIAgentService: # pylint: disable=too-many-instance-attributes
|
||||
session=session,
|
||||
web_search_enabled=self._is_web_search_enabled and self._is_smart_search_enabled,
|
||||
)
|
||||
self._web_search_tool_registered = False
|
||||
|
||||
self.conversation_agent = ConversationAgent(
|
||||
model_hrid=self.model_hrid,
|
||||
@@ -440,8 +441,7 @@ class AIAgentService: # pylint: disable=too-many-instance-attributes
|
||||
logger.warning("Web search is forced but the feature is disabled, ignoring.")
|
||||
return False
|
||||
|
||||
web_search_tool_name = self.conversation_agent.get_web_search_tool_name()
|
||||
if not web_search_tool_name:
|
||||
if not self.conversation_agent.is_web_search_configured():
|
||||
logger.warning("Web search is forced but no web search tool is available, ignoring.")
|
||||
return False
|
||||
|
||||
@@ -450,9 +450,7 @@ class AIAgentService: # pylint: disable=too-many-instance-attributes
|
||||
@self.conversation_agent.instructions
|
||||
def force_web_search_prompt() -> str:
|
||||
"""Dynamic system prompt function to force web search."""
|
||||
return (
|
||||
f"You must call the {web_search_tool_name} tool before answering the user request."
|
||||
)
|
||||
return "You must call the web_search tool before answering the user request."
|
||||
|
||||
return True
|
||||
|
||||
@@ -699,6 +697,33 @@ class AIAgentService: # pylint: disable=too-many-instance-attributes
|
||||
"""Wrap the document_summarize tool to provide context and add the tool."""
|
||||
return await document_summarize(ctx, *args, **kwargs)
|
||||
|
||||
def _setup_web_search_tool(self) -> None:
|
||||
"""Register model-specific web search tool when configured."""
|
||||
if self._web_search_tool_registered:
|
||||
return
|
||||
configuration = self.conversation_agent.configuration
|
||||
if not getattr(configuration, "web_search", None):
|
||||
return
|
||||
|
||||
async def only_if_web_search_enabled(ctx, tool_def):
|
||||
"""Prepare function to include a tool only if web search is enabled in the context."""
|
||||
return tool_def if ctx.deps.web_search_enabled else None
|
||||
|
||||
web_search_impl = import_string(configuration.web_search)
|
||||
|
||||
@self.conversation_agent.tool(
|
||||
name="web_search",
|
||||
retries=1,
|
||||
prepare=only_if_web_search_enabled,
|
||||
description="Search the web for up-to-date information",
|
||||
)
|
||||
@functools.wraps(web_search_impl)
|
||||
async def web_search(ctx: RunContext, *args, **kwargs) -> ToolReturn:
|
||||
"""Wrap the web_search tool to provide context and add the tool."""
|
||||
return await web_search_impl(ctx, *args, **kwargs)
|
||||
|
||||
self._web_search_tool_registered = True
|
||||
|
||||
async def _handle_input_documents(
|
||||
self,
|
||||
input_documents: List[BinaryContent | DocumentUrl],
|
||||
@@ -1016,6 +1041,7 @@ class AIAgentService: # pylint: disable=too-many-instance-attributes
|
||||
conversation_has_documents = doc_result.has_documents
|
||||
|
||||
await self._agent_stop_streaming(force_cache_check=True)
|
||||
self._setup_web_search_tool()
|
||||
self._setup_web_search(force_web_search)
|
||||
|
||||
if await self._check_should_enable_rag(conversation_has_documents):
|
||||
|
||||
@@ -125,6 +125,7 @@ class LLModel(BaseModel):
|
||||
supports_streaming: bool | None = None
|
||||
system_prompt: SettingEnvValue
|
||||
tools: list[str]
|
||||
web_search: SettingEnvValue | None = None
|
||||
|
||||
@field_validator("tools", mode="before")
|
||||
@classmethod
|
||||
@@ -134,6 +135,14 @@ class LLModel(BaseModel):
|
||||
return _get_setting_or_env_or_value(value)
|
||||
return value
|
||||
|
||||
@field_validator("web_search", mode="before")
|
||||
@classmethod
|
||||
def validate_web_search(cls, value: str | None) -> str | None:
|
||||
"""Convert web_search path if it's a setting or environment variable."""
|
||||
if isinstance(value, str):
|
||||
return _get_setting_or_env_or_value(value)
|
||||
return value
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_provider_or_provider_name(self) -> Self:
|
||||
"""
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
# pylint:disable=protected-access
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from freezegun import freeze_time
|
||||
from pydantic_ai import Agent
|
||||
from pydantic_ai.models.openai import OpenAIChatModel
|
||||
from pydantic_ai.models.test import TestModel
|
||||
|
||||
from chat.agents.conversation import ConversationAgent
|
||||
from chat.clients.pydantic_ai import ContextDeps
|
||||
from chat.llm_configuration import LLModel, LLMProvider
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -87,47 +85,30 @@ def test_add_dynamic_system_prompt():
|
||||
assert agent._instructions[2]() == "Answer in french."
|
||||
|
||||
|
||||
def test_agent_get_web_search_tool_name(settings):
|
||||
"""Test the web_search_available method."""
|
||||
settings.AI_AGENT_TOOLS = ["get_current_weather", "web_search_albert_rag"]
|
||||
def test_agent_is_web_search_configured():
|
||||
"""Test whether web search backend is configured on the model."""
|
||||
agent = ConversationAgent(model_hrid="default-model")
|
||||
assert agent.get_web_search_tool_name() == "web_search_albert_rag"
|
||||
assert agent.is_web_search_configured() is False
|
||||
|
||||
settings.AI_AGENT_TOOLS = ["get_current_weather"]
|
||||
|
||||
def test_agent_is_web_search_configured_when_defined_in_model_config(settings):
|
||||
"""Web search is configured when LLModel.web_search is set."""
|
||||
settings.LLM_CONFIGURATIONS = {
|
||||
"default-model": LLModel(
|
||||
hrid="default-model",
|
||||
model_name="model-123",
|
||||
human_readable_name="Default Model",
|
||||
is_active=True,
|
||||
icon=None,
|
||||
system_prompt="You are a helpful assistant",
|
||||
tools=[],
|
||||
web_search="chat.tools.web_search_brave.web_search_brave_llm_context",
|
||||
provider=LLMProvider(
|
||||
hrid="default-provider",
|
||||
base_url="https://api.llm.com/v1/",
|
||||
api_key="test-key",
|
||||
),
|
||||
),
|
||||
}
|
||||
agent = ConversationAgent(model_hrid="default-model")
|
||||
assert agent.get_web_search_tool_name() is None
|
||||
|
||||
settings.AI_AGENT_TOOLS = ["get_current_weather", "web_search_tavily", "web_search_albert_rag"]
|
||||
agent = ConversationAgent(model_hrid="default-model")
|
||||
assert agent.get_web_search_tool_name() == "web_search_tavily"
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_web_search_tool_avalability(settings):
|
||||
"""Test the web search tool availability according to context."""
|
||||
responses.add(
|
||||
responses.POST,
|
||||
"https://api.tavily.com/search",
|
||||
json={"results": []},
|
||||
status=200,
|
||||
)
|
||||
context_deps = ContextDeps(conversation=None, user=None, web_search_enabled=True)
|
||||
|
||||
# No tools (context allows web search, but no tool configured)
|
||||
agent = ConversationAgent(model_hrid="default-model")
|
||||
with agent.override(model=TestModel(), deps=context_deps):
|
||||
response = agent.run_sync("What tools do you have?")
|
||||
assert response.output == "success (no tool calls)"
|
||||
|
||||
# Tool configured, context allows web search
|
||||
settings.AI_AGENT_TOOLS = ["web_search_tavily"]
|
||||
agent = ConversationAgent(model_hrid="default-model") # re-init to pick up new settings
|
||||
with agent.override(model=TestModel(), deps=context_deps):
|
||||
response = agent.run_sync("What tools do you have?")
|
||||
assert response.output == '{"web_search_tavily":[]}'
|
||||
|
||||
# Tool configured, context disables web search
|
||||
context_deps.web_search_enabled = False
|
||||
with agent.override(model=TestModel(), deps=context_deps):
|
||||
response = agent.run_sync("What tools do you have?")
|
||||
assert response.output == "success (no tool calls)"
|
||||
assert agent.is_web_search_configured() is True
|
||||
|
||||
@@ -23,7 +23,8 @@ def _llm_config_with_websearch(settings):
|
||||
is_active=True,
|
||||
icon=None,
|
||||
system_prompt="You are an amazing assistant.",
|
||||
tools=["web_search_brave_with_document_backend"],
|
||||
tools=[],
|
||||
web_search="chat.tools.web_search_brave.web_search_brave_llm_context",
|
||||
provider=LLMProvider(
|
||||
hrid="unused",
|
||||
base_url="https://example.com",
|
||||
@@ -48,6 +49,7 @@ def test_smart_search_disabled_suppresses_tool_at_runtime(_llm_config_with_webse
|
||||
if not service._is_smart_search_enabled and service._is_web_search_enabled:
|
||||
service._context_deps.web_search_enabled = False
|
||||
|
||||
service._setup_web_search_tool()
|
||||
with service.conversation_agent.override(model=TestModel(), deps=service._context_deps):
|
||||
response = service.conversation_agent.run_sync("Search the web for something.")
|
||||
|
||||
@@ -65,10 +67,11 @@ def test_smart_search_enabled_tool_is_called(_llm_config_with_websearch):
|
||||
assert service._is_smart_search_enabled is True
|
||||
assert service._context_deps.web_search_enabled is True
|
||||
|
||||
service._setup_web_search_tool()
|
||||
with service.conversation_agent.override(model=TestModel(), deps=service._context_deps):
|
||||
response = service.conversation_agent.run_sync("Search the web for something.")
|
||||
|
||||
assert "web_search_brave_with_document_backend" in response.output
|
||||
assert "web_search" in response.output
|
||||
|
||||
|
||||
def test_force_websearch_overrides_smart_search_disabled(_llm_config_with_websearch):
|
||||
@@ -82,14 +85,16 @@ def test_force_websearch_overrides_smart_search_disabled(_llm_config_with_websea
|
||||
assert service._is_smart_search_enabled is False
|
||||
assert service._context_deps.web_search_enabled is False
|
||||
|
||||
# Match _run_agent: register the tool first, then enable deps + forced prompt.
|
||||
service._setup_web_search_tool()
|
||||
service._setup_web_search(force_web_search=True)
|
||||
|
||||
web_search_tool_name = service.conversation_agent.get_web_search_tool_name()
|
||||
assert service.conversation_agent.is_web_search_configured() is True
|
||||
assert service._context_deps.web_search_enabled is True
|
||||
assert any(
|
||||
callable(instr) and web_search_tool_name in instr()
|
||||
callable(instr) and "web_search" in instr()
|
||||
for instr in service.conversation_agent._instructions
|
||||
)
|
||||
with service.conversation_agent.override(model=TestModel(), deps=service._context_deps):
|
||||
response = service.conversation_agent.run_sync("Search the web for something.")
|
||||
assert "web_search_brave_with_document_backend" in response.output
|
||||
assert "web_search" in response.output
|
||||
|
||||
@@ -21,13 +21,14 @@ from chat.tools.web_search_brave import (
|
||||
_extract_and_summarize_snippets_async,
|
||||
_fetch_and_extract_async,
|
||||
_fetch_and_store_async,
|
||||
_query_brave_api_async,
|
||||
_query_brave_llm_context_api_async,
|
||||
format_tool_return,
|
||||
web_search_brave,
|
||||
web_search_brave_with_document_backend,
|
||||
)
|
||||
|
||||
BRAVE_URL = "https://api.search.brave.com/res/v1/web/search"
|
||||
BRAVE_WEB_SEARCH_URL = "https://api.search.brave.com/res/v1/web/search"
|
||||
BRAVE_LLM_CONTEXT_URL = "https://api.search.brave.com/res/v1/llm/context"
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -67,7 +68,7 @@ def fixture_mocked_context():
|
||||
@respx.mock
|
||||
async def test_agent_web_search_brave_success_with_extra_snippets(mocked_context):
|
||||
"""Test when the Brave search returns results with extra_snippets."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -122,7 +123,7 @@ async def test_agent_web_search_brave_success_with_extra_snippets(mocked_context
|
||||
@respx.mock
|
||||
async def test_agent_web_search_brave_success_without_extra_snippets(mocked_context):
|
||||
"""Test when the Brave search returns results without extra_snippets."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -168,7 +169,7 @@ async def test_agent_web_search_brave_success_without_extra_snippets_summarizati
|
||||
"""Test when the Brave search returns results without extra_snippets with summarization."""
|
||||
settings.BRAVE_SUMMARIZATION_ENABLED = True
|
||||
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -216,7 +217,7 @@ async def test_agent_web_search_brave_success_without_extra_snippets_summarizati
|
||||
@respx.mock
|
||||
async def test_agent_web_search_brave_empty_results(mocked_context):
|
||||
"""Test when the Brave search returns no results."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={"web": {"results": []}},
|
||||
@@ -233,7 +234,7 @@ async def test_agent_web_search_brave_empty_results(mocked_context):
|
||||
@respx.mock
|
||||
async def test_agent_web_search_brave_http_error(mocked_context):
|
||||
"""Test handling of HTTP errors from Brave API."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=500,
|
||||
json={"error": "Internal Server Error"},
|
||||
@@ -256,7 +257,7 @@ async def test_agent_web_search_brave_params_exclude_none(settings, mocked_conte
|
||||
settings.BRAVE_SEARCH_COUNTRY = None
|
||||
settings.BRAVE_SEARCH_LANG = None
|
||||
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={"web": {"results": []}},
|
||||
@@ -290,7 +291,7 @@ async def test_agent_web_search_brave_params_exclude_none(settings, mocked_conte
|
||||
@respx.mock
|
||||
async def test_agent_web_search_brave_concurrent_processing(mocked_context):
|
||||
"""Test concurrent processing with asyncio.gather."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -401,7 +402,7 @@ async def test_extract_and_summarize_snippets_summarization_failure(settings):
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_success(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend with successful RAG search."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -452,7 +453,7 @@ async def test_web_search_brave_with_document_backend_success(mocked_context):
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_fetch_error(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend when document fetching fails."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -495,7 +496,7 @@ async def test_web_search_brave_with_document_backend_no_matching_rag_results(mo
|
||||
|
||||
This is actually a problematic scenario, but we want to ensure graceful handling.
|
||||
"""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -565,37 +566,37 @@ async def test_fetch_and_store_empty_document():
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_missing_web_key():
|
||||
"""Test _query_brave_api_async when response doesn't contain 'web' key."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
"""Test _query_brave_llm_context_api_async when response doesn't contain 'web' key."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={"error": "no web results"},
|
||||
)
|
||||
)
|
||||
|
||||
result = await _query_brave_api_async("query")
|
||||
result = await _query_brave_llm_context_api_async("query")
|
||||
assert not result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_missing_results_key():
|
||||
"""Test _query_brave_api_async when 'web' exists but 'results' is missing."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
"""Test _query_brave_llm_context_api_async when 'web' exists but 'results' is missing."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={"web": {}},
|
||||
)
|
||||
)
|
||||
result = await _query_brave_api_async("query")
|
||||
result = await _query_brave_llm_context_api_async("query")
|
||||
assert not result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_rate_limit():
|
||||
"""Test _query_brave_api_async handles 429 rate limit errors."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
"""Test _query_brave_llm_context_api_async handles 429 rate limit errors."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=429,
|
||||
json={"error": "Rate limit exceeded"},
|
||||
@@ -603,7 +604,7 @@ async def test_query_brave_api_rate_limit():
|
||||
)
|
||||
|
||||
with pytest.raises(ModelRetry) as exc:
|
||||
await _query_brave_api_async("query")
|
||||
await _query_brave_llm_context_api_async("query")
|
||||
|
||||
assert "rate limited" in str(exc.value).lower()
|
||||
|
||||
@@ -611,8 +612,8 @@ async def test_query_brave_api_rate_limit():
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_client_error():
|
||||
"""Test _query_brave_api_async handles 4xx client errors."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
"""Test _query_brave_llm_context_api_async handles 4xx client errors."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=400,
|
||||
json={"error": "Bad request"},
|
||||
@@ -620,7 +621,7 @@ async def test_query_brave_api_client_error():
|
||||
)
|
||||
|
||||
with pytest.raises(ModelCannotRetry) as exc:
|
||||
await _query_brave_api_async("query")
|
||||
await _query_brave_llm_context_api_async("query")
|
||||
|
||||
assert "client error" in str(exc.value).lower()
|
||||
|
||||
@@ -628,11 +629,11 @@ async def test_query_brave_api_client_error():
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_timeout():
|
||||
"""Test _query_brave_api_async handles timeout errors."""
|
||||
respx.get(BRAVE_URL).mock(side_effect=httpx.TimeoutException("Request timed out"))
|
||||
"""Test _query_brave_llm_context_api_async handles timeout errors."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(side_effect=httpx.TimeoutException("Request timed out"))
|
||||
|
||||
with pytest.raises(ModelRetry) as exc:
|
||||
await _query_brave_api_async("query")
|
||||
await _query_brave_llm_context_api_async("query")
|
||||
|
||||
assert "timed out" in str(exc.value).lower()
|
||||
|
||||
@@ -640,11 +641,11 @@ async def test_query_brave_api_timeout():
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_generic_http_error():
|
||||
"""Test _query_brave_api_async handles generic HTTP errors."""
|
||||
respx.get(BRAVE_URL).mock(side_effect=httpx.ConnectError("Connection failed"))
|
||||
"""Test _query_brave_llm_context_api_async handles generic HTTP errors."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(side_effect=httpx.ConnectError("Connection failed"))
|
||||
|
||||
with pytest.raises(ModelRetry) as exc:
|
||||
await _query_brave_api_async("query")
|
||||
await _query_brave_llm_context_api_async("query")
|
||||
|
||||
assert "connection error" in str(exc.value).lower()
|
||||
|
||||
@@ -652,11 +653,11 @@ async def test_query_brave_api_generic_http_error():
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_query_brave_api_unexpected_error():
|
||||
"""Test _query_brave_api_async handles unexpected errors."""
|
||||
respx.get(BRAVE_URL).mock(side_effect=ValueError("Unexpected value error"))
|
||||
"""Test _query_brave_llm_context_api_async handles unexpected errors."""
|
||||
respx.get(BRAVE_LLM_CONTEXT_URL).mock(side_effect=ValueError("Unexpected value error"))
|
||||
|
||||
with pytest.raises(ModelCannotRetry) as exc:
|
||||
await _query_brave_api_async("query")
|
||||
await _query_brave_llm_context_api_async("query")
|
||||
|
||||
assert "unexpected error" in str(exc.value).lower()
|
||||
|
||||
@@ -768,7 +769,7 @@ async def test_fetch_and_store_extraction_error():
|
||||
@respx.mock
|
||||
async def test_web_search_brave_mixed_results(mocked_context):
|
||||
"""Test web_search_brave with mixed results (some with snippets, some without)."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -812,7 +813,7 @@ async def test_web_search_brave_mixed_results(mocked_context):
|
||||
@respx.mock
|
||||
async def test_web_search_brave_extraction_fails_for_all(mocked_context):
|
||||
"""Test web_search_brave when extraction fails for all results without snippets."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -845,7 +846,7 @@ async def test_web_search_brave_extraction_fails_for_all(mocked_context):
|
||||
@respx.mock
|
||||
async def test_web_search_brave_model_cannot_retry_exception(mocked_context):
|
||||
"""Test web_search_brave handling of ModelCannotRetry from API."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=403,
|
||||
json={"error": "Forbidden"},
|
||||
@@ -863,7 +864,7 @@ async def test_web_search_brave_model_cannot_retry_exception(mocked_context):
|
||||
@respx.mock
|
||||
async def test_web_search_brave_unexpected_exception(mocked_context):
|
||||
"""Test web_search_brave handling of unexpected exceptions."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -894,7 +895,7 @@ async def test_web_search_brave_unexpected_exception(mocked_context):
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_empty_rag_results(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend when RAG search returns empty results."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -929,7 +930,7 @@ async def test_web_search_brave_with_document_backend_empty_rag_results(mocked_c
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_store_exception(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend when document store raises exception."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
@@ -958,7 +959,7 @@ async def test_web_search_brave_with_document_backend_store_exception(mocked_con
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_unexpected_exception(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend handling of unexpected exceptions."""
|
||||
respx.get(BRAVE_URL).mock(side_effect=TypeError("Unexpected type error"))
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(side_effect=TypeError("Unexpected type error"))
|
||||
|
||||
result = await web_search_brave_with_document_backend(mocked_context, "error query")
|
||||
assert result == (
|
||||
@@ -971,7 +972,7 @@ async def test_web_search_brave_with_document_backend_unexpected_exception(mocke
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_model_cannot_retry(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend handling of ModelCannotRetry."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=401,
|
||||
json={"error": "Unauthorized"},
|
||||
@@ -989,7 +990,7 @@ async def test_web_search_brave_with_document_backend_model_cannot_retry(mocked_
|
||||
@respx.mock
|
||||
async def test_web_search_brave_with_document_backend_rag_search_params(mocked_context):
|
||||
"""Test web_search_brave_with_document_backend passes correct parameters to RAG search."""
|
||||
respx.get(BRAVE_URL).mock(
|
||||
respx.get(BRAVE_WEB_SEARCH_URL).mock(
|
||||
return_value=httpx.Response(
|
||||
status_code=200,
|
||||
json={
|
||||
|
||||
@@ -1,46 +1,13 @@
|
||||
"""Tools for the chat agent."""
|
||||
|
||||
from pydantic_ai import Tool, ToolDefinition
|
||||
|
||||
from pydantic_ai import Tool # noqa: I001
|
||||
from .fake_current_weather import get_current_weather
|
||||
from .web_seach_albert_rag import web_search_albert_rag
|
||||
from .web_search_brave import web_search_brave, web_search_brave_with_document_backend
|
||||
from .web_search_tavily import web_search_tavily
|
||||
|
||||
|
||||
async def only_if_web_search_enabled(ctx, tool_def: ToolDefinition) -> ToolDefinition | None:
|
||||
"""Prepare function to include a tool only if web search is enabled in the context."""
|
||||
return tool_def if ctx.deps.web_search_enabled else None
|
||||
|
||||
|
||||
def get_pydantic_tools_by_name(name: str) -> Tool:
|
||||
"""Get a tool by its name."""
|
||||
tool_dict = {
|
||||
"get_current_weather": Tool(get_current_weather, takes_ctx=False),
|
||||
"web_search_brave": Tool(
|
||||
web_search_brave,
|
||||
takes_ctx=True,
|
||||
prepare=only_if_web_search_enabled,
|
||||
max_retries=2,
|
||||
),
|
||||
"web_search_brave_with_document_backend": Tool(
|
||||
web_search_brave_with_document_backend,
|
||||
takes_ctx=True,
|
||||
prepare=only_if_web_search_enabled,
|
||||
max_retries=2,
|
||||
),
|
||||
"web_search_tavily": Tool(
|
||||
web_search_tavily,
|
||||
takes_ctx=False,
|
||||
prepare=only_if_web_search_enabled,
|
||||
max_retries=2,
|
||||
),
|
||||
"web_search_albert_rag": Tool(
|
||||
web_search_albert_rag,
|
||||
takes_ctx=True,
|
||||
prepare=only_if_web_search_enabled,
|
||||
max_retries=2,
|
||||
),
|
||||
}
|
||||
|
||||
return tool_dict[name] # will raise on purpose if name is not found
|
||||
|
||||
@@ -142,23 +142,33 @@ async def _fetch_and_store_async(url: str, document_store, **kwargs) -> None:
|
||||
# Continue with other documents
|
||||
|
||||
|
||||
async def _query_brave_api_async(query: str) -> List[dict]:
|
||||
"""Query the Brave Search API and return the raw results."""
|
||||
url = "https://api.search.brave.com/res/v1/web/search"
|
||||
def _normalize_llm_context_results(json_response: dict) -> List[dict]:
|
||||
"""Normalize Brave LLM context payload into our common result shape."""
|
||||
generic_results = json_response.get("grounding", {}).get("generic", []) or []
|
||||
normalized_results: List[dict] = []
|
||||
for item in generic_results:
|
||||
item_url = item.get("url")
|
||||
if not item_url:
|
||||
continue
|
||||
|
||||
normalized_results.append(
|
||||
{
|
||||
"url": item_url,
|
||||
# Fallback to URL if no title is provided
|
||||
"title": item.get("title") or item_url,
|
||||
# `snippets` is already a list
|
||||
"snippets": item.get("snippets") or [],
|
||||
}
|
||||
)
|
||||
return normalized_results
|
||||
|
||||
|
||||
async def _query_brave_api_with_endpoint_async(url: str, data: dict) -> List[dict]:
|
||||
"""Query a Brave endpoint and return raw results normalized to our schema."""
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"X-Subscription-Token": settings.BRAVE_API_KEY,
|
||||
}
|
||||
data = {
|
||||
"q": query,
|
||||
"country": settings.BRAVE_SEARCH_COUNTRY,
|
||||
"search_lang": settings.BRAVE_SEARCH_LANG,
|
||||
"count": settings.BRAVE_MAX_RESULTS,
|
||||
"safesearch": settings.BRAVE_SEARCH_SAFE_SEARCH,
|
||||
"spellcheck": settings.BRAVE_SEARCH_SPELLCHECK,
|
||||
"result_filter": "web,faq,query",
|
||||
"extra_snippets": settings.BRAVE_SEARCH_EXTRA_SNIPPETS,
|
||||
}
|
||||
params = {k: v for k, v in data.items() if v is not None}
|
||||
|
||||
try:
|
||||
@@ -167,6 +177,12 @@ async def _query_brave_api_async(query: str) -> List[dict]:
|
||||
response.raise_for_status()
|
||||
json_response = response.json()
|
||||
|
||||
# LLM context API: results are under `grounding.generic`
|
||||
# See: https://api-dashboard.search.brave.com/documentation/services/llm-context
|
||||
if "grounding" in json_response:
|
||||
return _normalize_llm_context_results(json_response)
|
||||
|
||||
# Fallback for classic web search JSON shape
|
||||
# https://api-dashboard.search.brave.com/app/documentation/web-search/responses#Result
|
||||
return json_response.get("web", {}).get("results", [])
|
||||
|
||||
@@ -209,44 +225,99 @@ async def _query_brave_api_async(query: str) -> List[dict]:
|
||||
) from e
|
||||
|
||||
|
||||
async def _query_brave_llm_context_api_async(query: str) -> List[dict]:
|
||||
"""Query Brave LLM context endpoint and return normalized results."""
|
||||
logger.debug("Using LLM context endpoint")
|
||||
return await _query_brave_api_with_endpoint_async(
|
||||
"https://api.search.brave.com/res/v1/llm/context",
|
||||
{
|
||||
"q": query,
|
||||
"country": settings.BRAVE_SEARCH_COUNTRY,
|
||||
"search_lang": settings.BRAVE_SEARCH_LANG,
|
||||
"count": settings.BRAVE_MAX_RESULTS,
|
||||
"safesearch": settings.BRAVE_SEARCH_SAFE_SEARCH,
|
||||
"spellcheck": settings.BRAVE_SEARCH_SPELLCHECK,
|
||||
"result_filter": "web,faq,query",
|
||||
"extra_snippets": settings.BRAVE_SEARCH_EXTRA_SNIPPETS,
|
||||
"maximum_number_of_urls": settings.BRAVE_MAX_RESULTS,
|
||||
"maximum_number_of_tokens": settings.BRAVE_MAX_TOKENS,
|
||||
"maximum_number_of_snippets": settings.BRAVE_MAX_SNIPPETS,
|
||||
"maximum_number_of_snippets_per_url": settings.BRAVE_MAX_SNIPPETS_PER_URL,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def _query_brave_web_search_api_async(query: str) -> List[dict]:
|
||||
"""Query Brave classic web search endpoint and return normalized results."""
|
||||
logger.debug("Using classic web search endpoint")
|
||||
return await _query_brave_api_with_endpoint_async(
|
||||
"https://api.search.brave.com/res/v1/web/search",
|
||||
{
|
||||
"q": query,
|
||||
"country": settings.BRAVE_SEARCH_COUNTRY,
|
||||
"search_lang": settings.BRAVE_SEARCH_LANG,
|
||||
"count": settings.BRAVE_MAX_RESULTS,
|
||||
"safesearch": settings.BRAVE_SEARCH_SAFE_SEARCH,
|
||||
"spellcheck": settings.BRAVE_SEARCH_SPELLCHECK,
|
||||
"result_filter": "web,faq,query",
|
||||
"extra_snippets": settings.BRAVE_SEARCH_EXTRA_SNIPPETS,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def format_tool_return(raw_search_results: List[dict]) -> ToolReturn:
|
||||
"""Format the raw search results into a ToolReturn object."""
|
||||
"""Build the tool payload from Brave results.
|
||||
|
||||
Keep only sources that have non-empty snippets and prefer `snippets` over
|
||||
`extra_snippets` when both are present.
|
||||
"""
|
||||
formatted_results = {}
|
||||
sources = set()
|
||||
|
||||
for idx, result in enumerate(raw_search_results):
|
||||
logger.debug("Formatting result: %s", result)
|
||||
snippets = result.get("snippets") or result.get("extra_snippets") or []
|
||||
if not snippets:
|
||||
continue
|
||||
|
||||
formatted_results[str(idx)] = {
|
||||
"url": result["url"],
|
||||
"title": result["title"],
|
||||
"snippets": snippets,
|
||||
}
|
||||
sources.add(result["url"])
|
||||
|
||||
return ToolReturn(
|
||||
# Format return value "mistral-like": https://docs.mistral.ai/capabilities/citations/
|
||||
return_value={
|
||||
str(idx): {
|
||||
"url": result["url"],
|
||||
"title": result["title"],
|
||||
"snippets": result.get("extra_snippets", []),
|
||||
}
|
||||
for idx, result in enumerate(raw_search_results)
|
||||
if result.get("extra_snippets", [])
|
||||
},
|
||||
metadata={
|
||||
"sources": {
|
||||
result["url"] for result in raw_search_results if result.get("extra_snippets", [])
|
||||
}
|
||||
},
|
||||
return_value=formatted_results,
|
||||
metadata={"sources": sources},
|
||||
)
|
||||
|
||||
|
||||
@last_model_retry_soft_fail
|
||||
async def web_search_brave(_ctx: RunContext, query: str) -> ToolReturn:
|
||||
"""
|
||||
Search the web for up-to-date information
|
||||
Search the web for up-to-date information.
|
||||
This function use the classic websearch endpoint of the Brave API.
|
||||
URLs are then fetched and extracted using trafilatura.
|
||||
The extracted text is then summarized using the LLM summarization agent.
|
||||
The results are then formatted and returned.
|
||||
|
||||
Args:
|
||||
_ctx (RunContext): The run context, used by the wrapper.
|
||||
query (str): The query to search for.
|
||||
"""
|
||||
logger.debug("Starting classic web search without RAG backend for query: %s", query)
|
||||
try:
|
||||
raw_search_results = await _query_brave_api_async(query)
|
||||
raw_search_results = await _query_brave_web_search_api_async(query)
|
||||
|
||||
await sync_to_async(reset_caches)() # Clear trafilatura caches to avoid memory bloat/leaks
|
||||
|
||||
# Parallelize fetch/extract for results that don't include extra_snippets
|
||||
# Parallelize fetch/extract only for results that don't already include any snippets
|
||||
# (neither Brave `snippets` nor `extra_snippets`).
|
||||
to_process = [
|
||||
(idx, r) for idx, r in enumerate(raw_search_results) if not r.get("extra_snippets")
|
||||
(idx, r)
|
||||
for idx, r in enumerate(raw_search_results)
|
||||
if not r.get("extra_snippets") and not r.get("snippets")
|
||||
]
|
||||
|
||||
if to_process:
|
||||
@@ -283,18 +354,45 @@ async def web_search_brave(_ctx: RunContext, query: str) -> ToolReturn:
|
||||
) from exc
|
||||
|
||||
|
||||
@last_model_retry_soft_fail
|
||||
async def web_search_brave_llm_context(_ctx: RunContext, query: str) -> ToolReturn:
|
||||
"""
|
||||
Search the web using Brave LLM context endpoint (no RAG post-processing).
|
||||
This function use the LLM context endpoint of the Brave API.
|
||||
The results are then formatted and returned.
|
||||
"""
|
||||
logger.debug("Starting web search with LLM context endpoint for query: %s", query)
|
||||
try:
|
||||
raw_search_results = await _query_brave_llm_context_api_async(query)
|
||||
formatted_result = format_tool_return(raw_search_results)
|
||||
if not formatted_result.return_value:
|
||||
raise ModelRetry("No valid search results were extracted from Brave LLM context.")
|
||||
return formatted_result
|
||||
except (ModelCannotRetry, ModelRetry):
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.exception("Unexpected error in web_search_brave_llm_context: %s", exc)
|
||||
raise ModelCannotRetry(
|
||||
f"An unexpected error occurred during web search: {type(exc).__name__}. "
|
||||
"You must explain this to the user and not try to answer based on your knowledge."
|
||||
) from exc
|
||||
|
||||
|
||||
@last_model_retry_soft_fail
|
||||
async def web_search_brave_with_document_backend(ctx: RunContext, query: str) -> ToolReturn:
|
||||
"""
|
||||
Search the web for up-to-date information using RAG backend
|
||||
Search the web for up-to-date information using RAG backend.
|
||||
URLs are then fetched and extracted using trafilatura.
|
||||
The extracted text is then stored in a temporary document store for RAG search.
|
||||
The RAG search is then performed and the results are returned.
|
||||
|
||||
Args:
|
||||
ctx (RunContext): The run context containing the conversation.
|
||||
query (str): The query to search for.
|
||||
"""
|
||||
logger.info("Starting web search with RAG backend for query: %s", query)
|
||||
logger.debug("Starting web search with RAG backend for query: %s", query)
|
||||
try:
|
||||
raw_search_results = await _query_brave_api_async(query)
|
||||
raw_search_results = await _query_brave_web_search_api_async(query)
|
||||
|
||||
# Clear trafilatura caches in thread pool to avoid blocking
|
||||
loop = asyncio.get_event_loop()
|
||||
@@ -328,7 +426,7 @@ async def web_search_brave_with_document_backend(ctx: RunContext, query: str) ->
|
||||
session=ctx.deps.session,
|
||||
user_sub=ctx.deps.user.sub,
|
||||
)
|
||||
logger.info("RAG search returned: %s", rag_results)
|
||||
logger.debug("RAG search returned: %s", rag_results)
|
||||
|
||||
ctx.usage += RunUsage(
|
||||
input_tokens=rag_results.usage.prompt_tokens,
|
||||
|
||||
@@ -74,3 +74,20 @@ class BraveSettings:
|
||||
environ_name="BRAVE_SEARCH_EXTRA_SNIPPETS",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
# LLM context endpoint limits
|
||||
BRAVE_MAX_TOKENS = values.IntegerValue(
|
||||
default=8192,
|
||||
environ_name="BRAVE_MAX_TOKENS",
|
||||
environ_prefix=None,
|
||||
)
|
||||
BRAVE_MAX_SNIPPETS = values.IntegerValue(
|
||||
default=50,
|
||||
environ_name="BRAVE_MAX_SNIPPETS",
|
||||
environ_prefix=None,
|
||||
)
|
||||
BRAVE_MAX_SNIPPETS_PER_URL = values.IntegerValue(
|
||||
default=10,
|
||||
environ_name="BRAVE_MAX_SNIPPETS_PER_URL",
|
||||
environ_prefix=None,
|
||||
)
|
||||
|
||||
@@ -51,7 +51,7 @@ dependencies = [
|
||||
"langfuse==3.10.0",
|
||||
"lxml==5.4.0",
|
||||
"markdown==3.10",
|
||||
"markitdown==0.1.5",
|
||||
"markitdown==0.0.2",
|
||||
"mozilla-django-oidc==4.0.1",
|
||||
"nested-multipart-parser==1.6.0",
|
||||
"posthog==7.0.0",
|
||||
|
||||
Generated
+322
-91
@@ -86,6 +86,89 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "audioop-lts"
|
||||
version = "0.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azure-ai-documentintelligence"
|
||||
version = "1.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-core" },
|
||||
{ name = "isodate" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/44/7b/8115cd713e2caa5e44def85f2b7ebd02a74ae74d7113ba20bdd41fd6dd80/azure_ai_documentintelligence-1.0.2.tar.gz", hash = "sha256:4d75a2513f2839365ebabc0e0e1772f5601b3a8c9a71e75da12440da13b63484", size = 170940, upload-time = "2025-03-27T02:46:20.606Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/75/c9ec040f23082f54ffb1977ff8f364c2d21c79a640a13d1c1809e7fd6b1a/azure_ai_documentintelligence-1.0.2-py3-none-any.whl", hash = "sha256:e1fb446abbdeccc9759d897898a0fe13141ed29f9ad11fc705f951925822ed59", size = 106005, upload-time = "2025-03-27T02:46:22.356Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azure-core"
|
||||
version = "1.38.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "requests" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/9b/23893febea484ad8183112c9419b5eb904773adb871492b5fa8ff7b21e09/azure_core-1.38.1.tar.gz", hash = "sha256:9317db1d838e39877eb94a2240ce92fa607db68adf821817b723f0d679facbf6", size = 363323, upload-time = "2026-02-11T02:03:06.051Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/88/aaea2ad269ce70b446660371286272c1f6ba66541a7f6f635baf8b0db726/azure_core-1.38.1-py3-none-any.whl", hash = "sha256:69f08ee3d55136071b7100de5b198994fc1c5f89d2b91f2f43156d20fcf200a4", size = 217930, upload-time = "2026-02-11T02:03:07.548Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azure-identity"
|
||||
version = "1.25.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-core" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "msal" },
|
||||
{ name = "msal-extensions" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c2/3a/439a32a5e23e45f6a91f0405949dc66cfe6834aba15a430aebfc063a81e7/azure_identity-1.25.2.tar.gz", hash = "sha256:030dbaa720266c796221c6cdbd1999b408c079032c919fef725fcc348a540fe9", size = 284709, upload-time = "2026-02-11T01:55:42.323Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/77/f658c76f9e9a52c784bd836aaca6fd5b9aae176f1f53273e758a2bcda695/azure_identity-1.25.2-py3-none-any.whl", hash = "sha256:1b40060553d01a72ba0d708b9a46d0f61f56312e215d8896d836653ffdc6753d", size = 191423, upload-time = "2026-02-11T01:55:44.245Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.17.0"
|
||||
@@ -298,6 +381,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cobble"
|
||||
version = "0.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/7a/a507c709be2c96e1bb6102eb7b7f4026c5e5e223ef7d745a17d239e9d844/cobble-0.1.4.tar.gz", hash = "sha256:de38be1539992c8a06e569630717c485a5f91be2192c461ea2b220607dfa78aa", size = 3805, upload-time = "2024-06-01T18:11:09.528Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/e1/3714a2f371985215c219c2a70953d38e3eed81ef165aed061d21de0e998b/cobble-0.1.4-py3-none-any.whl", hash = "sha256:36c91b1655e599fd428e2b95fdd5f0da1ca2e9f1abb0bc871dec21a0e78a2b44", size = 3984, upload-time = "2024-06-01T18:11:07.911Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
@@ -307,18 +399,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coloredlogs"
|
||||
version = "15.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "humanfriendly" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "conversations"
|
||||
version = "0.0.14"
|
||||
@@ -429,7 +509,7 @@ requires-dist = [
|
||||
{ name = "langfuse", specifier = "==3.10.0" },
|
||||
{ name = "lxml", specifier = "==5.4.0" },
|
||||
{ name = "markdown", specifier = "==3.10" },
|
||||
{ name = "markitdown", specifier = "==0.1.5" },
|
||||
{ name = "markitdown", specifier = "==0.0.2" },
|
||||
{ name = "mozilla-django-oidc", specifier = "==4.0.1" },
|
||||
{ name = "nested-multipart-parser", specifier = "==1.6.0" },
|
||||
{ name = "posthog", specifier = "==7.0.0" },
|
||||
@@ -866,6 +946,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/90/be137d0592501ec08d4a00c8430121e95dfe6c8114a1d4436b5cdd6b9803/easy_thumbnails-2.10.1-py3-none-any.whl", hash = "sha256:24462d63dd31543ef1585538b2bfefe0db96d3409bb431c70b81548fb2cfc5be", size = 79695, upload-time = "2025-08-17T20:49:09.119Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" }
|
||||
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.3.1"
|
||||
@@ -917,14 +1006,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/ec/91a434c8a53d40c3598966621dea9c50512bec6ce8e76fa1751015e74cef/faker-40.1.2-py3-none-any.whl", hash = "sha256:93503165c165d330260e4379fd6dc07c94da90c611ed3191a0174d2ab9966a42", size = 1985633, upload-time = "2026-01-13T20:51:47.982Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flatbuffers"
|
||||
version = "25.12.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freezegun"
|
||||
version = "1.5.5"
|
||||
@@ -966,7 +1047,6 @@ wheels = [
|
||||
name = "griffelib"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/06/eccbd311c9e2b3ca45dbc063b93134c57a1ccc7607c5e545264ad092c4a9/griffelib-2.0.0.tar.gz", hash = "sha256:e504d637a089f5cab9b5daf18f7645970509bf4f53eda8d79ed71cce8bd97934", size = 166312, upload-time = "2026-03-23T21:06:55.954Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" },
|
||||
]
|
||||
@@ -1045,18 +1125,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humanfriendly"
|
||||
version = "10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icdiff"
|
||||
version = "2.0.7"
|
||||
@@ -1160,6 +1228,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isodate"
|
||||
version = "0.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "6.1.0"
|
||||
@@ -1399,21 +1476,15 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magika"
|
||||
version = "0.6.3"
|
||||
name = "mammoth"
|
||||
version = "1.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "numpy" },
|
||||
{ name = "onnxruntime" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "cobble" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a3/f3/3d1dcdd7b9c41d589f5cff252d32ed91cdf86ba84391cfc81d9d8773571d/magika-0.6.3.tar.gz", hash = "sha256:7cc52aa7359af861957043e2bf7265ed4741067251c104532765cd668c0c0cb1", size = 3042784, upload-time = "2025-10-30T15:22:34.499Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/3c/a58418d2af00f2da60d4a51e18cd0311307b72d48d2fffec36a97b4a5e44/mammoth-1.11.0.tar.gz", hash = "sha256:a0f59e442f34d5b6447f4b0999306cbf3e67aaabfa8cb516f878fb1456744637", size = 53142, upload-time = "2025-09-19T10:35:20.373Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/e4/35c323beb3280482c94299d61626116856ac2d4ec16ecef50afc4fdd4291/magika-0.6.3-py3-none-any.whl", hash = "sha256:eda443d08006ee495e02083b32e51b98cb3696ab595a7d13900d8e2ef506ec9d", size = 2969474, upload-time = "2025-10-30T15:22:25.298Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8f/132b0d7cd51c02c39fd52658a5896276c30c8cc2fd453270b19db8c40f7e/magika-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:86901e64b05dde5faff408c9b8245495b2e1fd4c226e3393d3d2a3fee65c504b", size = 13358841, upload-time = "2025-10-30T15:22:27.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/03/5ed859be502903a68b7b393b17ae0283bf34195cfcca79ce2dc25b9290e7/magika-0.6.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:3d9661eedbdf445ac9567e97e7ceefb93545d77a6a32858139ea966b5806fb64", size = 15367335, upload-time = "2025-10-30T15:22:29.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/9e/f8ee7d644affa3b80efdd623a3d75865c8f058f3950cb87fb0c48e3559bc/magika-0.6.3-py3-none-win_amd64.whl", hash = "sha256:e57f75674447b20cab4db928ae58ab264d7d8582b55183a0b876711c2b2787f3", size = 12692831, upload-time = "2025-10-30T15:22:32.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/54/2e39566a131b13f6d8d193f974cb6a34e81bb7cc2fa6f7e03de067b36588/mammoth-1.11.0-py2.py3-none-any.whl", hash = "sha256:c077ab0d450bd7c0c6ecd529a23bf7e0fa8190c929e28998308ff4eada3f063b", size = 54752, upload-time = "2025-09-19T10:35:18.699Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1452,19 +1523,33 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "markitdown"
|
||||
version = "0.1.5"
|
||||
version = "0.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-ai-documentintelligence" },
|
||||
{ name = "azure-identity" },
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "defusedxml" },
|
||||
{ name = "magika" },
|
||||
{ name = "mammoth" },
|
||||
{ name = "markdownify" },
|
||||
{ name = "numpy" },
|
||||
{ name = "olefile" },
|
||||
{ name = "openai" },
|
||||
{ name = "openpyxl" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pathvalidate" },
|
||||
{ name = "pdfminer-six" },
|
||||
{ name = "puremagic" },
|
||||
{ name = "pydub" },
|
||||
{ name = "python-pptx" },
|
||||
{ name = "requests" },
|
||||
{ name = "speechrecognition" },
|
||||
{ name = "xlrd" },
|
||||
{ name = "youtube-transcript-api" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/93/3b93c291c99d09f64f7535ba74c1c6a3507cf49cffd38983a55de6f834b6/markitdown-0.1.5.tar.gz", hash = "sha256:4c956ff1528bf15e1814542035ec96e989206d19d311bb799f4df973ecafc31a", size = 45099, upload-time = "2026-02-20T19:45:23.886Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/bf/8d5ee5ceef8dc175b4c8714e6f5170e3bc38862eeb6a45f29433c64611c1/markitdown-0.0.2.tar.gz", hash = "sha256:678fc2c9bc4eded0941c01155251982cab1f86c8148c8ac7ca3869116d3d49d7", size = 21008, upload-time = "2025-03-08T00:21:55.828Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/8b/fd7e042455a829a1ede0bc8e9e3061aa6c7c4cf745385526ef62ff1b5a5b/markitdown-0.1.5-py3-none-any.whl", hash = "sha256:5180a9a841e20fc01c2c09dbc5d039638429bbebcdc2af1b2615c3c427840434", size = 63402, upload-time = "2026-02-20T19:45:27.195Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/f9/d0d6c6a11a16e2e22faf79bb4a86764110a8b391a510d6bd5cbb4ec57f59/markitdown-0.0.2-py3-none-any.whl", hash = "sha256:6ee5a6b70fbc36ba1586a40d5ab1ea8ea7f89bf37ebbaaa11013e026dc55b16e", size = 21278, upload-time = "2025-03-08T00:21:56.851Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1575,12 +1660,29 @@ dill = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpmath"
|
||||
version = "1.3.0"
|
||||
name = "msal"
|
||||
version = "1.34.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "pyjwt", extra = ["crypto"] },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msal-extensions"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "msal" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1635,24 +1737,12 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onnxruntime"
|
||||
version = "1.20.1"
|
||||
name = "olefile"
|
||||
version = "0.47"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coloredlogs" },
|
||||
{ name = "flatbuffers" },
|
||||
{ name = "numpy" },
|
||||
{ name = "packaging" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "sympy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/1b/077b508e3e500e1629d366249c3ccb32f95e50258b231705c09e3c7a4366/olefile-0.47.zip", hash = "sha256:599383381a0bf3dfbd932ca0ca6515acd174ed48870cbf7fee123d698c192c1c", size = 112240, upload-time = "2023-12-01T16:22:53.025Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/71/c5d980ac4189589267a06f758bd6c5667d07e55656bed6c6c0580733ad07/onnxruntime-1.20.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:cc01437a32d0042b606f462245c8bbae269e5442797f6213e36ce61d5abdd8cc", size = 31007574, upload-time = "2024-11-21T00:49:23.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/0d/13bbd9489be2a6944f4a940084bfe388f1100472f38c07080a46fbd4ab96/onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb44b08e017a648924dbe91b82d89b0c105b1adcfe31e90d1dc06b8677ad37be", size = 11951459, upload-time = "2024-11-21T00:49:26.269Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ea/4454ae122874fd52bbb8a961262de81c5f932edeb1b72217f594c700d6ef/onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda6aebdf7917c1d811f21d41633df00c58aff2bef2f598f69289c1f1dabc4b3", size = 13331620, upload-time = "2024-11-21T00:49:28.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/e0/50db43188ca1c945decaa8fc2a024c33446d31afed40149897d4f9de505f/onnxruntime-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:d30367df7e70f1d9fc5a6a68106f5961686d39b54d3221f760085524e8d38e16", size = 11331758, upload-time = "2024-11-21T00:49:31.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/55/3821c5fd60b52a6c82a00bba18531793c93c4addfe64fbf061e235c5617a/onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9158465745423b2b5d97ed25aa7740c7d38d2993ee2e5c3bfacb0c4145c49d8", size = 11950342, upload-time = "2024-11-21T00:49:34.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/56/fd990ca222cef4f9f4a9400567b9a15b220dee2eafffb16b2adbc55c8281/onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0df6f2df83d61f46e842dbcde610ede27218947c33e994545a22333491e72a3b", size = 13337040, upload-time = "2024-11-21T00:49:37.271Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/d3/b64c356a907242d719fc668b71befd73324e47ab46c8ebbbede252c154b2/olefile-0.47-py2.py3-none-any.whl", hash = "sha256:543c7da2a7adadf21214938bb79c83ea12b473a4b6ee4ad4bf854e7715e13d1f", size = 114565, upload-time = "2023-12-01T16:22:51.518Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1674,6 +1764,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/df/c306f7375d42bafb379934c2df4c2fa3964656c8c782bac75ee10c102818/openai-2.15.0-py3-none-any.whl", hash = "sha256:6ae23b932cd7230f7244e52954daa6602716d6b9bf235401a107af731baea6c3", size = 1067879, upload-time = "2026-01-09T22:10:06.446Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpyxl"
|
||||
version = "3.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "et-xmlfile" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" }
|
||||
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.39.1"
|
||||
@@ -1805,6 +1907,34 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.5"
|
||||
@@ -1814,6 +1944,28 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathvalidate"
|
||||
version = "3.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pdfminer-six"
|
||||
version = "20260107"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "charset-normalizer" },
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/a4/5cec1112009f0439a5ca6afa8ace321f0ab2f48da3255b7a1c8953014670/pdfminer_six-20260107.tar.gz", hash = "sha256:96bfd431e3577a55a0efd25676968ca4ce8fd5b53f14565f85716ff363889602", size = 8512094, upload-time = "2026-01-07T13:29:12.937Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/8b/28c4eaec9d6b036a52cb44720408f26b1a143ca9bce76cc19e8f5de00ab4/pdfminer_six-20260107-py3-none-any.whl", hash = "sha256:366585ba97e80dffa8f00cebe303d2f381884d8637af4ce422f1df3ef38111a9", size = 6592252, upload-time = "2026-01-07T13:29:10.742Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pexpect"
|
||||
version = "4.9.0"
|
||||
@@ -1981,6 +2133,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "puremagic"
|
||||
version = "1.30"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dd/7f/9998706bc516bdd664ccf929a1da6c6e5ee06e48f723ce45aae7cf3ff36e/puremagic-1.30.tar.gz", hash = "sha256:f9ff7ac157d54e9cf3bff1addfd97233548e75e685282d84ae11e7ffee1614c9", size = 314785, upload-time = "2025-07-04T18:48:36.061Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/91/ed/1e347d85d05b37a8b9a039ca832e5747e1e5248d0bd66042783ef48b4a37/puremagic-1.30-py3-none-any.whl", hash = "sha256:5eeeb2dd86f335b9cfe8e205346612197af3500c6872dffebf26929f56e9d3c1", size = 43304, upload-time = "2025-07-04T18:48:34.801Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "3.0"
|
||||
@@ -2112,6 +2273,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydub"
|
||||
version = "0.25.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyfakefs"
|
||||
version = "5.10.2"
|
||||
@@ -2209,15 +2379,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/f4/75543fa802b86e72f87e9395440fe1a89a6d149887e3e55745715c3352ac/pypdf-6.9.1-py3-none-any.whl", hash = "sha256:f35a6a022348fae47e092a908339a8f3dc993510c026bb39a96718fc7185e89f", size = 333661, upload-time = "2026-03-17T10:46:06.286Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyreadline3"
|
||||
version = "3.5.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.1"
|
||||
@@ -2338,6 +2499,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-pptx"
|
||||
version = "1.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "lxml" },
|
||||
{ name = "pillow" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "xlsxwriter" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
@@ -2634,6 +2810,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "speechrecognition"
|
||||
version = "3.14.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "audioop-lts" },
|
||||
{ name = "standard-aifc" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/ab/bb1c60e7bfd6b7a736f76439b78ebbfb5e92a81b626b6e94a87e166f2ea4/speechrecognition-3.14.5.tar.gz", hash = "sha256:2d185192986b9b67a1502825a330e971f59a2cae0262f727a19ad1f6b586d00a", size = 32859817, upload-time = "2025-12-31T11:25:46.518Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/a7/903429719d39ac2c42aa37086c90e816d883560f13c87d51f09a2962e021/speechrecognition-3.14.5-py3-none-any.whl", hash = "sha256:0c496d74e9f29b1daadb0d96f5660f47563e42bf09316dacdd57094c5095977e", size = 32856308, upload-time = "2025-12-31T11:25:41.161Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.5"
|
||||
@@ -2670,6 +2860,28 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "standard-aifc"
|
||||
version = "3.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "audioop-lts" },
|
||||
{ name = "standard-chunk" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/53/6050dc3dde1671eb3db592c13b55a8005e5040131f7509cef0215212cb84/standard_aifc-3.13.0.tar.gz", hash = "sha256:64e249c7cb4b3daf2fdba4e95721f811bde8bdfc43ad9f936589b7bb2fae2e43", size = 15240, upload-time = "2024-10-30T16:01:31.772Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/52/5fbb203394cc852334d1575cc020f6bcec768d2265355984dfd361968f36/standard_aifc-3.13.0-py3-none-any.whl", hash = "sha256:f7ae09cc57de1224a0dd8e3eb8f73830be7c3d0bc485de4c1f82b4a7f645ac66", size = 10492, upload-time = "2024-10-30T16:01:07.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "standard-chunk"
|
||||
version = "3.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/06/ce1bb165c1f111c7d23a1ad17204d67224baa69725bb6857a264db61beaf/standard_chunk-3.13.0.tar.gz", hash = "sha256:4ac345d37d7e686d2755e01836b8d98eda0d1a3ee90375e597ae43aaf064d654", size = 4672, upload-time = "2024-10-30T16:18:28.326Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/90/a5c1084d87767d787a6caba615aa50dc587229646308d9420c960cb5e4c0/standard_chunk-3.13.0-py3-none-any.whl", hash = "sha256:17880a26c285189c644bd5bd8f8ed2bdb795d216e3293e6dbe55bbd848e2982c", size = 4944, upload-time = "2024-10-30T16:18:26.694Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "starlette"
|
||||
version = "0.52.1"
|
||||
@@ -2682,18 +2894,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sympy"
|
||||
version = "1.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mpmath" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiktoken"
|
||||
version = "0.12.0"
|
||||
@@ -2908,6 +3108,37 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xlrd"
|
||||
version = "2.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xlsxwriter"
|
||||
version = "3.2.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "youtube-transcript-api"
|
||||
version = "1.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "defusedxml" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/32/f60d87a99c05a53604c58f20f670c7ea6262b55e0bbeb836ffe4550b248b/youtube_transcript_api-1.0.3.tar.gz", hash = "sha256:902baf90e7840a42e1e148335e09fe5575dbff64c81414957aea7038e8a4db46", size = 2153252, upload-time = "2025-03-25T18:14:21.119Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/44/40c03bb0f8bddfb9d2beff2ed31641f52d96c287ba881d20e0c074784ac2/youtube_transcript_api-1.0.3-py3-none-any.whl", hash = "sha256:d1874e57de65cf14c9d7d09b2b37c814d6287fa0e770d4922c4cd32a5b3f6c47", size = 2169911, upload-time = "2025-03-25T18:14:19.416Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.23.0"
|
||||
|
||||
Reference in New Issue
Block a user