Projeto do Agent Contas ORACLE

This commit is contained in:
2026-08-19 09:35:50 -03:00
commit 950a2bcd33
1366 changed files with 177217 additions and 0 deletions

10
app/agents/README.md Normal file
View File

@@ -0,0 +1,10 @@
# Agentes do domínio Contas
Estes agentes são especializações finas do `AgentRuntimeMixin` do `agent_framework_oci`.
- `FaturasAgent`: faturas e explicação de cobrança.
- `VasAgent`: serviços VAS, histórico e serviços estratégicos.
- `ContestacaoAgent`: ações transacionais de cancelamento e contestação.
- `SuporteContasAgent`: protocolos, acompanhamento e encerramento.
Confirmação, clarificação, memória, RAG, guardrails, judges, MCP routing e telemetria não são reimplementados aqui; são capacidades do framework.

View File

@@ -0,0 +1,10 @@
from __future__ import annotations
from functools import lru_cache
from pathlib import Path
import yaml
@lru_cache(maxsize=16)
def load_domain_prompt(name: str) -> str:
path = Path(__file__).resolve().parents[2] / "config" / "prompts" / f"{name}.yaml"
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
return str(data.get("system") or "").strip()

View File

@@ -0,0 +1,132 @@
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.contas_prompting import load_domain_prompt
from app.agents.runtime import AgentRuntimeMixin
class ContestacaoAgent(AgentRuntimeMixin):
name = "contestacao_agent"
def __init__(
self,
llm,
telemetry=None,
tool_router=None,
rag_service=None,
cache=None,
settings=None,
observer=None,
memory=None,
summary_memory=None,
guardrail_pipeline=None,
):
self.llm = llm
self.telemetry = telemetry
self.tool_router = tool_router
self.rag_service = rag_service
self.cache = cache
self.settings = settings
self.observer = observer
self.memory = memory
self.summary_memory = summary_memory
self.guardrail_pipeline = guardrail_pipeline
async def run(self, state):
await self._emit_ic(
"IC.CONTESTACAO_AGENT_STARTED",
state,
{"business_component": "contestacao"},
component="agent.contestacao.start",
)
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.CONTESTACAO_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.contestacao.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="ContestacaoAgent")
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.CONTESTACAO_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.contestacao.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(
state,
system_prompt=apply_agent_profile_prompt(
state,
load_domain_prompt("contestation"),
),
mcp_results=tool_context,
rag_context=rag_context,
rag_metadata=rag_metadata,
)
answer = await self._invoke_llm_cached(state, "ContestacaoAgent", messages)
result = {
"answer": f"[ContestacaoAgent] {answer}",
"next_state": "CONTESTACAO_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.CONTESTACAO_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.contestacao.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

132
app/agents/faturas_agent.py Normal file
View File

@@ -0,0 +1,132 @@
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.contas_prompting import load_domain_prompt
from app.agents.runtime import AgentRuntimeMixin
class FaturasAgent(AgentRuntimeMixin):
name = "faturas_agent"
def __init__(
self,
llm,
telemetry=None,
tool_router=None,
rag_service=None,
cache=None,
settings=None,
observer=None,
memory=None,
summary_memory=None,
guardrail_pipeline=None,
):
self.llm = llm
self.telemetry = telemetry
self.tool_router = tool_router
self.rag_service = rag_service
self.cache = cache
self.settings = settings
self.observer = observer
self.memory = memory
self.summary_memory = summary_memory
self.guardrail_pipeline = guardrail_pipeline
async def run(self, state):
await self._emit_ic(
"IC.FATURAS_AGENT_STARTED",
state,
{"business_component": "faturas"},
component="agent.faturas.start",
)
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.FATURAS_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.faturas.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="FaturasAgent")
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.FATURAS_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.faturas.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(
state,
system_prompt=apply_agent_profile_prompt(
state,
load_domain_prompt("billing"),
),
mcp_results=tool_context,
rag_context=rag_context,
rag_metadata=rag_metadata,
)
answer = await self._invoke_llm_cached(state, "FaturasAgent", messages)
result = {
"answer": f"[FaturasAgent] {answer}",
"next_state": "FATURAS_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.FATURAS_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.faturas.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

15
app/agents/prompting.py Normal file
View File

@@ -0,0 +1,15 @@
from __future__ import annotations
def apply_agent_profile_prompt(state: dict, default_prompt: str) -> str:
"""Adiciona o prefixo de prompt configurado para o agent_template selecionado.
Cada agent_id pode definir metadata.system_prefix em config/agents.yaml. Isso
mantém prompts isolados sem duplicar o código dos agentes especializados.
"""
profile = state.get("agent_profile") or (state.get("context") or {}).get("agent_profile") or {}
metadata = profile.get("metadata") or {}
prefix = (metadata.get("system_prefix") or "").strip()
if not prefix:
return default_prompt
return f"{prefix}\n\n{default_prompt}"

7
app/agents/runtime.py Normal file
View File

@@ -0,0 +1,7 @@
from __future__ import annotations
# Compatibilidade local do template/backend.
# A implementação oficial agora fica no framework para evitar duplicação entre agentes.
from agent_framework.runtime import AgentRuntimeMixin, MessageBuilder, RuntimeContext
__all__ = ["AgentRuntimeMixin", "MessageBuilder", "RuntimeContext"]

View File

@@ -0,0 +1,138 @@
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.contas_prompting import load_domain_prompt
from app.agents.runtime import AgentRuntimeMixin
from app.domain.contas.informational_context import infer_informational_context
class SuporteContasAgent(AgentRuntimeMixin):
name = "suporte_contas_agent"
def __init__(
self,
llm,
telemetry=None,
tool_router=None,
rag_service=None,
cache=None,
settings=None,
observer=None,
memory=None,
summary_memory=None,
guardrail_pipeline=None,
):
self.llm = llm
self.telemetry = telemetry
self.tool_router = tool_router
self.rag_service = rag_service
self.cache = cache
self.settings = settings
self.observer = observer
self.memory = memory
self.summary_memory = summary_memory
self.guardrail_pipeline = guardrail_pipeline
async def run(self, state):
await self._emit_ic(
"IC.SUPORTE_CONTAS_AGENT_STARTED",
state,
{"business_component": "suporte"},
component="agent.suporte_contas.start",
)
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.SUPORTE_CONTAS_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.suporte_contas.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="SuporteContasAgent")
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)
informational_patch = infer_informational_context(
str(rag_metadata.get("query") or state.get("sanitized_input") or state.get("user_text") or ""),
state.get("invoice_detail"),
) if rag_metadata.get("enabled") else {}
if rag_metadata.get("enabled"):
await self._emit_ic(
"IC.SUPORTE_CONTAS_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.suporte_contas.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(
state,
system_prompt=apply_agent_profile_prompt(
state,
load_domain_prompt("support"),
),
mcp_results=tool_context,
rag_context=rag_context,
rag_metadata=rag_metadata,
)
answer = await self._invoke_llm_cached(state, "SuporteContasAgent", messages)
result = {
"answer": f"[SuporteContasAgent] {answer}",
"next_state": "SUPORTE_CONTAS_ACTIVE",
"mcp_results": tool_context,
"rag": rag_metadata,
"memory_context_metadata": state.get("memory_context_metadata"),
**informational_patch,
**self.transaction_state_patch(state),
}
await self._emit_ic(
"IC.SUPORTE_CONTAS_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.suporte_contas.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)

138
app/agents/vas_agent.py Normal file
View File

@@ -0,0 +1,138 @@
from app.agents.prompting import apply_agent_profile_prompt
from app.agents.contas_prompting import load_domain_prompt
from app.agents.runtime import AgentRuntimeMixin
from app.domain.contas.informational_context import infer_informational_context
class VasAgent(AgentRuntimeMixin):
name = "vas_agent"
def __init__(
self,
llm,
telemetry=None,
tool_router=None,
rag_service=None,
cache=None,
settings=None,
observer=None,
memory=None,
summary_memory=None,
guardrail_pipeline=None,
):
self.llm = llm
self.telemetry = telemetry
self.tool_router = tool_router
self.rag_service = rag_service
self.cache = cache
self.settings = settings
self.observer = observer
self.memory = memory
self.summary_memory = summary_memory
self.guardrail_pipeline = guardrail_pipeline
async def run(self, state):
await self._emit_ic(
"IC.VAS_AGENT_STARTED",
state,
{"business_component": "vas"},
component="agent.vas.start",
)
tool_context = await self._collect_tool_context(state)
if tool_context:
await self._emit_ic(
"IC.VAS_MCP_CONTEXT_COLLECTED",
state,
{"tool_result_count": len(tool_context)},
component="agent.vas.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="VasAgent")
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)
informational_patch = infer_informational_context(
str(rag_metadata.get("query") or state.get("sanitized_input") or state.get("user_text") or ""),
state.get("invoice_detail"),
) if rag_metadata.get("enabled") else {}
if rag_metadata.get("enabled"):
await self._emit_ic(
"IC.VAS_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.vas.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(
state,
system_prompt=apply_agent_profile_prompt(
state,
load_domain_prompt("vas"),
),
mcp_results=tool_context,
rag_context=rag_context,
rag_metadata=rag_metadata,
)
answer = await self._invoke_llm_cached(state, "VasAgent", messages)
result = {
"answer": f"[VasAgent] {answer}",
"next_state": "VAS_ACTIVE",
"mcp_results": tool_context,
"rag": rag_metadata,
"memory_context_metadata": state.get("memory_context_metadata"),
**informational_patch,
**self.transaction_state_patch(state),
}
await self._emit_ic(
"IC.VAS_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.vas.completed",
)
return result
async def _collect_tool_context(self, state):
return await self._collect_mcp_context(state)