Skip to content

symfonic.agent.prompt_budget

prompt_budget

Shared composition of the budget service and prompt compiler.

Native platform composers and the compatibility cutover both use this binding. The facade compiler may import capability values and declared service ports; neither the capability nor the service imports the other. Keeping this owner outside cutover also avoids a forbidden facade-compiler import of platform.

With no declared window, bind only the service package's counter. With a window, bind BudgetService.as_token_estimator(); grant a PromptBudget only when the service derived a PROMPT line from an explicit share. No window or no share therefore introduces no ceiling. A share without a window is refused because there is no declared number to divide.

prompt_compile_options

prompt_compile_options(*, context_window: int | None = None, prompt_share: float | None = None, chars_per_token: int | None = None) -> dict[str, Any]

The compile options that bind this deployment's budgeting.

Returned as options for PromptingCapability rather than applied here: the capability owns the compile, and a composition root that pre-compiled for it would be a second compiler.

Source code in src/symfonic/agent/prompt_budget.py
def prompt_compile_options(
    *,
    context_window: int | None = None,
    prompt_share: float | None = None,
    chars_per_token: int | None = None,
) -> dict[str, Any]:
    """The compile options that bind this deployment's budgeting.

    Returned as options for ``PromptingCapability`` rather than applied here:
    the capability owns the compile, and a composition root that pre-compiled
    for it would be a second compiler.
    """
    if context_window is None:
        if prompt_share is not None:
            raise ValueError(
                "prompt_compile_options(prompt_share=...) needs a context_window: "
                "a share is a fraction of a window, and a fraction of an unknown "
                "number is not a ceiling. Declare the model's window, or drop the "
                "share and compile unbounded."
            )
        counter = (
            HeuristicTokenCounter(chars_per_token=chars_per_token)
            if chars_per_token is not None
            else HeuristicTokenCounter()
        )
        return {"estimator": counter}

    service = BudgetService.offline(
        context_window=context_window,
        prompt=prompt_share,
        chars_per_token=chars_per_token,
    )
    options: dict[str, Any] = {"estimator": service.as_token_estimator()}
    limit = service.limit_for(BudgetKind.PROMPT)
    if limit is not None:
        # ``None`` when no share was declared, and that is not a ceiling of
        # zero: an undeclared line is unbudgeted, so the compile stays
        # unbounded and the counter's numbers are a report rather than a gate.
        options["budget"] = PromptBudget(max_total_tokens=limit)
    return options