Files
compass_backoffice/app/workflows/backoffice_workflow_dispatcher.py
2026-06-13 15:40:44 -03:00

70 lines
3.0 KiB
Python

from __future__ import annotations
from typing import Any
from app.channels.backoffice_rest_adapter import BackofficeChannelEnvelope, BackofficeRestChannelAdapter
from app.workflows.backoffice_workflow_executor import BackofficeWorkflowExecutor
class BackofficeWorkflowDispatcher:
"""Framework-facing dispatcher for Backoffice operational workflows.
FastAPI routes call this dispatcher with a channel envelope. The dispatcher
first normalizes the envelope through the framework ChannelGateway and only
then dispatches the requested LangGraph workflow. This keeps REST as a
channel adapter concern and prevents routes from coupling directly to a
domain workflow executor.
"""
def __init__(self, *, channel_gateway: Any, executor: BackofficeWorkflowExecutor, telemetry: Any, adapter: BackofficeRestChannelAdapter | None = None):
self.channel_gateway = channel_gateway
self.executor = executor
self.telemetry = telemetry
self.adapter = adapter or BackofficeRestChannelAdapter()
async def execute(self, envelope: BackofficeChannelEnvelope, *, app_state: Any = None) -> dict[str, Any]:
msg = await self.adapter.normalize(self.channel_gateway, envelope)
context = dict(getattr(msg, "context", None) or envelope.gateway_payload())
metadata = {
**(envelope.metadata or {}),
"tenant_id": envelope.tenant_id,
"agent_id": envelope.agent_id,
"channel": envelope.channel,
"normalized_channel": getattr(msg, "channel", envelope.channel),
"channel_id": getattr(msg, "channel_id", None),
"message_id": envelope.message_id,
"user_id": getattr(msg, "user_id", envelope.user_id),
"business_context": envelope.business_context,
"channel_context": context,
"framework_entrypoint": "channel_gateway",
}
await self._event("backoffice.channel.normalized", {
"workflow_id": envelope.workflow_id,
"transaction_id": envelope.transaction_id,
"channel": envelope.channel,
"agent_id": envelope.agent_id,
"legacy_contract": envelope.metadata.get("legacy_contract"),
})
final_state = await self.executor.execute_workflow(
envelope.workflow_id,
payload=envelope.payload,
transaction_id=envelope.transaction_id,
app_state=app_state,
metadata=metadata,
)
await self._event("backoffice.workflow.dispatched", {
"workflow_id": envelope.workflow_id,
"transaction_id": envelope.transaction_id,
"current_step": str(final_state.get("current_step")),
"has_error": bool(final_state.get("error")),
})
return final_state
async def _event(self, name: str, payload: dict[str, Any]) -> None:
try:
await self.telemetry.event(name, payload, kind="workflow")
except TypeError:
await self.telemetry.event(name, payload)
except Exception:
pass