Skip to content

LLMTR Nanbeige4.2 3B

Nanbeige4.2 3B is a small chat model hosted by us in Turkey. It is called through /v1/chat/completions with the canonical model id llmtr/nanbeige4-2-3b. It fits short summaries, classification and tool-calling flows with few steps.

Property Value
Model id llmtr/nanbeige4-2-3b
Context window 32,768 tokens (32K)
Price (input / output / cache read) $1.00 / $3.00 / $0.25 per 1M tokens
Image input No
Prompt cache Yes
Tool calling Yes, tool_choice: "auto" only
JSON output No
Thinking mode Yes, off by default
Audio / video input No
Image generation No
Terminal window
curl "$LLMTR_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer llmtr-your_key" \
-H "Content-Type: application/json" \
-d '{
"model": "llmtr/nanbeige4-2-3b",
"messages": [
{ "role": "system", "content": "Answer briefly." },
{ "role": "user", "content": "Classify this customer message as positive, negative or neutral: The parcel arrived late but the product is intact." }
],
"max_tokens": 256
}'

The model is trained mostly on English and Chinese. If you want answers in Turkish, say so explicitly in the system message and check the output against your own examples.

Answers are generated slowly; a reply of a few hundred tokens can take minutes. The first send of a long prompt also takes time before the answer starts. So:

  • Use "stream": true wherever a user is watching; the answer appears as it is generated.
  • Set your client timeout to at least 300 seconds. Keep it longer when thinking is on or you expect a long answer.
  • Give max_tokens only what the task needs. If you do not send it, the answer stops at 4,096 tokens and finish_reason is length.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://llmtr.com/v1",
api_key=os.environ["LLMTR_API_KEY"],
timeout=300.0,
)
stream = client.chat.completions.create(
model="llmtr/nanbeige4-2-3b",
messages=[
{"role": "system", "content": "Answer briefly."},
{"role": "user", "content": "Which fields belong on an invoice? List three."},
],
max_tokens=512,
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)

See Timeouts for the timeout settings in detail.

On a plain request the model answers directly; no reasoning step runs and no extra tokens are spent. There are two ways to turn thinking on:

// :think suffix on the model id
{ "model": "llmtr/nanbeige4-2-3b:think", "messages": [{ "role": "user", "content": "How many working days are between these two dates?" }], "max_tokens": 4096 }
// Or the reasoning field in the request body
{ "model": "llmtr/nanbeige4-2-3b", "reasoning": true, "messages": [{ "role": "user", "content": "How many working days are between these two dates?" }], "max_tokens": 4096 }

With thinking on, the reasoning is returned in message.reasoning_content and the tokens it produces are spent from your max_tokens budget. If the budget is small, the model stops before it reaches the answer and finish_reason is length; content may then be empty or carry the unfinished reasoning. Check finish_reason, not content, before you use the reply. Thinking also makes the reply noticeably slower; turn it on only for work where waiting is acceptable.

reasoning_effort is not supported on this model and returns 400; thinking is only on or off. Reasoning tokens are billed as output and are already counted in completion_tokens.

The model supports the standard OpenAI tools schema and returns real tool_calls. On this model, tool calls are reliable only with thinking on. So a request that sends tools has thinking turned on automatically; you do not need to do anything. This adds a short reasoning step to the request, and those tokens are billed as output.

{
"model": "llmtr/nanbeige4-2-3b",
"max_tokens": 2048,
"messages": [{ "role": "user", "content": "What is the weather in Istanbul?" }],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
]
}
{
"choices": [
{
"message": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": { "name": "get_weather", "arguments": "{\"city\":\"Istanbul\"}" }
}
]
},
"finish_reason": "tool_calls"
}
]
}

Rules for tool calls on this model:

  • tool_choice accepts only auto, or leave the field out. required or a named function returns 400 invalid_request. Say in the prompt when the tool should be used.
  • Do not send tool_choice: "none" to switch tools off; remove the tools field from the request. With tools attached, none returns 400 unsupported_input.
  • A request that turns thinking off with :fast, reasoning: false or chat_template_kwargs.enable_thinking: false while sending tools returns 400 invalid_request.
  • One reply may contain more than one tool call, but that is not guaranteed; your client should process the whole tool_calls array.

A JSON object or JSON schema response_format is not supported on this model, and the request returns 400 invalid_request. If you need structured data, use tool calling: the function's parameters schema defines the fields the model fills in. { "type": "text" } is accepted.

Repeated prompt prefixes (a fixed system message, the conversation history) can be served from cache. Tokens served from cache are reported in prompt_tokens_details.cached_tokens and billed at the cache read rate instead of the input rate:

"usage": {
"prompt_tokens": 4401,
"completion_tokens": 4,
"total_tokens": 4405,
"prompt_tokens_details": { "cached_tokens": 4386 }
}

In this example 4386 tokens are billed at $0.25/1M and the remaining 15 tokens at $1.00/1M. A prefix served from cache also does not have to be processed again, so the answer starts sooner. Keep fixed content at the start of the message list and changing content at the end.

A cache hit is not guaranteed: a prefix does not hit on its first send and may hit on later repeats. Billing always follows the actual value reported in cached_tokens.

The context window is 32,768 tokens per request and covers the prompt and the answer together. If the prompt is longer than the window, the request returns 400 invalid_request:

{
"error": {
"message": "This request uses 40,040 input tokens, more than the 32,768-token window shared by the prompt and the answer. Shorten the conversation.",
"type": "invalid_request"
}
}

In long conversations, shorten or summarize older messages. The prompt and the answer share the same window, so do not expect a long answer next to a long prompt.

The model accepts text only. If a message contains an image_url, audio, video or file part, the request returns 400 unsupported_input. For work that needs to read images, pick one of the catalog models that support image input.