PT ▾

Chinese LLM APITradução

Obter chave de API

Atualizado

Tradução e localização chinês↔inglês via API de chat

Traduzir strings de interface e documentos com um modelo de chat é fácil de demonstrar, mas difícil de implementar. As falhas são triviais: um placeholder renomeado, um termo do glossário traduzido de três formas diferentes, uma resposta JSON envolta em texto corrido. Este guia trata a tradução como um pipeline com verificações em cada etapa, usando o endpoint de conclusões de chat compatível com OpenAI.

Pense em etapas, não em um único prompt

Um trabalho de tradução em produção tem quatro etapas: preparar as strings, chamar o modelo, validar o resultado e mesclá-lo de volta. A maioria dos problemas de qualidade vem de pular a etapa de validação. Um engenheiro de localização nunca enviaria um arquivo de tradução sem executar o linter de placeholders, e a mesma disciplina se aplica quando o tradutor é um modelo.

O endpoint é https://api.chinesellmapi.com/v1/chat/completions, o ID do modelo é uncensored e a autenticação é via Bearer. Ele suporta ambos os sentidos, inglês para chinês e chinês para inglês, e os exemplos abaixo usam strings de origem em inglês indo para o chinês simplificado. Inverta o sentido no prompt do sistema para o inverso.

Leve em consideração a janela compartilhada de 100.000 tokens, o max_tokens padrão de 2.048 (aumente para documentos longos, até 32.000) e o limite de corpo de requisição de 8 MB. Nenhum desses fatores afeta strings de interface, mas todos os três impactam documentos inteiros.

Injeção de glossário

Nomes de marcas, substantivos de produtos e termos juridicamente sensíveis precisam de uma única representação fixa. O mecanismo mais barato é um bloco de glossário no prompt do sistema, listando o termo de origem e o alvo necessário. Declare a regra explicitamente: use o glossário literalmente em qualquer flexão do termo de origem. Mantenha o glossário curto, pois cada linha é cobrada como entrada em cada chamada; algumas dezenas de entradas são normais, milhares não são.

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 seu glossário for grande, filtre-o por requisição: inclua apenas as entradas cujo termo de origem apareça no lote. Um teste simples de substring em texto minúsculo é suficiente para uma primeira versão e mantém o prompt pequeno. Defina a temperatura em torno de 0,2; variação criativa é um bug em textos de interface.

Também defina antecipadamente o guia de estilo para o idioma alvo: tratamento formal ou informal, se deve colocar espaços entre caracteres chineses e palavras ou números latinos embutidos, e qual conjunto de pontuação usar. Coloque essas decisões no mesmo prompt do sistema para que cada lote as herde.

Mantendo placeholders e marcação intactos

Placeholders falham de forma previsível: o modelo traduz o nome da variável, remove um %s final ou adiciona espaços dentro das chaves. As regras do prompt reduzem essas falhas, mas não as eliminam, então verifique no código. Extraia placeholders e tags do texto original e do traduzido com uma expressão regular e compare-os como multiconjuntos. A ordem pode mudar legitimamente entre idiomas, então compare como multiconjuntos em vez de sequências.

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 a verificação falhar, tente novamente uma vez citando o par falho no prompt, por exemplo, uma mensagem de usuário informando que a saída anterior alterou o placeholder e deve ser corrigida. Se ainda falhar, sinalize a string para revisão humana em vez de entrar em loop. Texto rico merece cuidado extra: prefira traduzir os nós de texto e reconstruir a marcação você mesmo, pois isso torna o dano às tags impossível por construção.

Saída estruturada por instruções

Agrupar strings em uma única requisição exige uma resposta legível por máquina. A API aceita os campos padrão de conclusão de chat; a saída estruturada aqui vem de instruções claras em vez de uma opção que impõe esquema, então escreva o contrato no prompt, inclua ids para realinhar os resultados e analise de forma defensiva. Modelos às vezes envolvem JSON em marcações de código ou adicionam uma frase amigável, então remova as marcações antes de analisar e trate um erro de análise como um evento retryável.

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>"]))

Sempre valide após a análise: os IDs devem corresponder, a contagem deve corresponder e cada item deve passar na verificação de placeholder da seção anterior. Cinquenta strings por requisição é um lote inicial sensato para textos curtos de interface; aumente-o apenas enquanto a taxa de aprovação da validação permanecer alta.

Tradução em lote sob o limite de taxa

Cada chave está limitada a 300 requisições por minuto. Um trabalho de localização com 50.000 strings a 20 strings por requisição gera 2.500 requisições, o que cabe em menos de dez minutos se você distribuir uniformemente. O script abaixo combina um semáforo para chamadas em andamento com um controlador baseado em lock, e faz backoff em 429 e 503. Diferente de uma falha de validação, esses dois erros são transitórios: 429 significa reduzir a velocidade, e 503 com upstream_busy significa fazer nova tentativa em alguns segundos. Um 402 significa que o saldo está esgotado, o que nenhuma nova tentativa pode corrigir.

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])

Esta versão divide por linhas para brevidade, o que é bom para strings de uma única linha; para qualquer coisa multilinha, use a variante JSON da seção anterior para que as quebras de linha dentro de uma string não sejam confundidas com limites de string.

O sentido reverso: texto de origem chinês

Traduzir chinês para inglês tem seus próprios modos de falha. O chinês elimina sujeitos e plurais livremente, então o modelo deve adivinhá-los; dê contexto a ele. Uma string como "已发送" poderia ser "Sent", "Has been sent" ou "You sent it", dependendo se ela rotula um botão, um crachá de status ou um toast. A correção é um campo de contexto ao lado de cada string, fornecido pelos seus desenvolvedores e passado junto no lote JSON: onde a string aparece, seu comprimento máximo e se é um rótulo ou uma frase.

Meça os limites de comprimento explicitamente. Texto de interface em inglês é frequentemente mais longo que o original chinês, e o inverso é verdadeiro para alvos chineses, que tendem a ser mais curtos em caracteres, mas mais largos na tela porque cada glifo é de largura total. Declare um orçamento de caracteres no prompt quando um botão ou cabeçalho de tabela tiver um limite rígido e verifique-o no código após a resposta chegar.

Para documentos de origem chinesa com nomes de pessoas e lugares, especifique antecipadamente uma convenção de romanização, como Hanyu Pinyin sem marcas de tom, e adicione nomes recorrentes ao glossário. Sem isso, a mesma pessoa pode aparecer com duas grafias dentro de um único documento, o que é exatamente a inconsistência que um revisor notará primeiro.

Amostragem e revisão antes de mesclar

Verificações automáticas capturam erros estruturais, não traduções erradas. Adicione uma etapa humana leve: amostre uma porcentagem fixa de cada lote, por exemplo, a cada vigésima string, além de toda string que precisou de retry, e envie-as para um revisor bilíngue. Acompanhe a taxa de edição do revisor por lote. Se ela aumentar, ajuste o prompt, reduza o tamanho do lote ou adicione entradas de glossário para os termos sendo corrigidos.

Mantenha o prompt, a versão do glossário e as configurações do lote junto com cada arquivo de saída. Quando um termo mudar no glossário, você pode então retraduzir apenas as strings que o contêm, em vez de executar todo o corpus novamente. Um hash de conteúdo simples da string de origem mais a versão do glossário funciona como uma chave de cache, e isso significa que strings inalteradas nunca custam nada na próxima execução.

Finalmente, mantenha um conjunto de regressão de strings difíceis: aquelas com placeholders aninhados, formas plurais, HTML embutido e substantivos compostos longos. Execute-o sempre que alterar o prompt e compare as saídas lado a lado antes de lançar a alteração.

Estimando custo para uma execução de localização

Suposições, apresentadas de forma clara: 30.000 strings de origem, 12 palavras em inglês cada, traduzidas em lotes de 20. Leve em conta aproximadamente 20 tokens de entrada por string mais 300 tokens de prompt do sistema fixo por requisição, e cerca de 35 tokens de saída por string. Isso resulta em 1.500 requisições, cerca de 1,05 milhão de tokens de entrada e cerca de 1,05 milhão de tokens de saída. A $0,25 por milhão de tokens de entrada e $1,00 por milhão de tokens de saída, a execução custa aproximadamente $0,26 mais $1,05, totalizando cerca de $1,31. Trate isso como uma estimativa de ordem de grandeza e substitua as suposições por dados dos seus próprios logs de usage.

O crédito de teste é de $0,50 por sete dias, o suficiente para validar um pipeline em uma amostra de algumas mil strings. Continue com o quickstart do app para controle de script e tratamento de UTF-8, ou veja a página de preços para as taxas atuais.

Perguntas e respostas

A API traduz em ambos os sentidos?

Sim. Chinês para inglês e inglês para chinês funcionam pelo mesmo endpoint de chat; você escolhe a direção no prompt do sistema.

Como faço para impedir que o modelo altere placeholders como {name}?

Defina a regra no prompt e verifique no código comparando as listas de placeholders entre o texto original e o traduzido, retentando ou sinalizando inconsistências.

Existe uma opção de modo JSON?

A saída estruturada é obtida por instruções. Especifique o formato exato no prompt, remova as marcações de código soltas e, em seguida, analise e valide o resultado.

Quão rápido posso processar um lote grande?

Cada chave permite 300 requisições por minuto. Distribua as requisições de forma uniforme e reduza a taxa em caso de respostas 429 ou 503.

Sua chave está a um formulário de distância

Crie uma conta, copie a chave e altere a URL base. Essa é toda a configuração.

Obter chave de API