mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
refactor(cli): replace InquirerPy with an in-house fuzzy picker
InquirerPy was a thin wrapper around prompt_toolkit, which was already a transitive dependency and is what the existing tests drove the widget through directly. Removes the InquirerPy (and its pfzy) dependency and reimplements the same type-to-filter/tab-to-toggle/ enter-to-confirm interaction directly on prompt_toolkit's Application and KeyBindings primitives in a new fuzzy_picker.py module, matching every existing test scenario exactly (single-select, multiselect toggling, filtering across matches). Along the way, fixed a real bug the port introduced: the cancel handler returned an empty result, which _fuzzy_pick's calling code in wizard.py treats as "please pick again," looping forever waiting for input that was never coming. Ctrl-C/Ctrl-D now raise KeyboardInterrupt instead, matching how interactive prompts conventionally cancel.
This commit is contained in:
parent
9d2ed13355
commit
0ba155fb03
6 changed files with 277 additions and 107 deletions
165
litellm/proxy/client/cli/commands/autoroute/fuzzy_picker.py
Normal file
165
litellm/proxy/client/cli/commands/autoroute/fuzzy_picker.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
from dataclasses import dataclass, field
|
||||
|
||||
from prompt_toolkit.application import Application
|
||||
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
||||
from prompt_toolkit.keys import Keys
|
||||
from prompt_toolkit.layout import HSplit, Layout, Window
|
||||
from prompt_toolkit.layout.controls import FormattedTextControl
|
||||
from prompt_toolkit.styles import Style
|
||||
|
||||
_MAX_VISIBLE_ROWS = 15
|
||||
|
||||
_STYLE = Style.from_dict(
|
||||
{
|
||||
"prompt": "bold",
|
||||
"highlighted": "reverse",
|
||||
"hint": "fg:ansibrightblack",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _fuzzy_match(query: str, choices: tuple[str, ...]) -> tuple[str, ...]:
|
||||
"""fzf-style subsequence filter: query chars must appear in order (not necessarily contiguous)
|
||||
in a choice, case-insensitive. Preserves the choices' original relative order among matches."""
|
||||
if not query:
|
||||
return choices
|
||||
needle = query.lower()
|
||||
|
||||
def _matches(choice: str) -> bool:
|
||||
haystack = choice.lower()
|
||||
pos = 0
|
||||
for ch in needle:
|
||||
pos = haystack.find(ch, pos)
|
||||
if pos == -1:
|
||||
return False
|
||||
pos += 1
|
||||
return True
|
||||
|
||||
return tuple(choice for choice in choices if _matches(choice))
|
||||
|
||||
|
||||
@dataclass
|
||||
class _PickerState:
|
||||
all_choices: tuple[str, ...]
|
||||
multiselect: bool
|
||||
query: str = ""
|
||||
cursor: int = 0
|
||||
selected: set[str] = field(default_factory=set)
|
||||
|
||||
@property
|
||||
def filtered(self) -> tuple[str, ...]:
|
||||
return _fuzzy_match(self.query, self.all_choices)
|
||||
|
||||
|
||||
def _confirmed_selection(state: _PickerState) -> list[str]:
|
||||
if state.multiselect:
|
||||
return [choice for choice in state.all_choices if choice in state.selected]
|
||||
filtered = state.filtered
|
||||
return [filtered[state.cursor]] if filtered else []
|
||||
|
||||
|
||||
def _cancel(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
event.app.exit(exception=KeyboardInterrupt)
|
||||
|
||||
|
||||
def _confirm(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
event.app.exit(result=_confirmed_selection(state))
|
||||
|
||||
|
||||
def _toggle(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
if not state.multiselect:
|
||||
return
|
||||
filtered = state.filtered
|
||||
if filtered:
|
||||
state.selected.symmetric_difference_update({filtered[state.cursor]})
|
||||
|
||||
|
||||
def _move_up(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
state.cursor = max(0, state.cursor - 1)
|
||||
|
||||
|
||||
def _move_down(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
state.cursor = min(max(0, len(state.filtered) - 1), state.cursor + 1)
|
||||
|
||||
|
||||
def _backspace(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
state.query = state.query[:-1]
|
||||
state.cursor = 0
|
||||
|
||||
|
||||
def _type_char(event: KeyPressEvent, state: _PickerState) -> None:
|
||||
if event.data and event.data.isprintable():
|
||||
state.query += event.data
|
||||
state.cursor = 0
|
||||
|
||||
|
||||
def _build_key_bindings(state: _PickerState) -> KeyBindings:
|
||||
kb = KeyBindings()
|
||||
kb.add("c-c")(lambda event: _cancel(event, state))
|
||||
kb.add("c-d")(lambda event: _cancel(event, state))
|
||||
kb.add("enter")(lambda event: _confirm(event, state))
|
||||
kb.add("tab")(lambda event: _toggle(event, state))
|
||||
kb.add("up")(lambda event: _move_up(event, state))
|
||||
kb.add("c-p")(lambda event: _move_up(event, state))
|
||||
kb.add("down")(lambda event: _move_down(event, state))
|
||||
kb.add("c-n")(lambda event: _move_down(event, state))
|
||||
kb.add("backspace")(lambda event: _backspace(event, state))
|
||||
kb.add(Keys.Any)(lambda event: _type_char(event, state))
|
||||
return kb
|
||||
|
||||
|
||||
def _prompt_text(state: _PickerState, message: str) -> StyleAndTextTuples:
|
||||
toggle_hint = "tab to toggle, " if state.multiselect else ""
|
||||
return [
|
||||
("class:prompt", f"{message}: "),
|
||||
("", state.query),
|
||||
("class:hint", f" ({toggle_hint}type to filter, enter to confirm)"),
|
||||
]
|
||||
|
||||
|
||||
def _visible_window(state: _PickerState, filtered: tuple[str, ...]) -> tuple[int, tuple[str, ...]]:
|
||||
state.cursor = min(state.cursor, len(filtered) - 1)
|
||||
start = max(0, min(state.cursor - _MAX_VISIBLE_ROWS + 1, len(filtered) - _MAX_VISIBLE_ROWS))
|
||||
return start, filtered[start : start + _MAX_VISIBLE_ROWS]
|
||||
|
||||
|
||||
def _choices_text(state: _PickerState) -> StyleAndTextTuples:
|
||||
filtered = state.filtered
|
||||
if not filtered:
|
||||
return [("class:hint", " (no matches)")]
|
||||
start, visible = _visible_window(state, filtered)
|
||||
lines: StyleAndTextTuples = []
|
||||
for offset, choice in enumerate(visible):
|
||||
index = start + offset
|
||||
marker = "> " if index == state.cursor else " "
|
||||
check = ("[x] " if choice in state.selected else "[ ] ") if state.multiselect else ""
|
||||
style = "class:highlighted" if index == state.cursor else ""
|
||||
lines.append((style, f"{marker}{check}{choice}\n"))
|
||||
return lines
|
||||
|
||||
|
||||
def fuzzy_pick(choices: tuple[str, ...], message: str, multiselect: bool) -> list[str]:
|
||||
"""Interactive type-to-filter picker (fzf-style): type to narrow the list, arrow keys to move
|
||||
the highlight, Enter to confirm. In multiselect mode, Tab toggles the highlighted item into the
|
||||
result set and Enter confirms whatever has been toggled (not the bare highlight); Ctrl-C/Ctrl-D
|
||||
raise KeyboardInterrupt rather than returning an empty result, so a cancel aborts the caller
|
||||
instead of silently looping back to prompt again.
|
||||
"""
|
||||
state = _PickerState(all_choices=choices, multiselect=multiselect)
|
||||
layout = Layout(
|
||||
HSplit(
|
||||
[
|
||||
Window(content=FormattedTextControl(lambda: _prompt_text(state, message)), height=1),
|
||||
Window(content=FormattedTextControl(lambda: _choices_text(state))),
|
||||
]
|
||||
)
|
||||
)
|
||||
app: Application[list[str]] = Application(
|
||||
layout=layout, key_bindings=_build_key_bindings(state), style=_STYLE, full_screen=False
|
||||
)
|
||||
return app.run()
|
||||
|
||||
|
||||
__all__ = ["fuzzy_pick"]
|
||||
|
|
@ -3,8 +3,6 @@ from pathlib import Path
|
|||
|
||||
import click
|
||||
import yaml
|
||||
from InquirerPy import inquirer
|
||||
from InquirerPy.base.control import Choice
|
||||
|
||||
from .... import Client
|
||||
from .config import (
|
||||
|
|
@ -22,6 +20,7 @@ from .config import (
|
|||
parse_discovered_models,
|
||||
validate_config,
|
||||
)
|
||||
from .fuzzy_picker import fuzzy_pick
|
||||
from .process import CONFIG_PATH, secure_create
|
||||
|
||||
|
||||
|
|
@ -30,26 +29,19 @@ def _is_interactive() -> bool:
|
|||
|
||||
|
||||
def _fuzzy_pick(models: tuple[DiscoveredModel, ...], prompt_label: str, multiselect: bool) -> list[str]:
|
||||
"""Type-to-filter picker over a (possibly huge) model pool, using InquirerPy's fzf-style fuzzy prompt.
|
||||
"""Type-to-filter picker over a (possibly huge) model pool.
|
||||
|
||||
A plain numbered table + typed index does not scale past a handful of models -- proxies with
|
||||
hundreds of model groups made that interaction unusable. This lets the user narrow the pool by
|
||||
typing a substring instead of scrolling/counting.
|
||||
|
||||
Assumes the caller already checked interactivity (run_configure_wizard does, once, up front) --
|
||||
checking here too would check the wrong thing under test, where InquirerPy is driven through its
|
||||
own injected input/output rather than the real process stdin.
|
||||
checking here too would check the wrong thing under test, where the picker is driven through
|
||||
prompt_toolkit's own injected input/output rather than the real process stdin.
|
||||
"""
|
||||
choices = [Choice(value=model.name, name=model.name) for model in models]
|
||||
toggle_hint = "tab to toggle, " if multiselect else ""
|
||||
choices = tuple(model.name for model in models)
|
||||
while True:
|
||||
result = inquirer.fuzzy(
|
||||
message=f"{prompt_label}: type to filter, {toggle_hint}enter to confirm",
|
||||
choices=choices,
|
||||
multiselect=multiselect,
|
||||
max_height="70%",
|
||||
).execute()
|
||||
selected = result if multiselect else [result]
|
||||
selected = fuzzy_pick(choices, prompt_label, multiselect)
|
||||
if selected:
|
||||
return selected
|
||||
click.echo("Select at least one model.")
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ proxy = [
|
|||
"litellm-enterprise==0.1.50",
|
||||
"RestrictedPython>=8.1,<9.0",
|
||||
"rich>=13.9.4,<14.0",
|
||||
"InquirerPy>=0.3.4,<1.0",
|
||||
"prompt-toolkit>=3.0.36,<4.0",
|
||||
"polars>=1.38.1,<2.0",
|
||||
"soundfile>=0.12.1,<1.0",
|
||||
"pyroscope-io>=0.8.16,<1.0; sys_platform != 'win32'",
|
||||
|
|
@ -80,7 +80,7 @@ cli = [
|
|||
"rich>=13.9.4,<14.0",
|
||||
"pyyaml>=6.0.3,<7.0",
|
||||
"requests>=2.32.0,<3.0",
|
||||
"InquirerPy>=0.3.4,<1.0",
|
||||
"prompt-toolkit>=3.0.36,<4.0",
|
||||
]
|
||||
extra_proxy = [
|
||||
"prisma>=0.11.0,<1.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
from litellm.proxy.client.cli.commands.autoroute.fuzzy_picker import _fuzzy_match
|
||||
|
||||
|
||||
class TestFuzzyMatch:
|
||||
def test_empty_query_returns_all_choices_in_order(self):
|
||||
choices = ("gpt-4o", "claude-opus", "o1")
|
||||
assert _fuzzy_match("", choices) == choices
|
||||
|
||||
def test_exact_substring_matches(self):
|
||||
choices = ("gpt-4o-mini", "gpt-4o", "claude-opus")
|
||||
assert _fuzzy_match("gpt-4o", choices) == ("gpt-4o-mini", "gpt-4o")
|
||||
|
||||
def test_matches_as_a_subsequence_not_just_a_substring(self):
|
||||
choices = ("gpt-4o-mini", "claude-opus")
|
||||
assert _fuzzy_match("g4om", choices) == ("gpt-4o-mini",)
|
||||
|
||||
def test_is_case_insensitive(self):
|
||||
choices = ("GPT-4o-Mini",)
|
||||
assert _fuzzy_match("gpt4o", choices) == ("GPT-4o-Mini",)
|
||||
|
||||
def test_no_match_excludes_the_choice(self):
|
||||
choices = ("gpt-4o", "claude-opus")
|
||||
assert _fuzzy_match("xyz", choices) == ()
|
||||
|
||||
def test_preserves_original_relative_order_among_matches(self):
|
||||
choices = ("z-model", "a-model", "m-model")
|
||||
assert _fuzzy_match("model", choices) == ("z-model", "a-model", "m-model")
|
||||
|
|
@ -6,7 +6,6 @@ import click
|
|||
import pytest
|
||||
import yaml
|
||||
from click.testing import CliRunner
|
||||
from InquirerPy.base.control import Choice
|
||||
from prompt_toolkit.application import create_app_session
|
||||
from prompt_toolkit.input import create_pipe_input
|
||||
from prompt_toolkit.output import DummyOutput
|
||||
|
|
@ -215,10 +214,10 @@ def _drive_fuzzy_pick(
|
|||
multiselect: bool,
|
||||
key_events: List[Tuple[str, float]],
|
||||
) -> List[str]:
|
||||
"""Drives the real InquirerPy fuzzy prompt through prompt_toolkit's own test input/output,
|
||||
"""Drives the real fuzzy-picker widget through prompt_toolkit's own test input/output,
|
||||
exercising the actual widget (filtering, tab-to-toggle, enter-to-confirm) rather than mocking
|
||||
it away. asyncio.to_thread propagates the create_app_session context into the worker thread
|
||||
running _fuzzy_pick's synchronous .execute() call."""
|
||||
running fuzzy_pick's synchronous Application.run() call."""
|
||||
|
||||
async def _run() -> List[str]:
|
||||
with create_pipe_input() as pipe_input:
|
||||
|
|
@ -267,10 +266,19 @@ class TestFuzzyPickWidget:
|
|||
)
|
||||
assert set(result) == {"model-3", "model-15"}
|
||||
|
||||
def test_choice_wraps_name_and_value_to_the_same_model_name(self):
|
||||
model = DiscoveredModel(name="only-model")
|
||||
choice = Choice(value=model.name, name=model.name)
|
||||
assert choice.value == choice.name == "only-model"
|
||||
def test_down_arrow_moves_the_highlighted_match(self):
|
||||
result = _drive_fuzzy_pick(
|
||||
self._models(),
|
||||
"test",
|
||||
multiselect=False,
|
||||
key_events=[("model-1", 0.3), ("\x1b[B", 0.1), ("\r", 0.1)],
|
||||
)
|
||||
# "model-1" fuzzy-matches model-1, model-10..19; one Down moves off model-1 to model-10
|
||||
assert result == ["model-10"]
|
||||
|
||||
def test_raises_keyboard_interrupt_on_ctrl_c(self):
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
_drive_fuzzy_pick(self._models(), "test", multiselect=False, key_events=[("model-13", 0.3), ("\x03", 0.1)])
|
||||
|
||||
|
||||
class TestRenderAndPromptForModelWrappers:
|
||||
|
|
|
|||
146
uv.lock
generated
146
uv.lock
generated
|
|
@ -10,7 +10,7 @@ resolution-markers = [
|
|||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "2026-07-13T03:38:04.421387Z"
|
||||
exclude-newer = "2026-07-13T04:54:35.730742Z"
|
||||
exclude-newer-span = "P3D"
|
||||
|
||||
[manifest]
|
||||
|
|
@ -222,9 +222,9 @@ name = "aiologic"
|
|||
version = "0.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "sniffio" },
|
||||
{ name = "sniffio", marker = "python_full_version < '3.13'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
||||
{ name = "wrapt" },
|
||||
{ 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 = [
|
||||
|
|
@ -516,14 +516,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" },
|
||||
{ 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", marker = "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" }
|
||||
wheels = [
|
||||
|
|
@ -1047,7 +1047,7 @@ name = "coloredlogs"
|
|||
version = "15.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "humanfriendly" },
|
||||
{ name = "humanfriendly", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
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 = [
|
||||
|
|
@ -1059,7 +1059,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 = [
|
||||
|
|
@ -1420,7 +1420,7 @@ name = "culsans"
|
|||
version = "0.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiologic" },
|
||||
{ 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" }
|
||||
|
|
@ -2966,7 +2966,7 @@ name = "humanfriendly"
|
|||
version = "10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
||||
{ name = "pyreadline3", marker = "python_full_version < '3.14' and 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 = [
|
||||
|
|
@ -3049,19 +3049,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inquirerpy"
|
||||
version = "0.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pfzy" },
|
||||
{ name = "prompt-toolkit" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/73/7570847b9da026e07053da3bbe2ac7ea6cde6bb2cbd3c7a5a950fa0ae40b/InquirerPy-0.3.4.tar.gz", hash = "sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e", size = 44431, upload-time = "2022-06-27T23:11:20.598Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/ff/3b59672c47c6284e8005b42e84ceba13864aa0f39f067c973d1af02f5d91/InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4", size = 67677, upload-time = "2022-06-27T23:11:17.723Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isodate"
|
||||
version = "0.7.2"
|
||||
|
|
@ -3293,10 +3280,10 @@ name = "jsonschema-path"
|
|||
version = "0.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pathable" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "referencing" },
|
||||
{ name = "requests" },
|
||||
{ name = "pathable", marker = "python_full_version < '3.14'" },
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.14'" },
|
||||
{ name = "referencing", marker = "python_full_version < '3.14'" },
|
||||
{ name = "requests", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" }
|
||||
wheels = [
|
||||
|
|
@ -3767,7 +3754,7 @@ caching = [
|
|||
{ name = "diskcache" },
|
||||
]
|
||||
cli = [
|
||||
{ name = "inquirerpy" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "requests" },
|
||||
{ name = "rich" },
|
||||
|
|
@ -3803,12 +3790,12 @@ proxy = [
|
|||
{ name = "fastapi-sso" },
|
||||
{ name = "granian" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "inquirerpy" },
|
||||
{ name = "litellm-enterprise" },
|
||||
{ name = "litellm-proxy-extras" },
|
||||
{ name = "mcp" },
|
||||
{ name = "orjson" },
|
||||
{ name = "polars" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "pyjwt" },
|
||||
{ name = "pynacl" },
|
||||
|
|
@ -3981,8 +3968,6 @@ requires-dist = [
|
|||
{ name = "gunicorn", marker = "extra == 'proxy'", specifier = ">=23.0.0,<24.0" },
|
||||
{ name = "httpx", specifier = ">=0.28.0,<1.0" },
|
||||
{ name = "importlib-metadata", specifier = ">=8.0.0,<9.0" },
|
||||
{ name = "inquirerpy", marker = "extra == 'cli'", specifier = ">=0.3.4,<1.0" },
|
||||
{ name = "inquirerpy", marker = "extra == 'proxy'", specifier = ">=0.3.4,<1.0" },
|
||||
{ name = "jinja2", specifier = ">=3.1.6,<4.0" },
|
||||
{ name = "jsonschema", specifier = ">=4.0.0,<5.0" },
|
||||
{ name = "langfuse", marker = "extra == 'proxy-runtime'", specifier = ">=2.59.7,<3.0" },
|
||||
|
|
@ -4004,6 +3989,8 @@ requires-dist = [
|
|||
{ name = "polars", marker = "extra == 'proxy'", specifier = ">=1.38.1,<2.0" },
|
||||
{ name = "prisma", marker = "extra == 'extra-proxy'", specifier = ">=0.11.0,<1.0" },
|
||||
{ name = "prometheus-client", marker = "extra == 'proxy-runtime'", specifier = ">=0.20.0,<1.0" },
|
||||
{ name = "prompt-toolkit", marker = "extra == 'cli'", specifier = ">=3.0.36,<4.0" },
|
||||
{ name = "prompt-toolkit", marker = "extra == 'proxy'", specifier = ">=3.0.36,<4.0" },
|
||||
{ name = "pydantic", specifier = ">=2.10.0,<3.0.0" },
|
||||
{ name = "pydantic-settings", marker = "extra == 'proxy'", specifier = ">=2.14.1,<3.0" },
|
||||
{ name = "pyjwt", marker = "extra == 'proxy'", specifier = ">=2.13.0,<3.0" },
|
||||
|
|
@ -4481,7 +4468,7 @@ version = "0.4.1"
|
|||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ 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'" },
|
||||
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' and python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/15/76f86faa0902836cc133939732f7611ace68cf54148487a99c539c272dc8/ml_dtypes-0.4.1.tar.gz", hash = "sha256:fad5f2de464fd09127e49b7fd1252b9006fb43d2edc1ff112d390c324af5ca7a", size = 692594, upload-time = "2024-09-13T19:07:11.624Z" }
|
||||
wheels = [
|
||||
|
|
@ -4980,14 +4967,14 @@ name = "openapi-core"
|
|||
version = "0.22.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "isodate" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "jsonschema-path" },
|
||||
{ name = "more-itertools" },
|
||||
{ name = "openapi-schema-validator" },
|
||||
{ name = "openapi-spec-validator" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "werkzeug" },
|
||||
{ name = "isodate", marker = "python_full_version < '3.14'" },
|
||||
{ name = "jsonschema", marker = "python_full_version < '3.14'" },
|
||||
{ name = "jsonschema-path", marker = "python_full_version < '3.14'" },
|
||||
{ name = "more-itertools", marker = "python_full_version < '3.14'" },
|
||||
{ name = "openapi-schema-validator", marker = "python_full_version < '3.14'" },
|
||||
{ name = "openapi-spec-validator", marker = "python_full_version < '3.14'" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.14'" },
|
||||
{ name = "werkzeug", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/65/ee75f25b9459a02df6f713f8ffde5dacb57b8b4e45145cde4cab28b5abba/openapi_core-0.22.0.tar.gz", hash = "sha256:b30490dfa74e3aac2276105525590135212352f5dd7e5acf8f62f6a89ed6f2d0", size = 109242, upload-time = "2025-12-22T19:19:49.608Z" }
|
||||
wheels = [
|
||||
|
|
@ -4999,9 +4986,9 @@ name = "openapi-schema-validator"
|
|||
version = "0.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonschema" },
|
||||
{ name = "jsonschema-specifications" },
|
||||
{ name = "rfc3339-validator" },
|
||||
{ name = "jsonschema", marker = "python_full_version < '3.14'" },
|
||||
{ name = "jsonschema-specifications", marker = "python_full_version < '3.14'" },
|
||||
{ name = "rfc3339-validator", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" }
|
||||
wheels = [
|
||||
|
|
@ -5013,10 +5000,10 @@ name = "openapi-spec-validator"
|
|||
version = "0.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonschema" },
|
||||
{ name = "jsonschema-path" },
|
||||
{ name = "lazy-object-proxy" },
|
||||
{ name = "openapi-schema-validator" },
|
||||
{ name = "jsonschema", marker = "python_full_version < '3.14'" },
|
||||
{ name = "jsonschema-path", marker = "python_full_version < '3.14'" },
|
||||
{ name = "lazy-object-proxy", marker = "python_full_version < '3.14'" },
|
||||
{ name = "openapi-schema-validator", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" }
|
||||
wheels = [
|
||||
|
|
@ -5879,15 +5866,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pfzy"
|
||||
version = "0.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/5a/32b50c077c86bfccc7bed4881c5a2b823518f5450a30e639db5d3711952e/pfzy-0.3.4.tar.gz", hash = "sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1", size = 8396, upload-time = "2022-01-28T02:26:17.946Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/d7/8ff98376b1acc4503253b685ea09981697385ce344d4e3935c2af49e044d/pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96", size = 8537, upload-time = "2022-01-28T02:26:16.047Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.3.0"
|
||||
|
|
@ -7163,16 +7141,16 @@ name = "redisvl"
|
|||
version = "0.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "coloredlogs" },
|
||||
{ name = "ml-dtypes" },
|
||||
{ name = "coloredlogs", marker = "python_full_version < '3.14'" },
|
||||
{ name = "ml-dtypes", 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'" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-ulid" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "redis" },
|
||||
{ name = "tabulate" },
|
||||
{ name = "tenacity" },
|
||||
{ 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 = "pydantic", marker = "python_full_version < '3.14'" },
|
||||
{ name = "python-ulid", marker = "python_full_version < '3.14'" },
|
||||
{ name = "pyyaml", marker = "python_full_version < '3.14'" },
|
||||
{ name = "redis", marker = "python_full_version < '3.14'" },
|
||||
{ name = "tabulate", marker = "python_full_version < '3.14'" },
|
||||
{ name = "tenacity", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/33/ab14865a0b2a31b1d003c29e7e8ea3a7a2f2c8ecb24e58e58d606e1f031b/redisvl-0.4.1.tar.gz", hash = "sha256:fd6a36426ba94792c0efca20915c31232d4ee3cc58eb23794a62c142696401e6", size = 77688, upload-time = "2025-02-21T22:51:41.389Z" }
|
||||
wheels = [
|
||||
|
|
@ -7406,7 +7384,7 @@ name = "rfc3339-validator"
|
|||
version = "0.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
{ name = "six", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
|
||||
wheels = [
|
||||
|
|
@ -7855,20 +7833,20 @@ 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 = "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'" },
|
||||
{ name = "openai" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "regex" },
|
||||
{ name = "tiktoken" },
|
||||
{ name = "tornado" },
|
||||
{ name = "urllib3" },
|
||||
{ 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", 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", marker = "python_full_version < '3.14'" },
|
||||
{ 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 = [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue