Token truncation pada multi-LLM gateway terjadi ketika output model terpotong sebelum menyelesaikan respons, atau payload input dipotong secara tidak terduga oleh provider LLM. Masalah ini biasanya muncul saat backend beralih dari satu provider (seperti OpenAI) ke routing arsitektur multi-provider yang melibatkan Anthropic Claude, Mistral, atau model lokal via vLLM/Ollama.
Dampaknya langsung merusak alur aplikasi: parsing JSON gagal (SyntaxError: Unexpected end of JSON input), Server-Sent Events (SSE) streaming terhenti tanpa close event normal, atau output logic terputus di tengah kalimat. Artikel ini membahas root cause perbedaan tokenisasi dan implementasi mitigasi pada layer backend gateway.
Gejala Kegagalan pada Gateway Backend
Sebelum memeriksa konfigurasi context window, identifikasi anomali lewat log network dan parser gateway:
- Invalid JSON Output: Gateway meneruskan payload structured output dari downstream LLM, namun JSON parser melempar error karena penutup objek (
}atau]) hilang. - Unclean Stream Drops: Klien menerima potongan data stream lalu koneksi HTTP putus tanpa metadata terminasi yang valid.
- Mismatch Finish Reason: Provider mengembalikan nilai status terminasi yang berbeda untuk pemotongan token, seperti
finish_reason: "length"pada OpenAI/vLLM ataustop_reason: "max_tokens"pada Anthropic Claude.
Root Cause: Mengapa Token Truncation Terjadi?
1. Disparitas Tokenizer Antar Model
Model yang berbeda menggunakan vocabulary dan tokenizer berbeda. Teks identik menghasilkan jumlah token berbeda tergantung provider:
- OpenAI (GPT-4o) menggunakan tokenizer o200k_base.
- Anthropic Claude menggunakan tokenizer berbasis byte-pair encoding tersendiri.
- Model open-source (Llama 3, Mistral) menggunakan SentencePiece atau Tiktoken dengan vocabulary berbeda (128k pada Llama 3 vs 32k pada Mistral v0.1).
Jika gateway menghitung kuota token input menggunakan tokenizer OpenAI (misal via tiktoken) lalu merutekan request ke Mistral atau model lokal tanpa normalisasi, batas context window yang sebenarnya bisa terlampaui.
2. Ambiguitas Definisi max_tokens
Beberapa runtime mendefinisikan parameter token secara berbeda. Pada API OpenAI dan Anthropic, parameter max_tokens (atau max_completion_tokens) merujuk pada maksimal token output yang diizinkan, bukan total context window (input + output). Jika gateway menetapkan nilai max_tokens output terlalu rendah (misal: 512 token) sementara schema JSON output memerlukan 800 token, provider memotong payload tepat di tengah proses generate.
3. Perbedaan Reservasi Context Window
Model lokal yang di-host via vLLM atau TGI memiliki context window hard ceiling (misal: max_model_len: 4096). Jika gateway mengirim prompt sepanjang 3.800 token dengan parameter max_tokens: 1000, API downstream akan melempar HTTP 400 (Bad Request) atau secara otomatis memangkas prompt input dari depan/belakang tanpa peringatan.
Implementasi Adapter Layer & Sliding Window Buffer
Untuk menstabilkan multi-LLM router, implementasikan adapter layer yang menormalisasi request/response, mengecek payload size sebelum dispatch, dan memvalidasi output buffer.
import json
from typing import Dict, Any, List
class TokenBudgetAdapter:
def __init__(self, provider_limits: Dict[str, Dict[str, int]]):
self.limits = provider_limits
def resolve_token_budget(
self,
provider: str,
estimated_input_tokens: int,
requested_output_tokens: int
) -> int:
context_limit = self.limits[provider]["max_context"]
max_output_limit = self.limits[provider]["max_output"]
# ponytail: simple conservative ceiling, upgrade to exact tokenizer encoding if edge margins < 5%
safety_margin = 128
available_for_output = context_limit - estimated_input_tokens - safety_margin
if available_for_output <= 0:
raise ValueError(f"Prompt melampaui context limit untuk provider {provider}")
return min(requested_output_tokens, available_for_output, max_output_limit)
def normalize_stop_reason(self, provider: str, raw_response: Dict[str, Any]) -> str:
if provider == "openai" or provider == "vllm":
return raw_response.get("choices", [{}])[0].get("finish_reason", "unknown")
elif provider == "anthropic":
mapping = {"max_tokens": "length", "end_turn": "stop"}
raw_reason = raw_response.get("stop_reason", "unknown")
return mapping.get(raw_reason, raw_reason)
return "unknown"
Skipped: tokenizer per model; add when prompt size exceeds 85% of provider context limit.
Sliding Window Context Buffer untuk Multi-turn Chat
Saat request membawa riwayat percakapan yang panjang, potong pesan di sisi gateway sebelum dikirim ke downstream LLM menggunakan sliding window buffer.
def prune_chat_history(
messages: List[Dict[str, str]],
max_allowed_tokens: int,
rough_chars_per_token: float = 3.5
) -> List[Dict[str, str]]:
if not messages:
return []
# Pertahankan system prompt utama
system_messages = [m for m in messages if m.get("role") == "system"]
conversation = [m for m in messages if m.get("role") != "system"]
def estimate_tokens(text: str) -> int:
return int(len(text) / rough_chars_per_token)
system_tokens = sum(estimate_tokens(m.get("content", "")) for m in system_messages)
budget = max_allowed_tokens - system_tokens
pruned = []
current_tokens = 0
# Ambil pesan terbaru terlebih dahulu (reverse sliding window)
for msg in reversed(conversation):
msg_token = estimate_tokens(msg.get("content", ""))
if current_tokens + msg_token > budget:
break
pruned.insert(0, msg)
current_tokens += msg_token
return system_messages + pruned
Handling Truncated JSON Output
Jika model terpotong di tengah jalan (finish reason bernilai length), response parser harus mendeteksi status tersebut dan menolak payload sebelum melempar parsing exception ke client.
def safe_parse_llm_json(raw_text: str, finish_reason: str) -> Dict[str, Any]:
if finish_reason == "length":
raise RuntimeError(
"Payload response terpotong karena mencapai max_tokens limit. JSON tidak valid."
)
try:
return json.loads(raw_text)
except json.JSONDecodeError as err:
raise ValueError(f"Gagal memproses JSON: {str(err)}")
Checklist Debugging Multi-LLM Gateway
- Log Stop/Finish Reason: Selalu simpan properti metadata terminasi (
finish_reasonataustop_reason) ke trace gateway APM (seperti OpenTelemetry). - Hindari Asumsi Parameter: Jangan hardcode nilai
max_tokensstatis untuk semua model. Pastikan parameter output disesuaikan dengan formula:context_window - input_tokens - margin. - Set Up Fallback Strategy: Jika target downstream memotong token dengan reason
lengthpada model ber-context kecil (misal 8k), konfigurasi gateway agar otomatis melakukan retry fallback ke model dengan context window lebih besar.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!