← บทความทั้งหมด

API ที่เข้ากันได้กับ OpenAI แบบ Drop-In — ย้ายมาได้ในสองบรรทัด

20 ส.ค. 2026 · 3 นาทีในการอ่าน · api, migration

unbleep เป็น API ที่เข้ากันได้กับ OpenAI (OpenAI compatible API) ซึ่งในทางปฏิบัติหมายความว่าการย้ายใช้แค่สองบรรทัดและไม่ต้องเพิ่ม dependency ใหม่ คุณเก็บ SDK ทางการ ตรรกะ retry ลูป streaming การนับ token และ error handler ของคุณไว้ได้ทั้งหมด สิ่งที่เปลี่ยนคือปลายทางที่ request วิ่งไปและ key ที่ใช้ยืนยันตัวตน โพสต์นี้ครอบคลุมการสลับนั้น แล้วตามด้วยสี่สิ่งที่ต่างกันมากพอจะทำให้บางอย่างพังได้ถ้าคุณไม่รู้ล่วงหน้า

ย้ายมา API ที่เข้ากันได้กับ OpenAI ในสองบรรทัด

python
import os

from openai import OpenAI

client = OpenAI(
    base_url="https://unbleep.ai/v1",
    api_key=os.environ["UNBLEEP_API_KEY"],
)

resp = client.chat.completions.create(
    model="unbleep",
    messages=[{"role": "user", "content": "Summarise this incident report."}],
)
print(resp.choices[0].message.content)

ถ้าคุณไม่อยากแตะโค้ดเลย SDK อ่านค่าทั้งสองจาก environment อยู่แล้ว ดังนั้นแค่เปลี่ยน config ก็พอ:

bash
export OPENAI_BASE_URL="https://unbleep.ai/v1"
export OPENAI_API_KEY="ub_live_9f2c..."

คู่ค่าเดียวกันนี้ครอบคลุม ecosystem ส่วนใหญ่ที่สร้างอยู่บน SDK — LangChain, LlamaIndex, Instructor, Vercel AI SDK หรืออะไรก็ตามที่มีการตั้งค่า base URL ให้ key มี prefix เพื่อให้ secret scanner เห็นการรั่วไหลได้ชัด: ub_live_ และ ub_test_ ต่างก็ตัดเงินจากเครดิตแบบเติมล่วงหน้าก้อนเดียวกันในอัตราต่อ token เท่ากัน test key เป็นข้อมูลรับรองแยกต่างหากที่เพิกถอนได้ และมีเพดานต่ำกว่า — 15 request/นาที แทนที่จะเป็น 60 ของบัญชี — ไม่ใช่ free tier เก็บทั้งสองไว้ฝั่งเซิร์ฟเวอร์เท่านั้น

GET /v1/models ใช้งานได้ ดังนั้นเครื่องมือที่ดึงรายชื่อโมเดลมาใส่ dropdown ไม่ต้องเขียนกรณีพิเศษ

เลือกโมเดล

มีสามระดับ (tier) id แบบมีวันที่ — unbleep-250811, unbleep-high-250811, unbleep-mini-250811 — ใช้ได้ในฐานะ alias แต่ ณ วันนี้มันชี้ไปที่ build เดียวกับ id เปล่า และ response จะรายงาน id เปล่ากลับมา ให้มองมันเป็นการสะกดที่รองรับอนาคต ไม่ใช่การรับประกันความสามารถในการทำซ้ำ ถ้าการประเมินต้องทำซ้ำได้ ให้บันทึกเอาต์พุต ไม่ใช่ model id

| โมเดล | Context | ราคา in / out ต่อ 1M | หมายเหตุ | | --- | --- | --- | --- | | unbleep | 256K | $3.00 / $3.00 | ค่าเริ่มต้น ระดับ reasoning | | unbleep-high | 1M* | $5.00 / $5.00 | งานที่ใหญ่ที่สุด ระดับ reasoning | | unbleep-mini | 32K | $1.00 / $1.00 | ถูกและเร็ว ตอบตรง ๆ ไม่มี reasoning trace |

*request body ถูกจำกัดไว้ที่ 2,000,000 ไบต์ — ประมาณ 500k token — ดังนั้นการเรียกครั้งเดียวจึงเติมหน้าต่าง 1M ให้เต็มไม่ได้จริง อะไรที่ใหญ่กว่านั้นจะได้ 413 payload_too_large กลับมา

เพดาน 32K ของ unbleep-mini คือตัวที่ดักคนที่ย้ายมาจากโมเดล context 128K: prompt ที่เคยพอดีจะถูกปฏิเสธ ถ้าคุณจัดเส้นทาง (route) ตามต้นทุน ก็ต้องจัดเส้นทางตามความยาวด้วย

python
def pick_model(prompt_chars: int) -> str:
    """~4 chars/token is a deliberate under-estimate; leave room for the completion."""
    est_tokens = prompt_chars // 4
    if est_tokens < 24_000:
        return "unbleep-mini"
    return "unbleep" if est_tokens < 200_000 else "unbleep-high"

การสตรีม (streaming)

ตั้ง stream=True แล้วคุณจะได้ Server-Sent Events มาตรฐาน: แต่ละ event เป็น chat.completion.chunk ที่บรรจุ delta และ stream จบด้วย data: [DONE] ตามตัวอักษร ลูปเดิมของคุณใช้ได้โดยไม่ต้องแก้

bash
curl -N https://unbleep.ai/v1/chat/completions \
  -H "Authorization: Bearer $UNBLEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "unbleep",
    "messages": [{"role": "user", "content": "Explain the residual stream."}],
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

คุณจะได้จำนวน token ไม่ว่าจะขอหรือไม่: unbleep ขอ usage จาก backend เสมอและส่งต่อให้ มีเพียง stream_options: {"include_usage": false} ที่ระบุชัดเจนเท่านั้นที่จะตัดมันออกในขาออก ไม่ว่าทางไหน stream จะจบด้วย chunk สุดท้ายหนึ่งตัวที่มีอาร์เรย์ choices ว่าง — และบรรจุอ็อบเจกต์ usage ที่มีข้อมูลครบ เว้นแต่คุณจะเลือกไม่รับ — นี่คือเหตุผลที่ลูปด้านล่างตรวจ choices ก่อนจะแตะมัน

ฟิลด์ reasoning_content

นี่คือส่วนเพิ่มเติมเดียวที่มีอยู่จริงใน schema unbleep และ unbleep-high คิดก่อนตอบ และ chain of thought นั้นถูกส่งกลับมาใน reasoning_content ซึ่งเป็นฟิลด์พี่น้องของ content บน message (แบบไม่ streaming) หรือบน delta (แบบ streaming) backend ต้นทางแต่ละเจ้าเรียกมันไม่ตรงกันว่า reasoning หรือ reasoning_content API จึง normalise ให้เป็น reasoning_content เพื่อให้คุณจัดการแค่รูปแบบเดียว

เพราะมันไม่ได้อยู่ใน schema ของ OpenAI มันจึงไม่อยู่ใน type stub ของ SDK response model อนุญาตให้มีฟิลด์เพิ่มเติมได้ การเข้าถึงแบบ attribute จึงใช้งานได้ตอนรัน — แต่ให้อ่านมันด้วย getattr เพื่อให้ response จาก mini ซึ่งไม่มี trace ไม่ raise ข้อผิดพลาด:

python
import sys

stream = client.chat.completions.create(
    model="unbleep",
    messages=[{"role": "user", "content": "Why did this detection rule misfire?"}],
    stream=True,
    stream_options={"include_usage": True},
)

usage = None
for chunk in stream:
    if not chunk.choices:          # final usage-only chunk
        usage = chunk.usage
        continue
    delta = chunk.choices[0].delta
    thought = getattr(delta, "reasoning_content", None)
    if thought:                    # trace to stderr, answer to stdout
        sys.stderr.write(thought)
    if delta.content:
        sys.stdout.write(delta.content)

if usage:
    details = usage.completion_tokens_details
    print(f"\nreasoning tokens: {getattr(details, 'reasoning_tokens', 0)}")

ผลในทางปฏิบัติสามข้อ:

python
resp = client.chat.completions.create(
    model="unbleep",
    messages=[{"role": "user", "content": "phishing or benign?"}],
    max_tokens=4,
    extra_body={"thinking": False},   # spend the budget on the answer, not the trace
)

อย่าป้อน reasoning_content กลับเข้าไปใน turn ถัดไปในฐานะ content ของ assistant เด็ดขาด มันเป็นเอาต์พุตเพื่อการวินิจฉัย ไม่ใช่ประวัติการสนทนา และการเล่นซ้ำมันจะทำให้ response ถัดไปแย่ลง

ข้อผิดพลาด และข้อควรระวังจริง ๆ สองข้อ

ข้อผิดพลาดใช้ envelope ของ OpenAI — {"error": {"type", "code", "message"}} — บล็อก except เดิมของคุณจึงใช้ได้ต่อ status code แมปตามที่คุณคาด: 401 key ไม่ถูกต้อง, 422 ถูกบล็อกโดย policy: strict, 429 rate limit, 5xx ปัญหาต้นทางที่ retry ได้

status ที่คุณอาจไม่เคยจัดการมาก่อนคือ 402 เครดิตหมด บัญชีเป็นแบบเติมเงินล่วงหน้า จึงไม่มีการใช้เกินและไม่มีใบแจ้งหนี้ — request จะหยุดไปเฉย ๆ จนกว่าคุณจะเติมเงิน OpenAI ส่งสัญญาณโควตาหมดเป็น 429 ซึ่งหมายความว่าเส้นทางย้ายแบบไม่คิดมากจะทำให้ตรรกะ backoff ของคุณ retry 402 ไปตลอดกาล ให้ถือว่ามันเป็นสถานะสิ้นสุดและตั้ง alert ไว้

ข้อควรระวังที่สอง: system_fingerprint ไม่ถูกส่งกลับมา มันระบุ backend ที่ให้บริการ จึงถูกตัดออกไปพร้อมกับฟิลด์เฉพาะผู้ให้บริการอื่น ๆ ถ้าคุณใช้มันเป็น key ของ cache หรือของการตรวจสอบความสามารถในการทำซ้ำ คุณจะต้องมี version marker ของตัวเอง: model id แบบมีวันที่เป็น alias ของ build ปัจจุบัน ไม่ใช่ snapshot ที่แช่แข็งไว้ มันจึงบอกคุณไม่ได้ว่า backend เปลี่ยนเมื่อไหร่

rate limit ถูกส่งกลับมาเป็น header ในทุก response — x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests — งาน batch จึงกำหนดจังหวะ request ของตัวเองได้แทนที่จะค้นพบเพดานด้วยการชนมัน มีเพดานที่สองที่ header ไม่ได้บอก: สูงสุด 8 request ที่กำลังดำเนินการพร้อมกันต่อบัญชี และ request ที่เก้าจะได้ 429 พร้อม code too_many_concurrent_requests กลับมา การเรียกแบบ streaming จะถือช่องของมันไว้จนกว่า stream จะจบ ดังนั้นจำกัด worker pool ของคุณเองไว้ที่ 8

สิ่งที่คุณกำลังชี้ไปหา

ควรพูดให้ชัด: โมเดลเบื้องหลัง endpoint นี้เป็นโมเดล abliterated หมายความว่าพฤติกรรมการปฏิเสธของมันถูกลบออกไปในระดับ weights นั่นแหละคือประเด็น — มันเป็น API สำหรับนักพัฒนาที่มุ่งไปที่งานวิจัยด้านความปลอดภัย red-teaming และการประเมิน ซึ่งการปฏิเสธคือความคลาดเคลื่อนของการวัด แต่นั่นก็หมายความว่า guardrail ตามปกติไม่ได้อยู่ตรงนั้นเพื่อดัก prompt ที่ไม่ดี ดังนั้นให้มีมนุษย์รับผิดชอบเอาต์พุตเสมอ และอ่านนโยบายการใช้งานที่ยอมรับได้ การใช้งานอย่างถูกกฎหมายเป็นความรับผิดชอบของคุณ

รับ API key — การย้ายใช้แค่สองบรรทัดจริง ๆ