Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,19 @@ class ContextBudgetDto:
reserved_response_tokens: int = 0
max_items: int | None = None
strategy: str = "balanced"
max_rendered_chars: int | None = None

def to_dict(self) -> JsonObject:
return {
"max_context_tokens": self.max_context_tokens,
"reserved_response_tokens": self.reserved_response_tokens,
"max_items": self.max_items,
"strategy": self.strategy,
**(
{"max_rendered_chars": self.max_rendered_chars}
if self.max_rendered_chars is not None
else {}
),
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
ContextBundle,
ContextEvidenceRenderer,
ContextQueryExpansionPolicy,
PromptSectionPlan,
PromptSectionPlanner,
)
from infinity_context_core.features.context_building.ports import (
Expand All @@ -40,9 +41,24 @@ class PackContextHandler:
)

async def execute(self, query: PackContextQuery) -> PackContextResult:
plan = self.budget_policy.plan(query.candidates, query.budget)
plan = self.budget_policy.plan(
query.candidates,
query.budget,
render=lambda items: self.evidence_renderer.render_plan(
self.prompt_section_planner.plan(items)
),
)
prompt_section_plan = self.prompt_section_planner.plan(plan.selected_items)
rendered_evidence = self.evidence_renderer.render_plan(prompt_section_plan)
max_rendered_chars = query.budget.max_rendered_chars
if max_rendered_chars is not None and len(rendered_evidence) > max_rendered_chars:
# Custom rendering may change between admission and final rendering.
# Fail explicitly for selected evidence; never truncate or discard it.
if plan.selected_items:
raise ValueError("Final rendered evidence exceeds max_rendered_chars")
# Empty output has a canonical representation, bypassing custom overhead.
prompt_section_plan = PromptSectionPlan(sections=())
rendered_evidence = ""
bundle = ContextBundle(
query=query.query,
items=plan.selected_items,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from infinity_context_core.features.context_building.domain.context import (
Expand All @@ -12,13 +13,21 @@

@dataclass(frozen=True, slots=True)
class ContextBudget:
"""Prompt token budget with explicit reserves outside memory evidence."""
"""Independent token and optional rendered Unicode character limits.

Token estimates and reserves retain their existing meaning. A character cap
measures the complete evidence rendering with Python's ``len(str)``; zero
allows an empty bundle and ``None`` preserves uncapped feature callers.
"""

max_prompt_tokens: int
reserved_response_tokens: int = 0
reserved_system_tokens: int = 0
max_rendered_chars: int | None = None

def __post_init__(self) -> None:
if self.max_rendered_chars is not None and self.max_rendered_chars < 0:
raise ValueError("Rendered character budget cannot be negative")
if self.max_prompt_tokens < 1:
raise ValueError("Context budget must allow at least one prompt token")
if self.reserved_response_tokens < 0:
Expand Down Expand Up @@ -57,7 +66,13 @@ def plan(
self,
items: tuple[ContextItem, ...],
budget: ContextBudget,
*,
render: Callable[[tuple[ContextItem, ...]], str] | None = None,
) -> ContextPackingPlan:
if budget.max_rendered_chars is not None and render is None:
from .rendering import ContextEvidenceRenderer

render = ContextEvidenceRenderer().render
remaining = budget.available_evidence_tokens
selected: list[ContextItem] = []
dropped: list[ContextDroppedItem] = []
Expand All @@ -70,6 +85,21 @@ def plan(
for _, item in ranked_items:
token_cost = item.token_cost
if token_cost <= remaining:
# Measure the actual proposed rendering, including section headings,
# escaped quotes, citations and numbering. Keep evidence records whole.
if (
budget.max_rendered_chars is not None
and render is not None
and len(render((*selected, item))) > budget.max_rendered_chars
):
dropped.append(
ContextDroppedItem(
item_id=item.item_id,
reason="rendered_character_budget_exceeded",
estimated_tokens=token_cost,
)
)
continue
selected.append(item)
remaining -= token_cost
used += token_cost
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,11 @@ async def _build_context_bundle(
as_of=request.as_of,
budget=context_building_server.ContextBudgetHttpRequest(
max_context_tokens=request.token_budget,
max_rendered_chars=(
max_rendered_chars
if max_rendered_chars is not None
else container.settings.max_context_chars
),
max_items=canonical_item_limit,
),
tags=(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ContextBudgetHttpRequest(BaseModel):

max_context_tokens: int = Field(default=1800, ge=1, le=64000)
reserved_response_tokens: int = Field(default=0, ge=0, le=64000)
max_rendered_chars: int | None = Field(default=None, ge=0)
max_items: int | None = Field(default=None, ge=1, le=1000)
strategy: str = Field(default="balanced", min_length=1, max_length=80)

Expand All @@ -27,6 +28,7 @@ def to_contract(self) -> ContextBudgetDto:
reserved_response_tokens=self.reserved_response_tokens,
max_items=self.max_items,
strategy=self.strategy,
max_rendered_chars=self.max_rendered_chars,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def build_context_query_from_contract(
budget=context_building.ContextBudget(
max_prompt_tokens=budget.max_context_tokens,
reserved_response_tokens=budget.reserved_response_tokens,
max_rendered_chars=budget.max_rendered_chars,
),
candidate_limit=budget.max_items or DEFAULT_CANDIDATE_LIMIT,
)
Expand Down Expand Up @@ -97,6 +98,11 @@ def _budget_from_contract(
),
max_items=_optional_positive_int(budget.get("max_items"), "budget.max_items"),
strategy=str(budget.get("strategy") or "balanced"),
max_rendered_chars=(
_non_negative_int(budget["max_rendered_chars"], "budget.max_rendered_chars")
if budget.get("max_rendered_chars") is not None
else None
),
)


Expand Down
Loading
Loading