Production & Evals ⏱️ 12 min read

End-to-End Capstone: Architecting an Autonomous Financial Analyst Agent

Bringing the entire series together: a production-grade autonomous financial analyst agent combining Hybrid RAG, LangGraph cyclic state machines, database tools, HITL approvals, and LangSmith tracing.

Across the preceding 16 articles, we built each subsystem of agentic intelligence in isolation:
- Module 1: Cognitive anatomy, deterministic tool schemas, stratified memory, and planning.
- Module 2: Hierarchical chunking, hybrid vector search, and Corrective RAG (CRAG).
- Module 3: Cyclic state graphs, durable checkpointing, human-in-the-loop gates, and multi-agent routing.
- Module 4: Distributed tracing with LangSmith, trajectory evals, guardrails, and cost optimization.

Now, we synthesize these components into a single, cohesive, production-grade application: The Autonomous Financial Analyst Agent.

This agent ingests raw 10-K SEC filings via Hybrid RAG, queries live SQL databases for ledger balances, executes calculations via tools, requests human authorization before issuing compliance flags, and streams telemetry to LangSmith.


1. System Architecture Blueprint

                                  [User Audit Request]
                                           │
                                           ▼
                                ┌─────────────────────┐
                                │   INPUT SANITIZER   │ (PII & Injection Guard)
                                └──────────┬──────────┘
                                           │
                                           ▼
                                ┌─────────────────────┐
                                │   SUPERVISOR NODE   │ ◄──────────────────────┐
                                └──────────┬──────────┘                        │
                     ┌─────────────────────┼─────────────────────┐             │
                     ▼                     ▼                     ▼             │
             ┌───────────────┐     ┌───────────────┐     ┌───────────────┐     │
             │   RAG NODE    │     │   SQL NODE    │     │ COMPUTE NODE  │     │
             │ (10-K Hybrid) │     │ (Postgres DB) │     │ (Spread Calc) │     │
             └───────┬───────┘     └───────┬───────┘     └───────┬───────┘     │
                     │                     │                     │             │
                     └─────────────────────┼─────────────────────┘             │
                                           │                                   │
                                           ▼                                   │
                                ┌─────────────────────┐                         │
                                │   AUDIT RISK GATE   │                         │
                                └──────────┬──────────┘                         │
                                           │                                   │
                              ┌────────────┴────────────┐                      │
                              │ Is Action Critical?     │                      │
                              ├─────────────┬───────────┤                      │
                              │ (Yes)       │ (No)      │                      │
                              ▼             ▼           │                      │
                       [HITL Breakpoint] [Continue] ────┴──────────────────────┘
                              │
                              ▼
                     [Executive Summary]

2. Defining the Unified State Schema

from typing import TypedDict, Annotated, Sequence, List, Optional
import operator
from langchain_core.messages import BaseMessage
from pydantic import BaseModel, Field

class AuditRecord(BaseModel):
    account_id: str
    risk_level: str
    discrepancy_amount: float
    justification: str

class FinancialAnalystState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    current_worker: str
    retrieved_sec_filings: List[str]
    sql_records: List[dict]
    audit_flags: List[AuditRecord]
    requires_human_approval: bool
    is_completed: bool

3. Tool Implementations (SQL & RAG)

from langchain_core.tools import tool

@tool
def query_sec_filing(ticker: str, inquiry: str) -> str:
    """Performs hybrid vector retrieval against SEC 10-K regulatory filings."""
    # Simulates our Post 06 & 07 Hybrid RAG engine
    return (
        f"[SEC 10-K Filing for {ticker}]: In Q3, liquidity reserves were measured at $4.2B, "
        f"with outstanding credit facility obligations of $1.1B."
    )

@tool
def query_general_ledger(account_id: str) -> str:
    """Queries internal relational database for live general ledger balances."""
    # Simulates safe parameterized SQL execution
    ledger = {
        "ACC-8812": {"cash_balance": 920000000.00, "status": "FLAGGED_INCONSISTENCY"},
        "ACC-9901": {"cash_balance": 3100000000.00, "status": "VERIFIED"}
    }
    return str(ledger.get(account_id, {"error": "Account not found"}))

4. The Complete LangGraph Workflow

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage

# Initialize LLM with tool bindings
llm = ChatOpenAI(model="gpt-4o", temperature=0.0)

def analyst_supervisor_node(state: FinancialAnalystState) -> dict:
    """Evaluates audit progress and routes to specialized worker or compliance gate."""
    last_msg = state["messages"][-1]

    # Check if a critical discrepancy was found
    if "FLAGGED_INCONSISTENCY" in str(state["messages"]):
        return {
            "current_worker": "compliance_gate",
            "requires_human_approval": True
        }

    if len(state["retrieved_sec_filings"]) == 0:
        return {"current_worker": "sec_researcher"}
    elif len(state["sql_records"]) == 0:
        return {"current_worker": "sql_auditor"}
    else:
        return {"current_worker": "synthesizer"}

def sec_researcher_node(state: FinancialAnalystState) -> dict:
    print("📑 [RESEARCH]: Querying SEC 10-K filings...")
    result = query_sec_filing.invoke({"ticker": "NVDA", "inquiry": "liquidity reserves"})
    return {
        "retrieved_sec_filings": [result],
        "messages": [AIMessage(content=f"SEC Context Ingested: {result}")]
    }

def sql_auditor_node(state: FinancialAnalystState) -> dict:
    print("📊 [SQL AUDITOR]: Interrogating general ledger balances...")
    result = query_general_ledger.invoke({"account_id": "ACC-8812"})
    return {
        "sql_records": [{"raw": result}],
        "messages": [AIMessage(content=f"Ledger Output: {result}")]
    }

def compliance_gate_node(state: FinancialAnalystState) -> dict:
    print("🚨 [COMPLIANCE GATE]: Severe discrepancy detected! Yielding for human signoff.")
    return {
        "messages": [AIMessage(content="CRITICAL ALERT: Cash balance discrepancy exceeds $100M threshold. Awaiting executive signature.")]
    }

def synthesizer_node(state: FinancialAnalystState) -> dict:
    print("📝 [SYNTHESIZER]: Formatting final executive audit memo...")
    return {
        "messages": [AIMessage(content="AUDIT COMPLETED: All internal ledger balances reconciled against SEC filings.")],
        "is_completed": True
    }

5. Wiring the Graph with Breakpoints & Checkpointing

workflow = StateGraph(FinancialAnalystState)

workflow.add_node("supervisor", analyst_supervisor_node)
workflow.add_node("sec_researcher", sec_researcher_node)
workflow.add_node("sql_auditor", sql_auditor_node)
workflow.add_node("compliance_gate", compliance_gate_node)
workflow.add_node("synthesizer", synthesizer_node)

workflow.set_entry_point("supervisor")

# Routing from supervisor
workflow.add_conditional_edges(
    "supervisor",
    lambda state: state["current_worker"],
    {
        "sec_researcher": "sec_researcher",
        "sql_auditor": "sql_auditor",
        "compliance_gate": "compliance_gate",
        "synthesizer": "synthesizer"
    }
)

# Return edges back to supervisor
workflow.add_edge("sec_researcher", "supervisor")
workflow.add_edge("sql_auditor", "supervisor")
workflow.add_edge("compliance_gate", END)
workflow.add_edge("synthesizer", END)

# Attach checkpointer & configure HITL breakpoint
checkpointer = MemorySaver()
app = workflow.compile(
    checkpointer=checkpointer,
    interrupt_before=["compliance_gate"] # Halts execution before compliance alert is sent
)

6. Execution Trace

When executed with session config {"configurable": {"thread_id": "audit_nvda_2024"}}:
1. Supervisor directs sec_researcher to pull SEC liquidity data.
2. Supervisor directs sql_auditor to check general ledger for ACC-8812.
3. SQL Auditor identifies FLAGGED_INCONSISTENCY (cash discrepancy).
4. Supervisor flags requires_human_approval = True and routes to compliance_gate.
5. LangGraph Intercepts: Execution freezes before compliance_gate, state is persisted to database, and the operator is notified via Slack or dashboard.
6. Telemetry: The entire multi-node run tree is logged to LangSmith, complete with token counts, latency distributions, and intermediate state dumps.


Conclusion of the Capstone

You have built a complete, resilient, enterprise-grade autonomous system from first principles. By decomposing tasks into specialized nodes, managing memory deterministically, and wrapping the graph in durable checkpoints and telemetry, your agents can execute mission-critical workloads safely.

Now, as we conclude our series, we look to the horizon.

In [Post 18: The Horizon of Agentic AI], we explore the cutting-edge frontier: self-improving code synthesis loops, dynamic tool creation, and what lies ahead for autonomous software engineering.

← Back to All Publications
B

Bharath

AI Engineer / Data Scientist specializing in Generative AI systems, transformer architectures, prompt engineering patterns, and autonomous agent orchestration. Writing technical breakdowns for production engineers.