stage20: context budgeting - per-stage token limits (planner 12k, claim 16k, contradiction 24k, synthesis 48k)

Implemented:
- context_budget.py: ContextBudgetConfig (Pydantic, frozen) mit 4 Stage-Limits,
  validation (ge/le), get_limit(), total_max_tokens, stage_keys
- context_budget.py: ContextBudgetTracker mit track_tokens(), get_usage(),
  is_exhausted(), reset_stage(), reset_all(), total_usage, elapsed_seconds
- context_budget.py: ContextBudgetExhaustedError mit stage_name, used_tokens, limit_tokens
- orchestrator.py: _track_context_tokens() Methode, context_budget_config/tracker init
- orchestrator.py: context_budget_tracker property export
- __init__.py: exports ContextBudgetConfig, ContextBudgetExhaustedError, ContextBudgetTracker
- priority_queue.py: __aenter__/__aexit__ auf async geandert (Testfix)
- test_context_budget.py: 28 Tests (DefaultConfig, TrackAccumulation, ExhaustedError,
  GetUsage, Reset, IsExhausted, InvalidStageNames, InvalidTokens)
- test_context_budget_integration.py: 6 Tests (Orchestrator-Integration)
- HANDOFF.md: Stage 20 abgeschlossen dokumentiert

Tests: 34 passed (28 unit + 6 integration) + 14 performance = 48 total
This commit is contained in:
NSCT Agent
2026-08-29 08:57:18 +00:00
parent 58488de7b3
commit 6e2e7386ad
7 changed files with 827 additions and 12 deletions

View File

@@ -30,6 +30,11 @@ from nsct.metrics import (
)
from nsct.models.claim import Claim
from nsct.orchestration.budget import BudgetExhaustedError, BudgetTracker, HardBudgetConfig
from nsct.orchestration.context_budget import (
ContextBudgetConfig,
ContextBudgetExhaustedError,
ContextBudgetTracker,
)
from nsct.orchestration.models import ResearchRun
from nsct.orchestration.state import ResearchRunState, StateMachine
from nsct.providers.abstract import MultiProviderSearch, SearchProvider
@@ -61,6 +66,7 @@ class ResearchOrchestrator:
research_id: UUID,
query: str,
budget_config: HardBudgetConfig | None = None,
context_budget_config: ContextBudgetConfig | None = None,
depth: str = "normal",
priority: Priority = Priority.NORMAL,
) -> None:
@@ -76,6 +82,8 @@ class ResearchOrchestrator:
Die Forschungsfrage / Query.
budget_config : HardBudgetConfig | None
Optionales Budget. Wird aus config abgeleitet, wenn None.
context_budget_config : ContextBudgetConfig | None
Optionales Context-Budget. Wird aus config abgeleitet, wenn None.
depth : str
Suchtiefe ("quick", "normal", "deep").
priority : Priority
@@ -94,6 +102,13 @@ class ResearchOrchestrator:
self._budget_config = HardBudgetConfig()
self._budget_tracker = BudgetTracker(self._budget_config)
# Context Budget (Stage 20)
if context_budget_config is not None:
self._context_budget_config = context_budget_config
else:
self._context_budget_config = ContextBudgetConfig()
self._context_budget_tracker = ContextBudgetTracker(self._context_budget_config)
# State Machine & Run
self._state_machine = StateMachine(ResearchRunState.CREATED)
self._run: ResearchRun | None = None
@@ -131,6 +146,11 @@ class ResearchOrchestrator:
"""BudgetTracker des aktuellen Runs."""
return self._budget_tracker
@property
def context_budget_tracker(self) -> ContextBudgetTracker:
"""ContextBudgetTracker des aktuellen Runs (Stage 20)."""
return self._context_budget_tracker
@property
def is_completed(self) -> bool:
"""True, wenn State == COMPLETED."""
@@ -209,6 +229,24 @@ class ResearchOrchestrator:
"""
self._budget_tracker.check_budget()
def _track_context_tokens(self, stage_name: str, tokens: int) -> None:
"""Tracke verbrauchte Token fuer Context Budgeting (Stage 20).
Parameters
----------
stage_name : str
Human-readable stage name (e.g. 'planner', 'claim_extraction',
'contradiction', 'synthesis').
tokens : int
Number of tokens consumed.
Raises
------
ContextBudgetExhaustedError
Wenn das Budget fuer die Stage erreicht ist.
"""
self._context_budget_tracker.track_tokens(stage_name, tokens)
# ---------------------------------------------------------------
# Private: Sub-Component Init
# ---------------------------------------------------------------