# বাংলা text নিয়ে কাজ করা

> API দিয়ে বাংলা বা অন্য non-Latin লেখা কীভাবে চলে: কেন বেশি token লাগে, usage থেকে সেটা কীভাবে মাপবেন, max_tokens, context আর খরচের ওপর তার প্রভাব, prompt লেখার টিপ, stream-এ UTF-8, আর নিজের text-এ model তুলনা করার script।

বাংলা লেখা Tokens-এর ভেতর দিয়ে অন্য যেকোনো text-এর মতোই যায়: `messages`-এ UTF-8 হিসেবে পাঠান, উত্তরও সেভাবেই ফেরত পান। তবে ইংরেজির সাথে তিন জায়গায় তফাত আছে। এক, একই কথা বলতে সাধারণত বেশি token লাগে, তাই খরচও বেশি হয় আর context window-ও তাড়াতাড়ি ভরে। দুই, কিছু bug শুধু multi-byte character থেকে হয়, বিশেষ করে stream-এ। তিন, model বেছে নেওয়ার আগে নিজের text-এ output-এর মান যাচাই করে নেওয়া দরকার। এই পেজে তিনটাই আছে, আর শেষে আছে একটা script, যেটা একই বাংলা prompt-এ কয়েকটা model তুলনা করে।

হিন্দি, আরবি, থাই, চীনা আর অন্য non-Latin script-এও একই কথা খাটে, শুধু মাত্রাটা আলাদা।

## বাংলায় কেন বেশি token লাগে

Model character পড়ে না। আগে একটা tokenizer text-কে ছোট ছোট টুকরোয় ভাগ করে, এই টুকরোগুলোই token, আর বিল হয় token ধরে। Tokenizer বানানো হয় বিশাল একটা text-ভান্ডার থেকে। সেই text-এ যে script কম ছিল, তাকে সাধারণত বেশি সংখ্যক ছোট টুকরোয় ভাঙা হয়। কতটা ভাঙবে, সেটা model-এর tokenizer-এর ওপর নির্ভর করে, আর যারা model বানায় তারা এক version থেকে আরেক version-এ tokenizer বদলেও দিতে পারে। যেমন Anthropic নিজেই লিখেছে, নতুন Claude model-এ পুরনোগুলোর চেয়ে আলাদা tokenizer থাকে, তাই একই text-এ token সংখ্যা আলাদা আসে। যে model ব্যবহার করবেন, prompt সেই model দিয়েই আবার গুনে নিতে বলেছে ([Anthropic: token counting](https://platform.claude.com/docs/en/build-with-claude/token-counting), October 2026-এ দেখা)।

বাংলা লেখা আবার অনেকগুলো ছোট Unicode অংশ দিয়ে তৈরি। Python-এ নিজেই দেখে নিন:

```python
text = "বাংলা"
print(len(text))                  # 5 Unicode code points
print(len(text.encode("utf-8")))  # 15 bytes: 3 bytes for each Bengali character
print(len("ক্ষ"))                  # 3: ক + hasanta (virama) + ষ make one conjunct
```

UTF-8-এ প্রতিটা বাংলা অক্ষর নেয় 3 byte, আর একটা যুক্তাক্ষর মানে কয়েকটা code point একসাথে। যে tokenizer byte ধরে কাজ করে, সে এই ছোট টুকরোগুলো থেকেই শুরু করে। সেগুলো জুড়ে কতটা বড় token বানাতে পারবে, তা নির্ভর করে তার training text-এ বাংলা কতটা ছিল তার ওপর, আর সেটা model ভেদে আলাদা।

কোথাও পড়া কোনো অনুপাতের ওপর ভরসা করবেন না। নিজের text আর নিজের model-এ মেপে দেখুন, নিচে যেভাবে দেখানো হয়েছে।

## আসল response থেকে মেপে দেখুন

প্রতিটা response-এর `usage`-এ token সংখ্যা দেওয়া থাকে। একই মানের একটা ইংরেজি আর একটা বাংলা text পাঠান, তারপর `prompt_tokens` মিলিয়ে দেখুন। `max_tokens` কম রাখুন, তাহলে call-এ প্রায় কিছুই খরচ হবে না: input-এর বিল হবে, আর output থাকবে বড়জোর কয়েকটা token।

```python title="measure_tokens.py"
import os
from openai import OpenAI

client = OpenAI(base_url="https://tokens.bd/v1", api_key=os.environ["TOKENS_API_KEY"])

ENGLISH = (
    "Our team launched a new payment page last week. In the first two days, "
    "some users got an error when paying by card. The cause was an old library, "
    "and we have updated it."
)
BENGALI = (
    "আমাদের দল গত সপ্তাহে নতুন পেমেন্ট পেজ চালু করেছে। প্রথম দুই দিনে কয়েকজন ব্যবহারকারী "
    "কার্ড দিয়ে টাকা দিতে গিয়ে ত্রুটি পেয়েছেন। সমস্যাটি একটি পুরনো লাইব্রেরির কারণে হয়েছিল, "
    "এবং আমরা সেটি আপডেট করেছি।"
)

def input_tokens(model: str, text: str) -> int:
    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": text}],
        max_tokens=16,  # the input is what we measure; keep the output tiny
    )
    return resp.usage.prompt_tokens

for model in ["deepseek/deepseek-v4.1-flash"]:  # add more ids from /models
    en = input_tokens(model, ENGLISH)
    bn = input_tokens(model, BENGALI)
    print(f"{model}: English {en} tokens, Bengali {bn} tokens, ratio {bn / en:.2f}")
```

ফলাফল পড়ার সময় কয়েকটা কথা মাথায় রাখুন:

- `prompt_tokens`-এ কয়েকটা বাড়তি token থাকে, যেগুলো model-এর chat format আপনার message-এর চারপাশে বসায়। খুব ছোট text-এ এই বাড়তিটাই অনুপাত বিগড়ে দেয়, তাই বাস্তব মাপের কয়েক অনুচ্ছেদ দিয়ে মাপুন।
- **যে যে model ব্যবহার করতে পারেন, প্রতিটার জন্য** চালান। একই text-এ model ভেদে token সংখ্যা অনেক আলাদা হতে পারে।
- আসল prompt দিয়ে মাপুন। System prompt, tool definition আর paste করা code বেশিরভাগই ইংরেজি বা code, আপনার input-এর শুধু একটা অংশ বাংলা।
- যেসব model উত্তরের আগে ভেবে নেয়, তাদের কেউ কেউ খুব ছোট `max_tokens` নিতে চায় না। error এলে সংখ্যাটা একটু বাড়িয়ে দিন।

পাঠানোর আগেও token গুনে নেওয়া যায়, দেখুন [token counting](/docs/token-counting)।

## `max_tokens`, context আর খরচের ওপর প্রভাব

- **`max_tokens`** গোনে output token। একই দৈর্ঘ্যের বাংলা উত্তরে বেশি token লাগে, তাই ইংরেজিতে যে সীমা যথেষ্ট, সেটা বাংলা উত্তরকে মাঝপথে কেটে দিতে পারে। `finish_reason` দেখুন: `length` মানে আপনার সীমাতেই উত্তর থেমে গেছে। বাংলা উত্তরের জন্য `max_tokens` বাড়ান, তারপর মেপে নিশ্চিত হয়ে নিন।
- **Context window।** window মাপা হয় token দিয়ে, character দিয়ে নয়। একই কথোপকথন ইংরেজির চেয়ে বাংলায় আগে সীমায় পৌঁছায়, তাই history আগেভাগে ছেঁটে দিন বা summary করে নিন। প্রতিটা model-এর সীমা আছে [/models](/models)-এ।
- **খরচ।** আপনি token ধরে model-এর দামে বিল দেন, তাই বাংলা request-এর খরচ হলো তার token সংখ্যা গুণ দাম। শুধু token-প্রতি দাম দেখে model তুলনা করবেন না, আপনার বাংলা কাজে request-প্রতি খরচ দেখুন: যে model-এর দাম কম কিন্তু বাংলার জন্য tokenizer খারাপ, তার request-প্রতি খরচ বেশিও হতে পারে। দেখুন [খরচ কমানোর উপায়](/docs/cutting-costs)।
- **গতি।** লিখতে বেশি token লাগলে শেষ হতেও বেশি সময় লাগে, প্রতি সেকেন্ডে token-এর হার একই থাকলেও।

## বাংলায় prompt লেখা

এগুলো এমন কিছু অভ্যাস, যেগুলো output-কে আন্দাজ করার মতো রাখে। প্রতিটা নিজের কাজে test করে নিন।

- **Output-এর ভাষা স্পষ্ট করে বলুন।** Model যে ভাষাকে সবচেয়ে সম্ভাব্য মনে করে সেই ভাষায় উত্তর দেয়, সেটা আপনার instruction, user-এর message বা system prompt যেটা থেকেই আসুক। তাই system prompt-এ লিখে দিন, যেমন `Always answer in Bengali (Bangla script).`। ইংরেজি চাইলে সেটাও একইভাবে পরিষ্কার বলুন।
- **Script-এর নাম বলুন।** "Bengali" চাইলে উত্তর আসতে পারে বাংলা হরফে, আবার Latin অক্ষরে লেখা বাংলাতেও (romanized Bengali)। আপনার যদি Bangla script দরকার হয়, সেটা বলে দিন।
- **Code আর identifier ইংরেজিতে রাখুন।** Variable-এর নাম, function-এর নাম, file path, JSON key আর error message ইংরেজিতে রাখাই ভালো। instruction এভাবে লিখুন: `Explain in Bengali, but keep code, identifiers and JSON keys unchanged.`
- **Machine-readable output।** আপনার code উত্তর parse করলে একটা fixed format চান আর কোন digit চাই বলে দিন। বাংলা অঙ্ক আর ASCII digit (0123) আলাদা character। Python-এ বাংলা অঙ্কের string-ও `int` দিয়ে সংখ্যা হয়ে যায়, কিন্তু `[0-9]` দেওয়া regular expression সেটা ধরবে না। ASCII চাইলে `Use ASCII digits` বলে দিন।
- **চাওয়া format-এ একটা উদাহরণ দিন।** Prompt-এ ছোট একটা বাংলা উদাহরণ থাকলে tone আর script যতটা বোঝা যায়, বর্ণনা দিয়ে ততটা যায় না।
- **Mixed-script text-এ সাবধান থাকুন।** বাংলার মধ্যে ইংরেজি technical term থাকা স্বাভাবিক, কিন্তু দেখতে একরকম character থেকে bug হতে পারে। বাংলা দাঁড়ি `।` (U+0964) pipe `|` নয়, Latin `I`-ও নয়। আবার বাংলা অঙ্কের শূন্য (U+09E6) Latin `0` নয়। Search, তুলনা আর parsing এগুলোকে আলাদা character ধরে।
- **তুলনা বা store করার আগে Unicode normalize করুন।** একই বাংলা লেখা একাধিকভাবে encode হতে পারে। যেমন `য়` একটা code point (U+09DF) হতে পারে, আবার দুটোও (U+09AF, তারপর U+09BC), আর `কো`-এর স্বরচিহ্ন একটা code point (U+09CB) হতে পারে, আবার দুটোও (U+09C7, তারপর U+09BE)। তুলনা, search বা duplicate বাছাইয়ের আগে NFC দিয়ে normalize করে নিন:

```python
import unicodedata

def clean(text: str) -> str:
    return unicodedata.normalize("NFC", text)

assert clean("য়") == "য়"          # য় composed form becomes two code points
assert clean("কো") == "কো"  # কো, two vowel parts become one sign
```

যে text পাঠান সেটা normalize করলে আপনার prompt-গুলোও একরকম থাকে, test বা cache করলে এটা কাজে লাগে। এতে model-এর উত্তর লেখার ধরন বদলায় না।

## Streaming আর client-এ UTF-8

`stream: true` দিলে উত্তর টুকরো টুকরো হয়ে আসে। Official SDK ব্যবহার করলে সে-ই stream decode করে দেয়, তখন এই অংশ বাদ দিতে পারেন। আপনি নিজে response-এর byte পড়লে মনে রাখবেন: একটা বাংলা অক্ষর 3 byte, আর network chunk ঠিক তার মাঝখানে শেষ হয়ে যেতে পারে। তখন প্রতিটা chunk আলাদা করে decode করলে replacement character (`�`) বা error আসে।

এর সমাধান incremental decoder, যে অসম্পূর্ণ byte ধরে রাখে, বাকিটা না আসা পর্যন্ত।

:::code-tabs

```python title="Python"
import codecs

decoder = codecs.getincrementaldecoder("utf-8")()

def feed(chunk: bytes) -> str:
    return decoder.decode(chunk)       # returns only complete characters

data = "বা".encode("utf-8")            # 6 bytes
print(repr(data[:2].decode("utf-8", errors="replace")))  # '�' when split naively
print(repr(feed(data[:2]) + feed(data[2:])))             # 'বা' with the incremental decoder
```

```javascript title="Node.js / browser"
const decoder = new TextDecoder("utf-8");

function feed(chunk) {
  // { stream: true } keeps an unfinished character until the next chunk
  return decoder.decode(chunk, { stream: true });
}
```

:::

আরও কয়েকটা কথা:

- আগে decode করুন, তারপর line-এ ভাগ করুন। Raw byte newline ধরে ভাগ করা UTF-8-এ নিরাপদ (newline byte কখনো multi-byte character-এর ভেতরে আসে না), কিন্তু decode হয়ে যাওয়া string-কে byte offset ধরে কাটা নিরাপদ নয়।
- দৈর্ঘ্য ধরে text কাটলে character ধরে কাটুন, byte ধরে নয়। Byte ধরে কাটলে একটা character-এর মাঝখানে কাটা পড়তে পারে।
- Request পাঠান `Content-Type: application/json` দিয়ে, আর আপনার HTTP library যেন body UTF-8-এ encode করে সেটা দেখে নিন। JSON-এর প্রতিটা character নিজে হাতে escape করে লিখবেন না।
- Server-sent events format-এর বিস্তারিত [streaming](/docs/streaming) পেজে আছে। Stream-এর শেষে token count পেতে request-এ `"stream_options": {"include_usage": true}` যোগ করুন।
- Terminal বা Windows-এ দেখালে font বা console code page-এর কারণে memory-তে ঠিক থাকা text-ও box বা `?` হয়ে দেখা দিতে পারে। যাচাই করতে string-টা একটা UTF-8 file-এ লিখে দেখুন।

## কোন model বাংলা ভালো পারে, যাচাই করুন

এর কোনো ভরসাযোগ্য shortcut নেই। কোনো model বানানেওয়ালা বলল তাদের model একটা ভাষা সাপোর্ট করে, তাতে আপনার কাজে সেটা কেমন চলবে বোঝা যায় না। public test-এ ভালো score মানেও আপনার text-এ ভালো চলা নয়। তাই ranking দেখে model বাছবেন না, নিজে test করুন:

1. **20 থেকে 50টা আসল prompt জোগাড় করুন।** আপনার product যে ধরনের text সামলায়, সেই ধরনেরই নিন: support message, summary, code ব্যাখ্যা, form-এর data।
2. **নিচের script দিয়ে একই prompt কয়েকটা model-এ চালান**, আর উত্তরগুলো রেখে দিন।
3. **উত্তর নিজে পড়ুন, অথবা কোনো বাংলাভাষীকে দিয়ে পড়িয়ে নিন।** আগে দেখুন উত্তর ঠিক কি না, তারপর ভাষা সাবলীল কি না, আপনি যে script আর ভাষা চেয়েছিলেন সেটাই এসেছে কি না, আর নাম, সংখ্যা ও technical term অক্ষত আছে কি না।
4. **খরচ আর গতি মিলিয়ে দেখুন** `usage` আর চলার সময় থেকে, মানের পাশাপাশি।
5. **Model বা prompt বদলালে আবার চালান।** Model ঘনঘন বদলায়, তাই আপনার prompt-এর সেটটা যত্ন করে রেখে দিন।

প্রার্থী model পাবেন [model বাছাই](/docs/choosing-a-model) আর [/models](/models) থেকে। সেখানকার ঠিক id ব্যবহার করুন, অথবা `GET /v1/models` থেকে নিন, যেটা আপনার key যেসব model call করতে পারে তার তালিকা দেয়।

## একই prompt-এ model তুলনা করার script

এই script প্রতিটা model-এ একটা বাংলা prompt পাঠায়, তারপর token usage, finish reason, কত সময় লাগল আর উত্তরটা print করে। যেসব model তুলনা করতে চান, তাদের id `MODELS`-এ বসান, অথবা `TOKENS_MODELS` environment variable-এ কমা দিয়ে আলাদা করে দিন।

```python title="compare_models.py"
import os
import time
import unicodedata

from openai import OpenAI

client = OpenAI(base_url="https://tokens.bd/v1", api_key=os.environ["TOKENS_API_KEY"])

MODELS = os.environ.get("TOKENS_MODELS", "deepseek/deepseek-v4.1-flash").split(",")

SYSTEM = (
    "তুমি একজন সহায়ক সহকারী। সবসময় বাংলা অক্ষরে বাংলায় উত্তর দেবে। "
    "কোড, ভেরিয়েবলের নাম এবং JSON কী ইংরেজিতেই রাখবে।"
)
PROMPT = unicodedata.normalize(
    "NFC",
    "নিচের বার্তাটি দুই বাক্যে সংক্ষেপে লেখো এবং গ্রাহকের জন্য একটি বিনীত উত্তরের খসড়া দাও।\n\n"
    "বার্তা: আমি গতকাল কার্ড দিয়ে টাকা দিয়েছি, কিন্তু আমার অ্যাকাউন্টে ক্রেডিট আসেনি। "
    "পেমেন্টের রসিদ আমার কাছে আছে। দয়া করে দ্রুত দেখুন।",
)

print(f"Prompt: {len(PROMPT)} characters\n")

for model in (m.strip() for m in MODELS if m.strip()):
    started = time.time()
    try:
        resp = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": SYSTEM},
                {"role": "user", "content": PROMPT},
            ],
            max_tokens=1000,
        )
    except Exception as err:  # a model your key cannot call, a rate limit, and so on
        print(f"=== {model}\nerror: {err}\n")
        continue

    elapsed = time.time() - started
    choice = resp.choices[0]
    usage = resp.usage
    print(f"=== {model}")
    print(
        f"input {usage.prompt_tokens} tokens, output {usage.completion_tokens} tokens, "
        f"finish_reason={choice.finish_reason}, {elapsed:.1f}s"
    )
    print(choice.message.content)
    print()
```

আপনার key set করে চালান:

:::code-tabs

```bash title="macOS / Linux"
export TOKENS_API_KEY="tok_live_your_key"
export TOKENS_MODELS="deepseek/deepseek-v4.1-flash"
python compare_models.py
```

```powershell title="Windows PowerShell"
$env:TOKENS_API_KEY = "tok_live_your_key"
$env:TOKENS_MODELS = "deepseek/deepseek-v4.1-flash"
$env:PYTHONUTF8 = "1"
python compare_models.py
```

:::

Windows-এ `PYTHONUTF8=1` দিলে Python console-এ বাংলা ঠিকভাবে print করে। এটা না দিলে request ঠিকমতো গেলেও encoding error দেখতে পারেন।

Output পড়ার সময় `output tokens`-এর পাশে `finish_reason` দেখুন। `length` মানে `max_tokens`-এ এসে উত্তর কেটে গেছে, আর যে model-গুলো তুলনা করছেন তাদের শেষ করতে আলাদা আলাদা সীমা লাগতে পারে। এই call-ও অন্য call-এর মতোই বিল হয়। উপরের script-এর প্রতিটা run ছোট, আর যে key দিয়ে test করছেন তাতে একটা cap দিয়ে রাখলে ভুল হলেও খরচ কম থাকে। দেখুন [API keys](/docs/api-keys)।

## সমস্যা হলে

**Prompt বাংলায় হলেও উত্তর ইংরেজিতে আসছে।** System prompt-এ স্পষ্ট instruction দিন, ইংরেজিতে আর বাংলাতেও, যেমন `Always answer in Bengali (Bangla script).`। Conversation-এর পরের দিকের কোনো instruction বা উদাহরণ ইংরেজিতে আছে কি না দেখে নিন।

**উত্তর আসছে Latin অক্ষরে লেখা বাংলায়।** Bangla script চাই, নাম ধরে বলুন, আর ছোট একটা উদাহরণ দেখিয়ে দিন।

**Output-এ box, `?` বা `�` দেখা যাচ্ছে।** শুধু screen-এ হলে সেটা font বা console-এর সমস্যা। save করা file বা data-তে থাকলে আপনার stream decoding আর byte কাটার ধাপগুলো দেখুন।

**উত্তর বাক্যের মাঝখানে থেমে গেছে।** `finish_reason` দেখুন। `length` হলে `max_tokens` বাড়ান। আপনার Wallet-এ ব্যালান্স কম থাকলে Tokens সেটা কমিয়ে দিয়ে থাকতে পারে, দেখুন [plan, credit আর Wallet](/docs/plans-and-wallet)।

**ইংরেজির তুলনায় বিল বেশি আসছে।** token সংখ্যা দেখে এটাই প্রত্যাশিত। প্রথম script দিয়ে মাপুন, নিজের text-এ খরচ দেখে model বাছুন, আর দেখুন [খরচ কমানোর উপায়](/docs/cutting-costs)।

**দেখতে একই রকম text তুলনা বা search-এ মিলছে না।** দুই দিকই NFC-তে normalize করুন, আর `।` ও `|`-এর মতো দেখতে একরকম character আছে কি না দেখুন।

---
Page: https://tokens.bd/bn/docs/bengali-text
