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,244 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import math
|
||||
from dataclasses import replace
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
|
||||
from ragcore.application.limits import ConcurrencyBudget
|
||||
from ragcore.application.models import EmbeddingProfileRegistry
|
||||
from ragcore.application.models.registry import EmbeddingProfileNotFoundError
|
||||
from ragcore.domain.control import DataPolicy
|
||||
from ragcore.domain.models import EmbeddingProfile, EmbeddingProvider, NormalizationMode
|
||||
from ragcore.infrastructure.models import InMemoryEmbeddingProfileStore, OllamaEmbeddingAdapter
|
||||
from ragcore.infrastructure.models.ollama import EmbeddingProviderError
|
||||
from ragcore.infrastructure.models.persistence import JsonEmbeddingProfileStore
|
||||
from ragcore.platform.telemetry import TelemetryMetrics
|
||||
|
||||
|
||||
def profile(**changes: object) -> EmbeddingProfile:
|
||||
value: dict[str, object] = {
|
||||
"id": uuid4(),
|
||||
"profile": "dense",
|
||||
"version": "v1",
|
||||
"provider": EmbeddingProvider.OLLAMA,
|
||||
"model_identifier": "nomic-embed-text:1.5",
|
||||
"model_digest": "a" * 64,
|
||||
"dimensions": 2,
|
||||
"context_limit": 4096,
|
||||
"tokenizer": "sentencepiece-v1",
|
||||
"data_policy": DataPolicy.LOCAL_ONLY,
|
||||
"max_batch_size": 2,
|
||||
"max_retries": 2,
|
||||
"normalization": NormalizationMode.L2,
|
||||
}
|
||||
value.update(changes)
|
||||
return EmbeddingProfile(**value)
|
||||
|
||||
|
||||
class FakeClient:
|
||||
def __init__(self, responses: list[httpx.Response]) -> None:
|
||||
self.responses = responses
|
||||
self.requests: list[dict[str, object]] = []
|
||||
|
||||
async def post(self, url: str, **kwargs: object) -> httpx.Response:
|
||||
self.requests.append({"url": url, **kwargs})
|
||||
return self.responses.pop(0)
|
||||
|
||||
|
||||
async def no_sleep(_: float) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def response(status: int, body: object) -> httpx.Response:
|
||||
return httpx.Response(status, json=body, request=httpx.Request("POST", "http://test"))
|
||||
|
||||
|
||||
|
||||
def test_adapter_rejects_nonlocal_provider_hosts() -> None:
|
||||
with pytest.raises(ValueError, match="allowlisted local"):
|
||||
OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([]), # type: ignore[arg-type]
|
||||
base_url="http://169.254.169.254",
|
||||
)
|
||||
def test_profile_is_immutable_and_rejects_floating_identifiers() -> None:
|
||||
value = profile()
|
||||
with pytest.raises(AttributeError):
|
||||
value.model_identifier = "nomic-embed-text:latest" # type: ignore[misc]
|
||||
with pytest.raises(ValueError, match="floating"):
|
||||
profile(model_identifier="nomic-embed-text:latest")
|
||||
with pytest.raises(ValueError, match="digest"):
|
||||
profile(model_digest="not-a-digest")
|
||||
with pytest.raises(ValueError, match="dimensions"):
|
||||
profile(dimensions=0)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_json_store_persists_profile_and_exact_active_id(tmp_path) -> None:
|
||||
path = tmp_path / "models.json"
|
||||
first = profile()
|
||||
store = JsonEmbeddingProfileStore(path)
|
||||
await store.add(first)
|
||||
await store.activate(first.id)
|
||||
|
||||
recovered = JsonEmbeddingProfileStore(path)
|
||||
assert await recovered.get(first.id) == first
|
||||
assert await recovered.get_active() == first
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_registry_activates_by_exact_profile_id_only() -> None:
|
||||
store = InMemoryEmbeddingProfileStore()
|
||||
registry = EmbeddingProfileRegistry(store)
|
||||
first = profile()
|
||||
second = profile(version="v2")
|
||||
await registry.register(first)
|
||||
with pytest.raises(ValueError, match="logical revision"):
|
||||
await registry.register(replace(first, id=uuid4(), model_digest="b" * 64))
|
||||
await registry.register(second)
|
||||
await registry.activate(second.id)
|
||||
assert (await registry.active()).id == second.id
|
||||
with pytest.raises(EmbeddingProfileNotFoundError):
|
||||
await registry.activate(uuid4())
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_adapter_batches_prefixes_and_normalizes() -> None:
|
||||
client = FakeClient(
|
||||
[
|
||||
response(200, {"embeddings": [[3.0, 4.0], [0.0, 2.0]]}),
|
||||
response(200, {"embeddings": [[1.0, 0.0]]}),
|
||||
]
|
||||
)
|
||||
adapter = OllamaEmbeddingAdapter(
|
||||
profile(document_prefix="passage: "),
|
||||
client, # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
sleep=no_sleep,
|
||||
)
|
||||
vectors = await adapter.embed(("one", "two", "three"))
|
||||
assert len(vectors) == 3
|
||||
assert all(math.isclose(math.sqrt(sum(x * x for x in vector)), 1.0) for vector in vectors)
|
||||
assert len(client.requests) == 2
|
||||
assert client.requests[0]["json"] == {
|
||||
"model": "nomic-embed-text:1.5",
|
||||
"input": ["passage: one", "passage: two"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_adapter_bounds_retries_timeout_and_response_shape() -> None:
|
||||
client = FakeClient(
|
||||
[
|
||||
response(503, {"error": "busy"}),
|
||||
response(429, {"error": "busy"}),
|
||||
response(200, {"embeddings": [[1.0, 0.0]]}),
|
||||
]
|
||||
)
|
||||
adapter = OllamaEmbeddingAdapter(profile(), client, base_url="http://ollama", sleep=no_sleep) # type: ignore[arg-type]
|
||||
assert await adapter.embed(("one",)) == ((1.0, 0.0),)
|
||||
assert len(client.requests) == 3
|
||||
|
||||
malformed = OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([response(200, {"embeddings": [[1.0]]})]), # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
sleep=no_sleep,
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="dimensions"):
|
||||
await malformed.embed(("one",))
|
||||
|
||||
cardinality = OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([response(200, {"embeddings": [[1.0, 0.0]]})]), # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
sleep=no_sleep,
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="cardinality"):
|
||||
await cardinality.embed(("one", "two"))
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_adapter_surfaces_concurrency_exhaustion_as_a_provider_error() -> None:
|
||||
budget = ConcurrencyBudget(name="embedding-calls", max_concurrent=1, max_queued=0)
|
||||
release = asyncio.Event()
|
||||
|
||||
async def occupy() -> None:
|
||||
async with budget.acquire():
|
||||
await release.wait()
|
||||
|
||||
holder = asyncio.create_task(occupy())
|
||||
await asyncio.sleep(0)
|
||||
|
||||
adapter = OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([]), # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
concurrency_budget=budget,
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="concurrency"):
|
||||
await adapter.embed(("one",))
|
||||
|
||||
release.set()
|
||||
await holder
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_adapter_rejects_zero_vector_and_unbounded_input() -> None:
|
||||
zero = OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([response(200, {"embeddings": [[0.0, 0.0]]})]), # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
sleep=no_sleep,
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="zero"):
|
||||
await zero.embed(("one",))
|
||||
with pytest.raises(ValueError, match="input"):
|
||||
await zero.embed(("x",) * 4_097)
|
||||
await zero.embed(("x" * 32_001,))
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
async def test_adapter_records_provider_duration_on_success_and_error_on_failure() -> None:
|
||||
reader = InMemoryMetricReader()
|
||||
provider = MeterProvider(
|
||||
resource=Resource.create({"service.name": "test"}), metric_readers=[reader]
|
||||
)
|
||||
metrics = TelemetryMetrics(meter_provider=provider)
|
||||
|
||||
ok_adapter = OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([response(200, {"embeddings": [[1.0, 0.0]]})]), # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
sleep=no_sleep,
|
||||
metrics=metrics,
|
||||
)
|
||||
await ok_adapter.embed(("one",))
|
||||
|
||||
failing_adapter = OllamaEmbeddingAdapter(
|
||||
profile(),
|
||||
FakeClient([response(200, {"embeddings": [[1.0]]})]), # type: ignore[arg-type]
|
||||
base_url="http://ollama",
|
||||
sleep=no_sleep,
|
||||
metrics=metrics,
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError):
|
||||
await failing_adapter.embed(("one",))
|
||||
|
||||
data = reader.get_metrics_data()
|
||||
assert data is not None
|
||||
names = {
|
||||
metric.name
|
||||
for resource_metric in data.resource_metrics
|
||||
for scope_metric in resource_metric.scope_metrics
|
||||
for metric in scope_metric.metrics
|
||||
}
|
||||
assert "ragcore_provider_duration_seconds" in names
|
||||
assert "ragcore_provider_errors_total" in names
|
||||
@@ -0,0 +1,225 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from ragcore.config import Settings
|
||||
from ragcore.domain.control import DataPolicy
|
||||
from ragcore.domain.models import EmbeddingProfile, EmbeddingProvider
|
||||
from ragcore.infrastructure.models.embedding_factory import (
|
||||
EmbeddingAdapterFactory,
|
||||
create_embedding_adapter_factory,
|
||||
)
|
||||
from ragcore.infrastructure.models.ollama import EmbeddingProviderError
|
||||
|
||||
|
||||
def ollama_profile() -> EmbeddingProfile:
|
||||
return EmbeddingProfile(
|
||||
id=uuid4(),
|
||||
profile="test-ollama",
|
||||
version="v1",
|
||||
provider=EmbeddingProvider.OLLAMA,
|
||||
model_identifier="nomic-embed-text:v1",
|
||||
model_digest="a" * 64,
|
||||
dimensions=2,
|
||||
context_limit=8192,
|
||||
tokenizer="sentencepiece-v1",
|
||||
data_policy=DataPolicy.LOCAL_ONLY,
|
||||
document_prefix="doc: ",
|
||||
query_prefix="query: ",
|
||||
)
|
||||
|
||||
|
||||
def modelforge_profile(**changes: object) -> EmbeddingProfile:
|
||||
values: dict[str, object] = {
|
||||
"id": uuid4(),
|
||||
"profile": "test-modelforge",
|
||||
"version": "v1",
|
||||
"provider": EmbeddingProvider.MODELFORGE,
|
||||
"model_identifier": "rag.embedding@1",
|
||||
"model_digest": "b" * 64,
|
||||
"dimensions": 3,
|
||||
"context_limit": 8192,
|
||||
"tokenizer": "capability-managed",
|
||||
"data_policy": DataPolicy.LOCAL_ONLY,
|
||||
"max_retries": 0,
|
||||
"embedding_space_id": uuid4(),
|
||||
}
|
||||
values.update(changes)
|
||||
return EmbeddingProfile(**values)
|
||||
|
||||
|
||||
def gateway_response(profile: EmbeddingProfile, count: int) -> dict[str, object]:
|
||||
return {
|
||||
"capability": "rag.embedding@1",
|
||||
"dimension": profile.dimensions,
|
||||
"normalized": True,
|
||||
"embedding_space_id": str(profile.embedding_space_id),
|
||||
"data": [[1.0, 0.0, 0.0] for _ in range(count)],
|
||||
"request_id": str(uuid4()),
|
||||
}
|
||||
|
||||
|
||||
def factory(
|
||||
client: httpx.AsyncClient,
|
||||
*,
|
||||
credential_file: Path,
|
||||
modelforge_base_url: str = "http://modelforge:8000",
|
||||
experiment_route: str | None = None,
|
||||
) -> EmbeddingAdapterFactory:
|
||||
settings = Settings(
|
||||
env="test",
|
||||
ollama_url="http://ollama:11434",
|
||||
modelforge_base_url=modelforge_base_url,
|
||||
modelforge_credential_file=credential_file,
|
||||
modelforge_embedding_capability="rag.embedding@1",
|
||||
modelforge_embedding_experiment_route=experiment_route,
|
||||
)
|
||||
return create_embedding_adapter_factory(settings=settings, http_client=client)
|
||||
|
||||
|
||||
def write_credential(path: Path) -> None:
|
||||
path.write_text("mf_" + "x" * 48, encoding="utf-8")
|
||||
path.chmod(0o600)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_factory_routes_modelforge_profile_to_pinned_capability(tmp_path: Path) -> None:
|
||||
profile = modelforge_profile()
|
||||
credential_file = tmp_path / "credential"
|
||||
write_credential(credential_file)
|
||||
seen: list[httpx.Request] = []
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
seen.append(request)
|
||||
body = json.loads(request.content)
|
||||
return httpx.Response(200, json=gateway_response(profile, len(body["input"])))
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.MockTransport(respond), follow_redirects=False
|
||||
) as client:
|
||||
adapter = factory(client, credential_file=credential_file)(profile)
|
||||
vectors = await adapter.embed(["question"], query=True)
|
||||
|
||||
assert vectors == ((1.0, 0.0, 0.0),)
|
||||
assert len(seen) == 1
|
||||
assert seen[0].url.host == "modelforge"
|
||||
assert seen[0].url.path == "/api/v1/capabilities/rag.embedding@1/invoke"
|
||||
assert json.loads(seen[0].content) == {"input": ["question"], "input_type": "query"}
|
||||
assert seen[0].headers["authorization"].startswith("Bearer mf_")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_factory_routes_exact_selection_experiment_key(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
profile = modelforge_profile()
|
||||
credential_file = tmp_path / "credential"
|
||||
write_credential(credential_file)
|
||||
seen: list[httpx.Request] = []
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
seen.append(request)
|
||||
body = json.loads(request.content)
|
||||
return httpx.Response(200, json=gateway_response(profile, len(body["input"])))
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.MockTransport(respond), follow_redirects=False
|
||||
) as client:
|
||||
adapter = factory(
|
||||
client,
|
||||
credential_file=credential_file,
|
||||
experiment_route="ignored-global-route",
|
||||
)(profile, experiment_route_key="rc-canary-2026-09")
|
||||
vectors = await adapter.embed(["document"])
|
||||
|
||||
assert vectors == ((1.0, 0.0, 0.0),)
|
||||
assert len(seen) == 1
|
||||
assert seen[0].url.path == ("/api/v1/capability-experiments/rc-canary-2026-09/invoke")
|
||||
assert json.loads(seen[0].content) == {"input": ["document"], "input_type": "document"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_factory_rejects_an_unsafe_configured_experiment_route(tmp_path: Path) -> None:
|
||||
def unused_handler(_request: httpx.Request) -> httpx.Response:
|
||||
return httpx.Response(500)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.MockTransport(unused_handler), follow_redirects=False
|
||||
) as client:
|
||||
configured = factory(
|
||||
client,
|
||||
credential_file=tmp_path / "credential",
|
||||
experiment_route="valid-global-route",
|
||||
)
|
||||
with pytest.raises(ValueError, match="experiment_route"):
|
||||
configured(modelforge_profile(), experiment_route_key="../production")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_modelforge_credential_fails_before_any_provider_request(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
seen: list[httpx.Request] = []
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
seen.append(request)
|
||||
return httpx.Response(200, json={"embeddings": [[0.0, 1.0]]})
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.MockTransport(respond), follow_redirects=False
|
||||
) as client:
|
||||
adapter = factory(client, credential_file=tmp_path / "missing")(modelforge_profile())
|
||||
with pytest.raises(EmbeddingProviderError, match="credential file"):
|
||||
await adapter.embed(["question"])
|
||||
|
||||
assert seen == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_failed_modelforge_gateway_never_falls_back_to_ollama(tmp_path: Path) -> None:
|
||||
profile = modelforge_profile()
|
||||
credential_file = tmp_path / "credential"
|
||||
write_credential(credential_file)
|
||||
seen: list[str] = []
|
||||
|
||||
def unavailable(request: httpx.Request) -> httpx.Response:
|
||||
seen.append(request.url.host + request.url.path)
|
||||
return httpx.Response(503)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.MockTransport(unavailable), follow_redirects=False
|
||||
) as client:
|
||||
adapter = factory(client, credential_file=credential_file)(profile)
|
||||
with pytest.raises(EmbeddingProviderError, match="unavailable"):
|
||||
await adapter.embed(["question"])
|
||||
|
||||
assert seen == ["modelforge/api/v1/capabilities/rag.embedding@1/invoke"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_factory_keeps_ollama_request_model_and_prefix_contract(tmp_path: Path) -> None:
|
||||
seen: list[httpx.Request] = []
|
||||
|
||||
def respond(request: httpx.Request) -> httpx.Response:
|
||||
seen.append(request)
|
||||
return httpx.Response(200, json={"embeddings": [[0.6, 0.8]]})
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
transport=httpx.MockTransport(respond), follow_redirects=False
|
||||
) as client:
|
||||
adapter = factory(client, credential_file=tmp_path / "unused")(ollama_profile())
|
||||
vectors = await adapter.embed(["question"], query=True)
|
||||
|
||||
assert vectors == ((0.6, 0.8),)
|
||||
assert len(seen) == 1
|
||||
assert seen[0].url.host == "ollama"
|
||||
assert seen[0].url.path == "/api/embed"
|
||||
assert json.loads(seen[0].content) == {
|
||||
"model": "nomic-embed-text:v1",
|
||||
"input": ["query: question"],
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from ragcore.domain.control import DataPolicy
|
||||
from ragcore.domain.models import EmbeddingProfile, EmbeddingProvider
|
||||
from ragcore.infrastructure.models import EmbeddingProviderError, ModelForgeEmbeddingAdapter
|
||||
|
||||
|
||||
def profile() -> EmbeddingProfile:
|
||||
return EmbeddingProfile(
|
||||
id=uuid4(),
|
||||
profile="modelforge-rag",
|
||||
version="v1",
|
||||
provider=EmbeddingProvider.MODELFORGE,
|
||||
model_identifier="rag.embedding@1",
|
||||
model_digest="a" * 64,
|
||||
dimensions=3,
|
||||
context_limit=8192,
|
||||
tokenizer="capability-managed",
|
||||
data_policy=DataPolicy.LOCAL_ONLY,
|
||||
max_batch_size=2,
|
||||
max_retries=1,
|
||||
embedding_space_id=uuid4(),
|
||||
)
|
||||
|
||||
|
||||
def credential(tmp_path: Path) -> Path:
|
||||
path = tmp_path / "credential"
|
||||
path.write_text("mf_" + "x" * 48, encoding="utf-8")
|
||||
path.chmod(0o600)
|
||||
return path
|
||||
|
||||
|
||||
def response(item: EmbeddingProfile, vectors: list[list[float]]) -> dict[str, object]:
|
||||
return {
|
||||
"capability": "rag.embedding@1",
|
||||
"dimension": item.dimensions,
|
||||
"normalized": True,
|
||||
"embedding_space_id": str(item.embedding_space_id),
|
||||
"data": vectors,
|
||||
"request_id": str(uuid4()),
|
||||
"execution": {
|
||||
"cold": False,
|
||||
"node": "Tower",
|
||||
"residency": "warm",
|
||||
"load_count": 1,
|
||||
"timings": {
|
||||
"gateway_ms": 1,
|
||||
"queue_ms": 0,
|
||||
"load_ms": 0,
|
||||
"inference_ms": 2,
|
||||
"total_ms": 3,
|
||||
},
|
||||
},
|
||||
"usage": {"input_count": len(vectors), "input_tokens": 1},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_capability_first_batch_auth_space_and_correlation(tmp_path: Path) -> None:
|
||||
item = profile()
|
||||
seen: list[httpx.Request] = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
seen.append(request)
|
||||
count = len(json.loads(request.content)["input"])
|
||||
return httpx.Response(200, json=response(item, [[1.0, 0.0, 0.0]] * count))
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item, client, base_url="http://modelforge:8000", credential_file=credential(tmp_path)
|
||||
)
|
||||
vectors, evidence = await adapter.embed_with_evidence(["one", "two", "three"])
|
||||
assert len(vectors) == 3
|
||||
assert len(seen) == 2
|
||||
assert all(
|
||||
request.url.path.endswith("/capabilities/rag.embedding@1/invoke") for request in seen
|
||||
)
|
||||
assert all(request.headers["authorization"].startswith("Bearer mf_") for request in seen)
|
||||
assert all(request.headers["x-correlation-id"].startswith("ragcore-") for request in seen)
|
||||
assert all(
|
||||
b"model" not in request.content and b"Tower" not in request.content for request in seen
|
||||
)
|
||||
assert evidence.total_ms >= evidence.gateway_round_trip_ms >= 0
|
||||
assert evidence.preparation_ms >= 0 and evidence.response_validation_ms >= 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("failure", ["capability", "space", "dimension", "nan"])
|
||||
async def test_response_contract_fails_closed(tmp_path: Path, failure: str) -> None:
|
||||
item = profile()
|
||||
body = response(item, [[1.0, 0.0, 0.0]])
|
||||
if failure == "capability":
|
||||
body["capability"] = "other.embedding@1"
|
||||
elif failure == "space":
|
||||
body["embedding_space_id"] = str(uuid4())
|
||||
elif failure == "dimension":
|
||||
body["dimension"] = 4
|
||||
else:
|
||||
body["data"] = [[float("nan"), 0.0, 0.0]]
|
||||
|
||||
def handler(_request: httpx.Request) -> httpx.Response:
|
||||
if failure == "nan":
|
||||
raw = json.dumps(body, allow_nan=True).encode()
|
||||
return httpx.Response(200, content=raw, headers={"content-type": "application/json"})
|
||||
return httpx.Response(200, json=body)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item, client, base_url="http://127.0.0.1:8000", credential_file=credential(tmp_path)
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError):
|
||||
await adapter.embed(["one"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_adapter_rejects_profile_that_does_not_match_configured_capability(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
item = profile()
|
||||
async with httpx.AsyncClient(follow_redirects=False) as async_client:
|
||||
with pytest.raises(ValueError, match="capability does not match"):
|
||||
ModelForgeEmbeddingAdapter(
|
||||
item,
|
||||
async_client,
|
||||
base_url="http://modelforge:8000",
|
||||
credential_file=credential(tmp_path),
|
||||
capability="other.embedding@1",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_normalization_uses_the_m5_bfloat16_contract_tolerance(tmp_path: Path) -> None:
|
||||
item = profile()
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
text = json.loads(request.content)["input"][0]
|
||||
vector = [1.019, 0.0, 0.0] if text == "inside" else [1.021, 0.0, 0.0]
|
||||
return httpx.Response(200, json=response(item, [vector]))
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item,
|
||||
client,
|
||||
base_url="http://127.0.0.1:8000",
|
||||
credential_file=credential(tmp_path),
|
||||
)
|
||||
assert await adapter.embed(["inside"]) == ((1.019, 0.0, 0.0),)
|
||||
with pytest.raises(EmbeddingProviderError, match="not normalized"):
|
||||
await adapter.embed(["outside"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_transient_but_not_unauthorized(tmp_path: Path) -> None:
|
||||
item = profile()
|
||||
attempts = 0
|
||||
|
||||
def transient(_request: httpx.Request) -> httpx.Response:
|
||||
nonlocal attempts
|
||||
attempts += 1
|
||||
return httpx.Response(503 if attempts == 1 else 200, json=response(item, [[1, 0, 0]]))
|
||||
|
||||
async def no_sleep(_seconds: float) -> None:
|
||||
return None
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(transient)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item,
|
||||
client,
|
||||
base_url="http://127.0.0.1:8000",
|
||||
credential_file=credential(tmp_path),
|
||||
sleep=no_sleep,
|
||||
)
|
||||
assert await adapter.embed(["one"]) == ((1.0, 0.0, 0.0),)
|
||||
assert attempts == 2
|
||||
|
||||
denied_attempts = 0
|
||||
|
||||
def denied(_request: httpx.Request) -> httpx.Response:
|
||||
nonlocal denied_attempts
|
||||
denied_attempts += 1
|
||||
return httpx.Response(401)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(denied)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item,
|
||||
client,
|
||||
base_url="http://127.0.0.1:8000",
|
||||
credential_file=credential(tmp_path),
|
||||
sleep=no_sleep,
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="credential"):
|
||||
await adapter.embed(["one"])
|
||||
assert denied_attempts == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_cloud_host_is_rejected(tmp_path: Path) -> None:
|
||||
async with httpx.AsyncClient() as client:
|
||||
with pytest.raises(ValueError, match="local/private"):
|
||||
ModelForgeEmbeddingAdapter(
|
||||
profile(),
|
||||
client,
|
||||
base_url="https://api.example.com",
|
||||
credential_file=credential(tmp_path),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_successful_batch_gateway_ids_are_retained_in_order(tmp_path: Path) -> None:
|
||||
item = profile()
|
||||
identifiers = [uuid4(), uuid4()]
|
||||
calls = 0
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
nonlocal calls
|
||||
count = len(json.loads(request.content)["input"])
|
||||
body = response(item, [[1, 0, 0]] * count)
|
||||
body["request_id"] = str(identifiers[calls])
|
||||
calls += 1
|
||||
return httpx.Response(200, json=body)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item, client, base_url="http://modelforge:8000", credential_file=credential(tmp_path)
|
||||
)
|
||||
vectors, evidence = await adapter.embed_with_evidence(["one", "two", "three"])
|
||||
assert len(vectors) == 3
|
||||
assert evidence.gateway_request_ids == tuple(identifiers)
|
||||
empty, empty_evidence = await adapter.embed_with_evidence([])
|
||||
assert empty == () and empty_evidence.gateway_request_ids == ()
|
||||
assert calls == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"identifier", [None, "not-a-uuid", "00000000-0000-0000-0000-000000000000", 12, True]
|
||||
)
|
||||
async def test_missing_or_invalid_gateway_identity_fails_closed(
|
||||
tmp_path: Path, identifier: object
|
||||
) -> None:
|
||||
item = profile()
|
||||
calls = 0
|
||||
|
||||
def handler(_request: httpx.Request) -> httpx.Response:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
body = response(item, [[1, 0, 0]])
|
||||
body["request_id"] = identifier
|
||||
return httpx.Response(200, json=body)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item, client, base_url="http://modelforge:8000", credential_file=credential(tmp_path)
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="gateway request identity"):
|
||||
await adapter.embed_with_evidence(["one"])
|
||||
assert calls == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_gateway_identity_across_batches_is_rejected(tmp_path: Path) -> None:
|
||||
item = profile()
|
||||
identifier = uuid4()
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
count = len(json.loads(request.content)["input"])
|
||||
body = response(item, [[1, 0, 0]] * count)
|
||||
body["request_id"] = str(identifier)
|
||||
return httpx.Response(200, json=body)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item, client, base_url="http://modelforge:8000", credential_file=credential(tmp_path)
|
||||
)
|
||||
with pytest.raises(EmbeddingProviderError, match="gateway request identity"):
|
||||
await adapter.embed_with_evidence(["one", "two", "three"])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_only_successful_retry_contributes_gateway_evidence(tmp_path: Path) -> None:
|
||||
item = profile()
|
||||
identifiers = [uuid4(), uuid4()]
|
||||
calls = 0
|
||||
|
||||
def handler(_request: httpx.Request) -> httpx.Response:
|
||||
nonlocal calls
|
||||
body = response(item, [[1, 0, 0]])
|
||||
body["request_id"] = str(identifiers[calls])
|
||||
calls += 1
|
||||
return httpx.Response(503 if calls == 1 else 200, json=body)
|
||||
|
||||
async def no_sleep(_seconds: float) -> None:
|
||||
return None
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
|
||||
adapter = ModelForgeEmbeddingAdapter(
|
||||
item,
|
||||
client,
|
||||
base_url="http://modelforge:8000",
|
||||
credential_file=credential(tmp_path),
|
||||
sleep=no_sleep,
|
||||
)
|
||||
_, evidence = await adapter.embed_with_evidence(["unchanged exact query"], query=True)
|
||||
assert evidence.gateway_request_ids == (identifiers[1],)
|
||||
assert calls == 2
|
||||
Reference in new issue
Block a user