mirror of
https://github.com/hoshikawa2/first_contas.git
synced 2026-07-10 02:34:20 +00:00
first commit
This commit is contained in:
BIN
app/workflows/.DS_Store
vendored
Normal file
BIN
app/workflows/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
app/workflows/__pycache__/agent_graph.cpython-313.pyc
Normal file
BIN
app/workflows/__pycache__/agent_graph.cpython-313.pyc
Normal file
Binary file not shown.
192
app/workflows/agent_graph.py
Normal file
192
app/workflows/agent_graph.py
Normal file
@@ -0,0 +1,192 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
|
||||
from agent_framework.cache.cache import create_cache
|
||||
from agent_framework.checkpoints.langgraph_saver import create_langgraph_checkpointer
|
||||
from agent_framework.guardrails.pipeline import GuardrailPipeline
|
||||
from agent_framework.guardrails.output_supervisor import OutputSupervisor
|
||||
from agent_framework.guardrails.rail_action import RailAction
|
||||
from agent_framework.judges.judge import JudgePipeline
|
||||
from agent_framework.observability.guardrail_events import GuardrailTelemetry
|
||||
from agent_framework.observability.judge_events import JudgeTelemetry
|
||||
from agent_framework.observability.langgraph_telemetry import LangGraphDeepTelemetry
|
||||
from agent_framework.observability.workflow_events import WorkflowTelemetry
|
||||
from agent_framework.rag.embedding_provider import create_embedding_provider
|
||||
from agent_framework.rag.rag_service import RagService
|
||||
from agent_framework.routing.enterprise_router import EnterpriseRouter
|
||||
from agent_framework.supervisor.supervisor import Supervisor
|
||||
from agent_framework.observer import AgentObserver
|
||||
|
||||
from app.agents.contas_agent import ContasAgent
|
||||
from app.state import AgentState
|
||||
|
||||
|
||||
class AgentWorkflow:
|
||||
"""Workflow LangGraph do agent_contas_first migrado.
|
||||
|
||||
O desenho é propositalmente fino: a orquestração corporativa fica no
|
||||
framework. O agente executa apenas o domínio Contas; guardrails, output
|
||||
supervisor, judges, RAG, MCP, checkpoints e observabilidade são nativos.
|
||||
"""
|
||||
|
||||
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
|
||||
self.analytics = analytics
|
||||
self.observer = observer or AgentObserver(analytics=analytics)
|
||||
self.settings = settings
|
||||
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)),
|
||||
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
|
||||
)
|
||||
self.output_supervisor_engine = OutputSupervisor(
|
||||
observer=self.observer,
|
||||
max_retries=int(getattr(settings, "OUTPUT_SUPERVISOR_MAX_RETRIES", 3)),
|
||||
enable_parallel=bool(getattr(settings, "ENABLE_PARALLEL_GUARDRAILS", True)),
|
||||
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
|
||||
)
|
||||
self.judges = JudgePipeline(llm=llm, settings=settings)
|
||||
self.supervisor = Supervisor()
|
||||
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
|
||||
self.workflow_telemetry = WorkflowTelemetry(telemetry)
|
||||
self.guardrail_telemetry = GuardrailTelemetry(telemetry)
|
||||
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, embedding_provider=self.embedding_provider, telemetry=telemetry, llm=llm)
|
||||
agent_kwargs = {
|
||||
"telemetry": telemetry,
|
||||
"tool_router": tool_router,
|
||||
"rag_service": self.rag_service,
|
||||
"cache": self.cache,
|
||||
"settings": settings,
|
||||
"observer": self.observer,
|
||||
"memory": memory,
|
||||
"summary_memory": summary_memory,
|
||||
}
|
||||
self.contas = ContasAgent(llm, **agent_kwargs)
|
||||
self.graph = self._build_graph()
|
||||
|
||||
def _node(self, name, fn):
|
||||
async def _wrapped(state):
|
||||
async with self.langgraph_telemetry.node(name, state):
|
||||
return await fn(state)
|
||||
return _wrapped
|
||||
|
||||
def _build_graph(self):
|
||||
builder = StateGraph(AgentState)
|
||||
builder.add_node("input_guardrails", self._node("input_guardrails", self.input_guardrails))
|
||||
builder.add_node("routing_decision", self._node("routing_decision", self.routing_decision))
|
||||
builder.add_node("contas_agent", self._node("contas_agent", self.contas_agent))
|
||||
builder.add_node("output_supervisor", self._node("output_supervisor", self.output_supervisor))
|
||||
builder.add_node("output_guardrails", self._node("output_guardrails", self.output_guardrails))
|
||||
builder.add_node("judge", self._node("judge", self.judge))
|
||||
builder.add_node("supervisor_review", self._node("supervisor_review", self.supervisor_review))
|
||||
builder.add_node("persist", self._node("persist", self.persist))
|
||||
|
||||
builder.add_edge(START, "input_guardrails")
|
||||
builder.add_conditional_edges("input_guardrails", lambda s: "blocked" if s.get("blocked") else "continue", {"blocked": "persist", "continue": "routing_decision"})
|
||||
builder.add_edge("routing_decision", "contas_agent")
|
||||
builder.add_edge("contas_agent", "output_supervisor")
|
||||
builder.add_edge("output_supervisor", "output_guardrails")
|
||||
builder.add_edge("output_guardrails", "judge")
|
||||
builder.add_edge("judge", "supervisor_review")
|
||||
builder.add_edge("supervisor_review", "persist")
|
||||
builder.add_edge("persist", END)
|
||||
return builder.compile(checkpointer=create_langgraph_checkpointer(self.settings))
|
||||
|
||||
async def input_guardrails(self, state: AgentState) -> dict[str, Any]:
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
async with self.telemetry.span("workflow.input_guardrails", session_id=session_id, input=state.get("user_text")):
|
||||
history_texts = [m.get("content", "") for m in state.get("history", [])]
|
||||
await self.observer.emit_grl("001", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "phase": "input"}, component="workflow.input_guardrails.start")
|
||||
sanitized, decisions = await self.guardrails.run_input(
|
||||
state["user_text"],
|
||||
{**(state.get("context") or {}), "history_texts": history_texts, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "agent_profile": state.get("agent_profile") or {}},
|
||||
)
|
||||
for decision in decisions:
|
||||
await self.guardrail_telemetry.evaluated("input", decision)
|
||||
await self.observer.emit_grl("002" if decision.allowed else "004", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "phase": "input", "rail_code": getattr(decision, "code", None), "allowed": bool(decision.allowed), "reason": getattr(decision, "reason", None)}, component="workflow.input_guardrails.decision")
|
||||
if not decision.allowed:
|
||||
await self.guardrail_telemetry.blocked("input", decision)
|
||||
blocked = any(not d.allowed for d in decisions)
|
||||
await self.observer.emit_grl("009", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "phase": "input", "blocked": blocked, "decision_count": len(decisions)}, component="workflow.input_guardrails.final")
|
||||
if blocked:
|
||||
return {"sanitized_input": sanitized, "answer": "Não consegui seguir com essa mensagem por regra de segurança.", "final_answer": "Não consegui seguir com essa mensagem por regra de segurança.", "guardrail_decisions": [d.model_dump() for d in decisions], "route": "blocked", "blocked": True}
|
||||
return {"sanitized_input": sanitized, "guardrail_decisions": [d.model_dump() for d in decisions], "blocked": False}
|
||||
|
||||
async def routing_decision(self, state: AgentState) -> dict[str, Any]:
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
async with self.telemetry.span("workflow.routing_decision", session_id=session_id, input={"text": state.get("sanitized_input") or state.get("user_text")}):
|
||||
decision = await self.router.route({**state, "route": "contas_agent"})
|
||||
await self.langgraph_telemetry.edge("routing_decision", "contas_agent", state, {"method": getattr(decision, "method", None), "intent": decision.intent, "confidence": decision.confidence})
|
||||
await self.observer.emit_ic("IC.CONTAS_ROUTE_SELECTED", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "route": "contas_agent", "intent": decision.intent, "confidence": decision.confidence, "method": getattr(decision, "method", None), "mcp_tools": decision.mcp_tools}, component="workflow.routing_decision")
|
||||
return {"route": "contas_agent", "intent": decision.intent, "route_decision": decision.model_dump(mode="json"), "domain": decision.domain, "mcp_tools": decision.mcp_tools, "next_state": decision.next_state}
|
||||
|
||||
async def contas_agent(self, state: AgentState) -> dict[str, Any]:
|
||||
async with self.telemetry.span("workflow.agent.contas", session_id=state.get("conversation_key") or state.get("session_id"), input={"intent": state.get("intent")}):
|
||||
return await self.contas.run(state)
|
||||
|
||||
async def output_supervisor(self, state: AgentState) -> dict[str, Any]:
|
||||
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
|
||||
return {"output_guardrails_already_applied": False, "supervisor_action": "disabled", "supervisor_attempt": int(state.get("supervisor_attempt", 0))}
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
candidate = state.get("answer") or ""
|
||||
context = {**(state.get("context") or {}), "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "session_id": session_id, "route": state.get("route"), "intent": state.get("intent")}
|
||||
async with self.telemetry.span("workflow.output_supervisor", session_id=session_id, input=candidate):
|
||||
decision = await self.output_supervisor_engine.evaluate(candidate, context)
|
||||
action = decision.action.value
|
||||
await self.observer.emit_ic("IC.OUTPUT_SUPERVISOR_COMPLETED", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "route": state.get("route"), "intent": state.get("intent"), "action": action, "approved": decision.approved, "result_count": len(decision.results)}, component="workflow.output_supervisor")
|
||||
if decision.action in {RailAction.ALLOW, RailAction.SANITIZE, RailAction.OBSERVE}:
|
||||
final_answer = decision.candidate
|
||||
elif decision.action == RailAction.HANDOVER:
|
||||
final_answer = "Vou encaminhar seu atendimento para continuidade com um especialista."
|
||||
else:
|
||||
final_answer = decision.fallback_message
|
||||
return {"answer": final_answer, "final_answer": final_answer, "supervisor_action": action, "supervisor_guidance": decision.guidance, "supervisor_attempt": int(state.get("supervisor_attempt", 0)) + (1 if decision.action == RailAction.RETRY else 0), "supervisor_handover_reason": decision.handover_reason, "output_supervisor_results": [{"code": r.code, "action": r.action.value, "reason": r.reason, "guidance": r.guidance, "metadata": r.metadata} for r in decision.results], "output_guardrails_already_applied": True}
|
||||
|
||||
async def output_guardrails(self, state: AgentState) -> dict[str, Any]:
|
||||
if state.get("output_guardrails_already_applied"):
|
||||
return {"final_answer": state.get("final_answer") or state.get("answer") or ""}
|
||||
session_id = state.get("conversation_key") or state.get("session_id")
|
||||
async with self.telemetry.span("workflow.output_guardrails", session_id=session_id, input=state.get("answer")):
|
||||
await self.observer.emit_grl("001", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "phase": "output", "route": state.get("route"), "intent": state.get("intent")}, component="workflow.output_guardrails.start")
|
||||
final, decisions = await self.guardrails.run_output(state["answer"], state.get("context", {}))
|
||||
for decision in decisions:
|
||||
await self.guardrail_telemetry.evaluated("output", decision)
|
||||
await self.observer.emit_grl("002" if decision.allowed else "004", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "phase": "output", "rail_code": getattr(decision, "code", None), "allowed": bool(decision.allowed), "reason": getattr(decision, "reason", None)}, component="workflow.output_guardrails.decision")
|
||||
if not decision.allowed:
|
||||
await self.guardrail_telemetry.blocked("output", decision)
|
||||
await self.observer.emit_grl("009", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "phase": "output", "blocked": any(not d.allowed for d in decisions), "decision_count": len(decisions)}, component="workflow.output_guardrails.final")
|
||||
return {"final_answer": final, "guardrail_decisions": state.get("guardrail_decisions", []) + [d.model_dump() for d in decisions]}
|
||||
|
||||
async def judge(self, state: AgentState) -> dict[str, Any]:
|
||||
async with self.telemetry.span("workflow.judge", session_id=state.get("conversation_key") or state.get("session_id"), input={"question": state.get("user_text"), "answer": state.get("final_answer")}):
|
||||
results = await self.judges.evaluate_all(state["user_text"], state["final_answer"], {**(state.get("context") or {}), "mcp_results": state.get("mcp_results", []), "rag_context": state.get("rag_context"), "route": state.get("route"), "intent": state.get("intent")})
|
||||
for result in results:
|
||||
await self.judge_telemetry.evaluated(result)
|
||||
return {"judge_results": [r.model_dump() for r in results]}
|
||||
|
||||
async def supervisor_review(self, state: AgentState) -> dict[str, Any]:
|
||||
async with self.telemetry.span("workflow.supervisor_review", session_id=state.get("conversation_key") or state.get("session_id"), input=state.get("final_answer")):
|
||||
ok, answer = await self.supervisor.review(state["final_answer"], state.get("context", {}))
|
||||
return {"final_answer": answer if ok else answer}
|
||||
|
||||
async def persist(self, state: AgentState) -> dict[str, Any]:
|
||||
session_id = state.get("conversation_key") or state["session_id"]
|
||||
async with self.telemetry.span("workflow.persist", session_id=session_id, input={"route": state.get("route"), "intent": state.get("intent")}):
|
||||
await self.observer.emit_ic("IC.CONTAS_AGENT_PERSISTED", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "route": state.get("route"), "intent": state.get("intent"), "route_decision": state.get("route_decision"), "judges": state.get("judge_results", []), "mcp_tools": state.get("mcp_tools", []), "mcp_results": state.get("mcp_results", [])}, component="workflow.persist")
|
||||
await self.observer.emit_noc("006", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "route": state.get("route"), "intent": state.get("intent"), "answer_chars": len(state.get("final_answer") or "")}, component="workflow.persist")
|
||||
await self.telemetry.event("agent.completed", {"session_id": session_id, "tenant_id": state.get("tenant_id"), "agent_id": state.get("agent_id"), "route": state.get("route"), "intent": state.get("intent"), "answer_chars": len(state.get("final_answer") or "")})
|
||||
return {"final_answer": state.get("final_answer") or state.get("answer") or ""}
|
||||
|
||||
async def ainvoke(self, state: AgentState, config: dict[str, Any] | None = None):
|
||||
return await self.graph.ainvoke(state, config=config or {"configurable": {"thread_id": state.get("conversation_key") or state.get("session_id")}})
|
||||
Reference in New Issue
Block a user