KO ▾

Chinese LLM API번역

API 키 받기

업데이트됨

채팅 API를 통한 중국어↔영어 번역 및 현지화

채팅 모델로 UI 문자열과 문서를 번역하는 것은 데모하기는 쉽지만 배포하기는 어렵습니다. 실패 사례는 흔합니다: 자리 표시자 이름 변경, 용어집 용어가 세 가지 다른 방식으로 번역됨, 텍스트 사이에 JSON 응답 포함 등. 이 가이드는 OpenAI 호환 채팅 완료 엔드포인트를 사용하여 각 단계에 검사가 있는 파이프라인으로 번역을 다룹니다.

하나의 프롬프트가 아닌 단계별로 생각하세요

프로덕션 번역 작업에는 네 가지 단계가 있습니다: 문자열 준비, 모델 호출, 결과 검증, 병합. 대부분의 품질 문제는 검증 단계를 건너뛰면서 발생합니다. 현지화 엔지니어는 자리 표시자 린터를 실행하지 않고 번역 파일을 배포하지 않으며, 번역자가 모델일 때도 같은 규율이 적용됩니다.

엔드포인트는 https://api.chinesellmapi.com/v1/chat/completions, 모델 ID uncensored, Bearer 인증입니다. 양방향(영어에서 중국어, 중국어에서 영어)을 처리하며, 아래 예제는 영어 원문을 간체 중국어로 번역하는 것을 사용합니다. 역방향 번역을 위해 시스템 프롬프트의 방향을 바꾸세요.

공유된 100,000 토큰 컨텍스트 창, 기본 max_tokens 2,048(긴 문서의 경우 최대 32,000까지 증가 가능), 8 MB 요청 본문 한계를 염두에 두세요. 이러한 제한은 UI 문자열에는 영향을 미치지 않지만 전체 문서에는 모두 중요합니다.

용어집 주입

상표명, 제품 명사 및 법적 민감 용어는 고정된 표기를 유지해야 합니다. 가장 저렴한 방법은 시스템 프롬프트에 글로사 블록을 추가하여 원문 용어와 필수 번역어를 나열하는 것입니다. 규칙을 명시하세요: 원문 용어의 어떤 활용형이든 글로사를 그대로 사용해야 합니다. 글로사는 짧게 유지하세요. 각 줄은 모든 호출에서 입력으로 청구되므로, 몇십 개 항목은 일반적이지만 수천 개는 아닙니다.

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

용어집이 큰 경우 요청별로 필터링하세요: 배치에 원문 용어가 포함된 항목만 포함하세요. 소문자 텍스트에 대한 일반 부분 문자열 테스트는 첫 번째 버전으로 충분하며 프롬프트를 작게 유지합니다. 온도를 약 0.2로 설정하세요. 창의적인 변형은 UI 텍스트에서 버그입니다.

또한 대상 언어의 스타일 가이드를 사전에 결정하세요: 격식체 또는 비격식체 호칭 방식, 중국어 문자와 내장 라틴어 단어 또는 숫자 사이에 공백을 넣을지 여부, 사용할 구두점 세트 등 이러한 결정은 동일한 시스템 프롬프트에 포함하여 모든 배치에서 이를 상속받도록 하세요.

자리 표시자 및 마크업 유지

플레이스홀더는 예측 가능한 방식으로 깨집니다: 모델이 변수 이름을 번역하거나, 끝에 있는 %s를 누락하거나, 중괄호 안에 공백을 추가할 수 있습니다. 프롬프트 규칙은 이러한 오류를 줄일 수 있지만 제거할 수는 없으므로 코드에서 검증해야 합니다. 정규식으로 원문과 번역문의 플레이스홀더와 태그를 추출한 후 정렬된 목록으로 비교하세요. 언어에 따라 순서가 legitimately 변경될 수 있으므로 시퀀스가 아닌 다중 집합으로 비교하세요.

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

검사가 실패하면 실패한 쌍을 프롬프트에 인용하여 한 번 더 시도하십시오. 예를 들어 이전 출력이 플레이스홀더를 변경했으며 수정해야 한다는 사용자 메시지를 입력합니다. 여전히 실패하면 루프에 빠지는 대신 사람이 검토할 수 있도록 해당 문자열에 플래그를 지정하십시오. 리치 텍스트는 각별한 주의가 필요합니다. 텍스트 노드를 번역하고 마크업을 직접 다시 작성하는 것을 선호하십시오. 이렇게 하면 태그 손상이 구조적으로 불가능해집니다.

명령을 통한 구조화된 출력

하나의 요청으로 문자열을 배치하려면 기계가 읽을 수 있는 응답이 필요합니다. API는 표준 채팅 완료 필드를 사용하며, 여기서 구조화된 출력은 스키마 강제 스위치가 아닌 명확한 지시를 통해 얻어지므로, 프롬프트에 계약을 작성하고 결과를 다시 정렬할 수 있는 ID를 포함하며 방어적으로 구문 분석해야 합니다. 모델은 때로 JSON을 코드 펜스에 감싸거나 친절한 문구를 추가하므로, 구문 분석 전에 펜스를 제거하고 구문 분석 오류를 재시도 가능한 이벤트로 처리하세요.

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

파싱 후 항상 유효성을 검사하십시오: ID가 일치해야 하고, 개수가 일치해야 하며, 각 항목은 이전 섹션의 플레이스홀더 검사를 통과해야 합니다. 짧은 UI 텍스트의 경우 요청당 50개의 문자열이 합리적인 시작 배치입니다. 검증 통과율이 높은 상태에서만 증가하십시오.

속도 제한 내 배치 번역

각 키는 분당 300개의 요청으로 제한됩니다. 50,000개의 문자열을 요청당 20개의 문자열로 처리하는 로컬라이제이션 작업은 2,500개의 요청이 필요하며, 균일하게 속도를 조절하면 10분 미만으로 완료할 수 있습니다. 아래 스크립트는 진행 중인 호출을 위한 세마포어와 잠금 기반 페이서를 결합하고 429 및 503 오류에서 백오프합니다. 유효성 검사 실패와 달리 이러한 두 오류는 일시적입니다: 429는 속도를 줄이라는 의미이며, upstream_busy가 있는 503은 몇 초 후 재시도하라는 의미입니다. 402는 잔액이 고갈되었음을 의미하며, 이는 재시도로 해결할 수 없습니다.

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

이 버전은 간결성을 위해 줄 단위로 분할하므로 단일 줄 문자열에는 적합합니다. 다중 줄의 경우 이전 섹션의 JSON 변형을 사용하십시오. 이렇게 하면 문자열 내의 줄 바꿈이 문자열 경계로 오해되는 것을 방지할 수 있습니다.

역방향: 중국어 소스 텍스트

길이 제한을 명시적으로 측정하십시오. 영어 UI 텍스트는 종종 중국어 원본보다 길며, 그 반대로 중국어 대상은 문자 수는 짧지만 각 글자가 전체 너비를 차지하므로 화면에서 더 넓게 나타납니다. 버튼이나 테이블 헤더에 하드 제한이 있는 경우 프롬프트에 문자 수 예산을 명시하고, 응답이 도착한 후 코드에서 이를 검증하십시오.

사람과 장소 이름이 포함된 중국어 소스 문서의 경우, 성조 기호 없는 한어 병음과 같은 로마자 표기 규정을 사전에 지정하고 반복되는 이름을 어휘집에 추가하십시오. 그렇지 않으면 동일한 인물이 단일 문서 내에서 두 가지 철자로 나타날 수 있으며, 이는 검토자가 가장 먼저 눈치채는 불일치입니다.

병합 전 샘플링 및 검토

자동 검사는 구조적 오류는 잡아내지만 오역은 잡아내지 못합니다. 경량화된 인간 검토 단계를 추가하세요: 각 배치의 고정된 비율(예: 20개 문자열마다 1개)과 재시도가 필요한 모든 문자열을 샘플링하여 양국어 검토자에게 보내세요. 배치별 검토자의 수정률을 추적하세요. 수정률이 상승하면 프롬프트를 강화하거나 배치 크기를 줄이거나 수정 중인 용어에 대한 글로사 항목을 추가하세요.

프롬프트, 어휘집 버전 및 배치 설정을 각 출력 파일과 함께 보관하십시오. 어휘집의 용어가 변경되면 전체 코퍼스를 다시 실행하는 대신 해당 용어가 포함된 문자열만 다시 번역할 수 있습니다. 소스 문자열과 어휘집 버전의 간단한 콘텐츠 해시는 캐시 키로 작동하며, 변경되지 않은 문자열은 다음 실행에서 비용이 발생하지 않음을 의미합니다.

마지막으로 까다로운 문자열의 회귀 테스트 세트를 유지하십시오: 중첩된 플레이스홀더, 복수형, 내장 HTML 및 긴 합성 명사가 포함된 문자열입니다. 프롬프트를 변경할 때마다 이를 실행하고 변경 사항을 롤아웃하기 전에 출력을 나란히 비교하십시오.

로컬라이제이션 실행 비용 추정

질문과 답변

API가 양방향 번역을 지원하나요?

네. 중국어에서 영어로, 영어에서 중국어로 모두 동일한 채팅 엔드포인트를 통해 작동하며, 시스템 프롬프트에서 방향을 선택합니다.

{name}과 같은 플레이스홀더가 변경되지 않도록 하려면 어떻게 하나요?

프롬프트에 규칙을 명시한 후, 소스와 대상 간 자리 표시자 목록을 비교하여 코드로 검증하고, 불일치 시 재시도하거나 플래그를 설정합니다.

JSON 모드 스위치가 있나요?

명령을 통해 구조화된 출력을 얻습니다. 프롬프트에서 정확한 형태를 지정하고, 불필요한 코드 펜스를 제거한 후 결과를 구문 분석하고 검증합니다.

대규모 배치 처리 속도는 얼마나 빠른가요?

각 키는 분당 300개의 요청을 허용합니다. 요청을 균등하게 분배하고 429 또는 503 응답 시 백오프합니다.

키는 양식 하나만 작성하면 받을 수 있습니다

계정을 생성하고 키를 복사한 후, 베이스 URL을 변경하세요. 설정은 이것으로 끝입니다.

API 키 받기