Resumen
Traces (1.240)
Evaluación
Dataset QA
Implementación
Traces hoy
1.240
▲ +18% vs ayer
Latencia media
2.4s
▼ −0.3s vs semana pasada
Tasa error
1.2%
▼ −0.5% objetivo ≤2%
Correctness score
0.91
▲ +0.03 vs gpt4o-v1
Coste tokens hoy
$4.32
~$0.0035 por consulta
Traces por hora — últimas 24h
lexflow-production
00:0006:0012:0018:0023:59
Alertas recientes
Timeout Azure OpenAI — 3 traces fallidos consecutivos (clausulas_extraccion)
14:22
Latencia >5s detectada — run_id: f8a2c9d — user: empresa_abc
11:47
Experiment gpt4o-v2 completado — correctness 0.91 > threshold 0.88 ✓
09:15
Resultados de evaluación — Experimento
gpt4o-v2
Dataset: lexflow-clausulas-qa · 10 ejemplos
Correctness Score
0.91 ✓
Conciseness Score
0.84 ✓
Traces recientes — Producción
lexflow-production · últimos 10
| Run ID | Entrada (truncada) | Estado | Latencia | Tokens | Coste | Tags |
|---|---|---|---|---|---|---|
| f8a2c9d1 | ¿Qué pasa si el proveedor incumple el SLA del 99.9%? | ✓ success | 6.2s ⚠ | 1,842 | $0.0068 | beta-usergpt4o |
| a3e8f120 | ¿Cuánto tiempo debe durar la cláusula de no competencia? | ✓ success | 2.1s | 1,204 | $0.0044 | produccionnda |
| 9c7b4e52 | Necesito incluir una cláusula arbitral internacional, ¿cómo? | ✕ error | — | 318 | $0.0009 | enterprise |
| b1d5a7c3 | ¿Qué cláusulas son obligatorias en un contrato de arrendamiento? | ✓ success | 1.9s | 987 | $0.0036 | produccion |
| d4c2e891 | ¿Cómo redactar la cláusula de confidencialidad para un NDA? | ✓ success | 2.4s | 1,531 | $0.0056 | ndagpt4o |
| e6f90234 | ¿Cuál es el preaviso mínimo para rescisión en contratos de servicios? | ✓ success | 4.1s ! | 1,103 | $0.0041 | produccion |
| c5a1b678 | Genera un contrato laboral indefinido para desarrollador senior | ✓ success | 2.8s | 2,341 | $0.0087 | laboralenterprise |
| 77de32f0 | ¿Cuál es la diferencia entre penalización y cláusula penal? | ✓ success | 1.6s | 844 | $0.0031 | produccion |
| 2a4f9c1e | ¿Se puede limitar la responsabilidad civil en B2B España? | ✓ success | 2.2s | 1,067 | $0.0039 | produccionb2b |
| 8b6e4d90 | ¿Cómo se regula la subcontratación en contratos públicos? | ✕ error | — | 0 | $0.0000 | produccion |
Implementación — Código entregado a LexFlow
lexflow_tracing.py
Python 3.11 · LangChain 0.2 · LangSmith
# lexflow_tracing.py — Tracing y evaluación LangSmith para LexFlow # Implementado por CULTIVA IA · 14 jun 2026 import os from langsmith import Client, traceable, evaluate from langchain_openai import AzureChatOpenAI from langchain_core.prompts import ChatPromptTemplate # ── Configuración de entorno ───────────────────────────── os.environ["LANGCHAIN_TRACING_V2"] = "true" os.environ["LANGCHAIN_API_KEY"] = "lsv2_pt_lexflow_xxxxxxxxxxxx" os.environ["LANGCHAIN_PROJECT"] = "lexflow-production" client = Client() # ── RAG chain con tracing automático ───────────────────── llm = AzureChatOpenAI( deployment_name="gpt-4o", temperature=0, metadata={"service": "clausula-qa", "version": "v2"} ) prompt = ChatPromptTemplate.from_template(""" Eres un asistente legal experto en derecho mercantil español. Responde de forma concisa (máximo 80 palabras). Contexto de la cláusula: {context} Pregunta: {question} """) chain = prompt | llm # ── Función principal con @traceable ───────────────────── @traceable( name="lexflow_clausula_qa", tags=["produccion", "gpt4o"], metadata={"environment": "production"} ) def answer_legal_question(question: str, user_id: str) -> dict: context = retrieve_relevant_clauses(question) # RAG retrieval result = chain.invoke( {"question": question, "context": context}, config={"metadata": {"user_id": user_id}} ) return {"answer": result.content, "user_id": user_id} # ── Dataset de evaluación ───────────────────────────────── dataset = client.create_dataset( "lexflow-clausulas-qa", description="10 preguntas reales de clientes LexFlow con respuesta esperada" ) client.create_examples( inputs=[ {"question": "¿Cómo se redacta una cláusula de no competencia efectiva?"}, {"question": "¿Qué cláusulas son obligatorias en un NDA en España?"}, {"question": "¿Cuál es el preaviso mínimo para rescisión de contrato de servicios?"}, ], outputs=[ {"answer": "Debe incluir ámbito geográfico, temporal (máx. 2 años) y actividades prohibidas"}, {"answer": "Definición de información confidencial, obligaciones de confidencialidad, excepciones y plazo"}, {"answer": "El CC art. 1594 no fija plazo mínimo; se recomienda 30 días pactados expresamente"}, ], dataset_id=dataset.id ) # ── Evaluadores personalizados ──────────────────────────── def correctness(run, example) -> dict: pred = run.outputs["answer"].lower() expected = example.outputs["answer"].lower() keywords = [w for w in expected.split() if len(w) > 5] hits = sum(1 for k in keywords if k in pred) return {"key": "correctness", "score": min(1.0, hits / max(len(keywords), 1))} def conciseness(run, example) -> dict: words = len(run.outputs["answer"].split()) score = 1.0 if words <= 80 else max(0, 1.0 - (words - 80) / 160) return {"key": "conciseness", "score": round(score, 2)} # ── Ejecutar evaluación ─────────────────────────────────── def app_wrapper(inputs: dict) -> dict: return answer_legal_question(inputs["question"], user_id="eval-run") results = evaluate( app_wrapper, data="lexflow-clausulas-qa", evaluators=[correctness, conciseness], experiment_prefix="gpt4o-v2", max_concurrency=4 )
tests/test_quality.py
pytest · GitHub Actions
# tests/test_quality.py — CI quality gate para LexFlow # Falla el PR si correctness < 0.85 o conciseness < 0.80 import pytest from langsmith import evaluate from lexflow_tracing import app_wrapper, correctness, conciseness def test_clausula_qa_quality(): results = evaluate( app_wrapper, data="lexflow-clausulas-qa", evaluators=[correctness, conciseness], experiment_prefix="ci-test", ) scores = {} for r in results: for ev in r["evaluation_results"]["results"]: scores.setdefault(ev.key, []).append(ev.score) avg_correctness = sum(scores["correctness"]) / len(scores["correctness"]) avg_conciseness = sum(scores["conciseness"]) / len(scores["conciseness"]) assert avg_correctness >= 0.85, \ f"Correctness cayó a {avg_correctness:.2f} (threshold: 0.85)" assert avg_conciseness >= 0.80, \ f"Conciseness cayó a {avg_conciseness:.2f} (threshold: 0.80)" print(f"✓ Correctness: {avg_correctness:.2f} | Conciseness: {avg_conciseness:.2f}")
Resultado CI/CD — GitHub Actions
PR #47 · feat/gpt4o-v2-prompt · Rama: main
test_clausula_qa_quality::correctness
0.91
≥ 0.85 ✓
test_clausula_qa_quality::conciseness
0.84
≥ 0.80 ✓
test_clausula_qa_quality::latencia_media
2.4s
≤ 3.0s ✓
test_clausula_qa_quality::tasa_error
1.2%
≤ 2.0% ✓
scripts/monitor_production.py
Cron diario · Railway
# Monitoreo diario: errores + latencia alta + resumen de coste from langsmith import Client; client = Client() runs = list(client.list_runs( project_name="lexflow-production", filter='or(eq(status,"error"),gt(latency,5))', limit=50 )) errors = [r for r in runs if r.status == "error"] slow_runs = [r for r in runs if r.total_time and r.total_time > 5] all_today = list(client.list_runs(project_name="lexflow-production", limit=1000)) total_tokens = sum(r.total_tokens or 0 for r in all_today) avg_latency = sum(r.total_time or 0 for r in all_today) / max(len(all_today), 1) print(f"Errores hoy: {len(errors)} | Lentos: {len(slow_runs)} | " f"Tokens: {total_tokens:,} | Latencia media: {avg_latency:.1f}s") # Output → Errores hoy: 15 | Lentos: 8 | Tokens: 1,284,310 | Latencia media: 2.4s