# AI Gateway TH — Quick Start (Thai)

> สำหรับ Wewasanad + integrators · v0.11+
> Last updated: 2026-05-31
> Contact: admin@kiatdamrong.shop

---

## 1. รับ Bearer Token

**Production endpoint**: `https://gateway.kiatdamrong.shop`

ติดต่อ admin@kiatdamrong.shop พร้อมระบุ:
- ชื่อทีม + 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)

```bash
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

```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

```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)

```csharp
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):

```csharp
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+)

```java
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`:
```yaml
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.md`
- `sdks/node/README.md`
- `sdks/dotnet/README.md`
- `sdks/java/README.md`
- `sdks/README.md` — overview ของทั้ง 4 ภาษา
- `sdks/PUBLISHING.md` — checklist สำหรับ publish PyPI/npm/NuGet/Maven (Kiat ใช้รัน)

---

## 3. List Models (`/v1/models`)

```bash
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), เร็วสำหรับ Thai
- `cloud` → ฟรี-ish, **ข้อมูลออกนอกประเทศ** (PII auto-redact ก่อนส่ง)
- `frontier` → per-token cost, **ข้อมูลออกนอกประเทศ** (PII auto-redact ก่อนส่ง)

---

## 4. Chat Completions (OpenAI-compat)

### curl

```bash
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)

```python
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

```javascript
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)

```php
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

```bash
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)

```bash
# 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`)

```python
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 รอบ)

```bash
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`:

```bash
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>` — เก็บไว้สำหรับ trace
- `X-Gateway-Audit-Url: /v1/audit/<id>` — ดู trail ของ request นี้
- `X-Gateway-PII-Class: NONE|REDACTABLE|SENSITIVE` — classification
- `X-Gateway-Tenant-Id: <id>` — ยืนยัน tenant
- `X-Gateway-Rate-Limit-Remaining: <int>` — quota ที่เหลือ (per-minute)

ดู audit ของ request:
```bash
curl https://gateway.kiatdamrong.shop/v1/audit/<request_id> \
  -H "Authorization: Bearer <TOKEN>"
```

---

## 10. Error Handling

ทุก error คืน OpenAI-shape `{"error":{...}}`:

```json
{
  "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 แนะนำ

```python
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` ตามจำนวนที่ขอ.
```bash
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.
```bash
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):**
```bash
curl -X POST .../v1/tools/read_file \
  -H "Authorization: Bearer <TOKEN>" \
  -H "X-Tenant-Id: <TENANT_ID>" \
  -F "file=@report.pdf" \
  -F "max_chars=30000"
```

Response:
```json
{
  "kind": "pdf",         // pdf | docx | xlsx | csv | text | unknown
  "chars": 12345,
  "content": "...ข้อความที่ดึงออกมา...",
  "filename": "report.pdf"
}
```

| Format | Extractor | หมายเหตุ |
|---|---|---|
| **PDF** | 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
# 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 ไม่กระเทือน.

ตัวอย่าง:
```json
POST /v1/smart
{
  "messages":[{"role":"user","content":"สังเคราะห์แนวทาง AL คณิต ป.4"}],
  "writer_grounding": true,
  "judge_tools": true,
  "stream": true
}
```

Response มี `smart.writer_grounding`:
```json
{
  "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+:**
```python
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):**
```ts
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: 3`
- `X-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` — ขัดเกลาภาษาไทยให้เป็นเนทีฟ
```bash
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`
```bash
curl -X POST .../v1/wizard/fact-check \
  -d '{"claim":"แผนการสอน...","pack_id":"pedagogy-th"}'
```
Tooled judge — verdict + grounded issues + citation. **ใช้ตอน:** ตรวจแผนสอน /
บทความ / ข้ออ้าง.

### `POST /v1/wizard/grounded-summarize`
```bash
curl -X POST .../v1/wizard/grounded-summarize \
  -d '{"question":"สรุปแนวทาง AL การคูณ ป.4","urls":["https://..."]}'
```
Grounded writer + citation verifier. **ใช้ตอน:** สังเคราะห์งานวิจัย /
สรุปเอกสารหลายแหล่งให้ผู้บริหารอ่าน.

### `POST /v1/wizard/shorten` — ตัดให้สั้นโดยรักษาใจความ
```bash
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` — แปลเข้าไทยแบบเนทีฟ
```bash
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` — ซ่อมสะกด/วรรค/การันต์
```bash
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](/try) สำหรับลองทุก wizard สด.

---

## 12.8 Knowledge Flywheel (admin) — ขยาย pack จากเว็บจริง

`/v1/packs/<pack_id>/draft-from-url` → review → approve → ingest. ทุก chunk
ใน pack จะมี **source URL + fetched_at** = ตรวจสอบที่มาได้ตลอด.

```bash
# 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:
```bash
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:
```bash
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.

### วิธีเรียกใช้

```bash
# 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 สำหรับคิดเงิน

```json
{
  "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 จริง:

```bash
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

```python
# ลูกค้าตัดสินใจฝั่งเขาเองว่าจะ 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](https://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 — คลังเอกสารต่อโปรเจค

```bash
# สร้าง 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)

```bash
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 — ออกไฟล์พร้อมใช้

```bash
# จาก 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 — รันประจำอัตโนมัติ

```bash
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**: admin@kiatdamrong.shop (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

ขอได้ผ่าน admin@kiatdamrong.shop:
- 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
