mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
Merge pull request #50463 from BerriAI/litellm_backport_50300_lts_1_108_x
fix(guardrails): backport #50300 to lts/1.108.x
This commit is contained in:
commit
26852a46f5
7 changed files with 921 additions and 224 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import contextvars
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
from collections.abc import Iterable, Mapping, Sequence
|
||||
|
|
@ -9,6 +10,7 @@ from types import MappingProxyType
|
|||
from typing import TYPE_CHECKING, Any, ClassVar, Final, Literal, Optional, cast, get_args
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel, TypeAdapter
|
||||
|
||||
from litellm._internal_context import with_service_target
|
||||
from litellm._logging import verbose_logger
|
||||
|
|
@ -71,6 +73,7 @@ GUARDRAIL_SESSIONS_TARGET: Final = "guardrail_sessions"
|
|||
_PRE_CALL_EXECUTED_TOKEN: Final = secrets.token_hex(16)
|
||||
_PRE_CALL_RAISED_TOKEN: Final = secrets.token_hex(16)
|
||||
_DEPLOYMENT_HOOK_RAN_TOKEN: Final = secrets.token_hex(16)
|
||||
_POST_CALL_CHECKED_TOKEN: Final = secrets.token_hex(16)
|
||||
|
||||
_GUARDRAIL_BLOCK_STATUS_CODES: Final = frozenset({400, 403, 422})
|
||||
|
||||
|
|
@ -239,6 +242,98 @@ def _request_carries_marker(data: Mapping[str, object], marker: str | None) -> b
|
|||
return False
|
||||
|
||||
|
||||
def _record_guardrail_marker(
|
||||
data: dict[str, object], # mutable-ok: the request payload the marker is recorded on
|
||||
marker: str | None,
|
||||
) -> None:
|
||||
if marker is None:
|
||||
return
|
||||
for meta_key in ("metadata", "litellm_metadata"):
|
||||
meta = data.get(meta_key)
|
||||
if isinstance(meta, dict):
|
||||
executed = meta.get(PRE_CALL_EXECUTED_GUARDRAILS_KEY)
|
||||
if isinstance(executed, list):
|
||||
if marker not in executed:
|
||||
executed.append(marker)
|
||||
else:
|
||||
meta[PRE_CALL_EXECUTED_GUARDRAILS_KEY] = [marker]
|
||||
return
|
||||
data["metadata"] = {PRE_CALL_EXECUTED_GUARDRAILS_KEY: [marker]}
|
||||
|
||||
|
||||
_RESPONSE_METADATA_KEYS: Final = frozenset(
|
||||
{
|
||||
"id",
|
||||
"created",
|
||||
"created_at",
|
||||
"model",
|
||||
"usage",
|
||||
"usage_info",
|
||||
"system_fingerprint",
|
||||
"_hidden_params",
|
||||
"_response_ms",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _response_fingerprint(response: object) -> str | None:
|
||||
"""Digest of everything but metadata set between layers, so an unknown field counts as content."""
|
||||
try:
|
||||
fields: Final[Mapping[str, object]] = (
|
||||
response.model_dump(mode="json")
|
||||
if isinstance(response, BaseModel)
|
||||
else _REQUEST_FIELDS.validate_python(response)
|
||||
)
|
||||
content: Final = {key: value for key, value in fields.items() if key not in _RESPONSE_METADATA_KEYS}
|
||||
if not content:
|
||||
return None
|
||||
return hashlib.sha256(
|
||||
json.dumps(content, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
|
||||
).hexdigest()
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
_RESPONSE_GUARDRAIL_CHECKS_KEY: Final = "guardrail_checks"
|
||||
|
||||
|
||||
def _str_keyed_dict(value: object) -> Mapping[str, object] | None:
|
||||
if not isinstance(value, dict):
|
||||
return None
|
||||
return cast("Mapping[str, object]", value) # cast-ok: response bodies and _hidden_params are keyed by field name
|
||||
|
||||
|
||||
def _response_hidden_params(response: object) -> Mapping[str, object] | None:
|
||||
as_dict: Final = _str_keyed_dict(response)
|
||||
return _str_keyed_dict(
|
||||
as_dict.get("_hidden_params") if as_dict is not None else getattr(response, "_hidden_params", None)
|
||||
)
|
||||
|
||||
|
||||
def _write_response_marker(response: object, marker: str | None) -> None:
|
||||
"""Record a check on the response itself, so it survives the router copying request metadata per hop."""
|
||||
if marker is None:
|
||||
return
|
||||
hidden: Final = _response_hidden_params(response)
|
||||
if not isinstance(hidden, dict):
|
||||
return
|
||||
hidden[_RESPONSE_GUARDRAIL_CHECKS_KEY] = tuple(
|
||||
_str_collection(hidden.get(_RESPONSE_GUARDRAIL_CHECKS_KEY)) | {marker}
|
||||
)
|
||||
|
||||
|
||||
def _response_carries_marker(response: object, marker: str | None) -> bool:
|
||||
hidden: Final = _response_hidden_params(response)
|
||||
return (
|
||||
marker is not None
|
||||
and hidden is not None
|
||||
and marker in _str_collection(hidden.get(_RESPONSE_GUARDRAIL_CHECKS_KEY))
|
||||
)
|
||||
|
||||
|
||||
_REQUEST_FIELDS: Final = TypeAdapter(Mapping[str, object])
|
||||
|
||||
|
||||
class CustomGuardrail(CustomLogger):
|
||||
# If True, during_call runs async_moderation_hook instead of the unified apply_guardrail path.
|
||||
use_native_during_call_hook: ClassVar[bool] = False
|
||||
|
|
@ -867,20 +962,7 @@ class CustomGuardrail(CustomLogger):
|
|||
With ``raised``, records instead that the hook raised as a policy pipeline
|
||||
step the pipeline then moved past. The deployment-level hook still runs it.
|
||||
"""
|
||||
marker: Final = self._pre_call_marker(raised=raised)
|
||||
if marker is None:
|
||||
return
|
||||
for meta_key in ("metadata", "litellm_metadata"):
|
||||
meta = data.get(meta_key)
|
||||
if isinstance(meta, dict):
|
||||
executed = meta.get(PRE_CALL_EXECUTED_GUARDRAILS_KEY)
|
||||
if isinstance(executed, list):
|
||||
if marker not in executed:
|
||||
executed.append(marker)
|
||||
else:
|
||||
meta[PRE_CALL_EXECUTED_GUARDRAILS_KEY] = [marker]
|
||||
return
|
||||
data["metadata"] = {PRE_CALL_EXECUTED_GUARDRAILS_KEY: [marker]}
|
||||
_record_guardrail_marker(data, self._pre_call_marker(raised=raised))
|
||||
|
||||
def pre_call_hook_ran_or_raised(self, data: Mapping[str, object]) -> bool:
|
||||
"""Whether this guardrail's ``async_pre_call_hook`` ran for this request, whatever it decided."""
|
||||
|
|
@ -1077,10 +1159,26 @@ class CustomGuardrail(CustomLogger):
|
|||
cast("dict[str, object]", request_data) # cast-ok: proxy request payloads are str-keyed
|
||||
)
|
||||
|
||||
if not self._is_valid_response_type(result):
|
||||
return None
|
||||
returned: Final = result if self._is_valid_response_type(result) else None
|
||||
if target is self:
|
||||
return returned
|
||||
checked: Final = returned if returned is not None else response
|
||||
marker: Final = self._post_call_check_marker(checked)
|
||||
_record_guardrail_marker(
|
||||
cast("dict[str, object]", request_data), # cast-ok: proxy request payloads are str-keyed
|
||||
marker,
|
||||
)
|
||||
_write_response_marker(checked, marker)
|
||||
return returned
|
||||
|
||||
return result
|
||||
def _post_call_check_marker(self, response: object) -> str | None:
|
||||
fingerprint: Final = _response_fingerprint(response)
|
||||
return self._marker(f"{_POST_CALL_CHECKED_TOKEN}:{fingerprint}") if fingerprint is not None else None
|
||||
|
||||
def post_call_already_checked(self, data: Mapping[str, object], response: object) -> bool:
|
||||
"""Whether a deployment-level hook of this request already ran this guardrail on this exact response."""
|
||||
marker: Final = self._post_call_check_marker(response)
|
||||
return _request_carries_marker(data, marker) or _response_carries_marker(response, marker)
|
||||
|
||||
async def async_logging_hook(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -328,6 +328,7 @@ _VIEW_SETUP_POLL_INTERVAL_SECONDS: Final = 5.0
|
|||
_VIEW_SETUP_DEADLINE_SECONDS: Final = 15 * 60.0
|
||||
_VIEW_SETUP_GATE_TABLE: Final = '"LiteLLM_SpendLogs"'
|
||||
_VIEW_SETUP_GATE_PROBE_ROWS: Final = TypeAdapter(tuple[Mapping[str, bool], ...])
|
||||
_REQUEST_FIELDS: Final = TypeAdapter(Mapping[str, object])
|
||||
|
||||
_ViewSetupOutcome: TypeAlias = Literal["ready", "timed_out"]
|
||||
_ViewSetupAttempt: TypeAlias = Literal["ready", "table_missing"] | Exception
|
||||
|
|
@ -4143,6 +4144,7 @@ class ProxyLogging:
|
|||
response = pipeline_response # rebind-ok: adopt the pipeline's replacement response, same contract as the callback loops below
|
||||
|
||||
pipeline_managed: Final = pipeline_managed_guardrail_names(data, "post_call")
|
||||
request_view: Final = _REQUEST_FIELDS.validate_python(data)
|
||||
guardrail_callbacks, other_callbacks = _partition_post_call_callbacks()
|
||||
try:
|
||||
# Merge model-level guardrails before checking which guardrails to run
|
||||
|
|
@ -4173,6 +4175,9 @@ class ProxyLogging:
|
|||
):
|
||||
continue
|
||||
|
||||
if callback.post_call_already_checked(request_view, response) is True:
|
||||
continue
|
||||
|
||||
guardrail_response: Any | None = None
|
||||
|
||||
if "apply_guardrail" in type(callback).__dict__ and not callback.use_native_lifecycle_hooks:
|
||||
|
|
@ -4246,10 +4251,13 @@ class ProxyLogging:
|
|||
immediately before awaiting, and the unified hook pops it before its first
|
||||
suspension point, so concurrent guardrails never race on that key.
|
||||
"""
|
||||
request_view: Final = _REQUEST_FIELDS.validate_python(data)
|
||||
|
||||
async def _run_one(callback: CustomGuardrail) -> None:
|
||||
if callback.should_run_guardrail(data=guardrail_data, event_type=GuardrailEventHooks.post_call) is not True:
|
||||
return
|
||||
if callback.post_call_already_checked(request_view, response) is True:
|
||||
return
|
||||
if "apply_guardrail" in type(callback).__dict__ and not callback.use_native_lifecycle_hooks:
|
||||
data["guardrail_to_apply"] = callback
|
||||
await self._run_guardrail_with_metrics(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ authors = [
|
|||
]
|
||||
dependencies = [
|
||||
"fastuuid>=0.14.0,<1.0",
|
||||
"filelock>=3.16.1,<4.0",
|
||||
"filelock>=3.16.1,<5",
|
||||
"httpx[http2]>=0.28.0,<1.0",
|
||||
"openai>=2.20.0,<4.0.0",
|
||||
"python-dateutil>=2.8.2,<3.0",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ dependencies = [
|
|||
# When changing a floor, verify it installs + imports on every supported
|
||||
# Python with: `uv pip install --resolution=lowest-direct .`
|
||||
"fastuuid>=0.14.0,<1.0",
|
||||
"filelock>=3.16.1,<4.0",
|
||||
"filelock>=3.16.1,<5",
|
||||
"httpx[http2]>=0.28.0,<1.0",
|
||||
"openai>=2.20.0,<4.0.0",
|
||||
"python-dotenv>=1.0.0,<2.0",
|
||||
|
|
@ -358,6 +358,8 @@ constraint-dependencies = [
|
|||
"httplib2>=0.32.0",
|
||||
"setuptools>=84.1.0",
|
||||
"dnspython>=2.9.0; python_version >= '3.11'",
|
||||
"filelock<3.20.4; python_version < '3.11'",
|
||||
"filelock>=4.2.1; python_version >= '3.11'",
|
||||
]
|
||||
override-dependencies = [
|
||||
# a2a-sdk 1.x requires packaging>=24.0; lunary 1.4.x still caps at <24.0.
|
||||
|
|
|
|||
566
tests/unit/integrations/test_custom_guardrail_post_call_once.py
Normal file
566
tests/unit/integrations/test_custom_guardrail_post_call_once.py
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
import asyncio
|
||||
import json
|
||||
from collections.abc import AsyncIterable, Callable, Iterator, Mapping
|
||||
from dataclasses import dataclass
|
||||
from types import MappingProxyType
|
||||
from typing import Final, Literal
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
from fastapi import HTTPException, Request, Response
|
||||
from fastapi.responses import StreamingResponse
|
||||
from mcp.types import Tool as MCPTool
|
||||
from pydantic import BaseModel, TypeAdapter
|
||||
|
||||
import litellm
|
||||
from litellm.caching.caching import Cache
|
||||
from litellm.integrations.custom_guardrail import CustomGuardrail
|
||||
from litellm.llms.base_llm.ocr.transformation import OCRResponse
|
||||
from litellm.proxy._types import UserAPIKeyAuth
|
||||
from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
|
||||
from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache
|
||||
from litellm.proxy.utils import ProxyLogging
|
||||
from litellm.responses.mcp.litellm_proxy_mcp_handler import LiteLLM_Proxy_MCP_Handler
|
||||
from litellm.router import Router
|
||||
from litellm.types.guardrails import GuardrailEventHooks
|
||||
from litellm.types.utils import CallTypes, Choices, GenericGuardrailAPIInputs, LLMResponseTypes, Message, ModelResponse
|
||||
|
||||
pytestmark = pytest.mark.respx(assert_all_called=False)
|
||||
|
||||
REPLY: Final = "hi from the model"
|
||||
REWRITTEN: Final = "rewritten by the proxy"
|
||||
MASKED: Final = "masked by G"
|
||||
|
||||
Route = Literal["acompletion", "aresponses", "anthropic_messages"]
|
||||
|
||||
_OPENAI_API_BASE: Final = "https://api.openai.com/v1"
|
||||
_FAILING_API_BASE: Final = "https://primary.invalid/v1"
|
||||
_MCP_API_BASE: Final = "https://mcp-rounds.invalid/v1"
|
||||
|
||||
_CHAT_REPLY: Final[Mapping[str, object]] = {
|
||||
"id": "chatcmpl-1",
|
||||
"object": "chat.completion",
|
||||
"created": 0,
|
||||
"model": "gpt-4o",
|
||||
"choices": [{"index": 0, "message": {"role": "assistant", "content": REPLY}, "finish_reason": "stop"}],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
}
|
||||
_CHAT_SSE: Final = (
|
||||
"".join(
|
||||
f"data: {json.dumps(chunk)}\n\n"
|
||||
for chunk in (
|
||||
{
|
||||
"id": "chatcmpl-1",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 0,
|
||||
"model": "gpt-4o",
|
||||
"choices": [{"index": 0, "delta": {"role": "assistant", "content": REPLY}, "finish_reason": None}],
|
||||
},
|
||||
{
|
||||
"id": "chatcmpl-1",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 0,
|
||||
"model": "gpt-4o",
|
||||
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
|
||||
},
|
||||
)
|
||||
)
|
||||
+ "data: [DONE]\n\n"
|
||||
)
|
||||
_RESPONSES_REPLY: Final[Mapping[str, object]] = {
|
||||
"id": "resp_1",
|
||||
"object": "response",
|
||||
"created_at": 0,
|
||||
"status": "completed",
|
||||
"model": "gpt-4o",
|
||||
"output": [
|
||||
{
|
||||
"type": "message",
|
||||
"id": "msg_1",
|
||||
"status": "completed",
|
||||
"role": "assistant",
|
||||
"content": [{"type": "output_text", "text": REPLY, "annotations": []}],
|
||||
}
|
||||
],
|
||||
"usage": {"input_tokens": 1, "output_tokens": 1, "total_tokens": 2},
|
||||
}
|
||||
_RESPONSES_SSE: Final = "".join(
|
||||
f"data: {json.dumps(event)}\n\n"
|
||||
for event in (
|
||||
{"type": "response.created", "sequence_number": 0, "response": {**_RESPONSES_REPLY, "output": []}},
|
||||
{
|
||||
"type": "response.output_text.delta",
|
||||
"sequence_number": 1,
|
||||
"item_id": "msg_1",
|
||||
"output_index": 0,
|
||||
"content_index": 0,
|
||||
"delta": REPLY,
|
||||
},
|
||||
{"type": "response.completed", "sequence_number": 2, "response": _RESPONSES_REPLY},
|
||||
)
|
||||
)
|
||||
_TOOL_CALL_ROUND: Final[Mapping[str, object]] = {
|
||||
**_RESPONSES_REPLY,
|
||||
"id": "resp_tool_call",
|
||||
"output": [
|
||||
{
|
||||
"type": "function_call",
|
||||
"id": "fc_1",
|
||||
"call_id": "call_echo",
|
||||
"name": "echo",
|
||||
"arguments": '{"value": "PING"}',
|
||||
"status": "completed",
|
||||
}
|
||||
],
|
||||
}
|
||||
_JSON_OBJECT: Final = TypeAdapter(dict[str, object])
|
||||
|
||||
|
||||
class _CountedGuardrail(CustomGuardrail):
|
||||
checked: tuple[str, ...] = ()
|
||||
|
||||
|
||||
class ApplyGuardrailG(_CountedGuardrail):
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
inputs: GenericGuardrailAPIInputs,
|
||||
request_data: dict[str, object],
|
||||
input_type: Literal["request", "response"],
|
||||
logging_obj: object = None,
|
||||
) -> GenericGuardrailAPIInputs:
|
||||
self.checked = (*self.checked, " ".join(inputs.get("texts") or ()))
|
||||
return inputs
|
||||
|
||||
|
||||
def _reply_text(response: LLMResponseTypes) -> str:
|
||||
match response:
|
||||
case ModelResponse(choices=[Choices(message=Message(content=str() as text))]):
|
||||
return text
|
||||
case _:
|
||||
return str(response)
|
||||
|
||||
|
||||
class OwnHookG(_CountedGuardrail):
|
||||
async def async_post_call_success_hook(
|
||||
self, data: dict[str, object], user_api_key_dict: UserAPIKeyAuth, response: LLMResponseTypes
|
||||
) -> None:
|
||||
self.checked = (*self.checked, _reply_text(response))
|
||||
|
||||
|
||||
class InheritedApplyGuardrailG(ApplyGuardrailG):
|
||||
pass
|
||||
|
||||
|
||||
class MaskingG(ApplyGuardrailG):
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
inputs: GenericGuardrailAPIInputs,
|
||||
request_data: dict[str, object],
|
||||
input_type: Literal["request", "response"],
|
||||
logging_obj: object = None,
|
||||
) -> GenericGuardrailAPIInputs:
|
||||
checked: Final = await super().apply_guardrail(inputs, request_data, input_type, logging_obj)
|
||||
return {**checked, "texts": [MASKED for _ in checked.get("texts") or ()]}
|
||||
|
||||
|
||||
class BlockingG(ApplyGuardrailG):
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
inputs: GenericGuardrailAPIInputs,
|
||||
request_data: dict[str, object],
|
||||
input_type: Literal["request", "response"],
|
||||
logging_obj: object = None,
|
||||
) -> GenericGuardrailAPIInputs:
|
||||
await super().apply_guardrail(inputs, request_data, input_type, logging_obj)
|
||||
raise HTTPException(status_code=400, detail="blocked by G")
|
||||
|
||||
|
||||
class RewritingGuardrail(CustomGuardrail):
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
inputs: GenericGuardrailAPIInputs,
|
||||
request_data: dict[str, object],
|
||||
input_type: Literal["request", "response"],
|
||||
logging_obj: object = None,
|
||||
) -> GenericGuardrailAPIInputs:
|
||||
return {**inputs, "texts": [REWRITTEN for _ in inputs.get("texts") or ()]}
|
||||
|
||||
|
||||
class ToolCallBlockingGuardrail(CustomGuardrail):
|
||||
async def apply_guardrail(
|
||||
self,
|
||||
inputs: GenericGuardrailAPIInputs,
|
||||
request_data: dict[str, object],
|
||||
input_type: Literal["request", "response"],
|
||||
logging_obj: object = None,
|
||||
) -> GenericGuardrailAPIInputs:
|
||||
if input_type == "response" and inputs.get("tool_calls"):
|
||||
raise HTTPException(status_code=400, detail="tool calls are not allowed")
|
||||
return inputs
|
||||
|
||||
|
||||
def _guardrail_g(
|
||||
style: type[_CountedGuardrail] = ApplyGuardrailG, default_on: bool = False, run_in_parallel: bool = False
|
||||
) -> _CountedGuardrail:
|
||||
return style(
|
||||
guardrail_name="G",
|
||||
event_hook=GuardrailEventHooks.post_call,
|
||||
default_on=default_on,
|
||||
run_in_parallel=run_in_parallel,
|
||||
)
|
||||
|
||||
|
||||
def _known_gap(action_item: str) -> pytest.MarkDecorator:
|
||||
return pytest.mark.xfail(
|
||||
raises=AssertionError,
|
||||
strict=True,
|
||||
reason=f"broken before #40154, out of scope for LIT-10481, tracked as RCA {action_item}",
|
||||
)
|
||||
|
||||
|
||||
def _deployment(model_name: str, guardrails: tuple[str, ...], api_base: str) -> dict[str, object]:
|
||||
return {
|
||||
"model_name": model_name,
|
||||
"litellm_params": {
|
||||
"model": "openai/gpt-4o",
|
||||
"api_key": "sk-test",
|
||||
"api_base": api_base,
|
||||
**({"guardrails": list(guardrails)} if guardrails else {}),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _router(primary: tuple[str, ...], fallback: tuple[str, ...] | None = None, cached: bool = False) -> Router:
|
||||
if fallback is None:
|
||||
return Router(model_list=[_deployment("gpt", primary, _OPENAI_API_BASE)], cache_responses=cached)
|
||||
return Router(
|
||||
model_list=[
|
||||
_deployment("gpt", primary, _FAILING_API_BASE),
|
||||
_deployment("gpt-backup", fallback, _OPENAI_API_BASE),
|
||||
],
|
||||
fallbacks=[{"gpt": ["gpt-backup"]}],
|
||||
num_retries=0,
|
||||
)
|
||||
|
||||
|
||||
def _json_or_sse(json_reply: Mapping[str, object], sse_reply: str) -> Callable[[httpx.Request], httpx.Response]:
|
||||
def reply(request: httpx.Request) -> httpx.Response:
|
||||
if _JSON_OBJECT.validate_json(request.content).get("stream"):
|
||||
return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=sse_reply.encode())
|
||||
return httpx.Response(200, json=dict(json_reply))
|
||||
|
||||
return reply
|
||||
|
||||
|
||||
async def _drain(stream: AsyncIterable[object]) -> str:
|
||||
return "".join([str(chunk) async for chunk in stream])
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def provider(respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch) -> Iterator[respx.MockRouter]:
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
ProxyLogging._callback_capabilities_cache.clear() # pyright: ignore[reportPrivateUsage] # keyed on id(), which a collected guardrail from an earlier test can reuse
|
||||
for endpoint in ("chat/completions", "responses"):
|
||||
respx_mock.post(f"{_FAILING_API_BASE}/{endpoint}").mock(
|
||||
return_value=httpx.Response(500, json={"error": {"message": "primary down"}})
|
||||
)
|
||||
respx_mock.post(f"{_OPENAI_API_BASE}/chat/completions").mock(side_effect=_json_or_sse(_CHAT_REPLY, _CHAT_SSE))
|
||||
respx_mock.post(f"{_OPENAI_API_BASE}/responses").mock(side_effect=_json_or_sse(_RESPONSES_REPLY, _RESPONSES_SSE))
|
||||
yield respx_mock
|
||||
litellm.in_memory_llm_clients_cache.flush_cache()
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ProxyCase:
|
||||
route: Route = "acompletion"
|
||||
stream: bool = False
|
||||
primary: tuple[str, ...] = ("G",)
|
||||
fallback: tuple[str, ...] | None = None
|
||||
request: tuple[str, ...] = ()
|
||||
default_on: bool = False
|
||||
style: type[_CountedGuardrail] = ApplyGuardrailG
|
||||
cached: bool = False
|
||||
client_sees: str = REPLY
|
||||
checks: tuple[str, ...] = (REPLY,)
|
||||
|
||||
|
||||
PROXY_CASES: Final = (
|
||||
pytest.param(ProxyCase(), id="A-chat"),
|
||||
pytest.param(ProxyCase(route="aresponses"), id="A-responses"),
|
||||
pytest.param(ProxyCase(route="anthropic_messages"), id="A-messages"),
|
||||
pytest.param(ProxyCase(request=("G",)), id="A-chat-model-and-request"),
|
||||
pytest.param(ProxyCase(style=MaskingG, client_sees=MASKED), id="A-chat-g-masks-its-own-output"),
|
||||
pytest.param(ProxyCase(stream=True), id="B-chat-stream"),
|
||||
pytest.param(ProxyCase(route="aresponses", stream=True), id="B-responses-stream"),
|
||||
pytest.param(ProxyCase(route="anthropic_messages", stream=True), id="B-messages-stream"),
|
||||
pytest.param(ProxyCase(primary=(), fallback=("G",)), id="E-only-on-fallback"),
|
||||
pytest.param(
|
||||
ProxyCase(request=("rewriter",), client_sees=REWRITTEN, checks=(REPLY, REWRITTEN)),
|
||||
id="G-rewritten-after-check",
|
||||
),
|
||||
pytest.param(ProxyCase(fallback=("G",)), id="H-on-both-deployments"),
|
||||
pytest.param(ProxyCase(primary=(), request=("G",)), id="I-request-chat"),
|
||||
pytest.param(ProxyCase(route="aresponses", primary=(), request=("G",)), id="I-request-responses"),
|
||||
pytest.param(ProxyCase(style=OwnHookG), id="J-A-chat-own-hook", marks=_known_gap("AI-5")),
|
||||
pytest.param(ProxyCase(primary=(), fallback=("G",), style=OwnHookG), id="E-only-on-fallback-own-hook"),
|
||||
pytest.param(ProxyCase(primary=(), request=("G",), style=OwnHookG), id="I-request-chat-own-hook"),
|
||||
pytest.param(
|
||||
ProxyCase(primary=(), request=("G",), style=InheritedApplyGuardrailG),
|
||||
id="K-I-request-chat-inherited-apply-guardrail",
|
||||
marks=_known_gap("AI-2"),
|
||||
),
|
||||
pytest.param(ProxyCase(primary=("other",), default_on=True), id="L-default-on"),
|
||||
)
|
||||
|
||||
_ROUTE_PATHS: Final[Mapping[Route, str]] = MappingProxyType(
|
||||
{
|
||||
"acompletion": "/v1/chat/completions",
|
||||
"aresponses": "/v1/responses",
|
||||
"anthropic_messages": "/v1/messages",
|
||||
}
|
||||
)
|
||||
_ROUTE_BODIES: Final[Mapping[Route, Mapping[str, object]]] = MappingProxyType(
|
||||
{
|
||||
"acompletion": {"messages": [{"role": "user", "content": "hello"}]},
|
||||
"aresponses": {"input": "hello"},
|
||||
"anthropic_messages": {"messages": [{"role": "user", "content": "hello"}], "max_tokens": 32},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def _proxy_reply(case: ProxyCase) -> str:
|
||||
from litellm.proxy.proxy_server import ProxyConfig, select_data_generator
|
||||
|
||||
path: Final = _ROUTE_PATHS[case.route]
|
||||
router: Final = _router(case.primary, case.fallback, case.cached)
|
||||
response: Final = await ProxyBaseLLMRequestProcessing(
|
||||
data={
|
||||
"model": "gpt",
|
||||
**_ROUTE_BODIES[case.route],
|
||||
**({"stream": True} if case.stream else {}),
|
||||
**({"guardrails": list(case.request)} if case.request else {}),
|
||||
}
|
||||
).base_process_llm_request(
|
||||
request=Request({"type": "http", "method": "POST", "path": path, "headers": [], "query_string": b""}),
|
||||
fastapi_response=Response(),
|
||||
user_api_key_dict=UserAPIKeyAuth(api_key="sk-test", request_route=path),
|
||||
route_type=case.route,
|
||||
proxy_logging_obj=ProxyLogging(user_api_key_cache=UserApiKeyCache()),
|
||||
general_settings={},
|
||||
proxy_config=ProxyConfig(),
|
||||
select_data_generator=select_data_generator,
|
||||
llm_router=router,
|
||||
is_streaming_request=case.stream,
|
||||
)
|
||||
text: Final = (
|
||||
await _drain(response.body_iterator)
|
||||
if isinstance(response, StreamingResponse)
|
||||
else json.dumps(response.model_dump() if isinstance(response, BaseModel) else response)
|
||||
)
|
||||
await asyncio.sleep(0)
|
||||
return text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", PROXY_CASES)
|
||||
async def test_a_proxy_request_checks_each_distinct_response_once(
|
||||
monkeypatch: pytest.MonkeyPatch, case: ProxyCase
|
||||
) -> None:
|
||||
guardrail: Final = _guardrail_g(case.style, default_on=case.default_on)
|
||||
rewriter: Final = RewritingGuardrail(guardrail_name="rewriter", event_hook=GuardrailEventHooks.post_call)
|
||||
monkeypatch.setattr(litellm, "callbacks", [rewriter, guardrail])
|
||||
|
||||
reply: Final = await _proxy_reply(case)
|
||||
|
||||
assert case.client_sees in reply
|
||||
assert sorted(guardrail.checked) == sorted(case.checks)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", ["acompletion", "aresponses"])
|
||||
async def test_a_cache_hit_is_checked_like_a_fresh_response(
|
||||
monkeypatch: pytest.MonkeyPatch, provider: respx.MockRouter, route: Route
|
||||
) -> None:
|
||||
guardrail: Final = _guardrail_g()
|
||||
monkeypatch.setattr(litellm, "callbacks", [guardrail])
|
||||
monkeypatch.setattr(litellm, "cache", Cache(type="local"))
|
||||
|
||||
case: Final = ProxyCase(route=route, cached=True)
|
||||
replies: Final = (await _proxy_reply(case), await _proxy_reply(case))
|
||||
|
||||
assert provider.calls.call_count == 1, "the second request must be served from the cache"
|
||||
assert all(REPLY in reply for reply in replies)
|
||||
assert guardrail.checked == (REPLY, REPLY)
|
||||
|
||||
|
||||
async def test_a_blocking_verdict_blocks_the_proxy_response_after_one_check(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
guardrail: Final = _guardrail_g(BlockingG)
|
||||
monkeypatch.setattr(litellm, "callbacks", [guardrail])
|
||||
|
||||
with pytest.raises(HTTPException, check=lambda e: e.detail == "blocked by G"):
|
||||
await _proxy_reply(ProxyCase())
|
||||
|
||||
assert guardrail.checked == (REPLY,)
|
||||
|
||||
|
||||
SdkCaller = Literal["sdk-call", "sdk-router"]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SdkCase:
|
||||
caller: SdkCaller = "sdk-call"
|
||||
stream: bool = False
|
||||
style: type[_CountedGuardrail] = ApplyGuardrailG
|
||||
|
||||
|
||||
SDK_CASES: Final = (
|
||||
pytest.param(SdkCase(), id="C-sdk-call"),
|
||||
pytest.param(SdkCase(caller="sdk-router"), id="C-sdk-router"),
|
||||
pytest.param(SdkCase(style=OwnHookG), id="C-sdk-call-own-hook"),
|
||||
pytest.param(SdkCase(stream=True), id="D-sdk-call-stream", marks=_known_gap("AI-4")),
|
||||
)
|
||||
|
||||
|
||||
async def _sdk_reply(case: SdkCase) -> str:
|
||||
messages: Final = [{"role": "user", "content": "hello"}]
|
||||
response: Final = await (
|
||||
_router(("G",)).acompletion(model="gpt", messages=messages, stream=case.stream)
|
||||
if case.caller == "sdk-router"
|
||||
else litellm.acompletion(
|
||||
model="openai/gpt-4o", api_key="sk-test", guardrails=["G"], messages=messages, stream=case.stream
|
||||
)
|
||||
)
|
||||
text: Final = await _drain(response) if isinstance(response, AsyncIterable) else str(response)
|
||||
await asyncio.sleep(0)
|
||||
return text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", SDK_CASES)
|
||||
async def test_an_sdk_call_checks_its_response_once(monkeypatch: pytest.MonkeyPatch, case: SdkCase) -> None:
|
||||
guardrail: Final = _guardrail_g(case.style)
|
||||
monkeypatch.setattr(litellm, "callbacks", [guardrail])
|
||||
|
||||
reply: Final = await _sdk_reply(case)
|
||||
|
||||
assert REPLY in reply
|
||||
assert guardrail.checked == (REPLY,)
|
||||
|
||||
|
||||
async def test_mcp_auto_execute_checks_the_tool_call_round_before_running_its_tools(
|
||||
monkeypatch: pytest.MonkeyPatch, provider: respx.MockRouter
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
litellm, "callbacks", [ToolCallBlockingGuardrail(guardrail_name="G", event_hook=GuardrailEventHooks.post_call)]
|
||||
)
|
||||
provider.post(f"{_MCP_API_BASE}/responses").mock(
|
||||
side_effect=[httpx.Response(200, json=_TOOL_CALL_ROUND), httpx.Response(200, json=_RESPONSES_REPLY)]
|
||||
)
|
||||
echo_tool: Final = MCPTool.model_validate(
|
||||
{"name": "echo", "inputSchema": {"type": "object", "properties": {"value": {"type": "string"}}}},
|
||||
by_name=False,
|
||||
)
|
||||
run_tools: Final = AsyncMock(return_value=[{"tool_call_id": "call_echo", "result": "PING", "name": "echo"}])
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
LiteLLM_Proxy_MCP_Handler,
|
||||
"process_mcp_tools_without_openai_transform",
|
||||
AsyncMock(return_value=([echo_tool], {"echo": "demo"})),
|
||||
),
|
||||
patch.object(LiteLLM_Proxy_MCP_Handler, "execute_tool_calls", run_tools),
|
||||
pytest.raises(HTTPException, check=lambda e: e.detail == "tool calls are not allowed"),
|
||||
):
|
||||
await litellm.aresponses(
|
||||
model="openai/gpt-4o",
|
||||
input="Call echo with PING",
|
||||
tools=[{"type": "mcp", "server_url": "litellm_proxy/mcp/demo", "require_approval": "never"}],
|
||||
api_key="sk-test",
|
||||
api_base=_MCP_API_BASE,
|
||||
guardrails=["G"],
|
||||
proxy_server_request={"url": "http://localhost:4000/v1/responses", "method": "POST"},
|
||||
litellm_metadata={"user_api_key_request_route": "/v1/responses"},
|
||||
)
|
||||
|
||||
run_tools.assert_not_awaited()
|
||||
|
||||
|
||||
def _same_response(reply: ModelResponse) -> ModelResponse:
|
||||
return reply
|
||||
|
||||
|
||||
def _model_renamed(reply: ModelResponse) -> ModelResponse:
|
||||
return reply.model_copy(update={"model": "public-alias"})
|
||||
|
||||
|
||||
def _rewritten(_: ModelResponse) -> ModelResponse:
|
||||
return ModelResponse(choices=[{"message": {"role": "assistant", "content": REWRITTEN}}])
|
||||
|
||||
|
||||
PROXY_LOOP_CASES: Final = (
|
||||
pytest.param(_same_response, (REPLY,), id="A-same-response"),
|
||||
pytest.param(_model_renamed, (REPLY,), id="A-model-renamed"),
|
||||
pytest.param(_rewritten, (REPLY, REWRITTEN), id="G-rewritten"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parallel", [False, True], ids=["sequential", "parallel"])
|
||||
@pytest.mark.parametrize(("proxy_sees", "checks"), PROXY_LOOP_CASES)
|
||||
async def test_each_proxy_post_call_loop_skips_only_output_the_deployment_checked(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
parallel: bool,
|
||||
proxy_sees: Callable[[ModelResponse], ModelResponse],
|
||||
checks: tuple[str, ...],
|
||||
) -> None:
|
||||
guardrail: Final = _guardrail_g(run_in_parallel=parallel)
|
||||
monkeypatch.setattr(litellm, "callbacks", [guardrail])
|
||||
data: Final = {
|
||||
"model": "gpt",
|
||||
"proxy_server_request": {"url": "http://localhost:4000/v1/chat/completions"},
|
||||
"metadata": {"guardrails": ["G"]},
|
||||
}
|
||||
reply: Final = ModelResponse(model="provider-model", choices=[{"message": {"role": "assistant", "content": REPLY}}])
|
||||
|
||||
await guardrail.async_post_call_success_deployment_hook(
|
||||
request_data={**data, "guardrails": ["G"]}, response=reply, call_type=CallTypes.acompletion
|
||||
)
|
||||
await ProxyLogging(user_api_key_cache=UserApiKeyCache()).post_call_success_hook(
|
||||
data=data, response=proxy_sees(reply), user_api_key_dict=UserAPIKeyAuth()
|
||||
)
|
||||
|
||||
assert sorted(guardrail.checked) == sorted(checks)
|
||||
|
||||
|
||||
def _ocr_reply(markdown: str) -> OCRResponse:
|
||||
return OCRResponse.model_validate(
|
||||
{"pages": [{"index": 0, "markdown": markdown, "images": [], "dimensions": None}], "model": "mistral-ocr-latest"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("proxy_sees", "checks"),
|
||||
[
|
||||
pytest.param(REPLY, (REPLY,), id="A-ocr-same-pages"),
|
||||
pytest.param(REWRITTEN, (REPLY, REWRITTEN), id="G-ocr-rewritten-pages"),
|
||||
],
|
||||
)
|
||||
async def test_an_ocr_response_is_skipped_only_when_its_pages_were_checked(
|
||||
monkeypatch: pytest.MonkeyPatch, proxy_sees: str, checks: tuple[str, ...]
|
||||
) -> None:
|
||||
guardrail: Final = _guardrail_g()
|
||||
monkeypatch.setattr(litellm, "callbacks", [guardrail])
|
||||
data: Final = {
|
||||
"model": "ocr",
|
||||
"document": {"type": "document_url", "document_url": "https://example.com/doc.pdf"},
|
||||
"proxy_server_request": {"url": "http://localhost:4000/v1/ocr"},
|
||||
"metadata": {"guardrails": ["G"], "user_api_key_request_route": "/v1/ocr"},
|
||||
}
|
||||
|
||||
await guardrail.async_post_call_success_deployment_hook(
|
||||
request_data={**data, "guardrails": ["G"]}, response=_ocr_reply(REPLY), call_type=CallTypes.aocr
|
||||
)
|
||||
await ProxyLogging(user_api_key_cache=UserApiKeyCache()).post_call_success_hook(
|
||||
data=data, response=_ocr_reply(proxy_sees), user_api_key_dict=UserAPIKeyAuth(request_route="/v1/ocr")
|
||||
)
|
||||
|
||||
assert sorted(guardrail.checked) == sorted(checks)
|
||||
|
|
@ -696,6 +696,7 @@ def _moderation_guardrail() -> MagicMock:
|
|||
cb.should_run_guardrail = MagicMock(return_value=True)
|
||||
cb.async_moderation_hook = AsyncMock(return_value=None)
|
||||
cb.async_post_call_success_hook = AsyncMock(return_value=None)
|
||||
cb.post_call_already_checked = MagicMock(return_value=False)
|
||||
cb.run_in_parallel = False
|
||||
return cb
|
||||
|
||||
|
|
|
|||
432
uv.lock
generated
432
uv.lock
generated
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 5
|
||||
revision = 3
|
||||
requires-python = ">=3.10, <3.15"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14'",
|
||||
|
|
@ -10,17 +10,17 @@ resolution-markers = [
|
|||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
|
||||
exclude-newer = "2026-11-03T22:57:01.748805Z"
|
||||
exclude-newer-span = "P3D"
|
||||
|
||||
[options.exclude-newer-package]
|
||||
boto3 = "2026-10-21T19:31:00Z"
|
||||
botocore = "2026-10-21T19:31:00Z"
|
||||
mlflow-skinny = "2026-10-21T03:00:00Z"
|
||||
brotli = "2026-10-22T14:01:00Z"
|
||||
certifi = "2026-10-20T03:00:00Z"
|
||||
grpcio = "2026-10-22T15:00:00Z"
|
||||
boto3 = "2026-10-21T19:31:00Z"
|
||||
botocore = "2026-10-21T19:31:00Z"
|
||||
mlflow = "2026-10-21T03:00:00Z"
|
||||
mlflow-skinny = "2026-10-21T03:00:00Z"
|
||||
mlflow-tracing = "2026-10-21T03:00:00Z"
|
||||
pypdf = "2026-10-28T17:01:00Z"
|
||||
|
||||
|
|
@ -33,6 +33,8 @@ members = [
|
|||
constraints = [
|
||||
{ name = "aiohttp", specifier = ">=3.14.2,<4.0" },
|
||||
{ name = "dnspython", marker = "python_full_version >= '3.11'", specifier = ">=2.9.0" },
|
||||
{ name = "filelock", marker = "python_full_version < '3.11'", specifier = "<3.20.4" },
|
||||
{ name = "filelock", marker = "python_full_version >= '3.11'", specifier = ">=4.2.1" },
|
||||
{ name = "httplib2", specifier = ">=0.32.0" },
|
||||
{ name = "packaging", specifier = ">=24.0" },
|
||||
{ name = "setuptools", specifier = ">=84.1.0" },
|
||||
|
|
@ -239,9 +241,9 @@ name = "aiologic"
|
|||
version = "0.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "wrapt" },
|
||||
{ name = "sniffio", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "wrapt", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/a7/809482759f40079f4c4328c7318bf569ae25d457f5017aad30a1b9aafedc/aiologic-0.17.0.tar.gz", hash = "sha256:65aa058e858c94cd208badb188e7f00b54dcabb3ba85b34f794db98074d108b9", size = 251625, upload-time = "2026-06-14T12:24:35.367Z" }
|
||||
wheels = [
|
||||
|
|
@ -533,14 +535,14 @@ name = "aurelio-sdk"
|
|||
version = "0.0.19"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiofiles" },
|
||||
{ name = "aiohttp" },
|
||||
{ name = "colorlog" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "requests" },
|
||||
{ name = "requests-toolbelt" },
|
||||
{ name = "tornado", version = "6.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" },
|
||||
{ name = "aiofiles", marker = "python_full_version < '3.14'" },
|
||||
{ name = "aiohttp", marker = "python_full_version < '3.14'" },
|
||||
{ name = "colorlog", marker = "python_full_version < '3.14'" },
|
||||
{ name = "pydantic", marker = "python_full_version < '3.14'" },
|
||||
{ name = "python-dotenv", marker = "python_full_version < '3.14'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||
{ name = "requests-toolbelt", marker = "python_full_version < '3.14'" },
|
||||
{ name = "tornado", version = "6.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "tornado", version = "6.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/0e/c2e369ad173fb3d76448e46d10beb3dcc53388318933ddf8169a3f21a810/aurelio_sdk-0.0.19.tar.gz", hash = "sha256:14107e7440ff2efd0b4a08c52fb595e7680bd4bc973a0ddfb3b64157c6666b91", size = 15258, upload-time = "2025-03-24T14:37:32.203Z" }
|
||||
|
|
@ -553,9 +555,9 @@ name = "aws-sdk-bedrock-runtime"
|
|||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "smithy-aws-core", extra = ["eventstream", "json"] },
|
||||
{ name = "smithy-core" },
|
||||
{ name = "smithy-http", extra = ["aiohttp"] },
|
||||
{ name = "smithy-aws-core", extra = ["eventstream", "json"], marker = "python_full_version >= '3.12'" },
|
||||
{ name = "smithy-core", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "smithy-http", extra = ["aiohttp"], marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/b3/9c225cbfe9f17ea2e3d75a0fdd0b325ef79839b9c09a376bda63a7bf3bb3/aws_sdk_bedrock_runtime-0.11.0.tar.gz", hash = "sha256:f2c45d34625bf6a7b56375e29a53a16b376880bda771e4bbf7d84491622eb193", size = 173854, upload-time = "2026-08-24T21:17:16.304Z" }
|
||||
wheels = [
|
||||
|
|
@ -564,7 +566,7 @@ wheels = [
|
|||
|
||||
[package.optional-dependencies]
|
||||
awscrt = [
|
||||
{ name = "smithy-http", extra = ["awscrt"] },
|
||||
{ name = "smithy-http", extra = ["awscrt"], marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1247,7 +1249,7 @@ name = "colorlog"
|
|||
version = "6.10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "colorama", marker = "python_full_version < '3.14' and sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" }
|
||||
wheels = [
|
||||
|
|
@ -1271,7 +1273,7 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" }
|
||||
wheels = [
|
||||
|
|
@ -1344,7 +1346,7 @@ resolution-markers = [
|
|||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" }
|
||||
|
|
@ -1614,8 +1616,8 @@ name = "culsans"
|
|||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiologic" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "aiologic", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/e3/49afa1bc180e0d28008ec6bcdf82a4072d1c7a41032b5b759b60814ca4b0/culsans-0.11.0.tar.gz", hash = "sha256:0b43d0d05dce6106293d114c86e3fb4bfc63088cfe8ff08ed3fe36891447fe33", size = 107546, upload-time = "2025-12-31T23:15:38.196Z" }
|
||||
wheels = [
|
||||
|
|
@ -1898,7 +1900,7 @@ name = "exceptiongroup"
|
|||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
|
|
@ -2058,11 +2060,29 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.32.7"
|
||||
version = "3.20.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/59/e19834834cb01a32febfbb0f8a23a9088088f5d45991824ff2bc3b5e8acb/filelock-3.32.7.tar.gz", hash = "sha256:37b8a3d9811b0f9aef7e5ec5c71bb320de52df51e6ca9bcd6f5ad81187660da7", size = 225154, upload-time = "2026-09-16T00:24:20.907Z" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/df/31098c5aeb4d966b553641472bd55fcf5fdfac953549894b8a765ba44e91/filelock-3.32.7-py3-none-any.whl", hash = "sha256:65ff0d0190ea42038b32bda4b77834fb05be2cad4c5b9b01aa4dfb3614536e52", size = 100157, upload-time = "2026-09-16T00:24:19.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "4.2.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14'",
|
||||
"python_full_version == '3.13.*'",
|
||||
"python_full_version == '3.12.*'",
|
||||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/28/3893324bcadf8f89b1616637468d766af3f625008c0735bf704fdfa86617/filelock-4.2.4.tar.gz", hash = "sha256:c6d8663535e1650ee430507b4ffbac7b464dd64b7647ea0112a9f406499d163d", size = 652444, upload-time = "2026-10-19T01:57:51.001Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/1b/5e74289a1878e666bb4b65fe5805a04cafcac2aa5f519ef1ead67ef91c56/filelock-4.2.4-py3-none-any.whl", hash = "sha256:301241438c57936ef93f4243a94f2559022268bfcf9b375a9a72eb0f8db8358e", size = 162475, upload-time = "2026-10-19T01:57:49.419Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2481,11 +2501,11 @@ resolution-markers = [
|
|||
"python_full_version >= '3.14'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "google-auth" },
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "proto-plus" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "requests" },
|
||||
{ name = "google-auth", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "googleapis-common-protos", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "proto-plus", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "protobuf", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/cd/63f1557235c2440fe0577acdbc32577c5c002684c58c7f4d770a92366a24/google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300", size = 166266, upload-time = "2025-10-03T00:07:34.778Z" }
|
||||
wheels = [
|
||||
|
|
@ -2494,8 +2514,8 @@ wheels = [
|
|||
|
||||
[package.optional-dependencies]
|
||||
grpc = [
|
||||
{ name = "grpcio" },
|
||||
{ name = "grpcio-status" },
|
||||
{ name = "grpcio", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "grpcio-status", marker = "python_full_version >= '3.14'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2509,11 +2529,11 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "google-auth" },
|
||||
{ name = "googleapis-common-protos" },
|
||||
{ name = "proto-plus" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "requests" },
|
||||
{ name = "google-auth", marker = "python_full_version < '3.14'" },
|
||||
{ name = "googleapis-common-protos", marker = "python_full_version < '3.14'" },
|
||||
{ name = "proto-plus", marker = "python_full_version < '3.14'" },
|
||||
{ name = "protobuf", marker = "python_full_version < '3.14'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" }
|
||||
wheels = [
|
||||
|
|
@ -2522,8 +2542,8 @@ wheels = [
|
|||
|
||||
[package.optional-dependencies]
|
||||
grpc = [
|
||||
{ name = "grpcio" },
|
||||
{ name = "grpcio-status" },
|
||||
{ name = "grpcio", marker = "python_full_version < '3.14'" },
|
||||
{ name = "grpcio-status", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2709,12 +2729,12 @@ resolution-markers = [
|
|||
"python_full_version >= '3.14'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "google-auth" },
|
||||
{ name = "google-cloud-core" },
|
||||
{ name = "google-crc32c" },
|
||||
{ name = "google-resumable-media" },
|
||||
{ name = "requests" },
|
||||
{ name = "google-api-core", version = "2.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" },
|
||||
{ name = "google-auth", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "google-cloud-core", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "google-crc32c", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "google-resumable-media", marker = "python_full_version >= '3.14'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bd/ef/7cefdca67a6c8b3af0ec38612f9e78e5a9f6179dd91352772ae1a9849246/google_cloud_storage-3.4.1.tar.gz", hash = "sha256:6f041a297e23a4b485fad8c305a7a6e6831855c208bcbe74d00332a909f82268", size = 17238203, upload-time = "2025-10-08T18:43:39.665Z" }
|
||||
wheels = [
|
||||
|
|
@ -2732,12 +2752,12 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "google-auth" },
|
||||
{ name = "google-cloud-core" },
|
||||
{ name = "google-crc32c" },
|
||||
{ name = "google-resumable-media" },
|
||||
{ name = "requests" },
|
||||
{ name = "google-api-core", version = "2.30.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" },
|
||||
{ name = "google-auth", marker = "python_full_version < '3.14'" },
|
||||
{ name = "google-cloud-core", marker = "python_full_version < '3.14'" },
|
||||
{ name = "google-crc32c", marker = "python_full_version < '3.14'" },
|
||||
{ name = "google-resumable-media", marker = "python_full_version < '3.14'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/47/205eb8e9a1739b5345843e5a425775cbdc472cc38e7eda082ba5b8d02450/google_cloud_storage-3.10.1.tar.gz", hash = "sha256:97db9aa4460727982040edd2bd13ff3d5e2260b5331ad22895802da1fc2a5286", size = 17309950, upload-time = "2026-03-23T09:35:23.409Z" }
|
||||
wheels = [
|
||||
|
|
@ -3446,7 +3466,8 @@ version = "1.32.0"
|
|||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "filelock" },
|
||||
{ name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "filelock", version = "4.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "fsspec" },
|
||||
{ name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
||||
{ name = "httpx" },
|
||||
|
|
@ -4199,13 +4220,13 @@ name = "langchain-classic"
|
|||
version = "1.0.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langchain-text-splitters" },
|
||||
{ name = "langsmith" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "langchain-core", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "langchain-text-splitters", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "langsmith", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pydantic", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pyyaml", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sqlalchemy", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/78/84b5065816f348c39fefa4316f209f0135e8410216340a953bec17d9e4e4/langchain_classic-1.0.7.tar.gz", hash = "sha256:debbec8065e69b95108d2652e8d5c44f4516e19aa8d716c02ed2211c3aee099d", size = 10554118, upload-time = "2026-05-07T15:46:56.8Z" }
|
||||
wheels = [
|
||||
|
|
@ -4220,18 +4241,18 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "dataclasses-json" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "langchain" },
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langsmith" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "aiohttp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "dataclasses-json", marker = "python_full_version < '3.11'" },
|
||||
{ name = "httpx-sse", marker = "python_full_version < '3.11'" },
|
||||
{ name = "langchain", marker = "python_full_version < '3.11'" },
|
||||
{ name = "langchain-core", marker = "python_full_version < '3.11'" },
|
||||
{ name = "langsmith", marker = "python_full_version < '3.11'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "pydantic-settings", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.11'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sqlalchemy", marker = "python_full_version < '3.11'" },
|
||||
{ name = "tenacity", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/49/2ff5354273809e9811392bc24bcffda545a196070666aef27bc6aacf1c21/langchain_community-0.3.31.tar.gz", hash = "sha256:250e4c1041539130f6d6ac6f9386cb018354eafccd917b01a4cff1950b80fd81", size = 33241237, upload-time = "2025-10-07T20:17:57.857Z" }
|
||||
wheels = [
|
||||
|
|
@ -4249,19 +4270,19 @@ resolution-markers = [
|
|||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "dataclasses-json" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "langchain-classic" },
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langsmith" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
{ name = "aiohttp", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "dataclasses-json", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "httpx-sse", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "langchain-classic", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "langchain-core", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "langsmith", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "pydantic-settings", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pyyaml", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sqlalchemy", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "tenacity", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144, upload-time = "2025-10-27T15:20:32.504Z" }
|
||||
wheels = [
|
||||
|
|
@ -4319,7 +4340,7 @@ name = "langchain-text-splitters"
|
|||
version = "1.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langchain-core", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/9f/6c545900fefb7b00ddfa3f16b80d61338a0ec68c31c5451eeeab99082760/langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627", size = 293580, upload-time = "2026-04-16T14:20:39.162Z" }
|
||||
wheels = [
|
||||
|
|
@ -4581,7 +4602,8 @@ dependencies = [
|
|||
{ name = "certifi" },
|
||||
{ name = "click" },
|
||||
{ name = "fastuuid" },
|
||||
{ name = "filelock" },
|
||||
{ name = "filelock", version = "3.20.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "filelock", version = "4.2.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "httpx", extra = ["http2"] },
|
||||
{ name = "huggingface-hub" },
|
||||
{ name = "importlib-metadata" },
|
||||
|
|
@ -4855,7 +4877,7 @@ requires-dist = [
|
|||
{ name = "fastapi", marker = "extra == 'proxy'", specifier = ">=0.136.3,<1.0" },
|
||||
{ name = "fastapi-sso", marker = "extra == 'proxy'", specifier = ">=0.19.0,<1.0" },
|
||||
{ name = "fastuuid", specifier = ">=0.14.0,<1.0" },
|
||||
{ name = "filelock", specifier = ">=3.16.1,<4.0" },
|
||||
{ name = "filelock", specifier = ">=3.16.1,<5" },
|
||||
{ name = "google-cloud-aiplatform", marker = "extra == 'google'", specifier = ">=1.133.0,<2.0" },
|
||||
{ name = "google-cloud-aiplatform", marker = "extra == 'proxy-runtime'", specifier = ">=1.133.0,<2.0" },
|
||||
{ name = "google-cloud-iam", marker = "extra == 'extra-proxy'", specifier = ">=2.19.1,<3.0" },
|
||||
|
|
@ -5051,10 +5073,10 @@ name = "litellm-admin-mcp"
|
|||
version = "0.1.0"
|
||||
source = { url = "https://github.com/BerriAI/liteadmin-mcp/archive/d35ec9c19c117d4c50cc1eccf6ce1296aac25a1a.tar.gz" }
|
||||
dependencies = [
|
||||
{ name = "httpx2" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "mcp" },
|
||||
{ name = "uvicorn" },
|
||||
{ name = "httpx2", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "jsonschema", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "mcp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "uvicorn", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { hash = "sha256:86b930f6706fb2da10d53d1798ee7fd14e44fb0afdef0da122cf0e6bb88fa2c3" }
|
||||
|
||||
|
|
@ -5160,16 +5182,16 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "chevron" },
|
||||
{ name = "jsonpickle" },
|
||||
{ name = "langchain-community", version = "0.3.31", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyhumps" },
|
||||
{ name = "requests" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "aiohttp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "chevron", marker = "python_full_version < '3.11'" },
|
||||
{ name = "jsonpickle", marker = "python_full_version < '3.11'" },
|
||||
{ name = "langchain-community", version = "0.3.31", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pydantic", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pyhumps", marker = "python_full_version < '3.11'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.11'" },
|
||||
{ name = "setuptools", marker = "python_full_version < '3.11'" },
|
||||
{ name = "tenacity", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4a/6f/9ca1acf766848aaf5f0ac4140c34c91ad0dbfad2654359699644be3352c9/lunary-1.4.36.tar.gz", hash = "sha256:53f002f385c83d9c0e6368e7999923acffbde987f53c5205c2c249c38ee2d75c", size = 20253, upload-time = "2026-02-09T20:49:30.56Z" }
|
||||
wheels = [
|
||||
|
|
@ -5187,16 +5209,16 @@ resolution-markers = [
|
|||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "chevron" },
|
||||
{ name = "jsonpickle" },
|
||||
{ name = "langchain-community", version = "0.4.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyhumps" },
|
||||
{ name = "requests" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "aiohttp", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "chevron", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "jsonpickle", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "langchain-community", version = "0.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "packaging", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pydantic", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pyhumps", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "setuptools", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "tenacity", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/ef/1acbc6957585cc0110e648d787663871717ced3df27fcd3cb5e18fa418f3/lunary-1.4.37.tar.gz", hash = "sha256:1781091e9dceffcc28ebc4be7e085c9fec4102d98d7ca945ed0021e9ce03c36f", size = 20248, upload-time = "2026-02-12T08:15:02.091Z" }
|
||||
wheels = [
|
||||
|
|
@ -9091,10 +9113,10 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "joblib" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "threadpoolctl" },
|
||||
{ name = "joblib", marker = "python_full_version < '3.11'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "threadpoolctl", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" }
|
||||
wheels = [
|
||||
|
|
@ -9141,11 +9163,11 @@ resolution-markers = [
|
|||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "joblib" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
{ name = "joblib", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "threadpoolctl" },
|
||||
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "threadpoolctl", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" }
|
||||
wheels = [
|
||||
|
|
@ -9195,7 +9217,7 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
|
||||
wheels = [
|
||||
|
|
@ -9257,7 +9279,7 @@ resolution-markers = [
|
|||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
|
||||
|
|
@ -9342,21 +9364,21 @@ name = "semantic-router"
|
|||
version = "0.1.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "aurelio-sdk" },
|
||||
{ name = "colorama" },
|
||||
{ name = "colorlog" },
|
||||
{ name = "litellm" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or python_full_version >= '3.14'" },
|
||||
{ name = "aiohttp", marker = "python_full_version < '3.14'" },
|
||||
{ name = "aurelio-sdk", marker = "python_full_version < '3.14'" },
|
||||
{ name = "colorama", marker = "python_full_version < '3.14'" },
|
||||
{ name = "colorlog", marker = "python_full_version < '3.14'" },
|
||||
{ name = "litellm", marker = "python_full_version < '3.14'" },
|
||||
{ name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
||||
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and python_full_version < '3.14'" },
|
||||
{ name = "openai" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "regex" },
|
||||
{ name = "tiktoken" },
|
||||
{ name = "tornado", version = "6.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or python_full_version >= '3.14'" },
|
||||
{ name = "openai", marker = "python_full_version < '3.14'" },
|
||||
{ name = "pydantic", marker = "python_full_version < '3.14'" },
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.14'" },
|
||||
{ name = "regex", marker = "python_full_version < '3.14'" },
|
||||
{ name = "tiktoken", marker = "python_full_version < '3.14'" },
|
||||
{ name = "tornado", version = "6.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "tornado", version = "6.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' and python_full_version < '3.14'" },
|
||||
{ name = "urllib3" },
|
||||
{ name = "urllib3", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/dc/a9/1a689e916e8b280f1fd8fb335cc059be626a22fe4533baa045d32fcd6de5/semantic_router-0.1.15.tar.gz", hash = "sha256:328256ddc3c2b713101ec69561d6585aecbf1198ea3461e1486289d8c3a35288", size = 95605, upload-time = "2026-05-23T12:58:15.444Z" }
|
||||
wheels = [
|
||||
|
|
@ -9430,9 +9452,9 @@ name = "smithy-aws-core"
|
|||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aws-sdk-signers" },
|
||||
{ name = "smithy-core" },
|
||||
{ name = "smithy-http" },
|
||||
{ name = "aws-sdk-signers", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "smithy-core", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "smithy-http", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/d3/501c0023548173416109ac42298ca33b708469dc922005770811a597949f/smithy_aws_core-0.11.0.tar.gz", hash = "sha256:29ee89976a520a87e3db557e03e115fdc21a0a60b81161e95174395a1b064da1", size = 38791, upload-time = "2026-08-24T21:16:59.631Z" }
|
||||
wheels = [
|
||||
|
|
@ -9441,10 +9463,10 @@ wheels = [
|
|||
|
||||
[package.optional-dependencies]
|
||||
eventstream = [
|
||||
{ name = "smithy-aws-event-stream" },
|
||||
{ name = "smithy-aws-event-stream", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
json = [
|
||||
{ name = "smithy-json" },
|
||||
{ name = "smithy-json", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -9452,7 +9474,7 @@ name = "smithy-aws-event-stream"
|
|||
version = "0.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "smithy-core" },
|
||||
{ name = "smithy-core", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/0e/6efb3a4ed92c0f1ada6de060ac92e7115a1e34d0ab1fb99a6056734a88ea/smithy_aws_event_stream-0.3.0.tar.gz", hash = "sha256:a0e227367a973144e205a075d0a424f95c92f26656a1018d08900da2ae547c49", size = 12818, upload-time = "2026-05-05T18:04:14.317Z" }
|
||||
wheels = [
|
||||
|
|
@ -9473,7 +9495,7 @@ name = "smithy-http"
|
|||
version = "0.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "smithy-core" },
|
||||
{ name = "smithy-core", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/78/b5f3113d6c8f0bc1f9777a7f5ca84b892d29efac05850e14f7d4f7e645b5/smithy_http-0.5.0.tar.gz", hash = "sha256:bb4a19672f7c7eeb872a308f777eb505281a5bafb1ee3d1ea9c760c06c352510", size = 31122, upload-time = "2026-08-24T21:16:56.488Z" }
|
||||
wheels = [
|
||||
|
|
@ -9482,11 +9504,11 @@ wheels = [
|
|||
|
||||
[package.optional-dependencies]
|
||||
aiohttp = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "yarl" },
|
||||
{ name = "aiohttp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "yarl", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
awscrt = [
|
||||
{ name = "awscrt" },
|
||||
{ name = "awscrt", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -9494,8 +9516,8 @@ name = "smithy-json"
|
|||
version = "0.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "ijson" },
|
||||
{ name = "smithy-core" },
|
||||
{ name = "ijson", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "smithy-core", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/04164eefb3da7479f52f6535b4b39cc8384c292cb2bb74279f2acc4f4b4d/smithy_json-0.3.0.tar.gz", hash = "sha256:c81c7034587e01bc64767cbbecb05a7d65ca9070612fd94e8a03e80540290a22", size = 7956, upload-time = "2026-08-20T17:55:32.177Z" }
|
||||
wheels = [
|
||||
|
|
@ -9573,23 +9595,23 @@ resolution-markers = [
|
|||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "alabaster" },
|
||||
{ name = "babel" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "imagesize" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pygments" },
|
||||
{ name = "requests" },
|
||||
{ name = "snowballstemmer" },
|
||||
{ name = "sphinxcontrib-applehelp" },
|
||||
{ name = "sphinxcontrib-devhelp" },
|
||||
{ name = "sphinxcontrib-htmlhelp" },
|
||||
{ name = "sphinxcontrib-jsmath" },
|
||||
{ name = "sphinxcontrib-qthelp" },
|
||||
{ name = "sphinxcontrib-serializinghtml" },
|
||||
{ name = "tomli" },
|
||||
{ name = "alabaster", marker = "python_full_version < '3.11'" },
|
||||
{ name = "babel", marker = "python_full_version < '3.11'" },
|
||||
{ name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" },
|
||||
{ name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "imagesize", marker = "python_full_version < '3.11'" },
|
||||
{ name = "jinja2", marker = "python_full_version < '3.11'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.11'" },
|
||||
{ name = "pygments", marker = "python_full_version < '3.11'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.11'" },
|
||||
{ name = "snowballstemmer", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" }
|
||||
wheels = [
|
||||
|
|
@ -9604,23 +9626,23 @@ resolution-markers = [
|
|||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "alabaster" },
|
||||
{ name = "babel" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "imagesize" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pygments" },
|
||||
{ name = "requests" },
|
||||
{ name = "roman-numerals" },
|
||||
{ name = "snowballstemmer" },
|
||||
{ name = "sphinxcontrib-applehelp" },
|
||||
{ name = "sphinxcontrib-devhelp" },
|
||||
{ name = "sphinxcontrib-htmlhelp" },
|
||||
{ name = "sphinxcontrib-jsmath" },
|
||||
{ name = "sphinxcontrib-qthelp" },
|
||||
{ name = "sphinxcontrib-serializinghtml" },
|
||||
{ name = "alabaster", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "babel", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" },
|
||||
{ name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "imagesize", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "jinja2", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "packaging", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "pygments", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "requests", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "roman-numerals", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "snowballstemmer", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" },
|
||||
{ name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" }
|
||||
wheels = [
|
||||
|
|
@ -9637,23 +9659,23 @@ resolution-markers = [
|
|||
"python_full_version == '3.12.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "alabaster" },
|
||||
{ name = "babel" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" } },
|
||||
{ name = "imagesize" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pygments" },
|
||||
{ name = "requests" },
|
||||
{ name = "roman-numerals" },
|
||||
{ name = "snowballstemmer" },
|
||||
{ name = "sphinxcontrib-applehelp" },
|
||||
{ name = "sphinxcontrib-devhelp" },
|
||||
{ name = "sphinxcontrib-htmlhelp" },
|
||||
{ name = "sphinxcontrib-jsmath" },
|
||||
{ name = "sphinxcontrib-qthelp" },
|
||||
{ name = "sphinxcontrib-serializinghtml" },
|
||||
{ name = "alabaster", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "babel", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" },
|
||||
{ name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
{ name = "imagesize", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "jinja2", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "packaging", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "pygments", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "requests", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "roman-numerals", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "snowballstemmer", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" },
|
||||
{ name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" }
|
||||
wheels = [
|
||||
|
|
@ -9801,8 +9823,8 @@ name = "standard-aifc"
|
|||
version = "3.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "audioop-lts" },
|
||||
{ name = "standard-chunk" },
|
||||
{ name = "audioop-lts", marker = "python_full_version >= '3.13'" },
|
||||
{ name = "standard-chunk", marker = "python_full_version >= '3.13'" },
|
||||
]
|
||||
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 = [
|
||||
|
|
@ -9823,7 +9845,7 @@ name = "standard-sunau"
|
|||
version = "3.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "audioop-lts" },
|
||||
{ name = "audioop-lts", marker = "python_full_version >= '3.13'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/e3/ce8d38cb2d70e05ffeddc28bb09bad77cfef979eb0a299c9117f7ed4e6a9/standard_sunau-3.13.0.tar.gz", hash = "sha256:b319a1ac95a09a2378a8442f403c66f4fd4b36616d6df6ae82b8e536ee790908", size = 9368, upload-time = "2024-10-30T16:01:41.626Z" }
|
||||
wheels = [
|
||||
|
|
@ -9857,8 +9879,8 @@ name = "taskgroup"
|
|||
version = "0.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "exceptiongroup" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/8d/e218e0160cc1b692e6e0e5ba34e8865dbb171efeb5fc9a704544b3020605/taskgroup-0.2.2.tar.gz", hash = "sha256:078483ac3e78f2e3f973e2edbf6941374fbea81b9c5d0a96f51d297717f4752d", size = 11504, upload-time = "2025-01-03T09:24:13.761Z" }
|
||||
wheels = [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue