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,