New features: Route Stickness, Handoff, Clarification, Read-Only/Transactional, Long Term Memory

This commit is contained in:
2026-08-03 08:57:02 -03:00
parent e684b0ecc3
commit 8e414e4e26
604 changed files with 38978 additions and 402 deletions

View File

@@ -135,12 +135,23 @@ ROUTING_CONFIG_PATH=./config/routing.yaml
# Em produção, costuma ser útil; em desenvolvimento, false evita custo e latência.
ENABLE_LLM_ROUTER=true
# Continuidade semântica, handoff humano e encerramento global.
ENABLE_ROUTE_STICKINESS=true
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
ROUTE_STICKINESS_HISTORY_TURNS=2
ROUTE_STICKINESS_MAX_TOKENS=80
HUMAN_HANDOFF_MESSAGE=Vou encaminhar seu atendimento para uma pessoa.
END_SESSION_MESSAGE=Atendimento encerrado. Obrigado pelo contato.
SESSION_ALREADY_ENDED_MESSAGE=Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.
###############################################################################
# MCP / Tools
###############################################################################
ENABLE_MCP_TOOLS=true
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
TOOLS_CONFIG_PATH=./config/tools.yaml
TOOL_POLICIES_PATH=./config/tool_policies.yaml
MCP_TOOL_TIMEOUT_SECONDS=30
# router = EnterpriseRouter seleciona um agente; supervisor = pode acionar múltiplos agentes

View File

@@ -1,6 +1,6 @@
FROM python:3.12-slim
WORKDIR /app
COPY agent_framework /agent_framework
COPY agent_template_backend /app
COPY agent_template_backend_day_zero /app
RUN pip install --no-cache-dir -e /agent_framework -r requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

View File

@@ -84,3 +84,6 @@ return {
- `app/agents/runtime.py`
Esses arquivos são o esqueleto de execução usando o framework.
# Política opcional de tools
O arquivo `config/tool_policies.yaml` classifica tools como `read_only` ou `transactional`. Para uma transação real, ative `require_confirmation: true`; chamadas sem `confirmed: true` ou `confirmation: true` serão bloqueadas antes do MCP. A ausência do arquivo preserva o comportamento de templates anteriores.

View File

@@ -1,10 +1,3 @@
"""
DAY ZERO TEMPLATE - BillingAgent
Esqueleto mínimo já compatível com ConversationSummaryMemory.
Substitua o prompt e a regra de negócio conforme o seu agente.
"""
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.runtime import AgentRuntimeMixin
@@ -35,12 +28,67 @@ class BillingAgent(AgentRuntimeMixin):
self.summary_memory = summary_memory
async def run(self, state):
# OPCIONAL: habilite quando seu agente precisar de MCP/RAG.
tool_context = []
rag_context = None
rag_metadata = {}
await self._emit_ic(
"IC.BILLING_AGENT_STARTED",
state,
{"business_component": "faturas"},
component="agent.billing.start",
)
# Prepara a memória resumida antes do prompt.
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.BILLING_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.billing.mcp",
)
state["mcp_results"] = tool_context
clarification_message = self.transaction_clarification_message(state)
if clarification_message:
return {
"answer": f"[{self.__class__.__name__}] {clarification_message}",
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
confirmation_message = self.transaction_confirmation_message(state)
if confirmation_message:
result = {
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
"next_state": state.get("next_state"),
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
return result
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="BillingAgent")
if direct_answer:
return {
"answer": direct_answer,
"next_state": state.get("next_state") or "ACTIVE",
"mcp_results": tool_context,
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
**self.transaction_state_patch(state),
}
rag_context, rag_metadata = await self._retrieve_rag_context(state)
if rag_metadata.get("enabled"):
await self._emit_ic(
"IC.BILLING_RAG_CONTEXT_RETRIEVED",
state,
{
"document_count": rag_metadata.get("document_count"),
"graph_neighbors": rag_metadata.get("graph_neighbors"),
"latency_ms": rag_metadata.get("latency_ms"),
},
component="agent.billing.rag",
)
# Prepara ConversationSummaryMemory antes de montar o prompt.
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
await self.prepare_memory_context(state)
messages = self.build_messages(
@@ -55,13 +103,27 @@ class BillingAgent(AgentRuntimeMixin):
)
answer = await self._invoke_llm_cached(state, "BillingAgent", messages)
return {
"answer": answer,
"next_state": "DAY_ZERO_ACTIVE",
result = {
"answer": f"[BillingAgent] {answer}",
"next_state": "BILLING_ACTIVE",
"mcp_results": tool_context,
"rag": rag_metadata,
"memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state),
}
await self._emit_ic(
"IC.BILLING_AGENT_COMPLETED",
state,
{
"answer_chars": len(result.get("answer") or ""),
"has_mcp_results": bool(tool_context),
"rag_enabled": bool(rag_metadata.get("enabled")),
"memory_context": state.get("memory_context_metadata"),
},
component="agent.billing.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

View File

@@ -1,10 +1,3 @@
"""
DAY ZERO TEMPLATE - OrdersAgent
Esqueleto mínimo já compatível com ConversationSummaryMemory.
Substitua o prompt e a regra de negócio conforme o seu agente.
"""
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.runtime import AgentRuntimeMixin
@@ -35,12 +28,67 @@ class OrdersAgent(AgentRuntimeMixin):
self.summary_memory = summary_memory
async def run(self, state):
# OPCIONAL: habilite quando seu agente precisar de MCP/RAG.
tool_context = []
rag_context = None
rag_metadata = {}
await self._emit_ic(
"IC.ORDERS_AGENT_STARTED",
state,
{"business_component": "pedidos"},
component="agent.orders.start",
)
# Prepara a memória resumida antes do prompt.
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.ORDERS_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.orders.mcp",
)
state["mcp_results"] = tool_context
clarification_message = self.transaction_clarification_message(state)
if clarification_message:
return {
"answer": f"[{self.__class__.__name__}] {clarification_message}",
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
confirmation_message = self.transaction_confirmation_message(state)
if confirmation_message:
result = {
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
"next_state": state.get("next_state"),
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
return result
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="OrdersAgent")
if direct_answer:
return {
"answer": direct_answer,
"next_state": state.get("next_state") or "ACTIVE",
"mcp_results": tool_context,
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
**self.transaction_state_patch(state),
}
rag_context, rag_metadata = await self._retrieve_rag_context(state)
if rag_metadata.get("enabled"):
await self._emit_ic(
"IC.ORDERS_RAG_CONTEXT_RETRIEVED",
state,
{
"document_count": rag_metadata.get("document_count"),
"graph_neighbors": rag_metadata.get("graph_neighbors"),
"latency_ms": rag_metadata.get("latency_ms"),
},
component="agent.orders.rag",
)
# Prepara ConversationSummaryMemory antes de montar o prompt.
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
await self.prepare_memory_context(state)
messages = self.build_messages(
@@ -55,13 +103,27 @@ class OrdersAgent(AgentRuntimeMixin):
)
answer = await self._invoke_llm_cached(state, "OrdersAgent", messages)
return {
"answer": answer,
"next_state": "DAY_ZERO_ACTIVE",
result = {
"answer": f"[OrdersAgent] {answer}",
"next_state": "ORDER_ACTIVE",
"mcp_results": tool_context,
"rag": rag_metadata,
"memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state),
}
await self._emit_ic(
"IC.ORDERS_AGENT_COMPLETED",
state,
{
"answer_chars": len(result.get("answer") or ""),
"has_mcp_results": bool(tool_context),
"rag_enabled": bool(rag_metadata.get("enabled")),
"memory_context": state.get("memory_context_metadata"),
},
component="agent.orders.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

View File

@@ -1,10 +1,3 @@
"""
DAY ZERO TEMPLATE - ProductAgent
Esqueleto mínimo já compatível com ConversationSummaryMemory.
Substitua o prompt e a regra de negócio conforme o seu agente.
"""
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.runtime import AgentRuntimeMixin
@@ -35,12 +28,67 @@ class ProductAgent(AgentRuntimeMixin):
self.summary_memory = summary_memory
async def run(self, state):
# OPCIONAL: habilite quando seu agente precisar de MCP/RAG.
tool_context = []
rag_context = None
rag_metadata = {}
await self._emit_ic(
"IC.PRODUCT_AGENT_STARTED",
state,
{"business_component": "produtos"},
component="agent.product.start",
)
# Prepara a memória resumida antes do prompt.
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.PRODUCT_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.product.mcp",
)
state["mcp_results"] = tool_context
clarification_message = self.transaction_clarification_message(state)
if clarification_message:
return {
"answer": f"[{self.__class__.__name__}] {clarification_message}",
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
confirmation_message = self.transaction_confirmation_message(state)
if confirmation_message:
result = {
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
"next_state": state.get("next_state"),
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
return result
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="ProductAgent")
if direct_answer:
return {
"answer": direct_answer,
"next_state": state.get("next_state") or "ACTIVE",
"mcp_results": tool_context,
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
**self.transaction_state_patch(state),
}
rag_context, rag_metadata = await self._retrieve_rag_context(state)
if rag_metadata.get("enabled"):
await self._emit_ic(
"IC.PRODUCT_RAG_CONTEXT_RETRIEVED",
state,
{
"document_count": rag_metadata.get("document_count"),
"graph_neighbors": rag_metadata.get("graph_neighbors"),
"latency_ms": rag_metadata.get("latency_ms"),
},
component="agent.product.rag",
)
# Prepara ConversationSummaryMemory antes de montar o prompt.
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
await self.prepare_memory_context(state)
messages = self.build_messages(
@@ -55,13 +103,27 @@ class ProductAgent(AgentRuntimeMixin):
)
answer = await self._invoke_llm_cached(state, "ProductAgent", messages)
return {
"answer": answer,
"next_state": "DAY_ZERO_ACTIVE",
result = {
"answer": f"[ProductAgent] {answer}",
"next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context,
"rag": rag_metadata,
"memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state),
}
await self._emit_ic(
"IC.PRODUCT_AGENT_COMPLETED",
state,
{
"answer_chars": len(result.get("answer") or ""),
"has_mcp_results": bool(tool_context),
"rag_enabled": bool(rag_metadata.get("enabled")),
"memory_context": state.get("memory_context_metadata"),
},
component="agent.product.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

View File

@@ -1,10 +1,3 @@
"""
DAY ZERO TEMPLATE - SupportAgent
Esqueleto mínimo já compatível com ConversationSummaryMemory.
Substitua o prompt e a regra de negócio conforme o seu agente.
"""
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.runtime import AgentRuntimeMixin
@@ -35,12 +28,67 @@ class SupportAgent(AgentRuntimeMixin):
self.summary_memory = summary_memory
async def run(self, state):
# OPCIONAL: habilite quando seu agente precisar de MCP/RAG.
tool_context = []
rag_context = None
rag_metadata = {}
await self._emit_ic(
"IC.SUPPORT_AGENT_STARTED",
state,
{"business_component": "suporte"},
component="agent.support.start",
)
# Prepara a memória resumida antes do prompt.
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.SUPPORT_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.support.mcp",
)
state["mcp_results"] = tool_context
clarification_message = self.transaction_clarification_message(state)
if clarification_message:
return {
"answer": f"[{self.__class__.__name__}] {clarification_message}",
"next_state": state.get("next_state") or "COLLECTING_PARAMETERS",
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
confirmation_message = self.transaction_confirmation_message(state)
if confirmation_message:
result = {
"answer": f"[{self.__class__.__name__}] {confirmation_message}",
"next_state": state.get("next_state"),
"mcp_results": tool_context,
**self.transaction_state_patch(state),
}
return result
direct_answer = self.build_direct_mcp_answer(state, tool_context, agent_label="SupportAgent")
if direct_answer:
return {
"answer": direct_answer,
"next_state": state.get("next_state") or "ACTIVE",
"mcp_results": tool_context,
"rag": {"enabled": False, "skipped": True, "reason": "direct_mcp_answer"},
**self.transaction_state_patch(state),
}
rag_context, rag_metadata = await self._retrieve_rag_context(state)
if rag_metadata.get("enabled"):
await self._emit_ic(
"IC.SUPPORT_RAG_CONTEXT_RETRIEVED",
state,
{
"document_count": rag_metadata.get("document_count"),
"graph_neighbors": rag_metadata.get("graph_neighbors"),
"latency_ms": rag_metadata.get("latency_ms"),
},
component="agent.support.rag",
)
# Prepara ConversationSummaryMemory antes de montar o prompt.
# O build_messages() do framework injeta resumo + últimas mensagens quando habilitado.
await self.prepare_memory_context(state)
messages = self.build_messages(
@@ -55,13 +103,27 @@ class SupportAgent(AgentRuntimeMixin):
)
answer = await self._invoke_llm_cached(state, "SupportAgent", messages)
return {
"answer": answer,
"next_state": "DAY_ZERO_ACTIVE",
result = {
"answer": f"[SupportAgent] {answer}",
"next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context,
"rag": rag_metadata,
"memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state),
}
await self._emit_ic(
"IC.SUPPORT_AGENT_COMPLETED",
state,
{
"answer_chars": len(result.get("answer") or ""),
"has_mcp_results": bool(tool_context),
"rag_enabled": bool(rag_metadata.get("enabled")),
"memory_context": state.get("memory_context_metadata"),
},
component="agent.support.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

View File

@@ -23,9 +23,22 @@ class AgentState(TypedDict, total=False):
domain: str
mcp_tools: list[str]
mcp_results: list[dict[str, Any]]
available_mcp_tools: list[str]
selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any]
transaction_status: str
confirmation_required: bool
confirmation_received: bool
tool_policy_result: dict[str, Any]
missing_parameters: list[str]
supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]]
active_agent: str
route_bypassed: bool
continuity_signal: dict[str, Any]
session_control: str
session_ended: bool
human_handoff_requested: bool
blocked: bool
supervisor_action: str
supervisor_guidance: str

View File

@@ -145,6 +145,8 @@ class AgentWorkflow:
builder.add_node("orders_agent", self._node("orders_agent", self.orders_agent))
builder.add_node("support_agent", self._node("support_agent", self.support_agent))
builder.add_node("handoff", self._node("handoff", self.handoff))
builder.add_node("human_handoff", self._node("human_handoff", self.human_handoff))
builder.add_node("end_session", self._node("end_session", self.end_session))
builder.add_node("supervisor_agent", self._node("supervisor_agent", self.supervisor_agent))
builder.add_node("output_supervisor", self._node("output_supervisor", self.output_supervisor))
builder.add_node("output_guardrails", self._node("output_guardrails", self.output_guardrails))
@@ -168,6 +170,8 @@ class AgentWorkflow:
"orders_agent": "orders_agent",
"support_agent": "support_agent",
"handoff": "handoff",
"human_handoff": "human_handoff",
"end_session": "end_session",
"supervisor_agent": "supervisor_agent",
},
)
@@ -176,6 +180,8 @@ class AgentWorkflow:
builder.add_edge("orders_agent", "output_supervisor")
builder.add_edge("support_agent", "output_supervisor")
builder.add_edge("handoff", "output_supervisor")
builder.add_edge("human_handoff", "output_supervisor")
builder.add_edge("end_session", "output_supervisor")
builder.add_edge("supervisor_agent", "output_supervisor")
builder.add_edge("output_supervisor", "output_guardrails")
builder.add_edge("output_guardrails", "judge")
@@ -190,6 +196,24 @@ class AgentWorkflow:
return "blocked" if state.get("blocked") else "continue"
async def input_guardrails(self, state):
if state.get("session_ended") is True:
answer = str(getattr(
self.settings,
"SESSION_ALREADY_ENDED_MESSAGE",
"Este atendimento já foi encerrado. Inicie uma nova sessão para continuar.",
))
await self.telemetry.event(
"session.message.rejected_after_end",
{"session_id": state.get("conversation_key") or state.get("session_id")},
)
return {
"answer": answer,
"final_answer": answer,
"blocked": True,
"session_control": "END_SESSION",
"session_ended": True,
"next_state": "SESSION_ENDED",
}
async with self.telemetry.span(
"workflow.input_guardrails",
session_id=state.get("conversation_key") or state.get("session_id"),
@@ -326,6 +350,17 @@ class AgentWorkflow:
"domain": decision.domain,
"mcp_tools": decision.mcp_tools,
"next_state": decision.next_state,
"active_agent": decision.agent,
"route_bypassed": decision.method == "continuity",
"session_control": (decision.metadata or {}).get("session_control", ""),
"human_handoff_requested": (decision.metadata or {}).get("session_control") == "HUMAN_HANDOFF",
"session_ended": (decision.metadata or {}).get("session_control") == "END_SESSION",
"continuity_signal": {
"decision": (decision.metadata or {}).get("continuity_decision"),
"confidence": decision.confidence if decision.method == "continuity" else None,
"reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {},
}
async def billing_agent(self, state):
@@ -415,6 +450,48 @@ class AgentWorkflow:
)
return {"answer": answer}
async def human_handoff(self, state):
session_id = state.get("conversation_key") or state.get("session_id")
async with self.telemetry.span("workflow.human_handoff", session_id=session_id):
answer = str(getattr(self.settings, "HUMAN_HANDOFF_MESSAGE", "Vou encaminhar seu atendimento para uma pessoa."))
await self.telemetry.event(
"session.human_handoff.requested",
{
"session_id": session_id,
"tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"),
"reason": (state.get("route_decision") or {}).get("reason"),
},
)
return {
"answer": answer,
"session_control": "HUMAN_HANDOFF",
"human_handoff_requested": True,
"session_ended": False,
"next_state": "HUMAN_HANDOFF_REQUESTED",
}
async def end_session(self, state):
session_id = state.get("conversation_key") or state.get("session_id")
async with self.telemetry.span("workflow.end_session", session_id=session_id):
answer = str(getattr(self.settings, "END_SESSION_MESSAGE", "Atendimento encerrado. Obrigado pelo contato."))
await self.telemetry.event(
"session.end.requested",
{
"session_id": session_id,
"tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"),
"reason": (state.get("route_decision") or {}).get("reason"),
},
)
return {
"answer": answer,
"session_control": "END_SESSION",
"session_ended": True,
"human_handoff_requested": False,
"next_state": "SESSION_ENDED",
}
async def output_supervisor(self, state):
"""Valida a resposta candidata com o OutputSupervisor corporativo.
@@ -576,8 +653,34 @@ class AgentWorkflow:
session_id=state.get("conversation_key") or state.get("session_id"),
input={"question": state.get("user_text"), "answer": state.get("final_answer")},
):
judge_context = dict(state.get("context", {}) or {})
judge_context["mcp_results"] = state.get("mcp_results", [])
judge_context["evidence"] = state.get("mcp_results", []) or judge_context.get("evidence")
judge_context["route"] = state.get("route")
judge_context["intent"] = state.get("intent")
# Judge sampling must see the finalized transaction state. These
# fields are populated by the agent/tool runtime before this node.
for key in (
"transaction_status",
"confirmation_required",
"confirmation_received",
"tool_policy_result",
"selected_tool_call",
"pending_tool_call",
):
judge_context[key] = state.get(key)
judge_context["transactional_tools"] = [
result.get("tool_name")
for result in state.get("mcp_results", [])
if isinstance(result, dict)
and (
(result.get("metadata") or {}).get("operation_type") == "transactional"
or result.get("awaiting_confirmation")
or result.get("transaction_status")
)
]
results = await self.judges.evaluate_all(
state["user_text"], state["final_answer"], state.get("context", {})
state["user_text"], state["final_answer"], judge_context
)
for _result in results:
await self.judge_telemetry.evaluated(_result)

View File

@@ -1,20 +1,18 @@
enabled: true
fail_closed: true
profile: judge
judges:
- name: response_quality
enabled: true
threshold: 0.7
- name: groundedness
enabled: true
threshold: 0.6
- name: sentiment
enabled: true
fail_on_negative: false
- name: tone
enabled: true
fail_closed: true
- name: response_quality
enabled: true
threshold: 0.7
- name: groundedness
enabled: true
threshold: 0.6
- name: sentiment
enabled: true
fail_on_negative: false
- name: tone
enabled: true
fail_closed: true
sample_rate: 0.25
always_run_for_transactional: true

View File

@@ -1,8 +1,3 @@
# ============================================================================
# DAY ZERO
# Este arquivo foi copiado do agent_template_backend original.
# Ajuste os exemplos abaixo para o domínio do seu novo agente.
# ============================================================================
mcp_parameter_mapping:
defaults:
use_mock: true
@@ -13,6 +8,16 @@ mcp_parameter_mapping:
contract_key: invoice_id
interaction_key: ura_call_id
session_key: session_id
extract:
mes_referencia:
from: message
type: int
strategy: month_name_pt
description: 'Extrair mês citado na mensagem. janeiro=1, fevereiro=2, março=3,
abril=4, maio=5, junho=6, julho=7, agosto=8, setembro=9, outubro=10, novembro=11,
dezembro=12.
'
consultar_pagamentos:
map:
customer_key: msisdn
@@ -31,21 +36,57 @@ mcp_parameter_mapping:
consultar_pedido:
map:
customer_key: customer_id
contract_key: order_id
session_key: session_id
extract:
order_id:
from: message
type: string
strategy: hybrid
description: Extraia somente o identificador do pedido informado explicitamente
pelo usuário. Retorne null quando não houver identificador de pedido na
mensagem.
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
group: 1
consultar_entrega:
map:
contract_key: order_id
session_key: session_id
extract:
order_id:
from: message
type: string
strategy: hybrid
description: Extraia somente o identificador do pedido informado explicitamente
pelo usuário. Retorne null quando não houver identificador de pedido na
mensagem.
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
group: 1
solicitar_troca:
map:
contract_key: order_id
session_key: session_id
defaults:
reason: Solicitação aberta pelo atendimento conversacional.
extract:
order_id:
from: message
type: string
strategy: hybrid
description: Extraia somente o identificador do pedido informado explicitamente
pelo usuário. Retorne null quando não houver identificador de pedido na
mensagem.
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
group: 1
solicitar_devolucao:
map:
contract_key: order_id
session_key: session_id
defaults:
reason: Solicitação aberta pelo atendimento conversacional.
extract:
order_id:
from: message
type: string
strategy: hybrid
description: Extraia somente o identificador do pedido informado explicitamente
pelo usuário. Retorne null quando não houver identificador de pedido na
mensagem.
pattern: (?i)\\b(?:pedido|order)\\s*[:#-]?\\s*([A-Z0-9-]+)\\b
group: 1

View File

@@ -25,6 +25,18 @@ state_policies:
- state: WAITING_SUPPORT_CONFIRMATION
agent: support_agent
description: Mantém confirmações no fluxo de suporte retail.
- state: COLLECTING_BILLING_PARAMETERS
agent: billing_agent
description: Mantém a coleta de parâmetros no fluxo de faturamento.
- state: COLLECTING_PRODUCT_PARAMETERS
agent: product_agent
description: Mantém a coleta de parâmetros no fluxo de produtos e serviços.
- state: COLLECTING_ORDER_PARAMETERS
agent: orders_agent
description: Mantém a coleta de parâmetros no fluxo de pedidos.
- state: COLLECTING_SUPPORT_PARAMETERS
agent: support_agent
description: Mantém a coleta de parâmetros no fluxo transacional de suporte retail.
intents:
- name: billing_invoice_explanation
@@ -60,7 +72,6 @@ intents:
- listar_servicos
keywords:
- plano
- produto
- serviço
- pacote
- internet
@@ -99,12 +110,15 @@ intents:
domain: retail
agent: support_agent
description: Suporte, troca, devolução, garantia e problema com produto.
priority: 40
priority: 25
mcp_tools:
- consultar_pedido
- solicitar_troca
- solicitar_devolucao
keywords:
- solicitar devolução
- devolver pedido
- solicitar troca
- troca
- devolução
- devolver

View File

@@ -0,0 +1,21 @@
version: 1
# Arquivo opcional da aplicação. A ausência mantém o comportamento dos
# templates anteriores e as políticas legadas declaradas em tools.yaml.
defaults:
operation_type: read_only
require_confirmation: false
tool_policies:
solicitar_troca:
operation_type: transactional
require_confirmation: true
solicitar_devolucao:
operation_type: transactional
require_confirmation: true
# Exemplo para uma operação real que só pode executar após confirmação:
# cancelar_servico:
# operation_type: transactional
# require_confirmation: true

View File

@@ -1,8 +1,3 @@
# ============================================================================
# DAY ZERO
# Este arquivo foi copiado do agent_template_backend original.
# Ajuste os exemplos abaixo para o domínio do seu novo agente.
# ============================================================================
tools:
consultar_fatura:
description: Consulta dados resumidos de fatura por msisdn/invoice_id.
@@ -11,14 +6,19 @@ tools:
args_schema:
msisdn: string
invoice_id: string
selection_keywords:
- fatura
- conta
- boleto
consultar_pagamentos:
description: Consulta histórico de pagamentos do cliente.
mcp_server: telecom
enabled: true
args_schema:
msisdn: string
selection_keywords:
- pagamento
- pagamentos
consultar_plano:
description: Consulta plano ativo e atributos comerciais.
mcp_server: telecom
@@ -26,14 +26,18 @@ tools:
args_schema:
msisdn: string
asset_id: string
selection_keywords:
- plano
listar_servicos:
description: Lista serviços ativos e adicionais VAS.
mcp_server: telecom
enabled: true
args_schema:
msisdn: string
selection_keywords:
- serviços
- servicos
- vas
consultar_pedido:
description: Consulta pedido de varejo por order_id/customer_id.
mcp_server: retail
@@ -41,32 +45,57 @@ tools:
args_schema:
order_id: string
customer_id: string
selection_keywords:
- consultar pedido
- status do pedido
- pedido
consultar_entrega:
description: Consulta entrega e rastreamento do pedido.
mcp_server: retail
enabled: true
args_schema:
order_id: string
selection_keywords:
- entrega
- rastreio
- rastreamento
- transportadora
- previsão
solicitar_troca:
description: Simula abertura de solicitação de troca.
mcp_server: retail
enabled: true
tool_type: action
requires: [order_id, reason]
confirmation_required: false
requires:
- order_id
- reason
confirmation_required: true
args_schema:
order_id: string
reason: string
selection_keywords:
- solicitar troca
- trocar
- troca
- defeito
- quebrado
solicitar_devolucao:
description: Simula abertura de solicitação de devolução.
mcp_server: retail
enabled: true
tool_type: action
requires: [order_id, reason]
confirmation_required: false
requires:
- order_id
- reason
confirmation_required: true
args_schema:
order_id: string
reason: string
selection_keywords:
- solicitar devolução
- solicitar devolucao
- devolver pedido
- devolver
- devolução
- devolucao
- arrependimento

View File

@@ -0,0 +1,13 @@
# Exemplos implementados no template Day Zero
O Day Zero preserva seu conteúdo simplificado, mas possui o mesmo conjunto transversal do template completo:
- route stickiness semântica com o perfil `route_continuity`;
- decisões `CONTINUE`, `ROUTE`, `HUMAN_HANDOFF` e `END_SESSION`;
- nós globais `human_handoff` e `end_session`;
- persistência de `active_agent`, `route_bypassed`, `continuity_signal` e controle de sessão;
- rejeição de novas mensagens depois de `session_ended=true`;
- políticas MCP `read_only` e `transactional` no backend;
- exemplo `solicitar_devolucao` com `require_confirmation: true`.
Para confirmar a transação, envie `confirmed: true` ou `confirmation: true` como booleano. Substitua os agentes e ferramentas de exemplo sem remover os controles transversais.

View File

@@ -0,0 +1,80 @@
profiles:
default:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
max_tokens: 2048
supervisor:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 700
route_continuity:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 80
timeout_seconds: 5
router:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 500
guardrail:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 600
grl:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 700
judge:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 800
rag_rewriter:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 300
rag_compressor:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 1200
rag_generation:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.1
max_tokens: 1800
summary_memory:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.1
max_tokens: 1200
noc:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 700
billing_agent:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
product_agent:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
backoffice_agent:
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