Обновлено
Разработка приложения на китайском языке: промпты, контроль скриптов и UTF-8
Выпуск функции на китайском языке — это в основном проблема локализации, а не моделирования. Вы решаете, какой скрипт видят пользователи, сохраняете чистоту кодировок от файла до сети и учитываете токены для текста, который не разбивается пробелами. Это руководство охватывает эти три уровня с кодом, который вы можете запустить против эндпоинта чат-завершений прямо сейчас.
Что вы подключаете
Сервис предоставляет POST /v1/chat/completions и GET /v1/models по адресу https://api.chinesellmapi.com/v1. Аутентификация — через bearer-ключ, единственный идентификатор модели — uncensored. Это API только для текста с одной моделью, поэтому выбирать не из чего: ваша работа по локализации происходит полностью в промпте и в вашем коде вокруг него.
Ограничения, о которых стоит знать перед проектированием: контекстное окно на 100 000 токенов, разделяемое промптом и завершением, max_tokens по умолчанию 2 048 с пределом 32 000, тела запросов до 8 МБ и 300 запросов в минуту на ключ. Потоковая передача (stream: true) работает через server-sent events, принимаются инструменты в формате вызова функций OpenAI.
Новые аккаунты получают пробный баланс $0,50 на семь дней без указания платёжных данных; зарегистрируйтесь с помощью электронной почты и пароля, и ключ появится сразу. FAQ по пробному периоду подробно описывает правила.
Пишите промпты на китайском и указывайте, какой именно
Смешанные языковые промпты — самый частый источник отклонений в локализованных функциях. Если инструкции на английском, а контент на китайском, ответы иногда приходят на английском или в смеси. Надёжный шаблон — системный промпт на целевом языке, указывающий три вещи: роль, скрипт и региональный словарный запас. Держите текст, предоставленный пользователем, в отдельном сообщении, чтобы его не приняли за инструкции.
Вот настройка на упрощённом китайском. Обратите внимание, что системный промпт говорит модели отвечать только упрощёнными иероглифами и избегать случайного английского, за исключением имен собственных:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.chinesellmapi.com/v1",
api_key=os.environ["API_KEY"],
)
SYSTEM_SC = (
"你是一名产品文案助手。始终使用简体中文回答,使用中国大陆常见的用词和标点,"
"不要夹杂繁体字或英文句子,专有名词除外。"
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": SYSTEM_SC},
{"role": "user", "content": "为一款记账应用写三条应用商店的一句话简介。"},
],
max_tokens=300,
temperature=0.7,
)
print(resp.choices[0].message.content)
print(resp.usage)
Две привычки быстро окупаются. Во-первых, делайте промпты короткими и конкретными: роль, формат вывода, одно-два ограничения. Во-вторых, помещайте требование языка в системное сообщение, а не повторяйте его в каждом повороте пользователя, чтобы история разговора не размывала его.
Контроль вывода: упрощённый против традиционного
Выбор скрипта — это продуктовое решение, связанное с локалью пользователя, а не настройка модели. Продукты, ориентированные на материковый Китай, ожидают упрощённые иероглифы; аудитории Тайваня и Гонконга ожидают традиционные, с разным словарным запасом (обычные примеры — термины ПО, сети и баз данных). Практический подход — сопоставить тег локали с системным промптом и сделать выбор явным в коде.
- zh-CN, zh-SG: Упрощённый китайский, материковый словарь, цифры в половину ширины, китайская пунктуация в полную ширину.
- zh-TW: Традиционный, тайваньский словарь, угловые скобки для цитат распространены.
- zh-HK: Традиционный с гонконгской терминологией; предоставьте краткий глоссарий, если в вашем продукте есть фиксированные термины.
Традиционный вариант того же вызова выглядит так; меняется только системный промпт:
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])
SYSTEM_TC = (
"你是一名產品文案助手。請一律使用繁體中文回答,採用臺灣常用的詞彙與全形標點,"
"例如「軟體」「網路」「資料庫」,不要混入簡體字。"
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": SYSTEM_TC},
{"role": "user", "content": "請用兩句話說明什麼是雙重驗證。"},
],
max_tokens=200,
)
print(resp.choices[0].message.content)
Модели иногда выдают несколько символов другого скрипта, особенно когда сообщение пользователя само на другом скрипте. Добавьте дешёвую пост-проверку и повторите попытку один раз с более строгим указанием, если сработает. Для всего, кроме выборочной проверки, пропустите вывод через специализированную библиотеку конвертации, такую как OpenCC:
# A cheap guard: flag replies that contain characters that exist only in Simplified.
# The set below is a small sample, not a full list; use a converter such as OpenCC
# for production-grade checks.
SIMPLIFIED_ONLY = set("这个们说话时间书买卖东车门开关见觉")
def looks_simplified(text: str) -> bool:
return any(ch in SIMPLIFIED_ONLY for ch in text)
reply = "請用繁體中文回覆的範例文字"
print(looks_simplified(reply)) # False
UTF-8 от диска до сети и обратно
Большинство багов с «битым» китайским — не проблема модели. Они возникают из-за кодировки где-то в конвейере: консоль Windows с устаревшей кодовой страницей, CSV, открытый без указания кодировки, прокси, переписывающий content-type. Делайте каждый разрыв явным.
В Python передавайте encoding="utf-8" в open, и если вы вручную создаёте JSON, используйте ensure_ascii=False, за которым следует .encode("utf-8"). Пример ниже делает это с помощью обычного requests, который также показывает сырую HTTP-форму вызова:
import json
import os
import requests
# 1) Always open files as UTF-8, never rely on the platform default encoding.
with open("notes_zh.txt", "r", encoding="utf-8") as f:
note = f.read()
payload = {
"model": "uncensored",
"messages": [{"role": "user", "content": "请把下面的笔记整理成三个要点:\n" + note}],
"max_tokens": 400,
}
# 2) requests encodes json= as UTF-8 for you. If you build the body by hand,
# keep Chinese readable and make the encoding explicit.
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
r = requests.post(
"https://api.chinesellmapi.com/v1/chat/completions",
headers={
"Authorization": "Bearer " + os.environ["API_KEY"],
"Content-Type": "application/json; charset=utf-8",
},
data=body,
timeout=60,
)
r.raise_for_status()
r.encoding = "utf-8"
print(r.json()["choices"][0]["message"]["content"])
В Node.js readFile возвращает Buffer, если не указана кодировка, и шаблонные строки с китайским текстом в порядке, если исходный файл сохранён как UTF-8. Официальный SDK сериализует тело за вас:
import { readFile } from "node:fs/promises";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.chinesellmapi.com/v1",
apiKey: process.env.API_KEY,
});
// Pass "utf8" explicitly; without it readFile returns a Buffer, not a string.
const note = await readFile("notes_zh.txt", "utf8");
const res = await client.chat.completions.create({
model: "uncensored",
messages: [{ role: "user", content: `请把下面的笔记整理成三个要点:\n${note}` }],
max_tokens: 400,
});
console.log(res.choices[0].message.content);
console.log(res.usage);
Ещё две ловушки. Обрезание строк по длине в байтах может разрезать символ пополам, поэтому всегда разрезайте по символам. А при потоковой передаче декодируйте поток как UTF-8 инкрементально, потому что многобайтный символ может оказаться разбитым между сетевыми чанками; SDK обрабатывают это, но самописные парсеры часто нет.
Учёт токенов для китайского текста
В китайском нет пробелов, поэтому подсчёт слов бесполезен для планирования. В качестве предположения для планирования, а не измеренной константы, считайте один китайский символ примерно 1–2 токенами и используйте 1,5, когда нужно одно число. Латинские слова и цифры, встроенные в текст, ближе к 1,3 токена на слово. Соотношение варьируется в зависимости от словарного запаса, поэтому единственная авторитетная цифра — объект usage, возвращаемый с каждым ответом.
Небольшая вспомогательная функция позволяет легко оценить промпт перед отправкой и настроить соотношения на основе реального использования со временем:
import re
CJK = re.compile(r"[㐀-鿿- -〿]")
def rough_tokens(text: str, per_cjk: float = 1.5, per_other_word: float = 1.3) -> int:
"""Planning estimate only. The 1.5 and 1.3 ratios are assumptions, not
measured values; compare against resp.usage and adjust them."""
cjk = len(CJK.findall(text))
others = len(re.findall(r"[A-Za-z0-9_]+", CJK.sub(" ", text)))
return round(cjk * per_cjk + others * per_other_word)
sample = "订单 A-1042 已发货,预计周三送达。"
print(rough_tokens(sample))
Рабочий пример с заявленными допущениями: 2000-символьная статья при 1,5 токена на символ — это около 3000 входных токенов. 400-символьное резюме — это около 600 выходных токенов. При цене $0,25 за миллион входных токенов и $1,00 за миллион выходных токенов один вызов стоит примерно $0,00075 за вход плюс $0,0006 за выход, всего около $0,00135. Поскольку контекст ограничен 100 000 токенов, то же предположение означает, что промпт из примерно 40 000 символов оставляет место для ответа.
При длинных документах разбивайте их по абзацам или заголовкам, не разрывая предложения, и явно указывайте max_tokens. Цены и лимиты — на странице с ценами.
Сохранение многооборотного китайского чата в пределах контекстного окна
Функции чата отправляют всю историю в каждом запросе, поэтому стоимость и контекст растут с каждым оборотом. Поскольку окно составляет 100 000 токенов, общих для промпта и завершения, длинный разговор в конечном итоге вызывает ошибку 400, если ничего не предпринимать. Решите политику обрезки заранее, а не реагируйте на ошибки.
Простая политика — сохранять системный промпт, удалять самые старые повороты, пока оценённый промпт не уложится в бюджет, и резервировать остаток для ответа. Скетч ниже повторно использует оценщик из предыдущего раздела:
MAX_PROMPT_TOKENS = 48_000 # leave headroom under the 100,000 window for the reply
def trim_history(messages, estimate):
# messages[0] is the system prompt and is always kept
system, rest = messages[0], messages[1:]
while rest and sum(estimate(m["content"]) for m in [system] + rest) > MAX_PROMPT_TOKENS:
rest.pop(0) # drop the oldest turn first
return [system] + rest
Для продуктов, где важен ранний контекст, таких как обучающий бот или помощник поддержки, заменяйте потерянные обороты коротким сообщением-резюме, а не отбрасывайте их. Попросите модель сжать старые обороты в несколько предложений на том же алфавите, что и в разговоре, а затем вставьте это резюме сразу после системного промпта. Это стоит одного дополнительного вызова время от времени и сохраняет стабильность персоны и фактов.
Также помните разумно ограничивать max_tokens для чата. Ответы в интерфейсе мессенджера редко требуют более нескольких сотен токенов, а более строгое ограничение делает задержку и стоимость более предсказуемыми. Потоковая передача ответа токен за токеном улучшает воспринимаемую скорость, особенно для китайского текста, где пользователи читают короткими фрагментами.
Чек-лист перед запуском и следующие шаги
- Сопоставьте каждую локаль с системным промптом и протестируйте отображение.
- Явно устанавливайте кодировки при чтении файлов, телах запросов и логах.
- Фиксируйте
usageпо функциям и еженедельно сравнивайте с оценками. - Обработайте 402 (
no_credit), 429 и 503 (upstream_busy) по-разному: пополните баланс, замедлите темп или повторите запрос через несколько секунд. - Рассматривайте 403 (
content_blocked) как окончательный ответ для этого запроса, а не как случай для повторной попытки.
Если ваше приложение включает конвейеры перевода, продолжайте с руководством по переводу и локализации; для сериализованной художественной литературы и чата персонажей см. руководство по веб-художественной литературе. Полный справочник параметров находится в документации.
Вопросы и ответы
Как сделать так, чтобы ответы приходили только на упрощённом китайском языке?
Укажите это в системном промпте на китайском, например «всегда отвечайте на упрощённом китайском языке с материковым словарём». Сохраните эту инструкцию в системном сообщении и добавьте проверку после генерации, если вывод должен быть строгим.
Могу ли я запросить традиционный китайский для пользователей из Тайваня?
Да. Используйте системный промпт, написанный традиционными иероглифами, с указанием нужной региональной лексики, и сопоставьте его с локалью zh-TW в вашем коде.
Почему я вижу искажённые китайские символы в выводе?
Почти всегда проблема кодировки на стороне клиента. Читайте файлы как UTF-8, устанавливайте charset типа контента и убедитесь, что ваш терминал или просмотрщик логов также использует UTF-8.
Сколько токенов занимает китайский текст?
Планируйте, исходя из примерно 1,5 токенов на символ как приближённой оценки, затем проверьте поле usage в каждом ответе и скорректируйте свою оценку.
Ваш ключ — в одной форме от вас
Создайте аккаунт, скопируйте ключ, измените базовый URL. Вот и вся настройка.