fix(seo): el reintento estricto llevaba meses sin ejecutarse
Build & Deploy ResearchOwl / build-and-push (push) Successful in 8s

`_raw` mandaba `temperature=0.0` a `messages.create`, y anthropic 1.2.0 dejó de
aceptarlo: los parámetros de muestreo se movieron a `output_config`, que sólo
expone `effort` y `format`. El `except` de abajo convertía el TypeError en un
warning, así que el reintento NUNCA ocurría: se quedaba el primer intento y
entraba el recortador mecánico.

Se vio generando el artículo EN de Trans-en-Provence: `custom_excerpt` quedó en
389 caracteres contra un tope de 300, porque el recorte limpio lo habría dejado
demasiado corto y el reintento que debía acortarlo con criterio estaba muerto.

Lo que hacía vincular el reintento no era la temperatura: es el turno de edición
—se le devuelve su propio JSON para que lo acorte, en vez de re-tirar de cero— y
un `max_tokens` más corto. Los dos siguen.

Y el `except` deja de disfrazar un error de programación: un TypeError se
registra como error, no como aviso. Así fue como esto vivió en silencio.

Los kwargs salen a `_create_kwargs()` para que un test los compare con la firma
del SDK instalado. ⚠️ Ese test NO puede fallar hoy en local: el host tiene
anthropic 0.102.0 y el pod 1.2.0, porque requirements.txt pone un suelo
(`>=0.40.0`) y el CI no corre los tests. Verificado a mano dentro del pod: con
`temperature` la firma lo rechaza, sin él pasa. El pin y el pytest en CI van
aparte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EHakatofHeJAzdXL26Q5bq
This commit is contained in:
ChemaVX
2026-09-02 08:53:28 +00:00
co-authored by Claude Opus 5
parent bf51fb30c0
commit 32f0b2c1ab
2 changed files with 86 additions and 14 deletions
+45
View File
@@ -9,6 +9,8 @@ BASE = {
"image_context": "c",
}
import src.seo.autofill as _autofill_module
def test_coerce_es_drops_invented_tags():
obj = dict(BASE, tags=["uap", "humanoides", "Desclasificados", "investigacion-2"])
@@ -167,3 +169,46 @@ def test_un_idioma_desconocido_no_revienta_la_generacion():
assert _check_con_sitio({"slug": "x"}, "pt") == []
finally:
R.check_post = orig
# --- la llamada al SDK -------------------------------------------------------
def test_los_kwargs_los_acepta_el_sdk_instalado():
"""Contra la firma REAL, no contra una copia nuestra ni contra un doble.
`temperature=0.0` viajaba en esta llamada y anthropic 1.2.0 dejó de
aceptarlo (los parámetros de muestreo se fueron a `output_config`). Nada lo
vio: la importación no falla, y un cliente simulado en un test acepta
cualquier kwarg encantado. Sólo se ve preguntándole al SDK instalado qué
admite — el mismo movimiento que publicar el contrato en vez de copiarlo.
Si esto falla tras subir el SDK, el arreglo es cambiar la llamada, no
relajar el assert.
"""
import inspect
from anthropic import AsyncAnthropic
from src.seo.autofill import _create_kwargs
kwargs = _create_kwargs("system", [{"role": "user", "content": "x"}], 768)
firma = inspect.signature(AsyncAnthropic(api_key="test").messages.create)
desconocidos = sorted(k for k in kwargs if k not in firma.parameters)
assert desconocidos == [], (
f"el SDK instalado no acepta {desconocidos} en messages.create; "
f"acepta {sorted(firma.parameters)}")
def test_el_reintento_no_pide_nada_que_no_este_en_los_kwargs():
"""El reintento usa la MISMA constructora, así que no puede divergir.
Antes tenía su propia rama —`temperature` sólo se añadía en el reintento—,
y por eso el fallo sólo aparecía cuando el primer intento violaba un límite:
el camino feliz nunca lo tocaba.
"""
normal = _autofill_module._create_kwargs("s", [], 1024)
reintento = _autofill_module._create_kwargs("s", [], 768)
assert set(normal) == set(reintento)
assert reintento["max_tokens"] == 768