การกำหนดค่า API กลางในเฟรมเวิร์กยอดนิยม: จาก SDK ถึง Dify
หัวใจสำคัญของการเชื่อมต่อ API กลางมีเพียงสามค่า: ที่อยู่เอนด์พอยต์, คีย์ API และชื่อโมเดล ความยากอยู่ที่แต่ละเฟรมเวิร์กใช้ชื่อเรียกค่าเหล่านี้ไม่เหมือนกัน บางตัวใช้ base_url บางตัวใช้ api_base และใน Dify เป็นฟอร์มเต็มๆ บทความนี้จัดรายการเปรียบเทียบให้ โค้ดแต่ละส่วนทำงานได้ทันที พร้อมรายการตรวจสอบข้อผิดพลาดตามลำดับ
สรุปสำคัญ
- รวมที่อยู่และคีย์ลงในตัวแปรสิ่งแวดล้อม API_BASE และ API_KEY ไม่ให้คีย์ปรากฏเป็นข้อความธรรมดาในโค้ด
- ที่อยู่เอนด์พอยต์ต้องมีต่อท้าย /v1 และชื่อโมเดลให้เขียนว่า uncensored
- LangChain ใช้ base_url ส่วน LlamaIndex แบบ OpenAILike ใช้ api_base ชื่อต่างกันแต่ความหมายเดียวกัน
- ใน Dify เลือกผู้ให้บริการ "OpenAI-API-compatible" แล้วกรอกชื่อโมเดล ที่อยู่ และความยาวหน้าต่างบริบทด้วยตนเอง
เตรียมสามค่าให้ครบ
ไม่ว่าจะใช้เฟรมเวิร์กใด ให้ตรวจสอบว่ามีสามสิ่งนี้ก่อน การกำหนดค่าที่เหลือคือการกรอกช่อง
- Endpoint:
https://api.llmzhongzhuan.com/v1. ต้องเก็บ/v1ไว้ แต่อย่าเพิ่ม/chat/completionsเพราะ SDK จะต่อให้เอง - คีย์ API: แสดงทันทีหลังลงทะเบียนด้วยอีเมลและรหัสที่ หน้ารับคีย์ แต่ละบัญชีมีหนึ่งคีย์ สามารถรีเซ็ตได้ โดยคีย์เก่าจะหมดอายุทันทีหลังรีเซ็ต
- ชื่อโมเดล: มีตัวเดียวคือ
uncensored. สามารถตรวจสอบเองด้วยGET /v1/models
จำขอบเขตเหล่านี้ไว้: ความยาว context รวม 100,000 token, max_tokens ค่าเริ่มต้น 2048 สูงสุด 32,000, คีย์ละ 300 requests/นาที
รูปแบบตัวแปรสิ่งแวดล้อม
การเขียนคีย์ลงโค้ดเป็นสาเหตุหลักของปัญหา แนะนำให้ใช้ตัวแปรสิ่งแวดล้อม โดย container หรือระบบจัดการคีย์จะ inject ให้ ใช้ API_KEY และ API_BASE เป็นชื่อตัวแปร
# Linux / macOS:写进 ~/.zshrc 或 .env 加载脚本
export API_KEY="替换为你的密钥"
export API_BASE="https://api.llmzhongzhuan.com/v1"
# Windows PowerShell(仅当前会话)
$env:API_KEY = "替换为你的密钥"
$env:API_BASE = "https://api.llmzhongzhuan.com/v1"
ถ้าใช้ไฟล์ .env อย่าลืมเพิ่มใน .gitignore SDK Python อ่าน OPENAI_API_KEY และ OPENAI_BASE_URL โดยอัตโนมัติ สามารถส่งค่าในคอนสตรัคเตอร์เพื่อหลีกเลี่ยงการรบกวนจากตัวแปรเก่าได้
SDK Python และ Node ของ OpenAI
Python ต้องการแพ็กเกจ openai เวอร์ชัน v1 ขึ้นไป ส่วน Node ต้องการเวอร์ชัน v4 ขึ้นไป ความแตกต่างมีเพียงไวยากรณ์ การสร้างไคลเอนต์เพียงระบุที่อยู่และคีย์ API ก็พอ เริ่มจาก Python แบบไม่สตรีมมิง พร้อมพิมพ์ usage เพื่อตรวจสอบการหักเงิน:
import os
from openai import OpenAI
client = OpenAI(
base_url=os.environ.get("API_BASE", "https://api.llmzhongzhuan.com/v1"),
api_key=os.environ["API_KEY"],
)
resp = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "写一条 20 字以内的发布公告标题。"}],
max_tokens=100,
)
print(resp.choices[0].message.content)
print(resp.usage)
ตัวอย่าง Node ใช้เอาต์พุตแบบสตรีมมิง ซึ่งเป็นรูปแบบที่ใช้บ่อยที่สุดสำหรับเอฟเฟกต์การพิมพ์แบบทีละตัวอักษร เมื่อคำขอสตรีมมิงสิ้นสุด เซิร์ฟเวอร์จะเพิ่มชิปที่มี usage ให้โดยอัตโนมัติ ไม่ต้องใช้พารามิเตอร์เพิ่มเติม:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: process.env.API_BASE ?? "https://api.llmzhongzhuan.com/v1",
apiKey: process.env.API_KEY,
});
const stream = await client.chat.completions.create({
model: "uncensored",
messages: [{ role: "user", content: "列出三个命名服务端日志字段的好习惯。" }],
stream: true,
max_tokens: 300,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
process.stdout.write("\n");
ตัวอย่าง Node ใช้ top-level await ต้องใช้ไฟล์ .mjs หรือตั้งค่า "type": "module" ใน package.json หาก runtime ไม่รองรับ ให้ใส่ใน async function
LangChain: base_url ของ ChatOpenAI
LangChain ไม่ต้องใช้ adapter พิเศษ ใช้ langchain_openai และ ChatOpenAI โดยชี้ base_url ไปที่ API กลาง
import os
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
model="uncensored",
base_url=os.environ.get("API_BASE", "https://api.llmzhongzhuan.com/v1"),
api_key=os.environ["API_KEY"],
temperature=0.7,
max_tokens=500,
timeout=60,
)
print(llm.invoke("把“服务已降级”改写成对用户友好的一句话。").content)
ข้อควรระวังสองข้อ: 1. ควรตั้งค่า max_tokens และ timeout ให้ชัดเจน เพราะค่าเริ่มต้นอาจไม่เหมาะกับงานของคุณ 2. หากใช้ tool_calls ใน chain ระบบรองรับ tools รูปแบบ OpenAI และ bind_tools ทำงานได้ปกติ สำหรับการ stream ให้ consume chunks ใน stream()
LlamaIndex: OpenAILike
คลาส OpenAI ของ LlamaIndex จะตรวจสอบว่าชื่อโมเดลอยู่ในรายการทางการหรือไม่ หากเป็นโมเดลกำหนดเองจะเกิด error ให้ใช้ llama-index-llms-openai-like และ OpenAILike แทน ซึ่งไม่มีการตรวจสอบนี้ และใช้ชื่อพารามิเตอร์ api_base สำหรับ address
import os
from llama_index.llms.openai_like import OpenAILike
llm = OpenAILike(
model="uncensored",
api_base=os.environ.get("API_BASE", "https://api.llmzhongzhuan.com/v1"),
api_key=os.environ["API_KEY"],
is_chat_model=True,
context_window=100000,
max_tokens=500,
)
print(llm.complete("用一句话解释什么是幂等请求。"))
is_chat_model=True ตรงนี้สำคัญมาก เพราะทำให้ LlamaIndex ใช้เอนด์พอยต์แชทแทนเอนด์พอยต์คอมพลีตเก่า การตั้งค่า context_window เป็น 100000 จะป้องกันไม่ให้ LlamaIndex ตัดเอกสารของคุณผิดโดยอิงจากโทเคนหลายพันตัวตามค่าเริ่มต้นเมื่อแบ่งบริบท
Dify: ผู้ให้บริการ OpenAI-API-compatible
แพลตฟอร์มแบบวิสชวลอย่าง Dify ไม่มีโค้ด แต่กำหนดค่าผ่านฟอร์ม แม้ข้อความในอินเทอร์เฟซจะแตกต่างกันเล็กน้อยในแต่ละเวอร์ชัน ขั้นตอนก็คล้ายกัน:
- ไปที่ "การตั้งค่า" หาหน้า "ผู้ให้บริการโมเดล" เลือก "OpenAI-API-compatible" จากรายการ แล้วคลิกเพิ่มโมเดล
- เลือกประเภทโมเดลเป็น "LLM" และกรอกชื่อโมเดลเป็น
uncensored - กรอกคีย์ API ของคุณใน API Key และกรอก
https://api.llmzhongzhuan.com/v1ใน API endpoint URL - กรอกความยาวหน้าต่างบริบทของโมเดลเป็น 100000 และขีดจำกัดสูงสุดของโทเคนเป็น 32000
- หากต้องการใช้การเรียกใช้ฟังก์ชันในเวิร์กโฟลว์ ให้เปิดตัวเลือกการรองรับการเรียกใช้ฟังก์ชัน และเปิดการสตรีมมิง
- บันทึกแล้วสร้างแอปแชทง่ายๆ เลือกโมเดลที่เพิ่งเพิ่มแล้วส่งข้อความหนึ่งข้อความ เพื่อยืนยันว่าสามารถตอบกลับได้ปกติ
แพลตฟอร์มบางครั้งจะส่งคำขอตรวจสอบเมื่อบันทึก หากขั้นตอนนี้ล้มเหลว มักเกิดจากที่อยู่มีเส้นทางเกินมา หรือคีย์มีช่องว่างนำหน้าหรือต่อท้าย หาก Dify ของคุณติดตั้งในคอนเทนเนอร์ ให้ตรวจสอบด้วยว่าคอนเทนเนอร์สามารถเข้าถึงโดเมนภายนอกได้
พารามิเตอร์และรูปแบบการดีพลอยที่ต้องตรวจสอบก่อนปล่อยใช้งาน
การที่เฟรมเวิร์กทำงานได้เป็นเพียงขั้นตอนแรก พารามิเตอร์ด้านล่างควรตรวจสอบทีละรายการก่อนปล่อยใช้งาน เพราะส่งผลต่อต้นทุนและอัตราความล้มเหลว
- max_tokens: ค่าเริ่มต้นคือ 2048 หากต้องการเอาต์พุตข้อความยาวให้ตั้งค่าให้สูงขึ้นด้วยตนเอง ขีดจำกัดสูงสุดคือ 32,000 ระวังว่าผลรวมของอินพุตและเอาต์พุตต้องไม่เกิน 100,000 มิฉะนั้นจะรับข้อผิดพลาด 400
- timeout: คำขอที่มีเอาต์พุตยาวจะใช้เวลา更长 แนะนำให้ตั้งค่าเวลาอ่าน超时เป็นมากกว่า 60 วินาทีในสถานการณ์สตรีมมิง และประมาณการตามเวลาเอาต์พุตที่ยาวที่สุดในกรณีที่ไม่ใช่สตรีมมิง
- temperature / top_p / stop: พารามิเตอร์การสุ่มมาตรฐานเหล่านี้จะถูกส่งต่อโดยตรง ค่าที่ตั้งในเฟรมเวิร์กจะมีผลทันที ไม่ต้องเปิดใช้งานเพิ่ม
- ความพร้อมกัน: คีย์ละ 300 requests/นาที หากหลายอินสแตนซ์ใช้คีย์เดียวกัน ขีดจำกัดอัตราจะถูกคำนวณรวมกัน อย่าวางแผนให้แต่ละอินสแตนซ์ใช้ 300 ครั้ง
- การลองใหม่: การลองใหม่ที่มีในเฟรมเวิร์คมักจัดการเฉพาะข้อผิดพลาดเครือข่าย สำหรับ 429 และ 503 ต้องเพิ่ม exponential backoff เอง ดูวิธีการเขียนในบทความความเสถียร
เมื่อดีพลอยไปยังคอนเทนเนอร์ แนวคิดก็คือ "ไม่ใส่คีย์ในภาพ" และให้เครื่องมือจัดตารางฉีดค่าเข้าไปตอนเริ่มต้น ตัวอย่างด้านล่างคือรูปแบบขั้นต่ำใน compose:
# docker-compose.yml 片段
services:
app:
image: your-app:latest
environment:
API_BASE: https://api.llmzhongzhuan.com/v1
API_KEY: ${API_KEY} # 从宿主机环境或 .env 读取,不写进镜像
ใน Kubernetes ให้เปลี่ยนเป็นการอ้างอิง Secret โดยหลักการไม่เปลี่ยน แนะนำให้เตรียมบัญชีหรือคีย์แยกกันสำหรับแต่ละสภาพแวดล้อม เช่น แยกสำหรับพัฒนา, พิสูจน์ และผลิต หากคีย์ของสภาพแวดล้อมหนึ่งรั่วไหลหรือเครดิตหมด จะไม่ส่งผลกระทบต่อระบบผลิต เนื่องจากแต่ละบัญชีมีเพียงคีย์เดียว จึงควรใช้หลายบัญชีสำหรับหลายสภาพแวดล้อม และเติมเครดิตล่วงหน้าในปริมาณที่เหมาะสม
สุดท้ายคือบันทึกข้อมูลจำนวนมาก เฟรมเวิร์กหลายตัวเมื่อเปิดโหมดดีบักจะพิมพ์หัวคำขอทั้งหมด ซึ่งรวมถึง Authorization โปรดปิดการแสดงผลดีบักเหล่านี้ในสภาพแวดล้อมผลิต หรือลบคีย์ออกในตัวกรองบันทึกข้อมูล การบันทึกฟิลด์ usage เป็นสิ่งที่ดี เพราะช่วยให้คุณตรวจสอบกับบิล และตรวจจับได้หากพรอมต์ของฟีเจอร์หนึ่งยาวขึ้นอย่างกะทันหัน
ลำดับการตรวจสอบเมื่อเกิดข้อผิดพลาด
ปัญหาในช่วงเชื่อมต่อ 90% เกิดจากจุดเหล่านี้ ตรวจสอบตามลำดับด้านล่างเพื่อแก้ปัญหาได้เร็วที่สุด:
- 401: คีย์ API ว่างเปล่า, มีการคัดลอกช่องว่างมาด้วย หรือใช้คีย์เก่าหลังจากการรีเซ็ตแล้ว
- 404: ระบุ address เป็น root path โดยไม่มี
/v1หรือพิมพ์ซ้ำ/chat/completionsเกินมา - 402: รหัสข้อผิดพลาดคือ no_credit ซึ่งหมายถึงยอดเงินหมดหรือเครดิตทดลองใช้หมดอายุ ต้องเติมเงิน prepaid credit
- 400: สาเหตุทั่วไปคือ input รวมกับ
max_tokensเกิน 100,000 หรือ request body เกิน 8 MB - 429 / 503: ตัวแรกคือ rate limit ที่ 300 requests ต่อนาที ส่วนตัวที่สองคือ upstream_busy ให้รอสักครู่แล้วลองใหม่ ดูวิธีทำใน 稳定性实践
ยังมีอีกประเภทหนึ่งซึ่งมักถูกละเลยคือสภาพแวดล้อมเครือข่าย ภายในเครือข่ายองค์กร, ซอฟต์แวร์ proxy หรือกฎ security group อาจทำให้การ resolve domain สำเร็จแต่ไม่สามารถสร้างการเชื่อมต่อ HTTPS ได้ ซึ่งแสดงอาการค้างนานแล้วหมดเวลา (timeout) แทนที่จะเป็นรหัสข้อผิดพลาดที่ชัดเจน เมื่อเจอกรณีนี้ ให้ใช้ curl เข้าถึง /v1/models จากเครื่องเดียวกันก่อน หากเชื่อมต่อได้แสดงว่าปัญหาอยู่ที่ application layer หากเชื่อมต่อไม่ได้ให้ตรวจสอบ proxy และไฟร์วอลล์ บันทึกกระบวนการตรวจสอบไว้ในเอกสารของทีม เพื่อให้เพื่อนร่วมงานใหม่สามารถเชื่อมต่อได้สะดวกรวดเร็วขึ้นในครั้งถัดไป
เมื่อค่าทั้งสามถูกต้องแต่ยังล้มเหลว ให้ใช้ curl ส่งคำขอตรงๆ ไปหนึ่งครั้งเพื่อตัดปัญหาจากตัวเฟรมเวิร์ก หากต้องการเข้าใจก่อนว่า API กลางทำงานอย่างไร สามารถย้อนกลับไปอ่าน หลักการ API กลาง ได้
คำถามที่พบบ่อย
base_url ต้องมี /v1 หรือไม่?
ต้องมี โดยระบุเป็น https://api.llmzhongzhuan.com/v1 SDK จะเพิ่ม /chat/completions ให้โดยอัตโนมัติ ไม่ต้องเพิ่มซ้ำ
LlamaIndex ใช้ OpenAILike แทน OpenAI ทำไม?
คลาส OpenAI จะตรวจสอบว่าชื่อโมเดลอยู่ในรายการทางการหรือไม่ หากเป็นชื่อโมเดลที่กำหนดเองจะเกิดข้อผิดพลาด OpenAILike ไม่มีการตรวจสอบ จึงเหมาะสำหรับการเชื่อมต่อ API ที่เข้ากันได้มากกว่า
ใน Dify สามารถตั้งชื่อโมเดลตามใจได้หรือไม่?
ไม่ได้ ชื่อโมเดลต้องเป็น uncensored เนื่องจาก request จะส่ง id นี้ไปตรงๆ ชื่อที่แสดงสามารถตั้งอื่นได้ แต่ฟิลด์โมเดลต้องตรงกัน
ทำไมเมื่อเปลี่ยน environment variable แล้วไม่生效?
ส่วนใหญ่เป็นเพราะ terminal หรือ process ไม่ได้ restart แนะนำให้ส่ง base_url และ api_key อย่างชัดเจนในโค้ด และพิมพ์ที่อยู่ที่ใช้จริงออกมาเพื่อยืนยัน
กรอกแบบฟอร์มเพื่อรับคีย์ API
สร้างบัญชี คัดลอกคีย์ แก้ไข Base URL การตั้งค่าก็ง่ายแค่นี้