mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 21:30:14 +03:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f651bc1014 |
@@ -75,7 +75,7 @@ tasks:
|
||||
desc: "Run type checking"
|
||||
deps: [install]
|
||||
cmds:
|
||||
- uv run pyright . --warnings
|
||||
- uv run pyrefly check
|
||||
|
||||
test:
|
||||
desc: "Run tests"
|
||||
|
||||
+24
-14
@@ -23,7 +23,7 @@ dev = [
|
||||
"anyio>=4.0.0",
|
||||
"datamodel-code-generator[ruff]>=0.26.0",
|
||||
"pytest>=8.0.0",
|
||||
"pyright>=1.1.408",
|
||||
"pyrefly==1.0.0",
|
||||
"referencing>=0.35.0",
|
||||
"ruff>=0.14.10",
|
||||
]
|
||||
@@ -51,21 +51,11 @@ select = [
|
||||
"W",
|
||||
"RUF100",
|
||||
"UP",
|
||||
"PYI", # flake8-pyi: flags deprecated typing constructs
|
||||
"FA", # flake8-future-annotations: flags missing future annotations imports
|
||||
"BLE", # flake8-blind-except: flags bare `except Exception`
|
||||
"PYI", # flake8-pyi: flags deprecated typing constructs
|
||||
"FA", # flake8-future-annotations: flags missing future annotations imports
|
||||
"BLE", # flake8-blind-except: flags bare `except Exception`
|
||||
]
|
||||
|
||||
[tool.pyright]
|
||||
pythonVersion = "3.13"
|
||||
|
||||
reportImportCycles = "warning"
|
||||
reportUnnecessaryCast = "warning"
|
||||
reportUnnecessaryTypeIgnoreComment = "warning"
|
||||
reportUnusedImport = "warning"
|
||||
reportUnknownParameterType = "warning"
|
||||
reportDeprecated = "warning"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
# ``tests`` is on the path so test modules can import shared helpers (e.g.
|
||||
@@ -74,3 +64,23 @@ pythonpath = ["src", "tests"]
|
||||
# Use importlib import mode so test directories don't need __init__.py files
|
||||
# and duplicate basenames (e.g. multiple test_routes.py) collect cleanly.
|
||||
addopts = "--import-mode=importlib"
|
||||
|
||||
[tool.pyrefly]
|
||||
python-version = "3.13.0"
|
||||
search-path = [
|
||||
"src",
|
||||
"tests",
|
||||
]
|
||||
# Disallow `type: ignore` comments to encourage accurate error codes to be used in ignore comments
|
||||
# Pyrefly won't flag `type: ignore [wrong-code]`, but it will flag `pyrefly: ignore [wrong-code]`
|
||||
enabled-ignores = ["pyrefly"]
|
||||
min-severity = "warn" # TODO: Change settings below to `warn` - https://github.com/facebook/pyrefly/issues/3370
|
||||
|
||||
[tool.pyrefly.errors]
|
||||
deprecated = "error"
|
||||
implicit-any = "error"
|
||||
redundant-cast = "error"
|
||||
unused-ignore = "error"
|
||||
# TODO: Add equivalents to:
|
||||
# - reportImportCycles
|
||||
# - reportUnusedImport
|
||||
|
||||
@@ -478,6 +478,7 @@ class MathAuditorAgent:
|
||||
|
||||
logger.info("[math-auditor-agent] extracting figures from page %d (%d chars)", folio.page, len(text))
|
||||
prompt = f"Page {folio.page + 1} text:\n{text}"
|
||||
figures: list[ExtractedFigure] = []
|
||||
try:
|
||||
result = await self._figure_extractor.run(prompt)
|
||||
figures = result.output.figures
|
||||
@@ -487,7 +488,6 @@ class MathAuditorAgent:
|
||||
folio.page,
|
||||
exc_info=True,
|
||||
)
|
||||
figures = []
|
||||
|
||||
logger.debug(
|
||||
"TOOL (extract_figures)\nArgs: %s\nResult: %s",
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Iterable
|
||||
from typing import Literal, overload
|
||||
from typing import Literal, Protocol, overload
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_ai import Agent
|
||||
@@ -72,6 +72,16 @@ class PdfEditSelectionAgent:
|
||||
return result.output
|
||||
|
||||
|
||||
class ParameterSelector(Protocol):
|
||||
async def select(
|
||||
self,
|
||||
request: PdfEditRequest,
|
||||
operation_plan: list[ToolEndpoint],
|
||||
operation_index: int,
|
||||
generated_steps: list[ToolOperationStep],
|
||||
) -> ParamToolModel: ...
|
||||
|
||||
|
||||
class PdfEditParameterSelector:
|
||||
def __init__(self, runtime: AppRuntime) -> None:
|
||||
self.runtime = runtime
|
||||
@@ -141,7 +151,7 @@ class PdfEditParameterSelector:
|
||||
class PdfEditAgent:
|
||||
def __init__(self, runtime: AppRuntime) -> None:
|
||||
self.runtime = runtime
|
||||
self.parameter_selector = PdfEditParameterSelector(runtime)
|
||||
self.parameter_selector: ParameterSelector = PdfEditParameterSelector(runtime)
|
||||
|
||||
async def orchestrate(self, request: OrchestratorRequest) -> PdfEditResponse:
|
||||
"""Entry point for the orchestrator delegate — adapts the orchestrator's
|
||||
|
||||
@@ -131,7 +131,7 @@ def _enable_http_debug(formatter: logging.Formatter) -> None:
|
||||
lg.setLevel(level)
|
||||
# Idempotent: avoid stacking handlers on settings reload.
|
||||
if not any(getattr(h, "_stirling_http_debug", False) for h in lg.handlers):
|
||||
handler._stirling_http_debug = True # type: ignore[attr-defined]
|
||||
handler._stirling_http_debug = True # pyrefly: ignore [missing-attribute]
|
||||
lg.addHandler(handler)
|
||||
lg.propagate = False
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ def _transform_output_choices(choices: list[Any]) -> list[Any]:
|
||||
for choice in choices:
|
||||
if not isinstance(choice, dict) or "parts" not in choice:
|
||||
continue
|
||||
tool_calls = []
|
||||
tool_calls: list[dict[str, Any]] = []
|
||||
for part in choice.get("parts", []):
|
||||
if isinstance(part, dict) and part.get("type") == "tool_call":
|
||||
tool_calls.append(
|
||||
|
||||
@@ -50,7 +50,7 @@ async def test_delegate_pdf_review_wires_prompt_to_tool_step(runtime: AppRuntime
|
||||
"stirling.agents.pdf_review.MathIntentClassifier.classify",
|
||||
new=AsyncMock(return_value=False),
|
||||
):
|
||||
response = await orchestrator.delegate_pdf_review(ctx) # type: ignore[arg-type]
|
||||
response = await orchestrator.delegate_pdf_review(ctx) # pyrefly: ignore [bad-argument-type]
|
||||
|
||||
assert isinstance(response, EditPlanResponse)
|
||||
assert len(response.steps) == 1
|
||||
|
||||
@@ -54,7 +54,7 @@ class StubEmbedder:
|
||||
@pytest.fixture
|
||||
def runtime_with_stub_docs(runtime: AppRuntime) -> AppRuntime:
|
||||
stub = DocumentService(
|
||||
embedder=StubEmbedder(), # type: ignore[arg-type]
|
||||
embedder=StubEmbedder(), # pyrefly: ignore [bad-argument-type]
|
||||
store=SqliteVecStore.ephemeral(),
|
||||
default_top_k=runtime.settings.rag_default_top_k,
|
||||
)
|
||||
@@ -198,13 +198,25 @@ async def test_read_full_document_budget_hides_tool_when_exhausted(
|
||||
sentinel: object = object()
|
||||
|
||||
# Budget intact -> prepare returns the tool.
|
||||
assert await capability._prepare_read_full_document(None, sentinel) is sentinel # type: ignore[arg-type]
|
||||
assert (
|
||||
await capability._prepare_read_full_document(
|
||||
None, # pyrefly: ignore [bad-argument-type]
|
||||
sentinel, # pyrefly: ignore [bad-argument-type]
|
||||
)
|
||||
is sentinel
|
||||
)
|
||||
|
||||
# Spend the budget.
|
||||
await capability._read_full_document("anything")
|
||||
|
||||
# Budget spent -> prepare returns None.
|
||||
assert await capability._prepare_read_full_document(None, sentinel) is None # type: ignore[arg-type]
|
||||
assert (
|
||||
await capability._prepare_read_full_document(
|
||||
None, # pyrefly: ignore [bad-argument-type]
|
||||
sentinel, # pyrefly: ignore [bad-argument-type]
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
|
||||
@@ -35,11 +35,10 @@ def test_folio_manifest_round_trip() -> None:
|
||||
assert reloaded == manifest
|
||||
|
||||
|
||||
def test_folio_manifest_round_bounds() -> None:
|
||||
@pytest.mark.parametrize("round_value", [0, 4])
|
||||
def test_folio_manifest_round_bounds(round_value: int) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=0)
|
||||
with pytest.raises(ValidationError):
|
||||
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=4)
|
||||
FolioManifest(session_id="x", page_count=1, folio_types=[FolioType.TEXT], round=round_value)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -12,6 +12,7 @@ from collections.abc import Iterator
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from conftest import build_app_settings
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from stirling.api import app
|
||||
@@ -34,8 +35,6 @@ from stirling.contracts.ledger import (
|
||||
|
||||
class StubSettingsProvider:
|
||||
def __call__(self) -> AppSettings:
|
||||
from conftest import build_app_settings
|
||||
|
||||
return build_app_settings()
|
||||
|
||||
|
||||
|
||||
@@ -152,7 +152,11 @@ class StubEmbeddingService:
|
||||
def documents() -> DocumentService:
|
||||
"""Each DocumentService test gets its own fresh ephemeral store to avoid dimension conflicts."""
|
||||
store = SqliteVecStore.ephemeral()
|
||||
return DocumentService(embedder=StubEmbeddingService(), store=store, default_top_k=3) # type: ignore[arg-type]
|
||||
return DocumentService(
|
||||
embedder=StubEmbeddingService(), # pyrefly: ignore [bad-argument-type]
|
||||
store=store,
|
||||
default_top_k=3,
|
||||
)
|
||||
|
||||
|
||||
def _pages(text: str) -> list[PageText]:
|
||||
@@ -265,7 +269,7 @@ async def _invoke_search_knowledge(capability: RagCapability, query: str, max_re
|
||||
toolset = capability.toolset
|
||||
assert isinstance(toolset, FunctionToolset)
|
||||
tool = toolset.tools["search_knowledge"]
|
||||
return await tool.function(query=query, max_results=max_results) # type: ignore[call-arg]
|
||||
return await tool.function(query=query, max_results=max_results)
|
||||
|
||||
|
||||
class TestRagCapability:
|
||||
@@ -345,12 +349,24 @@ class TestRagCapability:
|
||||
cap = RagCapability(documents, max_searches=2)
|
||||
tool_def = _dummy_tool_def()
|
||||
|
||||
assert await cap._prepare_search_knowledge(None, tool_def) is tool_def # type: ignore[arg-type]
|
||||
assert (
|
||||
await cap._prepare_search_knowledge(
|
||||
None, # pyrefly: ignore [bad-argument-type]
|
||||
tool_def, # pyrefly: ignore [bad-argument-type]
|
||||
)
|
||||
is tool_def
|
||||
)
|
||||
|
||||
await _invoke_search_knowledge(cap, "content")
|
||||
await _invoke_search_knowledge(cap, "content")
|
||||
|
||||
assert await cap._prepare_search_knowledge(None, tool_def) is None # type: ignore[arg-type]
|
||||
assert (
|
||||
await cap._prepare_search_knowledge(
|
||||
None, # pyrefly: ignore [bad-argument-type]
|
||||
tool_def, # pyrefly: ignore [bad-argument-type]
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def _dummy_tool_def() -> object:
|
||||
|
||||
@@ -45,7 +45,7 @@ class StubEmbedder:
|
||||
|
||||
def _build_service() -> DocumentService:
|
||||
return DocumentService(
|
||||
embedder=StubEmbedder(), # type: ignore[arg-type]
|
||||
embedder=StubEmbedder(), # pyrefly: ignore [bad-argument-type]
|
||||
store=SqliteVecStore.ephemeral(),
|
||||
default_top_k=3,
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ class StubPdfQuestionAgent(PdfQuestionAgent):
|
||||
def runtime_with_stub_rag(runtime: AppRuntime) -> AppRuntime:
|
||||
"""A runtime whose document service uses a stub embedder + ephemeral store."""
|
||||
stub = DocumentService(
|
||||
embedder=StubEmbedder(), # type: ignore[arg-type]
|
||||
embedder=StubEmbedder(), # pyrefly: ignore [bad-argument-type]
|
||||
store=SqliteVecStore.ephemeral(),
|
||||
default_top_k=runtime.settings.rag_default_top_k,
|
||||
)
|
||||
|
||||
Generated
+14
-19
@@ -621,7 +621,7 @@ dependencies = [
|
||||
dev = [
|
||||
{ name = "anyio" },
|
||||
{ name = "datamodel-code-generator", extra = ["ruff"] },
|
||||
{ name = "pyright" },
|
||||
{ name = "pyrefly" },
|
||||
{ name = "pytest" },
|
||||
{ name = "referencing" },
|
||||
{ name = "ruff" },
|
||||
@@ -647,7 +647,7 @@ requires-dist = [
|
||||
dev = [
|
||||
{ name = "anyio", specifier = ">=4.0.0" },
|
||||
{ name = "datamodel-code-generator", extras = ["ruff"], specifier = ">=0.26.0" },
|
||||
{ name = "pyright", specifier = ">=1.1.408" },
|
||||
{ name = "pyrefly", specifier = "==1.0.0" },
|
||||
{ name = "pytest", specifier = ">=8.0.0" },
|
||||
{ name = "referencing", specifier = ">=0.35.0" },
|
||||
{ name = "ruff", specifier = ">=0.14.10" },
|
||||
@@ -1671,15 +1671,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/04/eaac430d0e6bf21265ae989427d37e94be5e41dc216879f1fbb6c5339942/nexus_rpc-1.2.0-py3-none-any.whl", hash = "sha256:977876f3af811ad1a09b2961d3d1ac9233bda43ff0febbb0c9906483b9d9f8a3", size = 28166, upload-time = "2025-11-17T19:17:05.64Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.4"
|
||||
@@ -2500,16 +2491,20 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.408"
|
||||
name = "pyrefly"
|
||||
version = "1.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nodeenv" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/3a/9045b0097ac58979c7c30a4fa0e673db942d4adbc7b6d439bd54ae58c441/pyrefly-1.0.0.tar.gz", hash = "sha256:5c2b810ffcebd84be71de5df1223651edee951653a66935c6f091e957c452455", size = 5677995, upload-time = "2026-05-12T20:12:46.812Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/c6/90788819bac9c61dd7bacba53b79f3c12d47ccbe5e51b3d6d89f2387e1d2/pyrefly-1.0.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e355a0908555348ed4b9585ef25c76ff566673e345c866c325f1633f44d890b6", size = 13122950, upload-time = "2026-05-12T20:12:20.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/91/a3cf2a1e87d336eaa804a1e6fc93266faf6dc2a97eecdbc7eae289628022/pyrefly-1.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7038efc3a40f8294edee339895633cf22db268c0d434cdbcbefc34f78a9ecc3", size = 12599494, upload-time = "2026-05-12T20:12:23.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/ab/74d1e11e737e99b1c003ecc5d7d2e846c4ea1f328966bfdbbd0ac63fad0a/pyrefly-1.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da331ca515ed1c08791da2b5f664cf9c1294c48fd802133262e7d5d51e0f4416", size = 12995507, upload-time = "2026-05-12T20:12:25.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/ac/2df0899f8464c97e5d995f994c97c5cb5b0f58610432aa90d26d924e1db5/pyrefly-1.0.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c74219d8f3e63cdaa5501a0b21d1c9d37011820f9606728d0ed06f09ae86a878", size = 13947693, upload-time = "2026-05-12T20:12:29.188Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/3e/b247c24321e36f04b7d51f9ccf3df93e5009e4b29939524b36ec2e17dc2a/pyrefly-1.0.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0d05543b1bb6ee6d64149eb5d6b2fb15aa72d3962d6a97abca0afaca8b0c131", size = 13925803, upload-time = "2026-05-12T20:12:31.904Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/16/cfa2d61a4aa1e1f7bca48bb37acd01c6a09db4864b16a54f9587092765ff/pyrefly-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1382d5b1fcdb49a4de9f34d112d2bddf290a78ff93ee8149492ad5f1077ddffc", size = 13470398, upload-time = "2026-05-12T20:12:35.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/2b/6372c7dddb326223e24a46b17efd0d4bd7b4fe22c821e523157577eed2d2/pyrefly-1.0.0-py3-none-win32.whl", hash = "sha256:aa8b5d0e47080e3202a2547b39f7a5a61d2c781c712b3b67884f745ca2c759d2", size = 12222643, upload-time = "2026-05-12T20:12:38.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ad/1d23be700b6b2ddaeb362360c7145917a8edbbf7240ae428d40541772fce/pyrefly-1.0.0-py3-none-win_amd64.whl", hash = "sha256:c8abcb0f2082e83c890375128f9cff4aa4d3f210b85eea7b3046c1ae764e77f5", size = 13146369, upload-time = "2026-05-12T20:12:41.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/38/16589134f3012fd097a10dcc85771555f1a5fb76e04b682597180743af30/pyrefly-1.0.0-py3-none-win_arm64.whl", hash = "sha256:d150fa9e40e8392832be81c3bcfc0497c146674ce4d0f8e04e1ec29e775ffb8c", size = 12538326, upload-time = "2026-05-12T20:12:43.996Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user