अपडेट किया गया
चीनी↔अंग्रेजी अनुवाद और स्थानीयकरण चैट API के माध्यम से
चैट मॉडल के साथ UI स्ट्रिंग्स और दस्तावेज़ों का अनुवाद करना डेमो के लिए आसान है लेकिन शिप करने के लिए कठिन। विफलताएं सामान्य होती हैं: एक स्थिरांक का नाम बदलना, एक शब्दकोश पद का तीन अलग-अलग तरीकों से अनुवाद करना, या कथन में लिप्ट JSON प्रतिक्रिया। यह गाइड अनुवाद को एक पाइपलाइन के रूप में मानती है जिसमें प्रत्येक चरण पर जाँच होती है, OpenAI-संगत चैट पूरक एंडपॉइंट का उपयोग करके।
एक प्रॉम्प्ट में नहीं, बल्कि चरणों में सोचें
एक उत्पादन अनुवाद कार्य में चार चरण होते हैं: स्ट्रिंग्स तैयार करें, मॉडल को कॉल करें, परिणाम को सत्यापित करें, और इसे वापस मर्ज करें। गुणवत्ता की अधिकांश समस्याएं सत्यापन चरण छोड़ने से आती हैं। एक स्थानीयकरण इंजीनियर स्थानधरक लेंटर चलाए बिना अनुवाद फ़ाइल कभी शिप नहीं करेगा, और जब अनुवादक एक मॉडल हो तो भी वही अनुशासन लागू होता है।
एंडपॉइंट https://api.chinesellmapi.com/v1/chat/completions है, मॉडल आईडी uncensored है, bearer प्रमाणीकरण है। यह दोनों दिशाओं को संभालता है, अंग्रेजी से चीनी और चीनी से अंग्रेजी, और नीचे दिए गए उदाहरण अंग्रेजी स्रोत स्ट्रिंग्स का चीनी में अनुवाद दिखाते हैं। विपरीत दिशा के लिए सिस्टम प्रॉम्प्ट में दिशा बदलें।
साझा 100,000-टोकन विंडो, डिफ़ॉल्ट max_tokens 2,048 (लंबे दस्तावेज़ों के लिए इसे बढ़ाएं, 32,000 तक), और 8 MB request-body cap को ध्यान में रखें। इनमें से कोई भी 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 कॉपी में रचनात्मक विविधता एक बग है।
साथ ही, लक्ष्य भाषा के लिए शैली मार्गदर्शिका को पहले से तय करें: औपचारिक या अनौपचारिक संबोधन, चीनी अक्षरों और एम्बेडेड लैटिन शब्दों या संख्याओं के बीच स्पेस रखना है या नहीं, और कौन सा विराम चिह्न सेट उपयोग करना है। इन निर्णयों को उसी सिस्टम प्रॉम्प्ट में रखें ताकि हर बैच उन्हें विरासत में पाए।
स्थानधरकों और मार्कअप को अक्षुण्ण रखना
स्थिरांक भविष्यवाणी योग्य तरीकों से टूटते हैं: मॉडल वेरिएबल नाम का अनुवाद करता है, एक trailing %s छोड़ देता है, या ब्रेस के अंदर स्पेस जोड़ देता है। प्रॉम्प्ट नियम इन विफलताओं को कम करते हैं लेकिन उन्हें खत्म नहीं कर सकते, इसलिए कोड में सत्यापित करें। स्रोत और लक्ष्य से स्थिरांक और टैग को एक रेगुलर एक्सप्रेशन के साथ निकालें, फिर उन्हें सॉर्टेड सूचियों की तरह तुलना करें। भाषाओं के बीच क्रम वैध रूप से बदल सकता है, इसलिए सीक्वेंस के बजाय मल्टीसेट के रूप में तुलना करें।
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 मानक चैट पूर्णता फ़ील्ड लेता है; यहाँ संरचित आउटपुट स्कीमा-प्रतिबद्ध स्विच के बजाय स्पष्ट निर्देशों से आता है, इसलिए अनुबंध को प्रॉम्प्ट में लिखें, ids शामिल करें ताकि आप परिणामों को फिर से संरेखित कर सकें, और संरचना की जांच करते हुए पार्स करें। मॉडल कभी-कभी 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>"]))
पार्सिंग के बाद हमेशा सत्यापित करें: ids मिलने चाहिए, गिनती मिलनी चाहिए, और प्रत्येक आइटम को पिछले अनुभाग से स्थानधरक जांच को पास करना चाहिए। छोटी UI टेक्स्ट के लिए प्रति अनुरोध पचास स्ट्रिंग्स एक समझदारी भरा प्रारंभिक बैच है; इसे केवल तभी बढ़ाएं जब सत्यापन पास दर उच्च बनी रहे।
रेट लिमिट के तहत बैच अनुवाद
प्रत्येक कुंजी की सीमा 300 अनुरोध प्रति मिनट है। 50,000 स्ट्रिंग्स वाले एक लोकलाइजेशन जॉब में, 20 स्ट्रिंग्स प्रति अनुरोध के साथ 2,500 अनुरोध होते हैं, जो यदि आप pace को समान रूप से रखते हैं तो दस मिनट से कम समय में समाप्त हो जाते हैं। नीचे दिया गया स्क्रिप्ट इन-फ्लाइट कॉल्स के लिए एक सेमाफोर और लॉक-आधारित पसर को जोड़ता है, और 429 और 503 पर बैकऑफ करता है। एक वैलिडेशन विफलता के विपरीत, ये दो त्रुटियां अस्थायी हैं: 429 का अर्थ है धीमा करें, और 503 with 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 संस्करण का उपयोग करें ताकि स्ट्रिंग के अंदर लाइन ब्रेक को स्ट्रिंग सीमाओं के रूप में गलत न समझा जाए।
विपरीत दिशा: चीनी स्रोत टेक्स्ट
चीनी से अंग्रेजी अनुवाद के अपने विफलता मोड हैं। चीनी विषय और बहुवचन स्वतंत्र रूप से छोड़ देता है, इसलिए मॉडल को अनुमान लगाना होगा; उसे संदर्भ दें। "已发送" जैसी एक स्ट्रिंग "Sent", "Has been sent" या "You sent it" हो सकती है, यह इस बात पर निर्भर करता है कि यह एक बटन, एक स्टेटस बैज या एक टोस्ट लेबल है। समाधान प्रत्येक स्ट्रिंग के बगल में एक संदर्भ फ़ील्ड है, जो आपके डेवलपर्स द्वारा आपूर्ति की जाती है और JSON बैच में पास की जाती है: स्ट्रिंग कहाँ दिखाई देती है, इसकी अधिकतम लंबाई, और क्या यह एक लेबल है या एक वाक्य।
लंबाई सीमाओं को स्पष्ट रूप से मापें। अंग्रेजी UI टेक्स्ट अक्सर चीनी मूल से लंबा होता है, और चीनी लक्ष्यों के लिए विपरीत सत्य है, जो अक्षरों में छोटे लेकिन स्क्रीन पर चौड़े होते हैं क्योंकि प्रत्येक ग्लिफ़ पूर्ण चौड़ा होता है। जब एक बटन या टेबल हेडर की एक कठोर सीमा हो तो प्रॉम्प्ट में एक अक्षर बजट बताएं, और प्रतिक्रिया आने के बाद कोड में इसकी जांच करें।
चीनी स्रोत दस्तावेजों के लिए जिनमें लोगों और स्थानों के नाम हैं, शुरुआत में एक रोमाइजेशन रीतिनिर्देश निर्दिष्ट करें, जैसे कि बिना टोन मार्क्स वाला Hanyu Pinyin, और दोहराए जाने वाले नामों को ग्लोसरी में जोड़ें। इसके बिना, एक ही व्यक्ति एक ही दस्तावेज के भीतर दो वर्तनी के तहत दिखाई दे सकता है, जो वही असंगति है जिसे एक समीक्षक सबसे पहले नोटिस करेगा।
मर्ज करने से पहले नमूना और समीक्षा
स्वचालित जांच संरचनात्मक त्रुटियों को पकड़ती है, गलत अनुवाद नहीं। एक हल्का मानव चरण जोड़ें: प्रत्येक बैच के एक निश्चित प्रतिशत का नमूना लें, उदाहरण के लिए हर बीसवीं स्ट्रिंग, साथ ही हर स्ट्रिंग जिसे पुनः प्रयास की आवश्यकता थी, और उन्हें एक द्विभाषी समीक्षक को भेजें। बैच के प्रति समीक्षक की संपादन दर को ट्रैक करें। यदि यह बढ़ता है, तो प्रॉम्प्ट को कस करें, बैच आकार को छोटा करें, या सुधारे जा रहे पदों के लिए शब्दकोश प्रविष्टियां जोड़ें।
प्रॉम्प्ट, शब्दकोस संस्करण और बैट सेटिंग्स को प्रत्येक आउटपुट फ़ाइल के साथ रखें। जब शब्दकोश में कोई पद बदलता है, तो आप केवल उन स्ट्रिंग्स का पुनः अनुवाद कर सकते हैं जो उसे शामिल करते हैं, पूरे कॉरपस को फिर से चलाने के बजाय। स्रोत स्ट्रिंग और शब्दकोस संस्करण का एक साधारण content hash एक कैश कुंजी के रूप में काम करता है, और इसका अर्थ है कि अपरिवर्तित स्ट्रिंग्स अगली बार चलाने पर कुछ भी नहीं खर्च करतीं।
अंत में, मुश्किल स्ट्रिंग्स का एक रीग्रेशन सेट रखें: उनमें नेस्टेड स्थानधरक, बहुवचन रूप, एम्बेडेड HTML और लंबे संयुक्त नाम। जब भी आप प्रॉम्प्ट बदलें, इसे चलाएं, और बदलाव को रोल आउट करने से पहले आउटपुट को साइड-बाय-साइड तुलना करें।
स्थानीयकरण रन के लिए लागत का अनुमान
मान्यताएं, स्पष्ट रूप से: 30,000 स्रोत स्ट्रिंग्स, प्रत्येक में 12 अंग्रेजी शब्द, 20 के बैच में अनुवादित। प्रत्येक स्ट्रिंग के लिए लगभग 20 इनपुट टोकन और प्रत्येक अनुरोध के लिए 300 टोकन का फिक्स्ड सिस्टम प्रॉम्प्ट लें, और प्रत्येक स्ट्रिंग के लिए लगभग 35 आउटपुट टोकन। इससे 1,500 अनुरोध, लगभग 1.05 मिलियन इनपुट टोकन और लगभग 1.05 मिलियन आउटपुट टोकन मिलते हैं। प्रति मिलियन इनपुट टोकन $0.25 और प्रति मिलियन आउटपुट टोकन $1.00 पर, रन की लागत लगभग $0.26 plus $1.05, यानी लगभग $1.31 है। इसे एक order-of-magnitude estimate के रूप में मानें और अपनी खुद की usage लॉग से आंकड़ों के साथ मान्यताओं को बदलें।
ट्रायल क्रेडिट $0.50 का है और सात दिनों के लिए मान्य है, जो कुछ हजार स्ट्रिंग्स के नमूने पर पाइपलाइन की सत्यापन के लिए पर्याप्त है। स्क्रिप्ट नियंत्रण और UTF-8 हैंडलिंग के लिए ऐप क्विकस्टार्ट जारी रखें, या वर्तमान दरों के लिए मूल्य निर्धारण पृष्ठ देखें।
प्रश्न और उत्तर
क्या API दोनों दिशाओं में अनुवाद कर सकता है?
हाँ। चीनी से अंग्रेजी और अंग्रेजी से चीनी दोनों एक ही चैट एंडपॉइंट के माध्यम से काम करते हैं; आप प्रणाली प्रॉम्प्ट में दिशा चुनते हैं।
मैं मॉडल को {name} जैसे प्लेसहोल्डर बदलने से कैसे रोकें?
प्रॉम्प्ट में नियम बताएं, फिर कोड में स्रोत और लक्ष्य के बीच स्थानधरक सूची की तुलना करके सत्यापित करें, और असंगतता पर पुनः प्रयास करें या उसे फ्लैग करें।
क्या JSON मोड स्विच है?
संरचित आउटपुट निर्देशों के माध्यम से प्राप्त होता है। प्रॉम्प्ट में सटीक आकार निर्दिष्ट करें, अनावश्यक कोड फेंस हटाएं, फिर परिणाम को पार्स और सत्यापित करें।
मैं एक बड़े बैच को कितनी तेज़ी से चला सकता हूँ?
प्रति कुंजी प्रति मिनट 300 अनुरोध की अनुमति है। अनुरोधों को समान रूप से व्यवस्थित करें और 429 या 503 प्रतिक्रियाओं पर बैक ऑफ़ करें।
आपकी कुंजी बस एक फ़ॉर्म दूर है
एक खाता बनाएं, कुंजी कॉपी करें, बेस URL बदलें। सेटअप यही तक सीमित है।