flashlite 0.1.1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flashlite-0.1.1 → flashlite-0.2.0}/PKG-INFO +1 -1
- {flashlite-0.1.1 → flashlite-0.2.0}/pyproject.toml +5 -2
- flashlite-0.2.0/src/flashlite/_spinner.py +91 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/client.py +6 -2
- flashlite-0.2.0/src/flashlite/conversation/multi_agent.py +744 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/rate_limit.py +31 -8
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/inspect_compat.py +18 -6
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/logging.py +4 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/types.py +1 -1
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_multi_agent.py +249 -1
- flashlite-0.2.0/tests/test_rate_limit_regression.py +128 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/uv.lock +1 -1
- flashlite-0.1.1/src/flashlite/conversation/multi_agent.py +0 -378
- {flashlite-0.1.1 → flashlite-0.2.0}/.gitignore +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/.python-version +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/.vscode/settings.json +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/DEV.md +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/LICENSE.md +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/QUICK_START.md +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/README.md +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/examples/basic_example.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/examples/multi_agent_chat.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/base.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/disk.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/cache/memory.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/config.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/conversation/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/conversation/context.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/conversation/manager.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/core/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/core/completion.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/core/messages.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/base.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/cache.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/logging.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/middleware/retry.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/callbacks.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/observability/metrics.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/py.typed +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/structured/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/structured/outputs.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/structured/schema.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/engine.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/filters.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/templating/registry.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/tools/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/tools/definitions.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/src/flashlite/tools/execution.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/__init__.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/conftest.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_cache.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_cache_integration.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_client.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_config.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_conversation.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_integration.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_messages.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_middleware.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_observability.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_structured.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_templating.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_tools.py +0 -0
- {flashlite-0.1.1 → flashlite-0.2.0}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flashlite"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Batteries-included wrapper for litellm with rate limiting, retries, templating, and more"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -35,7 +35,10 @@ build-backend = "hatchling.build"
|
|
|
35
35
|
asyncio_mode = "auto"
|
|
36
36
|
asyncio_default_fixture_loop_scope = "function"
|
|
37
37
|
testpaths = ["tests"]
|
|
38
|
-
addopts = "-v --tb=short"
|
|
38
|
+
addopts = "-v --tb=short -m 'not slow'"
|
|
39
|
+
markers = [
|
|
40
|
+
"slow: marks tests that may require real waiting (deselected by default, run with -m slow)",
|
|
41
|
+
]
|
|
39
42
|
|
|
40
43
|
[tool.mypy]
|
|
41
44
|
python_version = "3.13"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Terminal spinner for user-visible progress during async waits.
|
|
2
|
+
|
|
3
|
+
Provides a lightweight, non-blocking spinner that renders to stderr
|
|
4
|
+
when — and only when — the output is an interactive terminal. Multiple
|
|
5
|
+
concurrent ``Spinner`` instances (e.g. from ``complete_many``) are
|
|
6
|
+
gracefully collapsed so only one animation is visible at a time.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
# Braille-dot frames — smooth and compact.
|
|
14
|
+
_FRAMES = ("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏")
|
|
15
|
+
_INTERVAL = 0.08 # seconds between frame updates
|
|
16
|
+
|
|
17
|
+
# ANSI helpers
|
|
18
|
+
_CYAN = "\033[36m"
|
|
19
|
+
_DIM = "\033[2m"
|
|
20
|
+
_RESET = "\033[0m"
|
|
21
|
+
_CLEAR_LINE = "\r\033[K"
|
|
22
|
+
|
|
23
|
+
# Module-level guard — only one spinner renders at a time.
|
|
24
|
+
_active: bool = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Spinner:
|
|
28
|
+
"""Async context manager that shows a terminal spinner on stderr.
|
|
29
|
+
|
|
30
|
+
The spinner only appears when stderr is a TTY **and** no other
|
|
31
|
+
``Spinner`` is already active, making it safe for concurrent use
|
|
32
|
+
inside ``complete_many``.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
message: Text displayed next to the spinner.
|
|
36
|
+
delay: Grace period (seconds) before the spinner appears.
|
|
37
|
+
If the wrapped operation finishes within this window the
|
|
38
|
+
spinner is never rendered, avoiding flicker for fast calls.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, message: str = "Working...", *, delay: float = 0.3) -> None:
|
|
42
|
+
self.message = message
|
|
43
|
+
self.delay = delay
|
|
44
|
+
self._task: asyncio.Task[None] | None = None
|
|
45
|
+
self._owns_active = False
|
|
46
|
+
self._start: float = 0.0
|
|
47
|
+
|
|
48
|
+
# -- internal -----------------------------------------------------
|
|
49
|
+
|
|
50
|
+
async def _render(self) -> None:
|
|
51
|
+
"""Background coroutine that draws frames until cancelled."""
|
|
52
|
+
await asyncio.sleep(self.delay)
|
|
53
|
+
idx = 0
|
|
54
|
+
while True:
|
|
55
|
+
elapsed = time.monotonic() - self._start
|
|
56
|
+
frame = _FRAMES[idx % len(_FRAMES)]
|
|
57
|
+
sys.stderr.write(
|
|
58
|
+
f"{_CLEAR_LINE}{_CYAN}{frame}{_RESET} {self.message} "
|
|
59
|
+
f"{_DIM}({elapsed:.1f}s){_RESET}"
|
|
60
|
+
)
|
|
61
|
+
sys.stderr.flush()
|
|
62
|
+
idx += 1
|
|
63
|
+
await asyncio.sleep(_INTERVAL)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _clear() -> None:
|
|
67
|
+
sys.stderr.write(_CLEAR_LINE)
|
|
68
|
+
sys.stderr.flush()
|
|
69
|
+
|
|
70
|
+
# -- context manager ----------------------------------------------
|
|
71
|
+
|
|
72
|
+
async def __aenter__(self) -> "Spinner":
|
|
73
|
+
global _active # noqa: PLW0603
|
|
74
|
+
if sys.stderr.isatty() and not _active:
|
|
75
|
+
_active = True
|
|
76
|
+
self._owns_active = True
|
|
77
|
+
self._start = time.monotonic()
|
|
78
|
+
self._task = asyncio.create_task(self._render())
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
async def __aexit__(self, *_: object) -> None:
|
|
82
|
+
global _active # noqa: PLW0603
|
|
83
|
+
if self._task is not None:
|
|
84
|
+
self._task.cancel()
|
|
85
|
+
try:
|
|
86
|
+
await self._task
|
|
87
|
+
except asyncio.CancelledError:
|
|
88
|
+
pass
|
|
89
|
+
self._clear()
|
|
90
|
+
if self._owns_active:
|
|
91
|
+
_active = False
|
|
@@ -7,6 +7,7 @@ from typing import Any, TypeVar, overload
|
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel
|
|
9
9
|
|
|
10
|
+
from ._spinner import Spinner
|
|
10
11
|
from .cache import CacheBackend, MemoryCache
|
|
11
12
|
from .config import FlashliteConfig, load_env_files
|
|
12
13
|
from .conversation import ContextManager, Conversation
|
|
@@ -223,7 +224,8 @@ class Flashlite:
|
|
|
223
224
|
if self._config.log_requests:
|
|
224
225
|
logger.info(f"Completion request: model={request.model}")
|
|
225
226
|
|
|
226
|
-
|
|
227
|
+
async with Spinner(f"Waiting for {request.model}...", delay=0.2):
|
|
228
|
+
response = await core_complete(request)
|
|
227
229
|
|
|
228
230
|
if self._config.log_requests:
|
|
229
231
|
logger.info(
|
|
@@ -395,10 +397,12 @@ class Flashlite:
|
|
|
395
397
|
else:
|
|
396
398
|
extra_kwargs["tools"] = tools_to_openai(tools)
|
|
397
399
|
|
|
398
|
-
# Build request
|
|
400
|
+
# Build request (template/variables stored for middleware traceability)
|
|
399
401
|
request = CompletionRequest(
|
|
400
402
|
model=resolved_model,
|
|
401
403
|
messages=final_messages,
|
|
404
|
+
template=template,
|
|
405
|
+
variables=variables,
|
|
402
406
|
temperature=temperature,
|
|
403
407
|
max_tokens=max_tokens,
|
|
404
408
|
max_completion_tokens=max_completion_tokens,
|