AI Gateway TH — Quick Start (Thai)
สำหรับ Wewasanad + integrators · v0.11+ Last updated: 2026-05-31 Contact: [email protected]
1. รับ Bearer Token
Production endpoint: https://gateway.kiatdamrong.shop
ติดต่อ [email protected] พร้อมระบุ:
- ชื่อทีม + email contact
- Use case (เช่น "NIDTEP teacher LMS")
- Required scopes — เลือกจาก:
chat,embed,charts,audit - Server IP allowlist (CIDR)
- Expected rate limit (req/min, req/hour, req/day)
จะได้ JWT token ที่ใช้ใน Authorization: Bearer <token> header
ทุก token ผูกกับ tenant_id เฉพาะ — share token ไม่ได้
2. Health Check (ไม่ต้อง auth)
curl https://gateway.kiatdamrong.shop/health
# → {"status":"ok","version":"0.11.0"}
ใช้ใน monitoring/load-balancer probe
2.5 Native SDKs (แนะนำสำหรับ production)
มี SDK ทางการ 4 ภาษา ห่อ HTTP + retry + typed responses ให้แล้ว — ครอบทุก endpoint ของ gateway รวมทั้ง Wizard / Grounding tools / Knowledge query / Smart Engine + grounded flags. ลูกค้าไม่ต้องเขียน prompt+glue เอง.
| ภาษา | path ใน repo | ติดตั้งจาก source* |
|---|---|---|
| Python | sdks/python/ |
pip install -e ./sdks/python |
| Node / TypeScript | sdks/node/ |
cd sdks/node && npm install && npm run build |
| .NET (net8 / net9) | sdks/dotnet/AiGatewayTh/ |
dotnet add reference sdks/dotnet/AiGatewayTh/AiGatewayTh.csproj |
| Java + Spring | sdks/java/ |
mvn -f sdks/java/pom.xml install |
* Honest: ตอนนี้ติดตั้งจาก source/git เท่านั้น — package registries
(PyPI / npm / NuGet / Maven) ยังไม่ publish. รายละเอียดดูที่
sdks/README.md.
Shared API surface (ทุก SDK)
ทุก SDK มีเมธอดเหมือนกัน เปลี่ยนแค่ naming convention ของภาษา
(snake_case ใน Python, camelCase ใน Node/JS, PascalCaseAsync ใน .NET,
camelCase ใน Java).
| กลุ่ม | เมธอด | endpoint | บันทึก |
|---|---|---|---|
| OpenAI-compat | health() |
GET /health |
liveness probe |
models() |
GET /v1/models |
model catalog | |
chat(req, idempotencyKey?) |
POST /v1/chat/completions |
streaming + tools + JSON mode | |
embeddings(req) |
POST /v1/embeddings |
gateway-embed-th = BGE-M3 1024-dim Thai |
|
| Smart Engine | smart(req, options?) |
POST /v1/smart |
sync; opt-in writer_grounding + judge_tools |
smart_stream(req, options?) |
POST /v1/smart (SSE) |
iterator / generator of per-step events | |
| Wizard facades | wizard_polish(text, style?) |
POST /v1/wizard/polish |
ขัด translationese — Typhoon SAFE Thai-native |
wizard_shorten(text, targetChars?, style?) |
POST /v1/wizard/shorten |
ย่อให้สั้นโดยรักษาใจความ | |
wizard_fact_check(claim, packId?) |
POST /v1/wizard/fact-check |
tooled cross-family judge | |
wizard_grounded_summarize(question, urls?) |
POST /v1/wizard/grounded-summarize |
grounded writer + citation verifier | |
wizard_translate_th(text, sourceLang?, style?) |
POST /v1/wizard/translate-th |
แปลเข้าไทยแบบเนทีฟ (Typhoon) | |
wizard_fix_thai(text) |
POST /v1/wizard/fix-thai |
ซ่อมสะกด/วรรค/การันต์ภาษาไทย | |
| Grounding tools | web_search(query, n?) |
POST /v1/tools/web_search |
SearXNG, Thai-first |
read_url(url, maxChars?) |
POST /v1/tools/read_url |
HTML/PDF + Thai ำ-repair + SSRF guard | |
read_file(file, filename?, maxChars?) |
POST /v1/tools/read_file |
upload PDF / DOCX / XLSX / CSV / TXT (multipart) | |
knowledge_query(packId, query, ...) |
POST /v1/knowledge/query |
curated pack hybrid (vector + tag) | |
| Async jobs | submit_job(req) / get_job(id) / wait_for_job(id) / list_jobs(limit) |
POST/GET /v1/jobs/... |
heavy compose with webhook |
| Audit | audit(request_id) |
GET /v1/audit/{id} |
metadata-only audit trail |
ทุก SDK มี retry+exponential backoff อัตโนมัติ สำหรับ 5xx + network
errors, idempotency-key opt-in per-call, และ cancellation native
ของภาษา (AbortSignal / CancellationToken / thread interrupt).
Python
from ai_gateway_th import GatewayClient
c = GatewayClient(
base_url="https://gateway.kiatdamrong.shop",
token="<YOUR_TOKEN>",
tenant_id="<YOUR_TENANT_ID>",
max_retries=3, # exponential backoff on 5xx + network
timeout=120.0, # default per-call timeout
)
# 1) Chat (OpenAI-compat — passthrough)
resp = c.chat(
model="gateway-th",
messages=[{"role": "user", "content": "อธิบาย scaffolding ใน 3 บรรทัด"}],
max_tokens=400,
idempotency_key="onboarding-msg-001", # safe to retry, replays cache
)
print(resp["choices"][0]["message"]["content"])
# 2) Wizard polish (Typhoon SAFE — ลบ "ที่ซึ่ง / ถูกทำให้")
r = c.wizard_polish(
"การเรียนรู้แบบลงมือทำที่ซึ่งผู้เรียนถูกทำให้มีส่วนร่วม",
style="neutral",
)
print(r["polished_text"])
# 3) Wizard grounded-summarize (writer_grounding + citation verifier)
r = c.wizard_grounded_summarize(
"สรุปแนวทาง active learning การคูณ ป.4 จากแหล่งจริง",
)
print(r["summary"])
print("URLs read:", len(r["writer_grounding"]["urls_read"]))
print("Fabricated:", r["writer_grounding"]["citations"]["urls_fabricated"])
# 4) Grounding tools — เอามาประกอบเอง
hits = c.web_search("แผนการสอนคณิต ป.4 active learning", n=3)
article = c.read_url(hits["results"][0]["url"])
print(article["content"][:500])
# 5) read_file — อัปโหลด PDF/DOCX/XLSX/CSV/TXT
with open("plan.pdf", "rb") as f:
out = c.read_file(f, max_chars=30000)
print(out["kind"], out["chars"], "chars")
# หรือส่ง bytes ตรงๆ ก็ได้:
out = c.read_file(b"name,score\nA,80\n", filename="grades.csv")
# 6) Smart Engine + opt-in grounding (raw control)
r = c.smart(
messages=[{"role": "user", "content": "วิเคราะห์ x จากงานวิจัยจริง"}],
writer_grounding=True,
judge_tools=True,
stream=False, # use smart_stream() for >100s compose
)
# 7) Smart streaming SSE (long compose ผ่าน CF tunnel)
for event in c.smart_stream(
messages=[{"role": "user", "content": "ออกแบบแผนการสอน ป.4 เรื่องการคูณ"}],
):
if event["event"] == "smart.complete":
print(event["data"]["choices"][0]["message"]["content"])
break
# 8) Async job (>10 นาที compose)
job = c.submit_job(
messages=[{"role": "user", "content": "งานใหญ่..."}],
webhook_url="https://your.app/cb",
webhook_secret="shared",
)
done = c.wait_for_job(job["job_id"])
# 9) Error handling
from ai_gateway_th import GatewayError
try:
c.chat(model="bad-model", messages=[{"role": "user", "content": "x"}])
except GatewayError as e:
print(e.status_code, e.error_type, e.message, e.request_id)
Node / TypeScript
import { GatewayClient, GatewayError } from "@ai-gateway-th/sdk";
const c = new GatewayClient({
baseUrl: "https://gateway.kiatdamrong.shop",
token: process.env.GATEWAY_TOKEN!,
tenantId: process.env.GATEWAY_TENANT_ID,
maxRetries: 3,
timeoutMs: 120_000,
});
// 1) Chat
const resp = await c.chat({
model: "gateway-th",
messages: [{ role: "user", content: "อธิบาย scaffolding ใน 3 บรรทัด" }],
max_tokens: 400,
}, { idempotencyKey: "onboarding-msg-001" });
// 2) Wizard polish
const r = await c.wizardPolish(
"การเรียนรู้แบบลงมือทำที่ซึ่งผู้เรียนถูกทำให้มีส่วนร่วม",
{ style: "neutral" },
);
console.log(r.polished_text);
// 3) Wizard grounded-summarize
const sum = await c.wizardGroundedSummarize(
"สรุปแนวทาง active learning การคูณ ป.4 จากแหล่งจริง",
);
console.log(sum.summary);
console.log("Fabricated:",
(sum.writer_grounding as any).citations.urls_fabricated);
// 4) read_file — รับ Blob / Uint8Array / ArrayBuffer
import { readFileSync } from "node:fs";
const bytes = readFileSync("plan.pdf");
const doc = await c.readFile(bytes, {
filename: "plan.pdf",
contentType: "application/pdf",
maxChars: 30000,
});
console.log(doc.kind, doc.chars);
// 5) Smart streaming (async iterator)
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 600_000); // safety cancel after 10min
for await (const ev of c.smartStream(
{ messages: [{ role: "user", content: "ออกแบบแผนการสอน ป.4" }] },
{ signal: ctrl.signal },
)) {
if (ev.event === "smart.complete") {
console.log(ev.data.choices[0].message.content);
}
}
// 6) Error handling
try {
await c.chat({ model: "bad", messages: [{ role: "user", content: "x" }] });
} catch (e) {
if (e instanceof GatewayError) {
console.error(e.statusCode, e.errorType, e.message, e.requestId);
} else {
throw e;
}
}
.NET (C# — net8 / net9)
using AiGatewayTh;
// Bare construction
using var client = new GatewayClient(new GatewayClientOptions
{
Token = Environment.GetEnvironmentVariable("GATEWAY_TOKEN")!,
TenantId = Environment.GetEnvironmentVariable("GATEWAY_TENANT_ID"),
BaseUrl = "https://gateway.kiatdamrong.shop",
MaxRetries = 3,
Timeout = TimeSpan.FromMinutes(2),
});
// 1) Chat
var resp = await client.ChatAsync(new ChatCompletionRequest
{
Model = "gateway-th",
Messages = new()
{
new ChatMessage { Role = "user",
Content = "อธิบาย scaffolding ใน 3 บรรทัด" }
},
MaxTokens = 400,
}, idempotencyKey: "onboarding-msg-001");
Console.WriteLine(resp.Choices[0].Message?.Content);
// 2) Wizard polish
var polish = await client.WizardPolishAsync(
"การเรียนรู้แบบลงมือทำที่ซึ่งผู้เรียนถูกทำให้มีส่วนร่วม",
style: "neutral");
Console.WriteLine(polish["polished_text"]);
// 3) Wizard grounded-summarize
var sum = await client.WizardGroundedSummarizeAsync(
"สรุปแนวทาง active learning การคูณ ป.4 จากแหล่งจริง");
Console.WriteLine(sum["summary"]);
// 4) read_file — upload PDF
byte[] pdfBytes = File.ReadAllBytes("plan.pdf");
var doc = await client.ReadFileAsync(
pdfBytes, filename: "plan.pdf", contentType: "application/pdf",
maxChars: 30000);
Console.WriteLine($"kind={doc["kind"]} chars={doc["chars"]}");
// 5) Smart streaming
await foreach (var ev in client.SmartStreamAsync(new SmartRequest
{
Messages = new() { new() { Role = "user",
Content = "ออกแบบแผนการสอน ป.4" } },
}))
{
if (ev.Event == "smart.complete")
{
using var doc2 = System.Text.Json.JsonDocument.Parse(ev.DataJson);
Console.WriteLine(doc2.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content").GetString());
}
}
// 6) Error handling
try
{
await client.ChatAsync(new ChatCompletionRequest { Model = "bad",
Messages = new() { new() { Role = "user", Content = "x" } } });
}
catch (GatewayException ex)
{
Console.WriteLine($"{ex.StatusCode} {ex.ErrorType}: {ex.Message}");
}
ASP.NET Core integration (DI + IHttpClientFactory pooling):
builder.Services.AddHttpClient("ai-gateway", c =>
{
c.BaseAddress = new Uri("https://gateway.kiatdamrong.shop");
c.Timeout = TimeSpan.FromMinutes(5);
});
builder.Services.AddSingleton(sp => new GatewayClient(new GatewayClientOptions
{
Token = builder.Configuration["AiGateway:Token"]!,
TenantId = builder.Configuration["AiGateway:TenantId"],
HttpClient = sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ai-gateway"),
}));
// then inject as `GatewayClient` constructor arg
Java + Spring (JDK 17+)
import shop.kiatdamrong.aigateway.*;
GatewayClient client = new GatewayClient(
GatewayClientOptions.builder()
.token(System.getenv("GATEWAY_TOKEN"))
.tenantId(System.getenv("GATEWAY_TENANT_ID"))
.baseUrl("https://gateway.kiatdamrong.shop")
.maxRetries(3)
.timeout(java.time.Duration.ofMinutes(2))
.build());
// 1) Chat
Types.ChatCompletionRequest req = new Types.ChatCompletionRequest();
req.model = "gateway-th";
req.messages = java.util.List.of(
new Types.ChatMessage("user", "อธิบาย scaffolding ใน 3 บรรทัด"));
req.maxTokens = 400;
Types.ChatCompletionResponse resp = client.chat(req, "onboarding-msg-001");
System.out.println(resp.choices.get(0).message.content);
// 2) Wizard polish
Map<String,Object> polish = client.wizardPolish(
"การเรียนรู้แบบลงมือทำที่ซึ่งผู้เรียนถูกทำให้มีส่วนร่วม",
"neutral", null, null);
System.out.println(polish.get("polished_text"));
// 3) Wizard grounded-summarize
Map<String,Object> sum = client.wizardGroundedSummarize(
"สรุปแนวทาง active learning การคูณ ป.4 จากแหล่งจริง",
null, null, null);
System.out.println(sum.get("summary"));
// 4) read_file — upload PDF
byte[] pdfBytes = java.nio.file.Files.readAllBytes(
java.nio.file.Paths.get("plan.pdf"));
Map<String,Object> doc = client.readFile(
pdfBytes, "plan.pdf", "application/pdf", 30000);
System.out.println("kind=" + doc.get("kind") +
" chars=" + doc.get("chars"));
// 5) Smart streaming
Types.SmartRequest sr = new Types.SmartRequest();
sr.messages = java.util.List.of(
new Types.ChatMessage("user", "ออกแบบแผนการสอน ป.4"));
try (GatewayClient.SmartStreamIterator it = client.smartStream(sr, false)) {
while (it.hasNext()) {
Types.SseEvent ev = it.next();
if ("smart.complete".equals(ev.event)) {
// ev.dataJson = JSON string of the completion
System.out.println(ev.dataJson);
}
}
}
// 6) Error handling
try {
Types.ChatCompletionRequest bad = new Types.ChatCompletionRequest();
bad.model = "bad-model";
bad.messages = java.util.List.of(new Types.ChatMessage("user", "x"));
client.chat(bad);
} catch (GatewayException ex) {
System.err.printf("%d %s: %s%n",
ex.getStatusCode(), ex.getErrorType(), ex.getMessage());
}
Spring Boot auto-configuration (no manual wiring):
application.yml:
ai-gateway:
token: ${GATEWAY_TOKEN}
tenant-id: ${GATEWAY_TENANT_ID}
base-url: https://gateway.kiatdamrong.shop
Then @Autowired GatewayClient ใน service ของคุณ — auto-config โหลด
จาก META-INF/spring/...AutoConfiguration.imports (มากับ jar).
Common patterns (ทุก SDK)
Idempotency — SDK v0.5.0 ขึ้นไป สร้าง key ให้อัตโนมัติ จาก
เนื้อหา request (ปิดได้ด้วย auto_idempotency=False) — ไม่ต้องส่งเองแล้ว.
ผลที่ได้:
- retry หลัง network fail → ได้คำตอบเดิมกลับใน หลัก ms (cache 7 วัน)
- request เหมือนกันที่ยิงพร้อมกัน (เช่น user กดซ้ำ, หลาย tab) → gateway รวมเป็น LLM call เดียว แล้วแจกคำตอบให้ทุก caller (รวมถึง streaming) — ประหยัดและกัน race
- server ล่มกลางคัน → retry ด้วย key เดิม resume/replay ได้
ถ้าต้องการคุม key เอง (เช่น แยกตาม business transaction) ก็ส่งเองได้เหมือนเดิม:
| SDK | usage |
|---|---|
| Python | c.chat(..., idempotency_key="my-key-1") |
| Node | await c.chat(req, { idempotencyKey: "my-key-1" }) |
| .NET | await c.ChatAsync(req, idempotencyKey: "my-key-1") |
| Java | c.chat(req, "my-key-1") |
Cancellation — กัน compose ค้างเกิน budget:
| SDK | usage |
|---|---|
| Python | c.smart_stream(...) ภายใน try/except KeyboardInterrupt |
| Node | AbortController → signal: ctrl.signal |
| .NET | CancellationToken arg ทุกเมธอด *Async |
| Java | thread interrupt → catch InterruptedException |
Streaming (long compose >100s through CF tunnel) — ใช้ *_stream
methods เสมอ. SDK ส่ง SSE keepalive comments (: ping) อัตโนมัติ — กัน
CF tunnel idle-timeout ที่ 100s.
Why use the native SDK (vs OpenAI-compat client)
| OpenAI SDK (passthrough) | Native SDK | |
|---|---|---|
chat / embeddings |
✅ | ✅ typed |
smart + grounded flags |
❌ (must hand-roll JSON) | ✅ typed |
wizard/* (polish / fact-check / grounded-summarize) |
❌ | ✅ one method per wizard |
tools/web_search / read_url / read_file |
❌ | ✅ |
knowledge_query |
❌ | ✅ |
| Idempotency-Key | manual header | ✅ method arg |
| Retry + backoff | client default | ✅ built-in (5xx + network) |
| Streaming SSE | works (text only) | ✅ + per-step events typed |
| Anti-fabrication metadata | not surfaced | ✅ writer_grounding.citations field |
ใช้ OpenAI SDK ตอนย้าย legacy code; production ใหม่ใช้ native SDK ของเรา จะคุ้มกว่า — ได้ Wizard / Grounding / read_file endpoints ครบ + typed responses + retry built-in.
SDK reference files
ทุก SDK มีไฟล์รายละเอียดเฉพาะภาษา (install, build, examples, API list ครบ):
sdks/python/README.mdsdks/node/README.mdsdks/dotnet/README.mdsdks/java/README.mdsdks/README.md— overview ของทั้ง 4 ภาษาsdks/PUBLISHING.md— checklist สำหรับ publish PyPI/npm/NuGet/Maven (Kiat ใช้รัน)
3. List Models (/v1/models)
curl https://gateway.kiatdamrong.shop/v1/models \
-H "Authorization: Bearer <YOUR_TOKEN>"
Models สำคัญที่ควรรู้:
| public_id | tier | description |
|---|---|---|
gateway-th |
local | Smart Thai routing — แนะนำสำหรับ Thai content (ฟรี, in-country) |
gateway-embed |
local | Embedding model (nomic-embed-text 768-dim) |
local-fast |
local | Qwen3.6 35B-A3B Q8 — fast Thai chat |
frontier-claude |
frontier | Anthropic Claude Sonnet — สำหรับงานยาก |
frontier-gemini |
frontier | Google Gemini Pro — JSON mode ดี |
cloud-large |
cloud | GPT-OSS 120B (free, out-of-country) |
Tier policy:
local→ ฟรี, in-country (PDPA-clean), เร็วสำหรับ Thaicloud→ ฟรี-ish, ข้อมูลออกนอกประเทศ (PII auto-redact ก่อนส่ง)frontier→ per-token cost, ข้อมูลออกนอกประเทศ (PII auto-redact ก่อนส่ง)
4. Chat Completions (OpenAI-compat)
curl
curl -X POST https://gateway.kiatdamrong.shop/v1/chat/completions \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"model": "gateway-th",
"messages": [{"role": "user", "content": "อธิบาย scaffolding ใน 2 บรรทัด"}]
}'
Python (OpenAI SDK)
from openai import OpenAI
client = OpenAI(
base_url="https://gateway.kiatdamrong.shop/v1",
api_key="<TOKEN>",
)
resp = client.chat.completions.create(
model="gateway-th",
messages=[{"role": "user", "content": "อธิบาย scaffolding ใน 2 บรรทัด"}],
)
print(resp.choices[0].message.content)
Node.js
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://gateway.kiatdamrong.shop/v1",
apiKey: process.env.GATEWAY_TOKEN,
});
const r = await client.chat.completions.create({
model: "gateway-th",
messages: [{ role: "user", content: "อธิบาย scaffolding ใน 2 บรรทัด" }],
});
console.log(r.choices[0].message.content);
PHP (orhanerday/open-ai)
require 'vendor/autoload.php';
use Orhanerday\OpenAi\OpenAi;
$client = new OpenAi($_ENV['GATEWAY_TOKEN']);
$client->setBaseURL("https://gateway.kiatdamrong.shop/v1");
$resp = $client->chat([
'model' => 'gateway-th',
'messages' => [['role' => 'user', 'content' => 'อธิบาย scaffolding']],
]);
5. Smart Engine (/v1/smart) — ใช้แทน /v1/chat สำหรับงาน structured
/v1/smart คือ flagship endpoint — auto-classify intent + route packs +
compose write→judge→revise → enforce JSON schema
curl -N -X POST https://gateway.kiatdamrong.shop/v1/smart \
-H "Authorization: Bearer <TOKEN>" \
-H "X-Tenant-Id: <YOUR_TENANT_ID>" \
-H "Content-Type: application/json" \
-d '{
"stream": true,
"messages": [{
"role": "user",
"content": "ออกแบบแผนการสอนคณิตศาสตร์ ป.4 เรื่อง การคูณ โดยใช้ active learning + ระบุตัวชี้วัด"
}]
}'
stream: true สำคัญสำหรับ /v1/smart — compose ใช้เวลา 30-300s (งานหนัก
ที่เข้า judge→revise→fresh_think อาจถึง ~6 นาที); CF Tunnel จะตัดการเชื่อมต่อ
แบบ synchronous ที่ 100s (ได้ HTTP 524) ถ้าไม่ stream
SSE event order:
smart.received → smart.classified → smart.routed → compose.* → smart.enforce → smart.complete → [DONE]
Keepalive (verified 2026-05-31): ระหว่าง step ที่เงียบ (judge / revise / fresh_think / JSON-enforce) server จะส่ง SSE comment
: keepaliveทุก ~15s เพื่อกัน proxy idle-timeout — client ที่ทำตาม SSE spec จะข้ามบรรทัดที่ ขึ้นต้นด้วย:อัตโนมัติ ดังนั้นไม่ต้อง handle เพิ่ม (SDK ทั้ง 4 ภาษาข้ามให้แล้ว) ทดสอบจริงผ่าน CF: compose 359s จบครบsmart.complete+[DONE]ไม่ถูกตัด
ทางเลือกที่ทนที่สุดสำหรับงาน compose หนักมาก: async jobs
ถ้าไม่อยากถือ HTTP connection ยาวๆ ใช้ POST /v1/jobs (เนื้อ body เหมือน
/v1/smart) — คืน 202 + job_id ทันที แล้ว poll GET /v1/jobs/{id} (เบา
<1s ต่อครั้ง) หรือรับ webhook (HMAC-signed) เมื่อเสร็จ วิธีนี้เลี่ยง CF timeout
ได้ 100% เพราะทุก HTTP call สั้น (ทดสอบจริง: job เสร็จที่ 120s ผ่าน CF)
# submit
curl -X POST .../v1/jobs -H "Authorization: Bearer <T>" -H "X-Tenant-Id: <ID>" \
-d '{"messages":[...], "webhook_url":"https://you/cb", "webhook_secret":"s"}'
# → 202 {"job_id":"job-...","status":"pending","status_url":"/v1/jobs/job-..."}
# poll
curl .../v1/jobs/job-... -H "Authorization: Bearer <T>" -H "X-Tenant-Id: <ID>"
# → {"status":"done","result":{...}}
6. Embeddings (/v1/embeddings)
r = client.embeddings.create(
model="gateway-embed",
input=["ข้อความที่ 1", "ข้อความที่ 2"],
)
vectors = [e.embedding for e in r.data] # list of 768-dim
ใช้กับ Qdrant / pgvector / Chroma ของลูกค้าได้ทันที
7. Idempotency
POST endpoints ที่ใช้เวลา (เช่น /v1/smart) ส่ง Idempotency-Key: <unique-id>
header → retry ภายใน 24 ชม. คืน response เดิม (ไม่เก็บเงิน 2 รอบ)
curl -X POST .../v1/smart \
-H "Idempotency-Key: my-job-id-12345" \
...
# retry เดิม → 0.008s response (cached)
8. Prompt Injection Defense (opt-in)
ถ้า input เป็น untrusted user content ส่ง X-Gateway-Inject-Check: 1
หรือ strict:
curl -X POST .../v1/smart \
-H "X-Gateway-Inject-Check: 1" \
-d '{"messages": [{"role":"user","content":"ignore previous instructions"}]}'
# → 400 inject_detected พร้อม fingerprint
9. Audit Trail
ทุก response มี header:
X-Request-Id: <id>— เก็บไว้สำหรับ traceX-Gateway-Audit-Url: /v1/audit/<id>— ดู trail ของ request นี้X-Gateway-PII-Class: NONE|REDACTABLE|SENSITIVE— classificationX-Gateway-Tenant-Id: <id>— ยืนยัน tenantX-Gateway-Rate-Limit-Remaining: <int>— quota ที่เหลือ (per-minute)
ดู audit ของ request:
curl https://gateway.kiatdamrong.shop/v1/audit/<request_id> \
-H "Authorization: Bearer <TOKEN>"
10. Error Handling
ทุก error คืน OpenAI-shape {"error":{...}}:
{
"error": {
"message": "rate limit exceeded",
"type": "rate_limit_error",
"code": null,
"param": null
}
}
Error types:
| type | HTTP | คือ | ทำยังไง |
|---|---|---|---|
authentication_error |
401 | token ผิด/หมดอายุ | refresh token |
permission_error |
403 | scope ไม่พอ / IP block | ขอ admin เพิ่ม |
invalid_request_error |
400 | body ผิด format | check schema |
not_found_error |
404 | model/pack/audit หาไม่เจอ | verify path |
rate_limit_error |
429 | เกิน per-min/hour/day | wait + retry |
payload_too_large_error |
413 | CSV/content เกินขนาด | shrink |
backend_error |
502 | upstream LLM fail | retry กับ exponential backoff |
timeout_error |
504 | step timeout | retry |
inject_detected |
400 | prompt injection ตรวจพบ | sanitize user input |
internal_server_error |
500 | bug ของเรา | report + retry |
11. Rate Limit + Retry Policy แนะนำ
import time
from openai import OpenAI, RateLimitError, APIError
client = OpenAI(base_url=..., api_key=...)
def call_with_retry(*args, max_retries=3, **kwargs):
delay = 1.0
for attempt in range(max_retries):
try:
return client.chat.completions.create(*args, **kwargs)
except RateLimitError:
time.sleep(delay)
delay *= 2
except APIError as e:
if e.status_code >= 500:
time.sleep(delay)
delay *= 2
else:
raise
raise RuntimeError("max retries exceeded")
12. Performance & SLA แนะนำ
| Endpoint | p50 | p95 | Note |
|---|---|---|---|
/health |
<10ms | <50ms | liveness only |
/v1/models |
<50ms | <200ms | catalog |
/v1/chat/completions (local) |
1-5s | 10s | depends on tokens |
/v1/chat/completions (frontier) |
2-15s | 30s | network + provider |
/v1/smart synchronous |
45-90s | 180s | ใช้ stream:true |
/v1/smart streaming |
first byte <2s | full <300s | SSE |
/v1/embeddings |
<100ms/text | <500ms | local nomic |
SLA ที่เราการันตี:
- Service uptime: 99.0% (Spark SPOF — Stage 2 จะเพิ่ม HA)
- Mean time to acknowledge incident: 24h business hours
- Mean time to resolve P0 incident: 72h
12.5 Grounding Tools (สำหรับ AI ที่ต้องอ้างของจริง)
หลักการ: AI ของเราอ่านแหล่งจริง ไม่มโน. 3 tools ที่ Smart Engine / Wizard เรียกใช้เพื่อ ground คำตอบกับข้อมูลจริง:
POST /v1/tools/web_search
ค้นเว็บภาษาไทย (SearXNG ในประเทศ) คืน title + url + snippet ตามจำนวนที่ขอ.
curl -X POST .../v1/tools/web_search \
-H "Authorization: Bearer <TOKEN>" \
-d '{"query":"แผนการสอนคณิต ป.4 active learning","n":5}'
POST /v1/tools/read_url
ดึงเนื้อหาจริงจาก URL (HTML ผ่าน trafilatura, PDF ผ่าน PyMuPDF + ซ่อมสระไทยที่แตก ำ). SSRF-guarded — block private / loopback / link-local IPs.
curl -X POST .../v1/tools/read_url \
-d '{"url":"https://example.com/article","max_chars":8000}'
POST /v1/tools/read_file
อัปโหลดไฟล์จริง (PDF / DOCX / XLSX / CSV / TXT / MD) → ดึงข้อความสะอาด. ใช้สำหรับให้ AI วิเคราะห์/สรุป/ถอดความเอกสารที่ลูกค้าอัปโหลด.
Multipart upload (ไม่ใช่ JSON):
curl -X POST .../v1/tools/read_file \
-H "Authorization: Bearer <TOKEN>" \
-H "X-Tenant-Id: <TENANT_ID>" \
-F "[email protected]" \
-F "max_chars=30000"
Response:
{
"kind": "pdf", // pdf | docx | xlsx | csv | text | unknown
"chars": 12345,
"content": "...ข้อความที่ดึงออกมา...",
"filename": "report.pdf"
}
| Format | Extractor | หมายเหตุ |
|---|---|---|
| PyMuPDF (fitz) | + ซ่อมสระไทย ำ ที่ extractor PDF แตกออกเป็น "<พยัญชนะ> า" — verified safe (เวลา/ปัญหา ไม่พัง) | |
| DOCX | python-docx | รวม paragraph + table cells |
| XLSX/XLSM | openpyxl | ทุก sheet, cap 500 rows/sheet |
| CSV/TSV | csv stdlib | auto delimiter (.csv=comma, .tsv=tab), cap 500 rows |
| TXT/MD/log | UTF-8 → CP874 → Latin-1 | fallback chain สำหรับ Thai legacy encoding |
Limits: ไฟล์สูงสุด 10 MB · max_chars default 30000, สูงสุด 60000.
Format detection: magic bytes ก่อน → filename extension fallback.
# Python (requests)
with open("plan.pdf", "rb") as f:
r = requests.post(
"https://gateway.kiatdamrong.shop/v1/tools/read_file",
headers={"Authorization": f"Bearer {TOKEN}",
"X-Tenant-Id": TENANT},
files={"file": ("plan.pdf", f, "application/pdf")},
data={"max_chars": 30000},
)
text = r.json()["content"]
POST /v1/knowledge/query
ค้นจาก curated knowledge pack (pedagogy-th, cs-fundamentals-th,
dev-reference-en, thai-medical-pdpa-th, ...). มีอยู่ก่อนแล้ว.
ทำไมสำคัญ: raw LLM (Claude/GPT/Gemini) เวลาขอให้ "ค้นและสรุป" มัน มโน citation บ่อย (ใช้ชื่อโดเมนจริง + ใส่ชื่อบทความ/ปีปลอม). 4 tools นี้บังคับให้ AI เปิดอ่านจริงก่อน — แล้ว Smart Engine เก็บ tool-call log ไว้ verify ทุก URL/source ในคำตอบ.
12.6 Smart Engine — opt-in grounding flags
/v1/smart มี 2 flag ใหม่ที่ปลด anti-fabrication ระดับ engine:
| flag | ทำงานเมื่อไร | ใช้ตอนไหน |
|---|---|---|
writer_grounding: true |
writer ค้น+อ่าน real sources ผ่าน tools ก่อนเขียน, citation verifier เทียบ URL ที่อ้างกับ tool-call log | งาน analysis/synthesis ที่ต้องอ้างอิงแหล่งจริง |
judge_tools: true |
judge เรียก tools (lookup_knowledge / web_search / read_url) ก่อนตัดสิน, verdict มี citation จากของจริง | งานตรวจสอบเนื้อหาว่าตรงหลักการ/ตัวชี้วัด/งานวิจัยจริงไหม |
PDPA gate: ทั้ง 2 flag เปิดได้เฉพาะตอน prompt ไม่มี PII (engine classify อัตโนมัติ). ถ้ามี PII → fallback เป็น path local ไม่กระเทือน.
ตัวอย่าง:
POST /v1/smart
{
"messages":[{"role":"user","content":"สังเคราะห์แนวทาง AL คณิต ป.4"}],
"writer_grounding": true,
"judge_tools": true,
"stream": true
}
Response มี smart.writer_grounding:
{
"enabled": true,
"tool_calls": 13,
"urls_read": ["https://nuir.lib.nu.ac.th/...", "https://moelearn.com/..."],
"queries": ["active learning ป.4", ...],
"citations": {
"urls_total": 12,
"urls_grounded": 12,
"urls_fabricated": 0,
"fabricated_list": []
}
}
citations.fabricated_list = URL ที่ AI อ้างในคำตอบแต่ไม่ได้เปิดอ่านจริง
— ใช้ตัดสินใจว่าจะแสดงผลให้ end-user ดีไหม.
Streaming mode — รับ citations ระหว่างทาง (gateway 0.12.0+)
ตอน "stream": true จะมี SSE event ใหม่ ชื่อ smart.grounding.complete
ยิงออกมา หลัง smart.enforce และ ก่อน smart.complete — ทำให้
UI/SDK ที่อ่าน SSE ตรงๆ (เช่น เว็บผ่าน Cloudflare Tunnel ที่ต้องหลีกเลี่ยง
100s edge timeout) แสดง 📚 อ้างอิง ได้ทันทีโดยไม่ต้องรอ body จบ:
event: smart.enforce
data: {...}
event: smart.grounding.complete
data: {"tool_calls": 3,
"urls_read": ["https://...", "https://..."],
"queries": ["bitcoin price USD"]}
event: smart.complete
data: {... full SmartResponse envelope ...}
Python SDK 0.3.0+:
for ev in client.smart_stream(
messages=[{"role":"user","content":"ราคา BTC วันนี้?"}],
writer_grounding=True,
):
if ev["event"] == "smart.grounding.complete":
urls = ev["data"]["urls_read"]
print(f"📚 ใช้ {len(urls)} แหล่ง:", urls)
elif ev["event"] == "smart.complete":
finalize(ev["data"])
break
Node SDK 0.3.0+ (TypeScript):
import type { SmartGroundingComplete } from "@ai-gateway-th/sdk";
for await (const ev of client.smartStream({
messages: [{ role: "user", content: "ราคา BTC วันนี้?" }],
writer_grounding: true,
})) {
if (ev.event === "smart.grounding.complete") {
const g = ev.data as SmartGroundingComplete;
console.log(`📚 ใช้ ${g.urls_read.length} แหล่ง`, g.urls_read);
} else if (ev.event === "smart.complete") {
finalize(ev.data);
break;
}
}
Raw HTTP (ไม่ใช้ SDK): อ่าน SSE line-by-line ตามมาตรฐาน W3C, parse
event: + data: คู่. response headers ที่เพิ่มมาช่วยให้
client เห็น "ตอนนี้ใช้กี่ tool แล้ว" จาก outside:
X-Gateway-Writer-Grounding-Calls: 3X-Gateway-Writer-Grounding-Urls: ["https://...", ...](JSON list, first 5 URLs only — header size cap)
smart.grounding.complete จะไม่ยิง ถ้า writer ไม่ได้เรียก tool เลย
(tool_calls = 0) — ดังนั้น absence ของ event = ไม่มี grounded sources,
ไม่ใช่ event หาย.
12.7 Wizard endpoints — สำเร็จรูป (ไม่ต้องเขียน prompt เอง)
Wizard = preset ของ Smart Engine ออกมาเป็น URL ตรง ลูกค้าไม่ต้องประกอบ SmartApiRequest เอง. ใช้ engine + tools เดียวกัน → ได้ PDPA/audit/load-aware offload/idempotency/streaming ฟรี.
POST /v1/wizard/polish — ขัดเกลาภาษาไทยให้เป็นเนทีฟ
curl -X POST .../v1/wizard/polish \
-d '{"text":"...ที่ซึ่งผู้เรียนถูกทำให้มีส่วนร่วม...","style":"neutral"}'
Default writer = Typhoon 2.5 SAFE (Thai-native SOTA, stop-token-hardened).
Style: formal | neutral | casual. Returns polished_text + change list.
ใช้ตอน: Claude/GPT/Gemini เขียนไทยมาแล้วได้สำนวนแปลตรงตัว POST ผ่านนี้ก็เนทีฟ.
POST /v1/wizard/fact-check
curl -X POST .../v1/wizard/fact-check \
-d '{"claim":"แผนการสอน...","pack_id":"pedagogy-th"}'
Tooled judge — verdict + grounded issues + citation. ใช้ตอน: ตรวจแผนสอน / บทความ / ข้ออ้าง.
POST /v1/wizard/grounded-summarize
curl -X POST .../v1/wizard/grounded-summarize \
-d '{"question":"สรุปแนวทาง AL การคูณ ป.4","urls":["https://..."]}'
Grounded writer + citation verifier. ใช้ตอน: สังเคราะห์งานวิจัย / สรุปเอกสารหลายแหล่งให้ผู้บริหารอ่าน.
POST /v1/wizard/shorten — ตัดให้สั้นโดยรักษาใจความ
curl -X POST .../v1/wizard/shorten \
-d '{"text":"ข้อความยาว...","target_chars":120,"style":"neutral"}'
Optional target_chars (ระบุยาวที่ต้องการ), style: formal|neutral|casual,
tier: standard|premium|frontier. Returns revised_text + summary of cuts.
ใช้ตอน: ย่อบทความ / สรุป exec brief / ตัดคำอวยให้กระชับ.
POST /v1/wizard/translate-th — แปลเข้าไทยแบบเนทีฟ
curl -X POST .../v1/wizard/translate-th \
-d '{"text":"Active learning is...","source_lang":"en","style":"neutral"}'
source_lang optional (auto-detect ได้). Default writer = Typhoon 2.5 SAFE
→ ผลลัพธ์เป็นไทยเนทีฟ ไม่ใช่แปลตรงตัว. ใช้ตอน: แปลเอกสารวิจัย /
slogan / SOP ของบริษัทต่างชาติเข้าไทย.
POST /v1/wizard/fix-thai — ซ่อมสะกด/วรรค/การันต์
curl -X POST .../v1/wizard/fix-thai \
-d '{"text":"การเรียนรุ้แบบลงมือทำ..."}'
แก้ทีละจุด พร้อม changelog (ก่อน→หลัง). ใช้ตอน: OCR ออกมาผิด / เอกสารที่ user พิมพ์เร่งๆ / clean dataset ก่อน fine-tune.
Wizard endpoints ทั้ง 6 ใช้ engine เดียวกับ Smart — ได้ PDPA/audit/streaming ฟรี. ดู Playground ที่ /try สำหรับลองทุก wizard สด.
12.8 Knowledge Flywheel (admin) — ขยาย pack จากเว็บจริง
/v1/packs/<pack_id>/draft-from-url → review → approve → ingest. ทุก chunk
ใน pack จะมี source URL + fetched_at = ตรวจสอบที่มาได้ตลอด.
# 1) Admin ดึงบทความจริงเป็น draft
curl -X POST .../v1/packs/pedagogy-th/draft-from-url \
-H "Authorization: Bearer <ADMIN>" \
-d '{"url":"https://nuir.lib.nu.ac.th/.../paper.pdf"}'
# 2) ดู drafts ที่รอ
curl .../v1/packs/pedagogy-th/drafts -H "Authorization: Bearer <ADMIN>"
# 3) อ่าน draft ก่อนอนุมัติ
curl .../v1/packs/pedagogy-th/drafts/<draft_id> -H "Authorization: Bearer <ADMIN>"
# 4) อนุมัติ → ย้ายเข้า sources/ + re-embed
curl -X POST .../v1/packs/pedagogy-th/drafts/<draft_id>/approve \
-H "Authorization: Bearer <ADMIN>"
# (หรือปฏิเสธ)
curl -X POST .../v1/packs/pedagogy-th/drafts/<draft_id>/reject \
-H "Authorization: Bearer <ADMIN>"
Admin tenant เท่านั้น (is_admin_tenant). SSRF guard ที่ shared layer.
12.9 Thai writer aliases
| alias | model | ใช้เมื่อ |
|---|---|---|
local-writer |
qwen3.6 35B-a3b-q4 (DGX) | general writer, bulk Thai prose |
local-writer-th |
Typhoon 2.5 SAFE (SCB 10X, stop-hardened) | Thai polish / rewrite — default ของ wizard polish |
local-analyst-th |
Pathumma 8B-think (NECTEC) | backstage support only — vis analytical context, ไม่ขัดเกลาให้ |
local-editor |
qwen3-next:80b-a3b-q8 | judge / fact-check (raw path) |
cloud-fast |
GLM-4.7 (Ollama Cloud free) | cross-family judge (tooled, default) |
cloud-large |
GPT-OSS 120B (Ollama Cloud free) | grounded writer (tooled, default) |
Pathumma เก่ง Thai cultural context แต่ ไม่เคารพ rewrite instruction (think-tuned → อธิบายแทน) ใช้เป็น override/internal helper เท่านั้น
12.9 Compose / Charts / Knowledge packs (in SDK)
3 endpoint นี้ตอนนี้ มีใน native SDK ทั้ง 4 ภาษาแล้ว (2026-06-01). ตัวอย่าง curl ด้านล่างไว้สำหรับ debug / integration test — production code ใช้ SDK method ตรงๆ ได้เลย.
POST /v1/compose — Orchestrated write+judge+revise
KoishiAI / AInovel ใช้ pipeline นี้.
/v1/smartเป็น OpenAI-compat wrapper รอบมัน — ใช้smart()ก็พอสำหรับ 95% ของ use case. ใช้compose()เมื่อต้อง control writer/editor/fallback model split, score threshold, หรือ knowledge pack injection โดยตรง.
SDK methods:
- Python
client.compose(messages=[...], writer_model=..., editor_model=..., max_revisions=2, knowledge_packs=[...]) - Node
await client.compose(messages, { writerModel, maxRevisions, knowledgePacks }) - .NET
await client.ComposeAsync(messages, writerModel: ..., maxRevisions: 2) - Java
client.compose(messages, writerModel, editorModel, ...)
Raw HTTP:
curl -X POST https://gateway.kiatdamrong.shop/v1/compose \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: $TENANT" \
-H "Content-Type: application/json" \
-d '{
"messages": [{"role":"user","content":"เขียนบทความ active learning ป.4"}],
"max_revisions": 3,
"editor_score_threshold": 80
}'
Response มี OpenAI-shape choices + compose.trace แสดงทุก step ของ pipeline.
POST /v1/charts/render — Render PNG / SVG / Vega-Lite
ChartSpec รองรับ 6 ประเภท: bar, line, scatter, histogram, boxplot, pie. Data field ขึ้นกับ type:
bar/pie→categories: {label: value}scatter/line→points: [{x, y}]histogram→values: [float]boxplot→valuesหรือgroups: {name: [float]}
SDK methods:
- Python
client.charts_render(chart={"type":"bar","categories":{...}}, format="svg") - Node
await client.chartsRender({ type:"bar", categories: {...} }, { format:"svg" }) - .NET
await client.ChartsRenderAsync(chart, format: "svg", width: 300) - Java
client.chartsRender(chart, "svg", 300, 200, 100)
Raw HTTP:
curl -X POST https://gateway.kiatdamrong.shop/v1/charts/render \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"chart": {"type":"bar","title":"Demo","categories":{"A":3,"B":5,"C":2}},
"format": "svg",
"width": 300, "height": 200
}'
Returns {format, chart_type, width, height, render_ms, image_base64|svg|vega_lite_spec}. PNG ~15-50KB base64, SVG ~5-20KB.
GET /v1/knowledge/packs — List knowledge packs
SDK methods:
- Python
client.list_knowledge_packs() - Node
await client.listKnowledgePacks() - .NET
await client.ListKnowledgePacksAsync() - Java
client.listKnowledgePacks()
Returns {"object": "list", "data": [{pack_id, version, curator, languages, description, ...}, ...]}. ใช้ list ก่อนเรียก knowledge_query(pack_id, query).
Demo task endpoints (tags: Demo Tasks)
/v1/analyze-csv, /v1/cohorts/analyze, /v1/exam — สำหรับ sales demo
ในประเทศไทย, ไม่ stable contract. ใช้ผ่าน /try playground หรือทาง
HTTP, แต่อย่า build production บนของพวกนี้.
12.10 Tier system — paid backends (Phase 1 shipped 2026-06-01)
ลูกค้าสามารถ opt-in ใช้ commercial AI provider สำหรับ judge step เพื่อ
แลก ความเร็ว + ความนิ่ง กับค่าใช้จ่ายต่อ token. เรียกใช้โดยส่ง
tier field ใน request body. Gateway เก็บ token count + per-step
breakdown ให้ในทุก response → ลูกค้าคำนวณค่าใช้จ่ายฝั่งระบบของตัวเอง.
4 tiers ที่ใช้ได้
| Tier | Judge backend | p95 typical | Cost (upstream rates) | PDPA classes |
|---|---|---|---|---|
standard (default, ฟรี) |
cloud-fast (Ollama Cloud GPT-OSS 120B) | 3-32s variance | ฿0 | ทุก class |
premium |
Gemini 2.5 Flash | 2-5s reliable | ~$0.075/M input + $0.30/M output | NONE · REDACTABLE |
frontier |
Claude Haiku 4.5 | 1.7-4s reliable | ~$0.80/M input + $4/M output | NONE only |
auto (Phase 4 — TBD) |
gateway decides | varies | ≤ premium | varies |
Writer ยังเป็น Typhoon 2.5 local เสมอ (Thai SOTA, PDPA-safe). tier แก้แค่ judge step.
วิธีเรียกใช้
# default (ฟรี, เดิม)
curl -X POST $BASE/v1/wizard/polish \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: $TENANT" \
-H "Content-Type: application/json" \
-d '{"text":"..."}'
# premium — Gemini Flash judge
curl -X POST $BASE/v1/wizard/polish \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: $TENANT" \
-H "Content-Type: application/json" \
-d '{"text":"...","tier":"premium"}'
# frontier — Claude Haiku judge (เร็วสุด)
curl -X POST $BASE/v1/wizard/polish \
-d '{"text":"...","tier":"frontier"}'
ใช้ได้ที่: /v1/wizard/polish, /v1/wizard/fact-check, /v1/wizard/grounded-summarize, /v1/smart.
Response headers (observability)
| Header | ความหมาย |
|---|---|
X-Gateway-Tier-Requested |
tier ที่ลูกค้าส่งมา |
X-Gateway-Tier-Used |
tier ที่ระบบใช้จริง (อาจถูก downgrade) |
X-Gateway-Tier-Downgraded-Reason |
(มีเฉพาะตอน downgrade) e.g. pdpa_pinned |
X-Gateway-Compose-Judge |
model ที่ judge ใช้จริง (e.g. frontier-gemini) |
X-Gateway-Compose-Judge-Reason |
เหตุผลในการเลือก judge |
X-Gateway-Compose-Judge-Tools |
on ถ้า judge ใช้ tools (paid tiers ก็ใช้ได้) |
Response body — usage field สำหรับคิดเงิน
{
"polished_text": "...",
"compose": {...},
"usage": {
"prompt_tokens": 2184,
"completion_tokens": 1329,
"total_tokens": 3513,
"per_step": [
{"public_id":"local-writer-th","tier":"local",
"prompt_tokens":962,"completion_tokens":255,"total_tokens":1217},
{"public_id":"frontier-gemini","tier":"frontier",
"prompt_tokens":1222,"completion_tokens":1074,"total_tokens":2296}
]
}
}
ลูกค้าใช้ per_step ดูว่า backend ตัวไหนเผา token เท่าไร แล้วเอาไปคูณกับ rate ของ provider นั้น → คิดค่าใช้จ่ายแล้วเก็บจาก user ของลูกค้าเอง.
PDPA — auto-downgrade (ไม่ใช่ silent)
ถ้าลูกค้าส่ง tier=premium แต่ classification = REDACTABLE/SENSITIVE → ระบบจะ pin judge ที่ LOCAL อัตโนมัติพร้อมส่ง:
X-Gateway-Tier-Requested: premium
X-Gateway-Tier-Used: standard
X-Gateway-Tier-Downgraded-Reason: pdpa_pinned
X-Gateway-Compose-Judge: local-editor
X-Gateway-Compose-Judge-Reason: local (downgraded from premium: pdpa_pinned)
→ ลูกค้าตรวจ header ใน middleware แล้วตัดสินใจว่า:
- ยอมรับ downgrade + คิดราคา standard
- ตอบลูกค้าว่า request นี้ใช้ premium ไม่ได้เพราะมี PII
- retry หลัง redact ฝั่งลูกค้าเอง
Smart Engine tools ทำงานกับทุก tier
นี่คือ value-add ที่ผู้เรียก Gemini/Claude API ตรงไม่ได้ — เมื่อใช้ judge_tools=true หรือเรียก /v1/wizard/fact-check, paid judges ยังเข้าถึง tool ecosystem ของเรา ได้ครบ:
| Tool | ทำอะไร |
|---|---|
lookup_knowledge(query, pack_id) |
hybrid search บน curated Thai knowledge packs (BGE-M3) |
web_search(query, n) |
private SearXNG ของเรา |
read_url(url) |
fetch + clean (PDPA-safe SSRF guard + Thai ำ-repair) |
ตัวอย่าง — Gemini Flash judge ground เนื้อหา factcheck ผ่าน knowledge pack จริง:
curl -X POST $BASE/v1/wizard/fact-check \
-d '{"claim":"...","pack_id":"thai-edu","tier":"premium"}'
# Response headers:
# X-Gateway-Compose-Judge: frontier-gemini
# X-Gateway-Compose-Judge-Tools: on ← Gemini เรียก tools ของเราจริง
ลูกค้าได้ทั้งความเร็วของ commercial API + ความถูกต้องของ grounded retrieval — ทำเองที่ฝั่งลูกค้าจะต้อง wire ทุก provider ทุก tool เอง.
Integration recipe
# ลูกค้าตัดสินใจฝั่งเขาเองว่าจะ pay tier ไหน
tier = "premium" if user.has_premium_plan else "standard"
resp = httpx.post(f"{BASE}/v1/wizard/polish",
headers={"Authorization": f"Bearer {TOKEN}", "X-Tenant-Id": TENANT},
json={"text": user_input, "tier": tier},
timeout=180.0).json()
# คิดค่าใช้จ่ายจาก usage.per_step
paid_step = next((s for s in resp["usage"]["per_step"]
if s["tier"] == "frontier"), None)
if paid_step:
upstream_cost_usd = (
paid_step["prompt_tokens"] * GEMINI_INPUT_USD_PER_M / 1e6 +
paid_step["completion_tokens"] * GEMINI_OUTPUT_USD_PER_M / 1e6
)
charge_user(upstream_cost_usd * MARKUP) # ลูกค้า bill ผู้ใช้ของตัวเอง
return resp["polished_text"]
Gateway ไม่จัดการ billing/cap/quota ฝั่งเรา — เป็น infrastructure provider. ลูกค้าใช้
usage.per_stepคำนวณ + คิดเงิน + บังคับ quota ในระบบของเขาเอง.
12.11 PDPA Auto-Cut — ใช้ cloud โดยไม่ส่ง PII ออกนอก (shipped 2026-06-10)
ทุก request ที่ classify เป็น REDACTABLE (พบ PII เช่น ชื่อ, เบอร์, อีเมล,
เลขบัตร, ที่อยู่) gateway จะ ตัด PII เป็น placeholder ก่อนส่ง cloud
แล้ว คืนค่าจริงให้ในคำตอบ อัตโนมัติ — cloud ไม่เคยเห็นข้อมูลจริง:
"ผมชื่อ นายสมชาย ใจดี เบอร์ 081-234-5678"
→ cloud เห็น: "ผมชื่อ [[PII_PERSON_1]] เบอร์ [[PII_PHONE_1]]"
→ ลูกค้าได้คำตอบที่มีชื่อจริงกลับมาตามปกติ
ตรวจจับ 3 ชั้น: regex (เลขบัตร/เบอร์/อีเมล) + Thai heuristic (คำนำหน้าชื่อ/คำบ่งชี้ที่อยู่) + ThaiNER ML (ชื่อไม่มีคำนำหน้า). กฎเหล็ก: ถ้า placeholder หลุดมาในคำตอบ → gateway ไม่ส่งคำตอบนั้น (502).
Response headers ที่อ่านได้ทุก request:
| Header | ความหมาย |
|---|---|
X-Gateway-PII-Class |
NONE / REDACTABLE / SENSITIVE |
X-Gateway-PII-Counts |
เช่น PII_PERSON=2,PII_PHONE=1 |
X-Gateway-Redacted-Tokens |
จำนวน placeholder ที่ใช้ |
X-Gateway-Redaction-Layers |
regex,heuristic-th,ml-thainer |
X-Gateway-Redaction-Ms |
เวลาที่ใช้ตัด (ms) |
ใช้ headers เหล่านี้แสดง badge "🛡️ Auto-protected" ใน UI ของคุณได้เลย — ดูตัวอย่างจริงที่ lms-demo.kiatdamrong.shop (โหมด 🛡️ Auto-Cut PDPA / 🚀🛡️ PDPA Pro).
SENSITIVE (เช่น ข้อมูลสุขภาพ) ไม่ออก cloud เลย — ปักหมุดรันใน local model.
12.12 Document Intelligence — เอกสาร 1000+ หน้า → รายงาน/สไลด์ (shipped 2026-06-11)
ชุดความสามารถใหม่ 4 ตัวที่ทำงานต่อกันเป็นสาย: Corpus → Longform →
Export (+ตั้งเวลารันประจำ) — ออกแบบมาเพื่องาน batch ขนาดใหญ่ที่
read_file (cap 60K chars) รับไม่ไหว
Benchmark จริง (PDF 1,000 หน้า / 1.07M ตัวอักษรไทย — production)
| ขั้น | เวลาวัดจริง |
|---|---|
| Ingest (334 chunks + PII profile) | 16.1 วินาที |
| Longform map-reduce (384 nodes, local 100%) | 93.7 นาที · ฿0 |
| Export DOCX + PPTX | ~2 วินาที |
| โดน kill กลางทางแล้ว resume เอง | overhead ~31 วินาที |
1) Corpus — คลังเอกสารต่อโปรเจค
# สร้าง corpus
curl -X POST $BASE/v1/corpus -H "$AUTH" -H "Content-Type: application/json" \
-d '{"name": "งานวิจัยปี 2569"}'
# → {"id": "abc123...", ...}
# อัปโหลดเอกสาร (PDF/DOCX/TXT — ไม่จำกัดหน้า, ทีละไฟล์ ซ้ำได้หลายไฟล์)
curl -X POST $BASE/v1/corpus/{id}/documents -H "$AUTH" \
-F "file=@report_1000pages.pdf"
# → {"pages": 1000, "chunks": 334, "pii_chunks": 91,
# "corpus": {"pii_profile": {"pii_share": 0.27,
# "recommended_pii_mode": "autocut_cloud"}}}
# ค้นหา (explicit tool-call — ไม่มี silent RAG)
curl -X POST $BASE/v1/corpus/{id}/query -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{"query": "ผลการศึกษาด้านต้นทุน", "top_k": 8}'
# → hits พร้อม doc + page_start/page_end (ใช้อ้างอิงเลขหน้าได้)
ระบบตรวจ PII ตั้งแต่ตอน ingest แล้วแนะนำ pii_mode ที่เหมาะ —
การตัดสินใจขั้นสุดท้ายเป็นของคุณเสมอ
2) Longform — สรุป/เรียบเรียงทั้ง corpus (resumable)
curl -X POST $BASE/v1/longform -H "$AUTH" -H "Content-Type: application/json" \
-d '{"corpus_id": "{id}",
"instruction": "สรุปประเด็นหลักพร้อมอ้างอิงเลขหน้า",
"pii_mode": "local_only"}'
# → {"job_id": "job-...", "plan": {"chunks": 334, "total_nodes": 384}}
# ติดตามความคืบหน้า real-time
curl $BASE/v1/jobs/{job_id} -H "$AUTH"
# → {"status": "running", "progress": {"stage": "map",
# "nodes_done": 120, "nodes_total": 384}}
pii_mode 3 โหมด (เลือกต่องาน):
| โหมด | พฤติกรรม | เหมาะกับ |
|---|---|---|
local_only |
ทุก node รันในเครื่อง — ข้อมูลไม่ออกเลย, ฿0 | เอกสารลับ/PII สูง |
autocut_cloud |
map=local · reduce/final=cloud ผ่านการตัด PII (cloud เห็นแค่ [[PII_*]] placeholder) |
สมดุลเร็ว+ปลอดภัย |
none |
ใช้ model ที่เลือกทุก node | เอกสารสาธารณะ |
ถ้า corpus มี PII สูงแต่ส่ง pii_mode=none → ระบบปฏิเสธ (400) พร้อมคำแนะนำ
Resume หลัง crash/restart: ระบบ respawn เองตอน boot (อัตโนมัติ) หรือสั่ง
ผ่าน API: ส่ง resume_job_id ใน POST เดิม — node ที่เสร็จแล้วคืนจาก
checkpoint cache ฟรี จ่ายเฉพาะส่วนที่เหลือ
3) Export — ออกไฟล์พร้อมใช้
# จาก job ที่เสร็จแล้ว → DOCX/PPTX/MD โดยตรง
curl -G "$BASE/v1/jobs/{job_id}/export" -H "$AUTH" \
--data-urlencode "format=docx" \
--data-urlencode "title=รายงานวิจัย" -o report.docx
# หรือจาก markdown ใดๆ
curl -X POST $BASE/v1/export -H "$AUTH" -H "Content-Type: application/json" \
-d '{"markdown": "# รายงาน...", "format": "pptx", "title": "นำเสนอ"}' \
-o slides.pptx
DOCX: A4 ฟอนต์ TH Sarabun New + ปก + เลขหน้า + ตาราง · PPTX: 16:9, H1/H2 = สไลด์ใหม่ — พร้อมพิมพ์/นำเสนอทันที
4) Schedules — รันประจำอัตโนมัติ
curl -X POST $BASE/v1/longform/schedules -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{"corpus_id": "{id}", "instruction": "สรุปรายสัปดาห์",
"pii_mode": "local_only", "every_hours": 168}'
# GET /v1/longform/schedules · DELETE /v1/longform/schedules/{sid}
SDK note: ความสามารถชุดนี้เรียกผ่าน HTTP ตรงได้ทันทีตามตัวอย่างบน — native SDK methods กำลังตามมา (Python ก่อน)
13. Support
- Technical: [email protected] (Mon-Fri 9-17 ICT)
- Emergency P0: LINE @kiat (24/7 best-effort, founder)
- Status page: https://gateway.kiatdamrong.shop/status
- Swagger /docs: https://gateway.kiatdamrong.shop/docs
- Admin dashboard (Owner only): https://gateway.kiatdamrong.shop/admin/tenants
14. Compliance Artifacts
ขอได้ผ่าน [email protected]:
- Sub-processor list (
SUBPROCESSORS.md) - Incident response plan (
INCIDENT_RESPONSE.md) - API versioning policy (
API_VERSIONING.md) - Acceptable Use Policy + Terms (
AUP_TERMS.md) — pending legal review - Data Processing Agreement (DPA) template — pending legal review