88 lines
3.5 KiB
Python
88 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Traduce ES->EN con Claude Haiku 4.5 vía API directa. Prueba de coste/calidad.
|
|
|
|
Lee la ANTHROPIC_API_KEY de portfolio-tracker/.env (la misma que usa el
|
|
portfolio tracker para trade setups). Reporta tokens reales de la API.
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
# Cargar API key del .env de portfolio-tracker sin pisar el entorno existente.
|
|
ENV_PATH = "/home/rafa/portfolio-tracker/.env"
|
|
if "ANTHROPIC_API_KEY" not in os.environ:
|
|
for line in open(ENV_PATH):
|
|
line = line.strip()
|
|
if line.startswith("ANTHROPIC_API_KEY="):
|
|
os.environ["ANTHROPIC_API_KEY"] = line.split("=", 1)[1].strip().strip('"').strip("'")
|
|
break
|
|
|
|
import anthropic
|
|
|
|
MODEL = "claude-haiku-4-5"
|
|
|
|
LANG_NAMES = {"en": "English", "fr": "French (français)",
|
|
"it": "Italian (italiano)", "pt": "Portuguese (português)"}
|
|
|
|
|
|
def system_prompt(lang: str) -> str:
|
|
target = LANG_NAMES[lang]
|
|
return (
|
|
f"Eres un traductor profesional de textos religiosos cristianos "
|
|
f"(espiritualidad y teología católica). Traduce del español al {target}. "
|
|
f"REGLAS ESTRICTAS:\n"
|
|
f"1. Conserva EXACTAMENTE el marcado HTML (etiquetas y atributos) y los "
|
|
f"shortcodes entre [ ] y {{ }}. No los traduzcas ni los reordenes.\n"
|
|
f"2. NO traduzcas las referencias bíblicas ni sus abreviaturas "
|
|
f"(p.ej. 'Jn 3, 16', 'Mt 5'). Déjalas idénticas.\n"
|
|
f"3. Conserva los nombres propios de persona y lugar (salvo exónimos establecidos).\n"
|
|
f"4. Términos litúrgicos correctos (p.ej. 'Cuaresma' = Lent/Carême/Quaresima/Quaresma; "
|
|
f"NO inventes palabras).\n"
|
|
f"5. Traducción FIEL: no resumas, no añadas, no comentes.\n"
|
|
f"6. Devuelve SOLO la traducción entre las marcas <<<INI>>> y <<<FIN>>>, sin nada más."
|
|
)
|
|
|
|
|
|
def extract(text: str) -> str:
|
|
# Coge el bloque <<<INI>>>...<<<FIN>>> de contenido MÁS LARGO (robusto al
|
|
# bug del runner local, donde el modelo a veces re-menciona las marcas).
|
|
blocks = re.findall(r"<<<INI>>>(.*?)<<<FIN>>>", text, re.S)
|
|
out = max(blocks, key=len).strip() if blocks else text.strip()
|
|
out = re.sub(r"^```[a-z]*\n?", "", out)
|
|
out = re.sub(r"\n?```$", "", out)
|
|
return out.strip()
|
|
|
|
|
|
def translate(text: str, lang: str, *, is_title: bool = False) -> tuple[str, object]:
|
|
client = anthropic.Anthropic()
|
|
kind = "el TÍTULO" if is_title else "el texto"
|
|
user = (
|
|
f"Traduce {kind} que va entre las marcas. "
|
|
f"Debe quedar en {LANG_NAMES[lang]} de forma natural.\n"
|
|
f"<<<INI>>>{text}<<<FIN>>>"
|
|
)
|
|
max_tokens = max(1024, int(len(text) * 0.9))
|
|
resp = client.messages.create(
|
|
model=MODEL,
|
|
max_tokens=min(max_tokens, 16000),
|
|
system=system_prompt(lang),
|
|
messages=[{"role": "user", "content": user}],
|
|
)
|
|
body = "".join(b.text for b in resp.content if b.type == "text")
|
|
return extract(body), resp.usage
|
|
|
|
|
|
if __name__ == "__main__":
|
|
path = sys.argv[1] if len(sys.argv) > 1 else "/tmp/orig_es.html"
|
|
lang = sys.argv[2] if len(sys.argv) > 2 else "en"
|
|
src = open(path).read()
|
|
out, usage = translate(src, lang)
|
|
open("/tmp/trad_haiku.html", "w").write(out)
|
|
cost = usage.input_tokens / 1e6 * 1.0 + usage.output_tokens / 1e6 * 5.0
|
|
print(f"MODEL={MODEL} lang={lang}")
|
|
print(f"input_tokens={usage.input_tokens} output_tokens={usage.output_tokens}")
|
|
print(f"coste_articulo=${cost:.5f}")
|
|
print(f"chars_in={len(src)} chars_out={len(out)}")
|
|
print("--- primeras 500 car ---")
|
|
print(out[:500])
|