IT ▾

Chinese LLM APITraduzione

Ottieni la chiave API

Aggiornato

Traduzione e localizzazione cinese↔inglese tramite un'API chat

Tradurre stringhe UI e documenti con un modello chat è facile da dimostrare ma difficile da rilasciare. I fallimenti sono banali: un segnaposto rinominato, un termine del glossario tradotto in tre modi diversi, una risposta JSON avvolta in un testo. Questa guida tratta la traduzione come una pipeline con controlli in ogni fase, utilizzando l'endpoint di completamento chat compatibile con OpenAI.

Pensa per fasi, non in un unico prompt

Un lavoro di traduzione in produzione ha quattro fasi: preparare le stringhe, chiamare il modello, validare il risultato e unirlo. La maggior parte dei problemi di qualità deriva dal saltare la fase di validazione. Un ingegnere di localizzazione non rilascierebbe mai un file di traduzione senza eseguire il linter dei segnaposto, e la stessa disciplina si applica quando il traduttore è un modello.

L'endpoint è https://api.chinesellmapi.com/v1/chat/completions, l'ID del modello è uncensored, autenticazione Bearer. Gestisce entrambe le direzioni, dall'inglese al cinese e dal cinese all'inglese, e gli esempi seguenti utilizzano stringhe sorgente in inglese convertite in cinese semplificato. Inverti la direzione nel prompt di sistema per il caso opposto.

Tieni presente la finestra di contesto condivisa da 100.000 token, il valore predefinito di max_tokens pari a 2.048 (aumentalo per documenti lunghi, fino a 32.000) e il limite del corpo della richiesta di 8 MB. Nessuno di questi limiti incide sulle stringhe dell'interfaccia, ma tutti e tre sono rilevanti per i documenti interi.

Iniezione del glossario

I nomi di marca, i sostantivi di prodotto e i termini legalmente sensibili richiedono una resa fissa. Il meccanismo più economico è un blocco glossario nel prompt di sistema, che elenca il termine sorgente e il target richiesto. Definisci esplicitamente la regola: usa il glossario letteralmente in qualsiasi flessione del termine sorgente. Mantieni il glossario breve, perché ogni riga viene fatturata come input su ogni chiamata; alcune decine di voci sono normali, migliaia no.

import os
from openai import OpenAI

client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])

GLOSSARY = {
    "workspace": "工作区",
    "pull request": "合并请求",
    "seat": "席位",
    "billing cycle": "计费周期",
}

def build_system(glossary, target="Simplified Chinese"):
    rows = "\n".join(f"- {src} => {dst}" for src, dst in glossary.items())
    return (
        f"You are a software localization translator. Translate English UI strings into {target}.\n"
        "Rules:\n"
        "1. Use the glossary below verbatim whenever the source term appears, in any inflection.\n"
        "2. Copy placeholders such as {name}, %s, %d and {{count}} exactly, character for character.\n"
        "3. Copy HTML tags and attributes exactly; translate only the text between tags.\n"
        "4. Output the translation only, with no notes.\n\n"
        "Glossary:\n" + rows
    )

def translate(text):
    r = client.chat.completions.create(
        model="uncensored",
        messages=[
            {"role": "system", "content": build_system(GLOSSARY)},
            {"role": "user", "content": text},
        ],
        temperature=0.2,
        max_tokens=400,
    )
    return r.choices[0].message.content.strip()

print(translate("Invite {name} to the workspace before the next billing cycle."))

Se il tuo glossario è grande, filtralo per richiesta: includi solo le voci il cui termine sorgente appare nel batch. Un semplice test di sottostringa su testo minuscolo è sufficiente per una prima versione e mantiene il prompt piccolo. Imposta la temperatura intorno a 0,2; la variazione creativa è un bug nel testo UI.

Decidi anche le linee guida di stile per la lingua target fin dall'inizio: indirizzo formale o informale, se mettere spazi tra i caratteri cinesi e le parole latine o i numeri incorporati e quale set di punteggiatura usare. Inserisci queste decisioni nello stesso prompt di sistema in modo che ogni batch le erediti.

Mantenere intatti segnaposto e markup

I segnaposto si rompono in modi prevedibili: il modello traduce il nome della variabile, perde un %s finale o aggiunge spazi tra le parentesi graffe. Le regole del prompt riducono questi errori ma non li eliminano, quindi verifica nel codice. Estrai segnaposto e tag dal sorgente e dal target con un'espressione regolare, quindi confrontali come liste ordinate. L'ordine può legittimamente cambiare tra le lingue, quindi confrontali come multiset anziché come sequenze.

import re

PLACEHOLDER = re.compile(r"\{\{?\w+\}?\}|%[sd]|</?[a-zA-Z][^>]*>")

def placeholders_ok(source: str, target: str) -> bool:
    # Same multiset of placeholders and tags, regardless of order.
    return sorted(PLACEHOLDER.findall(source)) == sorted(PLACEHOLDER.findall(target))

src = 'You have <b>{count}</b> unread messages in %s.'
bad = '你在 %s 中有 <b>{数量}</b> 条未读消息。'
good = '你在 %s 中有 <b>{count}</b> 条未读消息。'
print(placeholders_ok(src, bad), placeholders_ok(src, good))   # False True

Quando il controllo fallisce, riprova una volta citando la coppia fallita nel prompt, ad esempio un messaggio utente che dice che l'output precedente ha modificato il segnaposto e deve essere corretto. Se fallisce ancora, segnala la stringa per la revisione umana invece di fare looping. Il testo ricco merita ulteriore cura: preferisci tradurre i nodi di testo e ricostruire il markup tu stesso, poiché ciò rende impossibile il danno ai tag per costruzione.

Output strutturato tramite istruzioni

L'elaborazione in batch delle stringhe in una singola richiesta richiede una risposta leggibile dalla macchina. L'API accetta i campi standard di completamento chat; l'output strutturato qui deriva da istruzioni chiare piuttosto che da un interruttore che impone uno schema, quindi scrivi il contratto nel prompt, includi gli ID per poter riallineare i risultati ed esegui un'analisi difensiva. I modelli a volte racchiudono il JSON in blocchi di codice o aggiungono una frase amichevole, quindi rimuovi i blocchi prima dell'analisi e tratta un errore di parsing come un evento retryabile.

import json
import os
import re
from openai import OpenAI

client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])

SYSTEM = (
    "Translate each item from English to Simplified Chinese. "
    "Reply with a JSON object only, no code fences, no commentary. "
    'Shape: {"items": [{"id": <number>, "zh": "<translation>"}]}. '
    "Keep the same ids, keep every placeholder and HTML tag unchanged."
)

def translate_batch(strings):
    payload = [{"id": i, "en": s} for i, s in enumerate(strings)]
    r = client.chat.completions.create(
        model="uncensored",
        messages=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": json.dumps(payload, ensure_ascii=False)},
        ],
        temperature=0.2,
        max_tokens=2000,
    )
    raw = r.choices[0].message.content.strip()
    raw = re.sub(r"^```(?:json)?|```$", "", raw, flags=re.M).strip()   # tolerate stray fences
    data = json.loads(raw)
    by_id = {item["id"]: item["zh"] for item in data["items"]}
    return [by_id[i] for i in range(len(strings))]

print(translate_batch(["Save changes", "Delete {count} files?", "Welcome back, <b>{name}</b>"]))

Convalida sempre dopo l'analisi: gli ID devono corrispondere, il conteggio deve corrispondere e ogni elemento deve superare il controllo dei segnaposto della sezione precedente. Cinquanta stringhe per richiesta sono un batch iniziale sensato per il testo UI breve; aumentalo solo mentre il tasso di passaggio della convalida rimane alto.

Traduzione batch sotto il limite di richieste

Ogni chiave è limitata a 300 richieste al minuto. Un lavoro di localizzazione con 50.000 stringhe a 20 stringhe per richiesta richiede 2.500 richieste, che rientrano in meno di dieci minuti se si regola il ritmo in modo uniforme. Lo script che segue combina un semaforo per le chiamate in corso con un pacemaker basato su lock, e riduce il carico in caso di 429 e 503. A differenza di un errore di convalida, questi due errori sono transitori: 429 significa ridurre il carico, e 503 con upstream_busy significa riprovare tra qualche secondo. Un 402 indica che il saldo è esaurito, cosa che nessun retry può risolvere.

import asyncio
import os
import time
from openai import AsyncOpenAI, APIStatusError

client = AsyncOpenAI(
    base_url="https://api.chinesellmapi.com/v1",
    api_key=os.environ["API_KEY"],
    max_retries=0,
    timeout=90,
)

RATE = 240 / 60            # requests per second, below the 300/min cap
gate = asyncio.Lock()
next_slot = 0.0
slots = asyncio.Semaphore(6)

async def pace():
    global next_slot
    async with gate:
        now = time.monotonic()
        if next_slot > now:
            await asyncio.sleep(next_slot - now)
        next_slot = max(now, next_slot) + 1 / RATE

async def call(batch, attempt=0):
    async with slots:
        await pace()
        try:
            r = await client.chat.completions.create(
                model="uncensored",
                messages=[
                    {"role": "system", "content": "Translate to Simplified Chinese. Keep placeholders unchanged. One line per input line."},
                    {"role": "user", "content": "\n".join(batch)},
                ],
                max_tokens=1500,
                temperature=0.2,
            )
            return r.choices[0].message.content.splitlines()
        except APIStatusError as e:
            if e.status_code in (429, 503) and attempt < 4:
                await asyncio.sleep(2 ** attempt + 1)     # 1s, 3s, 5s, 9s
                return await call(batch, attempt + 1)
            raise

async def run(all_strings, size=20):
    chunks = [all_strings[i:i + size] for i in range(0, len(all_strings), size)]
    results = await asyncio.gather(*(call(c) for c in chunks))
    return [line for part in results for line in part]

if __name__ == "__main__":
    strings = [f"Item {n} was updated by {{user}}" for n in range(60)]
    out = asyncio.run(run(strings))
    print(len(out), out[0])

Questa versione suddivide per righe per brevità, il che va bene per le stringhe su una riga; per qualsiasi cosa multilinea, usa la variante JSON della sezione precedente in modo che le interruzioni di riga all'interno di una stringa non possano essere scambiate per confini di stringa.

La direzione inversa: testo sorgente cinese

La traduzione dal cinese all'inglese ha i suoi modi di fallimento. Il cinese omette liberamente soggetti e plurali, quindi il modello deve indovinarli; fornisci contesto. Una stringa come "已发送" potrebbe essere "Sent", "Has been sent" o "You sent it", a seconda che etichetti un pulsante, un badge di stato o un toast. La soluzione è un campo di contesto accanto a ogni stringa, fornito dai tuoi sviluppatori e passato nel batch JSON: dove appare la stringa, la sua lunghezza massima e se è un'etichetta o una frase.

Misura esplicitamente i limiti di lunghezza. Il testo UI inglese è spesso più lungo dell'originale cinese, e il contrario è vero per i target cinesi, che tendono ad essere più brevi in caratteri ma più larghi sullo schermo perché ogni glifo è a larghezza piena. Definisci un budget di caratteri nel prompt quando un pulsante o un'intestazione di tabella ha un limite rigido, e verificane il rispetto nel codice dopo l'arrivo della risposta.

Per i documenti sorgente cinesi con nomi di persone e luoghi, specifica una convenzione di romanizzazione fin dall'inizio, come l'Hanyu Pinyin senza segni di tono, e aggiungi i nomi ricorrenti al glossario. Senza quello, la stessa persona può apparire con due grafie all'interno di un singolo documento, che è esattamente l'incoerenza che un revisore noterebbe per prima.

Campionamento e revisione prima di unire

I controlli automatici catturano errori strutturali, non traduzioni errate. Aggiungi un passaggio umano leggero: campiona una percentuale fissa di ogni batch, ad esempio ogni ventesima stringa, più ogni stringa che ha richiesto un retry, e inviale a un revisore bilingue. Tieni traccia del tasso di modifica del revisore per batch. Se aumenta, stringi il prompt, riduci la dimensione del batch o aggiungi voci del glossario per i termini corretti.

Mantieni il prompt, la versione del glossario e le impostazioni del batch accanto a ogni file di output. Quando un termine cambia nel glossario, puoi quindi ritradurre solo le stringhe che lo contengono, invece di rieseguire l'intero corpus. Un semplice hash del contenuto della stringa sorgente più la versione del glossario funziona come chiave di cache, e significa che le stringhe invariate non costano nulla nella prossima esecuzione.

Infine, mantieni un set di regressione di stringhe difficili: quelle con segnaposto nidificati, forme plurali, HTML incorporato e nomi composti lunghi. Esegui il set ogni volta che cambi il prompt e confronta gli output fianco a fianco prima di distribuire la modifica.

Stima dei costi per un'esecuzione di localizzazione

Ipotesi, esplicitate: 30.000 stringhe sorgente, 12 parole inglesi ciascuna, tradotte in batch da 20. Richiedi circa 20 token di input per stringa più 300 token di prompt di sistema fissi per richiesta, e circa 35 token di output per stringa. Questo porta a 1.500 richieste, circa 1,05 milioni di token di input e circa 1,05 milioni di token di output. A $0,25 per milione di token di input e $1,00 per milione di token di output, l'esecuzione costa circa $0,26 più $1,05, per un totale di circa $1,31. Considera questo una stima di ordine di grandezza e sostituisci le ipotesi con i dati dai tuoi log di usage.

Il credito di prova è di $0,50 per sette giorni, sufficienti a convalidare una pipeline su un campione di poche migliaia di stringhe. Prosegui con la quickstart dell'app per il controllo degli script e la gestione UTF-8, oppure consulta la pagina dei prezzi per i tassi attuali.

Domande e risposte

L'API può tradurre in entrambe le direzioni?

Sì. Cinese in inglese e inglese in cinese funzionano entrambi tramite lo stesso endpoint chat; scegli la direzione nel prompt di sistema.

Come faccio a impedire al modello di modificare segnaposto come {name}?

Definisci la regola nel prompt, poi verifica nel codice confrontando le liste di segnaposto tra sorgente e target, riprovando o segnalando le discrepanze.

Esiste un'opzione per la modalità JSON?

L'output strutturato si ottiene tramite istruzioni. Specifica la forma esatta nel prompt, rimuovi i codici fence estranei, poi analizza e valida il risultato.

Quanto velocemente posso eseguire un batch grande?

Ogni chiave permette 300 richieste al minuto. Rallenta le richieste in modo uniforme e riduci il carico in caso di risposte 429 o 503.

La tua chiave è a un modulo di distanza

Crea un account, copia la chiave, modifica l'URL di base. È tutta qui la configurazione.

Ottieni la chiave API