Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e
This commit is contained in:
commit
7ca726caf2
659 files changed
+98171
No files matched your search
@@ -0,0 +1,110 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from ragcore.application.models import GenerationProfileRegistry
|
||||
from ragcore.domain.control.entities import DataPolicy
|
||||
from ragcore.domain.models import GenerationProfile, GenerationProvider
|
||||
from ragcore.infrastructure.models.answer_generator import (
|
||||
AnswerGenerationUnavailableError,
|
||||
OllamaAnswerGenerator,
|
||||
)
|
||||
|
||||
|
||||
class InMemoryGenerationProfileStore:
|
||||
def __init__(self) -> None:
|
||||
self._profiles: dict[UUID, GenerationProfile] = {}
|
||||
self._active_id: UUID | None = None
|
||||
|
||||
async def add(self, profile: GenerationProfile) -> None:
|
||||
self._profiles[profile.id] = profile
|
||||
|
||||
async def get(self, profile_id: UUID) -> GenerationProfile | None:
|
||||
return self._profiles.get(profile_id)
|
||||
|
||||
async def activate(self, profile_id: UUID) -> None:
|
||||
self._active_id = profile_id
|
||||
|
||||
async def get_active(self) -> GenerationProfile | None:
|
||||
return self._profiles.get(self._active_id) if self._active_id else None
|
||||
|
||||
|
||||
def _profile(**overrides: object) -> GenerationProfile:
|
||||
values: dict[str, object] = {
|
||||
"id": uuid4(),
|
||||
"profile": "answer-hybrid",
|
||||
"version": "v1",
|
||||
"provider": GenerationProvider.OLLAMA,
|
||||
"model_identifier": "llama3.1:8b-instruct-q4_0",
|
||||
"model_digest": "a" * 64,
|
||||
"context_limit": 4096,
|
||||
"max_output_tokens": 512,
|
||||
"temperature": 0.2,
|
||||
"data_policy": DataPolicy.LOCAL_ONLY,
|
||||
"timeout_seconds": 5.0,
|
||||
"max_retries": 0,
|
||||
}
|
||||
values.update(overrides)
|
||||
return GenerationProfile(**values)
|
||||
|
||||
|
||||
def _ndjson(*lines: dict[str, object]) -> bytes:
|
||||
return b"\n".join(json.dumps(line).encode() for line in lines)
|
||||
|
||||
|
||||
def _client(handler) -> httpx.AsyncClient: # type: ignore[no-untyped-def]
|
||||
return httpx.AsyncClient(transport=httpx.MockTransport(handler), follow_redirects=False)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generates_text_from_the_active_profile() -> None:
|
||||
store = InMemoryGenerationProfileStore()
|
||||
registry = GenerationProfileRegistry(store)
|
||||
profile = await registry.register(_profile())
|
||||
await registry.activate(profile.id)
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
200,
|
||||
content=_ndjson(
|
||||
{"response": "Restart the ", "done": False},
|
||||
{"response": "payment service.", "done": True},
|
||||
),
|
||||
)
|
||||
|
||||
generator = OllamaAnswerGenerator(registry, _client(handler), base_url="http://ollama")
|
||||
text = await generator.generate(
|
||||
prompt="How do I restart it?", generation_profile="anything", spaces=()
|
||||
)
|
||||
assert text == "Restart the payment service."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_raises_when_no_active_profile() -> None:
|
||||
registry = GenerationProfileRegistry(InMemoryGenerationProfileStore())
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
raise AssertionError("must not call Ollama without an active profile")
|
||||
|
||||
generator = OllamaAnswerGenerator(registry, _client(handler), base_url="http://ollama")
|
||||
with pytest.raises(AnswerGenerationUnavailableError):
|
||||
await generator.generate(prompt="anything", generation_profile="anything", spaces=())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_raises_when_the_provider_rejects_the_request() -> None:
|
||||
store = InMemoryGenerationProfileStore()
|
||||
registry = GenerationProfileRegistry(store)
|
||||
profile = await registry.register(_profile())
|
||||
await registry.activate(profile.id)
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(500, json={"error": "model not found"})
|
||||
|
||||
generator = OllamaAnswerGenerator(registry, _client(handler), base_url="http://ollama")
|
||||
with pytest.raises(AnswerGenerationUnavailableError):
|
||||
await generator.generate(prompt="anything", generation_profile="anything", spaces=())
|
||||
@@ -0,0 +1,131 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from ragcore.domain.control.entities import DataPolicy
|
||||
from ragcore.domain.models import GenerationProfile, GenerationProfileError, GenerationProvider
|
||||
|
||||
|
||||
def _profile(**overrides: object) -> GenerationProfile:
|
||||
values: dict[str, object] = {
|
||||
"id": uuid4(),
|
||||
"profile": "answer-hybrid",
|
||||
"version": "v1",
|
||||
"provider": GenerationProvider.OLLAMA,
|
||||
"model_identifier": "llama3.1:8b-instruct-q4_0",
|
||||
"model_digest": "a" * 64,
|
||||
"context_limit": 8192,
|
||||
"max_output_tokens": 1024,
|
||||
"temperature": 0.4,
|
||||
"data_policy": DataPolicy.LOCAL_ONLY,
|
||||
}
|
||||
values.update(overrides)
|
||||
return GenerationProfile(**values)
|
||||
|
||||
|
||||
def test_valid_profile_is_accepted() -> None:
|
||||
profile = _profile()
|
||||
assert profile.provider is GenerationProvider.OLLAMA
|
||||
assert profile.data_policy is DataPolicy.LOCAL_ONLY
|
||||
|
||||
|
||||
def test_profile_rejects_nil_id() -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(id=UUID(int=0))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bad_id", ["not a safe id!", "Also-Not-Safe", ""])
|
||||
def test_profile_rejects_unsafe_profile_or_version(bad_id: str) -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(profile=bad_id)
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(version=bad_id)
|
||||
|
||||
|
||||
def test_profile_rejects_floating_model_tags() -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(model_identifier="llama3.1:latest")
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(model_identifier="llama3.1:stable")
|
||||
|
||||
|
||||
def test_profile_requires_a_pinned_sha256_digest() -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(model_digest="not-a-digest")
|
||||
|
||||
|
||||
def test_max_output_tokens_must_not_exceed_context_limit() -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(context_limit=100, max_output_tokens=200)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("temperature", [-0.1, 2.1, float("nan"), float("inf")])
|
||||
def test_temperature_must_be_within_bounds(temperature: float) -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(temperature=temperature)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("policy", [DataPolicy.RETRIEVAL_ONLY, DataPolicy.NO_GENERATION])
|
||||
def test_profile_rejects_space_only_data_policies(policy: DataPolicy) -> None:
|
||||
with pytest.raises(GenerationProfileError):
|
||||
_profile(data_policy=policy)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"policy", [DataPolicy.LOCAL_ONLY, DataPolicy.LOCAL_PREFERRED, DataPolicy.CLOUD_ALLOWED]
|
||||
)
|
||||
def test_profile_accepts_generation_eligible_data_policies(policy: DataPolicy) -> None:
|
||||
profile = _profile(data_policy=policy)
|
||||
assert profile.data_policy is policy
|
||||
|
||||
|
||||
def test_resolve_data_policy_never_broadens_beyond_the_profile() -> None:
|
||||
profile = _profile(data_policy=DataPolicy.LOCAL_ONLY)
|
||||
assert profile.resolve_data_policy(DataPolicy.CLOUD_ALLOWED) is DataPolicy.LOCAL_ONLY
|
||||
assert profile.resolve_data_policy(DataPolicy.LOCAL_PREFERRED) is DataPolicy.LOCAL_ONLY
|
||||
assert profile.resolve_data_policy(DataPolicy.LOCAL_ONLY) is DataPolicy.LOCAL_ONLY
|
||||
assert profile.resolve_data_policy(None) is DataPolicy.LOCAL_ONLY
|
||||
|
||||
|
||||
def test_resolve_data_policy_can_narrow_a_looser_profile() -> None:
|
||||
profile = _profile(data_policy=DataPolicy.CLOUD_ALLOWED)
|
||||
assert profile.resolve_data_policy(DataPolicy.LOCAL_ONLY) is DataPolicy.LOCAL_ONLY
|
||||
assert profile.resolve_data_policy(None) is DataPolicy.CLOUD_ALLOWED
|
||||
|
||||
|
||||
def test_resolve_data_policy_rejects_a_non_generation_request() -> None:
|
||||
profile = _profile()
|
||||
with pytest.raises(GenerationProfileError):
|
||||
profile.resolve_data_policy(DataPolicy.NO_GENERATION)
|
||||
|
||||
|
||||
def test_resolve_temperature_never_exceeds_the_profile_ceiling() -> None:
|
||||
profile = _profile(temperature=0.4)
|
||||
assert profile.resolve_temperature(1.5) == pytest.approx(0.4)
|
||||
assert profile.resolve_temperature(0.1) == pytest.approx(0.1)
|
||||
assert profile.resolve_temperature(None) == pytest.approx(0.4)
|
||||
|
||||
|
||||
def test_resolve_temperature_rejects_invalid_requests() -> None:
|
||||
profile = _profile()
|
||||
with pytest.raises(GenerationProfileError):
|
||||
profile.resolve_temperature(-1.0)
|
||||
with pytest.raises(GenerationProfileError):
|
||||
profile.resolve_temperature(float("nan"))
|
||||
|
||||
|
||||
def test_resolve_max_output_tokens_never_exceeds_the_profile_cap() -> None:
|
||||
profile = _profile(max_output_tokens=1024)
|
||||
assert profile.resolve_max_output_tokens(4096) == 1024
|
||||
assert profile.resolve_max_output_tokens(256) == 256
|
||||
assert profile.resolve_max_output_tokens(None) == 1024
|
||||
|
||||
|
||||
def test_resolve_max_output_tokens_rejects_invalid_requests() -> None:
|
||||
profile = _profile()
|
||||
with pytest.raises(GenerationProfileError):
|
||||
profile.resolve_max_output_tokens(0)
|
||||
with pytest.raises(GenerationProfileError):
|
||||
profile.resolve_max_output_tokens(-5)
|
||||
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from ragcore.application.models import (
|
||||
DuplicateGenerationProfileError,
|
||||
GenerationProfileNotFoundError,
|
||||
GenerationProfileRegistry,
|
||||
)
|
||||
from ragcore.domain.control.entities import DataPolicy
|
||||
from ragcore.domain.models import GenerationProfile, GenerationProvider
|
||||
|
||||
|
||||
class InMemoryGenerationProfileStore:
|
||||
def __init__(self) -> None:
|
||||
self._profiles: dict[UUID, GenerationProfile] = {}
|
||||
self._active_id: UUID | None = None
|
||||
|
||||
async def add(self, profile: GenerationProfile) -> None:
|
||||
self._profiles[profile.id] = profile
|
||||
|
||||
async def get(self, profile_id: UUID) -> GenerationProfile | None:
|
||||
return self._profiles.get(profile_id)
|
||||
|
||||
async def activate(self, profile_id: UUID) -> None:
|
||||
self._active_id = profile_id
|
||||
|
||||
async def get_active(self) -> GenerationProfile | None:
|
||||
return self._profiles.get(self._active_id) if self._active_id else None
|
||||
|
||||
|
||||
def _profile(**overrides: object) -> GenerationProfile:
|
||||
values: dict[str, object] = {
|
||||
"id": uuid4(),
|
||||
"profile": "answer-hybrid",
|
||||
"version": "v1",
|
||||
"provider": GenerationProvider.OLLAMA,
|
||||
"model_identifier": "llama3.1:8b-instruct-q4_0",
|
||||
"model_digest": "a" * 64,
|
||||
"context_limit": 8192,
|
||||
"max_output_tokens": 1024,
|
||||
"temperature": 0.4,
|
||||
"data_policy": DataPolicy.LOCAL_ONLY,
|
||||
}
|
||||
values.update(overrides)
|
||||
return GenerationProfile(**values)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_persists_a_new_profile() -> None:
|
||||
registry = GenerationProfileRegistry(InMemoryGenerationProfileStore())
|
||||
profile = _profile()
|
||||
registered = await registry.register(profile)
|
||||
assert registered.id == profile.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_rejects_a_duplicate_id() -> None:
|
||||
store = InMemoryGenerationProfileStore()
|
||||
registry = GenerationProfileRegistry(store)
|
||||
profile = _profile()
|
||||
await registry.register(profile)
|
||||
with pytest.raises(DuplicateGenerationProfileError):
|
||||
await registry.register(profile)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_activate_requires_a_registered_profile() -> None:
|
||||
registry = GenerationProfileRegistry(InMemoryGenerationProfileStore())
|
||||
with pytest.raises(GenerationProfileNotFoundError):
|
||||
await registry.activate(uuid4())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_active_requires_something_activated_first() -> None:
|
||||
store = InMemoryGenerationProfileStore()
|
||||
registry = GenerationProfileRegistry(store)
|
||||
profile = await registry.register(_profile())
|
||||
with pytest.raises(GenerationProfileNotFoundError):
|
||||
await registry.active()
|
||||
|
||||
activated = await registry.activate(profile.id)
|
||||
assert activated.id == profile.id
|
||||
assert (await registry.active()).id == profile.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_activating_a_second_profile_replaces_the_active_one() -> None:
|
||||
store = InMemoryGenerationProfileStore()
|
||||
registry = GenerationProfileRegistry(store)
|
||||
first = await registry.register(_profile())
|
||||
second = await registry.register(_profile(id=uuid4(), version="v2"))
|
||||
await registry.activate(first.id)
|
||||
await registry.activate(second.id)
|
||||
assert (await registry.active()).id == second.id
|
||||
Reference in new issue
Block a user