@@ -164,6 +164,9 @@ class AgentConfig:
164164 system_prompt : str = "You are a helpful AI assistant."
165165 max_iterations : int = 10
166166 temperature : float = 0.7
167+ # Default output-token budget for every run(). None lets the model pick a
168+ # size-aware default; run(max_tokens=...) overrides it for a single call.
169+ max_tokens : int | None = None
167170 enable_sub_agents : bool = True
168171 enable_memory : bool = True
169172 enable_streaming : bool = False
@@ -915,6 +918,13 @@ def run(self,
915918 output_model: Pydantic BaseModel class — when provided, output is
916919 validated and the parsed instance is stored in
917920 ``response.metadata["parsed"]``.
921+
922+ Reasoning models and deeply nested schemas need a generous output
923+ budget: the model spends tokens on internal reasoning and on the
924+ JSON structure before it fills any values. Set ``max_tokens``
925+ accordingly (``run(..., max_tokens=8192)`` for a reasoning model).
926+ When an extraction validates but every field is empty,
927+ ``response.metadata["structured_output_empty"]`` is set to True.
918928 inputs: Optional list of multimodal content parts created by
919929 ``image_from``, ``audio_from``, or ``video_from``. When present,
920930 the agent sends a structured Message directly to the model.
@@ -963,6 +973,10 @@ def run(self,
963973 # every run reports its own cost_usd + token counts (see _finalize_cost).
964974 self ._run_cost_accum = {}
965975 debug = kwargs .pop ("debug" , False )
976+ # A max_tokens set on the config is the default output budget for every
977+ # run(); an explicit run(max_tokens=...) still overrides it per call.
978+ if "max_tokens" not in kwargs and self .config .max_tokens is not None :
979+ kwargs ["max_tokens" ] = self .config .max_tokens
966980 run_id = generate_run_id ()
967981 # Capture checkpoint args here so the outer run() can use
968982 # them for the final-checkpoint write even after _run_single_agent
@@ -975,6 +989,7 @@ def run(self,
975989 prom_metrics .active_agents .inc (labels = labels )
976990
977991 # Pre-run input guardrail check
992+ input_redaction : dict [str , Any ] | None = None
978993 if self ._guardrail_chain is not None :
979994 from ..guardrails .base import GuardrailPosition
980995 gr = self ._guardrail_chain .check (task , position = GuardrailPosition .INPUT )
@@ -988,6 +1003,13 @@ def run(self,
9881003 )
9891004 if gr .modified_content is not None :
9901005 task = gr .modified_content
1006+ # Record what the input redaction removed so a run is auditable
1007+ # from its response (e.g. a note de-identified before a cloud call).
1008+ pii_types = gr .metadata .get ("pii_types" )
1009+ if pii_types :
1010+ input_redaction = {"types" : pii_types }
1011+ if gr .metadata .get ("pii_counts" ):
1012+ input_redaction ["counts" ] = gr .metadata ["pii_counts" ]
9911013
9921014 # Resolve structured output schema. Accept either a JSON-Schema dict or
9931015 # a Pydantic model class for `output_schema` (and the config default),
@@ -1080,6 +1102,8 @@ def run(self,
10801102 response .execution_trace = self .execution_tracker .get_trace ()
10811103 response .execution_tree = self .execution_tracker .generate_execution_tree ()
10821104 response .metadata ["run_id" ] = run_id
1105+ if input_redaction is not None :
1106+ response .metadata ["input_redaction" ] = input_redaction
10831107 # Surface this run's cost + token usage on the result so callers
10841108 # can budget per call without a side channel.
10851109 self ._finalize_cost_metadata (response )
0 commit comments