Saltar al contenido principal

Desafío 03 — Orquestador verificable

🏛️ Escenario empresarial

Empresa: Vantage Analytics — una firma de servicios de datos financieros que vende reportes de inteligencia de mercado generados por IA a inversionistas institucionales
Situación: FINRA ha abierto una revisión de tu sistema de reportes con IA. La pregunta es: "For each figure in your Q1 2026 AI-generated report, can you demonstrate it came directly from a data source, was computed deterministically, and was not altered by the AI model?"
Arquitectura actual: Simple Agentic — el LLM obtiene datos, hace cálculos dentro del contexto y formatea la salida.
Respuesta actual a FINRA: no. No puedes rastrear ninguna cifra hasta su fuente.

Tienes 30 días para rediseñar la arquitectura antes de la auditoría formal.


La idea en 30 segundos

Lo que vas a construir: un agente de reportes donde el LLM nunca toca un número. Solo convierte la solicitud del usuario en parámetros estructurados; luego, código determinista hace la obtención de datos, el cálculo y el formateo, y cada cifra lleva un source_ref que puedes entregar a un auditor.

El principio clave: el LLM decide qué calcular. Nunca lo calcula.

❌ Simple Agentic — la trampa   (el modelo obtiene y calcula, así que un número incorrecto se ve exactamente igual que uno correcto)

✅ Verifiable Orchestrator — la corrección   (el LLM solo emite parámetros; el código determinista produce cada número)

🏗️ Llévalo a un cliente — componentes reales de Azure, tabla de decisión y discurso de venta

Simple Agentic vs Verifiable Orchestrator

ComponenteSimple AgenticVerifiable Orchestrator
Intent parsingLLMLLM
Data fetchingEl LLM decide parámetros de herramientas de forma probabilísticaEl LLM emite parámetros estructurados → obtención determinista
CalculationAritmética del LLM (predicción de tokens)Matemática en Python (determinista)
FormattingLenguaje natural del LLMRenderizado basado en templates
Audit trailNingunoCada valor tiene source_ref
Accuracy guaranteeNinguna100% para valores obtenidos, solo <0.001% de redondeo
Regulatory defensibilityNingunaCompleta — audit log consultable

Qué desplegar realmente

Etapa del pipelineSu única funciónServicio Azure / Microsoft (principal)Alternativa confiable de terceros
Channel / UIDónde pregunta el usuarioMicrosoft Teams (Copilot), Power Apps, Azure Static Web Apps / App ServiceReact SPA, Slack (third-party)
OrchestrationCoordinar el flujo + enrutar herramientasAzure AI Foundry Agent Service · Semantic Kernel (docs)LangGraph, LlamaIndex (third-party)
Intent-only LLMLenguaje → parámetros estructurados solamenteAzure OpenAI gpt-4o + Structured Outputs— (mantener en Azure OpenAI)
Schema validationRechazar cualquier cosa fuera del contratoPydantic v2 / JSON Schemazod (TS) (third-party)
Deterministic computeTodo el cálculo, agregación y formateoAzure Functions (docs)Container job on AKS
System of recordLos datos reales — nunca el LLMAzure SQL Database · Microsoft Fabric / OneLake · Azure Cosmos DB · DataversePostgres, Snowflake (third-party)
Audit log (source_ref)Cadena de custodia inmutable y detectable ante manipulaciónAzure SQL Database Ledger (docs) · temporal tables · WORM Blob
ObservabilitySeparar spans del LLM vs spans deterministasAzure AI Foundry Tracing + Application Insights + Azure MonitorOpenTelemetry + Grafana (third-party)
Identity & secretsAutenticación sin claves + almacenamiento de secretosMicrosoft Entra managed identity · Azure Key VaultHashiCorp Vault (third-party)
GovernancePolítica + clasificación de datosMicrosoft Purview · Azure Policy

Cómo fluye una solicitud

  1. El usuario pregunta en Teams / Power Apps / web → llega al front end.
  2. El orquestador envía el mensaje a Azure OpenAI con Structured Outputs; el modelo puede devolver solo un QuerySpec válido por esquema. Ningún dato en bruto entra al modelo.
  3. La compuerta de validación rechaza cualquier salida fuera de contrato antes de leer una sola fila.
  4. Azure Functions ejecuta la consulta determinista contra el system of record y hace toda la aritmética en código.
  5. Cada valor de salida se escribe en el audit log de Azure SQL Ledger con un source_ref, de forma criptográficamente evidente ante manipulación.
  6. Un template renderiza la respuesta (sin LLM en la ruta de salida); Foundry Tracing mantiene separados los spans del LLM y los de cómputo.

🟦 La frase que cierra tratos regulados: "the LLM decides what to compute; it never computes it — and Azure SQL Ledger makes every figure tamper-evident." Esa frase responde la pregunta de FINRA del escenario — was this number altered by the AI?provably no.


🧰 Antes de empezar — configuración del entorno

Este desafío trata sobre determinismo demostrable, así que tu entorno debe permitirte volver a ejecutar exactamente el mismo cálculo y obtener resultados idénticos byte a byte. El LLM solo interpreta la intención; un motor determinista hace toda la aritmética.

Prerrequisitos

RequisitoPor qué lo necesitasCómo comprobarlo
Python 3.10+Orquestador + motor deterministapython --version
Azure OpenAI mediante Azure AI Foundry con Structured OutputsObligar al LLM a emitir un QuerySpec validado por esquema y nada másDespliega gpt-4o + structured outputs
Un motor SQL determinista — Azure SQL Database o Microsoft Fabric (prod); DuckDB localLa misma consulta debe devolver siempre el mismo número — esta es la base de tu auditoríaAzure portal / pip show duckdb
Un almacén append-only de auditoría — Azure SQL o Cosmos DB (prod); archivo local aquíCadena de custodia inmutable para cada cifraAzure portal / mkdir .audit
Azure AI Foundry — TracingRegistrar por separado spans del LLM y de herramientas deterministasDocs

Paso 0 — Crea un espacio de trabajo aislado (5 min)

Dónde ejecutar esto: todo el Paso 0 corre localmente en tu propia máquina. Abre una terminal (la terminal integrada de VS Code, PowerShell o bash) en la carpeta donde guardas tus proyectos. No tocas Azure ni la nube hasta el Paso 1. Un entorno virtual (venv) mantiene aislados los paquetes de este desafío para que nada de lo que instales aquí afecte otro proyecto.

mkdir verifiable-orchestrator && cd verifiable-orchestrator
python -m venv .venv
# Windows (PowerShell): .venv\Scripts\Activate.ps1 | macOS/Linux: source .venv/bin/activate
pip install azure-ai-projects azure-identity openai pydantic duckdb python-dotenv
mkdir .audit # local stand-in for the Azure SQL / Cosmos DB audit log

Listo cuando tu prompt muestra (.venv) y pip list incluye azure-ai-projects.

Paso 1 — Aprovisiona tu modelo e inicia sesión (10 min)

Este desafío obliga al LLM a emitir un QuerySpec validado por esquema mediante Structured Outputs, así que necesitas un gpt-4o desplegado. Si todavía no tienes uno, realiza los Pasos 1–2 de Desafío 01 — Auditoría de alucinaciones para seguir el recorrido exacto del portal y obtener los dos valores siguientes; luego crea un .env:

# .env — from Azure AI Foundry (never commit this file)
# PROJECT_ENDPOINT=https://<your-project>.services.ai.azure.com/api/projects/<name>
# MODEL_DEPLOYMENT_NAME=gpt-4o
az login # keyless auth via DefaultAzureCredential

Confirma que tu modelo soporta Structured Outputs y haz una prueba rápida de la conexión (structured outputs reference):

# smoke_test.py — prints "setup works" when endpoint + deployment + az login are all correct
import os
from dotenv import load_dotenv
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
load_dotenv()
project = AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=DefaultAzureCredential())
client = project.inference.get_azure_openai_client(api_version="2024-10-21")
print(client.chat.completions.create(model=os.environ["MODEL_DEPLOYMENT_NAME"],
messages=[{"role":"user","content":"Reply with exactly: setup works"}]).choices[0].message.content)

Correcciones comunes: DefaultAzureCredential failed → ejecuta az login otra vez. DeploymentNotFound → el deployment name no coincide. 401 → asigna el rol Azure AI User al proyecto.

Paso 2 — Carga un dataset CONOCIDO (10 min)

Carga una tabla pequeña de precios con valores que ya conozcas (estos son sample values, not real market data). Como conoces los números verdaderos, puedes demostrar que tu motor los devuelve exactamente.

# seed.py — sample values only, NOT real market data
ROWS = [
("NFLX", "2026-03-14", 605.88),
("NFLX", "2026-03-15", 611.20),
]
# In production this is an Azure SQL table or a Fabric Lakehouse table.

🟦 Nota Microsoft-first: DuckDB y la carpeta .audit son sustitutos locales para que puedas trabajar offline. En producción, el motor determinista es Azure SQL Database o un warehouse de Microsoft Fabric (SQL es determinista por definición), y el audit log append-only vive en Azure SQL o Azure Cosmos DB. El patrón de orquestación es idéntico.

La ruta a través de este desafío

  1. Tarea 1 — escribe el contrato intent-only del LLM (structured outputs).
  2. Tarea 2 — construye la capa de cómputo determinista.
  3. Tarea 3 — construye el generador de salida auditable (source_ref).
  4. Tarea 4 — demuestra capacidad de defensa regulatoria (prove_value()).
  5. Criterios de éxito — cada número se rastrea hasta una fila + fórmula.
  6. Adáptalo a tu negocio — aplica esto a tus números regulados.

⏱️ Presupuesto de tiempo: ~3–4 horas. La garantía de auditoría se gana en el motor determinista (Tarea 2); invierte ahí.


Tareas

Tarea 1 — Diseña el contrato intent-only del LLM

Todo el trabajo del LLM consiste en convertir lenguaje natural en una especificación estructurada de consulta. Nunca ve datos en bruto.

# intent_parser.py
from pydantic import BaseModel
from typing import Optional, List
from enum import Enum

class MetricType(str, Enum):
CLOSE = "close"
OPEN = "open"
HIGH = "high"
LOW = "low"
VOLUME = "volume"
ADJ_CLOSE = "adj_close"

class AggregationType(str, Enum):
NONE = "none" # return raw rows
PERCENT_RETURN = "pct_return"
MAX = "max"
MIN = "min"
AVERAGE = "avg"
SUM = "sum"

class FinancialQuerySpec(BaseModel):
"""
Structured query specification output by LLM.
All fields are deterministic primitives — no prose, no calculations.
"""
tickers: List[str] # ["NFLX", "AMZN"]
start_date: str # "2024-03-15" (YYYY-MM-DD)
end_date: str # "2025-03-14"
metric: MetricType # what column to retrieve
aggregation: AggregationType # what computation to perform
comparison: bool = False # compare across tickers?
intent_summary: str # human-readable summary for audit log

INTENT_SYSTEM_PROMPT = """
You are a financial query parser. Convert user questions into structured query specifications.

CRITICAL RULES:
1. Output ONLY valid JSON matching the FinancialQuerySpec schema
2. Do NOT perform any calculations
3. Do NOT include any data values in your output
4. Do NOT add commentary or explanation
5. If the query is ambiguous, choose the most conservative interpretation

Today's date: {current_date}

Respond with JSON only.
"""

def parse_intent(user_query: str, current_date: str) -> FinancialQuerySpec:
"""
Single LLM call with constrained output schema.
LLM sees: user query + today's date.
LLM outputs: structured parameters only.
LLM never sees: raw data, calculation results, or previous tool outputs.
"""
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import ResponseFormatJsonSchema

client = AIProjectClient.from_connection_string(
conn_str=os.environ["AZURE_AI_PROJECTS_CONNECTION_STRING"],
credential=DefaultAzureCredential()
)

response = client.agents.create_and_process_run(
agent_id=INTENT_PARSER_AGENT_ID,
thread_messages=[
{"role": "system", "content": INTENT_SYSTEM_PROMPT.format(current_date=current_date)},
{"role": "user", "content": user_query}
],
response_format=ResponseFormatJsonSchema(
name="FinancialQuerySpec",
schema=FinancialQuerySpec.model_json_schema()
)
)

return FinancialQuerySpec.model_validate_json(response.content)

Idea clave: la llamada al LLM usa ResponseFormatJsonSchema; la respuesta queda validada por esquema antes de llegar a tu código. El LLM no puede emitir prosa, no puede incluir valores de datos y no puede agregar contexto alucinado.


Tarea 2 — Construye la capa de cómputo determinista

Toda la matemática ocurre aquí, en Python, con trazabilidad completa de la fuente.

# deterministic_engine.py
import duckdb
import hashlib
import json
from datetime import datetime
from typing import Optional

class ComputationResult:
def __init__(self, value, source_ref: str, computation_log: list):
self.value = value
self.source_ref = source_ref # e.g., "stock_prices:NFLX:2024-03-15:close"
self.computation_log = computation_log # step-by-step audit trail

class DeterministicEngine:

def __init__(self, db_path: str):
self.conn = duckdb.connect(db_path, read_only=True)

def execute(self, spec: FinancialQuerySpec) -> dict:
"""
Fetches data and performs computation entirely in Python.
Returns results with full audit trail.
"""
audit_log = []
results = {}

for ticker in spec.tickers:
# Step 1: Fetch raw rows
rows = self._fetch_rows(ticker, spec.start_date, spec.end_date, spec.metric)
audit_log.append({
"step": "fetch",
"ticker": ticker,
"query": f"SELECT {spec.metric} FROM stock_prices WHERE ticker='{ticker}' AND date BETWEEN '{spec.start_date}' AND '{spec.end_date}'",
"row_count": len(rows),
"query_hash": self._hash_query(ticker, spec)
})

# Step 2: Apply aggregation in Python (never in LLM)
computed = self._aggregate(rows, spec.aggregation, spec.metric)
audit_log.append({
"step": "compute",
"ticker": ticker,
"aggregation": spec.aggregation,
"input_values": [r[spec.metric] for r in rows[:5]], # sample for audit
"result": computed.value,
"formula": self._describe_formula(spec.aggregation)
})

results[ticker] = ComputationResult(
value=computed.value,
source_ref=f"stock_prices:{ticker}:{spec.start_date}:{spec.end_date}:{spec.metric}:{spec.aggregation}",
computation_log=audit_log.copy()
)

return results

def _fetch_rows(self, ticker, start_date, end_date, metric):
return self.conn.execute(
f"SELECT date, {metric} FROM stock_prices "
f"WHERE ticker=? AND date BETWEEN ? AND ? ORDER BY date",
[ticker, start_date, end_date]
).fetchdf().to_dict(orient="records")

def _aggregate(self, rows: list, aggregation: AggregationType, metric: str) -> ComputationResult:
values = [row[metric] for row in rows if row[metric] is not None]

if aggregation == AggregationType.NONE:
return ComputationResult(values, "raw", [])
elif aggregation == AggregationType.PERCENT_RETURN:
# Formula: (last - first) / first * 100
pct = ((values[-1] - values[0]) / values[0]) * 100
return ComputationResult(
round(pct, 4),
f"pct_return:({values[-1]}-{values[0]})/{values[0]}*100",
[{"first": values[0], "last": values[-1]}]
)
elif aggregation == AggregationType.MAX:
max_val = max(values)
max_date = rows[[r[metric] for r in rows].index(max_val)]["date"]
return ComputationResult(max_val, f"max_of_{len(values)}_values:date={max_date}", [])
# ... other aggregations

def _hash_query(self, ticker, spec) -> str:
"""Content-addressable hash of the exact query — for immutable audit log."""
query_str = f"{ticker}:{spec.start_date}:{spec.end_date}:{spec.metric}:{spec.aggregation}"
return hashlib.sha256(query_str.encode()).hexdigest()[:16]

def _describe_formula(self, aggregation: AggregationType) -> str:
formulas = {
AggregationType.PERCENT_RETURN: "(last_close - first_close) / first_close * 100",
AggregationType.MAX: "max(values)",
AggregationType.MIN: "min(values)",
AggregationType.AVERAGE: "sum(values) / count(values)",
}
return formulas.get(aggregation, "raw")

Tarea 3 — Construye el generador de salida auditable

Formatea la salida a partir de resultados de cómputo, nunca a partir de prosa generada por el LLM.

# output_generator.py
import json
from datetime import datetime

class AuditableReport:
"""
Generates output from deterministic computation results.
Every value in the output has a traceable source_ref.
"""

def __init__(self, query_spec: FinancialQuerySpec, results: dict):
self.spec = query_spec
self.results = results
self.generated_at = datetime.utcnow().isoformat()

def to_markdown(self) -> str:
"""Generate human-readable report with inline source references."""
lines = [
f"## {self.spec.intent_summary}",
f"*Generated: {self.generated_at} | Query: {self.spec.start_date}{self.spec.end_date}*",
"",
"| Ticker | Value | Source Reference |",
"|--------|-------|-----------------|"
]

for ticker, result in self.results.items():
formatted_value = self._format_value(result.value, self.spec.metric, self.spec.aggregation)
lines.append(f"| {ticker} | {formatted_value} | `{result.source_ref}` |")

return "\n".join(lines)

def to_audit_record(self) -> dict:
"""
Machine-readable audit record for regulatory submission.
Contains complete provenance for every value.
"""
return {
"report_id": self._generate_report_id(),
"generated_at": self.generated_at,
"query_spec": self.spec.model_dump(),
"values": {
ticker: {
"value": result.value,
"source_ref": result.source_ref,
"computation_steps": result.computation_log,
"formula": result.computation_log[-1].get("formula") if result.computation_log else None
}
for ticker, result in self.results.items()
}
}

def _format_value(self, value, metric, aggregation) -> str:
if aggregation == AggregationType.PERCENT_RETURN:
return f"{value:+.2f}%"
elif metric in ["close", "open", "high", "low", "adj_close"]:
return f"${value:,.2f}"
elif metric == "volume":
return f"{value:,}"
return str(value)

def _generate_report_id(self) -> str:
import hashlib
content = json.dumps(self.spec.model_dump(), sort_keys=True)
return hashlib.sha256(content.encode()).hexdigest()[:12]

# Usage
def answer_query(user_question: str) -> tuple[str, dict]:
"""
Full Verifiable Orchestrator pipeline.
Returns (human_readable_answer, audit_record).
"""
from datetime import date

# 1. LLM parses intent ONLY
spec = parse_intent(user_question, current_date=date.today().isoformat())

# 2. Deterministic engine fetches + computes
engine = DeterministicEngine(db_path="market_data.duckdb")
results = engine.execute(spec)

# 3. Template-based output (no LLM involvement)
report = AuditableReport(spec, results)

# 4. Persist audit record
audit_record = report.to_audit_record()
persist_to_audit_log(audit_record)

return report.to_markdown(), audit_record

Tarea 4 — Demuestra capacidad de defensa regulatoria

Simula la consulta de auditoría de FINRA. Dado un reporte, demuestra cada cifra.

# audit_query.py
def prove_value(report_id: str, ticker: str, value: float) -> dict:
"""
Given a report ID, ticker, and value — reconstruct the exact
data retrieval and calculation that produced it.
FINRA answer: "Here is the SQL, the raw rows, the formula, and the result."
"""
# Load audit record
audit_record = load_audit_log(report_id)
value_record = audit_record["values"].get(ticker)

if not value_record:
return {"found": False, "report_id": report_id, "ticker": ticker}

# Reconstruct the query
spec = FinancialQuerySpec(**audit_record["query_spec"])
engine = DeterministicEngine(db_path="market_data.duckdb")

# Re-execute deterministically — result must match
re_computed = engine.execute(spec)
re_computed_value = re_computed[ticker].value

match = abs(float(value) - float(re_computed_value)) < 0.01

return {
"found": True,
"original_value": value,
"recomputed_value": re_computed_value,
"values_match": match,
"source_ref": value_record["source_ref"],
"sql_query": value_record["computation_steps"][0]["query"],
"formula_applied": value_record["formula"],
"computation_steps": value_record["computation_steps"],
"defensible": match
}

Criterios de éxito

  • El LLM nunca ve datos en bruto — solo emite un FinancialQuerySpec estructurado
  • Toda la aritmética se realiza en Python — verificable al volver a ejecutar la misma función
  • Cada valor de la salida tiene un source_ref que apunta a filas exactas de la DB y a la fórmula aplicada
  • prove_value() devuelve defensible: true para cada número de un reporte de prueba
  • El sistema maneja con elegancia fallas de validación de esquema del LLM (pide aclaración al usuario; no alucina)
  • El audit log es append-only y puede consultarse por report ID, ticker y rango de fechas

🔁 Adapta esto a tu propio negocio

El escenario es un reporte financiero bajo auditoría FINRA, pero el patrón aplica a cualquier negocio donde un número deba ser demostrablemente correcto y trazable; es decir, donde "la IA probablemente acertó" no sea suficiente.

Paso 1 — Encuentra tu momento de "cada número debe ser defendible"

IndustriaLos números de alto riesgoQuién los audita
Financial servicesRendimientos, métricas de riesgo, valores de portafolioFINRA / SEC / auditores
Healthcare billingMontos de reclamaciones, codificación, reembolsosCMS / pagadores
InsurancePrimas, reservas, cálculos de pagoReguladores estatales / actuarios
Energy / commoditiesPrecios de liquidación, cálculos de volumenFERC / bolsas
Supply chainLanded cost, aranceles, valoración de inventarioAduanas / finanzas
Tax & accountingMontos gravables, depreciación, créditosIRS / auditores externos

Si un número incorrecto puede desencadenar una multa, una reformulación contable o una demanda, necesitas el Verifiable Orchestrator.

Paso 2 — Mapea los bloques a tu stack (Microsoft-first)

En este desafíoEn tu proyecto — reemplázalo por
Intent parser (LLM)Azure OpenAI Structured Outputs — solo parámetros validados por esquema
DuckDB engineAzure SQL Database o warehouse de Microsoft Fabric (SQL determinista)
source_ref on each valueUn puntero a fila/fórmula almacenado con cada campo de salida
Append-only audit logAzure SQL (temporal tables) o Azure Cosmos DB
prove_value()Un stored procedure / API que reproduzca la consulta exacta
LLM-vs-tool span separationAzure AI Foundry Tracing + Application Insights

Paso 3 — Checklist de implementación de 5 preguntas

  1. ¿Tu LLM hace aritmética en algún punto? Si sí → mueve todas las matemáticas a código/SQL determinista. El LLM solo interpreta intención.
  2. ¿Puedes volver a ejecutar cualquier salida y obtener el número idéntico? Si no → tu motor aún no es determinista.
  3. ¿Cada número mostrado lleva un puntero a su fila fuente + fórmula? Si no → agrega source_ref.
  4. ¿Tu audit log es append-only e inmutable? Si puede editarse → no es defendible.
  5. ¿Puedes responder "prove this number" en menos de un minuto? Si no → construye la ruta prove_value().

Paso 4 — Plan de despliegue de 1 semana

DíaAcciónResponsable
Day 1Inventariar cada número producido por IA y su blast radiusCompliance + eng
Day 2Mover el intent parsing a Azure OpenAI Structured OutputsBackend dev
Day 3Mover todo el cómputo a Azure SQL / Fabric (determinista)Data eng
Day 4Agregar source_ref + audit log append-only (Azure SQL / Cosmos)Backend dev
Day 5Construir prove_value() y ejecutar una auditoría simuladaEng + compliance

Paso 5 — Demuestra el ROI

  • Cobertura de trazabilidad — % de números de salida con source_ref válido (objetivo: 100%).
  • Reproducibilidad — % de salidas que se recomputan al valor idéntico (objetivo: 100%).
  • Tiempo de respuesta de auditoría — minutos para demostrar cualquier cifra individual (objetivo: menos de 1 min).

💡 Regla práctica: el LLM debe decidir qué calcular, nunca calcularlo. Si un regulador no puede volver a ejecutar tu cifra y obtener la misma respuesta, entonces no es defendible, por muy bueno que sea el modelo.

Hacerlo por tu cuenta (sin equipo, con foco en portafolio)

¿Sin equipo ni presupuesto? "Every number is provable and reproducible" es exactamente la disciplina que buscan los empleadores regulados. Puedes hacerlo en una semana tú solo:

  • Lun–Mar — saca toda la aritmética del LLM y llévala a SQL (SQLite/DuckDB local por ahora); el LLM solo interpreta intención mediante Structured Outputs.
  • Mié–Jue — agrega un source_ref a cada valor + un audit log append-only + una ruta de replay prove_value().
  • Vie — ejecuta una auditoría simulada: toma 5 números de salida y demuestra cada uno en menos de un minuto.

📦 Entrega este artefacto: un repositorio público donde cualquier número de salida se recompute al valor idéntico, más una demo corta de prove_value(). Bullet para CV: "Built a verifiable analytics agent — 100% of output numbers reproducible and source-traceable, any figure provable to an auditor in under a minute."

🆓 Ruta de costo mínimo: SQLite/DuckDB + la capa de consumo de Azure OpenAI; demostrar cómputo determinista prácticamente no cuesta.


📋 Mapeo regulatorio — FINRA · SEC · EU AI Act · SOX · MiFID II
RegulaciónRequisitoCómo lo aborda este desafío
FINRA Rule 4511Books and records — conservar datos con trazabilidadsource_ref + audit log por reporte
SEC Rule 17a-4Registros inmutables para broker-dealersAudit log append-only con hash de consulta
EU AI Act Art. 13Transparencia — logging de la operación del sistema de IALog completo de cómputo para cada salida
SOX Section 302/906Certificación de exactitud financiera por CEO/CFOprove_value() aporta evidencia para la certificación
MiFID IIAudit trail para asesoría de inversiónReport ID + comprobación de defensibilidad

🧪 Break & Fix — identifica por qué tres "arreglos" plausibles reintroducen la caja negra
# broken_orchestrator.py

def answer_query(question):
spec = parse_intent(question)
engine = DeterministicEngine("data.duckdb")
results = engine.execute(spec)

# "Fix" 1: Let LLM format the final output for better readability
llm_response = llm.generate(
f"Format this data nicely: {results}" # ← what breaks here?
)
return llm_response

def prove_value(report_id, ticker, value):
record = load_audit_log(report_id)
# "Fix" 2: Check if value is in the audit log
return value in str(record) # ← why is this inadequate for FINRA?

def parse_intent(question):
# "Fix" 3: Use free-form LLM output for flexibility
raw = llm.generate(f"Extract: ticker, dates, metric from: {question}")
return parse_free_form(raw) # ← what's the reliability risk?

Haz clic para ver las respuestas

  1. El formateo con LLM reintroduce la caja negra: aunque el cómputo sea determinista, si dejas que el LLM formatee la salida, puede reformular, redondear distinto o mezclar mal los números. La salida deja de ser totalmente trazable. Usa solo renderizado basado en templates.
  2. Buscar una cadena no es prueba: value in str(record) devuelve True si 174.4 aparece en cualquier parte del registro, incluso como coincidencia parcial de 174.42. Eso no demuestra que el valor provino de una fila específica de la DB mediante una fórmula específica. FINRA exige una cadena de custodia completa.
  3. El parsing libre del LLM es no determinista: la misma pregunta formulada de dos maneras puede producir parámetros distintos. Usar ResponseFormatJsonSchema con validación de esquema garantiza que la salida del LLM siempre sea parseable y coincida con los tipos esperados.

Comprobación de conocimientos

  1. En el Verifiable Orchestrator el LLM sigue presente. ¿Por qué eso es aceptable para fines regulatorios cuando la participación del LLM en Simple Agentic no lo es?
  2. Un usuario pregunta: "Compare Netflix Q1 2024 vs Q1 2025 returns." El intent parser produce start_date: 2024-01-01, end_date: 2024-03-31 para el primer periodo. ¿Cómo maneja el sistema el segundo periodo y dónde se define el calendario fiscal?
  3. Tu motor determinista calcula el percent return de NFLX como 51.5234%. El reporte muestra 51.52%. ¿Es defendible ante un regulador? ¿Por qué sí o por qué no?
  4. Un competidor dice: "Just use Claude's extended thinking — it's more accurate at math than GPT-4." ¿Por qué eso no resuelve el problema de verificabilidad?

📚 Herramientas y referencias

Herramientas clave para este desafío

Microsoft-first: prioriza herramientas nativas de Azure. Las herramientas de terceros se listan solo cuando aportan una capacidad confiable y de primer nivel que aún no está cubierta de forma nativa.

HerramientaFunción en este desafíoEnlace
Azure SQL Database / Microsoft FabricMotor de cómputo determinista — SQL devuelve el mismo resultado cada vez y te da un rastro de auditoría demostrableAzure SQL · Fabric
Azure OpenAI — Structured OutputsObligar al LLM a emitir un QuerySpec validado por esquema y nada más — intent parsing tipado y no negociableDocs
Azure Cosmos DBAudit log append-only e inmutable — cadena de custodia para cada cifraDocs
Azure AI Foundry TracingCapturar llamadas del LLM y llamadas deterministas a herramientas como tipos de span distintos — visibles en Application InsightsDocs
Azure Monitor / Application InsightsConsultar y alertar sobre el log de cómputo; retener evidencia de auditoríaDocs
DuckDB (third-party)Sustituto local y offline del motor determinista mientras lo construyes — las consultas SQL devuelven resultados idénticos en cada ejecuciónduckdb.org
Pydantic v2 (third-party)Validación local de esquema para intención parseada cuando no usas Structured Outputsdocs.pydantic.dev
Great Expectations (third-party)Validación de contratos de datos — asegurar que los datasets cumplen el esquema esperado antes de ejecutarlosgreatexpectations.io

Lectura obligatoria

RecursoPor qué importa
The Verifiable Orchestrator (Part 2)El artículo fuente de este desafío — explica la arquitectura TRACE con patrones de código completos
Azure OpenAI Structured OutputsCómo garantizar que el LLM solo emita parámetros de consulta válidos por esquema
Azure AI Foundry Tracing SetupCómo separar en producción los spans de herramientas deterministas de los spans de razonamiento del LLM
FINRA Rule 4511 — Books and RecordsLa regulación real que gobierna la conservación de registros en broker-dealers: qué significa de verdad "defensible ante un regulador"