การดู : 111

26/08/2026 03:00น.

ภาพหน้าปกบทความ Golang The Series EP.172 หัวข้อ Function Calling สอน AI ให้เรียกใช้ Function ใน Go

Golang The Series EP.172: Function Calling สอน AI เรียกใช้ Go Function

#Golang AI

#Function Calling Go

#Go OpenAI API

#สอนเขียน AI Agent

#SuperDev Academy

ยินดีต้อนรับเข้าสู่ EP.172 ครับ! ในตอนที่แล้ว เราได้ทำความเข้าใจปูพื้นฐานแนวคิดของ AI Agent และวงลูป ReAct กันไปแล้ว ในตอนนี้เราจะมาลงมือปฏิบัติตามรากฐานที่สำคัญที่สุดของการสร้าง Agent นั่นคือ "Function Calling" (หรือ Tool Calling)

Function Calling คือกลไกที่ช่วยให้โมเดล AI (LLM) สามารถโต้ตอบและลงมือทำแอ็กชันกับโลกภายนอกได้ โดยแทนที่ AI จะตอบกลับมาเป็นข้อความแชตธรรมดา AI จะทำการวิเคราะห์บริบทคำถาม แล้วคายโครงสร้างข้อมูล JSON ที่ระบุ "ชื่อฟังก์ชัน" และ "อาร์กิวเมนต์ (Arguments)" ออกมาอย่างแม่นยำ เพื่อสั่งให้ระบบ Go Backend ของเรานำค่าเหล่านั้นไปรันฟังก์ชันจริงครับ!

ลำดับขั้นตอนการทำงานของ Function Calling

Plaintext

[1. User Request] ──> "เช็กยอดสต็อกของสินค้า SKU-1024 ให้หน่อย"
        │
        ▼
[2. Go App Call LLM]  <--- แนบโครงสร้าง Tool Definition (JSON Schema) ไปด้วย
        │
        ▼
[3. LLM Processing]   <--- AI เลือกฟังก์ชัน 'GetStockQuantity' และแกะ SKU
        │
        ▼
[4. LLM Response]     <--- คาย Tool Calls (Name: "GetStockQuantity", Args: {"sku": "SKU-1024"})
        │
        ▼
[5. Go App Execution] <--- Go อ่านชื่อ Tool -> รันฟังก์ชัน GetStockQuantity("SKU-1024") จริง
        │
        ▼
[6. Return Tool Result to LLM] <--- ส่งผลลัพธ์ (เช่น "150 ชิ้น") กลับไปให้ LLM
        │
        ▼
[7. Final AI Answer]  ──> "ขณะนี้สินค้า SKU-1024 มีสต็อกคงเหลืออยู่ในระบบทั้งหมด 150 ชิ้นครับ"

การลงทะเบียน Tool Schema ในภาษา Go

ในการแจ้งให้ AI รู้จักฟังก์ชันใน Go ของเรา เราต้องนิยามโครงสร้าง JSON Schema ของฟังก์ชันนั้นๆ เพื่อบอก AI ว่า:

  • Name: ฟังก์ชันนี้ชื่ออะไร (ต้องสอดคล้องกับ Identifier ใน Go)

  • Description: ฟังก์ชันนี้มีหน้าที่ทำอะไร (สำคัญมากเพราะ AI ใช้ข้อความนี้ประเมินว่าจะเลือกใช้ Tool ตัวไหน)

  • Parameters: พารามิเตอร์ที่ต้องรับคืออะไร ชนิดข้อมูลเป็น Type ไหน และ Field ไหนที่จำเป็นต้องมี (Required)

ลงมือเขียนโค้ด Go เพื่อทำ Function Calling

เพื่อให้เห็นภาพและอ่านง่าย เราจะแบ่งโค้ดออกเป็น 4 ส่วนหลักๆ ตามสเต็ปการทำงานจริงครับ

ส่วนที่ 1: เตรียมฟังก์ชันของจริงใน Go (Native Function)

เริ่มจากการเขียน Business Logic ปกติของเราก่อน สมมติว่าเป็นฟังก์ชันเช็กสต็อกสินค้าในคลัง

Go

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/sashabaranov/go-openai"
	"github.com/sashabaranov/go-openai/jsonschema"
)

// --- 1. นิยามฟังก์ชันจริงในภาษา Go (Native Functions) ---

// GetStockQuantity ฟังก์ชันดึงสต็อกสินค้าจาก Database
func GetStockQuantity(sku string) (int, error) {
	// ในระบบจริง: อาจจะเป็นการยิง SQL Query ไปที่ Database
	stocks := map[string]int{
		"SKU-1024": 150,
		"SKU-2048": 0,
		"SKU-9999": 42,
	}
	qty, exists := stocks[sku]
	if !exists {
		return 0, fmt.Errorf("ไม่พบรหัสสินค้า %s ในระบบ", sku)
	}
	return qty, nil
}

ส่วนที่ 2: นิยาม Schema และยิง Request ไปหา AI

ต่อมาเราจะสร้าง openai.Tool เพื่ออธิบายให้ AI ฟังว่าฟังก์ชันเราหน้าตาเป็นอย่างไร แล้วแนบไปพร้อมกับคำถามของ User

Go

func main() {
	client := openai.NewClient("YOUR_OPENAI_API_KEY")

	// --- 2. สร้าง Tool Definition เพื่อลงทะเบียนแจ้ง AI ---
	stockTool := openai.Tool{
		Type: openai.ToolTypeFunction,
		Function: &openai.FunctionDefinition{
			Name:        "GetStockQuantity",
			Description: "ดึงจำนวนสินค้าคงเหลือในคลังจากรหัส SKU",
			Parameters: jsonschema.Definition{
				Type: jsonschema.Object,
				Properties: map[string]jsonschema.Definition{
					"sku": {
						Type:        jsonschema.String,
						Description: "รหัส SKU ของสินค้า เช่น SKU-1024",
					},
				},
				Required: []string{"sku"}, // บังคับว่า AI ต้องแกะค่านี้มาให้ได้
			},
		},
	}

	userQuery := "อยากรู้ว่าตอนนี้สินค้า SKU-1024 เหลืออยู่กี่ชิ้นครับ?"
	fmt.Printf("👤 User: %s\n\n", userQuery)

	messages := []openai.ChatCompletionMessage{
		{Role: openai.ChatMessageRoleUser, Content: userQuery},
	}

	// --- 3. ยิง Request ครั้งแรกพร้อมแนบ Tools ไปด้วย ---
	resp, err := client.CreateChatCompletion(
		context.Background(),
		openai.ChatCompletionRequest{
			Model:    openai.GPT4oMini,
			Messages: messages,
			Tools:    []openai.Tool{stockTool},
		},
	)
	if err != nil {
		log.Fatalf("API Error: %v", err)
	}

	msg := resp.Choices[0].Message

ส่วนที่ 3: รับการตัดสินใจจาก AI และนำมารัน Go Function

เมื่อ AI ตอบกลับมา เราต้องเช็กว่า AI เลือกใช้ Tool หรือไม่ ถ้าใช้ เราก็เอา Arguments ที่ AI คายออกมาไปแปลงเป็น Struct แล้วรันฟังก์ชันของเรา

Go

	// --- 4. ตรวจสอบว่า AI ต้องการเรียกใช้ Tool หรือไม่ ---
	if len(msg.ToolCalls) > 0 {
		toolCall := msg.ToolCalls[0]
		fmt.Printf("🤖 AI ตัดสินใจเรียกใช้ Tool: %s\n", toolCall.Function.Name)
		fmt.Printf("📦 Arguments ที่ AI แกะมาได้: %s\n", toolCall.Function.Arguments)

		// ปาร์ส Arguments จาก JSON string ที่ AI คายออกมา
		var args struct {
			SKU string `json:"sku"`
		}
		if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args); err != nil {
			log.Fatalf("Failed to parse tool arguments: %v", err)
		}

		// --- 5. รันฟังก์ชันใน Go จริง ---
		var toolResult string
		if toolCall.Function.Name == "GetStockQuantity" {
			qty, err := GetStockQuantity(args.SKU)
			if err != nil {
				toolResult = fmt.Sprintf(`{"error": "%s"}`, err.Error())
			} else {
				// ห่อผลลัพธ์เป็น JSON เตรียมส่งกลับ
				toolResult = fmt.Sprintf(`{"sku": "%s", "quantity": %d}`, args.SKU, qty)
			}
		}

		fmt.Printf("⚙️ ผลลัพธ์จากการรัน Go Function: %s\n\n", toolResult)

ส่วนที่ 4: ส่งผลลัพธ์กลับไปให้ AI สรุปคำตอบสุดท้าย

นี่คือสเต็ปสุดท้ายที่ขาดไม่ได้! เราต้องส่ง History ที่ AI คิด (Assistant Message) ตามด้วยผลลัพธ์จาก Go (Tool Message) กลับไปให้ AI เรียบเรียงเป็นภาษามนุษย์

Go

		// --- 6. ส่งผลลัพธ์ของ Tool กลับไปให้ AI ประมวลผล ---
		messages = append(messages, msg) // เก็บ History ความคิดของ AI ไว้ใน Context
		messages = append(messages, openai.ChatCompletionMessage{
			Role:       openai.ChatMessageRoleTool, // ระบุว่าเป็น Role แบบ Tool
			Content:    toolResult,
			ToolCallID: toolCall.ID, // ต้องตรงกับ ID ที่ AI ส่งมาตอนแรก
		})

		finalResp, err := client.CreateChatCompletion(
			context.Background(),
			openai.ChatCompletionRequest{
				Model:    openai.GPT4oMini,
				Messages: messages,
			},
		)
		if err != nil {
			log.Fatalf("API Error on Final Response: %v", err)
		}

		// 7. ได้คำตอบสุดท้ายที่สรุปจากข้อมูลในระบบเรา!
		fmt.Printf("🤖 AI คำตอบสุดท้าย: %s\n", finalResp.Choices[0].Message.Content)

	} else {
		// กรณี AI ตอบได้ทันทีโดยไม่ต้องใช้ Tool
		fmt.Printf("🤖 AI คำตอบทั่วไป: %s\n", msg.Content)
	}
}

ข้อควรระวังและการทำ Guardrails ในการรัน Function

  • Never Trust AI Arguments Blindly: ข้อมูลพารามิเตอร์ที่ AI คายออกมา (เช่น args.SKU) จำเป็นต้องผ่านการ Validate ใน Go เสมอก่อนนำไปทำ Database Query หรือยิงสั่งการระบบอื่นๆ เพื่อป้องกันปัญหา SQL Injection หรือการสั่งการที่ผิดพลาด

  • Type Safety Handling: บางครั้ง AI อาจจะคายตัวเลขมาในรูปแบบ String (เช่น "1024") การใช้วิธี json.Unmarshal เข้า Struct ใน Go ช่วยรับประกันได้ว่าชนิดข้อมูลจะถูกแปลงกลับเป็น Type ที่ถูกต้องเสมอก่อนนำเข้า Business Logic

🎯 ท้าให้ลอง (Daily Mission)

ลองนำโค้ดด้านบนไปรันในโปรเจกต์ของคุณแล้วสังเกตการทำงานของ Tool Calls ใน Console

การบ้านชวนคิด: หากในระบบของคุณมีฟังก์ชัน CalculateDiscount(customerTier string, totalAmount float64) เพิ่มเข้ามาเป็น Tool ที่สอง คุณจะเขียนส่วนคัดแยก Routing ของชื่อฟังก์ชัน (switch toolCall.Function.Name หรือใช้ Map pattern map[string]func(...)) ในภาษา Go อย่างไร ให้สามารถรองรับการเพิ่ม Tools ใหม่ๆ นับสิบตัวได้อย่างสะอาด ยืดหยุ่น และอ่านง่ายโดยไม่ต้องใช้ if-else ต่อกันยาวๆ? ลองออกแบบโครงสร้างกันดูนะครับ!

❓ คำถามที่พบบ่อย (FAQ)

เราสามารถแนบ Tool ไปพร้อมกันหลายๆ ตัวได้ไหม?

ได้แน่นอนครับ! คุณสามารถใส่ Schema หลายๆ ตัวลงไปใน Slice []openai.Tool{} ได้เลย AI จะวิเคราะห์จากบริบทคำถามเองว่าควรหยิบ Tool ตัวไหนมาใช้ (หรืออาจจะใช้หลายตัวพร้อมกันในคราวเดียวแบบ Parallel Tool Calling ก็ได้)

ถ้า AI ให้ Argument มาผิดรูปแบบจะทำอย่างไร?

ในภาษา Go เมื่อ json.Unmarshal แล้วพัง ให้จับ Error นั้นจับยัดใส่ toolResult แล้วส่งกลับไปให้ AI รับรู้ (เช่น คืนค่ากลับไปว่า "Invalid parameter format, please provide numbers only") AI จะรับรู้ความผิดพลาดและพยายามคายค่าที่ถูกต้องกลับมาให้ใหม่ในลูปถัดไปครับ

ทำไมต้องส่ง messages = append(messages, msg) (ข้อความของ Assistant) กลับไปด้วย?

เป็นข้อบังคับของ OpenAI API ครับ หากมีการเรียกใช้ Tool เราต้องส่งประวัติ "ความคิด/การตัดสินใจ" ของ AI ควบคู่ไปกับ "ผลลัพธ์ของ Tool" เสมอ เพื่อให้ AI จำ Context ได้ว่าตัวเองเพิ่งสั่งให้ทำอะไรไป


สรุป

ในตอนนี้เราได้เรียนรู้เรื่อง กลไกการทำงานของ Function Calling แบบ Step-by-step ตั้งแต่การนิยาม JSON Schema ให้ AI เข้าใจ, การรับ Tool Call, ไปจนถึงการยัดผลลัพธ์กลับไปให้ AI สรุปความ ซึ่งนี่คือหัวใจสำคัญในการสร้าง Agent ให้สามารถเชื่อมต่อกับโลกภายนอกได้จริง

ในตอนต่อไป (EP.173): เมื่อเราเข้าใจกลไก Function Calling ในภาษา Go เรียบร้อยแล้ว ในตอนหน้าเราจะขยับขึ้นไปสร้าง AI Agent ที่ทรงพลังและใช้งานจริงในองค์กรมากที่สุดตัวหนึ่ง นั่นคือ "Database Agent: สร้าง AI ที่เขียน SQL ไปดึงข้อมูลมาตอบเองได้" เราจะมาดูวิธีสอน AI ให้เข้าใจ Schema ของฐานข้อมูล แล้วแปลงคำถามภาษาธรรมชาติไปเป็น SQL Query เพื่อนำผลลัพธ์มาสรุปให้ผู้ใช้โดยปลอดภัย ห้ามพลาดครับ Gophers!

Follow Superdev Academy on all platforms: