Source code for qfa.services.call_context
"""Request-scoped ContextVar carrying tenant + operation to tracking adapters.
The orchestrator enters ``call_scope(...)`` at each public-method entry; the
``TrackingLLMAdapter`` reads ``current_call_context.get()`` at LLM-call time.
``asyncio`` propagates ContextVars across ``create_task`` / ``gather`` via
snapshot-on-spawn, so fan-out from a public orchestrator method preserves the
context without explicit forwarding.
"""
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from contextvars import ContextVar
from qfa.domain.models import CallContext, Operation
current_call_context: ContextVar[CallContext | None] = ContextVar(
"current_call_context",
default=None,
)
[docs]
@asynccontextmanager
async def call_scope(
tenant_id: str,
operation: Operation,
) -> AsyncIterator[CallContext]:
"""Set ``current_call_context`` for the duration of the block.
Parameters
----------
tenant_id : str
Tenant making the call.
operation : Operation
Public orchestrator operation issuing the call.
Yields
------
CallContext
The context that was set.
"""
ctx = CallContext(tenant_id=tenant_id, operation=operation)
token = current_call_context.set(ctx)
try:
yield ctx
finally:
current_call_context.reset(token)