Interactive

Systems

Most agent demos fall over the moment something real happens. This one is built for real conditions - step through an actual run of my Agentic Integration Service and watch the audit log build, hit the human approval gate, and prove the state is durable by restarting mid-run.

Intake
CRM lookup
Enrichment
Approval gate
Ticketing
Complete

A live model of the Agentic Integration Service. Step through a real run: Azure OpenAI-routed tool calls, a human approval gate on the high-value action, and durable state you can prove by restarting mid-run.

Decision-level audit log

- empty -

Architecture

How it's actually put together

The stepper above shows behavior over time. This is the structural view - services, boundaries, and the state that makes durability possible.

Azure Container AppsClientLangGraphOrchestratorTool RegistryCRM lookup · Enrichment · TicketingHuman-in-the-LoopApproval GateAzure OpenAIrouting decisionsPostgresstate + audit logCI/CDContainer Registrydeploy

Persisted run state

RunState {
  run_id: string
  status: "running" |
    "awaiting_approval" |
    "complete"
  tool_calls: ToolCall[]
  pending_action?: Action
  checkpoint_at: timestamp
  audit_log: AuditEntry[]
}

Why this shape

  • status lets a resumed run pick up exactly where it paused, instead of restarting from scratch.
  • checkpoint_at is written before the gate opens, so a mid-approval restart never loses the pending action.
  • audit_log is append-only - every step is replayable after the fact.

Deep dives

How the systems are built

Agentic Integration Service

LangGraph + Azure

Deployed live

A multi-step enterprise agent that gates high-value actions behind human approval, writes idempotently, and keeps durable state that outlives a container restart.

  • Orchestrates 3 enterprise tools (CRM lookup, enrichment, ticketing) via Azure OpenAI-driven routing.
  • High-value actions gated behind human-in-the-loop approval; idempotent writes; decision-level audit log that replays every step.
  • State persisted to Postgres so a pending approval survives a full container restart; resilient pooled DB connections.
  • Containerised and deployed to Azure Container Apps with CI/CD and runtime-injected secrets.
LangGraphAzurePostgresHuman-in-the-loop

AI Data Copilot

NL→SQL · FastAPI

Deployed & evaluated

Ask a question in plain English, get an answer from the database with read-only guardrails and a self-correction loop that repairs failed queries.

  • Evaluated on a reference-SQL golden set with order-independent result comparison.
  • 91.7% execution accuracy (11/12 answerable) and 100% refusal correctness.
  • Self-correction loop lifted accuracy 83.3% → 91.7%. Deterministic, reproducible runs.
  • FastAPI + Streamlit, deployed on Azure Container Apps.
FastAPINL→SQLGuardrailsStreamlit
Open live app ↗

RAG / Agent Evaluation Harness

Reusable quality platform

Scores retrieval and generation on hit-rate, MRR, faithfulness, answer-relevance and refusal-rate inside a regression suite so quality is tracked over time, not assumed.

  • Retrieval metrics: hit-rate and MRR.
  • Generation metrics: faithfulness, answer-relevance, refusal-rate.
  • Runs as a regression suite so model or prompt changes can't silently degrade quality.
EvaluationRegressionRetrieval

MCP Tool Connector

FastMCP

An MCP server exposing Pydantic-validated tool contracts typed, safe interfaces for agent tool use.

  • Exposes tools over the Model Context Protocol with strict, typed input/output contracts.
  • Pydantic validation makes agent tool calls safe and predictable.
tools/crm.pypython
from fastmcp import FastMCP
from pydantic import BaseModel, Field

mcp = FastMCP("crm-tools")

class LookupAccountInput(BaseModel):
    account_id: str = Field(..., description="CRM account identifier")

class LookupAccountOutput(BaseModel):
    account_id: str
    name: str
    tier: str
    open_tickets: int

@mcp.tool()
def lookup_account(input: LookupAccountInput) -> LookupAccountOutput:
    """Resolve a CRM account by ID. Read-only, idempotent."""
    record = crm_client.get_account(input.account_id)
    return LookupAccountOutput(
        account_id=record.id,
        name=record.name,
        tier=record.tier,
        open_tickets=record.open_ticket_count,
    )
MCPFastMCPPydantic