Com separar una PoC d'IA d'un sistema que pots mantenir
Què canvia quan una demo amb IA es converteix en producte real: logs, costos, avaluació, permisos, fallback i manteniment.

La setmana passada vaig veure una demo d’un producte amb IA que feia una cosa impressionant: agafava un document legal de 40 pàgines, l’analitzava i generava un resum executiu amb els punts clau. A la demo funcionava perfecte. Cinc minuts després, el fundador em va dir que portaven tres mesos intentant posar-ho en producció i no ho aconseguien. Els resums eren inconsistents, els costos s’havien multiplicat per sis respecte a l’estimació inicial i no tenien manera de saber si el LLM estava generant resums correctes sense que un advocat els revisés un per un.
Aquesta història me l’he trobat més vegades de les que m’agradaria. I cada cop el patró és el mateix: algú munta una PoC amb un LLM, funciona bé en condicions controlades, l’empresa s’entusiasma i de sobte cal convertir-ho en un producte real. Aquí és on tot es complica.
Perquè el que separa una PoC d’un sistema en producció no és la qualitat del prompt ni la potència del model. És tot el que envolta el model: logs, costos, avaluació, fallback, permisos, versionat i la capacitat de mantenir el sistema funcionant quan ningú no hi està mirant.
PoC vs producció: el que realment canvia
La diferència entre una PoC i un sistema en producció no és una qüestió de grau. És una diferència de naturalesa. En una PoC demostres que una cosa és possible. En producció demostres que una cosa és fiable, sostenible i mantenible al llarg del temps.
| Aspecte | PoC | Producció |
|---|---|---|
| Dades d’entrada | Controlades, seleccionades a mà | Qualsevol cosa que arribi |
| Volum | Desenes de peticions | Milers o milions |
| Costos | ”Ja ho veurem” | Partida pressupostària real |
| Errors del model | ”De vegades falla, però mola” | Cada error és un ticket de suport |
| Latència | Acceptable si és inferior a 30s | L’usuari marxa si passa de 3s |
| Avaluació | Cop d’ull manual, “es veu bé” | Mètriques automatitzades, benchmarks |
| Logs | print() a la consola | Observabilitat estructurada |
| Prompts | Un string al codi | Versionats, testejats, documentats |
| Fallback | No existeix | Obligatori |
| Permisos i seguretat | Localhost amb la teva API key | Multi-tenant, RBAC, data isolation |
| Dependència del proveïdor | ”Usem OpenAI i prou” | Pla B si l’API cau o puja preus |
| Human-in-the-loop | El propi desenvolupador | Flux dissenyat per a usuaris no tècnics |
Aquesta taula no és un exercici teòric. És la llista de coses que m’he trobat en portar funcionalitats d’IA de Rolsfera de “funciona a la meva màquina” a “funciona cada dia sense que jo hi estigui pendent”.
Costos reals: la conversa que ningú no vol tenir
En una PoC, els costos d’API són un nombre petit que ningú no qüestiona. En producció, aquest nombre es converteix en una línia del pressupost que algú ha de justificar.
Fem números reals. Suposem un servei que processa documents amb un LLM:
# Estimación de costes para procesamiento con LLM
# Modelo: GPT-4o (ejemplo de precios a mayo 2026)
# Input: ~5.00$ / 1M tokens
# Output: ~15.00$ / 1M tokens
tokens_por_documento_input = 4000 # documento de ~3000 palabras
tokens_por_documento_output = 800 # resumen + clasificación
documentos_por_dia = 500
coste_input_diario = (tokens_por_documento_input * documentos_por_dia / 1_000_000) * 5.00
coste_output_diario = (tokens_por_documento_output * documentos_por_dia / 1_000_000) * 15.00
coste_diario = coste_input_diario + coste_output_diario
coste_mensual = coste_diario * 30
print(f"Coste diario: ${coste_diario:.2f}") # ~$16.00
print(f"Coste mensual: ${coste_mensual:.2f}") # ~$480.00480 dòlars al mes per 500 documents diaris. Sembla gestionable. Però ara afegeix-hi:
- Reintents per errors. Un 5% de peticions fallen i es reintenten. Això és un 5% més de cost.
- Avaluació i testing. Cada cop que canvies un prompt, necessites provar-lo contra un conjunt de dades de referència. Això són peticions addicionals.
- Creixement. Si el producte té èxit, el volum es multiplica. 500 documents passen a 5000 i els costos se’n van a 4800$/mes.
- Model més potent. El client demana més precisió. Puges de model i els costos es dupliquen.
El cost de la PoC multiplicat pel volum real no dona el cost de producció. Dona una estimació optimista. Els costos reals sempre inclouen reintents, avaluació, pics de tràfic i la temptació inevitable d’usar un model més car.
A Rolsfera ho vaig aprendre ràpid. La meva primera versió processava tots els articles amb el model més potent disponible. Quan vaig veure la factura del primer mes, vaig reestructurar el pipeline per usar models petits en classificació i reservar els grans només per a resums d’articles ja aprovats. El cost va baixar un 60%.
# Estrategia de costes: modelo según la tarea
TASK_MODELS = {
"classification": "gpt-4o-mini", # barato, suficiente para categorizar
"relevance_scoring": "gpt-4o-mini", # no necesita el modelo grande
"summary": "gpt-4o", # aquí sí importa la calidad
"entity_extraction": "gpt-4o-mini", # tarea estructurada, modelo pequeño basta
}
def get_model_for_task(task: str) -> str:
return TASK_MODELS.get(task, "gpt-4o-mini") # default al baratoAvaluació: com saber si el LLM funciona bé
Aquest és probablement el problema més difícil de passar de PoC a producció. En una PoC, l’avaluació és “ho miro jo i sembla correcte”. En producció, necessites alguna cosa més rigorosa.
El problema d’avaluar outputs de LLM
Un LLM no és determinista (o almenys no ho és a la pràctica amb temperature superior a 0). El mateix input pot generar outputs lleugerament diferents. I “lleugerament diferent” de vegades és “subtilment incorrecte”.
Per a una tasca de classificació, l’avaluació és relativament fàcil: compares la categoria assignada amb la correcta i calcules precisió. Per a una tasca de generació de text (resums, respostes), l’avaluació és molt més complexa.
Com ho faig a Rolsfera
El meu sistema d’avaluació té tres nivells:
Nivell 1: Validació estructural. L’output té el format esperat. Si demano JSON, és JSON vàlid? Si demano un resum de 2-3 frases, té entre 1 i 5 frases? Si demano una puntuació d’1-10, està en aquest rang?
def validate_llm_output(output: dict, task: str) -> list[str]:
issues = []
if task == "classification":
valid_categories = ["tech", "politics", "economy", "science", "other"]
if output.get("category") not in valid_categories:
issues.append(f"Categoría inválida: {output.get('category')}")
if task == "summary":
summary = output.get("summary", "")
sentences = summary.split(".")
if len(sentences) < 1 or len(sentences) > 6:
issues.append(f"Resumen con {len(sentences)} frases (esperado: 1-5)")
if len(summary) < 50:
issues.append("Resumen demasiado corto")
if len(summary) > 1000:
issues.append("Resumen demasiado largo")
if task == "relevance_scoring":
score = output.get("relevance_score", 0)
if not isinstance(score, (int, float)) or score < 1 or score > 10:
issues.append(f"Puntuación fuera de rango: {score}")
return issuesNivell 2: Avaluació amb dataset de referència. Mantinc un conjunt de 50 articles manualment classificats i resumits. Quan canvio un prompt, executo el pipeline contra aquest dataset i comparo resultats.
def evaluate_against_reference(
pipeline_fn,
reference_data: list[dict],
) -> dict:
correct = 0
total = len(reference_data)
for item in reference_data:
result = pipeline_fn(item["article"])
if result["category"] == item["expected_category"]:
correct += 1
accuracy = correct / total if total > 0 else 0
return {
"accuracy": accuracy,
"correct": correct,
"total": total,
"threshold": 0.85, # mínimo aceptable
"passed": accuracy >= 0.85,
}Nivell 3: Feedback del procés editorial. Quan reviso articles a Rolsfera i en rebutjo un que la IA havia puntuat com a rellevant, o quan edito un resum perquè era imprecís, això queda registrat. Amb el temps, aquestes dades em donen una mesura real de la qualitat del pipeline d’IA.
Versionat de prompts
Els prompts són codi. O haurien de tractar-se com a tal. En una PoC, el prompt és un string que edites quan vols. En producció, necessites saber quin prompt ha generat quin output, quan va canviar i per què.
# prompts/classification_v3.py
PROMPT_VERSION = "3.2.1"
PROMPT_DATE = "2026-05-15"
PROMPT_CHANGELOG = """
3.2.1 - Añadida categoría 'devops', ajustado umbral de relevancia
3.2.0 - Simplificado formato de salida, eliminado campo 'confidence'
3.1.0 - Añadido ejemplo few-shot para mejorar precisión en categoría 'science'
3.0.0 - Reescritura completa del prompt de clasificación
"""
CLASSIFICATION_PROMPT = """Clasifica el siguiente artículo en una de estas categorías:
- tech: tecnología, programación, software, hardware
- politics: política nacional o internacional
- economy: economía, finanzas, mercados
- science: ciencia, investigación, medio ambiente
- devops: infraestructura, CI/CD, cloud, monitorización
- other: no encaja en ninguna anterior
Artículo:
Título: {title}
Contenido: {content}
Responde SOLO con un JSON:
{{"category": "...", "relevance_score": 1-10}}
Ejemplos:
- "Kubernetes 1.30 introduces sidecar containers" → {{"category": "devops", "relevance_score": 8}}
- "New study links sleep to cognitive performance" → {{"category": "science", "relevance_score": 4}}
"""Cada cop que el prompt canvia, s’incrementa la versió. Els outputs generats s’emmagatzemen amb la versió del prompt que els ha generat. Així, si detecto una degradació de qualitat, puc rastrejar exactament quin canvi de prompt l’ha causat.
def process_with_tracking(article: dict, prompt_template: str, prompt_version: str) -> dict:
filled_prompt = prompt_template.format(
title=article["title"],
content=article["content"][:2500],
)
response = call_llm(filled_prompt)
result = json.loads(response)
# Guardar metadata de trazabilidad
result["_meta"] = {
"prompt_version": prompt_version,
"model": "gpt-4o-mini",
"timestamp": datetime.utcnow().isoformat(),
"input_tokens": count_tokens(filled_prompt),
"output_tokens": count_tokens(response),
}
return resultRiscos tècnics que no apareixen a la PoC
Al·lucinacions
En una PoC, si el LLM inventa una dada, la detectes perquè estàs mirant l’output. En producció, les al·lucinacions es barregen amb milers d’outputs correctes i passen desapercebudes.
La meva estratègia per mitigar al·lucinacions a Rolsfera:
- Tasques acotades. No demano al LLM que generi informació nova. Li demano que classifiqui, resumeixi o extregui informació del text que li passo. Això redueix l’espai per inventar.
- Validació creuada. Si el resum menciona una dada que no apareix al text original, és sospitós. Tinc una validació bàsica que compara entitats del resum amb entitats del text font.
- Outputs estructurats. Demanar JSON amb camps específics és millor que demanar text lliure. El format acotat redueix la superfície d’al·lucinació.
def check_hallucination_risk(original_text: str, summary: str) -> float:
"""Heurística simple: qué proporción de entidades del resumen
aparecen en el texto original."""
import re
# Extraer palabras capitalizadas como proxy de entidades
summary_entities = set(re.findall(r'\b[A-Z][a-z]+(?:\s[A-Z][a-z]+)*\b', summary))
original_lower = original_text.lower()
if not summary_entities:
return 0.0
found = sum(1 for e in summary_entities if e.lower() in original_lower)
return 1.0 - (found / len(summary_entities))
# 0.0 = todas las entidades están en el original (bien)
# 1.0 = ninguna entidad está en el original (probable alucinación)No és un sistema perfecte. Però detecta els casos més flagrants.
Latència
Un LLM tarda entre 1 i 15 segons a respondre, depenent del model, la mida de l’input i la càrrega del servidor. En una PoC, esperes. En producció, si l’usuari espera 10 segons sense feedback, se’n va.
Solucions que aplico:
- Processament asíncron. L’usuari no espera el LLM. Llança la petició i rep el resultat quan està llest (notificació, polling, websocket).
- Cache de resultats. Si el mateix input ja va ser processat, retorno el resultat cachejat. A Rolsfera, si un article ja va ser classificat, no el torno a passar pel LLM.
- Timeouts estrictes. Si l’API del LLM no respon en 20 segons, la petició falla i s’encua per a reintent. Mai bloquejo el sistema esperant una resposta que pot no arribar.
Dependència del proveïdor
Si el teu sistema depèn d’una sola API de LLM, tens un single point of failure. L’API pot caure, pot pujar preus, pot canviar les seves condicions de servei o pot deprecar el model que uses.
# Patrón de multi-proveedor con fallback
LLM_PROVIDERS = [
{
"name": "openai",
"model": "gpt-4o-mini",
"endpoint": "https://api.openai.com/v1/chat/completions",
"priority": 1,
},
{
"name": "anthropic",
"model": "claude-sonnet",
"endpoint": "https://api.anthropic.com/v1/messages",
"priority": 2,
},
]
def call_llm_with_fallback(prompt: str) -> str:
providers = sorted(LLM_PROVIDERS, key=lambda p: p["priority"])
for provider in providers:
try:
return call_provider(provider, prompt)
except (APIError, TimeoutError) as e:
logger.warning(
f"Proveedor {provider['name']} falló: {e}. "
f"Intentando siguiente..."
)
raise AllProvidersFailedError("Todos los proveedores de LLM han fallado")Human-in-the-loop: el patró que ningú no vol implementar
A la PoC, l’humà hi és implícitament: ets tu mirant els resultats. En producció, el human-in-the-loop ha de ser un flux dissenyat, no un accident.
A Rolsfera, la revisió humana és part del disseny del sistema, no un pedaç. El LLM processa i suggereix; jo reviso i decideixo. Això té implicacions arquitectòniques:
Cua de revisió. Els articles processats per IA no es publiquen automàticament. Arriben a una cua on jo els aprovo, edito o descarto. La cua està prioritzada: articles de fonts d’alta confiança amb puntuació de rellevància alta apareixen primer.
Feedback loop. Les meves decisions d’aprovació/rebuig s’emmagatzemen i serveixen per avaluar la qualitat del pipeline. Si començo a rebutjar més articles del que és habitual, alguna cosa ha canviat (a les fonts, al prompt o al model).
Escalació. Si el LLM produeix un output que no passa la validació estructural, l’article es marca per a revisió obligatòria en lloc de descartar-se. De vegades el problema no és l’article, sinó el prompt.
# Lógica de decisión: cuándo publicar automáticamente vs. pedir revisión
def decide_workflow(article: dict) -> str:
ai_meta = article.get("ai_metadata", {})
source_trust = get_source_trust_score(article["source_name"])
relevance = ai_meta.get("relevance_score", 0)
validation_issues = ai_meta.get("validation_issues", [])
# Si hay problemas de validación, siempre revisión humana
if validation_issues:
return "manual_review"
# Fuente de alta confianza + alta relevancia = puede ir automático
if source_trust > 0.9 and relevance >= 8:
return "auto_publish"
# El resto, a la cola de revisión
return "manual_review"A la pràctica, menys del 10% dels articles passen per auto-publicació. I això està bé. Prefereixo revisar més del necessari que publicar alguna cosa que no hauria d’haver sortit.
Observabilitat: logs que serveixen per a alguna cosa
Els print() de la PoC han de convertir-se en logs estructurats que puguis consultar, filtrar i agregar.
import structlog
logger = structlog.get_logger()
def process_article_with_logging(article: dict) -> dict:
log = logger.bind(article_url=article["url"], source=article["source_name"])
log.info("processing_started")
try:
result = call_llm(article)
log.info("processing_completed",
category=result.get("category"),
tokens_used=result.get("_meta", {}).get("input_tokens", 0),
latency_ms=result.get("_meta", {}).get("latency_ms", 0))
return result
except TimeoutError:
log.error("llm_timeout", timeout_seconds=20)
raise
except Exception as e:
log.error("processing_failed", error=str(e), exc_info=True)
raiseEl que m’interessa dels logs en producció:
- Latència per petició. Si les peticions al LLM comencen a trigar més del que és habitual, vull saber-ho abans que els usuaris es queixen.
- Tokens consumits. Per controlar costos en temps real, no a final de mes.
- Taxa d’errors. Si el percentatge de peticions fallides puja per sobre del 5%, alguna cosa va malament.
- Distribució de categories. Si de sobte el 80% dels articles es classifiquen com a “other”, probablement el prompt necessita ajustos.
La checklist que uso abans de portar IA a producció
Després de diverses iteracions, m’he quedat amb aquesta llista de verificació. No és exhaustiva, però cobreix el que més problemes m’ha donat:
| Àrea | Preguntes clau |
|---|---|
| Costos | Estimació mensual amb volum real. Models per tasca. Alertes de despesa. Pla B si pugen preus. |
| Avaluació | Dataset de referència. Validació estructural. Mètriques de qualitat. Procés d’actualització. |
| Prompts | Versionat. Changelog. Tests en canviar prompt. Traçabilitat output-prompt. |
| Resiliència | Timeouts. Reintents amb backoff. Fallback multi-proveïdor. Comportament sense IA. |
| Observabilitat | Logs estructurats. Mètriques (latència, tokens, errors). Alertes. Dashboard. |
| Human-in-the-loop | Flux de revisió. Feedback loop. Escalació. Docs per a revisors no tècnics. |
Reflexió
La PoC demostra el que és possible. La producció demostra el que és sostenible. I entre l’una i l’altra hi ha un abisme de feina que no es veu en demos ni en fils de Twitter.
No escric això per desanimar ningú. La IA és útil de veritat, i els LLMs obren possibilitats que fa dos anys eren ciència-ficció. Però la conversa honesta és aquesta: muntar una demo amb un LLM costa un dia. Portar aquesta demo a producció amb fiabilitat, costos controlats i manteniment raonable costa mesos.
El que he après amb Rolsfera és que l’èxit d’un sistema amb IA no depèn de triar el model correcte. Depèn de construir tot el que envolta el model: l’avaluació que et diu si funciona, els logs que et diuen quan falla, els costos que et diuen si és sostenible i el human-in-the-loop que et diu si l’output té sentit al món real.
Si estàs a la fase de PoC i et demanen passar a producció, aquesta checklist és un bon punt de partida. I si algú et diu que és qüestió de “simplement pujar el prompt a un endpoint”, ara saps per què això no és suficient.


