Mesa de Ayuda IA implements the O (Observability) and C (Costs) pillars of the E-O-C-S framework. Every request processed by the LangGraph orchestrator is exported as an OpenTelemetry trace to Arize Phoenix, giving operators full visibility into which agent nodes ran, how many tokens each Gemini call consumed, and how much that call cost in USD. This page explains how the instrumentation is wired together and how to use the resulting data.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/geremyjampiersalasgarcia-eng/Caso_Practico_Semillero_IA/llms.txt
Use this file to discover all available pages before exploring further.
Arize Phoenix Setup
Phoenix runs as a Docker container alongside PostgreSQL. Thedocker-compose.yml exposes it on port 6006 (UI + OTLP HTTP) and maps OTLP gRPC on port 4317:
lifespan startup hook (app/main.py), the backend configures an OTLPSpanExporter pointed at http://localhost:6006/v1/traces, creates a TracerProvider with a BatchSpanProcessor, and then activates the LangChainInstrumentor to auto-instrument every LangChain and LangGraph call:
PHOENIX_PROJECT_NAME environment variable and appears as Patito_SA_Ventas in the Phoenix UI. The instrumentation is tolerant to failures — if the Phoenix container is not reachable, the backend logs a warning and continues without tracing.
Phoenix must be started before the backend for the
OTLPSpanExporter connection to succeed during lifespan startup. Always run docker-compose up -d postgres phoenix first, wait for the containers to report as healthy, and only then start uvicorn.What Is Traced
BecauseLangChainInstrumentor auto-instruments the entire LangChain/LangGraph stack, every node of the StateGraph is captured as its own OpenTelemetry span without any manual instrumentation in agent code. The graph has the following key nodes:
| Node | Description |
|---|---|
classify | Gemini call at temperature=0 that detects intent category |
agente_catalogo | RAG agent for product catalog and pricing |
agente_politicas | RAG agent for commercial policies |
agente_proceso_ventas | RAG agent for the sales process and CRM |
agente_multimodal | Gemini Vision agent for image analysis |
agente_accion | Function-calling agent for CRM registration |
consolidate | Final Gemini call that merges multi-agent answers |
- Node name — which graph node or chain produced the span
- Input / output — the prompt sent to Gemini and the raw text response
- Token counts —
prompt_token_count(input) andcandidates_token_count(output) from Gemini’susage_metadata - Latency — wall-clock duration of the LLM call in milliseconds
mixta), LangGraph fires three agent nodes in sequence, so you will see a parent trace with multiple child spans — one per agent plus the final consolidator.
Viewing Traces
Once the backend has handled at least one chat request, open http://localhost:6006 in your browser. Phoenix presents:- Traces list — one trace per
POST /api/v1/chatrequest, labeled with theservice.namefrom the resource (Patito_SA_Ventas) - Span waterfall — a hierarchical tree showing the classify span, each agent span, and the consolidate span, with start times and durations
- Token consumption — per-span breakdown of input and output tokens, visible in the span detail panel
- Latency per agent — the waterfall makes it easy to identify which agent is slowest
Cost Metrics
In addition to Phoenix traces, every request is recorded in theaudit_logs PostgreSQL table by ChatService. The full set of columns defined in app/models/audit_log.py is:
| Column | Type | Description |
|---|---|---|
id | String | UUID primary key, auto-generated |
intent_category | String | Classified intent (catalogo_precios, politicas_comerciales, etc.) |
agents_invoked | JSON | List of agent node names that ran for this request |
sources_retrieved | JSON | List of RAG source snippets returned by the agents |
latency_ms | Float | Total request latency in milliseconds |
tokens_used | Integer | Combined total token count (input + output) |
conversation_id | String | ID of the conversation this request belongs to |
trace_id | String | OpenTelemetry trace ID linking back to the Phoenix span |
tokens_input | Integer | Prompt token count from Gemini usage_metadata |
tokens_output | Integer | Completion token count from Gemini usage_metadata |
cost_usd | Float | Computed cost: (tokens_input / 1_000_000 × $0.075) + (tokens_output / 1_000_000 × $0.30) |
cost_usd is computed directly in ChatService.process_chat() using Gemini Flash pricing rates:
GET /api/v1/metrics/costs endpoint:
audit_logs using a SQLAlchemy aggregate query grouped by intent_category, so it reflects every conversation processed since the backend started writing audit records.
Structured Logging
All backend components write structured JSON logs via structlog. The logger is initialised inapp/utils/logger.py and configured with the following processors:
add_log_level— adds the severity level to every log entryadd_logger_name— includes the Python module nameTimeStamper(fmt="iso")— ISO 8601 timestampsStackInfoRenderer— attaches stack-frame information when presentformat_exc_info— formats exception tracebacks into the log recordJSONRenderer— outputs each log line as a single-line JSON object for easy ingestion into log aggregators
LOG_LEVEL environment variable (default: INFO). Set it to DEBUG in .env to see prompt content and retriever results: