Files
Stirling-PDF/engine/src/stirling/services/progress.py
T
James BruntonandGitHub 672e81d286 Add ability for Stirling engine to reason across large documents (#6314)
# Description of Changes
Adds storage in the database for full document content alongside the RAG
content (and changes the service to `DocumentService` instead of
`RagService`). Then adds a generic capability that should be usable by
any agent (currently just used by the Question Agent) which allows the
agent to pull out the full contents of the doc, chunks it into various
sections that will fit in the context window, and then processes them in
parallel to create an intermediate result, and then processes the
intermediate result into a final answer. It will re-chunk as many times
as necessary to get the content small enough for the actual answer to be
analysed (I've tested on PDFs ~3500 pages long, which is well above the
context limit and requires maybe 3 rounds of compression to get an
answer).

The new full doc analysis stuff is heavier than the RAG lookup so both
remain. The agents should use RAG for targeted info and the chunked
reasoner for info that requires reading the full doc.
2026-05-14 13:19:38 +00:00

48 lines
1.5 KiB
Python

"""Per-request progress emission, plumbed via a ContextVar so deep call stacks
can publish typed events to the streaming orchestrator endpoint without every
intermediate layer knowing about it.
Outside a streaming request no emitter is bound and ``emit_progress`` is a
no-op, so callers in agents/services can emit unconditionally.
"""
from __future__ import annotations
import asyncio
import logging
from collections.abc import Awaitable, Callable
from contextvars import ContextVar, Token
from stirling.contracts import ProgressEvent
logger = logging.getLogger(__name__)
type ProgressEmitter = Callable[[ProgressEvent], Awaitable[None]]
_emitter: ContextVar[ProgressEmitter | None] = ContextVar("stirling_progress_emitter", default=None)
def set_progress_emitter(emitter: ProgressEmitter | None) -> Token[ProgressEmitter | None]:
return _emitter.set(emitter)
def reset_progress_emitter(token: Token[ProgressEmitter | None]) -> None:
_emitter.reset(token)
async def emit_progress(event: ProgressEvent) -> None:
"""Publish ``event`` to the current request's emitter, if any.
Failures inside the emitter are logged and swallowed so progress emission
can never break the work it's reporting on.
"""
emitter = _emitter.get()
if emitter is None:
return
try:
await emitter(event)
except asyncio.CancelledError:
raise
except Exception:
logger.exception("progress emitter raised; dropping event %r", event.phase)