Initial curated RAGcore source from c16667f0c9397e2bfc8dd0255b114fba23c8d05e

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:34:21 +02:00
commit 7ca726caf2
659 files changed
+98171

No files matched your search

+399
View File
@@ -0,0 +1,399 @@
"""Fixture FastAPI apps for the SDK contract tests.
Mirrors the same fixture-construction pattern used by the existing
`tests/contract/api/{search,context,answers}` contract tests (real
`RetrievalAuthorizationService`, a fixture executor/generator standing in
only for the retrieval backend/model provider) -- these tests exist to
prove the SDK's HTTP layer (request shape, response typing, SSE parsing,
Problem Details mapping, retries), not to re-prove authorization/retrieval
business logic that is already covered there.
"""
from __future__ import annotations
import asyncio
import contextlib
import json
import re
import socket
import sys
import threading
import time
from collections.abc import Iterator
from dataclasses import dataclass, replace
from datetime import UTC, datetime
from typing import Any
from uuid import UUID, uuid4
import uvicorn
from fastapi import FastAPI
from ragcore.api.v1.answers.dependencies import get_answer_application, get_answer_principal
from ragcore.api.v1.answers.service import AnswerService
from ragcore.api.v1.context.dependencies import get_context_application, get_context_principal
from ragcore.api.v1.context.service import (
ContextExecutionRequest,
ContextExecutionResult,
ContextService,
)
from ragcore.api.v1.search.dependencies import get_search_application, get_search_principal
from ragcore.api.v1.search.service import SearchHit, SearchQuery, SearchService
from ragcore.application.retrieval.authorization import RetrievalAuthorizationService
from ragcore.domain.authorization import Principal, PrincipalType
from ragcore.domain.citations import Citation
from ragcore.domain.control import Application, Role, Workspace
from ragcore.main import create_app
from ragcore.retrieval.context import ContextCandidate
from ragcore.retrieval.ranking import FusedCandidate
from ragcore.retrieval.search.models import Candidate, CandidateBackendKind, CandidateProvenance
EVIDENCE_EXCERPT = "The refund window is thirty days from the original purchase date."
class _FixtureInputs:
def __init__(self, workspace: Workspace, space_id: UUID, space_name: str) -> None:
self.workspace = workspace
self.space_id = space_id
self.space_name = space_name
async def load(self, principal: Principal) -> Any:
space = self.workspace.create_knowledge_space(
id=self.space_id, name=self.space_name, slug=self.space_name.lower().replace(" ", "-")
)
return type(
"Inputs",
(),
{
"spaces": (space,),
"grants": (),
"user_grants": (
self.workspace.grant_user(
id=uuid4(), user_id=principal.id, knowledge_space=space, role=Role.EDITOR
),
),
"application": Application(
id=uuid4(),
workspace_id=self.workspace.id,
name=f"{self.space_name} application",
default_knowledge_space_ids=frozenset({self.space_id}),
),
},
)()
@dataclass
class SearchFixture:
app: FastAPI
principal: Principal
space_id: UUID
calls: list[SearchQuery]
def build_search_app() -> SearchFixture:
workspace = Workspace(uuid4(), "SDK search workspace", "sdk-search-workspace")
space_id = uuid4()
principal = Principal(uuid4(), workspace.id, PrincipalType.HUMAN, frozenset({Role.VIEWER}))
document_id, version_id, chunk_id = uuid4(), uuid4(), uuid4()
citation = Citation.create(
workspace_id=workspace.id,
space_id=space_id,
document_id=document_id,
document_version_id=version_id,
chunk_id=chunk_id,
parent_chunk_id=None,
location={"page_start": 4, "page_end": 4, "section": "Refunds"},
excerpt=EVIDENCE_EXCERPT,
source_uri="https://example.test/policy.md",
acquired_at=datetime(2026, 8, 1, 12, tzinfo=UTC),
retrieval_run_id=uuid4(),
index_alias="rag_dense_hybrid_active",
profile_version="hybrid-v1",
)
candidate = Candidate(
point_id=uuid4(),
score=0.87,
rank=1,
backend=CandidateBackendKind.DENSE,
provenance=CandidateProvenance(
workspace_id=workspace.id,
space_id=space_id,
document_id=document_id,
document_version_id=version_id,
chunk_id=chunk_id,
source_id=uuid4(),
parent_chunk_id=None,
status="published",
content_type="text/markdown",
language="en",
trust_level="trusted_internal",
),
)
hit = SearchHit(
fused=FusedCandidate(candidate, 1 / 61, 1, None),
text=EVIDENCE_EXCERPT,
title="Refund policy",
citation=citation,
dense_score=0.87,
)
calls: list[SearchQuery] = []
class _Executor:
async def execute(self, request: Any) -> Any:
refreshed = replace(
hit,
citation=Citation.create(
workspace_id=hit.citation.workspace_id,
space_id=hit.citation.space_id,
document_id=hit.citation.document_id,
document_version_id=hit.citation.document_version_id,
chunk_id=hit.citation.chunk_id,
parent_chunk_id=hit.citation.parent_chunk_id,
location=hit.citation.location,
excerpt=hit.citation.excerpt,
source_uri=hit.citation.source_uri,
acquired_at=hit.citation.acquired_at,
retrieval_run_id=request.retrieval_run_id,
index_alias=hit.citation.index_alias,
profile_version=hit.citation.profile_version,
),
)
return type(
"Result",
(),
{
"hits": (refreshed,),
"degraded": False,
"degradation_codes": (),
"timings_ms": {},
},
)()
application = Application(
id=uuid4(),
workspace_id=workspace.id,
name="SDK search application",
default_knowledge_space_ids=frozenset({space_id}),
)
class _Inputs(_FixtureInputs):
async def load(self, principal: Principal) -> Any:
base = await super().load(principal)
return type(
"Inputs",
(),
{
"spaces": base.spaces,
"grants": base.grants,
"user_grants": base.user_grants,
"application": application,
},
)()
service = SearchService(
authorization=RetrievalAuthorizationService(),
inputs=_Inputs(workspace, space_id, "SDK search space"),
executor=_Executor(),
)
app = create_app()
app.dependency_overrides[get_search_principal] = lambda: principal
app.dependency_overrides[get_search_application] = lambda: service
return SearchFixture(app=app, principal=principal, space_id=space_id, calls=calls)
@dataclass
class ContextFixture:
app: FastAPI
principal: Principal
space_id: UUID
def build_context_app() -> ContextFixture:
workspace = Workspace(uuid4(), "SDK context workspace", "sdk-context-workspace")
space_id = uuid4()
principal = Principal(uuid4(), workspace.id, PrincipalType.HUMAN, frozenset({Role.EDITOR}))
citation = Citation.create(
workspace_id=workspace.id,
space_id=space_id,
document_id=uuid4(),
document_version_id=uuid4(),
chunk_id=uuid4(),
parent_chunk_id=None,
location={"page_start": 1},
excerpt=EVIDENCE_EXCERPT,
source_uri="https://example.test/context.md",
acquired_at=datetime(2026, 8, 1, 12, tzinfo=UTC),
retrieval_run_id=uuid4(),
index_alias="rag_dense_hybrid_active",
profile_version="hybrid-v1",
)
candidate = ContextCandidate(citation, EVIDENCE_EXCERPT, 1, 1.0)
class _Executor:
async def execute(self, request: ContextExecutionRequest) -> ContextExecutionResult:
refreshed = Citation.create(
workspace_id=citation.workspace_id,
space_id=citation.space_id,
document_id=citation.document_id,
document_version_id=citation.document_version_id,
chunk_id=citation.chunk_id,
parent_chunk_id=citation.parent_chunk_id,
location=citation.location,
excerpt=citation.excerpt,
source_uri=citation.source_uri,
acquired_at=citation.acquired_at,
retrieval_run_id=request.retrieval_run_id,
index_alias=citation.index_alias,
profile_version=citation.profile_version,
)
return ContextExecutionResult(candidates=(replace(candidate, citation=refreshed),))
service = ContextService(
authorization=RetrievalAuthorizationService(),
inputs=_FixtureInputs(workspace, space_id, "SDK context space"),
executor=_Executor(),
)
app = create_app()
app.dependency_overrides[get_context_principal] = lambda: principal
app.dependency_overrides[get_context_application] = lambda: service
return ContextFixture(app=app, principal=principal, space_id=space_id)
_CITATION_ID = re.compile(r"citation_ids=([0-9a-fA-F-]{36})")
@dataclass
class AnswerFixture:
app: FastAPI
principal: Principal
space_id: UUID
def build_answer_app(*, generator_responses: list[str | None] | None = None) -> AnswerFixture:
workspace = Workspace(uuid4(), "SDK answers workspace", "sdk-answers-workspace")
space_id = uuid4()
principal = Principal(uuid4(), workspace.id, PrincipalType.HUMAN, frozenset({Role.EDITOR}))
citation = Citation.create(
workspace_id=workspace.id,
space_id=space_id,
document_id=uuid4(),
document_version_id=uuid4(),
chunk_id=uuid4(),
parent_chunk_id=None,
location={"page_start": 1},
excerpt=EVIDENCE_EXCERPT,
source_uri="https://example.test/answers.md",
acquired_at=datetime(2026, 8, 1, 12, tzinfo=UTC),
retrieval_run_id=uuid4(),
index_alias="rag_dense_hybrid_active",
profile_version="hybrid-v1",
)
candidate = ContextCandidate(citation, EVIDENCE_EXCERPT, 1, 1.0)
class _ContextExecutor:
async def execute(self, request: ContextExecutionRequest) -> ContextExecutionResult:
refreshed = Citation.create(
workspace_id=citation.workspace_id,
space_id=citation.space_id,
document_id=citation.document_id,
document_version_id=citation.document_version_id,
chunk_id=citation.chunk_id,
parent_chunk_id=citation.parent_chunk_id,
location=citation.location,
excerpt=citation.excerpt,
source_uri=citation.source_uri,
acquired_at=citation.acquired_at,
retrieval_run_id=request.retrieval_run_id,
index_alias=citation.index_alias,
profile_version=citation.profile_version,
)
return ContextExecutionResult(candidates=(replace(candidate, citation=refreshed),))
responses = list(generator_responses if generator_responses is not None else [None])
class _Generator:
async def generate(self, *, prompt: str, generation_profile: str, spaces: Any) -> str:
response = responses.pop(0) if responses else None
if response is not None:
return response
match = _CITATION_ID.search(prompt)
assert match is not None
return json.dumps(
{
"status": "answerable",
"answer": "The refund window is thirty days from purchase.",
"citations": [match.group(1)],
}
)
service = AnswerService(
authorization=RetrievalAuthorizationService(),
inputs=_FixtureInputs(workspace, space_id, "SDK answers space"),
context_executor=_ContextExecutor(),
generator=_Generator(),
)
app = create_app()
app.dependency_overrides[get_answer_principal] = lambda: principal
app.dependency_overrides[get_answer_application] = lambda: service
return AnswerFixture(app=app, principal=principal, space_id=space_id)
def _serve_forever(server: uvicorn.Server, sock: socket.socket) -> None:
# Deliberately not `server.run()`: that wraps `server.serve()` in
# `asyncio.run(..., loop_factory=...)`, and uvicorn's own loop factory
# (`uvicorn.loops.asyncio.asyncio_loop_factory`) hardcodes
# `asyncio.ProactorEventLoop` on Windows -- which the real app's
# lifespan (an async psycopg connection) refuses to run under, no
# matter what global event-loop policy is set beforehand. Driving the
# loop directly sidesteps uvicorn's own hardcoded choice.
loop = asyncio.SelectorEventLoop() if sys.platform == "win32" else asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
loop.run_until_complete(server.serve(sockets=[sock]))
finally:
loop.close()
@contextlib.contextmanager
def run_app(app: FastAPI) -> Iterator[str]:
"""Serve `app` over a real local TCP socket for the duration of the block.
`RAGCoreClient` (sync) needs this rather than `httpx.ASGITransport`:
ASGITransport only implements httpx's *async* transport interface, so a
sync `httpx.Client` can't use it at all (`AsyncRAGCoreClient` tests use
ASGITransport directly instead, which is the supported combination).
"""
# Hand uvicorn the already-bound socket itself, rather than closing it
# and passing the port number for uvicorn to rebind: closing and
# rebinding is a real race (another process, or even this same
# process's next bind, can win the port first), which is what
# produced Windows `STARTUP_FAILURE`s here in practice.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 0))
sock.listen(128)
port = sock.getsockname()[1]
config = uvicorn.Config(app, log_level="warning")
server = uvicorn.Server(config)
thread = threading.Thread(target=_serve_forever, args=(server, sock), daemon=True)
thread.start()
deadline = time.monotonic() + 10
while not server.started and time.monotonic() < deadline:
time.sleep(0.01)
try:
yield f"http://127.0.0.1:{port}"
finally:
server.should_exit = True
thread.join(timeout=10)
with contextlib.suppress(OSError):
sock.close()
__all__ = [
"AnswerFixture",
"ContextFixture",
"SearchFixture",
"build_answer_app",
"build_context_app",
"build_search_app",
"run_app",
]