new feature: Multi-Intent

This commit is contained in:
2026-09-10 16:12:04 -03:00
parent d535ec2a16
commit a5f24dac3f
223 changed files with 1121 additions and 21 deletions

View File

@@ -185,6 +185,7 @@ O framework deve concentrar os motores reutilizáveis:
- Telemetria Langfuse/OpenTelemetry.
- Analytics IC/NOC/GRL.
- MCP Tool Router.
- Planejamento multi-intent e fila persistente de `pending_topics`.
- Cache.
- RAG genérico.
@@ -230,6 +231,8 @@ Input Guardrails
↓
Enterprise Router ou Supervisor
↓
Planejamento multi-intent / pending_topics
↓
Agente especializado
↓
MCP Tool Router / RAG / Cache / LLM
@@ -11486,6 +11489,8 @@ O conteúdo desta pasta deve ser tratado como uma extensão adicional do framewo
| Um exemplo antigo não compila | documentação histórica versus API atual | [Validação README x Código](docs/developer/pt/VALIDATION_README_ALIGNMENT.md) |
| Preciso criar um agente novo do zero | fluxo completo | [`README.md`](README.md) |
| Preciso saber onde colocar uma nova feature | arquitetura e boundaries | [Arquitetura e Conceitos](docs/developer/pt/01_architecture_and_concepts.md) |
| Duas solicitações na mesma mensagem acionam apenas uma intent | decomposição multi-intent, `pending_topics`, allowlist de tools | [Planejamento Multi-Intent](docs/features/pt-BR/16_multi_intent_planning.md) |
| O agent secundário repete a transação ou confirmação primária | isolamento de `active_transaction`, `mcp_tools` e estado operacional | [Planejamento Multi-Intent](docs/features/pt-BR/16_multi_intent_planning.md) |
### 34. Funcionalidades Avançadas
@@ -11561,9 +11566,31 @@ O conteúdo desta pasta deve ser tratado como uma extensão adicional do framewo
**Use quando:** um `COER`/guardrail de entrada gera mensagem genérica, `route=blocked` carrega resultados antigos ou há dúvida sobre a precedência entre input guardrails, routing e tools.
### Guias de features implementadas
Os manuais `01` a `12` acima organizam os assuntos por área de desenvolvimento. Os guias abaixo documentam cada feature executável individualmente:
1. [Autenticação](docs/features/pt-BR/01_authentication.md)
2. [Workflow Transacional Determinístico](docs/features/pt-BR/02_deterministic_transactional_workflow.md)
3. [Composição por LLM Solicitada pelo Domínio](docs/features/pt-BR/03_domain_requested_llm_composition.md)
4. [RAG Solicitado pelo Domínio](docs/features/pt-BR/04_domain_requested_rag.md)
5. [Memória de Longo Prazo](docs/features/pt-BR/05_long_term_memory.md)
6. [Regressão Offline de Workflow](docs/features/pt-BR/06_offline_workflow_regression.md)
7. [Pause e Resume de Workflow](docs/features/pt-BR/07_pause_resume_workflow.md)
8. [Route Stickiness](docs/features/pt-BR/08_route_stickiness.md)
9. [Replay em Interrupções de Voz](docs/features/pt-BR/09_voice_interruption_replay.md)
10. [Recuperação de Erro em Workflow](docs/features/pt-BR/10_workflow_error_recovery.md)
11. [Clarificação](docs/features/pt-BR/11_clarification.md)
12. [Idempotência Durável](docs/features/pt-BR/12_durable_idempotency.md)
13. [Estados Transacionais Dinâmicos](docs/features/pt-BR/13_dynamic_transaction_states.md)
14. [Replay Após Finalização](docs/features/pt-BR/14_post_finalization_replay.md)
15. [Guardrails de Retrieval e Tools](docs/features/pt-BR/15_retrieval_tool_guardrails.md)
16. [Planejamento Multi-Intent e Pending Topics](docs/features/pt-BR/16_multi_intent_planning.md)
O [índice de features em português](docs/features/pt-BR/README.md) deve ser atualizado sempre que um novo guia for adicionado.
### Tutorial principal
[`README.md`](README.md) continua sendo a referência para o passo a passo completo:
`arquitetura → configuração → criação do agente → registro → estado → routing → tools → MCP → identidade → execução → testes → gateways → memória → RAG`.

View File

@@ -126,7 +126,7 @@ Details the evaluation and certification framework, including evaluation archite
Defines the platform operational readiness and SRE model, including managed components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity planning, and production checklists.
### [IMPORTANT: Disclaimer Auth and Security](specs/Disclaimer%20Auth%20and%20Security_EN.md)
### [IMPORTANT: Disclaimer Auth and Security](specs/Disclaimer_Auth_and_Security_EN.md)
Oracle Cloud Infrastructure Security Best Practice Recommendations.
@@ -186,6 +186,7 @@ The framework should concentrate the reusable engines:
- Langfuse/OpenTelemetry Telemetry.
- Analytics IC/NOC/GRL.
- MCP Tool Router.
- Multi-intent planning and the persistent `pending_topics` queue.
- Cache.
- Generic RAG.
@@ -231,6 +232,8 @@ Input Guardrails
↓
Enterprise Router or Supervisor
↓
Multi-intent planning / pending_topics
↓
Specialized agent
↓
MCP Tool Router / RAG / Cache / LLM
@@ -11388,6 +11391,8 @@ The content of this folder should be treated as an additional framework extensio
| Logs/traces do not correlate the same agent | labels, IDs, observability mapping | [Observability](docs/developer/en/11_observability_persistence_and_operational_readiness.md) |
| Historical example no longer compiles | stale docs vs current API | [README Alignment Validation](docs/developer/en/VALIDATION_README_ALIGNMENT.md) |
| Need to create a new agent from scratch | complete flow | [`README_en.md`](README_en.md) |
| Two requests in one message trigger only one intent | multi-intent decomposition, `pending_topics`, tool allowlist | [Multi-Intent Planning](docs/features/en/16_multi_intent_planning.md) |
| A secondary agent repeats the primary transaction or confirmation | isolation of `active_transaction`, `mcp_tools`, and operational state | [Multi-Intent Planning](docs/features/en/16_multi_intent_planning.md) |
### 34. Advanced Features
@@ -11463,6 +11468,29 @@ The content of this folder should be treated as an additional framework extensio
**Use it when:** `COER`/input guardrails generate generic messages, `route=blocked` carries stale results, or you need to reason about precedence between input guardrails, routing, and tools.
### Implemented feature guides
The `01` through `12` manuals above organize documentation by development area. The guides below document each executable feature individually:
1. [Authentication](docs/features/en/01_authentication.md)
2. [Deterministic Transactional Workflow](docs/features/en/02_deterministic_transactional_workflow.md)
3. [Domain-Requested LLM Composition](docs/features/en/03_domain_requested_llm_composition.md)
4. [Domain-Requested RAG](docs/features/en/04_domain_requested_rag.md)
5. [Long-Term Memory](docs/features/en/05_long_term_memory.md)
6. [Offline Workflow Regression](docs/features/en/06_offline_workflow_regression.md)
7. [Pause and Resume Workflow](docs/features/en/07_pause_resume_workflow.md)
8. [Route Stickiness](docs/features/en/08_route_stickiness.md)
9. [Voice Interruption Replay](docs/features/en/09_voice_interruption_replay.md)
10. [Workflow Error Recovery](docs/features/en/10_workflow_error_recovery.md)
11. [Clarification](docs/features/en/11_clarification.md)
12. [Durable Idempotency](docs/features/en/12_durable_idempotency.md)
13. [Dynamic Transaction States](docs/features/en/13_dynamic_transaction_states.md)
14. [Post-Finalization Replay](docs/features/en/14_post_finalization_replay.md)
15. [Retrieval and Tool Guardrails](docs/features/en/15_retrieval_tool_guardrails.md)
16. [Multi-Intent Planning and Pending Topics](docs/features/en/16_multi_intent_planning.md)
The [English feature index](docs/features/en/README.md) must be updated whenever a new feature guide is added.
### Main tutorial
[`README_en.md`](README_en.md) remains the complete step-by-step guide.

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -409,6 +410,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -547,14 +552,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -623,6 +642,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -409,6 +410,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -547,14 +552,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -623,6 +642,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -407,6 +408,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -545,14 +550,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -621,6 +640,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -409,6 +410,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -547,14 +552,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -623,6 +642,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -407,6 +408,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -545,14 +550,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -621,6 +640,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -407,6 +408,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -545,14 +550,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -621,6 +640,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -407,6 +408,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -545,14 +550,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -621,6 +640,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -35,6 +35,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -407,6 +408,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -545,14 +550,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -621,6 +640,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -38,6 +38,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -409,6 +410,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -547,14 +552,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -623,6 +642,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -39,6 +39,8 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]

View File

@@ -7,6 +7,7 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -409,6 +410,10 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan") else []
),
}
async def billing_agent(self, state):
@@ -547,14 +552,28 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
"""
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"output_guardrails_already_applied": False,
"supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)),
}
candidate = state.get("answer") or ""
context = {
**(state.get("context") or {}),
"tenant_id": state.get("tenant_id"),
@@ -623,6 +642,8 @@ class AgentWorkflow:
for r in decision.results
],
"output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
}

View File

@@ -0,0 +1,27 @@
# Multi-Intent Planning
`MultiIntentPlanner` recognizes independent requests in one utterance and
builds a plan constrained to the intents, agents, and tools declared in
`config/routing.yaml`. It does not execute operations or let the LLM invent a
sequence: the router selects the primary operation and the domain workflow
continues to own the transaction.
## Configuration and execution
No additional configuration is required. The planner uses the intents already
declared in `routing.yaml`, selects the single transactional operation from
`tool_policies.yaml`, and persists the remaining requests as `pending_topics`.
Known read-only topics execute through their own agent. A second mutation stays
pending for its own confirmation, while an unknown clause becomes off-context.
The router resolves clauses from registered intent keywords and routes the
transactional operation first. The graph drains read-only pending topics before
output supervision. During an open transaction,
`yes, and send me the invoice` confirms the transaction and records the
secondary request instead of causing an intent shift.
For multiple side-effecting actions, implement a transactional coordinator
that persists the plan, confirms it, executes idempotently, and records partial
failure or compensation. See `tests/unit/test_multi_intent_planner.py`. No
special Tuning-Performance template is needed because the behavior is native.

View File

@@ -17,3 +17,4 @@ Documentation for the main `agent_framework_oci` features.
- [Dynamic Transaction States](13_dynamic_transaction_states.md)
- [Post Finalization Replay](14_post_finalization_replay.md)
- [Retrieval / Tool Guardrails](15_retrieval_tool_guardrails.md)
- [Multi-Intent Planning](16_multi_intent_planning.md)

View File

@@ -0,0 +1,46 @@
# Planejamento Multi-Intent
O `MultiIntentPlanner` reconhece solicitações independentes na mesma mensagem e
cria um plano limitado às intents, agents e tools declarados em
`config/routing.yaml`. O planejador não executa operações nem permite que o LLM
invente uma sequência: o roteador escolhe a operação primária e o workflow do
domínio continua responsável pela transação.
## Configuração
Não existe configuração adicional. O planejador usa as intents do `routing.yaml`
e lê `tool_policies.yaml` para priorizar automaticamente a única operação
transacional. Pedidos secundários são persistidos em `pending_topics`.
- intent conhecida e read-only: executada pelo agent correspondente;
- intent conhecida e transacional: mantida pendente para confirmação própria;
- trecho sem capability correspondente: respondido como `off_context`.
Consultas secundárias com `execute` podem ser executadas pelo agent correspondente
e consolidadas. Uma segunda mutação é marcada como `defer`, pois exige um
workflow transacional coordenador.
## Fluxo de execução
1. O `EnterpriseRouter` separa a entrada em cláusulas e resolve cada uma pelas
keywords das intents cadastradas.
2. `tool_policies.yaml` identifica a operação transacional e a torna primária.
3. A primeira operação é roteada normalmente para seu agent/workflow.
4. O `agent_graph` compõe as mensagens públicas antes do supervisor de saída.
5. Durante uma confirmação, uma frase como `sim e me manda a segunda via`
confirma a transação aberta e registra a solicitação secundária, sem causar
troca indevida de intent.
## Operações mutáveis
Para executar mais de uma ação com efeito colateral, implemente um workflow
transacional coordenador. Ele deve persistir o plano, pedir confirmação quando
necessário, executar cada passo com idempotência e registrar sucesso parcial,
falha e compensação. O planejador fornece o contrato; a política de negócio e a
orquestração permanecem no domínio.
## Referência executável
Veja os testes genéricos em `tests/unit/test_multi_intent_planner.py`. Como a
feature é padrão do runtime, não existe uma cópia especial do
`agent_template_backend` em `Tuning-Performance`.

View File

@@ -17,3 +17,4 @@ Documentação das principais features do `agent_framework_oci`.
- [Estados Transacionais Dinâmicos](13_dynamic_transaction_states.md)
- [Replay Após Finalização](14_post_finalization_replay.md)
- [Guardrails de Retrieval e Tools](15_retrieval_tool_guardrails.md)
- [Planejamento Multi-Intent](16_multi_intent_planning.md)

View File

@@ -201,6 +201,8 @@ src/agent_framework/routing/config_loader.py
src/agent_framework/routing/continuity.py
src/agent_framework/routing/enterprise_router.py
src/agent_framework/routing/models.py
src/agent_framework/routing/multi_intent.py
src/agent_framework/routing/pending_topics.py
src/agent_framework/runtime/__init__.py
src/agent_framework/runtime/agent_runtime.py
src/agent_framework/runtime/transaction_input.py

Some files were not shown because too many files have changed in this diff Show More