ผมเบื่อที่จะเขียน AI Agent Code ซ้ำๆ ทุกโปรเจกต์ — เลยสร้าง Boilerplate ที่ใช้งานได้จริง
ทุกครั้งที่เริ่มโปรเจกต์ AI Agent ใหม่ ผมต้องเขียน code เดิมซ้ำๆ
Tool use loop. Memory persistence. Retry logic. Structured logging.
ซ้ำแล้วซ้ำเล่า. Copy-paste จากโปรเจกต์เก่า. แก้ชื่อตัวแปร. หวังว่าจะไม่เกิด bug ใหม่.
คุณเคยเป็นแบบนี้ไหม?
ผมเลยหยุดทำแบบนั้น — และแพ็กทุกอย่างลงใน TypeScript boilerplate ที่สะอาดและใช้งานได้จริง ในบทความนี้ผมจะอธิบายว่ามันทำงานอย่างไร เพื่อให้คุณสามารถนำไปใช้หรือสร้างเองได้เลย
ปัญหาของ Tutorial AI Agent ทั่วไป
Tutorial ส่วนใหญ่สอนแค่นี้:
const response = await anthropic.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.content);
โอเคสำหรับ demo แต่พอจะทำอะไรจริงๆ คุณจะเจอปัญหา:
-
Tool calls คืออะไร? Claude ไม่ได้ return แค่ text — มันส่ง
tool_useblocks ที่คุณต้องรัน และส่งผลกลับไป - Multi-turn ทำยังไง? Agent จริงต้อง loop จนกว่างานจะเสร็จ ไม่ใช่แค่ request/response เดียว
- API ล้มเหลวทำยังไง? Rate limits, timeout, network error — Tutorial ไม่มีใครสอนเรื่องนี้
- Memory จะเก็บยังไง? Agent ลืมทุกอย่างทันทีที่ process restart
ผมเบื่อที่จะแก้ปัญหาเดิมๆ ทุกโปรเจกต์ นี่คือวิธีที่ผมแก้ครั้งเดียวและใช้ได้ตลอด
Core Agent Loop
นี่คือหัวใจของทุกอย่าง Tutorial ส่วนใหญ่ทำผิดตรงนี้:
async function runAgent(task: string, config: AgentConfig): Promise<void> {
const messages: MessageParam[] = [
{ role: "user", content: task }
];
for (let turn = 0; turn < config.maxTurns; turn++) {
const response = await retryWithBackoff(() =>
anthropic.messages.create({
model: config.model,
max_tokens: config.maxTokens,
tools: getToolDefinitions(config.tools),
messages,
})
);
logger.info(`Turn ${turn + 1}`, {
stopReason: response.stop_reason,
inputTokens: response.usage.input_tokens,
outputTokens: response.usage.output_tokens,
});
const toolResults: ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type === "text") {
logger.info("Agent:", { text: block.text });
} else if (block.type === "tool_use") {
logger.info("Tool call:", { name: block.name, input: block.input });
const result = await executeToolCall(block, config.tools);
logger.info("Tool result:", { name: block.name, result });
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: result,
});
}
}
messages.push({ role: "assistant", content: response.content });
if (toolResults.length > 0) {
messages.push({ role: "user", content: toolResults });
}
if (response.stop_reason === "end_turn" && toolResults.length === 0) {
logger.info("Agent ทำงานเสร็จแล้ว");
return;
}
}
logger.warn("ถึง max turns แล้วแต่ยังไม่เสร็จ");
}
สิ่งที่ handle ที่ Tutorial ข้ามไป:
- Tool calls หลายตัวใน turn เดียว — Claude เรียก 3 tools พร้อมกันได้ คุณต้องรันทั้งหมดและส่งผลกลับพร้อมกัน
-
Mixed content — Response มีทั้ง text และ tool calls ต้อง loop ผ่าน
response.contentทั้งหมด -
โครงสร้าง turn ที่ถูกต้อง — Tool results ต้องอยู่ใน
usermessage ไม่ใช่assistant -
เงื่อนไขหยุด — หยุดเมื่อ
stop_reason === "end_turn"และไม่มี tool calls ค้างอยู่
Retry Logic ที่ใช้งานได้จริง
API calls ล้มเหลวเสมอ Rate limits, network blips, timeout — Agent คุณจะเจอแน่นอน:
const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 504];
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
const isRetryable =
error instanceof Anthropic.APIError &&
RETRYABLE_STATUS_CODES.includes(error.status);
if (!isRetryable || attempt === maxRetries) {
throw error;
}
const delay = baseDelayMs * Math.pow(2, attempt);
const jitter = Math.random() * 200;
await sleep(delay + jitter);
}
}
throw lastError!;
}
Exponential backoff พร้อม jitter — retry บน 429 (rate limit), 500, 502, 503, 504 แต่ fail fast บน 400, 401, 403 เพราะนั่นคือ bug ของคุณ ไม่ใช่ error ชั่วคราว
Persistent Memory
Agent จริงต้องมี memory นี่คือ file-based store ที่เรียบง่าย:
export class MemoryStore {
private data: Record<string, unknown> = {};
private filePath: string;
constructor(filePath: string) {
this.filePath = filePath;
this.load();
}
private load(): void {
try {
const raw = readFileSync(this.filePath, "utf-8");
this.data = JSON.parse(raw);
} catch {
this.data = {};
}
}
private save(): void {
writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
}
set(key: string, value: unknown): void {
this.data[key] = value;
this.save();
}
get(key: string): unknown {
return this.data[key] ?? null;
}
keys(): string[] {
return Object.keys(this.data);
}
}
เรียบง่าย อ่านจาก disk ตอน init เขียนทุกครั้งที่มีการเปลี่ยนแปลง รอด process restart สำหรับ production คุณอาจ swap เป็น Redis หรือ Postgres แต่นี่เพียงพอสำหรับเริ่มต้นโดยไม่ต้องมี dependencies เพิ่ม
Tool Registry Pattern
การเพิ่ม tool ควรทำได้ง่ายมาก นี่คือ pattern ที่ทำให้ใช้แค่ 10 บรรทัด:
export interface ToolHandler {
definition: Tool;
execute: (input: Record<string, unknown>) => Promise<string>;
}
// เพิ่ม tool ใหม่แค่นี้:
export const myTool: ToolHandler = {
definition: {
name: "my_tool",
description: "อธิบายให้ชัดเจน Claude จะอ่านตรงนี้",
input_schema: {
type: "object",
properties: {
input: { type: "string", description: "Input ที่รับเข้ามา" },
},
required: ["input"],
},
},
execute: async (input) => {
return `ผลลัพธ์: ${input["input"]}`;
},
};
Register ตอนสร้าง agent:
runAgent(task, {
tools: { my_tool: myTool, ...defaultTools },
});
แค่นั้นเอง
ทดสอบรันจริง
npm start "คำนวณดอกเบี้ยทบต้นของ 100,000 บาท ที่ 7% เป็นเวลา 10 ปี และบันทึกผลลัพธ์ลงใน memory"
Output:
{"timestamp":"2026-02-27T04:00:00.000Z","level":"INFO","message":"Turn 1","data":{"stopReason":"tool_use","inputTokens":512,"outputTokens":128}}
{"timestamp":"2026-02-27T04:00:00.001Z","level":"INFO","message":"Tool call:","data":{"name":"calculator","input":{"expression":"100000 * (1 + 0.07)^10"}}}
{"timestamp":"2026-02-27T04:00:00.002Z","level":"INFO","message":"Tool result:","data":{"name":"calculator","result":"196715.14"}}
{"timestamp":"2026-02-27T04:00:00.100Z","level":"INFO","message":"Tool call:","data":{"name":"memory_write","input":{"key":"compound_interest","value":196715.14}}}
{"timestamp":"2026-02-27T04:00:00.101Z","level":"INFO","message":"Agent ทำงานเสร็จแล้ว"}
ดาวน์โหลด Boilerplate เต็ม
ถ้าคุณต้องการ codebase ที่พร้อมรัน — ทุกอย่างที่เขียนไว้ข้างบน เชื่อมต่อกันหมดแล้ว TypeScript configure แล้ว มี examples ให้ครบ .env.example พร้อมใช้:
AI Agent Boilerplate บน Gumroad →
$39 (ประมาณ 1,400 บาท). MIT license. Clone ได้เลย เป็นเจ้าของ 100% ใช้ทำ production ได้เลย
หรือจะสร้างเองจากบทความนี้ก็ได้ — ทุกอย่างที่จำเป็นมีครบที่นี่แล้ว
สรุป
หยุดเขียน scaffolding เดิมซ้ำๆ แล้วไปสร้างของจริงดีกว่า
มีคำถามอะไรคอมเมนต์ไว้เลยนะครับ ยินดีช่วยเสมอ 🙏
Top comments (0)