# Vision: model-কে image পাঠানো

> Chat Completions-এ (image_url part) বা Messages-এ (image block) model-কে image পাঠানোর নিয়ম: URL আর base64 দুটো form, size limit, gateway কী translate করে, image input-এর billing, আর model image নেয় কি না কীভাবে যাচাই করবেন।

Vision মানে model-কে একটা image পাঠিয়ে সেটা নিয়ে প্রশ্ন করা: যেমন error-এর screenshot, কোনো diagram, বা whiteboard-এর ছবি। Tokens-এ image আলাদা করে যায় না, সাধারণ chat request-এর ভেতরেই যায়। `POST https://tokens.bd/v1/chat/completions`-এ যায় `image_url` content part হিসেবে, আর `POST https://tokens.bd/v1/messages`-এ `image` content block হিসেবে। gateway image-টা provider পর্যন্ত পৌঁছে দেয় আর provider যা report করে, সেটাই bill করে। model image পড়তে পারবে কি না, সেটা model-এর ওপর নির্ভর করে।

Tokens-এ আলাদা কোনো image endpoint নেই। image generation, file upload আর Anthropic Files API এখানে চলে না ([Models ও usage](/docs/models-and-usage) দেখুন)। এই পেজ শুধু image **input** নিয়ে।

## Model image নেয় কি না যাচাই করুন

সব model image পড়তে পারে না। [/models](/models) পেজের model catalog-এ প্রতিটা model-এর context window, দাম আর বর্ণনা আছে, কিন্তু "image support করে" বলে আলাদা কোনো field নেই। জানার উপায়:

- [/models](/models) পেজে model-এর বর্ণনা পড়ুন, আর model-টা যারা বানিয়েছে তাদের নিজেদের documentation দেখুন।
- [Model বেছে নেওয়া](/docs/choosing-a-model) পেজের vision অংশে কয়েকটা model-এর তালিকা আছে, কোনটা কী নেয় সেই হিসেবে।
- একটা ছোট test image পাঠিয়ে (নিচের উদাহরণগুলো) জিজ্ঞেস করুন "What is in this image?"। যে model image পড়ে, সে ছবি নিয়েই উত্তর দেবে।

text-only model সব সময় জোরে আওয়াজ করে fail করে না। সেটা provider-এর সিদ্ধান্ত: হয় `400` পাঠাবে (আপনার কাছে আসবে `invalid_request` হয়ে, [errors](/docs/errors) দেখুন), নয়তো image বাদ দিয়ে শুধু text দেখে উত্তর দেবে। উত্তরে ছবির কোনো ছাপ না থাকলে ধরে নিন, এই model image নেয় না।

## Chat Completions: image_url part

message-এর `content`-এ string-এর বদলে একটা array দিন। array-র প্রতিটা element হয় `text` part, নয়তো `image_url` part। `url` হবে একটা public `https` URL, অথবা base64 data URL (`data:<media type>;base64,<data>`)।

```json
{
  "role": "user",
  "content": [
    { "type": "text", "text": "What does this error screenshot say?" },
    {
      "type": "image_url",
      "image_url": { "url": "data:image/png;base64,<BASE64_DATA>", "detail": "auto" }
    }
  ]
}
```

`detail` হলো OpenAI-এর optional hint (`low`, `high` বা `auto`; না দিলে `auto`)। অন্য provider এটা না-ও মানতে পারে। Anthropic-style provider-এর জন্য gateway যখন request translate করে, তখন `detail` বাদ দিয়ে দেয় (নিচে দেখুন)।

:::code-tabs

```bash title="cURL (public URL)"
curl https://tokens.bd/v1/chat/completions \
  -H "Authorization: Bearer $TOKENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek/deepseek-v4.1-flash",
    "max_tokens": 300,
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "image_url", "image_url": {"url": "https://YOUR-HOST/path/to/image.png"}},
          {"type": "text", "text": "Describe this image in two sentences."}
        ]
      }
    ]
  }'
```

```python title="Python (local file)"
import base64
import os
from openai import OpenAI

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

with open("screenshot.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode("ascii")

resp = client.chat.completions.create(
    model="deepseek/deepseek-v4.1-flash",
    max_tokens=300,
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
                {"type": "text", "text": "Describe this image in two sentences."},
            ],
        }
    ],
)
print(resp.choices[0].message.content)
print(resp.usage)
```

```typescript title="Node.js (local file)"
import fs from "node:fs";
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://tokens.bd/v1", apiKey: process.env.TOKENS_API_KEY });

const b64 = fs.readFileSync("screenshot.png").toString("base64");

const resp = await client.chat.completions.create({
  model: "deepseek/deepseek-v4.1-flash",
  max_tokens: 300,
  messages: [
    {
      role: "user",
      content: [
        { type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } },
        { type: "text", text: "Describe this image in two sentences." },
      ],
    },
  ],
});
console.log(resp.choices[0].message.content, resp.usage);
```

:::

`https://YOUR-HOST/path/to/image.png`-এর জায়গায় আপনার নিজের image-এর link বসান। data URL-এর `media type` ফাইলের সাথে মিলতে হবে (`image/png`, `image/jpeg`, `image/webp`, `image/gif`)।

:::note[URL দেওয়া image কে আনে]
`https` URL দিলে Tokens image download করে না। URL-টা যেমন আছে তেমন forward করে দেয়, আর image আনে provider। তাই link-টা public হতে হবে আর provider সেখানে পৌঁছাতে পারতে হবে। কোনো কোনো provider শুধু base64 data URL নেয়। URL দিলে fail করছে কিন্তু একই image base64-এ চলছে, তাহলে base64-ই ব্যবহার করুন।
:::

## Messages: image content block

`/v1/messages`-এ image হলো `source`-ওয়ালা একটা content block। Tokens-এ দুই ধরনের source চলে:

```json
{
  "role": "user",
  "content": [
    {
      "type": "image",
      "source": { "type": "base64", "media_type": "image/png", "data": "<BASE64_DATA>" }
    },
    { "type": "text", "text": "What does this error screenshot say?" }
  ]
}
```

```json
{
  "type": "image",
  "source": { "type": "url", "url": "https://YOUR-HOST/path/to/image.png" }
}
```

`data` হবে খাঁটি base64 string, শুরুতে `data:` prefix থাকবে না। Anthropic-এর তৃতীয় source `{"type": "file", "file_id": "..."}` চলতে হলে তাদের Files API লাগে, যেটা Tokens-এ নেই। তাই image-টাই সরাসরি পাঠান।

:::code-tabs

```bash title="cURL (public URL)"
curl https://tokens.bd/v1/messages \
  -H "x-api-key: $TOKENS_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "deepseek/deepseek-v4.1-flash",
    "max_tokens": 300,
    "messages": [
      {
        "role": "user",
        "content": [
          {"type": "image", "source": {"type": "url", "url": "https://YOUR-HOST/path/to/image.png"}},
          {"type": "text", "text": "Describe this image in two sentences."}
        ]
      }
    ]
  }'
```

```python title="Python (local file)"
import base64
import os
import anthropic

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

with open("screenshot.png", "rb") as f:
    b64 = base64.standard_b64encode(f.read()).decode("ascii")

message = client.messages.create(
    model="deepseek/deepseek-v4.1-flash",
    max_tokens=300,
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": b64}},
                {"type": "text", "text": "Describe this image in two sentences."},
            ],
        }
    ],
)
print(message.content[0].text)
print(message.usage)
```

```typescript title="Node.js (local file)"
import fs from "node:fs";
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({ baseURL: "https://tokens.bd", apiKey: process.env.TOKENS_API_KEY });

const b64 = fs.readFileSync("screenshot.png").toString("base64");

const message = await client.messages.create({
  model: "deepseek/deepseek-v4.1-flash",
  max_tokens: 300,
  messages: [
    {
      role: "user",
      content: [
        { type: "image", source: { type: "base64", media_type: "image/png", data: b64 } },
        { type: "text", text: "Describe this image in two sentences." },
      ],
    },
  ],
});
console.log(message.content[0], message.usage);
```

:::

Anthropic-এর পরামর্শ: যে text দিয়ে image নিয়ে প্রশ্ন করছেন, image রাখুন তার আগে। একাধিক image থাকলে text-এ label দিন ("Image 1:", "Image 2:"), তাহলে পরে নাম ধরে বলা যাবে। conversation-এর আগের image model-এর চোখের সামনেই থাকে, কিন্তু প্রতিটা request-এ সেগুলো আবার পাঠাতে হবে, কারণ API কিছু মনে রাখে না।

## Command line থেকে base64

base64 image এত বড় যে `curl -d '...'` argument-এ হাতে লেখা যায় না। request-টা একটা file-এ লিখে সেই file পাঠান। macOS আর Linux-এ, `jq` install থাকলে:

```bash
base64 screenshot.png | tr -d '\n' > screenshot.b64

jq -n --rawfile img screenshot.b64 '{
  model: "deepseek/deepseek-v4.1-flash",
  max_tokens: 300,
  messages: [{
    role: "user",
    content: [
      {type: "image_url", image_url: {url: ("data:image/png;base64," + $img)}},
      {type: "text", text: "Describe this image in two sentences."}
    ]
  }]
}' > request.json

curl https://tokens.bd/v1/chat/completions \
  -H "Authorization: Bearer $TOKENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
```

## Image নিয়ে gateway কী করে

native request-এ (provider আপনার request-এর format-ই বোঝে) gateway body forward করে, শুধু `model` field বদলায় (আর stream করা Chat Completions-এ usage report করার একটা option যোগ করে)। image decode, resize বা যাচাই কিছুই করে না।

কিছু model এমন provider-এর কাছেই পাওয়া যায়, যে অন্য format বোঝে। তখন gateway request translate করে, আর image-এর ক্ষেত্রে হয় এরকম:

| আপনার request                       | Provider যে format বোঝে | Image-এর কী হয়                                                                                                                                                                                                                         |
| ----------------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Messages (`image` block)            | OpenAI format           | Base64 হয়ে যায় data URL, আর URL থাকে URL-ই, user message-এর `image_url` part হিসেবে। `assistant` turn-এর image বাদ পড়ে। `tool_result`-এর ভেতরের image আর image হিসেবে যায় না (সতর্কতাটা দেখুন)।                                      |
| Chat Completions (`image_url` part) | Anthropic format        | data URL হয়ে যায় base64 `image` block, অন্য যেকোনো URL হয় URL `image` block। `detail` বাদ পড়ে। শুধু `user` message-এর image যায়।                                                                                                     |

:::warning[Tool result-এর ভেতরের image]
যে Messages request gateway OpenAI format-এ translate করে, সেখানে `tool_result`-এর content-এ `image` block থাকলে পুরোটা text হয়ে যায়, আর image block-টা JSON হিসেবে সেই text-এর ভেতরে লেখা হয়। model কোনো ছবি দেখে না, উল্টে base64 data input token হিসেবে গোনা হয়। আপনার agent যদি tool থেকে screenshot ফেরত দেয়, তাহলে Anthropic format-এ চলে এমন model নিন, অথবা tool-কে দিয়ে image-টার বর্ণনা text-এ ফেরত দেওয়ান।
:::

translation-এর একই নিয়ম আর যেসব field সাথে যায়, সেগুলো [Messages](/docs/messages) পেজে লেখা আছে।

## Size limit

দুটো limit আছে, আর যেটা ছোট সেটাই খাটে।

**Tokens:** পুরো request body সর্বোচ্চ 10 MB হতে পারে, নইলে gateway `413 request_entity_too_large` দেয়। base64 করলে data ফাইলের চেয়ে প্রায় এক-তৃতীয়াংশ বড় হয়ে যায়। তাই এক request-এর সব image মিলিয়ে বড়জোর প্রায় 7 MB-র image file যেতে পারে, আর text এবং আগের turn গুনলে আরও কম। প্রতিটা request পুরো conversation আবার পাঠায়, আগের image-সহ।

**Provider:** image-এর format, dimension, সংখ্যা আর প্রতিটা image-এর size নিয়ে প্রত্যেক provider-এর নিজস্ব limit আছে। October 2026-এ যেমন দেখা গেছে:

- Anthropic: JPEG, PNG, GIF (শুধু প্রথম frame) আর WebP; প্রতি image সর্বোচ্চ 8000 x 8000 pixel; প্রতি image 10 MB (base64); 200K-token context window-ওয়ালা model-এ এক request-এ 100টা পর্যন্ত image, বাকি model-এ 600টা। এক request-এ 20টার বেশি image হলে প্রতি image-এর pixel-এর কড়াকড়ি বাড়ে, আর Anthropic প্রতিটা পাশ 2000 px-এর মধ্যে রাখতে বলে। সূত্র: [Anthropic-এর vision documentation](https://platform.claude.com/docs/en/build-with-claude/vision)।
- OpenAI: PNG, JPEG, WebP আর animation ছাড়া GIF; এক request-এ 1,500টা পর্যন্ত image। সূত্র: [OpenAI-এর images ও vision guide](https://developers.openai.com/api/docs/guides/images-vision)। OpenAI-এর 512 MB payload limit Tokens-এর 10 MB-র চেয়ে বড়, তাই আগে Tokens-এর limit-ই লাগে।
- অন্য maker-রা নিজেদের limit নিজেরা প্রকাশ করে। যে model ব্যবহার করছেন, তার maker-এর documentation দেখে নিন।

বড় ছবি পাঠানোর আগে resize করে নিন। ফোনের ছবি প্রায়ই 4000 pixel চওড়া হয়, আর model সেটা এমনিই ছোট করে নেয়। ফলে upload-এর size আর latency-র দাম দিচ্ছেন, কিন্তু detail বাড়ছে না। screenshot-এর লেখা যেন পড়া যায়, সেদিকে খেয়াল রাখুন: JPEG বেশি জোরে compress করলে ছোট লেখা আর পড়া যায় না।

## Image input-এর billing

provider image-কে input token-এ রূপান্তর করে, আর মোট সংখ্যাটা `usage`-এ report করে। Tokens সেই input count-কে model-এর input price ধরে bill করে, অন্য যেকোনো input-এর মতোই। মোটামুটি, provider-রা image গোনে patch ধরে: Anthropic-এর documentation অনুযায়ী প্রতি image-এ `ceil(width / 28) x ceil(height / 28)` token, তবে আগে image ছোট করে একটা সীমায় আনা হয় (standard tier-এ প্রায় 1,568 token, high-resolution tier-এ 4,784 পর্যন্ত)। OpenAI-র documentation-এ patch-based আর tile-based দুই রকম হিসাব আছে, যা model আর `detail`-এর ওপর নির্ভর করে। অন্যদের হিসাব আলাদা। তাই আপনার model-এর আসল খরচ জানতে একটা test request পাঠিয়ে `usage` দেখে নিন।

Admission অন্য একটা সংখ্যা ধরে চলে। request forward করার আগে gateway আপনার ব্যালান্স থেকে তার worst-case খরচ reserve করে, আর input অংশের হিসাব ধরে request body-র প্রতি চার character-এ এক token। বড় base64 image মানে অনেক character, তাই কয়েক MB-র image-ওয়ালা request-এর reservation provider-এর শেষ পর্যন্ত যা charge করবে তার চেয়ে অনেক বেশি হতে পারে। আপনার কাছ থেকে আসল usage-ই কাটা হয়, reservation নয়। তবু ব্যালান্স কম থাকলে, বা key-র spend cap-এর কাছাকাছি থাকলে, যে request-এর খরচ আসলে খুবই কম হতো সেটাও `insufficient_credits` বা `monthly_spend_cap_exceeded` দিয়ে ফিরিয়ে দেওয়া হতে পারে। image resize করলে খরচও কমে, refusal-ও যায়। reservation নিয়ে বিস্তারিত [Chat Completions](/docs/chat-completions) পেজে আছে।

## সমস্যা হলে

| লক্ষণ                                                  | সম্ভাব্য কারণ                                                       | সমাধান                                                                                               |
| ------------------------------------------------------ | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `400 invalid_request`, message-এ image বা content আছে  | Model image input নেয় না, অথবা media type-টা সে support করে না     | image নেয় এমন model চেষ্টা করুন; data URL-এর media type ফাইলের সাথে মিলছে কি না দেখুন।              |
| উত্তরে ছবির কোনো উল্লেখ নেই                            | text-only model-এ provider image বাদ দিয়ে দিয়েছে                  | image পড়ে এমন model-এ যান।                                                                          |
| `413 request_entity_too_large`                         | Body 10 MB-র বেশি, সাধারণত base64 image বা image-ভরা লম্বা history  | Resize বা compress করুন, কম image পাঠান, অথবা পুরোনো turn বাদ দিন।                                   |
| ব্যালান্স অল্প, তবু `402 insufficient_credits`         | input-এর হিসাবে base64-এর character-গুলোও ধরা হয়                  | image resize করুন, অথবা [billing](/dashboard/billing) থেকে টাকা যোগ করুন।                            |
| URL image fail করে, base64 চলে                         | Provider URL আনতে পারেনি, অথবা শুধু base64 নেয়                     | base64 ব্যবহার করুন, নয়তো URL public করুন।                                                          |
| tool screenshot-ওয়ালা Messages request, model কিছুই দেখে না | translate হওয়া model-এ `tool_result`-এর image text হয়ে যায় | উপরের সতর্কতাটা দেখুন।                                                                               |

error-এ কী ভুল হয়েছে স্পষ্ট না থাকলে upstream-এর message বদলে একটা সাধারণ message দেওয়া হয়। তখন `x-tokens-request-id` নিয়ে [Support](/docs/support)-এ জানান, আর code-গুলোর জন্য [errors](/docs/errors) দেখুন।

---
Page: https://tokens.bd/bn/docs/vision
