From 20a891b1d7b824e4d9e67be57be3dd5549dc71b6 Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Sat, 19 Sep 2026 00:30:51 -0300 Subject: [PATCH] bugfix: max_tokens hardcoded --- MULTI_ITEM_FRAMEWORK_FIX_20260918.md | 29 ++++++ .../llm_profiles.yaml | 8 +- .../agent_template_backend/llm_profiles.yaml | 8 +- .../agent_template_backend/llm_profiles.yaml | 9 +- .../agent_template_backend/llm_profiles.yaml | 8 +- .../agent_template_backend/llm_profiles.yaml | 9 +- .../llm_profiles.yaml | 8 +- .../agent_template_backend/llm_profiles.yaml | 8 +- .../llm_profiles.yaml | 8 +- .../agent_template_backend/llm_profiles.yaml | 8 +- .../agent_template_backend/llm_profiles.yaml | 8 +- ...X_LLM_MAX_TOKENS_CONFIGURATION_20260918.md | 58 +++++++++++ .../configs/llm_profiles/llm_profiles.yaml | 8 ++ .../agent_framework/channels/interruption.py | 2 +- .../agent_framework/guardrails/llm_rails.py | 2 +- .../agent_framework/llm/profile_resolver.py | 61 +++++++++++ .../lib/agent_framework/llm/providers.py | 8 ++ .../agent_framework/memory/summary_memory.py | 2 +- .../lib/agent_framework/rag/rag_service.py | 4 +- .../routing/enterprise_router.py | 8 +- .../agent_framework/runtime/agent_runtime.py | 2 +- libs/agent_framework/config/llm_profiles.yaml | 15 ++- .../agent_framework/llm_profiles.yaml.example | 17 +++- .../__pycache__/interruption.cpython-313.pyc | Bin 6443 -> 6407 bytes .../agent_framework/channels/interruption.py | 2 +- .../__pycache__/llm_rails.cpython-313.pyc | Bin 5910 -> 5874 bytes .../agent_framework/guardrails/llm_rails.py | 2 +- .../profile_resolver.cpython-313.pyc | Bin 9309 -> 12071 bytes .../llm/__pycache__/providers.cpython-313.pyc | Bin 46965 -> 47183 bytes .../agent_framework/llm/profile_resolver.py | 61 +++++++++++ .../src/agent_framework/llm/providers.py | 8 ++ .../summary_memory.cpython-313.pyc | Bin 12488 -> 12462 bytes .../agent_framework/memory/summary_memory.py | 2 +- .../__pycache__/rag_service.cpython-313.pyc | Bin 10884 -> 10848 bytes .../src/agent_framework/rag/rag_service.py | 4 +- .../enterprise_router.cpython-313.pyc | Bin 82165 -> 82129 bytes .../routing/enterprise_router.py | 8 +- .../__pycache__/agent_runtime.cpython-313.pyc | Bin 211750 -> 211730 bytes .../agent_framework/runtime/agent_runtime.py | 2 +- .../agent_template_backend/llm_profiles.yaml | 8 +- .../llm_profiles.yaml | 8 +- ...configuration.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 12590 bytes tests/test_llm_max_tokens_configuration.py | 95 ++++++++++++++++++ 43 files changed, 464 insertions(+), 34 deletions(-) create mode 100644 MULTI_ITEM_FRAMEWORK_FIX_20260918.md create mode 100644 docs/FIX_LLM_MAX_TOKENS_CONFIGURATION_20260918.md create mode 100644 tests/__pycache__/test_llm_max_tokens_configuration.cpython-313-pytest-9.0.2.pyc create mode 100644 tests/test_llm_max_tokens_configuration.py diff --git a/MULTI_ITEM_FRAMEWORK_FIX_20260918.md b/MULTI_ITEM_FRAMEWORK_FIX_20260918.md new file mode 100644 index 00000000..0f8e4376 --- /dev/null +++ b/MULTI_ITEM_FRAMEWORK_FIX_20260918.md @@ -0,0 +1,29 @@ +# Multi-item result preservation fix + +## Objective +Preserve the terminal outcome of each item executed by the primary transactional tool. A failure in a later auxiliary/composite step must not erase successful item outcomes or turn an already-completed multi-item operation into a total failure. + +## Framework-only change +No agent/template business logic is required for this fix. The runtime now recognizes an explicit structural contract on the requested tool: a `results` array with two or more entries carrying boolean `success` or `ok` fields. + +For workflow-backed tools the authoritative source is preferentially: + +`workflow.output[tool_name].results` + +The framework derives one of: +- `SUCCESS` +- `PARTIAL_SUCCESS` +- `FAILED` + +and publishes a generic `metadata.multi_item` summary with item counts. + +When one or more primary items completed successfully but the outer wrapper is `ok=false` because a later auxiliary/post-processing step failed, the normalized result preserves the original failure as `original_ok=false` / `secondary_error` while exposing the requested operation as operationally successful (`ok=true`). + +The LLM context also receives an explicit generic instruction that successful per-item terminal evidence cannot be overwritten by a secondary error and that mixed outcomes must be reported individually. + +## Regression tests +Added `tests/test_multi_item_result_normalization.py` covering: +- 2 success + 1 failure -> `PARTIAL_SUCCESS`; +- all primary items successful + later secondary failure -> primary `SUCCESS` preserved; +- single-item behavior unchanged; +- unrelated nested tool results cannot override failure of the requested primary tool. diff --git a/Tuning-Performance/Authentication/agent_template_backend_authentication/llm_profiles.yaml b/Tuning-Performance/Authentication/agent_template_backend_authentication/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Authentication/agent_template_backend_authentication/llm_profiles.yaml +++ b/Tuning-Performance/Authentication/agent_template_backend_authentication/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/Deterministic_Transactional_Workflow/agent_template_backend/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/llm_profiles.yaml index bebfcf1f..06996e27 100644 --- a/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/External_Guardrails_Judges/agent_template_backend/llm_profiles.yaml @@ -9,11 +9,18 @@ profiles: model: openai.gpt-4.1 temperature: 0 max_tokens: 700 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 + router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/Long_Term_Memory/templates/agent_template_backend/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Normal/templates/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/Normal/templates/agent_template_backend/llm_profiles.yaml index bebfcf1f..06996e27 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/Normal/templates/agent_template_backend/llm_profiles.yaml @@ -9,11 +9,18 @@ profiles: model: openai.gpt-4.1 temperature: 0 max_tokens: 700 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 + router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/llm_profiles.yaml b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/llm_profiles.yaml +++ b/Tuning-Performance/Normal/templates/agent_template_backend_day_zero/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/llm_profiles.yaml b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/llm_profiles.yaml +++ b/Tuning-Performance/Route_Stickness/templates/agent_template_backend_day_zero/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Transaction_Evidence/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/Transaction_Evidence/agent_template_backend/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Transaction_Evidence/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/Transaction_Evidence/agent_template_backend/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/llm_profiles.yaml b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/llm_profiles.yaml +++ b/Tuning-Performance/Transaction_Pre_Validation/agent_template_backend/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/docs/FIX_LLM_MAX_TOKENS_CONFIGURATION_20260918.md b/docs/FIX_LLM_MAX_TOKENS_CONFIGURATION_20260918.md new file mode 100644 index 00000000..6521abb5 --- /dev/null +++ b/docs/FIX_LLM_MAX_TOKENS_CONFIGURATION_20260918.md @@ -0,0 +1,58 @@ +# Correção — `max_tokens` por `llm_profiles.yaml` / `.env` + +## Objetivo + +Remover `max_tokens` efetivos fixados nos pontos de inferência do framework sem perder compatibilidade com os budgets anteriores. + +A precedência usada pelos pontos corrigidos passa a ser: + +1. `max_tokens` do profile selecionado em `llm_profiles.yaml`; +2. `max_tokens` do profile `default` do mesmo YAML, quando aplicável ao resolver; +3. `LLM_MAX_TOKENS` configurado no ambiente / `.env`; +4. valor legado do ponto de inferência, usado somente como fallback de compatibilidade. + +O parâmetro interno `fallback_max_tokens` **não é uma nova configuração do usuário**. Ele apenas preserva o valor histórico quando não existe configuração externa. + +## Pontos corrigidos + +- Enterprise Router: + - transaction parameter relevance: fallback legado `256`; + - transaction intent shift: fallback legado `512`; + - multi-intent: fallback legado `768`; + - routing normal: fallback legado `512`. +- MCP parameter extraction: fallback `80`. +- Processing interruption classifier: fallback `8`. +- LLM guardrail: fallback `600`. +- RAG query rewrite: fallback `300`. +- RAG context compression: mantém a fórmula histórica `max(512, max_chars // 3)` apenas como fallback. +- Conversation summary memory: mantém a fórmula histórica `max(256, max_summary_chars // 4)` apenas como fallback. + +## Atualização de `llm_profiles.yaml` + +Como os quatro pontos do Enterprise Router usam o mesmo profile `router`, o profile foi ajustado para o **maior budget histórico**, conforme solicitado: + +```yaml +router: + max_tokens: 768 +``` + +Também foram garantidos os profiles explícitos: + +```yaml +processing_interruption_classifier: + max_tokens: 8 + +mcp_parameter_extraction: + max_tokens: 80 +``` + +Os profiles já existentes foram mantidos: + +- `guardrail: 600` +- `rag_rewriter: 300` +- `rag_compressor: 1200` +- `summary_memory: 1200` + +## Compatibilidade + +Se `llm_profiles.yaml` não estiver disponível e `LLM_MAX_TOKENS` não estiver definido, cada ponto corrigido continua usando seu budget histórico. Portanto a correção não transforma a ausência de configuração em `2048` apenas por causa do default global de `Settings`. diff --git a/evals/offline/configs/llm_profiles/llm_profiles.yaml b/evals/offline/configs/llm_profiles/llm_profiles.yaml index 7975152c..db997275 100644 --- a/evals/offline/configs/llm_profiles/llm_profiles.yaml +++ b/evals/offline/configs/llm_profiles/llm_profiles.yaml @@ -9,3 +9,11 @@ profiles: model: mock-judge temperature: 0 max_tokens: 300 + + mcp_parameter_extraction: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 80 + timeout_seconds: 5 + diff --git a/libs/agent_framework/build/lib/agent_framework/channels/interruption.py b/libs/agent_framework/build/lib/agent_framework/channels/interruption.py index 7c6f55db..9151a1f0 100644 --- a/libs/agent_framework/build/lib/agent_framework/channels/interruption.py +++ b/libs/agent_framework/build/lib/agent_framework/channels/interruption.py @@ -58,7 +58,7 @@ async def classify_processing_interruption( response = await llm.ainvoke( [{"role": "system", "content": prompt}], temperature=0, - max_tokens=8, + fallback_max_tokens=8, profile_name=profile_name, component_name=profile_name, generation_name=f"llm.{profile_name}", diff --git a/libs/agent_framework/build/lib/agent_framework/guardrails/llm_rails.py b/libs/agent_framework/build/lib/agent_framework/guardrails/llm_rails.py index 40c3f901..a5851053 100644 --- a/libs/agent_framework/build/lib/agent_framework/guardrails/llm_rails.py +++ b/libs/agent_framework/build/lib/agent_framework/guardrails/llm_rails.py @@ -47,7 +47,7 @@ class LLMGuardrailRail(Guardrail): {"role": "user", "content": prompt}, ], temperature=0, - max_tokens=600, + fallback_max_tokens=600, profile_name=self.profile_name, component_name=self.profile_name, generation_name=f"llm.{self.profile_name}", diff --git a/libs/agent_framework/build/lib/agent_framework/llm/profile_resolver.py b/libs/agent_framework/build/lib/agent_framework/llm/profile_resolver.py index 3372658c..22cda60b 100644 --- a/libs/agent_framework/build/lib/agent_framework/llm/profile_resolver.py +++ b/libs/agent_framework/build/lib/agent_framework/llm/profile_resolver.py @@ -2,6 +2,7 @@ from __future__ import annotations import copy import logging +import os import re from pathlib import Path from typing import Any @@ -164,6 +165,66 @@ class LLMProfileResolver: return effective + def resolve_configured_value( + self, + profile_name: str | None, + key: str, + *, + env_var: str | None = None, + fallback: Any = None, + ) -> Any: + """Resolve one configuration value with an explicit compatibility fallback. + + Precedence is intentionally aligned with the framework configuration model: + 1. specific ``llm_profiles.yaml`` profile; + 2. YAML ``default`` profile; + 3. explicitly configured general environment variable; + 4. the caller-provided legacy fallback. + + The last step is important for old inference points that historically had a + local constant. Pydantic's Settings object always has framework defaults, so + checking only ``settings.LLM_MAX_TOKENS`` would make an *unset* environment + indistinguishable from the global default. + """ + selected_name = self.normalize_profile_name(profile_name) + if self.enabled: + specific_profile = self._profiles.get(selected_name) or {} + default_profile = self._profiles.get("default") or {} + if key in specific_profile and specific_profile.get(key) is not None: + return specific_profile.get(key) + if key in default_profile and default_profile.get(key) is not None: + return default_profile.get(key) + + if env_var: + raw = os.getenv(env_var) + if raw is not None and str(raw).strip() != "": + raw = str(raw).strip() + try: + if isinstance(fallback, bool): + return raw.lower() in {"1", "true", "yes", "on"} + if isinstance(fallback, int) and not isinstance(fallback, bool): + return int(raw) + if isinstance(fallback, float): + return float(raw) + except (TypeError, ValueError): + logger.warning( + "Invalid %s=%r for profile=%s key=%s; using legacy fallback=%r", + env_var, raw, selected_name, key, fallback, + ) + return fallback + return raw + + # Also honor a value explicitly supplied when Settings was instantiated + # programmatically, without treating the class default as configuration. + field_name = env_var or "" + fields_set = getattr(self.settings, "model_fields_set", set()) or set() + if field_name and field_name in fields_set: + value = getattr(self.settings, field_name, None) + if value is not None: + return value + + return fallback + def normalize_profile_name(self, profile_name: str | None) -> str: return _canonical_profile_name(profile_name) diff --git a/libs/agent_framework/build/lib/agent_framework/llm/providers.py b/libs/agent_framework/build/lib/agent_framework/llm/providers.py index ee91acde..e706f28c 100644 --- a/libs/agent_framework/build/lib/agent_framework/llm/providers.py +++ b/libs/agent_framework/build/lib/agent_framework/llm/providers.py @@ -370,6 +370,14 @@ class OCICompatibleOpenAIProvider(LLMProvider): profile_name = kwargs.pop("profile_name", None) component_name = kwargs.pop("component_name", None) or kwargs.pop("component", None) or profile_name or "default" generation_name = kwargs.pop("generation_name", None) or f"llm.{component_name}" + fallback_max_tokens = kwargs.pop("fallback_max_tokens", None) + if fallback_max_tokens is not None: + kwargs["max_tokens"] = self.profile_resolver.resolve_configured_value( + profile_name, + "max_tokens", + env_var="LLM_MAX_TOKENS", + fallback=fallback_max_tokens, + ) generation_name, generation_mapping_meta = _normalize_generation_name(self.telemetry, generation_name) effective = self.profile_resolver.resolve(profile_name, **kwargs) provider = str(effective.get("provider") or self.provider_name) diff --git a/libs/agent_framework/build/lib/agent_framework/memory/summary_memory.py b/libs/agent_framework/build/lib/agent_framework/memory/summary_memory.py index 024571b5..e1688fe3 100644 --- a/libs/agent_framework/build/lib/agent_framework/memory/summary_memory.py +++ b/libs/agent_framework/build/lib/agent_framework/memory/summary_memory.py @@ -167,7 +167,7 @@ class ConversationSummaryMemory: summary = await self.llm.ainvoke([ {"role": "system", "content": "Você resume memória conversacional para agentes corporativos."}, {"role": "user", "content": prompt}, - ], max_tokens=max(256, max_summary_chars // 4), temperature=0.1, profile_name="summary_memory", component_name="summary_memory", generation_name="llm.summary_memory") + ], fallback_max_tokens=max(256, max_summary_chars // 4), temperature=0.1, profile_name="summary_memory", component_name="summary_memory", generation_name="llm.summary_memory") summary = (summary or "").strip() if not summary: return self._deterministic_summary(previous_summary=previous_summary, messages=messages, max_chars=max_summary_chars) diff --git a/libs/agent_framework/build/lib/agent_framework/rag/rag_service.py b/libs/agent_framework/build/lib/agent_framework/rag/rag_service.py index d641fe2c..ea79035f 100644 --- a/libs/agent_framework/build/lib/agent_framework/rag/rag_service.py +++ b/libs/agent_framework/build/lib/agent_framework/rag/rag_service.py @@ -84,7 +84,7 @@ class RagService: {'role': 'user', 'content': prompt}, ], temperature=0, - max_tokens=300, + fallback_max_tokens=300, profile_name=profile_name, component_name=profile_name, generation_name=f"llm.{profile_name}", @@ -112,7 +112,7 @@ class RagService: {'role': 'user', 'content': prompt}, ], temperature=0, - max_tokens=max(512, max_chars // 3), + fallback_max_tokens=max(512, max_chars // 3), profile_name=profile_name, component_name=profile_name, generation_name=f"llm.{profile_name}", diff --git a/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py b/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py index f6ec8a73..bfb476a9 100644 --- a/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py +++ b/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py @@ -1045,7 +1045,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=256, + fallback_max_tokens=256, profile_name="router", component_name="router.transaction_parameter_relevance", generation_name="llm.transaction_parameter_relevance", @@ -1328,7 +1328,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(user, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=512, + fallback_max_tokens=512, profile_name="router", component_name="router", generation_name="llm.transaction_intent_shift", @@ -1674,7 +1674,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=768, + fallback_max_tokens=768, profile_name="router", component_name="router.multi_intent", generation_name="llm.router.multi_intent", @@ -1730,7 +1730,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(user, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=512, + fallback_max_tokens=512, profile_name="router", component_name="router", generation_name="llm.router", diff --git a/libs/agent_framework/build/lib/agent_framework/runtime/agent_runtime.py b/libs/agent_framework/build/lib/agent_framework/runtime/agent_runtime.py index 2ec6be62..c4ee75dc 100644 --- a/libs/agent_framework/build/lib/agent_framework/runtime/agent_runtime.py +++ b/libs/agent_framework/build/lib/agent_framework/runtime/agent_runtime.py @@ -849,7 +849,7 @@ class AgentRuntimeMixin: component_name="mcp_parameter_extraction", generation_name="llm.mcp_parameter_extraction", temperature=0.0, - max_tokens=80, + fallback_max_tokens=80, ) raw = self._llm_response_text(response).strip() payload = parse_json_object(raw) diff --git a/libs/agent_framework/config/llm_profiles.yaml b/libs/agent_framework/config/llm_profiles.yaml index 9e00520e..8eb5129d 100644 --- a/libs/agent_framework/config/llm_profiles.yaml +++ b/libs/agent_framework/config/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 @@ -72,6 +78,13 @@ profiles: provider: oci_openai model: openai.gpt-4.1 temperature: 0.2 + mcp_parameter_extraction: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 80 + timeout_seconds: 5 + transaction_parameter_extraction: provider: oci_openai model: openai.gpt-4.1-mini diff --git a/libs/agent_framework/llm_profiles.yaml.example b/libs/agent_framework/llm_profiles.yaml.example index 3ef6f870..230c72ee 100644 --- a/libs/agent_framework/llm_profiles.yaml.example +++ b/libs/agent_framework/llm_profiles.yaml.example @@ -15,11 +15,18 @@ profiles: temperature: 0 max_tokens: 700 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 + router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 # Safety / evaluation guardrail: @@ -87,3 +94,11 @@ profiles: provider: oci_openai model: openai.gpt-4.1 temperature: 0.2 + + mcp_parameter_extraction: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 80 + timeout_seconds: 5 + diff --git a/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc b/libs/agent_framework/src/agent_framework/channels/__pycache__/interruption.cpython-313.pyc index 7292b69cfeb61eb8563b360b7e337ca2c4e55f05..70d9720f2495105274f2cda8937e549412e05e82 100644 GIT binary patch delta 53 zcmZ2&)NaK4nU|M~0SNy5T$?4nk#`CcqvPiDOiUbH!fA;)IZ27h+3}k%aRo9mDJX7M I5flBA!Q ro|;z@pH`HZn_8YS* I#dn7T0O4a0)&Kwi delta 89 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 PxyjM{=4L4SecgWm(JM&m delta 4982 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 diff --git a/libs/agent_framework/src/agent_framework/llm/profile_resolver.py b/libs/agent_framework/src/agent_framework/llm/profile_resolver.py index 90ea4e1a..f77f0843 100644 --- a/libs/agent_framework/src/agent_framework/llm/profile_resolver.py +++ b/libs/agent_framework/src/agent_framework/llm/profile_resolver.py @@ -2,6 +2,7 @@ from __future__ import annotations import copy import logging +import os import re from pathlib import Path from typing import Any @@ -164,6 +165,66 @@ class LLMProfileResolver: return effective + def resolve_configured_value( + self, + profile_name: str | None, + key: str, + *, + env_var: str | None = None, + fallback: Any = None, + ) -> Any: + """Resolve one configuration value with an explicit compatibility fallback. + + Precedence is intentionally aligned with the framework configuration model: + 1. specific ``llm_profiles.yaml`` profile; + 2. YAML ``default`` profile; + 3. explicitly configured general environment variable; + 4. the caller-provided legacy fallback. + + The last step is important for old inference points that historically had a + local constant. Pydantic's Settings object always has framework defaults, so + checking only ``settings.LLM_MAX_TOKENS`` would make an *unset* environment + indistinguishable from the global default. + """ + selected_name = self.normalize_profile_name(profile_name) + if self.enabled: + specific_profile = self._profiles.get(selected_name) or {} + default_profile = self._profiles.get("default") or {} + if key in specific_profile and specific_profile.get(key) is not None: + return specific_profile.get(key) + if key in default_profile and default_profile.get(key) is not None: + return default_profile.get(key) + + if env_var: + raw = os.getenv(env_var) + if raw is not None and str(raw).strip() != "": + raw = str(raw).strip() + try: + if isinstance(fallback, bool): + return raw.lower() in {"1", "true", "yes", "on"} + if isinstance(fallback, int) and not isinstance(fallback, bool): + return int(raw) + if isinstance(fallback, float): + return float(raw) + except (TypeError, ValueError): + logger.warning( + "Invalid %s=%r for profile=%s key=%s; using legacy fallback=%r", + env_var, raw, selected_name, key, fallback, + ) + return fallback + return raw + + # Also honor a value explicitly supplied when Settings was instantiated + # programmatically, without treating the class default as configuration. + field_name = env_var or "" + fields_set = getattr(self.settings, "model_fields_set", set()) or set() + if field_name and field_name in fields_set: + value = getattr(self.settings, field_name, None) + if value is not None: + return value + + return fallback + def normalize_profile_name(self, profile_name: str | None) -> str: return _canonical_profile_name(profile_name) diff --git a/libs/agent_framework/src/agent_framework/llm/providers.py b/libs/agent_framework/src/agent_framework/llm/providers.py index b20c298a..eb15700f 100644 --- a/libs/agent_framework/src/agent_framework/llm/providers.py +++ b/libs/agent_framework/src/agent_framework/llm/providers.py @@ -370,6 +370,14 @@ class OCICompatibleOpenAIProvider(LLMProvider): profile_name = kwargs.pop("profile_name", None) component_name = kwargs.pop("component_name", None) or kwargs.pop("component", None) or profile_name or "default" generation_name = kwargs.pop("generation_name", None) or f"llm.{component_name}" + fallback_max_tokens = kwargs.pop("fallback_max_tokens", None) + if fallback_max_tokens is not None: + kwargs["max_tokens"] = self.profile_resolver.resolve_configured_value( + profile_name, + "max_tokens", + env_var="LLM_MAX_TOKENS", + fallback=fallback_max_tokens, + ) generation_name, generation_mapping_meta = _normalize_generation_name(self.telemetry, generation_name) effective = self.profile_resolver.resolve(profile_name, **kwargs) provider = str(effective.get("provider") or self.provider_name) diff --git a/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc b/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc index 2b62fc7fa2407f00a458b1c44b9d1a54f10f982a..d985ade6af3d295125c7890a4f96277f83f5f5dc 100644 GIT binary patch delta 114 zcmX?+xGs_RGcPX}0}%ZAxi-sgBky5mM#s%xncvEB38y9II;f5=-3_*yr|=JS=sqQV915!;tvcAu1uj!Kq{T7n&|^OgRJfc24>kBrtb{Q S;x&`ks`@k5Z2qfynhOA|u_@#L delta 140 zcmZ3Ncp{PaGcPX}0}yDMuFW#t$a|PsHB~=1uS7p3u_RGHB|q8FTt6{AHLoN-ttc@! zwLHHlJ3c=-Q-AYC=C^W;T$@iQ&1GafG&xP>myxvGjN}W-COa4}DqCKbvO2+hAu8(w s149j?4-=4zV=7|$z|J76`+x(oDmI&a70o#TeN7k+ delta 108 zcmaD5(h|!1nU|M~0SGiq*JjPz$m`6am!Y4VSE8Sy?;GqHA5fH^m6}{qte>5flBA!Q zo|;z@pH`HZn_8YV?{<+zde9v`FYW=a0rE4YNy5_58r5|gvzH-Fu3#muCl gzS(g1ds`5#^mCdPlY;X0UCN9fxIv6H`i!1}0En3z-~a#s delta 131 zcmccE$ojRBmG?6*FBbz4sG6?LlHJIAk58{iKR2&LKSkd+*fTz$C_gJTxujS>J1Hee zKQTQuuOvRLC^0v+JijPAK0i5AfAbr@+bbBkHecLs#mpovx0!eMds`6w@5eMPZgHs_ hl8QH^4Q|MoPM@gHsJ#7Rc diff --git a/libs/agent_framework/src/agent_framework/routing/enterprise_router.py b/libs/agent_framework/src/agent_framework/routing/enterprise_router.py index f6ec8a73..bfb476a9 100644 --- a/libs/agent_framework/src/agent_framework/routing/enterprise_router.py +++ b/libs/agent_framework/src/agent_framework/routing/enterprise_router.py @@ -1045,7 +1045,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=256, + fallback_max_tokens=256, profile_name="router", component_name="router.transaction_parameter_relevance", generation_name="llm.transaction_parameter_relevance", @@ -1328,7 +1328,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(user, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=512, + fallback_max_tokens=512, profile_name="router", component_name="router", generation_name="llm.transaction_intent_shift", @@ -1674,7 +1674,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(payload, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=768, + fallback_max_tokens=768, profile_name="router", component_name="router.multi_intent", generation_name="llm.router.multi_intent", @@ -1730,7 +1730,7 @@ class EnterpriseRouter: {"role": "user", "content": json.dumps(user, ensure_ascii=False)}, ], temperature=0.0, - max_tokens=512, + fallback_max_tokens=512, profile_name="router", component_name="router", generation_name="llm.router", diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc index c23d6d16a51840a9dc3599f6d70e123491136822..f14cadc3cbf22a948e848cb501860640993774a0 100644 GIT binary patch delta 891 zcmZuvO=uHQ5YA?jG$xxUly+N?=C|2wk~TtUkytIEp{8mpg`!e)vuWB2w*GYetD*(* z5VaSH$aqyjM6}rCu;eI+NWqf_u@~{=L3$DjLJz)~6z#!1yf^QgZ|D1F-g~hrS2tzf zFQ3n26W`YFH|HL#`l@azSAXyR{L0fhTbiGrDwXGo^(XZXmp#>cx7MiC8j@9O*q=_w z>z?za#p0EVb2Aq%<0(IMxehsPHgnLr<%czQ%P~5PIx*M|9d>gJ&nj?0I*!j37?BFr zh60mrNykDEroBa?z7!rz9jzc-RSW`#3HT%y&0+qe*ubF>EPHiIR+{XuAxKL_+!cm^ zD5iy>B2D3H82SYGBMgV}TLPSziNK1Vf8(0VlFf#>DC|XV6imTfiV9B#evHCoh`lf^ zK+F9g3T58mO5kHun^y)K^0<(KBVK;6+?sBENWr*WE)Z#gmebBhhN4*6G#vCw3%Hnt Fl|L|L$hrUk delta 971 zcmZ8fOK1~e5YFx-ZOEe{+NK^f@5d%7t6;U(7ez^^RYB?lDw{R0s+Bfzi4_l3#6uJh zLN0UAgH{j`Drlr3;KhR;gsRY!9z;<@5Dy|2!HYBhR@-zBv$Nm-?R@jiKeYwdy#<&1 zr`zqY(DUre%-H2e?wUPZRL3VOY9U+6s)bU1(^fTmvN%!EM~&=w@qEcRqnGlh)%tDw z`~%5RpQ?Aa$-(dyY*w);HLxk)Ijk6;C5z>qtx_uMWu?vdKwm~Lhn+SGpEzTNv8!>a z176zOhZx>(Lsu7c%fTuA}OQ>rRait0w!oA52QH4KV}>yyDLHWOtTzf2*+Wsf*>EdJdDUK6_o zfCy~qS^T}me+u>m&O{;MNSs@ZJ|I-X(FpXC@T~|8(d%mjcAMSyMPZw3X$;ij*$AxV zR^JC8h_g{xMP^<^A=N3S_vgRJ8_qK5kHJ8a3n+HO$B>u9N*cQnU3~;0fYULUq{8bI z@X9$M){_dbU14RBD&`Zn{zF$7WG2mTk`1=AsLD(YXQ(EXo>SncTMWTr&RD~Zaaeyz z*jPTHt6X3S<*dbvxfASQFAJ=guw$8PaTj@JUgS$X!I_Iq@phU1b;+EtSiKgR9n8`2 zcO3fVqDhi+IGBKIUUp%wW`VO92Z1S^W$;G=*3hlml5kAggIW^yImMNVeznHqBpi~R UY32@dINs<`X~m>67W(1gUqXA_umAu6 diff --git a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py index 2ec6be62..c4ee75dc 100644 --- a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py +++ b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py @@ -849,7 +849,7 @@ class AgentRuntimeMixin: component_name="mcp_parameter_extraction", generation_name="llm.mcp_parameter_extraction", temperature=0.0, - max_tokens=80, + fallback_max_tokens=80, ) raw = self._llm_response_text(response).strip() payload = parse_json_object(raw) diff --git a/templates/agent_template_backend/llm_profiles.yaml b/templates/agent_template_backend/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/templates/agent_template_backend/llm_profiles.yaml +++ b/templates/agent_template_backend/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/templates/agent_template_backend_day_zero/llm_profiles.yaml b/templates/agent_template_backend_day_zero/llm_profiles.yaml index 29fda415..93e04010 100644 --- a/templates/agent_template_backend_day_zero/llm_profiles.yaml +++ b/templates/agent_template_backend_day_zero/llm_profiles.yaml @@ -15,11 +15,17 @@ profiles: temperature: 0 max_tokens: 80 timeout_seconds: 5 + processing_interruption_classifier: + provider: oci_openai + model: openai.gpt-4.1-mini + temperature: 0 + max_tokens: 8 + timeout_seconds: 5 router: provider: oci_openai model: openai.gpt-4.1 temperature: 0 - max_tokens: 500 + max_tokens: 768 guardrail: provider: oci_openai model: openai.gpt-4.1 diff --git a/tests/__pycache__/test_llm_max_tokens_configuration.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/test_llm_max_tokens_configuration.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d22753a1ab6bc19c21a82c7b2ade7f259d6f9eb9 GIT binary patch literal 12590 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 literal 0 HcmV?d00001 diff --git a/tests/test_llm_max_tokens_configuration.py b/tests/test_llm_max_tokens_configuration.py new file mode 100644 index 00000000..c2580881 --- /dev/null +++ b/tests/test_llm_max_tokens_configuration.py @@ -0,0 +1,95 @@ +from __future__ import annotations + +import os +import sys +from pathlib import Path +from types import SimpleNamespace + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +LIB = ROOT / "libs" / "agent_framework" / "src" +if str(LIB) not in sys.path: + sys.path.insert(0, str(LIB)) + +from agent_framework.llm.profile_resolver import LLMProfileResolver + + +def _settings(path: Path): + return SimpleNamespace( + LLM_PROFILES_PATH=str(path), + LLM_PROVIDER="oci_openai", + OCI_GENAI_MODEL="openai.gpt-4.1", + LLM_TEMPERATURE=0.2, + LLM_MAX_TOKENS=2048, + LLM_TIMEOUT_SECONDS=120, + OCI_GENAI_BASE_URL="", + OCI_GENAI_API_KEY=None, + OCI_GENAI_PROJECT_OCID=None, + OCI_AUTH_MODE="config_file", + OCI_GENAI_ENDPOINT=None, + OCI_REGION="", + model_fields_set=set(), + ) + + +def test_profile_max_tokens_wins_over_env_and_legacy_fallback(tmp_path, monkeypatch): + profile = tmp_path / "llm_profiles.yaml" + profile.write_text( + "profiles:\n" + " default:\n" + " provider: oci_openai\n" + " router:\n" + " max_tokens: 768\n", + encoding="utf-8", + ) + monkeypatch.setenv("LLM_MAX_TOKENS", "999") + resolver = LLMProfileResolver.from_settings(_settings(profile)) + assert resolver.resolve_configured_value( + "router", "max_tokens", env_var="LLM_MAX_TOKENS", fallback=512 + ) == 768 + + +def test_env_max_tokens_wins_when_profiles_file_is_absent(tmp_path, monkeypatch): + missing = tmp_path / "missing.yaml" + monkeypatch.setenv("LLM_MAX_TOKENS", "901") + resolver = LLMProfileResolver.from_settings(_settings(missing)) + assert resolver.resolve_configured_value( + "router", "max_tokens", env_var="LLM_MAX_TOKENS", fallback=512 + ) == 901 + + +def test_legacy_fallback_is_kept_when_yaml_and_env_are_absent(tmp_path, monkeypatch): + missing = tmp_path / "missing.yaml" + monkeypatch.delenv("LLM_MAX_TOKENS", raising=False) + resolver = LLMProfileResolver.from_settings(_settings(missing)) + assert resolver.resolve_configured_value( + "router", "max_tokens", env_var="LLM_MAX_TOKENS", fallback=512 + ) == 512 + + +def test_canonical_profiles_keep_existing_budgets_and_router_uses_largest_value(): + profile_path = ROOT / "libs" / "agent_framework" / "config" / "llm_profiles.yaml" + profiles = yaml.safe_load(profile_path.read_text(encoding="utf-8"))["profiles"] + assert profiles["router"]["max_tokens"] == 768 + assert profiles["processing_interruption_classifier"]["max_tokens"] == 8 + assert profiles["mcp_parameter_extraction"]["max_tokens"] == 80 + assert profiles["guardrail"]["max_tokens"] == 600 + assert profiles["rag_rewriter"]["max_tokens"] == 300 + assert profiles["rag_compressor"]["max_tokens"] == 1200 + assert profiles["summary_memory"]["max_tokens"] == 1200 + + +def test_inference_points_use_fallback_max_tokens_instead_of_direct_max_tokens(): + checks = { + "libs/agent_framework/src/agent_framework/routing/enterprise_router.py": [256, 512, 768], + "libs/agent_framework/src/agent_framework/runtime/agent_runtime.py": [80], + "libs/agent_framework/src/agent_framework/channels/interruption.py": [8], + "libs/agent_framework/src/agent_framework/guardrails/llm_rails.py": [600], + "libs/agent_framework/src/agent_framework/rag/rag_service.py": [300], + } + for relative, values in checks.items(): + text = (ROOT / relative).read_text(encoding="utf-8") + for value in values: + assert f"fallback_max_tokens={value}" in text + assert f"max_tokens={value}," not in text.replace(f"fallback_max_tokens={value},", "")