mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 02:54:06 +02:00
SaaS fixes (#6578)
Co-authored-by: Claude Opus 4.8 <[email protected]> Co-authored-by: James Brunton <[email protected]> Co-authored-by: Reece Browne <[email protected]> Co-authored-by: ConnorYoh <[email protected]> Co-authored-by: Reece <[email protected]> Co-authored-by: EthanHealy01 <[email protected]> Co-authored-by: Ludy <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
James Brunton
Reece Browne
ConnorYoh
Reece
EthanHealy01
Ludy
parent
96accea984
commit
ddf78d11ae
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from stirling.config import AppSettings, RagBackend, load_settings
|
||||
from stirling.config import AppSettings, DocumentsBackend, load_settings
|
||||
from stirling.services import build_runtime
|
||||
from stirling.services.runtime import AppRuntime
|
||||
|
||||
@@ -23,10 +23,13 @@ def build_app_settings() -> AppSettings:
|
||||
fast_model_name="test",
|
||||
smart_model_max_tokens=8192,
|
||||
fast_model_max_tokens=2048,
|
||||
rag_backend=RagBackend.SQLITE,
|
||||
model_max_concurrency=32,
|
||||
documents_backend=DocumentsBackend.SQLITE,
|
||||
rag_embedding_model="voyageai:voyage-4",
|
||||
rag_store_path=Path(":memory:"),
|
||||
rag_pgvector_dsn="",
|
||||
documents_sqlite_path=Path(":memory:"),
|
||||
documents_pgvector_dsn="",
|
||||
documents_pgvector_pool_min_size=1,
|
||||
documents_pgvector_pool_max_size=10,
|
||||
rag_chunk_size=512,
|
||||
rag_chunk_overlap=64,
|
||||
rag_default_top_k=5,
|
||||
@@ -41,6 +44,7 @@ def build_app_settings() -> AppSettings:
|
||||
contradiction_canonicaliser_batch_size=500,
|
||||
max_pages=200,
|
||||
max_characters=200_000,
|
||||
require_user_id=False,
|
||||
posthog_enabled=False,
|
||||
posthog_api_key="",
|
||||
posthog_host="https://eu.i.posthog.com",
|
||||
|
||||
@@ -16,7 +16,7 @@ from fastapi.testclient import TestClient
|
||||
|
||||
from stirling.api import app
|
||||
from stirling.api.dependencies import get_pdf_comment_agent
|
||||
from stirling.config import AppSettings, RagBackend, load_settings
|
||||
from stirling.config import AppSettings, DocumentsBackend, load_settings
|
||||
from stirling.contracts.pdf_comments import (
|
||||
PdfCommentInstruction,
|
||||
PdfCommentRequest,
|
||||
@@ -35,10 +35,13 @@ class StubSettingsProvider:
|
||||
fast_model_name="test",
|
||||
smart_model_max_tokens=8192,
|
||||
fast_model_max_tokens=2048,
|
||||
rag_backend=RagBackend.SQLITE,
|
||||
model_max_concurrency=32,
|
||||
documents_backend=DocumentsBackend.SQLITE,
|
||||
rag_embedding_model="test-embed",
|
||||
rag_store_path=Path(":memory:"),
|
||||
rag_pgvector_dsn="",
|
||||
documents_sqlite_path=Path(":memory:"),
|
||||
documents_pgvector_dsn="",
|
||||
documents_pgvector_pool_min_size=1,
|
||||
documents_pgvector_pool_max_size=10,
|
||||
rag_chunk_size=512,
|
||||
rag_chunk_overlap=64,
|
||||
rag_default_top_k=5,
|
||||
@@ -49,6 +52,7 @@ class StubSettingsProvider:
|
||||
chunked_reasoner_notes_char_budget=250_000,
|
||||
max_pages=100,
|
||||
max_characters=100_000,
|
||||
require_user_id=False,
|
||||
posthog_enabled=False,
|
||||
posthog_api_key="",
|
||||
posthog_host="https://eu.i.posthog.com",
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart
|
||||
from pydantic_ai.models import Model, ModelRequestParameters
|
||||
from pydantic_ai.settings import ModelSettings
|
||||
|
||||
from stirling.services.runtime import ConcurrencyLimitedModel
|
||||
|
||||
|
||||
class TrackingModel(Model):
|
||||
"""Records the high-water mark of concurrent in-flight requests."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.active = 0
|
||||
self.max_active = 0
|
||||
|
||||
@property
|
||||
def model_name(self) -> str:
|
||||
return "tracking"
|
||||
|
||||
@property
|
||||
def system(self) -> str:
|
||||
return "test"
|
||||
|
||||
async def request(
|
||||
self,
|
||||
messages: list[ModelMessage],
|
||||
model_settings: ModelSettings | None,
|
||||
model_request_parameters: ModelRequestParameters,
|
||||
) -> ModelResponse:
|
||||
self.active += 1
|
||||
self.max_active = max(self.max_active, self.active)
|
||||
# Yield twice so every gathered task gets a chance to be in flight
|
||||
# together before any of them completes.
|
||||
await asyncio.sleep(0)
|
||||
await asyncio.sleep(0)
|
||||
self.active -= 1
|
||||
return ModelResponse(parts=[TextPart(content="ok")])
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_shared_semaphore_caps_concurrency_across_models() -> None:
|
||||
inner = TrackingModel()
|
||||
semaphore = asyncio.Semaphore(2)
|
||||
fast = ConcurrencyLimitedModel(inner, semaphore)
|
||||
smart = ConcurrencyLimitedModel(inner, semaphore)
|
||||
params = ModelRequestParameters()
|
||||
|
||||
await asyncio.gather(
|
||||
*(fast.request([], None, params) for _ in range(5)),
|
||||
*(smart.request([], None, params) for _ in range(5)),
|
||||
)
|
||||
|
||||
assert inner.max_active == 2
|
||||
@@ -214,6 +214,26 @@ def test_pdf_edit_route() -> None:
|
||||
assert response.json()["outcome"] == "cannot_do"
|
||||
|
||||
|
||||
def test_routes_require_user_id_when_enforced() -> None:
|
||||
"""With STIRLING_REQUIRE_USER_ID on, an identity-less request is rejected
|
||||
at the boundary before any handler runs; supplying X-User-Id is accepted.
|
||||
The other route tests in this module run with the flag off and cover the
|
||||
identity-less self-hosted path."""
|
||||
app.dependency_overrides[load_settings] = lambda: build_app_settings().model_copy(update={"require_user_id": True})
|
||||
try:
|
||||
anonymous = client.post("/api/v1/pdf/edit", json={"userMessage": "rotate this"})
|
||||
identified = client.post(
|
||||
"/api/v1/pdf/edit",
|
||||
json={"userMessage": "rotate this"},
|
||||
headers={"X-User-Id": "alice"},
|
||||
)
|
||||
finally:
|
||||
app.dependency_overrides[load_settings] = build_app_settings
|
||||
|
||||
assert anonymous.status_code == 401
|
||||
assert identified.status_code == 200
|
||||
|
||||
|
||||
def test_pdf_questions_route() -> None:
|
||||
response = client.post(
|
||||
"/api/v1/pdf/questions",
|
||||
|
||||
@@ -77,17 +77,20 @@ def test_pdf_question_answer_defaults_evidence_list() -> None:
|
||||
def test_app_settings_accepts_model_configuration() -> None:
|
||||
from pathlib import Path
|
||||
|
||||
from stirling.config import RagBackend
|
||||
from stirling.config import DocumentsBackend
|
||||
|
||||
settings = AppSettings(
|
||||
smart_model_name="claude-sonnet-4-5-20250929",
|
||||
fast_model_name="claude-haiku-4-5-20251001",
|
||||
smart_model_max_tokens=8192,
|
||||
fast_model_max_tokens=2048,
|
||||
rag_backend=RagBackend.SQLITE,
|
||||
model_max_concurrency=32,
|
||||
documents_backend=DocumentsBackend.SQLITE,
|
||||
rag_embedding_model="voyageai:voyage-4",
|
||||
rag_store_path=Path(":memory:"),
|
||||
rag_pgvector_dsn="",
|
||||
documents_sqlite_path=Path(":memory:"),
|
||||
documents_pgvector_dsn="",
|
||||
documents_pgvector_pool_min_size=1,
|
||||
documents_pgvector_pool_max_size=10,
|
||||
rag_chunk_size=512,
|
||||
rag_chunk_overlap=64,
|
||||
rag_default_top_k=5,
|
||||
@@ -98,6 +101,7 @@ def test_app_settings_accepts_model_configuration() -> None:
|
||||
chunked_reasoner_notes_char_budget=250_000,
|
||||
max_pages=200,
|
||||
max_characters=200_000,
|
||||
require_user_id=False,
|
||||
posthog_enabled=False,
|
||||
posthog_api_key="",
|
||||
posthog_host="https://eu.i.posthog.com",
|
||||
|
||||
Reference in New Issue
Block a user