TH ▾

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 ท้ายบรรทัด หรือเพิ่มช่องว่างในวงเล็บ กฎในพรอมต์ลดข้อผิดพลาดเหล่านี้แต่ไม่สามารถขจัดได้ จึงต้องตรวจสอบในโค้ด แยกเพลสโฮลเดอร์และแท็กจากต้นทางและเป้าหมายด้วย regular expression แล้วเปรียบเทียบเป็นรายการที่เรียงลำดับ ลำดับอาจเปลี่ยนได้ตามภาษา จึงเปรียบเทียบแบบ multisets แทนลำดับ

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

เมื่อการตรวจสอบล้มเหลว ให้ลองอีกครั้งหนึ่งครั้งโดยมีการจับคู่ที่ล้มเหลวถูกกล่าวถึงในพรอมต์ ตัวอย่างเช่น ข้อความผู้ใช้ที่บอกว่าผลลัพธ์ก่อนหน้าเปลี่ยนตัวแปรและต้องแก้ไข หากยังคงล้มเหลว ให้ทำเครื่องหมายสตริงสำหรับการตรวจสอบโดยมนุษย์มากกว่าการวนซ้ำ ข้อความ rich text ต้องการความระมัดระวังเป็นพิเศษ: เลือกแปลโหนดข้อความและสร้างมาร์กอัปของคุณเอง เนื่องจากนั่นทำให้ความเสียหายของแท็กเป็นไปไม่ได้โดยโครงสร้าง

ผลลัพธ์ที่มีโครงสร้างผ่านคำสั่ง

การส่งสตริงแบบแบทช์ในคำขอเดียวต้องการการตอบกลับที่เครื่องอ่านได้ API รับฟิลด์การตอบแชทมาตรฐาน; การเอาต์พุตที่มีโครงสร้างมาจากการระบุคำสั่งที่ชัดเจนแทนสวิตช์บังคับ schema จึงเขียนสัญญาในพรอมต์, รวม id เพื่อจัดลำดับผลลัพธ์ใหม่ และแยกวิเคราะห์แบบป้องกัน โมเดลอาจห่อ JSON ใน code fence หรือเพิ่มประโยคสุภาพ จึงลบ fence ก่อนแยกวิเคราะห์และจัดการข้อผิดพลาดการแยกวิเคราะห์เป็นเหตุการณ์ที่ลองใหม่ได้

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 สั้น; เพิ่มเฉพาะเมื่ออัตราการผ่านการตรวจสอบยังคงสูง

การแปลแบทช์ภายใต้ขีดจำกัดอัตรา

คีย์แต่ละตัวจำกัดที่ 300 คำขอต่อนาที งานโลคัลไลเซชันที่มีสตริง 50,000 รายการที่ 20 สตริงต่อคำขอคือ 2,500 คำขอ ซึ่งพอดีภายในสิบนาทีหากคุณจัดจังหวะอย่างสม่ำเสมอ สคริปต์ด้านล่างรวมเซมิโฟร์สำหรับคำขอที่กำลังดำเนินการพร้อมตัวควบคุมแบบล็อก และถอยหลังเมื่อได้รับ 429 และ 503 ต่างจากข้อผิดพลาดการตรวจสอบ ข้อผิดพลาดสองข้อนี้เป็นชั่วคราว: 429 หมายถึงช้าลง และ 503 พร้อม upstream_busy หมายถึงลองใหม่ในไม่กี่วินาที 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 ภาษาอังกฤษมักยาวกว่าต้นฉบับภาษาจีน และในทางกลับกันเป็นจริงสำหรับเป้าหมายภาษาจีน ซึ่งมักจะมีอักขระสั้นกว่าแต่กว้างกว่าบนหน้าจอเพราะแต่ละ glyph กว้างเต็มช่อง กำหนดงบประมาณอักขระในพรอมต์เมื่อปุ่มหรือส่วนหัวตารางมีขีดจำกัดแข็ง และตรวจสอบในโค้ดหลังจากได้รับคำตอบ

สำหรับเอกสารต้นฉบับภาษาจีนที่มีชื่อคนและสถานที่ ระบุข้อตกลงการโรมันไนเซชันล่วงหน้า เช่น Hanyu Pinyin โดยไม่มีเครื่องหมายเสียง และเพิ่มชื่อที่พบบ่อยลงในพจนานุกรม หากไม่มีสิ่งนั้น คนคนเดียวกันอาจปรากฏภายใต้การสะกดสองแบบภายในเอกสารเดียว ซึ่งนั่นคือความไม่สอดคล้องกันที่ผู้ตรวจสอบจะสังเกตเห็นเป็นครั้งแรก

การสุ่มตัวอย่างและตรวจสอบก่อนรวม

การตรวจสอบอัตโนมัติจับข้อผิดพลาดโครงสร้าง ไม่ใช่การแปลผิด เพิ่มขั้นตอนมนุษย์แบบเบา: สุ่มเปอร์เซ็นต์คงที่ของแบทช์แต่ละชุด ตัวอย่างเช่น สตริงที่ 20 ทุกครั้ง รวมถึงสตริงทั้งหมดที่ต้องลองซ้ำ และส่งไปยังผู้ตรวจสอบสองภาษา ติดตามอัตราการแก้ไขของผู้ตรวจสอบต่อแบทช์ หากเพิ่มขึ้น ให้กระชับพรอมต์ ลดขนาดแบทช์ หรือเพิ่มรายการพจนานุกรมสำหรับคำที่กำลังแก้ไข

เก็บพรอมต์ เวอร์ชันพจนานุกรมและการตั้งค่าแบทช์ไว้พร้อมกับไฟล์ผลลัพธ์แต่ละไฟล์ เมื่อคำเปลี่ยนในพจนานุกรม คุณสามารถแปลสตริงที่มีคำนั้นใหม่ได้ แทนที่จะรันคอร์ปัสทั้งหมด แฮชเนื้อหาแบบง่ายของสตริงต้นฉบับบวกกับเวอร์ชันพจนานุกรมทำงานเป็นคีย์แคช และหมายความว่าสตริงที่ไม่เปลี่ยนแปลงไม่มีค่าใช้จ่ายในการรันครั้งถัดไป

สุดท้าย เก็บชุดรีเกรสชันของสตริงยาก: ที่มีเพลสโฮลเดอร์ซ้อน, รูปแบบพหูพจน์, HTML ในตัว และคำนามประสมยาว รันชุดนี้ whenever เปลี่ยนพรอมต์ และเปรียบเทียบเอาต์พุตเคียงข้างก่อนปรับใช้การเปลี่ยนแปลง

การประมาณค่าใช้จ่ายสำหรับการรันโลคัลไลเซชัน

สมมติฐานที่ระบุอย่างชัดเจน: สตริงต้นทาง 30,000 อัน, 12 คำภาษาอังกฤษต่อสตริง, แปลแบบแบทช์ละ 20 ใช้โทเคนอินพุตประมาณ 20 ต่อสตริง บวกระบบพรอมต์คงที่ 300 โทเคนต่อคำขอ และโทเคนเอาต์พุตประมาณ 35 ต่อสตริง นั่นคือ 1,500 คำขอ, อินพุตโทเคนประมาณ 1.05 ล้าน และเอาต์พุตโทเคนประมาณ 1.05 ล้าน ที่ $0.25 ต่อล้านโทเคนอินพุตและ $1.00 ต่อล้านโทเคนเอาต์พุต การรันนี้ใช้งบประมาณ $0.26 บวก $1.05 รวมประมาณ $1.31 ถือเป็นการประมาณการลำดับขนาดและแทนที่สมมติฐานด้วยตัวเลขจาก usage logs ของคุณ

เครดิตทดลองใช้คือ $0.50 เป็นเวลาเจ็ดวัน ซึ่งเพียงพอสำหรับการตรวจสอบสายการผลิตบนตัวอย่างของสตริงไม่กี่พันรายการ ดำเนินการต่อด้วย app quickstart สำหรับการควบคุมสคริปต์และการจัดการ UTF-8 หรือดู หน้าราคา สำหรับอัตราปัจจุบัน

ถาม-ตอบ

API สามารถแปลได้ทั้งสองทิศทางหรือไม่?

ใช่ ภาษาจีนเป็นภาษาอังกฤษและภาษาอังกฤษเป็นจีนทำงานผ่านเอนด์พอยต์แชทเดียวกัน; คุณเลือกทิศทางในพรอมต์ระบบ

ฉันจะหยุดโมเดลจากการเปลี่ยนเพลสโฮลเดอร์เช่น {name} ได้อย่างไร?

ระบุกฎในพรอมต์ จากนั้นตรวจสอบในโค้ดโดยเปรียบเทียบรายการตัวแปรระหว่างต้นฉบับและเป้าหมาย ทำซ้ำหรือแจ้งเตือนเมื่อไม่ตรงกัน

มีสวิตช์โหมด JSON หรือไม่?

เอาพุตที่มีโครงสร้างได้รับผ่านคำแนะนำ ระบุรูปร่างที่แน่นอนในพรอมต์ ตัดโค้ด fences ที่หลุดออก จากนั้นแยกวิเคราะห์และตรวจสอบผลลัพธ์

ฉันสามารถรันแบทช์ขนาดใหญ่ได้เร็วแค่ไหน?

คีย์แต่ละตัวอนุญาต 300 คำขอต่อนาที จัดจังหวะคำขออย่างสม่ำเสมอและถอยหลังเมื่อได้รับ 429 หรือ 503

คีย์ของคุณอยู่ห่างแค่แบบฟอร์มเดียว

สร้างบัญชี คัดลอกคีย์ เปลี่ยน URL ฐาน แค่นี้ก็พร้อมใช้งาน

รับคีย์ API