VI ▾

Chinese LLM APIDịch thuật

Lấy khóa API

Cập nhật

Dịch và bản địa hóa Trung↔Anh qua API chat

Dịch chuỗi UI và tài liệu bằng mô hình chat dễ để demo nhưng khó để triển khai. Các lỗi thường gặp rất đơn giản: một điểm giữ chỗ bị đổi tên, một thuật ngữ từ điển được dịch theo ba cách khác nhau, một phản hồi JSON bị lồng trong văn bản. Hướng dẫn này xử lý dịch như một quy trình có kiểm tra ở mỗi giai đoạn, sử dụng endpoint chat completions tương thích OpenAI.

Hãy tư duy theo từng giai đoạn, không phải trong một prompt

Một công việc dịch thuật sản xuất có bốn giai đoạn: chuẩn bị chuỗi, gọi mô hình, xác thực kết quả và hợp nhất lại. Hầu hết các vấn đề về chất lượng đến từ việc bỏ qua giai đoạn xác thực. Một kỹ sư bản địa hóa sẽ không bao giờ xuất bản một tệp dịch mà không chạy trình kiểm tra chỗ trống, và kỷ luật tương tự cũng áp dụng khi người dịch là một mô hình.

Endpoint là https://api.chinesellmapi.com/v1/chat/completions, model id uncensored, xác thực bearer. Nó xử lý cả hai chiều, Anh sang Trung và Trung sang Anh, và các ví dụ dưới đây dùng chuỗi nguồn tiếng Anh chuyển sang tiếng Trung giản thể. Đổi chiều trong system prompt để thực hiện chiều ngược lại.

Hãy nhớ cửa sổ ngữ cảnh 100.000 token chung, giá trị mặc định của max_tokens là 2.048 (tăng lên cho tài liệu dài, tối đa 32.000), và giới hạn thân yêu cầu 8 MB. Không điều nào ảnh hưởng đến chuỗi UI, nhưng cả ba đều quan trọng với tài liệu đầy đủ.

Chèn từ điển

Tên thương hiệu, danh từ sản phẩm và các thuật ngữ nhạy cảm về pháp lý cần một cách hiển thị cố định. Cơ chế rẻ nhất là một khối từ điển trong system prompt, liệt kê thuật ngữ nguồn và mục tiêu bắt buộc. Quy định rõ luật: sử dụng từ điển nguyên văn trong bất kỳ biến thể nào của thuật ngữ nguồn. Giữ từ điển ngắn, vì mỗi dòng được tính phí là đầu vào trong mọi lệnh gọi; vài chục mục nhập là bình thường, hàng nghìn thì không.

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

Nếu từ điển của bạn lớn, hãy lọc nó theo từng yêu cầu: chỉ bao gồm các mục có thuật ngữ nguồn xuất hiện trong lô. Kiểm tra chuỗi con đơn giản trên văn bản viết thường là đủ cho phiên bản đầu tiên và giữ prompt nhỏ. Đặt nhiệt độ khoảng 0,2; sự biến thể sáng tạo là lỗi trong bản sao UI.

Cũng hãy quyết định trước hướng dẫn phong cách cho ngôn ngữ đích: xưng hô trang trọng hay thân mật, liệu có đặt khoảng cách giữa các ký tự tiếng Trung và các từ tiếng Latin hoặc số nhúng hay không, và bộ dấu câu nào sẽ sử dụng. Đặt các quyết định này trong cùng một system prompt để mọi lô đều kế thừa chúng.

Giữ nguyên chỗ trống và markup

Điểm giữ chỗ bị phá vỡ theo những cách dễ dự đoán: mô hình dịch tên biến, bỏ sót %s ở cuối, hoặc thêm khoảng trắng trong ngoặc nhọn. Quy tắc prompt giảm các lỗi này nhưng không loại bỏ được, nên hãy xác minh trong mã. Trích xuất điểm giữ chỗ và thẻ từ nguồn và đích bằng biểu thức chính quy, sau đó so sánh chúng dưới dạng danh sách đã sắp xếp. Thứ tự có thể thay đổi hợp lệ giữa các ngôn ngữ, nên so sánh dưới dạng đa tập hợp thay vì chuỗi.

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

Khi kiểm tra thất bại, hãy thử lại một lần với cặp lỗi được trích dẫn lại trong prompt, ví dụ một tin nhắn người dùng cho biết đầu ra trước đó đã thay đổi chỗ trống và phải được sửa chữa. Nếu vẫn thất bại, hãy đánh dấu chuỗi để xem xét bởi con người thay vì lặp lại. Văn bản giàu định dạng cần sự cẩn thận hơn: ưu tiên dịch các nút văn bản và tự xây dựng lại markup, vì điều đó làm cho việc hỏng thẻ không thể xảy ra về mặt cấu trúc.

Đầu ra cấu trúc qua hướng dẫn

Việc gom chuỗi trong một yêu cầu cần một phản hồi có thể đọc được bởi máy. API chấp nhận các trường hoàn thành chat tiêu chuẩn; đầu ra cấu trúc ở đây đến từ các hướng dẫn rõ ràng thay vì một công tắc bắt buộc schema, vì vậy hãy viết hợp đồng vào prompt, bao gồm id để bạn có thể căn chỉnh lại kết quả, và phân tích một cách thận trọng. Mô hình đôi khi bao bọc JSON trong code fence hoặc thêm một câu thân thiện, vì vậy hãy loại bỏ fence trước khi phân tích và coi lỗi phân tích là một sự kiện có thể thử lại.

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

Luôn xác thực sau khi phân tích: id phải khớp, số lượng phải khớp và mỗi mục phải vượt qua kiểm tra chỗ trống từ phần trước. Năm mươi chuỗi mỗi yêu cầu là một lô bắt đầu hợp lý cho văn bản giao diện người dùng ngắn; tăng nó chỉ khi tỷ lệ vượt qua xác thực vẫn cao.

Dịch hàng loạt dưới giới hạn tốc độ

Mỗi khóa bị giới hạn 300 yêu cầu mỗi phút. Một công việc bản địa hóa với 50.000 chuỗi ở tốc độ 20 chuỗi mỗi yêu cầu là 2.500 yêu cầu, vừa trong dưới mười phút nếu bạn điều chỉnh nhịp độ đều đặn. Script dưới đây kết hợp semaphore cho các lệnh gọi đang chạy với bộ điều chỉnh nhịp dựa trên khóa, và lùi lại khi nhận 429 và 503. Khác với lỗi xác thực, hai lỗi này là tạm thời: 429 nghĩa là giảm tốc, và 503 với upstream_busy nghĩa là thử lại sau vài giây. 402 nghĩa là số dư đã hết, không thể sửa bằng cách thử lại.

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

Phiên bản này chia theo dòng để ngắn gọn, điều này ổn cho các chuỗi đơn dòng; đối với bất kỳ thứ gì đa dòng, hãy sử dụng biến thể JSON từ phần trước để các ngắt dòng bên trong một chuỗi không bị nhầm lẫn với ranh giới chuỗi.

Hướng ngược lại: văn bản nguồn tiếng Trung

Dịch tiếng Trung sang tiếng Anh có các chế độ lỗi riêng của nó. Tiếng Trung bỏ chủ ngữ và số nhiều một cách tự do, vì vậy mô hình phải đoán chúng; hãy cung cấp ngữ cảnh cho nó. Một chuỗi như "已发送" có thể là "Sent", "Has been sent" hoặc "You sent it", tùy thuộc vào việc nó nhãn một nút, huy hiệu trạng thái hay một thông báo toast. Giải pháp là một trường ngữ cảnh bên cạnh mỗi chuỗi, do nhà phát triển của bạn cung cấp và truyền cùng trong lô JSON: nơi chuỗi xuất hiện, độ dài tối đa của nó và liệu nó là nhãn hay câu.

Đo lường các giới hạn độ dài một cách rõ ràng. Văn bản giao diện người dùng tiếng Anh thường dài hơn bản gốc tiếng Trung, và điều ngược lại đúng với các mục tiêu tiếng Trung, vốn có xu hướng ngắn hơn về ký tự nhưng rộng hơn trên màn hình vì mỗi ký tự là chiều rộng đầy đủ. Quy định một ngân sách ký tự trong prompt khi một nút hoặc tiêu đề bảng có giới hạn cứng, và xác minh nó bằng code sau khi nhận phản hồi.

Đối với các tài liệu nguồn tiếng Trung có tên người và địa danh, hãy quy định trước một quy ước phiên âm, chẳng hạn như Hanyu Pinyin không có dấu thanh, và thêm các tên lặp lại vào từ điển. Không có điều đó, cùng một người có thể xuất hiện dưới hai cách đánh vần trong cùng một tài liệu, đó chính xác là sự không nhất quán mà một người xem sẽ nhận ra đầu tiên.

Lấy mẫu và xem xét trước khi hợp nhất

Các kiểm tra tự động bắt lỗi cấu trúc, không phải lỗi dịch sai. Thêm một bước con người nhẹ nhàng: lấy mẫu một phần trăm cố định của mỗi lô, ví dụ mỗi chuỗi thứ hai mươi, cộng với mọi chuỗi cần thử lại, và gửi những chuỗi đó cho người xem song ngữ. Theo dõi tỷ lệ chỉnh sửa của người xem theo lô. Nếu nó tăng, hãy siết chặt prompt, thu nhỏ kích thước lô hoặc thêm các mục nhập từ điển cho các thuật ngữ đang được chỉnh sửa.

Giữ prompt, phiên bản từ điển và cài đặt lô cùng với mỗi tệp đầu ra. Khi một thuật ngữ thay đổi trong từ điển, bạn có thể sau đó dịch lại chỉ các chuỗi chứa nó, thay vì chạy lại toàn bộ kho dữ liệu. Một băm nội dung đơn giản của chuỗi nguồn cộng với phiên bản từ điển hoạt động như một khóa bộ nhớ cache, và điều đó có nghĩa là các chuỗi không thay đổi không tốn gì trong lần chạy tiếp theo.

Cuối cùng, hãy giữ một bộ kiểm tra hồi quy các chuỗi khó: những chuỗi có chỗ trống lồng nhau, dạng số nhiều, HTML nhúng và các danh từ ghép dài. Chạy nó bất cứ khi nào bạn thay đổi prompt, và so sánh đầu ra cạnh nhau trước khi phát hành thay đổi.

Ước tính chi phí cho một lần chạy bản địa hóa

Giả định, nêu rõ ràng: 30.000 chuỗi nguồn, 12 từ tiếng Anh mỗi chuỗi, dịch theo lô 20. Lấy khoảng 20 token đầu vào mỗi chuỗi cộng 300 token của system prompt cố định mỗi yêu cầu, và khoảng 35 token đầu ra mỗi chuỗi. Điều đó cho 1.500 yêu cầu, khoảng 1,05 triệu token đầu vào và khoảng 1,05 triệu token đầu ra. Ở mức $0,25 cho mỗi triệu token đầu vào và $1,00 cho mỗi triệu token đầu ra, chi phí chạy khoảng $0,26 cộng $1,05, tổng cộng khoảng $1,31. Coi đây là ước tính bậc độ lớn và thay thế các giả định bằng số liệu từ nhật ký usage của bạn.

Tín dụng dùng thử là $0,50 trong bảy ngày, đủ để xác thực một quy trình trên một mẫu vài nghìn chuỗi. Tiếp tục với bắt đầu nhanh ứng dụng để điều khiển script và xử lý UTF-8, hoặc xem trang giá cả để biết tỷ lệ hiện tại.

Hỏi đáp

API có thể dịch theo cả hai chiều không?

Có. Dịch từ tiếng Trung sang tiếng Anh và ngược lại đều hoạt động qua cùng một endpoint chat; bạn chọn hướng trong system prompt.

Làm thế nào để ngăn mô hình thay đổi các điểm giữ chỗ như {name}?

Quy định rõ luật trong prompt, sau đó xác minh bằng code bằng cách so sánh danh sách chỗ trống giữa bản gốc và bản dịch, thử lại hoặc đánh dấu nếu có sai lệch.

Có công tắc chế độ JSON không?

Kết cấu trúc đạt được qua hướng dẫn. Chỉ định chính xác cấu trúc trong prompt, loại bỏ các code fence thừa, sau đó phân tích và xác thực kết quả.

Tôi có thể chạy một lô lớn nhanh đến mức nào?

Mỗi khóa cho phép 300 yêu cầu mỗi phút. Điều chỉnh nhịp độ yêu cầu đều và lùi lại khi nhận phản hồi 429 hoặc 503.

Khóa của bạn chỉ cách một biểu mẫu

Tạo tài khoản, sao chép khóa, thay đổi URL cơ sở. Đó là toàn bộ quá trình thiết lập.

Lấy khóa API