ข้าม​ไป​ยัง​เนื้อหา

การ​เรียก model ครั้ง​แรก

บท​ที่​แล้ว​เรา​วาง​กรอบ​ว่า LLM คือ function text → token → text ที่​คุณ​เรียก​ผ่าน HTTPS บท​นี้​เรา​ลงมือ เรียก​จริง — และ​เรา​จะ​เรียก​ด้วย​มือเปล่า ไม่​ใช้ SDK ไม่​ใช้ library บัง มี​แค่ HttpClient กับ System.Text.Json ที่​คุณ​ใช้​อยู่​ทุก​วัน เหตุผล​ที่​เริ่ม​จากดิบๆ แบบ​นี้​เพราะ​ทั้ง​คอร์ส​สร้าง​อยู่​บน primitive ตัว​เดียว: การ​ส่ง JSON ก้อน​หนึ่ง​ไป​ที่ endpoint หนึ่ง แล้ว​อ่าน JSON ที่​ตอบ​กลับ​มา ทุก SDK ทุก abstraction ในบทหลังๆ ก็​แค่​ห่อ​สิ่ง​นี้​ไว้ ถ้า​คุณ​เห็น​ทุก byte ตรง​นี้​ชัด บท​ที่​เหลือ​จะ​ไม่มี​อะไร​เป็นกล่อง​ดำ​อีก​เลย

📦 code ตัวอย่าง

คอร์ส​นี้​ค่อยๆ ประกอบ agent สำหรับ support ลูกค้า​บน domain Order ของบริการฟู้ด​เดลิ​เวอรี จาก repo kaen-food-ordering (กำลัง​จัด​ทำ) เป็น​ตัวอย่าง​เดิน​เรื่อง​ตลอด​คอร์ส — บท​นี้​คือ​การ​เรียก API ครั้ง​แรก​ด้วย​มือ สิ่ง​ที่​คุณ​เห็น​ใน​บท​นี้​คือ​ราก​ที่​ทุก​บท​หลัง​จาก​นี้​ยืน​อยู่

ปลายทาง​มี​ตัว​เดียว: POST https://api.anthropic.com/v1/messages คุณ​ส่ง JSON เข้าไป​ใน​เนื้อ request แล้ว​รับ JSON กลับ​มา จบ ส่วน​ที่​พลาด​กัน​บ่อย​ที่สุด​ไม่ใช่ body แต่​เป็น header — ต้อง​ส่ง​ให้​ครบ​สาม​ตัว​และ​ต้อง ตรง​ตัว:

  • x-api-key — key ของ​คุณ (อ่าน​จาก environment variable ANTHROPIC_API_KEY)
  • anthropic-version — ต้อง​เป็น 2023-06-01
  • content-typeapplication/json
⚠️ 2 header ที่​คน​เขียน​ใหม่​พลาด​บ่อย​ที่สุด

หนึ่ง — header key คือ x-api-key ไม่ใช่ Authorization: Bearer สาย Bearer ใช้​กับ OAuth token เท่านั้น การ​เรียก​ด้วย API key ตรงๆ ใช้ x-api-key: $ANTHROPIC_API_KEY ถ้า​คุณ​เผลอ​เอา pattern Authorization: Bearer ที่​คุ้น​จาก API อื่น​มา​ใช้ จะ​ได้ 401 กลับ​มา

สอง — anthropic-version: 2023-06-01 คือ​ค่า​ปัจจุบัน​และ​เป็น​ค่า​เดียว​ที่​ยัง​ไม่​ถูก deprecate ไม่มี version ที่​ใหม่​กว่า​นี้ และ​ต้อง​ส่ง​ไป​กับ ทุก request ไม่ใช่​ตั้ง​ครั้ง​เดียว วัน​ที่​นี้​ไม่ใช่ “version เก่า” ที่​ต้อง​อัปเกรด — มัน​คือ​ค่าที่​ถูกต้อง ณ ตอน​นี้

เนื้อ request เป็น JSON ก้อน​เล็กๆ ที่​มี4 field หลัก:

{
"model": "claude-opus-4-8",
"max_tokens": 1024,
"system": "You are a helpful assistant.",
"messages": [ { "role": "user", "content": "Hello, Claude!" } ]
}

แกะ​ที​ละ field:

  • model — id ของ model ใช้ alias เปล่าๆ อย่าง claude-opus-4-8 (ตัวอย่าง ณ 2026-07 — id เลื่อน​ไหว​ตาม​เวลา) อย่า​เติม​วัน​ที่​ต่อ​ท้าย alias พวก​นี้
  • max_tokens — เพดาน​จำนวน TokenTokenหน่วย​ย่อย​ที่สุด​ที่ model ประมวล​ผล อาจ​เทียบเท่า​คำ ส่วน​ของ​คำ ตัว​อักษร หรือ byte ก็ได้ — ข้อความ​ทุก​ชิ้น​ถูก​แปลง​เป็น​ชุด token ก่อน​เข้า model สำหรับ Claude 1 token ราว 3.5 ตัว​อักษร​อังกฤษ (แปร​ตาม​ภาษา​ที่​ใช้) จำนวน token คือ​ทั้ง​หน่วย​วัด​ขนาด​และ​หน่วย​คิด​เงิน (ดู Token Cost) — อย่า​เดา token ของ Claude ด้วย tiktoken ของ OpenAI เพราะ​นับ​ต่ำ​กว่า​จริงArchitecture ที่ model สร้าง​ได้​ใน​คำ​ตอบ​นี้ (จำนวน​หน่วย​ข้อความ ไม่ใช่​จำนวน​ตัว​อักษร) เป็น field บังคับ ถ้า​คำ​ตอบ​ยาว​กว่า​นี้​มัน​จะ​ถูก​ตัด
  • system — คำ​สั่ง​กำกับ​บทบาท/กติกา​ของ model จุด​ที่​ต้อง​จำ​ให้​ขึ้นใจ: system เป็น field ระดับ​บน​สุด​ของ request ไม่ใช่​ข้อความ​ที่​มี role: "system" ใน array messages (นี่​ต่าง​จาก API ของ​เจ้า​อื่น​ที่​คุณ​อาจ​เคย​เห็น) เรื่อง​เนื้อหา​ใน system จะ​เจาะ​ใน​บท​ที่ 3
  • messages — array ของ MessageMessageหน่วย​หนึ่ง​เทิร์นของ​บทสนทนา ประกอบ​ด้วย role (`user` หรือ `assistant`) กับ content — Messages API รับ `messages[]` ที่​เรียง​สลับ​กัน​ไป และ​ตัว API เป็น​แบบ stateless คือ​ต้อง​ส่ง​ประวัติ​ทั้ง​ชุด​กลับ​ไป​ใหม่​ทุก​ครั้ง หมายเหตุ: ใน API ดิบ​ไม่มี role ชื่อ `system` (มัน​เป็น field ระดับ​บน​สุด​แยก​ต่างหาก) และ​ไม่มี role `tool` — ผล​ของ tool ถูก​ส่ง​กลับ​ใน​ข้อความ role `user`Architecture คือ​บทสนทนา​ทั้งหมด แต่ละ​ชิ้น​มี role ("user" หรือ "assistant") กับ content ประวัติ​สนทนา​ทั้งหมด​อยู่​ใน array นี้

จุด​ที่​วิศวกร .NET ต้อง​ปรับ​มุมมอง​คือ​ข้อ​สุดท้าย: Messages API ไม่มี state ฝั่ง server model ไม่​ได้ “จำ” รอบ​ก่อน​ไว้​ให้ ทุก​ครั้ง​ที่​เรียก คุณ​ต้อง​ส่ง array messages ทั้ง​ชุด​ไป​ใหม่​หมด บทสนทนา​ต่อ​เนื่อง​เกิด​จาก​การ​ที่ คุณ สะสม message แล้ว​ส่ง​ทั้ง​กอง​ไป​ทุก​ครั้ง — role user คือ​สิ่ง​ที่​ผู้​ใช้​พูด role assistant คือ​คำ​ตอบ​ของ model ที่​คุณ​เก็บ​ไว้​ต่อ​ท้าย เรื่อง​นี้​จะ​กลาย​เป็น​หัวใจ​ของ loop ใน​บท​ที่ 6

นี่​คือ​จุด​ที่​คน​เขียน​ใหม่​สะดุด​แรง​ที่สุด response ที่​ตอบ​กลับ​มา​หน้าตา​แบบ​นี้:

{
"id": "msg_013Zva2CMHLNnXjNJJKqJ2EF",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-8",
"content": [ { "type": "text", "text": "Hello! How can I help you today?" } ],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": { "input_tokens": 14, "output_tokens": 11 }
}

สังเกต​ว่า ไม่มี field .text ที่​ระดับ​บน​สุด แม้​จะ​เป็น​คำ​ตอบ​ข้อความ​ธรรมดา​ที่สุด มัน​ก็​มา​ใน​รูป content ที่​เป็น array ของ block แต่ละ block มี type ของ​ตัวเอง (ตรง​นี้​เป็น "text") คุณ​ต้อง​วน array content แล้ว​เช็ค type ของ​แต่ละ block เพื่อ​ดึง​ข้อความ​ออก​มา จะ​ไป​หยิบ .text ตรงๆ จาก root ไม่​ได้

ทำไม​ต้อง​ยุ่ง​ขนาด​นี้​ใน​เมื่อ​คำ​ตอบ​มี block เดียว? เพราะ​โครงสร้าง array นี้​คือ​ราก​ที่​รองรับ​ของ​ที่​ใหญ่​กว่า text — ใน​บท​ที่ 4 คำ​ตอบ​จะ​มี​ทั้ง block text และ block tool_use ปน​กัน​ใน array เดียว ถ้า​คุณ​คุ้น​กับ​การวน content ตั้งแต่​ตอน​นี้ tool use จะ​ไม่ใช่​เรื่อง​ใหม่​เลย มัน​แค่​มี type เพิ่ม​ขึ้น​อีก​แบบ

ทุก response มี field Stop ReasonStop Reasonfield `stop_reason` ใน​ทุก​คำ​ตอบ​ที่​บอกว่า model หยุด​เพราะ​อะไร — `end_turn` = ตอบ​จบ​เอง​ตาม​ธรรมชาติ, `tool_use` = model ขอ​เรียก tool (สัญญาณ​ให้​แตก​กิ่ง​ไป​รัน tool), `max_tokens` = ชน​เพดาน​ที่​ตั้ง​ไว้ (คำ​ตอบ​อาจ​ถูก​ตัด) — เป็น​สัญญาณ​ควบคุม​หลัก​ของ Agent Loop ว่า​จะ​วน​ต่อ​หรือ​หยุดArchitecturestop_reason — บอกว่า model หยุด​สร้าง​ข้อความ​เพราะ​อะไร ใน​บท​นี้​คุณ​จะ​เจอ​ค่า "end_turn" ซึ่ง​แปล​ว่า “Claude finished its response naturally” — model พูด​จบ​ตาม​ธรรมชาติ​ของ​มัน​เอง

ค่า​นี้​ดูเหมือน​ข้อมูล​ประกอบ​เฉยๆ ใน​ตอน​นี้ แต่​มัน​คือ สัญญาณ​ควบคุม loop ใน​อนาคต ค่า​อื่น​ที่​จะ​สำคัญ​คือ "tool_use" (model ขอ​เรียก tool — บท​ที่ 4) และ "max_tokens" (คำ​ตอบ​ชน​เพดาน​จน​ถูก​ตัด — บท​ที่ 7) การ​อ่าน stop_reason ทุก​ครั้ง​แล้ว​แตก​กิ่ง​ตาม​ค่า​ของ​มัน คือ​กลไก​ที่​ทำให้การ​เรียก​ครั้ง​เดียว​กลาย​เป็น agent ที่​วน​ได้​ใน​บท​ที่ 6 บท​นี้​แค่​ปัก​หมุด​ว่า field นี้​มี​อยู่​และ​ต้อง​อ่าน​มัน​เสมอ

field usage รายงาน​ว่าการ​เรียก​นี้​ใช้ token ไป​เท่าไร — input_tokens คือ token ของ​สิ่ง​ที่​คุณ​ส่ง​เข้าไป output_tokens คือ token ของ​คำ​ตอบ นี่​คือ​หน่วย​ที่​ผูก​กับ​ทั้ง​ขนาด​และ​ค่า​ใช้​จ่าย​โดยตรง (ทวน​จาก​บท​ที่ 1: token คือ​ทั้ง​ไม้บรรทัด​วัด​ขนาด​และ​มิเตอร์​คิด​เงิน)

มี​รายละเอียด​ที่​ต้อง​ระวัง​ตอน​คำนวณ​ต้นทุน​จริง: ขนาด prompt ทั้งหมด​ไม่​ได้​เท่ากับ input_tokens เพียง​ตัว​เดียว แต่​เท่ากับ input_tokens + cache_creation_input_tokens + cache_read_input_tokensinput_tokens เดี่ยวๆ นับ​เฉพาะ​ส่วน​ที่​ไม่​ได้​มา​จาก cache เท่านั้น ตอน​นี้​จำ​แค่​ว่า usage คือ​แหล่ง​ความ​จริง​เรื่อง​ต้นทุน และ​ใน​บท​ที่ 6 จะ​เห็น​ว่า​ทุกรอบ​ของ loop ส่ง​ประวัติ​ทั้ง​กอง​ไป​ใหม่ ทำให้ token โต​ขึ้น​เรื่อยๆ ทุกรอบ — งบ token จึง​กลาย​เป็น​หนึ่ง​ใน​เงื่อนไข​หยุด​ของ loop เอง

รวม​ทุก​อย่าง​ข้าง​บน​เป็น code ที่​รัน​ได้​จริง สังเกต​ว่า​ไม่มี temperature ใน​นี้ — และ​นั่น​ตั้งใจ (ทวน​จาก​บท​ที่ 1: ส่ง temperature/top_p/top_k ไป​กับ model รุ่น​ล่าสุด​จะ​ได้ HTTP 400)

using System.Net.Http.Json;
using System.Text.Json;
var http = new HttpClient { BaseAddress = new Uri("https://api.anthropic.com/") };
http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
http.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
var body = new {
model = "claude-opus-4-8", // ตัวอย่าง ณ 2026-07 — อย่าเติมวันที่ต่อท้าย
max_tokens = 1024,
system = "You are a helpful assistant.",
messages = new[] { new { role = "user", content = "Hello, Claude!" } }
}; // ไม่มี temperature — 400 บน model รุ่นล่าสุด
using var resp = await http.PostAsJsonAsync("v1/messages", body); // ตั้ง content-type: application/json ให้เอง
resp.EnsureSuccessStatusCode();
using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync());
var root = doc.RootElement;
string? stopReason = root.GetProperty("stop_reason").GetString();
foreach (var block in root.GetProperty("content").EnumerateArray())
if (block.GetProperty("type").GetString() == "text")
Console.WriteLine(block.GetProperty("text").GetString()); // อ่านจาก content[] — ไม่มี .text ระดับบนสุด
var usage = root.GetProperty("usage");
Console.WriteLine($"หยุดเพราะ: {stopReason} | " +
$"in={usage.GetProperty("input_tokens").GetInt32()} " +
$"out={usage.GetProperty("output_tokens").GetInt32()} token");

ทุก​บรรทัด​ที่​ดู​จุกจิก​ใน​นี้​ล้วน​มี​เหตุผล​ตาม​ที่​อธิบาย​มา: x-api-key ไม่ใช่ Bearer, anthropic-version ส่ง​ทุก​ครั้ง, PostAsJsonAsync ตั้ง content-type ให้​เอง, วน content แทน​การ​หยิบ .text, และ​ไม่มี temperature เลย นี่​คือ primitive ทั้งหมด​ของ​คอร์ส​นี้​ใน code หน้า​เดียว

flowchart TB
  subgraph REQ["request ที่คุณส่ง"]
    H["header: x-api-key<br/>anthropic-version 2023-06-01<br/>content-type json"]
    B["body: model, max_tokens<br/>system, messages[]<br/>role user/assistant"]
  end
  REQ -->|POST /v1/messages| API["Anthropic<br/>Messages API"]
  API -->|JSON ตอบกลับ| RES["response"]
  subgraph RES["response ที่ได้กลับ"]
    C["content[] — array ของ block<br/>วนเช็ค type แล้วอ่าน text"]
    S["stop_reason<br/>เช่น end_turn"]
    U["usage<br/>input/output token"]
  end
  classDef hot fill:#ea580c,stroke:#7c2d12,color:#f8fafc;
  class C hot;

คำ​บรรยาย​ภาพ: ซ้าย/บน — request ที่​คุณ​ประกอบ​เอง มี2 header ที่​พลาด​บ่อย (x-api-key ไม่ใช่ Bearer และ anthropic-version: 2023-06-01) กับ body ที่​มี messages[] แยก role user/assistant ยิง​ไป​ที่ POST /v1/messages ขวา/ล่าง — response ที่​ตอบ​กลับ​มา​มี​สาม​ส่วน​ที่​ต้อง​อ่าน: content[] (จุด​สี​ส้ม — เป็น array ของ block คุณ​ต้อง​วน​เช็ค type แล้ว​ดึง text ไม่มี .text ที่​ระดับ​บน​สุด), stop_reason ที่​บอกว่า model หยุด​เพราะ​อะไร และ usage ที่​นับ token เข้า-ออก

คุณ​เรียก Messages API ด้วย​มือเปล่า​เป็น​แล้ว — ประกอบ3 header ส่ง messages[] ที่​แยก role และ​อ่าน​คำ​ตอบ​จาก content[] พร้อม stop_reason และ usage ทุก​อย่าง​หลัง​จาก​นี้​คือ​การ​ต่อยอด​จาก primitive ก้อน​นี้ บท​ที่ 3 จะ​เจาะ field system ที่​เรา​ปล่อย​ผ่าน​ไป​ใน​บท​นี้ — วิธี​ตั้ง​บทบาท​และ​กติกา​ให้ model ด้วย​โครงสร้าง แล้ว​บท​ที่ 4 จะ​เพิ่ม block tool_use เข้าไป​ใน array content เดิม​ที่​คุณ​คุ้น​แล้ว เพื่อ​ให้ model เรียก code C# ของ​คุณ​ได้​จริง


🔗 อ้างอิง​ต้นทาง​ของ​บท​นี้

บท​นี้​อ้างอิง​จาก​เอกสาร​ทางการ​ของ Anthropic สาม​หน้า อ่าน​ต่อ​ได้ที่​ต้นทาง​โดยตรง:

  • Anthropic, Messages API reference — endpoint POST /v1/messages, header ที่​ต้อง​ส่ง, รูปร่าง body (model/max_tokens/system/messages[]) และ response (content[] เป็น array ไม่มี .text ระดับ​บน​สุด, usage) เข้าถึง 2026-07-19
  • Anthropic, Handling stop reasons — ความหมาย​ของ stop_reason เช่น end_turn (“Claude finished its response naturally”) ใน​ฐานะ​สัญญาณ​ควบคุม loop เข้าถึง 2026-07-19
  • Anthropic, API versioninganthropic-version: 2023-06-01 เป็น​ค่า​ปัจจุบัน​และ​ค่า​เดียว​ที่​ยัง​ไม่​ถูก deprecate ต้อง​ส่ง​ทุก request เข้าถึง 2026-07-19

เช็กความเข้าใจ — บทที่ 2

ข้อ 1 / 3

เมื่อเรียก Messages API ด้วยมือด้วย API key ควรตั้ง header key อย่างไร?