205 lines
6.5 KiB
Python
205 lines
6.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Tests for the provider-agnostic prompt enhancer."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from dataclasses import dataclass
|
|
|
|
import pytest
|
|
|
|
from fastvideo.entrypoints.streaming.prompt import (
|
|
LLMProvider,
|
|
LLMProviderError,
|
|
LLMRequest,
|
|
LLMResponse,
|
|
PromptEnhancer,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class _StaticProvider:
|
|
name: str
|
|
content: str
|
|
|
|
async def complete(self, request: LLMRequest) -> LLMResponse:
|
|
return LLMResponse(
|
|
content=self.content,
|
|
provider=self.name,
|
|
model=request.model,
|
|
latency_ms=1.0,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class _FailingProvider:
|
|
name: str
|
|
message: str = "boom"
|
|
retryable: bool = True
|
|
|
|
async def complete(self, request: LLMRequest) -> LLMResponse:
|
|
raise LLMProviderError(self.message, retryable=self.retryable)
|
|
|
|
|
|
class TestConstruction:
|
|
|
|
def test_requires_at_least_one_provider(self):
|
|
with pytest.raises(ValueError):
|
|
PromptEnhancer(providers=[], model="m")
|
|
|
|
def test_registers_system_prompts_from_defaults(self, tmp_path):
|
|
enh = PromptEnhancer(
|
|
providers=[_StaticProvider("p", "ok")],
|
|
model="m",
|
|
)
|
|
# Defaults live inside _DEFAULT_SYSTEM_PROMPTS; no hot-reload file
|
|
# means the enhancer falls back to the shipped values.
|
|
assert "prompt enhancer" in enh._system_prompts.enhance.lower()
|
|
|
|
def test_reads_override_files_when_present(self, tmp_path):
|
|
(tmp_path / "enhance.txt").write_text("customized enhance")
|
|
(tmp_path / "auto_extend.txt").write_text("") # empty -> default
|
|
enh = PromptEnhancer(
|
|
providers=[_StaticProvider("p", "ok")],
|
|
model="m",
|
|
system_prompt_dir=str(tmp_path),
|
|
)
|
|
assert enh._system_prompts.enhance == "customized enhance"
|
|
# Empty file fell back to the default.
|
|
assert "continuation assistant" in enh._system_prompts.auto_extend
|
|
|
|
|
|
class TestEnhance:
|
|
|
|
def test_enhance_returns_primary_provider_response(self):
|
|
enh = PromptEnhancer(
|
|
providers=[_StaticProvider("primary", "enhanced!")],
|
|
model="m",
|
|
)
|
|
response = asyncio.run(enh.enhance("a fox"))
|
|
assert response.content == "enhanced!"
|
|
assert response.provider == "primary"
|
|
assert response.fallback_used is False
|
|
|
|
def test_enhance_falls_back_on_provider_error(self):
|
|
enh = PromptEnhancer(
|
|
providers=[
|
|
_FailingProvider("a"),
|
|
_StaticProvider("b", "fallback!"),
|
|
],
|
|
model="m",
|
|
)
|
|
response = asyncio.run(enh.enhance("x"))
|
|
assert response.provider == "b"
|
|
assert response.content == "fallback!"
|
|
assert response.fallback_used is True
|
|
|
|
def test_enhance_stops_on_non_retryable_error(self):
|
|
enh = PromptEnhancer(
|
|
providers=[
|
|
_FailingProvider("a", message="hard-fail", retryable=False),
|
|
_StaticProvider("b", "should-not-be-reached"),
|
|
],
|
|
model="m",
|
|
)
|
|
with pytest.raises(LLMProviderError, match="hard-fail"):
|
|
asyncio.run(enh.enhance("x"))
|
|
|
|
def test_enhance_raises_when_all_providers_fail(self):
|
|
enh = PromptEnhancer(
|
|
providers=[
|
|
_FailingProvider("a"),
|
|
_FailingProvider("b"),
|
|
],
|
|
model="m",
|
|
)
|
|
with pytest.raises(LLMProviderError):
|
|
asyncio.run(enh.enhance("x"))
|
|
|
|
|
|
class TestAutoExtendAndRewrite:
|
|
|
|
def test_auto_extend_joins_prior_prompts(self):
|
|
captured: list[str] = []
|
|
|
|
class _Capturer:
|
|
name = "cap"
|
|
|
|
async def complete(self, request: LLMRequest) -> LLMResponse:
|
|
user = next(m.content for m in request.messages if m.role == "user")
|
|
captured.append(user)
|
|
return LLMResponse(
|
|
content="next",
|
|
provider=self.name,
|
|
model=request.model,
|
|
latency_ms=1.0,
|
|
)
|
|
|
|
enh = PromptEnhancer(providers=[_Capturer()], model="m")
|
|
asyncio.run(enh.auto_extend(["first", "second"]))
|
|
assert captured[0] == "first\nsecond"
|
|
|
|
def test_rewrite_passes_seed_through(self):
|
|
captured: list[str] = []
|
|
|
|
class _Capturer:
|
|
name = "cap"
|
|
|
|
async def complete(self, request: LLMRequest) -> LLMResponse:
|
|
user = next(m.content for m in request.messages if m.role == "user")
|
|
captured.append(user)
|
|
return LLMResponse(
|
|
content="one\ntwo\nthree",
|
|
provider=self.name,
|
|
model=request.model,
|
|
latency_ms=1.0,
|
|
)
|
|
|
|
enh = PromptEnhancer(providers=[_Capturer()], model="m")
|
|
response = asyncio.run(enh.rewrite("seed prompt"))
|
|
assert captured == ["seed prompt"]
|
|
assert response.content.splitlines() == ["one", "two", "three"]
|
|
|
|
|
|
class TestRegisterProvider:
|
|
|
|
def test_register_appends_by_default(self):
|
|
enh = PromptEnhancer(
|
|
providers=[_StaticProvider("primary", "a")],
|
|
model="m",
|
|
)
|
|
enh.register_provider(_StaticProvider("extra", "b"))
|
|
assert [p.name for p in enh.providers] == ["primary", "extra"]
|
|
|
|
def test_register_priority_zero_makes_primary(self):
|
|
enh = PromptEnhancer(
|
|
providers=[_StaticProvider("old", "a")],
|
|
model="m",
|
|
)
|
|
enh.register_provider(_StaticProvider("new-primary", "b"), priority=0)
|
|
assert enh.providers[0].name == "new-primary"
|
|
|
|
def test_registered_provider_is_used_in_enhance(self):
|
|
enh = PromptEnhancer(
|
|
providers=[_FailingProvider("broken")],
|
|
model="m",
|
|
)
|
|
enh.register_provider(_StaticProvider("fallback", "ok"))
|
|
response = asyncio.run(enh.enhance("x"))
|
|
assert response.content == "ok"
|
|
assert response.fallback_used is True
|
|
|
|
|
|
class TestHotReload:
|
|
|
|
def test_reload_picks_up_new_file(self, tmp_path):
|
|
(tmp_path / "enhance.txt").write_text("first version")
|
|
enh = PromptEnhancer(
|
|
providers=[_StaticProvider("p", "ok")],
|
|
model="m",
|
|
system_prompt_dir=str(tmp_path),
|
|
)
|
|
assert enh._system_prompts.enhance == "first version"
|
|
(tmp_path / "enhance.txt").write_text("second version")
|
|
enh.reload_system_prompts()
|
|
assert enh._system_prompts.enhance == "second version"
|