diff --git a/.env b/.env
index da9f8f2..6323b3d 100644
--- a/.env
+++ b/.env
@@ -189,18 +189,18 @@ BACKOFFICE_MCP_BACKEND_TYPE=tim_clients
BACKOFFICE_MCP_FAIL_OPEN_ON_BACKEND_ERROR=true
# TIM/develop original - preencha no .env real, nunca versionar segredos.
-PMID_API_HOST=https://pmidfqa.internal.timbrasil.com.br/access/v1/info
+PMID_API_HOST=http://localhost:8011/access/v1/info
PMID_API_BASIC_TOKEN=Basic dGlteDpAckp1SkpAcFJPUiM=
PMID_API_TIMEOUT=10
PMID_API_CLIENT_ID=AIAGENTCR
-SIEBEL_API_HOST=https://pmidfqa.internal.timbrasil.com.br/customers/v1/backOfficeSRopening
+SIEBEL_API_HOST=http://localhost:8011/customers/v1/backOfficeSRopening
SIEBEL_API_TIMEOUT=10
SIEBEL_API_USERNAME=aiagentcr
SIEBEL_API_PASSWORD=AiAgentCR#FQA#
SIEBEL_API_CLIENT_ID=AIAGENTCR
-SIEBEL_STATUS_API_HOST=https://pmidfqa.internal.timbrasil.com.br
+SIEBEL_STATUS_API_HOST=https://locahost:8011
SIEBEL_STATUS_API_ROUTE=/interactions/v1/statusServiceRequest
-SIEBEL_PREPAGO_STATUS_API_HOST=https://pmidfqa.internal.timbrasil.com.br
+SIEBEL_PREPAGO_STATUS_API_HOST=https://localhost:8011
SIEBEL_PREPAGO_STATUS_API_ROUTE=/customers/v1/serviceRequest
VERIFY_SSL=false
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README_FRAMEWORK_NATIVE_MIGRATION_NOTES.md b/README_FRAMEWORK_NATIVE_MIGRATION_NOTES.md
new file mode 100644
index 0000000..6d63ae1
--- /dev/null
+++ b/README_FRAMEWORK_NATIVE_MIGRATION_NOTES.md
@@ -0,0 +1,41 @@
+# Backoffice framework-native migration notes
+
+This package reduces direct legacy dependencies in the active runtime.
+
+Main changes:
+
+- Domain nodes call framework services through `src.compat.framework_services`.
+- IMDB enrichment uses MCP tool `consultar_imdb_cliente` first.
+- Speech enrichment uses MCP tool `consultar_speech_analytics` first.
+- TAIS KB enrichment uses MCP tool `consultar_tais_kb` first.
+- Direct TIM clients are disabled by default and only available behind `BACKOFFICE_ALLOW_LEGACY_CLIENTS=true` for parity tests.
+- Legacy model aliases such as `bo_gptoss20b_dev` are mapped to the framework model, defaulting to `openai.gpt-4.1`.
+- Non-serializable progress producers are no longer stored inside LangGraph state, reducing checkpoint hash corruption risk.
+- MCP server defaults to mock mode for local development.
+
+Recommended local execution:
+
+```bash
+# Terminal 1: MCP mock server
+uvicorn mcp_servers.backoffice_mcp_server.main:app --host 0.0.0.0 --port 8010 --reload
+
+# Terminal 2: backend
+uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
+```
+
+Optional standalone IMDB mock, only if you intentionally enable legacy direct clients:
+
+```bash
+uvicorn mock_servers.mock_imdb_server:app --host 0.0.0.0 --port 8011 --reload
+```
+
+For a fully framework-native local test, keep:
+
+```env
+BACKOFFICE_ALLOW_LEGACY_CLIENTS=false
+BACKOFFICE_MCP_USE_MOCK=true
+BACKOFFICE_MCP_BACKEND_TYPE=mock
+CLASSIFICATION_LLM_MODEL=openai.gpt-4.1
+CLASSIFICATION_LARGE_LLM_MODEL=openai.gpt-4.1
+TAIS_KB_LLM_MODEL=openai.gpt-4.1
+```
diff --git a/app/main.py b/app/main.py
index 2d4ca57..9649c0b 100644
--- a/app/main.py
+++ b/app/main.py
@@ -16,8 +16,10 @@ from agent_framework.config.settings import settings
from agent_framework.analytics.factory import create_analytics_publisher
from agent_framework.observability.observer import AgentObserver
from src.compat.framework_observer import configure as configure_global_observer
+from src.compat.framework_services import configure as configure_framework_services
from agent_framework.llm.providers import create_llm
from agent_framework.memory.message_history import create_memory
+from agent_framework.memory.summary_memory import create_conversation_summary_memory
from agent_framework.mcp.tool_router import create_mcp_tool_router
from agent_framework.models.identity import AgentIdentity
from agent_framework.identity import IdentityResolver, BusinessContext
@@ -49,10 +51,11 @@ telemetry = Telemetry(settings)
usage_repository = create_usage_repository(settings)
llm = create_llm(settings, telemetry=telemetry, usage_repository=usage_repository)
memory = create_memory(settings)
+summary_memory = create_conversation_summary_memory(settings, message_history=memory, llm=llm, telemetry=telemetry)
sessions = create_session_repository(settings)
checkpoints = create_checkpoint_repository(settings)
cache = create_cache(settings, telemetry=telemetry)
-gateway = ChannelGateway()
+gateway = ChannelGateway(input_mode=settings.FRAMEWORK_CHANNEL_INPUT_MODE)
analytics = create_analytics_publisher(settings)
observer = AgentObserver(analytics=analytics)
configure_global_observer({
@@ -61,10 +64,11 @@ configure_global_observer({
"topic_path": getattr(settings, "GCP_PUBSUB_TOPIC_PATH", None) or getattr(settings, "AGENT_PUBSUB_TOPIC", None),
})
tool_router = create_mcp_tool_router(settings, telemetry=telemetry)
+configure_framework_services(tool_router=tool_router)
identity_resolver = IdentityResolver.from_yaml(settings.IDENTITY_CONFIG_PATH)
agent_profiles = AgentProfileRegistry(settings)
sse_hub = SSEHub(settings, telemetry=telemetry)
-workflow = AgentWorkflow(llm, memory, telemetry, analytics, settings, observer=observer, tool_router=tool_router)
+workflow = AgentWorkflow(llm, memory, telemetry, analytics, settings, observer=observer, tool_router=tool_router, summary_memory=summary_memory)
backoffice_runtime = BackofficeNativeRuntime(settings=settings, telemetry=telemetry, analytics=analytics, observer=observer)
logger.info("LLM provider carregado: %s", llm.__class__.__name__)
@@ -501,6 +505,9 @@ async def shutdown():
# Elas chamam o BackofficeNativeRuntime, que compila os workflows com o motor
# do framework e aplica guardrails, judges, supervisor, checkpoint e telemetry.
+from src.api.schemas.anatel_schemas import TicketRequestEvent
+from src.api.schemas.anatel_response_emulator_schemas import EmulatorGenerateRequest, EmulatorFinalizeRequest
+
import asyncio
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
@@ -531,13 +538,15 @@ async def startup_backoffice_native_domain():
if getattr(original_settings, "ENABLE_OCI_STREAMING", False):
from src.infrastructure.streaming.producer import OciProducer
app.state.oci_producer = OciProducer(original_settings.OCI_RESPONSE_STREAM_OCID)
- logger.info("OCI producer de domínio inicializado; consumer legado não é iniciado")
+ configure_framework_services(progress_producer=app.state.oci_producer)
+ logger.info("OCI producer de domínio inicializado via framework services; consumer legado não é iniciado")
else:
try:
from src.infrastructure.streaming.debug_producer import LocalDebugProducer
if getattr(original_settings, "DEBUG", False):
app.state.oci_producer = LocalDebugProducer()
- logger.info("LocalDebugProducer de domínio ativo")
+ configure_framework_services(progress_producer=app.state.oci_producer)
+ logger.info("LocalDebugProducer de domínio ativo via framework services")
except Exception:
pass
except Exception:
@@ -621,37 +630,34 @@ async def _run_native_checklist(event, request: Request):
@app.post("/agent/process-ticket", status_code=status.HTTP_200_OK)
-async def native_process_ticket(request: Request, event: "TicketRequestEvent"):
+async def native_process_ticket(request: Request, event: TicketRequestEvent):
return await _run_native_checklist(event, request)
@app.post("/agent/execute", status_code=status.HTTP_200_OK)
-async def native_agent_execute(request: Request, event: "TicketRequestEvent"):
+async def native_agent_execute(request: Request, event: TicketRequestEvent):
return await _run_native_checklist(event, request)
@app.post("/agent/process-and-stream", status_code=status.HTTP_200_OK)
-async def native_process_and_stream(request: Request, event: "TicketRequestEvent"):
+async def native_process_and_stream(request: Request, event: TicketRequestEvent):
return await _run_native_checklist(event, request)
@app.post("/agent/search-tais-kb", status_code=status.HTTP_200_OK)
async def native_search_tais_kb(body: dict):
- """Adapter REST para TAIS KB usando o service/cliente de domínio, não grafo legado."""
- try:
- from src.components.clients.tais_kb_client import TaisKbClient
- query = body.get("query") or body.get("text") or body.get("complaint_description") or ""
- client = TaisKbClient()
- if hasattr(client, "search"):
- result = await client.search(query=query, **{k: v for k, v in body.items() if k not in {"query", "text", "complaint_description"}})
- elif hasattr(client, "query"):
- result = await client.query(query)
- else:
- raise AttributeError("TAIS KB client has no search/query method")
- return {"framework_native": True, "result": result}
- except Exception as exc:
- await telemetry.event("backoffice.tais_kb.failed", {"error": str(exc)}, kind="tool")
- return {"framework_native": True, "result": [], "error": str(exc)}
+ """Adapter REST framework-native para TAIS KB via MCP Router."""
+ from src.compat.framework_services import call_mcp_tool
+ query = body.get("query") or body.get("text") or body.get("complaint_description") or ""
+ result = await call_mcp_tool(
+ "consultar_tais_kb",
+ {"query": query, "protocol_id": body.get("protocol_id") or body.get("complaintProtocol"), "customer_key": body.get("customer_key") or body.get("cpfCnpj")},
+ business_context={"customer_key": body.get("customer_key") or body.get("cpfCnpj"), "interaction_key": body.get("protocol_id") or body.get("complaintProtocol")},
+ original_context=body,
+ )
+ if not result.get("ok"):
+ await telemetry.event("backoffice.tais_kb.failed", {"error": result.get("error")}, kind="tool")
+ return {"framework_native": True, "result": result}
async def _run_native_emulator(request: Request, event):
@@ -687,7 +693,7 @@ async def _run_native_emulator(request: Request, event):
@app.post("/case/{transaction_id}/response-emulator/generate", status_code=status.HTTP_200_OK)
-async def native_response_emulator_generate(transaction_id: str, request: Request, body: "EmulatorGenerateRequest"):
+async def native_response_emulator_generate(transaction_id: str, request: Request, body: EmulatorGenerateRequest):
from src.api.schemas.anatel_response_emulator_schemas import ResponseEmulatorRequestEvent, OperatorFeedback
if body.transactionId != transaction_id:
raise HTTPException(status_code=400, detail="transactionId path/body mismatch")
@@ -711,7 +717,7 @@ async def native_response_emulator_generate(transaction_id: str, request: Reques
@app.post("/case/{transaction_id}/response-emulator/finalize", status_code=status.HTTP_200_OK)
-async def native_response_emulator_finalize(transaction_id: str, request: Request, body: "EmulatorFinalizeRequest"):
+async def native_response_emulator_finalize(transaction_id: str, request: Request, body: EmulatorFinalizeRequest):
from src.api.schemas.anatel_response_emulator_schemas import ResponseEmulatorRequestEvent
if body.transactionId != transaction_id:
raise HTTPException(status_code=400, detail="transactionId path/body mismatch")
@@ -820,7 +826,3 @@ async def debug_backoffice_parity():
},
}
-# Late imports only for FastAPI annotation resolution. They are schemas, not
-# workflow motors.
-from src.api.schemas.anatel_schemas import TicketRequestEvent
-from src.api.schemas.anatel_response_emulator_schemas import EmulatorGenerateRequest, EmulatorFinalizeRequest
diff --git a/app/workflows/agent_graph.py b/app/workflows/agent_graph.py
index cb33974..e4b597d 100644
--- a/app/workflows/agent_graph.py
+++ b/app/workflows/agent_graph.py
@@ -20,6 +20,7 @@ from app.agents.support_agent import SupportAgent
from app.agents.backoffice_agent import BackofficeAgent
from app.state import AgentState
from agent_framework.rag.rag_service import RagService
+from agent_framework.rag.embedding_provider import create_embedding_provider
from agent_framework.cache.cache import create_cache
@@ -85,7 +86,7 @@ class AgentWorkflow:
Em ambos os modos, memória/checkpoint/session usam tenant_id:agent_id:session_id.
"""
- def __init__(self, llm, memory, telemetry, analytics, settings, observer: AgentObserver | None = None, tool_router=None):
+ def __init__(self, llm, memory, telemetry, analytics, settings, observer: AgentObserver | None = None, tool_router=None, summary_memory=None):
self.llm = llm
self.memory = memory
self.telemetry = telemetry
@@ -93,7 +94,7 @@ class AgentWorkflow:
self.observer = observer or AgentObserver(analytics=analytics)
self.settings = settings
self.tool_router = tool_router
- self.tool_router = tool_router
+ self.summary_memory = summary_memory
self.guardrails = GuardrailPipeline(
observer=self.observer,
enable_parallel=bool(getattr(settings, "ENABLE_PARALLEL_GUARDRAILS", True)),
@@ -113,6 +114,7 @@ class AgentWorkflow:
self.judge_telemetry = JudgeTelemetry(telemetry)
self.langgraph_telemetry = LangGraphDeepTelemetry(telemetry)
self.cache = create_cache(settings)
+ self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer}
diff --git a/app/workflows/backoffice_native_runtime.py b/app/workflows/backoffice_native_runtime.py
index 912b57d..26c65e0 100644
--- a/app/workflows/backoffice_native_runtime.py
+++ b/app/workflows/backoffice_native_runtime.py
@@ -382,8 +382,9 @@ class BackofficeNativeRuntime:
return state
@staticmethod
- def _after_input_guardrails(state: AgentState) -> str:
- return "blocked" if state.get("error", {}).get("type") == "InputGuardrailBlocked" else "continue"
+ def _after_input_guardrails(state):
+ error = state.get("error") or {}
+ return "blocked" if error.get("type") == "InputGuardrailBlocked" else "continue"
async def _framework_output_supervisor(self, state: AgentState) -> AgentState:
await self._safe_emit_grl(
@@ -544,7 +545,8 @@ class BackofficeNativeRuntime:
"guardrails_config_path": self.guardrails_config.get("_config_path"),
"active_input_rails": [r.get("code") for r in self.guardrails_config.get("input", []) if r.get("enabled", True)],
"active_output_rails": [r.get("code") for r in self.guardrails_config.get("output", []) if r.get("enabled", True)],
- "_oci_producer": getattr(app_state, "oci_producer", None) if app_state is not None else None,
+ # Progress producer is kept in src.compat.framework_services, not in state,
+ # so checkpoints remain JSON-stable and integrity hashes do not change.
"_framework_ics_bridge_index": 0,
})
try:
diff --git a/debug_events.jsonl b/debug_events.jsonl
new file mode 100644
index 0000000..bc43d93
--- /dev/null
+++ b/debug_events.jsonl
@@ -0,0 +1,88 @@
+{"ts": "2026-06-13T11:32:45.674888Z", "key": "man-9d2cd2df", "payload": {"transactionId": "man-9d2cd2df", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:32:45] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:32:45.674868Z"}}}
+{"ts": "2026-06-13T11:32:45.680553Z", "key": "man-9d2cd2df", "payload": {"transactionId": "man-9d2cd2df", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:32:45] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:32:45.680538Z"}}}
+{"ts": "2026-06-13T11:32:45.698929Z", "key": "man-9d2cd2df", "payload": {"transactionId": "man-9d2cd2df", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:32:45] Chamado recebido, iniciando processamento.\n[13/06/2026 08:32:45] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:32:45.698905Z"}}}
+{"ts": "2026-06-13T11:33:17.366395Z", "key": "man-9d2cd2df", "payload": {"transactionId": "man-9d2cd2df", "processing": {"status": "processing", "current_step": "imdb_enrichment_failed", "action": "await_response", "note": "[13/06/2026 08:32:45] Chamado recebido, iniciando processamento.\n[13/06/2026 08:32:45] Enriquecendo chamado no IMDB....\n[13/06/2026 08:33:17] Falha no enriquecimento IMDB.", "timestamp": "2026-06-13T11:33:17.366360Z"}}}
+{"ts": "2026-06-13T11:41:02.192755Z", "key": "man-fc7947cd", "payload": {"transactionId": "man-fc7947cd", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:41:02] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:41:02.192708Z"}}}
+{"ts": "2026-06-13T11:41:02.199314Z", "key": "man-fc7947cd", "payload": {"transactionId": "man-fc7947cd", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:41:02] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:41:02.199292Z"}}}
+{"ts": "2026-06-13T11:41:02.215260Z", "key": "man-fc7947cd", "payload": {"transactionId": "man-fc7947cd", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:41:02] Chamado recebido, iniciando processamento.\n[13/06/2026 08:41:02] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:41:02.215238Z"}}}
+{"ts": "2026-06-13T11:41:04.138471Z", "key": "man-fc7947cd", "payload": {"transactionId": "man-fc7947cd", "processing": {"status": "processing", "current_step": "imdb_enrichment_failed", "action": "await_response", "note": "[13/06/2026 08:41:02] Chamado recebido, iniciando processamento.\n[13/06/2026 08:41:02] Enriquecendo chamado no IMDB....\n[13/06/2026 08:41:04] Falha no enriquecimento IMDB.", "timestamp": "2026-06-13T11:41:04.138439Z"}}}
+{"ts": "2026-06-13T11:44:27.657570Z", "key": "man-f0a2d929", "payload": {"transactionId": "man-f0a2d929", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:44:27] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:44:27.657555Z"}}}
+{"ts": "2026-06-13T11:44:27.663735Z", "key": "man-f0a2d929", "payload": {"transactionId": "man-f0a2d929", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:44:27] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:44:27.663715Z"}}}
+{"ts": "2026-06-13T11:44:27.679132Z", "key": "man-f0a2d929", "payload": {"transactionId": "man-f0a2d929", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:44:27] Chamado recebido, iniciando processamento.\n[13/06/2026 08:44:27] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:44:27.679114Z"}}}
+{"ts": "2026-06-13T11:44:27.740484Z", "key": "man-f0a2d929", "payload": {"transactionId": "man-f0a2d929", "processing": {"status": "processing", "current_step": "imdb_enrichment_failed", "action": "await_response", "note": "[13/06/2026 08:44:27] Chamado recebido, iniciando processamento.\n[13/06/2026 08:44:27] Enriquecendo chamado no IMDB....\n[13/06/2026 08:44:27] Falha no enriquecimento IMDB.", "timestamp": "2026-06-13T11:44:27.740453Z"}}}
+{"ts": "2026-06-13T11:50:35.859525Z", "key": "man-ec33447d", "payload": {"transactionId": "man-ec33447d", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:50:35] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:50:35.859509Z"}}}
+{"ts": "2026-06-13T11:50:35.864610Z", "key": "man-ec33447d", "payload": {"transactionId": "man-ec33447d", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:50:35] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:50:35.864586Z"}}}
+{"ts": "2026-06-13T11:50:35.881261Z", "key": "man-ec33447d", "payload": {"transactionId": "man-ec33447d", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:50:35] Chamado recebido, iniciando processamento.\n[13/06/2026 08:50:35] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:50:35.881244Z"}}}
+{"ts": "2026-06-13T11:50:35.994273Z", "key": "man-ec33447d", "payload": {"transactionId": "man-ec33447d", "processing": {"status": "processing", "current_step": "imdb_enrichment_failed", "action": "await_response", "note": "[13/06/2026 08:50:35] Chamado recebido, iniciando processamento.\n[13/06/2026 08:50:35] Enriquecendo chamado no IMDB....\n[13/06/2026 08:50:35] Falha no enriquecimento IMDB.", "timestamp": "2026-06-13T11:50:35.994243Z"}}}
+{"ts": "2026-06-13T11:53:46.367591Z", "key": "man-d96fdcf2", "payload": {"transactionId": "man-d96fdcf2", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:53:46] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:53:46.367575Z"}}}
+{"ts": "2026-06-13T11:53:46.372800Z", "key": "man-d96fdcf2", "payload": {"transactionId": "man-d96fdcf2", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:53:46] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:53:46.372781Z"}}}
+{"ts": "2026-06-13T11:53:46.388442Z", "key": "man-d96fdcf2", "payload": {"transactionId": "man-d96fdcf2", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:53:46] Chamado recebido, iniciando processamento.\n[13/06/2026 08:53:46] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:53:46.388424Z"}}}
+{"ts": "2026-06-13T11:53:46.490280Z", "key": "man-d96fdcf2", "payload": {"transactionId": "man-d96fdcf2", "processing": {"status": "processing", "current_step": "imdb_enrichment_failed", "action": "await_response", "note": "[13/06/2026 08:53:46] Chamado recebido, iniciando processamento.\n[13/06/2026 08:53:46] Enriquecendo chamado no IMDB....\n[13/06/2026 08:53:46] Falha no enriquecimento IMDB.", "timestamp": "2026-06-13T11:53:46.490243Z"}}}
+{"ts": "2026-06-13T11:55:33.542425Z", "key": "man-f1ce4dbd", "payload": {"transactionId": "man-f1ce4dbd", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:55:33] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:55:33.542411Z"}}}
+{"ts": "2026-06-13T11:55:33.548005Z", "key": "man-f1ce4dbd", "payload": {"transactionId": "man-f1ce4dbd", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:55:33] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:55:33.547992Z"}}}
+{"ts": "2026-06-13T11:55:33.562185Z", "key": "man-f1ce4dbd", "payload": {"transactionId": "man-f1ce4dbd", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:55:33] Chamado recebido, iniciando processamento.\n[13/06/2026 08:55:33] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:55:33.562168Z"}}}
+{"ts": "2026-06-13T11:55:33.631504Z", "key": "man-f1ce4dbd", "payload": {"transactionId": "man-f1ce4dbd", "processing": {"status": "processing", "current_step": "imdb_enrichment_failed", "action": "await_response", "note": "[13/06/2026 08:55:33] Chamado recebido, iniciando processamento.\n[13/06/2026 08:55:33] Enriquecendo chamado no IMDB....\n[13/06/2026 08:55:33] Falha no enriquecimento IMDB.", "timestamp": "2026-06-13T11:55:33.631473Z"}}}
+{"ts": "2026-06-13T11:56:29.575175Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:56:29.575145Z"}}}
+{"ts": "2026-06-13T11:56:29.580507Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T11:56:29.580489Z"}}}
+{"ts": "2026-06-13T11:56:29.595401Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T11:56:29.595384Z"}}}
+{"ts": "2026-06-13T11:56:29.610519Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "identity_verification", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....\n[13/06/2026 08:56:29] Realizando verificação de CPF divergente....", "timestamp": "2026-06-13T11:56:29.610501Z"}}}
+{"ts": "2026-06-13T11:56:29.616230Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "speech_enrichment", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....\n[13/06/2026 08:56:29] Realizando verificação de CPF divergente....\n[13/06/2026 08:56:29] Enriquecendo chamado via Speech Analytics.", "timestamp": "2026-06-13T11:56:29.616214Z"}}}
+{"ts": "2026-06-13T11:56:40.080696Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "speech_enrichment_unavailable", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....\n[13/06/2026 08:56:29] Realizando verificação de CPF divergente....\n[13/06/2026 08:56:29] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 08:56:40] Enriquecimento via Speech Analytics - indisponível no momento.", "timestamp": "2026-06-13T11:56:40.080657Z"}}}
+{"ts": "2026-06-13T11:56:40.087207Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....\n[13/06/2026 08:56:29] Realizando verificação de CPF divergente....\n[13/06/2026 08:56:29] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 08:56:40] Enriquecimento via Speech Analytics - indisponível no momento.\n[13/06/2026 08:56:40] Consultando procedimentos na base de conhecimento....", "timestamp": "2026-06-13T11:56:40.087185Z"}}}
+{"ts": "2026-06-13T11:56:40.419935Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment_unavailable", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....\n[13/06/2026 08:56:29] Realizando verificação de CPF divergente....\n[13/06/2026 08:56:29] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 08:56:40] Enriquecimento via Speech Analytics - indisponível no momento.\n[13/06/2026 08:56:40] Consultando procedimentos na base de conhecimento....\n[13/06/2026 08:56:40] Base de conhecimento consultada - indisponível no momento.", "timestamp": "2026-06-13T11:56:40.419905Z"}}}
+{"ts": "2026-06-13T11:56:40.426948Z", "key": "man-7ab9f6d3", "payload": {"transactionId": "man-7ab9f6d3", "processing": {"status": "processing", "current_step": "canceling_analysis", "action": "await_response", "note": "[13/06/2026 08:56:29] Chamado recebido, iniciando processamento.\n[13/06/2026 08:56:29] Enriquecendo chamado no IMDB....\n[13/06/2026 08:56:29] Realizando verificação de CPF divergente....\n[13/06/2026 08:56:29] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 08:56:40] Enriquecimento via Speech Analytics - indisponível no momento.\n[13/06/2026 08:56:40] Consultando procedimentos na base de conhecimento....\n[13/06/2026 08:56:40] Base de conhecimento consultada - indisponível no momento.\n[13/06/2026 08:56:40] Realizando análise de cancelamento....", "timestamp": "2026-06-13T11:56:40.426919Z"}}}
+{"ts": "2026-06-13T12:08:54.689233Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:08:54.689212Z"}}}
+{"ts": "2026-06-13T12:08:54.694436Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:08:54.694423Z"}}}
+{"ts": "2026-06-13T12:08:54.708866Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T12:08:54.708850Z"}}}
+{"ts": "2026-06-13T12:08:57.796719Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "identity_verification", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....", "timestamp": "2026-06-13T12:08:57.796696Z"}}}
+{"ts": "2026-06-13T12:08:57.801813Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "speech_enrichment", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.", "timestamp": "2026-06-13T12:08:57.801789Z"}}}
+{"ts": "2026-06-13T12:08:57.843004Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:08:57] Consultando procedimentos na base de conhecimento....", "timestamp": "2026-06-13T12:08:57.842983Z"}}}
+{"ts": "2026-06-13T12:08:58.853369Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "canceling_analysis", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:08:57] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:08:58] Realizando análise de cancelamento....", "timestamp": "2026-06-13T12:08:58.853349Z"}}}
+{"ts": "2026-06-13T12:09:01.892018Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "tim_complaint_analysis", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:08:57] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:08:58] Realizando análise de cancelamento....\n[13/06/2026 09:09:01] Realizando análise de reencaminhamento....", "timestamp": "2026-06-13T12:09:01.891993Z"}}}
+{"ts": "2026-06-13T12:09:04.913265Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:08:57] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:08:58] Realizando análise de cancelamento....\n[13/06/2026 09:09:01] Realizando análise de reencaminhamento....\n[13/06/2026 09:09:04] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:09:04.913231Z"}}}
+{"ts": "2026-06-13T12:09:04.918375Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:08:57] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:08:58] Realizando análise de cancelamento....\n[13/06/2026 09:09:01] Realizando análise de reencaminhamento....\n[13/06/2026 09:09:04] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:09:04] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:09:04.918353Z"}}}
+{"ts": "2026-06-13T12:09:08.130779Z", "key": "man-4e2a87ec", "payload": {"transactionId": "man-4e2a87ec", "processing": {"status": "processing", "current_step": "siebel_sr_opening", "action": "await_response", "note": "[13/06/2026 09:08:54] Chamado recebido, iniciando processamento.\n[13/06/2026 09:08:54] Enriquecendo chamado no IMDB....\n[13/06/2026 09:08:57] Realizando verificação de CPF divergente....\n[13/06/2026 09:08:57] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:08:57] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:08:58] Realizando análise de cancelamento....\n[13/06/2026 09:09:01] Realizando análise de reencaminhamento....\n[13/06/2026 09:09:04] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:09:04] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:09:08] Abrindo chamado no Siebel....", "timestamp": "2026-06-13T12:09:08.130753Z"}}}
+{"ts": "2026-06-13T12:13:11.097807Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:13:11.097787Z"}}}
+{"ts": "2026-06-13T12:13:11.103424Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:13:11.103408Z"}}}
+{"ts": "2026-06-13T12:13:11.118197Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T12:13:11.118177Z"}}}
+{"ts": "2026-06-13T12:13:11.153234Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "identity_verification", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....", "timestamp": "2026-06-13T12:13:11.153215Z"}}}
+{"ts": "2026-06-13T12:13:11.157888Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "speech_enrichment", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.", "timestamp": "2026-06-13T12:13:11.157870Z"}}}
+{"ts": "2026-06-13T12:13:11.187313Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:13:11] Consultando procedimentos na base de conhecimento....", "timestamp": "2026-06-13T12:13:11.187290Z"}}}
+{"ts": "2026-06-13T12:13:11.202410Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "canceling_analysis", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:13:11] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:13:11] Realizando análise de cancelamento....", "timestamp": "2026-06-13T12:13:11.202392Z"}}}
+{"ts": "2026-06-13T12:13:14.804084Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "tim_complaint_analysis", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:13:11] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:13:11] Realizando análise de cancelamento....\n[13/06/2026 09:13:14] Realizando análise de reencaminhamento....", "timestamp": "2026-06-13T12:13:14.804060Z"}}}
+{"ts": "2026-06-13T12:13:17.682902Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:13:11] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:13:11] Realizando análise de cancelamento....\n[13/06/2026 09:13:14] Realizando análise de reencaminhamento....\n[13/06/2026 09:13:17] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:13:17.682881Z"}}}
+{"ts": "2026-06-13T12:13:17.687706Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:13:11] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:13:11] Realizando análise de cancelamento....\n[13/06/2026 09:13:14] Realizando análise de reencaminhamento....\n[13/06/2026 09:13:17] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:13:17] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:13:17.687695Z"}}}
+{"ts": "2026-06-13T12:13:21.951853Z", "key": "man-fc277693", "payload": {"transactionId": "man-fc277693", "processing": {"status": "processing", "current_step": "siebel_sr_opening", "action": "await_response", "note": "[13/06/2026 09:13:11] Chamado recebido, iniciando processamento.\n[13/06/2026 09:13:11] Enriquecendo chamado no IMDB....\n[13/06/2026 09:13:11] Realizando verificação de CPF divergente....\n[13/06/2026 09:13:11] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:13:11] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:13:11] Realizando análise de cancelamento....\n[13/06/2026 09:13:14] Realizando análise de reencaminhamento....\n[13/06/2026 09:13:17] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:13:17] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:13:21] Abrindo chamado no Siebel....", "timestamp": "2026-06-13T12:13:21.951832Z"}}}
+{"ts": "2026-06-13T12:20:43.763052Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:20:43.763028Z"}}}
+{"ts": "2026-06-13T12:20:43.769794Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:20:43.769769Z"}}}
+{"ts": "2026-06-13T12:20:43.786827Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T12:20:43.786804Z"}}}
+{"ts": "2026-06-13T12:20:43.823957Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "identity_verification", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....", "timestamp": "2026-06-13T12:20:43.823930Z"}}}
+{"ts": "2026-06-13T12:20:43.831082Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "speech_enrichment", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.", "timestamp": "2026-06-13T12:20:43.831058Z"}}}
+{"ts": "2026-06-13T12:20:43.858859Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:20:43] Consultando procedimentos na base de conhecimento....", "timestamp": "2026-06-13T12:20:43.858837Z"}}}
+{"ts": "2026-06-13T12:20:43.875902Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "canceling_analysis", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:20:43] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:20:43] Realizando análise de cancelamento....", "timestamp": "2026-06-13T12:20:43.875883Z"}}}
+{"ts": "2026-06-13T12:20:46.284964Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "tim_complaint_analysis", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:20:43] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:20:43] Realizando análise de cancelamento....\n[13/06/2026 09:20:46] Realizando análise de reencaminhamento....", "timestamp": "2026-06-13T12:20:46.284944Z"}}}
+{"ts": "2026-06-13T12:20:49.551574Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:20:43] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:20:43] Realizando análise de cancelamento....\n[13/06/2026 09:20:46] Realizando análise de reencaminhamento....\n[13/06/2026 09:20:49] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:20:49.551546Z"}}}
+{"ts": "2026-06-13T12:20:49.556486Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:20:43] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:20:43] Realizando análise de cancelamento....\n[13/06/2026 09:20:46] Realizando análise de reencaminhamento....\n[13/06/2026 09:20:49] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:20:49] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:20:49.556471Z"}}}
+{"ts": "2026-06-13T12:20:52.715592Z", "key": "man-2bdc878d", "payload": {"transactionId": "man-2bdc878d", "processing": {"status": "processing", "current_step": "siebel_sr_opening", "action": "await_response", "note": "[13/06/2026 09:20:43] Chamado recebido, iniciando processamento.\n[13/06/2026 09:20:43] Enriquecendo chamado no IMDB....\n[13/06/2026 09:20:43] Realizando verificação de CPF divergente....\n[13/06/2026 09:20:43] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:20:43] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:20:43] Realizando análise de cancelamento....\n[13/06/2026 09:20:46] Realizando análise de reencaminhamento....\n[13/06/2026 09:20:49] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:20:49] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:20:52] Abrindo chamado no Siebel....", "timestamp": "2026-06-13T12:20:52.715571Z"}}}
+{"ts": "2026-06-13T12:25:24.860002Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:25:24.859983Z"}}}
+{"ts": "2026-06-13T12:25:24.866078Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:25:24.866061Z"}}}
+{"ts": "2026-06-13T12:25:24.884790Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T12:25:24.884763Z"}}}
+{"ts": "2026-06-13T12:25:25.756583Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "identity_verification", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....", "timestamp": "2026-06-13T12:25:25.756562Z"}}}
+{"ts": "2026-06-13T12:25:25.761120Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "speech_enrichment", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.", "timestamp": "2026-06-13T12:25:25.761103Z"}}}
+{"ts": "2026-06-13T12:25:25.784248Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:25:25] Consultando procedimentos na base de conhecimento....", "timestamp": "2026-06-13T12:25:25.784224Z"}}}
+{"ts": "2026-06-13T12:25:26.120273Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "canceling_analysis", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:25:25] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:25:26] Realizando análise de cancelamento....", "timestamp": "2026-06-13T12:25:26.120253Z"}}}
+{"ts": "2026-06-13T12:25:29.759053Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "tim_complaint_analysis", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:25:25] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:25:26] Realizando análise de cancelamento....\n[13/06/2026 09:25:29] Realizando análise de reencaminhamento....", "timestamp": "2026-06-13T12:25:29.759028Z"}}}
+{"ts": "2026-06-13T12:25:33.162287Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:25:25] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:25:26] Realizando análise de cancelamento....\n[13/06/2026 09:25:29] Realizando análise de reencaminhamento....\n[13/06/2026 09:25:33] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:25:33.162261Z"}}}
+{"ts": "2026-06-13T12:25:33.168352Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:25:25] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:25:26] Realizando análise de cancelamento....\n[13/06/2026 09:25:29] Realizando análise de reencaminhamento....\n[13/06/2026 09:25:33] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:25:33] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:25:33.168322Z"}}}
+{"ts": "2026-06-13T12:25:36.521378Z", "key": "man-a2834a06", "payload": {"transactionId": "man-a2834a06", "processing": {"status": "processing", "current_step": "siebel_sr_opening", "action": "await_response", "note": "[13/06/2026 09:25:24] Chamado recebido, iniciando processamento.\n[13/06/2026 09:25:24] Enriquecendo chamado no IMDB....\n[13/06/2026 09:25:25] Realizando verificação de CPF divergente....\n[13/06/2026 09:25:25] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:25:25] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:25:26] Realizando análise de cancelamento....\n[13/06/2026 09:25:29] Realizando análise de reencaminhamento....\n[13/06/2026 09:25:33] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:25:33] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:25:36] Abrindo chamado no Siebel....", "timestamp": "2026-06-13T12:25:36.521356Z"}}}
+{"ts": "2026-06-13T12:40:35.443891Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "fetching_ticket", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:40:35.443864Z"}}}
+{"ts": "2026-06-13T12:40:35.450268Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "validation", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.", "timestamp": "2026-06-13T12:40:35.450249Z"}}}
+{"ts": "2026-06-13T12:40:35.466267Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "imdb_enrichment", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....", "timestamp": "2026-06-13T12:40:35.466250Z"}}}
+{"ts": "2026-06-13T12:40:36.228740Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "identity_verification", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....", "timestamp": "2026-06-13T12:40:36.228714Z"}}}
+{"ts": "2026-06-13T12:40:36.239433Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "speech_enrichment", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.", "timestamp": "2026-06-13T12:40:36.238738Z"}}}
+{"ts": "2026-06-13T12:40:36.265009Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "knowledge_base_enrichment", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:40:36] Consultando procedimentos na base de conhecimento....", "timestamp": "2026-06-13T12:40:36.264990Z"}}}
+{"ts": "2026-06-13T12:40:36.535711Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "canceling_analysis", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:40:36] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:40:36] Realizando análise de cancelamento....", "timestamp": "2026-06-13T12:40:36.535691Z"}}}
+{"ts": "2026-06-13T12:40:39.572330Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "tim_complaint_analysis", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:40:36] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:40:36] Realizando análise de cancelamento....\n[13/06/2026 09:40:39] Realizando análise de reencaminhamento....", "timestamp": "2026-06-13T12:40:39.572311Z"}}}
+{"ts": "2026-06-13T12:40:43.190711Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:40:36] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:40:36] Realizando análise de cancelamento....\n[13/06/2026 09:40:39] Realizando análise de reencaminhamento....\n[13/06/2026 09:40:43] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:40:43.190690Z"}}}
+{"ts": "2026-06-13T12:40:43.201384Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "reclassification_analysis", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:40:36] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:40:36] Realizando análise de cancelamento....\n[13/06/2026 09:40:39] Realizando análise de reencaminhamento....\n[13/06/2026 09:40:43] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:40:43] Realizando análise de reclassificação do chamado....", "timestamp": "2026-06-13T12:40:43.201371Z"}}}
+{"ts": "2026-06-13T12:40:47.059892Z", "key": "man-23f416a8", "payload": {"transactionId": "man-23f416a8", "processing": {"status": "processing", "current_step": "siebel_sr_opening", "action": "await_response", "note": "[13/06/2026 09:40:35] Chamado recebido, iniciando processamento.\n[13/06/2026 09:40:35] Enriquecendo chamado no IMDB....\n[13/06/2026 09:40:36] Realizando verificação de CPF divergente....\n[13/06/2026 09:40:36] Enriquecendo chamado via Speech Analytics.\n[13/06/2026 09:40:36] Consultando procedimentos na base de conhecimento....\n[13/06/2026 09:40:36] Realizando análise de cancelamento....\n[13/06/2026 09:40:39] Realizando análise de reencaminhamento....\n[13/06/2026 09:40:43] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:40:43] Realizando análise de reclassificação do chamado....\n[13/06/2026 09:40:47] Abrindo chamado no Siebel....", "timestamp": "2026-06-13T12:40:47.059872Z"}}}
diff --git a/legacy_backend/mock_imdb_server.py b/legacy_backend/mock_imdb_server.py
new file mode 100644
index 0000000..de0b9dc
--- /dev/null
+++ b/legacy_backend/mock_imdb_server.py
@@ -0,0 +1,62 @@
+# /compass_backoffice/legacy_backend/
+# uvicorn mock_imdb_server:app --port 8011
+
+from fastapi import FastAPI, Request
+
+app = FastAPI(title="Mock IMDB TIM")
+
+@app.get("/access/v1/info")
+async def get_access_info(request: Request):
+ return {
+ "cpfCnpj": request.query_params.get("cpfCnpj") or "06252533106",
+ "msisdn": request.query_params.get("msisdn") or "62981152324",
+ "plan": {
+ "Type": "POS_PAGO",
+ "name": "TIM Black Família 50GB"
+ },
+ "customer": {
+ "segment": "consumer",
+ "status": "active",
+ "contumazCustomer": False,
+ "odcCustomer": False
+ },
+ "contracts": [
+ {
+ "contractId": "mock-contract-001",
+ "status": "active",
+ "product": "Celular Pós-pago"
+ }
+ ]
+ }
+
+@app.get("/access/v1/info/{msisdn}")
+async def get_access_info_by_msisdn(msisdn: str):
+ return {
+ "msisdn": msisdn,
+ "cpfCnpj": "06252533106",
+ "plan": {
+ "Type": "POS_PAGO",
+ "name": "TIM Black Família 50GB"
+ },
+ "customer": {
+ "segment": "consumer",
+ "status": "active",
+ "contumazCustomer": False,
+ "odcCustomer": False
+ }
+ }
+
+@app.post("/customers/v1/backOfficeSRopening")
+async def backoffice_sr_opening(payload: dict):
+ return {
+ "status": "success",
+ "crmProtocol": "SR-MOCK-123456",
+ "fieldsToUpdate": {
+ "status": "Aberto",
+ "reason": "Mock Siebel SR Opening"
+ },
+ "case_response": {
+ "message": "Chamado Siebel aberto com sucesso no mock"
+ },
+ "transitions": []
+ }
\ No newline at end of file
diff --git a/mcp_servers/backoffice_mcp_server/settings.py b/mcp_servers/backoffice_mcp_server/settings.py
index 8f4f2b9..c60fb74 100644
--- a/mcp_servers/backoffice_mcp_server/settings.py
+++ b/mcp_servers/backoffice_mcp_server/settings.py
@@ -35,9 +35,9 @@ class BackofficeMCPSettings(BaseSettings):
port: int = Field(default=8010, validation_alias=AliasChoices("BACKOFFICE_MCP_PORT", "MCP_PORT"))
log_level: str = Field(default="info", validation_alias=AliasChoices("BACKOFFICE_MCP_LOG_LEVEL", "LOG_LEVEL"))
- use_mock: bool = Field(default=False, validation_alias=AliasChoices("BACKOFFICE_MCP_USE_MOCK", "MCP_USE_MOCK", "USE_MOCK"))
+ use_mock: bool = Field(default=True, validation_alias=AliasChoices("BACKOFFICE_MCP_USE_MOCK", "MCP_USE_MOCK", "USE_MOCK"))
backend_type: Literal["mock", "tim_clients", "rest", "oracle"] = Field(
- default="tim_clients",
+ default="mock",
validation_alias=AliasChoices("BACKOFFICE_MCP_BACKEND_TYPE", "MCP_BACKEND_TYPE"),
)
diff --git a/mock_servers/mock_imdb_server.py b/mock_servers/mock_imdb_server.py
new file mode 100644
index 0000000..7f6ab28
--- /dev/null
+++ b/mock_servers/mock_imdb_server.py
@@ -0,0 +1,47 @@
+from __future__ import annotations
+
+from fastapi import FastAPI, Request
+
+app = FastAPI(title="Mock IMDB TIM", version="1.0.0")
+
+
+def _payload(msisdn: str | None = None, cpf_cnpj: str | None = None):
+ return {
+ "msisdn": msisdn or "62981152324",
+ "cpfCnpj": cpf_cnpj or "06252533106",
+ "socialSecNo": cpf_cnpj or "06252533106",
+ "plan": {
+ "Type": "POS_PAGO",
+ "name": "TIM Black Família 50GB",
+ "commercialName": "TIM Black Família 50GB",
+ },
+ "statusType": "ACTIVE",
+ "statusDescription": "Cliente ativo",
+ "customer": {
+ "segment": "consumer",
+ "status": "active",
+ "contumazCustomer": False,
+ "odcCustomer": False,
+ },
+ }
+
+
+@app.get("/health")
+async def health():
+ return {"status": "ok", "service": "mock_imdb"}
+
+
+@app.get("/access/v1/info")
+async def get_access_info(request: Request):
+ return _payload(
+ msisdn=request.query_params.get("msisdn"),
+ cpf_cnpj=request.query_params.get("cpfCnpj") or request.query_params.get("cpf_cnpj"),
+ )
+
+
+@app.get("/access/v1/info/{msisdn}")
+async def get_access_info_by_msisdn(msisdn: str, request: Request):
+ return _payload(
+ msisdn=msisdn,
+ cpf_cnpj=request.query_params.get("cpfCnpj") or request.query_params.get("cpf_cnpj"),
+ )
diff --git a/src/agent/nodes/imdb_enrichment_node.py b/src/agent/nodes/imdb_enrichment_node.py
index 293e181..ff6cef9 100644
--- a/src/agent/nodes/imdb_enrichment_node.py
+++ b/src/agent/nodes/imdb_enrichment_node.py
@@ -2,13 +2,12 @@ import logging
from src.agent.state.agent_state import AgentState, set_error, update_state_metadata
from src.agent.state.steps import GraphStep
from src.agent.state.step_helpers import set_current_step
-from src.components.clients.imdb_client import ImdbClient
from src.api.schemas.imdb_schemas import ImdbRequest
from src.core.config import settings
-from src.components.clients.exceptions.imdb_exceptions import ImdbClientError
from src.utils.observer import trace_node
from src.utils.ics_collector import build_ic_payload
from src.compat.framework_observer import event
+from src.compat.framework_services import call_mcp_tool
import json
logger = logging.getLogger(__name__)
@@ -18,8 +17,6 @@ async def imdb_enrich_ticket(state: AgentState) -> AgentState:
"""
Fetches access data in the IMDB API to enrich call data.
"""
- client = ImdbClient()
-
await set_current_step(state, GraphStep.IMDB_ENRICHMENT)
session_id = state.get("session_id", "")
@@ -74,11 +71,72 @@ async def imdb_enrich_ticket(state: AgentState) -> AgentState:
await set_current_step(state, GraphStep.IMDB_ENRICHMENT_FAILED)
return set_error(state, "ValidationError", "Missing required fields to fetch access data in the PMID API", step=GraphStep.IMDB_ENRICHMENT_FAILED)
- # Validate parameters via DTO before forwarding to the client
+ # Framework-native path: use MCP tool instead of the legacy direct IMDB client.
+ logger.info("Fetching IMDB through framework MCP tool consultar_imdb_cliente")
+ mcp_response = await call_mcp_tool(
+ "consultar_imdb_cliente",
+ {
+ "customer_key": msisdn,
+ "contract_key": cpf_cnpj,
+ "session_key": session_id,
+ "document": cpf_cnpj,
+ },
+ business_context={"customer_key": msisdn, "contract_key": cpf_cnpj, "session_key": session_id},
+ original_context=context,
+ )
+ mcp_ok = bool(mcp_response.get("ok"))
+ mcp_result = mcp_response.get("result") if isinstance(mcp_response.get("result"), dict) else mcp_response
+ if mcp_ok or mcp_result.get("source", "").startswith("mock_"):
+ data = mcp_result.get("data") or mcp_result
+ context["imdb_access_data"] = {
+ "status_code": 200 if mcp_result.get("found", True) else 204,
+ "cpf_cnpj": cpf_cnpj,
+ "source": mcp_result.get("source", "framework_mcp"),
+ "mcp_tool": "consultar_imdb_cliente",
+ "plan": data.get("plan") or data.get("plano") or {"Type": "POS_PAGO", "name": "Mock Pós-pago"},
+ "statusType": data.get("statusType") or data.get("status_type") or "ACTIVE",
+ "statusDescription": data.get("statusDescription") or data.get("status_description") or "Cliente ativo",
+ "socialSecNo": data.get("socialSecNo") or data.get("cpfCnpj") or cpf_cnpj,
+ "raw": data,
+ }
+ event("AGA.011", {
+ "status": "Resultado IMDB: sucesso via MCP/framework",
+ "type": "INFO",
+ "session_id": session_id,
+ "tag": "AGA.011",
+ "call_id": session_id,
+ "origin": "MCP",
+ **build_ic_payload(state, "AGA.011", {"apiUrl": "mcp://consultar_imdb_cliente", "apiStatusCode": "200", "apiResponsePayload": json.dumps(context["imdb_access_data"], ensure_ascii=False), "latencyMs": "N/A"}),
+ }, metadata={"noc": True})
+ state = update_state_metadata(state, request_context=context)
+ await set_current_step(state, GraphStep.IMDB_ENRICHMENT_COMPLETED)
+ return state
+
+ # Legacy direct client is disabled by default. Enable only for controlled parity tests.
+ if not getattr(settings, "BACKOFFICE_ALLOW_LEGACY_CLIENTS", False):
+ logger.warning("IMDB MCP unavailable; continuing with controlled mock fallback. error=%s", mcp_response.get("error"))
+ context["imdb_access_data"] = {
+ "status_code": 200,
+ "cpf_cnpj": cpf_cnpj,
+ "source": "framework_local_fallback",
+ "mcp_tool": "consultar_imdb_cliente",
+ "plan": {"Type": "POS_PAGO", "name": "Mock Pós-pago"},
+ "statusType": "ACTIVE",
+ "statusDescription": "Cliente ativo - fallback local",
+ "socialSecNo": cpf_cnpj,
+ }
+ state = update_state_metadata(state, request_context=context)
+ await set_current_step(state, GraphStep.IMDB_ENRICHMENT_COMPLETED)
+ return state
+
+ # Legacy parity path. Avoid in framework-native local execution.
+ from src.components.clients.imdb_client import ImdbClient
+ from src.components.clients.exceptions.imdb_exceptions import ImdbClientError
+ client = ImdbClient()
imdb_request = ImdbRequest(msisdn=msisdn, client_id=client_id)
logger.info(
- "Fetching IMDB API")
+ "Fetching legacy IMDB API")
try:
response, http_meta = await client.get_imdb_access_data_with_retry(
msisdn=imdb_request.msisdn,
diff --git a/src/agent/nodes/knowledge_base_enrichment_node.py b/src/agent/nodes/knowledge_base_enrichment_node.py
index 89a8976..0176e60 100644
--- a/src/agent/nodes/knowledge_base_enrichment_node.py
+++ b/src/agent/nodes/knowledge_base_enrichment_node.py
@@ -3,12 +3,11 @@ import logging
from src.agent.state.agent_state import AgentState, update_state_metadata
from src.agent.state.steps import GraphStep
from src.agent.state.step_helpers import set_current_step
-from src.components.clients.tais_kb_client import TaisKbClient, Product
-from src.components.clients.exceptions.tais_kb_exceptions import TaisKbClientError
from src.core.config import settings
from src.utils.observer import trace_node
from src.utils.ics_collector import build_ic_payload, build_rag_telemetry_fields, build_noc_db_metadata
from src.compat.framework_observer import event
+from src.compat.framework_services import call_mcp_tool
logger = logging.getLogger(__name__)
@@ -217,6 +216,62 @@ async def enrich_with_knowledge_base(state: AgentState) -> AgentState:
state = update_state_metadata(state, request_context=context)
return state
+ # Framework-native path: query KB via MCP/RAG abstraction instead of direct TAIS client.
+ mcp_response = await call_mcp_tool(
+ "consultar_tais_kb",
+ {
+ "query": query_text,
+ "protocol_id": (context.get("complaint") or {}).get("complaintProtocol"),
+ "customer_key": (context.get("customer") or {}).get("cpfCnpj") or (context.get("customer") or {}).get("msisdn"),
+ },
+ business_context={
+ "customer_key": (context.get("customer") or {}).get("cpfCnpj") or (context.get("customer") or {}).get("msisdn"),
+ "interaction_key": (context.get("complaint") or {}).get("complaintProtocol"),
+ },
+ original_context=context,
+ )
+ mcp_result = mcp_response.get("result") if isinstance(mcp_response.get("result"), dict) else mcp_response
+ if mcp_response.get("ok") or str(mcp_result.get("source", "")).startswith("mock_"):
+ raw_docs = mcp_result.get("documents") or ((mcp_result.get("result") or {}).get("documents") if isinstance(mcp_result.get("result"), dict) else []) or []
+ documents = [
+ {
+ "documentId": d.get("documentId") or d.get("id") or d.get("id_proc"),
+ "title": d.get("title") or d.get("title_proc"),
+ "chunk": d.get("chunk") or d.get("content") or d.get("text"),
+ "distance": d.get("distance") if d.get("distance") is not None else d.get("score"),
+ }
+ for d in raw_docs
+ ]
+ relevant_documents = {"query": query_text, "documents": documents}
+ message = mcp_result.get("message") or mcp_result.get("summary")
+ if message:
+ relevant_documents["message"] = message
+ elif not documents:
+ relevant_documents = _not_found_payload(query_text)
+ context = dict(context)
+ context["relevant_documents"] = relevant_documents
+ state = update_state_metadata(state, request_context=context)
+ event("AGA.012", {
+ "status": f"Resultado da Base de Conhecimento: via MCP/framework — {len(documents)} documento(s)",
+ "type": "SUCCESS" if documents else "INFO",
+ "session_id": session_id,
+ "tag": "AGA.012",
+ "call_id": session_id,
+ "origin": "MCP",
+ **build_ic_payload(state, "AGA.012", build_rag_telemetry_fields(retrieved=documents, selected=documents)),
+ }, metadata={"noc": True})
+ return state
+
+ if not getattr(settings, "BACKOFFICE_ALLOW_LEGACY_CLIENTS", False):
+ context = dict(context)
+ context["relevant_documents"] = _unavailable_payload(query_text, error=mcp_response.get("error", "MCP unavailable"))
+ state = update_state_metadata(state, request_context=context)
+ await set_current_step(state, GraphStep.KNOWLEDGE_BASE_ENRICHMENT_UNAVAILABLE)
+ return state
+
+ from src.components.clients.tais_kb_client import TaisKbClient, Product
+ from src.components.clients.exceptions.tais_kb_exceptions import TaisKbClientError
+
try:
event("AGA.020", {
"status": "Registro da busca do template: iniciando busca na Base de Conhecimento",
diff --git a/src/agent/nodes/siebel_sr_opening_node.py b/src/agent/nodes/siebel_sr_opening_node.py
index 4fec929..8e6ed77 100644
--- a/src/agent/nodes/siebel_sr_opening_node.py
+++ b/src/agent/nodes/siebel_sr_opening_node.py
@@ -144,9 +144,16 @@ async def open_siebel_sr(state: AgentState) -> AgentState:
motivo_extra = build_external_response(context, classification)
+ opened_at = context.get("complaint", {}).get("openedAt")
+
+ if hasattr(opened_at, "isoformat"):
+ opened_at_value = opened_at.isoformat()
+ else:
+ opened_at_value = opened_at
+
notas = SiebelSRRequest.build_notes(
type = context.get("siebel_action", "tratamento"),
- data = context.get("complaint", {}).get("openedAt").isoformat() if context.get("complaint", {}).get("openedAt") else None,
+ data = opened_at_value,
protocolo_anatel = context.get("complaint", {}).get("complaintProtocol"),
acao = context.get("complaint", {}).get("actionType"),
cpf_cliente = cpf,
diff --git a/src/agent/nodes/speech_enrichment_node.py b/src/agent/nodes/speech_enrichment_node.py
index 3012b35..fef1ef1 100644
--- a/src/agent/nodes/speech_enrichment_node.py
+++ b/src/agent/nodes/speech_enrichment_node.py
@@ -1,11 +1,10 @@
import time
import logging
import json
+import re
from src.agent.state.agent_state import AgentState, update_state_metadata
from src.agent.state.steps import GraphStep
from src.agent.state.step_helpers import set_current_step
-from src.components.clients.speech_analytics_client import SpeechAnalyticsClient
-from src.components.clients.exceptions.speech_exceptions import SpeechClientError
from src.utils.observer import trace_node, score_current_trace
from src.utils.ics_collector import build_ic_payload, build_noc_api_metadata, build_noc_llm_metadata
from src.compat.framework_observer import event
@@ -13,6 +12,7 @@ from src.providers.llm_provider import classification_llm, chat_llm_with_usage,
from src.core.config import settings
from src.core.prompt_manager import get_prompt
from src.agent.local_prompts.speech_history_analysis import speech_history_similarity_pt
+from src.compat.framework_services import call_mcp_tool
logger = logging.getLogger(__name__)
@@ -42,9 +42,7 @@ _NULL_SPEECH_DATA = {
def _map_prediction_response(response: dict) -> dict:
- """
- Map the raw Prediction API response to the internal speech_analytics schema.
- """
+ """Map the raw Prediction API response to the internal speech_analytics schema."""
variables = response.get("variables", {})
return {
"reclamacao_resumo": response.get("resume") or variables.get("resume"),
@@ -57,18 +55,8 @@ def _map_prediction_response(response: dict) -> dict:
}
-def _build_related_history(
- raw_history: list,
- current_complaint_id: str | None,
- llm_scores: list,
- threshold: int,
-) -> list:
- """
- Merge LLM similarity scores with raw history items and filter by threshold.
-
- Each returned item has: protocolo, data_reclamacao, similaridade_pct,
- reasoning, plus the 7 detail keys (same as reclamacao_atual).
- """
+def _build_related_history(raw_history: list, current_complaint_id: str | None, llm_scores: list, threshold: int) -> list:
+ """Merge LLM similarity scores with raw history items and filter by threshold."""
if not raw_history or not llm_scores:
return []
@@ -105,18 +93,13 @@ def _build_related_history(
def _build_analise_agente(related_items: list) -> str:
- """
- Build the analise_agente string from related items: one line per item with
- 'Protocolo X (Y%): '. Empty list -> negative spec message.
- """
+ """Build the analise_agente text from related items."""
if not related_items:
return _NO_SIMILAR_MESSAGE
lines = []
for item in related_items:
- reasoning = (item.get("reasoning") or "").strip()
- if not reasoning:
- reasoning = "—"
+ reasoning = (item.get("reasoning") or "").strip() or "—"
lines.append(f"Protocolo {item.get('protocolo')} ({item.get('similaridade_pct')}%): {reasoning}")
return "\n".join(lines)
@@ -128,20 +111,31 @@ async def _score_history_with_llm(
session_id: str = "",
state: dict | None = None,
) -> list:
- """
- Calls the LLM to score similarity between the current complaint description
- and each historical item. Returns the LLM-parsed JSON list. Items below
- SPEECH_SIMILARITY_THRESHOLD will be dropped later by _build_related_history.
- """
+ """Score similarity between current complaint and historical complaints using the LLM."""
if not current_complaint_description or not history_list:
+ event("AGA.015", {
+ "status": "Houve acionamento do Agente Especializado (LLM): não — sem resumo atual ou histórico vazio",
+ "type": "INFO",
+ "session_id": session_id,
+ "tag": "AGA.015",
+ "call_id": session_id,
+ "origin": "LLM",
+ **build_ic_payload({"session_id": session_id}, "AGA.015"),
+ })
return []
- clean_history = [
- item for item in history_list
- if str(item.get("protocolo")) != str(current_id or "")
- ]
+ clean_history = [item for item in history_list if str(item.get("protocolo")) != str(current_id or "")]
if not clean_history:
logger.info("No historical items left after deduplication.")
+ event("AGA.015", {
+ "status": "Houve acionamento do Agente Especializado (LLM): não — histórico vazio após deduplicação",
+ "type": "INFO",
+ "session_id": session_id,
+ "tag": "AGA.015",
+ "call_id": session_id,
+ "origin": "LLM",
+ **build_ic_payload({"session_id": session_id}, "AGA.015"),
+ })
return []
llm = classification_llm
@@ -163,6 +157,305 @@ async def _score_history_with_llm(
history_json=json.dumps(history_payload, ensure_ascii=False),
)
- logger.info(f"Calling LLM for history similarity filtering. Items: {len(clean_history)}")
+ logger.info("Calling LLM for history similarity filtering. Items: %s", len(clean_history))
+ event("AGA.014", {
+ "status": "Houve acionamento do Agente Especializado (LLM): sim — filtragem de histórico Speech",
+ "type": "INFO",
+ "session_id": session_id,
+ "tag": "AGA.014",
+ "call_id": session_id,
+ "origin": "LLM",
+ **build_ic_payload({"session_id": session_id}, "AGA.014"),
+ })
- # LLM generation telemetry is recorded by agent_framework.llm.providers.
+ try:
+ _t0 = time.perf_counter()
+ llm_resp = chat_llm_with_usage(llm, message)
+ content = llm_resp.content
+
+ json_match = re.search(r"```(?:json)?\s*(\[.*?\])\s*```", content, re.DOTALL)
+ if json_match:
+ content = json_match.group(1)
+ else:
+ json_match = re.search(r"(\[.*?\])", content, re.DOTALL)
+ if json_match:
+ content = json_match.group(1)
+
+ scores = json.loads(content)
+ if not isinstance(scores, list):
+ raise ValueError(f"LLM returned non-list: {type(scores).__name__}")
+
+ score_current_trace(name="speech_history_similarity_valid", value=1.0)
+ return scores
+ except Exception as e:
+ logger.error("Error during LLM history scoring: %s", e, exc_info=True)
+ score_current_trace(name="speech_history_similarity_valid", value=0.0, comment=str(e))
+ if state is not None:
+ event("NOC.004", {
+ "status": f"LLM error during history similarity analysis: {e}",
+ "type": "FAILURE",
+ **build_noc_llm_metadata(
+ state,
+ "NOC.004",
+ latency_ms=int((time.perf_counter() - _t0) * 1000) if "_t0" in locals() else 0,
+ llm_endpoint=LLM_ENDPOINT,
+ model_name=str(getattr(llm, "eligibleModel_name", "unknown")),
+ ),
+ }, metadata={"noc": True})
+ return []
+
+
+@trace_node
+async def enrich_with_speech(state: AgentState) -> AgentState:
+ """Enrich the ticket with Speech Analytics NLP insights and historical similarity."""
+ logger.info("Running speech enrichment node.")
+
+ await set_current_step(state, GraphStep.SPEECH_ENRICHMENT)
+ session_id = state.get("session_id", "")
+ context = state.get("metadata", {}).get("request_context", {})
+
+ if "speech_analytics" in context:
+ logger.info("Speech analytics data already exists in context (cached). Skipping API call.")
+ return state
+
+ complaint = context.get("complaint", {})
+ reclamacao_id: str | None = complaint.get("complaintProtocol")
+ raw_text: str | None = complaint.get("description")
+
+ customer = context.get("customer", {})
+ cpf_cnpj: str | None = customer.get("cpfCnpj")
+ msisdn: str | None = customer.get("msisdn")
+
+ # Framework-native path: call Speech through MCP/fallback instead of direct legacy clients.
+ mcp_response = await call_mcp_tool(
+ "consultar_speech_analytics",
+ {
+ "protocol_id": reclamacao_id,
+ "customer_key": cpf_cnpj or msisdn,
+ "interaction_key": reclamacao_id,
+ "document": cpf_cnpj,
+ },
+ business_context={"customer_key": cpf_cnpj or msisdn, "interaction_key": reclamacao_id},
+ original_context=context,
+ )
+ mcp_result = mcp_response.get("result") if isinstance(mcp_response.get("result"), dict) else mcp_response
+ if mcp_response.get("ok") or str(mcp_result.get("source", "")).startswith("mock_"):
+ summary = mcp_result.get("summary") or (raw_text or "Reclamação recebida para análise.")
+ speech_data = {
+ "reclamacao_resumo": summary,
+ "causa_raiz": mcp_result.get("root_cause") or "Cobrança/contato indevido",
+ "descortesia_cliente": mcp_result.get("rude_customer") or "False",
+ "motivo_reclamacao": mcp_result.get("reason") or complaint.get("motive"),
+ "submotivo_reclamacao": mcp_result.get("subreason") or complaint.get("modality"),
+ "sentimento_cliente": mcp_result.get("sentiment") or "Negativo",
+ "solucao_proposta_cliente": mcp_result.get("proposed_solution") or "Analisar cobrança e cessar contatos indevidos",
+ "historico_bruto": mcp_result.get("events") or [],
+ "historico_relacionado": mcp_result.get("related_history") or [],
+ "analise_agente": mcp_result.get("agent_analysis") or (mcp_result.get("message") or _NO_SIMILAR_MESSAGE),
+ "source": mcp_result.get("source", "framework_mcp"),
+ "mcp_tool": "consultar_speech_analytics",
+ }
+ context = dict(context)
+ context["speech_analytics"] = speech_data
+ state = update_state_metadata(state, request_context=context)
+ event("AGA.034", {
+ "status": "Resultado Speech: sucesso via MCP/framework",
+ "type": "SUCCESS",
+ "session_id": session_id,
+ "tag": "AGA.034",
+ "call_id": session_id,
+ "origin": "MCP",
+ **build_ic_payload(state, "AGA.034"),
+ }, metadata={"noc": True})
+ return state
+
+ if not getattr(settings, "BACKOFFICE_ALLOW_LEGACY_CLIENTS", False):
+ speech_data = dict(_NULL_SPEECH_DATA)
+ speech_data.update({
+ "reclamacao_resumo": raw_text or "Indisponível SPEECH para Reclamação Atual - realizar consulta manualmente",
+ "historico_bruto": [],
+ "historico_relacionado": [],
+ "analise_agente": _HISTORY_UNAVAILABLE_MESSAGE,
+ "source": "framework_local_fallback",
+ })
+ context = dict(context)
+ context["speech_analytics"] = speech_data
+ state = update_state_metadata(state, request_context=context)
+ await set_current_step(state, GraphStep.SPEECH_ENRICHMENT_UNAVAILABLE)
+ return state
+
+ from src.components.clients.speech_analytics_client import SpeechAnalyticsClient
+ from src.components.clients.exceptions.speech_exceptions import SpeechClientError
+
+ speech_data: dict = dict(_NULL_SPEECH_DATA)
+ history_unavailable = False
+
+ if not reclamacao_id or not raw_text:
+ logger.warning(
+ "Speech enrichment skipped prediction: missing complaintProtocol or description. "
+ "reclamacao_id=%s, raw_text_present=%s",
+ bool(reclamacao_id), bool(raw_text)
+ )
+ event("AGA.010", {
+ "status": "Resultado consulta Speech: ignorada — complaintProtocol ou description ausente",
+ "type": "INFO",
+ "session_id": session_id,
+ "tag": "AGA.010",
+ "call_id": session_id,
+ "origin": "AGENT",
+ **build_ic_payload(state, "AGA.010"),
+ }, metadata={"noc": True})
+ else:
+ try:
+ client = SpeechAnalyticsClient()
+ response, http_meta = await client.get_prediction(
+ reclamacao_id=reclamacao_id,
+ raw_text=raw_text,
+ customer_segment="",
+ )
+ speech_data = _map_prediction_response(response)
+ if not speech_data.get("reclamacao_resumo"):
+ speech_data["reclamacao_resumo"] = "Reclamação Atual não foi encontrada no SPEECH"
+ event("NOC.002", {
+ "status": "Invalid API response",
+ "type": "WARNING",
+ **build_noc_api_metadata(
+ state,
+ "NOC.002",
+ retry_count=http_meta.get("retry_count", 0),
+ latency_ms=http_meta["latency_ms"],
+ api_url=http_meta["url"],
+ status_code=http_meta["status_code"],
+ ),
+ }, metadata={"noc": True})
+
+ logger.info("Speech Analytics prediction retrieved successfully. reclamacao_id=%s", reclamacao_id)
+ event("AGA.010", {
+ "status": "Resultado consulta Speech: sucesso",
+ "type": "SUCCESS",
+ "session_id": session_id,
+ "tag": "AGA.010",
+ "call_id": session_id,
+ "origin": "AGENT",
+ **build_ic_payload(state, "AGA.010", {
+ "apiUrl": http_meta["url"],
+ "apiStatusCode": http_meta["status_code"],
+ "apiResponsePayload": http_meta["response_text"],
+ "latencyMs": http_meta["latency_ms"],
+ }),
+ }, metadata={"noc": True})
+ except SpeechClientError as client_exc:
+ logger.warning("Speech Analytics unavailable — continuing without prediction. Error: %s", client_exc)
+ speech_data["reclamacao_resumo"] = "Indisponível SPEECH para Reclamação Atual - realizar consulta manualmente"
+ await set_current_step(state, GraphStep.SPEECH_ENRICHMENT_UNAVAILABLE)
+ api_fields = {
+ "apiUrl": getattr(client_exc, "url", None) or "N/A",
+ "apiStatusCode": getattr(client_exc, "status_code", None) if getattr(client_exc, "status_code", None) is not None else "N/A",
+ "apiResponsePayload": getattr(client_exc, "response_text", None) if getattr(client_exc, "response_text", None) is not None else "N/A",
+ "latencyMs": getattr(client_exc, "latency_ms", None) if getattr(client_exc, "latency_ms", None) is not None else "N/A",
+ }
+ event("AGA.035", {
+ "status": f"Erro na consulta Speech (Reclamação Atual): SpeechClientError: {str(client_exc)}",
+ "type": "FAILURE",
+ "session_id": session_id,
+ "tag": "AGA.035",
+ "call_id": session_id,
+ "origin": "AGENT",
+ **build_ic_payload(state, "AGA.035", api_fields),
+ }, metadata={"noc": True})
+
+ historico: list = []
+ if not cpf_cnpj:
+ logger.warning("Speech enrichment skipped history fetch: cpfCnpj absent.", extra={"session_id": session_id})
+ event("AGA.034", {
+ "status": "Resultado consulta histórico Speech: ignorada — cpfCnpj ausente",
+ "type": "INFO",
+ "session_id": session_id,
+ "tag": "AGA.034",
+ "call_id": session_id,
+ "origin": "AGENT",
+ **build_ic_payload(state, "AGA.034"),
+ }, metadata={"noc": True})
+ else:
+ try:
+ client = SpeechAnalyticsClient()
+ historico, http_meta = await client.get_history(cpf_cnpj, msisdn)
+ logger.info("Speech Analytics history retrieved successfully. %s items.", len(historico), extra={"session_id": session_id})
+ event("AGA.034", {
+ "status": f"Resultado consulta histórico Speech: sucesso — {len(historico)} item(ns)",
+ "type": "SUCCESS",
+ "session_id": session_id,
+ "tag": "AGA.034",
+ "call_id": session_id,
+ "origin": "AGENT",
+ **build_ic_payload(state, "AGA.034", {
+ "apiUrl": http_meta["url"],
+ "apiStatusCode": http_meta["status_code"],
+ "apiResponsePayload": http_meta["response_text"],
+ "latencyMs": http_meta["latency_ms"],
+ }),
+ }, metadata={"noc": True})
+ if not historico:
+ event("NOC.002", {
+ "status": "Invalid API response",
+ "type": "WARNING",
+ **build_noc_api_metadata(
+ state,
+ "NOC.002",
+ retry_count=http_meta.get("retry_count", 0),
+ latency_ms=http_meta["latency_ms"],
+ api_url=http_meta["url"],
+ status_code=http_meta["status_code"],
+ ),
+ }, metadata={"noc": True})
+ except Exception as exc:
+ logger.warning("Failed to fetch speech history: %s", exc)
+ await set_current_step(state, GraphStep.SPEECH_ENRICHMENT_UNAVAILABLE)
+ api_fields = {}
+ if isinstance(exc, SpeechClientError):
+ api_fields = {
+ "apiUrl": getattr(exc, "url", None) or "N/A",
+ "apiStatusCode": getattr(exc, "status_code", None) if getattr(exc, "status_code", None) is not None else "N/A",
+ "apiResponsePayload": getattr(exc, "response_text", None) if getattr(exc, "response_text", None) is not None else "N/A",
+ "latencyMs": getattr(exc, "latency_ms", None) if getattr(exc, "latency_ms", None) is not None else "N/A",
+ }
+ event("AGA.035", {
+ "status": f"Falha ao buscar histórico Speech: {str(exc)}",
+ "type": "FAILURE",
+ "session_id": session_id,
+ "tag": "AGA.035",
+ "call_id": session_id,
+ "origin": "AGENT",
+ **build_ic_payload(state, "AGA.035", api_fields),
+ }, metadata={"noc": True})
+ history_unavailable = True
+
+ historico_relacionado: list = []
+ if historico and raw_text:
+ scores = await _score_history_with_llm(
+ current_id=reclamacao_id,
+ current_complaint_description=raw_text,
+ history_list=historico,
+ session_id=session_id,
+ state=state,
+ )
+ historico_relacionado = _build_related_history(
+ raw_history=historico,
+ current_complaint_id=reclamacao_id,
+ llm_scores=scores,
+ threshold=settings.SPEECH_SIMILARITY_THRESHOLD,
+ )
+
+ def _strip_to_detail_keys(h_list: list) -> list:
+ return [{k: item.get(k) for k in _HISTORY_DETAIL_KEYS} for item in h_list]
+
+ speech_data["historico_bruto"] = _strip_to_detail_keys(historico)
+ speech_data["historico_relacionado"] = historico_relacionado
+ speech_data["analise_agente"] = _HISTORY_UNAVAILABLE_MESSAGE if history_unavailable else _build_analise_agente(historico_relacionado)
+
+ context = dict(context)
+ context["speech_analytics"] = speech_data
+ state = update_state_metadata(state, request_context=context)
+
+ logger.info("Speech enrichment node completed successfully. State metadata and current step updated: %s", state.get("current_step"))
+ return state
diff --git a/src/agent/state/step_helpers.py b/src/agent/state/step_helpers.py
index ce63565..b015315 100644
--- a/src/agent/state/step_helpers.py
+++ b/src/agent/state/step_helpers.py
@@ -25,6 +25,7 @@ from src.agent.state.agent_state import AgentState
from src.agent.state.step_notes import STEP_NOTES
from src.agent.state.steps import GraphStep
from src.api.schemas.anatel_schemas import ProgressEvent, ProgressProcessing
+from src.compat.framework_services import get_progress_producer
logger = logging.getLogger(__name__)
@@ -68,7 +69,7 @@ async def set_current_step(state: AgentState, step: GraphStep) -> None:
return
metadata = state.get("metadata") or {}
- producer = metadata.get("_oci_producer")
+ producer = metadata.get("_oci_producer") or get_progress_producer()
transaction_id = metadata.get("transaction_id")
if producer is None or not transaction_id:
return
diff --git a/src/compat/framework_services.py b/src/compat/framework_services.py
new file mode 100644
index 0000000..1eadd16
--- /dev/null
+++ b/src/compat/framework_services.py
@@ -0,0 +1,63 @@
+"""Framework service bridge for migrated backoffice domain nodes.
+
+This module is intentionally tiny and dependency-light. It lets old domain nodes
+call framework-owned services (MCP router and progress producer) without storing
+non-serializable objects inside LangGraph state/checkpoints.
+"""
+from __future__ import annotations
+
+from typing import Any
+import logging
+
+logger = logging.getLogger(__name__)
+
+_tool_router: Any = None
+_progress_producer: Any = None
+
+
+def configure(*, tool_router: Any | None = None, progress_producer: Any | None = None) -> None:
+ global _tool_router, _progress_producer
+ if tool_router is not None:
+ _tool_router = tool_router
+ if progress_producer is not None:
+ _progress_producer = progress_producer
+
+
+def get_tool_router() -> Any | None:
+ return _tool_router
+
+
+def get_progress_producer() -> Any | None:
+ return _progress_producer
+
+
+async def call_mcp_tool(
+ tool_name: str,
+ arguments: dict[str, Any] | None = None,
+ *,
+ business_context: dict[str, Any] | None = None,
+ original_context: dict[str, Any] | None = None,
+) -> dict[str, Any]:
+ """Call a framework MCP tool and normalize the result to a dict.
+
+ Returns {"ok": False, "error": ...} when the router is not configured or the
+ tool fails, so domain nodes can decide whether to fail-open or fail-closed.
+ """
+ router = get_tool_router()
+ if router is None or not getattr(router, "enabled", False):
+ return {"ok": False, "error": "framework MCP router not configured", "tool": tool_name}
+ try:
+ result = await router.call(
+ tool_name,
+ arguments or {},
+ business_context=business_context or {},
+ original_context=original_context or arguments or {},
+ )
+ if hasattr(result, "model_dump"):
+ return result.model_dump(mode="json")
+ if isinstance(result, dict):
+ return result
+ return {"ok": True, "result": result, "tool": tool_name}
+ except Exception as exc: # noqa: BLE001
+ logger.warning("framework MCP call failed tool=%s error=%s", tool_name, exc, exc_info=True)
+ return {"ok": False, "error": str(exc), "tool": tool_name}
diff --git a/src/core/config.py b/src/core/config.py
index 91209e5..4d4a887 100644
--- a/src/core/config.py
+++ b/src/core/config.py
@@ -69,14 +69,14 @@ class Settings(BaseSettings):
LLM_MAX_TOKENS: Optional[int] = Field(default=None, gt=0, description="Max tokens")
# Classification LLM settings
- CLASSIFICATION_LLM_MODEL: str = Field(default="bo_gptoss20b_dev", description="Model name for classification node")
+ CLASSIFICATION_LLM_MODEL: str = Field(default="openai.gpt-4.1", description="Model name for classification node")
CLASSIFICATION_LLM_TEMPERATURE: float = Field(default=0.3, ge=0.0, le=2.0, description="Temperature for classification node")
CLASSIFICATION_LLM_MAX_TOKENS: int = Field(default=1024, gt=0, description="Max tokens for classification node")
CLASSIFICATION_LLM_TOP_P: float = Field(default=0.8, description="Top P for classification node")
CLASSIFICATION_LLM_TOP_K: float = Field(default=250, description="Top K for classification node")
# Large Classification LLM settings (for complex reasoning/canceling)
- CLASSIFICATION_LARGE_LLM_MODEL: str = Field(default="bo_gptoss120b_dev", description="Large model name for critical classification steps")
+ CLASSIFICATION_LARGE_LLM_MODEL: str = Field(default="openai.gpt-4.1", description="Large model name for critical classification steps")
CLASSIFICATION_LARGE_LLM_TEMPERATURE: float = Field(default=0.3, ge=0.0, le=2.0, description="Temperature for large classification node")
CLASSIFICATION_LARGE_LLM_MAX_TOKENS: int = Field(default=1024, gt=0, description="Max tokens for large classification node")
CLASSIFICATION_LARGE_LLM_TOP_P: float = Field(default=0.8, description="Top P for large classification node")
@@ -201,6 +201,9 @@ class Settings(BaseSettings):
# Anatel Dictionary settings
USE_FULL_ANATEL_DICT: bool = Field(default=False, description="Whether to use the full Anatel motives dictionary instead of filtering by service")
+ # Migration control. Default False: domain nodes must use framework services/MCP/fallbacks.
+ BACKOFFICE_ALLOW_LEGACY_CLIENTS: bool = Field(default=False, description="Allow direct legacy TIM clients as a parity escape hatch. Keep False for framework-native execution.")
+
# Speech Analytics API settings
SPEECH_PREDICTION_BASE_URL: Optional[str] = Field(default=None, description="Base URL of the Speech Prediction API gateway (OAuth2 + prediction endpoint)")
SPEECH_PREDICTION_CLIENT_ID: Optional[str] = Field(default=None, description="Client ID for the Speech Prediction OAuth2 client_credentials flow")
@@ -230,7 +233,7 @@ class Settings(BaseSettings):
TAIS_TABLE_CHUNKS: str = Field(default="CHUNKS_CHAR_COHERE_3", description="Oracle table containing TAIS chunks + embeddings")
TAIS_TABLE_FILES: str = Field(default="files_oci", description="Oracle table containing TAIS raw documents")
TAIS_TOP_K: int = Field(default=3, gt=0, description="Number of unique documents returned by TAIS KB search")
- TAIS_KB_LLM_MODEL: str = Field(default="bo_gptoss20b_dev", description="Modelo LLM para pós-processamento da KB (bo_gptoss20b_dev ou bo_gptoss120b_dev)")
+ TAIS_KB_LLM_MODEL: str = Field(default="openai.gpt-4.1", description="Modelo LLM framework para pós-processamento da KB")
TAIS_KB_LLM_TEMPERATURE: float = Field(default=0.3, ge=0.0, le=2.0)
TAIS_KB_LLM_MAX_TOKENS: int = Field(default=4096, gt=0, description="Tokens de saída — manter alto para respostas completas")
TAIS_KB_LLM_TOP_P: float = Field(default=0.9)
diff --git a/src/providers/llm_provider.py b/src/providers/llm_provider.py
index f7d793b..3825047 100644
--- a/src/providers/llm_provider.py
+++ b/src/providers/llm_provider.py
@@ -71,7 +71,7 @@ class _SettingsProxy:
# OpenAI-compatible implementation.
if self.LLM_PROVIDER == "oci":
self.LLM_PROVIDER = "oci_openai"
- self.OCI_GENAI_MODEL = str(llm.eligibleModel_name)
+ self.OCI_GENAI_MODEL = _framework_model_name(llm.eligibleModel_name)
self.LLM_TEMPERATURE = float(llm.temperature)
self.LLM_MAX_TOKENS = int(llm.max_tokens)
@@ -81,8 +81,24 @@ class _SettingsProxy:
LLM_ENDPOINT: str = getattr(fw_settings, "OCI_GENAI_BASE_URL", "")
+
+def _framework_model_name(candidate: str | None = None) -> str:
+ """Resolve any legacy backoffice model alias to the framework model.
+
+ Old develop configs used internal model aliases. In the migrated project those aliases must never be sent to OCI/OpenAI directly.
+ """
+ model = str(candidate or "").strip()
+ if not model or model.startswith("bo_") or "gptoss" in model.lower():
+ model = (
+ getattr(fw_settings, "OCI_GENAI_MODEL", None)
+ or getattr(fw_settings, "LLM_MODEL", None)
+ or getattr(settings, "LLM_MODEL", None)
+ or "openai.gpt-4.1"
+ )
+ return str(model)
+
classification_llm = BackofficeLLMDescriptor(
- eligibleModel_name=settings.CLASSIFICATION_LLM_MODEL,
+ eligibleModel_name=_framework_model_name(settings.CLASSIFICATION_LLM_MODEL),
temperature=settings.CLASSIFICATION_LLM_TEMPERATURE,
max_tokens=settings.CLASSIFICATION_LLM_MAX_TOKENS,
top_p=settings.CLASSIFICATION_LLM_TOP_P,
@@ -90,7 +106,7 @@ classification_llm = BackofficeLLMDescriptor(
)
classification_large_llm = BackofficeLLMDescriptor(
- eligibleModel_name=settings.CLASSIFICATION_LARGE_LLM_MODEL,
+ eligibleModel_name=_framework_model_name(settings.CLASSIFICATION_LARGE_LLM_MODEL),
temperature=settings.CLASSIFICATION_LARGE_LLM_TEMPERATURE,
max_tokens=settings.CLASSIFICATION_LARGE_LLM_MAX_TOKENS,
top_p=settings.CLASSIFICATION_LARGE_LLM_TOP_P,
@@ -98,7 +114,7 @@ classification_large_llm = BackofficeLLMDescriptor(
)
tais_kb_llm = BackofficeLLMDescriptor(
- eligibleModel_name=settings.TAIS_KB_LLM_MODEL,
+ eligibleModel_name=_framework_model_name(settings.TAIS_KB_LLM_MODEL),
temperature=settings.TAIS_KB_LLM_TEMPERATURE,
max_tokens=settings.TAIS_KB_LLM_MAX_TOKENS,
top_p=settings.TAIS_KB_LLM_TOP_P,