Building AI Agent with ADK

Go Meetup 2026 @ Arise by Infinitas

20 มี.ค. 2026 — ADK Go 1.0: GA
30 มิ.ย. 2026 — ADK Go 2.0: graph workflow, Task API

Pallat Anchaleechamaikorn

ดูตัวจริงกันก่อน

(สลับไป terminal — live demo)

go run . console

ADK คืออะไร?

Framework จาก Google สำหรับสร้าง stateful AI agent

แบบ code-first (ไม่ใช่ low-code / drag-drop)

มีให้เลือก 4 ภาษา: Python · Java · Go · TypeScript

Google ADK (Go)

เหมาะกับ: enterprise ที่ flow ซับซ้อนขึ้นเรื่อยๆ — ต้อง testable, version-control ได้, integrate เข้า SDLC เดิม

A2A Protocol — Multi-Agent ข้ามระบบ

Agent คนละ process / คนละ vendor คุยกันผ่าน HTTP มาตรฐานเดียวกัน (Google, Apache-2.0, ตอนนี้อยู่ใต้ Linux Foundation)

// ฝั่ง root agent — เรียก remote agent เหมือน local agent
remoteAgent, _ := remoteagent.NewA2A(remoteagent.A2AConfig{
    Name:            "prime_agent",
    AgentCardSource: "http://localhost:8001",
})

llmagent.New(llmagent.Config{
    Name:      "root_agent",
    SubAgents: []agent.Agent{rollAgent, remoteAgent}, // local + remote ปนกันได้
})

ต่างจาก MCP: MCP = agent → tool (data/action), A2A = agent → agent (peer ที่มี capability ของตัวเอง)

4 Core Concepts

ADK Core Concepts Merged Diagram

1. Agents — 3 Types

Workflow Agent

LLM Agent

Custom Agent

2 – 4. Tools / Sessions / Callbacks

Sessions

Tools

Callbacks

Agent Type 1: LLM Agent (สมอง)

1. เราเขียนฟังก์ชัน → 2. โยนเข้า Context → 3. LLM เรียก Tool → 4. ผลลัพธ์ส่งกลับไปที่ LLM

LLM Agent — สร้าง Tool (ADK Go v2)

type SearchTimeArgs struct {
	Timezone string `json:"timezone" jsonschema:"the IANA timezone name"`
}

type SearchTimeResult struct {
	Time string `json:"time"`
}

// A plain Go function exposed to the LLM as a callable tool.
func searchTime(ctx agent.Context, args SearchTimeArgs) (SearchTimeResult, error) {
	return SearchTimeResult{Time: "10:30 " + args.Timezone}, nil
}

searchTimeTool, err := functiontool.New(functiontool.Config{
	Name:        "search_time",
	Description: "Returns the current time for a given IANA timezone.",
}, searchTime)

LLM Agent — ประกอบ Agent (ADK Go v2)

geminiModel, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})

assistant, err := llmagent.New(llmagent.Config{
	Name:        "assistant_agent",
	Model:       geminiModel, // model.LLM, ไม่ใช่ string
	Instruction: "You are a helpful assistant. Answer concisely in Thai.",
	Tools:       []tool.Tool{searchTimeTool},
})

Agent Type 2: Workflow Agent (ผู้จัดการ)

ไม่ใช้ LLM ตัดสินใจ flow เลย — deterministic control ล้วน

compose กันได้: เอา LLM Agent ไปเป็น sub-agent ใน Sequential/Parallel/Loop ได้ ผสมเป็น tree

SequentialAgent — ทำงานยังไง?

1. Agent 1 ทำงาน → 2. ส่ง Event บันทึกลง Session → 3. Agent 2 อ่าน Context จาก Session → 4. ทำงานต่อ

Workflow Agent — SequentialAgent (ADK Go v2)

func runDraft(ic agent.InvocationContext) iter.Seq2[*session.Event, error] {
	return func(yield func(*session.Event, error) bool) {
		yield(emit(ic, "draft: เขียน proposal เวอร์ชันแรกแล้ว"), nil)
	}
}

// runReview is identical — only the message differs.

draftAgent, _ := agent.New(agent.Config{Name: "draft_agent", Run: runDraft})
reviewAgent, _ := agent.New(agent.Config{Name: "review_agent", Run: runReview})

// SubAgents go inside the embedded agent.Config; runs them in list order.
pipeline, err := sequentialagent.New(sequentialagent.Config{
	AgentConfig: agent.Config{
		Name:      "proposal_pipeline",
		SubAgents: []agent.Agent{draftAgent, reviewAgent},
	},
})

ParallelAgent — ทำงานยังไง?

แจกงานให้ทุก sub-agent ประมวลผลพร้อมกัน เพื่อลดระยะเวลา (Concurrency)

LoopAgent — ทำงานยังไง?

ทำงานวนซ้ำๆ จนกว่าจะสั่งหยุด (Escalate) หรือจนกว่าจะถึงจำนวนรอบสูงสุด (MaxIterations)

Workflow Agent — Parallel & Loop (ADK Go v2)

// ParallelAgent — runs every sub-agent concurrently.
fetchAll, err := parallelagent.New(parallelagent.Config{
	AgentConfig: agent.Config{
		Name:      "fetch_all_sources",
		SubAgents: []agent.Agent{fetchDocs, fetchTickets, fetchLogs},
	},
})

// LoopAgent — repeats until escalate or MaxIterations.
retryLoop, err := loopagent.New(loopagent.Config{
	AgentConfig: agent.Config{
		Name:      "retry_loop",
		SubAgents: []agent.Agent{attemptAgent},
	},
	MaxIterations: 5,
})

// Inside attemptAgent: escalate exits the loop immediately.
if n >= 3 {
	event.Actions.Escalate = true
}

Agent Type 3: Custom Agent

เราเขียน Go function ธรรมดา และสั่ง `yield(event)` เพื่อส่งค่า Event ให้ ADK Runner ทีละตัว

Custom Agent — โค้ดจริง (ADK Go v2)

func runGreeting(ic agent.InvocationContext) iter.Seq2[*session.Event, error] {
	steps := []string{
		"เริ่มทำงาน...",
		"กำลังประมวลผล...",
		"เสร็จแล้ว: สวัสดี จาก custom agent ล้วนๆ ไม่มี LLM",
	}
	return func(yield func(*session.Event, error) bool) {
		for _, text := range steps {
			event := session.NewEvent(ic, ic.InvocationID())
			event.Author = ic.Agent().Name()
			event.Branch = ic.Branch()
			event.LLMResponse = model.LLMResponse{
				Content: genai.NewContentFromText(text, genai.RoleModel),
			}
			if !yield(event, nil) {
				return // caller (Runner) ขอหยุดกลางทาง
			}
		}
	}
}

3 Types — สังเกตจากโค้ดยังไง

LLM Agent

ADK คุม flow ให้
(reason→plan→tool loop)

Workflow Agent

ADK คุม flow ให้
(deterministic, แก้ไม่ได้)

Custom Agent

คุณเขียน flow เองทั้งหมด

Event Flow: ask-yield pattern

ทำไม Run ต้องคืน iter.Seq2

ปัญหาที่ต้องแก้

  1. 1 agent ส่งได้หลาย event
  2. lazy/streaming — ห้ามรอครบก่อนส่ง
  3. หยุดกลางทางได้ทันที ไม่ leak goroutine

ทำไมไม่ใช้ slice/channel

iter.Seq2 ทำงานยังไง (compiler desugar)

// type จริงใน std lib "iter" (Go 1.23+)
type Seq2[K, V any] func(yield func(K, V) bool)

// สิ่งที่เราเขียน (สไลด์ก่อนหน้า):
for event, err := range runGreeting(ic) {
    fmt.Println(event)
    if err != nil { break }
}

// สิ่งที่ compiler แปลงให้จริงๆ — แค่ฟังก์ชันเรียกฟังก์ชัน ไม่มี magic:
runGreeting(ic)(func(event *session.Event, err error) bool {
    fmt.Println(event)
    if err != nil { return false } // break → return false แทน
    return true                     // จบ body ปกติ → return true (ทำต่อ)
})

range บน func value ไม่ได้รันขนาน — เป็นแค่ nested function call บน stack เดียวกัน

iter.Seq2 Execution Flow

LLM Agent — Flow.Run() ทำงานยังไง?

Architecture

Event

Version Timeline (ไฮไลต์)

Version วันออก จุดเด่น
v0.4.0 2026-01-30 plugin system, HITL, MCP session reconnect
v0.5.0 2026-02-20 plugin เต็มรูป, OTel tracing + semconv
v1.0.0 2026-03-20 GA แรก — SSE streaming, unify context
v1.3.0 2026-05-20 bidirectional streaming, session resumption
v1.5.0 2026-06-30 StrictContextMock, AgentSpace compat
v2.0.0 2026-06-30 breaking/v2, graph-based workflow

จุดเปลี่ยนสำคัญ 3 จุด

  1. v0.4–v0.5 — วาง foundation plugin + observability (OTel) ตั้งแต่ก่อน GA
  2. v1.0.0 — GA production-ready ตัวแรก
  3. v2.0.0 — เปลี่ยน paradigm จาก "Sequential/Parallel/Loop แบบ fix pattern" ไปเป็น graph-based workflow เขียน conditional routing เองได้

Graph-based Workflow (v2.0)

  • Node = agent
  • Edge = เส้นทางไปต่อ พร้อม condition
  • Routing เขียนเองได้ ไม่ผูก fix pattern

ลองเล่นเองได้

go get google.golang.org/adk/v2

ตัวอย่างจริงทั้งหมด: github.com/pallat/adk/tree/main/examples examples/adk-custom-agent — build ผ่าน, run ผ่านจริง กับ google.golang.org/adk/v2

Q&A 🐹