from __future__ import annotations import json from app.modules.rag.explain.models import ExplainPack class PromptBudgeter: def __init__( self, *, max_paths: int = 3, max_symbols: int = 25, max_excerpts: int = 40, max_chars: int = 30000, ) -> None: self._max_paths = max_paths self._max_symbols = max_symbols self._max_excerpts = max_excerpts self._max_chars = max_chars def build_prompt_input(self, question: str, pack: ExplainPack) -> str: symbol_ids: list[str] = [] for path in pack.trace_paths[: self._max_paths]: for symbol_id in path.symbol_ids: if symbol_id and symbol_id not in symbol_ids and len(symbol_ids) < self._max_symbols: symbol_ids.append(symbol_id) excerpts = [] total_chars = 0 for excerpt in pack.code_excerpts: if symbol_ids and excerpt.symbol_id and excerpt.symbol_id not in symbol_ids: continue body = excerpt.content.strip() remaining = self._max_chars - total_chars if remaining <= 0 or len(excerpts) >= self._max_excerpts: break if len(body) > remaining: body = body[:remaining].rstrip() + "...[truncated]" excerpts.append( { "evidence_id": excerpt.evidence_id, "title": excerpt.title, "path": excerpt.path, "start_line": excerpt.start_line, "end_line": excerpt.end_line, "focus": excerpt.focus, "content": body, } ) total_chars += len(body) payload = { "question": question, "intent": pack.intent.model_dump(mode="json"), "selected_entrypoints": [item.model_dump(mode="json") for item in pack.selected_entrypoints[:5]], "seed_symbols": [item.model_dump(mode="json") for item in pack.seed_symbols[: self._max_symbols]], "trace_paths": [path.model_dump(mode="json") for path in pack.trace_paths[: self._max_paths]], "evidence_index": {key: value.model_dump(mode="json") for key, value in pack.evidence_index.items()}, "code_excerpts": excerpts, "missing": pack.missing, "conflicts": pack.conflicts, } return json.dumps(payload, ensure_ascii=False, indent=2)