🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 12 Min Lesezeit
0

Una capa de prompts que se califica a sí misma por resultados, hace A/B testing de sus propias reescrituras, e intercambia al ganador casi sin despliegue

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Casi todo el "manejo de prompts" es una carpeta de archivos .txt y una revisión de código. Esto es lo opuesto: los prompts viven en la base de datos como filas versionadas, cada agente busca el actual en el momento de la petición, la calidad se puntúa a partir de lo que de verdad pasó (una publicacion fue aceptada, un borrador se publicó, no un LLM calificándose a sí mismo), y un ciclo diario reescribe a los perdedores, les hace A/B testing a las reescrituras, y promueve al ganador de manera automática.






TL;DR



El ciclo, una vez que un prompt está en el registro:






































Etapa Qué pasa Dónde
Resolver el agente busca el prompt activo en el momento de la petición (intercambiable en caliente) resolve_prompt
Medir explícito (👍/⭐) + implícito (resultado aguas abajo) → combined_score
recolector de feedback + trabajador implícito
Optimizar a diario: reescribe a los de bajo rendimiento con un LLM → una sugerencia PENDING optimizador de prompts
Probar abre un A/B test 90/10, enruta el tráfico por versión gestor de A/B
Promover tras ≥50 muestras, el ganador por ≥10 pts → actívalo gestor de A/B


La única idea que lo hace funcionar: combined_score = explícito·0.4 + implícito·0.6, donde lo implícito es un resultado de negocio real. El modelo nunca califica su propia tarea.






El punto de partida: prompts como constantes



La línea base que casi todos publican:




CODE
SUMMARY_PROMPT = """You are an expert reviewer. Summarise: {text}"""

async def summarise(text: str) -> str:
return await llm(SUMMARY_PROMPT.format(text=text))






Dos problemas, los dos invisibles hasta que muerden:





  1. Cambiar un prompt es un despliegue. Ajustar una coma en el system prompt significa un PR, un build, un rollout. Así que nadie itera: el prompt se queda sin mantener.


  2. "¿El nuevo es mejor?" es una corazonada. Cambias la redacción, le echas un ojo a tres salidas, y publicas. No hay medición, así que no hay mejora, solo cambio.



Todo lo de abajo reemplaza "constante + corazonadas" con "fila + puntaje".






Capa 1: los prompts son filas versionadas



El prompt se vuelve dato. Una fila por (agent, prompt_type, version); una está marcada como activa.




CODE
class PromptVersion(Base):
__tablename__ = "prompt_versions"
agent_name: Mapped[str]
prompt_type: Mapped[str]
version: Mapped[int]
prompt_text: Mapped[str]
is_active: Mapped[bool] # exactamente 1 TRUE por (agent, type), normalmente
# calidad acumulada (llenada por el puntuador, Capa 3)
usage_count: Mapped[int]
avg_explicit_rating: Mapped[float]
explicit_approval_rate: Mapped[float]
implicit_success_rate: Mapped[float]
combined_score: Mapped[float]
performance_trend: Mapped[str] # improving | stable | declining






Registrar una versión nueva desactiva a la activa anterior en la misma transacción, así que "publicar un prompt nuevo" es una escritura a la base de datos, no un despliegue:




CODE
async def register_prompt(self, *, agent_name, prompt_type, prompt_text, activate):
current_max = await self._max_version(agent_name, prompt_type)
if activate:
await self.db.execute(
update(PromptVersion)
.where(PromptVersion.agent_name == agent_name,
PromptVersion.prompt_type == prompt_type,
PromptVersion.is_active.is_(True))
.values(is_active=False)
)
self.db.add(PromptVersion(
agent_name=agent_name, prompt_type=prompt_type,
version=current_max + 1, prompt_text=prompt_text, is_active=activate,
))






Los prompts fijos que ya existían siembran el registro como versión 1, una vez (idempotente). Esa semilla es también el fallback, ve la siguiente capa.






Capa 2: los agentes resuelven el prompt en el momento de la petición



El intercambio que hace gratis la iteración. En lugar de

SUMMARY_PROMPT.format(...), el agente llama a un resolvedor que lee la fila actual:




CODE
async def resolve_prompt(db, *, agent_name, prompt_type, template_vars, fallback_text):
"""Reemplazo directo de `FOO_PROMPT.format(**vars)`, pero el texto viene del
registro. Cae al constante fijo si el registro no tiene fila (despliegue
fresco, o las tablas de aprendizaje todavía no están sembradas).
"""
try:
text, version_id = await PromptRegistry(db).get_prompt(
agent_name=agent_name, prompt_type=prompt_type, template_vars=template_vars,
)
return text
except LookupError:
return fallback_text.format(**template_vars)






Dos detalles que no son obvios:





  1. El fallback es el prompt fijo. El registro es un override, nunca una dependencia dura. Un entorno de dev con las tablas de aprendizaje vacías se comporta exactamente como el código viejo basado en constantes. Puedes adoptarlo un agente a la vez.


  2. get_prompt incrementa usage_count y regresa el version_id. Ese id se estampa en la traza de la petición, así que cuando un resultado aterriza después (Capa 3) se atribuye a la versión exacta del prompt que lo produjo. Sin ese enlace, nada de la puntuación significa algo.



Después de esta capa, cambiar un prompt en prod es un UPDATE (o una fila activa nueva), en vivo en la siguiente petición, sin despliegue. Lo cual levanta la pregunta de verdad: ¿cómo sabes que la fila nueva es mejor?






Capa 3: calidad desde los resultados, no desde las opiniones



Este es el meollo, y donde la mayoría de la "evaluación de prompts" se equivoca. Dos tipos de señal, ponderadas a propósito:



Explícita: el usuario te dice. Un 👍/👎 o una estrella del 1 al 5 sobre la salida. Honesta pero escasa y sesgada (la gente califica cuando está molesta). Vale la pena recolectarla, vale la pena ponderarla de menos.



Implícita: el mundo te dice. ¿La cosa que el agente produjo de verdad funcionó aguas abajo? Estos son eventos de negocio reales, atados de regreso a la versión del prompt vía la traza de la Capa 2:




CODE
# Las señales implícitas son resultados aguas abajo, no la autoevaluación del modelo:
# cfp_accepted — la charla que el agente ayudó a proponer fue ACEPTADA
# post_published — el borrador que el agente escribió se publicó
# message_converted — el mensaje que el agente redactó convirtió un lead
# contract_reuploaded — (negativo) el usuario re-subió → la primera pasada falló






Un trabajador recorre los resultados recientes y registra cada uno como un éxito/fracaso contra la versión que lo generó. Luego el puntuador acumula todo:




CODE
# combined_score = explícito·0.4 + implícito·0.6
explicit = (avg_rating / 5 * 100) * 0.75 + approval_rate * 0.25
implicit = implicit_success_rate # 0–100, la verdad de aguas abajo
combined = explicit * 0.4 + implicit * 0.6






Lo implícito pesa más que lo explícito (0.6 vs 0.4) a propósito. Un prompt que la gente dice que le gusta pero cuyas propuestas de CFP nunca se aceptan es un peor prompt que uno tosco que sí consigue aceptaciones. Los resultados son la verdad de fondo; las calificaciones son una pista.



La misma pasada calcula una tendencia comparando el combined_score de hoy con el puntaje del mismo prompt de hace una semana (improving | stable | declining). La tendencia, no solo el puntaje absoluto, es lo que marca un prompt que se está pudriendo calladito mientras el mundo cambia a su alrededor.



El principio que vale la pena tatuarse en la pared: nunca dejes que el modelo califique su propia salida. "Pídele a GPT que califique esta respuesta del 1 al 10" mide fluidez, no utilidad. Ata el puntaje a algo que pase después de que la respuesta sale del sistema.






Capa 4: enrutamiento A/B por versión



Un prompt candidato nuevo no se activa: se registra como una variante y un A/B test enruta una rebanada del tráfico hacia él:




CODE
# start_test: registra la variante (NO activa), abre un test 90/10
variant = await registry.register_prompt(..., activate=False)
db.add(ABTest(agent_name=..., prompt_type=...,
control_version_id=control.id, variant_version_id=variant.id,
control_traffic_pct=90, variant_traffic_pct=10,
min_samples=50, status="RUNNING"))






La resolución (Capa 2) revisa primero si hay un test RUNNING y tira los dados:




CODE
test = await running_test_for(agent_name, prompt_type)
if test:
chosen = test.variant_version_id if randint(1, 100) <= test.variant_traffic_pct \
else test.control_version_id
return version_by_id(chosen)
return active_version(agent_name, prompt_type) # sin test → la fila activa






90/10, no 50/50: una variante mala solo grava el 10% del tráfico mientras se prueba a sí misma o muere. El control sigue sirviendo el otro 90%.






Capa 5: declarar un ganador (o no)



La evaluación corre en un calendario. Las reglas son aburridas a propósito: lo aburrido es lo que te evita promover ruido:




CODE
if variant.usage_count < test.min_samples:        # < 50 usos
return "not_enough_samples" # sigue esperando

diff = variant.combined_score - control.combined_score
if abs(diff) >= 10: # WINNER_DIFF_PTS
winner = variant if diff > 0 else control
activate(winner); test.status = "COMPLETED" # promover — intercambio en caliente
return "variant_win" if diff > 0 else "control_win"

if variant.usage_count >= 500: # HARD_STOP_SAMPLES
activate(control); test.status = "COMPLETED" # sin ventaja clara → quédate con el control
return "inconclusive"

return "still_running" # todavía no hay suficiente separación






Tres umbrales, cada uno se gana su lugar:





  1. min_samples = 50: por debajo de esto, un "gane" es puro azar. No mires todavía.


  2. |diff| ≥ 10 puntos: una ventaja de 2 puntos es ruido. Exige una brecha real.


  3. hard_stop = 500: si 500 usos no los pueden separar, son equivalentes; quédate con el titular y deja de desperdiciar tráfico. Sin un tope duro, los tests inconclusos corren por siempre.



La promoción es nada más activate(winner): el mismo intercambio en

caliente de una escritura de la Capa 1. El prompt nuevo está en vivo en la siguiente petición.





Capa 6: cerrar el ciclo, reescribir a los perdedores



Hasta ahora un humano todavía tiene que escribir la variante. La última pieza automatiza eso. Un optimizador diario encuentra a los de bajo rendimiento:




CODE
# de bajo rendimiento si CUALQUIERA:
# usage_count >= 30 AND combined_score < 40 (suficientes datos, mal puntaje)
# performance_trend == "declining" AND combined_score < 60 (pudriéndose)






…le pasa el prompt débil + sus puntajes a un LLM ("aquí hay un prompt y cómo le está yendo; reescríbelo para mejorar los resultados"), y archiva la reescritura como una sugerencia PENDING. El gestor de A/B (Capa 4) toma las sugerencias PENDING y abre tests. El ciclo ahora corre de punta a punta:




CODE
sembrar → resolver → medir (resultados) → optimizar (reescribir) → A/B → promover → resolver …






Un humano todavía aprueba lo que entra a un test: el optimizador sugiere, no auto despliega a prod. Pero la escritura, la puntuación, y la promoción son automáticas.






La trampa: un bug tumbó cada query



Una función de autoservicio empezó a fallar con:




CODE
Multiple rows were found when one or none was required






El selector de versión del resolvedor usaba scalar_one_or_none():




CODE
test = (await db.execute(
select(ABTest).where(ABTest.status == "RUNNING",
ABTest.agent_name == name,
ABTest.prompt_type == ptype))
).scalar_one_or_none() # ← lanza MultipleResultsFound con 2 filas






Nada en el esquema fuerza un único test RUNNING por prompt, y se habían abierto dos con 13 segundos de diferencia. De ahí en adelante, cada petición para ese prompt pegaba contra scalar_one_or_none(), encontraba dos filas, y lanzaba excepción. Peor, el resolvedor solo atrapaba LookupError, así que la excepción se pasó de largo el fallback y salió hasta el usuario.



Dos arreglos, los dos necesarios:




CODE
# 1) Tolera duplicados en lugar de tronar: elige el más nuevo, registra la anomalía.
tests = (await db.execute(
select(ABTest).where(...).order_by(ABTest.created_at.desc())
)).scalars().all()
if len(tests) > 1:
logger.warning("multiple_running_abtests", agent=name, type=ptype, count=len(tests))
test = tests[0] if tests else None









CODE
-- 2) De duplica el dato: cancela el test RUNNING más viejo.
UPDATE prompt_ab_tests SET status = 'CANCELLED' WHERE id = '<older>';






La lección se generaliza: scalar_one_or_none() es un crash latente en cualquier lado donde "solo debería haber uno" no es una restricción de la base de datos. Si el invariante no lo fuerza un índice único, la ruta de lectura tiene que tolerar la violación, no darla por descartada.






Lo que NO ayudó





  • Calidad autocalificada. Pedirle al modelo que puntúe su propia salida medía fluidez, nunca utilidad. Reemplazado por completo por los resultados de aguas abajo.


  • Splits A/B 50/50. Gravar la mitad del tráfico con una variante sin probar. 90/10 prueba una variante igual de bien con un décimo del radio de impacto.


  • El puntaje absoluto solo. Un prompt en 70 que va declinando es un problema más grande que uno estable en 60. La tendencia atrapó la pudrición que el puntaje escondía.


  • Suponer un solo test RUNNING. Sin restricción única → scalar_one_or_none era una bomba de tiempo.






Lecciones





  1. Puntúa sobre resultados, no sobre opiniones. Todo el sistema se para sobre las señales implícitas: eventos reales atados a la versión del prompt. Todo lo demás es decoración.


  2. Haz gratis el cambiar un prompt. Un registro + resolución en el momento de la petición convierte "cambiar un prompt" de un despliegue a una fila. La iteración gratis es la precondición para mejorar.


  3. El override tiene que degradar al constante. Un fallback fijo
    significa que adoptas de manera incremental y nunca endureces una
    dependencia del ciclo.


  4. Los umbrales aburridos le ganan a los ingeniosos. El mínimo de
    muestras, un requisito de brecha real, y un tope duro son lo que te evita promover ruido.


  5. Radio de impacto chico para los cambios sin probar. 90/10, no 50/50.


  6. Si no es una restricción de la base de datos, no es un invariante. Las rutas de lectura tienen que tolerar la segunda fila "imposible".

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hackers Just Poisoned the Rust Supply Chain | Threat Wire
1 Quelle
Hackers Found a Way Into Humanoid Robots | Threat Wire
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Una capa de prompts que se califica a sí misma por resultados, hace A/B testing de sus propias reescrituras, e intercambia al ganador casi sin despliegue

Thematisch verwandte Begriffe: capa, prompts, califica, misma · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...