$ cat structured-outputs-tool-calling-llms.mdx
LLM
ការទាញយក JSON ដែលអាចទុកចិត្តបានពី LLM
ក្នុងអត្ថបទនេះ
ការទាញយក JSON ដែលអាចទុកចិត្តបានពី LLM
នៅពេលអ្នកបោះបង់ការជជែកផ្តេសផ្តាស (chat) ហើយងាកមក ប្រើប្រាស់ ទិន្នន័យទាញយករបស់ម៉ូដែល AI ដើម្បីបម្រើនៅក្នុងកូដ - ឧទាហរណ៍ដូចជា បំពេញ form, កាច់ចង្កូតសំបុត្រ (route a ticket), ឬទាញយកទិន្នន័យពីវិក័យបត្រ - អ្នកច្បាស់ជាត្រូវការទិន្នន័យដែលមានរចនាសម្ព័ន្ធ (structured data)។ ហើយរឿងដំបូងដែលអ្នករាល់គ្នាសាកល្បងគឺ ការស្នើរសុំតាម prompt ដូចខាងក្រោម:
"សូមឆ្លើយនៅក្នុងទម្រង់ JSON:
{ "sentiment": "...", "score": ... }"
វាអាចដំណើរការបានប្រហែល 90% វាពិតជាបញ្ហាមួយទៅវិញ។ ចំណែក 10% ទៀតវាអាចចេញនូវ markdown fence ការពារបិទបើក, អក្សរព្រាង Sure! Here's the JSON: , ក្បៀសនៅចុងកន្ទុយ, ឫតម្លៃដែលវាចេះតែថែមយកខ្លួនឯង (hallucinated extra field) - ហើយចំណុចមួយៗនេះធ្វើអោយ JSON.parse គាំងដំណើរការ (crash) ភ្លាម។ នៅកម្រិតប្រើប្រាស់ធំទូលាយ, ការទាញយកបានតែ 90% គឺជាការរកឆ្លងភ្លើងឆេះ។
មានវិធីមួយល្អជាងនេះ ដែលបែងចែកជាបីវគ្គ។
1. ប្រើប្រាស់ Tool calling ជាជាងប្រើការសន្ទនា
គ្រប់ក្រុមហ៊ុនផ្តល់សេវាកម្មម៉ូដែល AI សំខាន់ៗបច្ចុប្បន្ន តែងគាំទ្រ tool calling (ឫទូទៅហៅថា function calling): ដោយគ្រាន់តែអ្នកគូសបញ្ជាក់ពីមុខងារបញ្ជាជាមួយនឹងជើង JSON Schema មួយ, រួចម៉ូដែលនឹងហុចត្រលប់មកវិញនូវចម្លើយនៅក្នុងទម្រង់នោះ - ដែលទទួលបានជាទិន្នន័យរចនាសម្ព័ន្ធ (structured data) មិនមែនអត្ថបទដែលវាទុកក្នុងប្រអប់ការសន្ទនាទេ។ បែបនេះវានឹងជួយបញ្ចៀសពីបញ្ហា "ការបកប្រែពាក្យធម្មតា" (parse the prose) ទាំងស្រុង, ព្រោះក្រុមហ៊ុនបានផ្តល់លក្ខខណ្ឌយ៉ាងជាក់លាក់ថាចម្លើយវានឹងត្រូវតាមទម្រង់។
ល្បិចនេះគឺថាអ្នកមិនចាំបាច់ត្រូវការ tool ដើម្បី ធ្វើអ្វី នោះទេ។ គ្រាន់តែបង្កើត tool មួយដែលតួនាទីរបស់វាគឺគូសបញ្ជាក់រូបរាងនៃចម្លើយរបស់អ្នកប៉ុណ្ណោះ:
const tools = [
{
name: "record_analysis",
description: "Record the sentiment analysis of a message.",
input_schema: {
type: "object",
properties: {
sentiment: {
type: "string",
enum: ["positive", "neutral", "negative"],
},
score: { type: "number", minimum: 0, maximum: 1 },
reason: { type: "string" },
},
required: ["sentiment", "score", "reason"],
},
},
];បន្ទាប់មក បង្ខំអោយម៉ូដែលហៅវាទាល់តែបាន:
const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 512,
tools,
tool_choice: { type: "tool", name: "record_analysis" },
messages: [{ role: "user", content: text }],
});
const block = response.content.find((b) => b.type === "tool_use");
const data = block?.input; // វាចេញមកជា object ស្រាប់ មិនត្រូវការ JSON.parse ទេtool_choice គឺជួរកូដសំខាន់ណាស់។ បើគ្មានវា ម៉ូដែល អាច ហៅ tool មួយនេះ ប៉ុន្តែបើមានវា, វាតម្រូវអោយហៅ។ enum និងការចង minimum/maximum លក្ខខណ្ឌកម្រិតកំណត់ក៏បានធ្វើតួនាទីជាពត៌មានឯកសារដែលម៉ូដែលនឹងអានផងដែរ - ក្របខ័ណ្ឌការងារយ៉ាងល្អប្រសើរដែលជួយជំរុញគុណភាពពត៌មានដែលឆ្លើយត្រលប់មកវិញយ៉ាងរលូន។
2. រកការផ្ទៀងផ្ទាត់ (Validate) ដដែល
schema ចងរចនាសម្ព័ន្ធពិតមែន ប៉ុន្តែវាមិនអាចបង្គាប់នូវ កម្មវត្ថុមុខជំនួញ (business rules) របស់អ្នកបានទេ - ដូចជាការបញ្ជាក់ថាកាលបរិច្ឆេទទៅអតីតកាល តម្លៃទំនិញស្មើនឹងតម្លៃបូកបញ្ចូល ឬលទ្ធផល enum ក្នុងទម្រង់ដែលត្រឹមត្រូវ។ ដូច្នេះសូមផ្ទៀងផ្ទាត់លទ្ធផលជាមួយនឹង schema ពិតប្រាកដរបស់អ្នក។ ខ្ញុំប្រើប្រាស់ Zod ជាទូទៅ:
import { z } from "zod";
const Analysis = z.object({
sentiment: z.enum(["positive", "neutral", "negative"]),
score: z.number().min(0).max(1),
reason: z.string().min(1),
});
const result = Analysis.safeParse(data);ឥឡូវនេះអ្នកនឹងមានប្រភេទ object ប្រាកដ និង ការអះអាងថាវាឆ្លើយត្រូវតាមលក្ខខណ្ឌរបស់អ្នក - ឫវាអាចរាយនូវបញ្ជីប្រភេទពន្យល់ពីកំហុសខុសឆ្គង។
3. ជួសជុល មិនមែនស្មានដដែលៗទេ (Repair, don't retry blindly)
នៅពេល validation បរាជ័យ ការគិតងាយៗគឺជាការទាញយកដំណើរការនេះម្ដងទៀត ហើយរំពឹងថាវាត្រូវ។ អ្វីដែលប្រសើរជាង: បង្ហាញពីលទ្ធផលមានបញ្ហានោះទៅកាន់ម៉ូដែល រួមផ្សំនឹងពន្យល់ពីបញ្ហា validation ហើយប្រាប់កំហុសឲ្យវាជួសជុលវិញ។ ម៉ូដែលគឺឆ្លាតវៃក្នុងការរកដំណោះស្រាយចំពោះកំហុសដែលវាអាចមើលឃើញបាន។
async function extract(text: string, attempts = 2) {
let messages = [{ role: "user", content: text }];
for (let i = 0; i < attempts; i++) {
const data = await callTool(messages);
const parsed = Analysis.safeParse(data);
if (parsed.success) return parsed.data;
messages = [
...messages,
{ role: "assistant", content: JSON.stringify(data) },
{
role: "user",
content: `ផ្ទៀងផ្ទាត់មិនជោគជ័យទេ: ${parsed.error.message}។ សូមកែតម្រូវវា។`,
},
];
}
throw new Error("មិនអាចទទួលបានចម្លើយដែលត្រឹមត្រូវទេ");
}នៅលើការអនុវត្តពិតប្រាកដ រង្វិលជុំ (repair loop) មួយអាចដោះស្រាយកំហុសបានស្ទើរតែទាំងស្រុង ដែលជួយឲ្យកម្មវិធីចៀសវាងការស្មានបញ្ហាដដែលៗរហូតដល់គាំង។
កិច្ចការបន្ថែម
- ទាញយកវាលពីរបីមែនទេ? Tool calling តែឯងភាគច្រើនគឺគ្រប់គ្រាន់។
- បញ្ចូលទៅ database ឬប្រព័ន្ធ billing មែនទេ? បន្ថែម Zod validation - តម្លៃនៃ row ខុសមួយខ្ពស់ជាងការត្រួតពិនិត្យបន្ថែមមួយដង។
- បរិមាណខ្ពស់ ឬ input រញ៉េរញ៉ៃមែនទេ? បន្ថែម repair loop; វាបម្លែងកំហុសបណ្តោះអាសន្នទៅជាដំណើរការដែលអាចជួសជុលខ្លួនឯងបាន។
ចំណុចសំខាន់ៗដែលទទួលបាន
- Prompt-and-parse មានភាពជឿជាក់ប្រហែល 90% ដែលមានន័យថាវានឹងបរាជ័យ។ កុំសាងសង់ប្រព័ន្ធលើវា។
- Tool calling ជាមួយ
tool_choiceផ្តល់ទិន្នន័យមានរចនាសម្ព័ន្ធតាំងពីដំបូង - មិនចាំបាច់ parse prose ទេ។ - Schema ដែលលម្អិតក៏ជា prompt មួយដែរ - enum និង bounds ជួយបង្កើនគុណភាពចម្លើយ មិនមែនតែរូបរាងទិន្នន័យទេ។
- Validate business rules ដែល schema មិនអាចបង្ហាញបាន ជាមួយឧបករណ៍ដូចជា Zod។
- ជួសជុលដោយប្រើកំហុសជាក់លាក់ ជំនួសឱ្យការសាកល្បងឡើងវិញដោយងងឹត; កំណត់ចំនួនដងសាកល្បងជានិច្ច។
ចាត់ទុកម៉ូដែលដូចជា input ដែលមិនទុកចិត្ត - ព្រោះវាគឺជាអ្វីនោះឯង - នោះ structured output នឹងលែងជាការភ្នាល់ទៀតហើយ។