LINE in the front, AWS Strands in the back: How to build AI agents that actually scale.

Article cover: Architecture for building an AI Agent using AWS Strands connected to the LINE Messaging API

อยากมี AI Agent ของตัวเองสักตัว ที่ตอบคำถามได้ ค้นข้อมูลปัจจุบันให้ได้ แล้วก็อยากให้คนคุยด้วยง่ายๆ ไม่ต้องไปโหลดแอปใหม่ ไม่ต้องสมัครอะไรเพิ่ม เพราะ LINE นั้นเป็น แอพยอดฮิตที่ในไทยที่ยังไงก็ได้เสมอ

ผมเลยลองทำโปรเจกต์เล็กๆตัวหนึ่งขึ้นมา เอา AI Agent ไปต่อกับ LINE ให้มันกลายเป็นเพื่อนแชทที่ตอบได้ทั้งเรื่องทั่วไปและเรื่องที่ต้องเช็คข้อมูลสดๆจากเว็บ เพื่อเป็นการส่งต่อความรู้และที่สำคัญคือเน้นเรื่องเอาขึ้น AWS แบบใช้งานจริงได้ยังไง แบบที่ใช้ใน Production เลย ไปกันต่อ!


Strands คืออะไร ทำไมต้องเป็นตัวนี้

ก่อนจะไปถึง LINE ต้องมาทำความรู้จักตัวหลักกันก่อน นั่นก็คือ AWS Strands Agents SDK ครับ

Strands Agents คือ open source SDK จาก AWS สำหรับสร้าง AI Agent ด้วยแนวคิดแบบ model-driven เขียนโค้ดไม่กี่บรรทัดก็ได้ Agent ที่เรียกใช้ Tool เองได้ทันที

จุดที่ผมชอบของ Strands คือมันไม่ได้ผูกติดกับ Model ค่ายไหนค่ายหนึ่ง จะยิงไป Bedrock, Gemini, OpenAI หรือค่ายอื่นก็สลับได้ผ่าน Config ไม่ต้องเขียนโค้ด Agent ใหม่ทั้งหมด แล้วเรื่อง Tool ก็ง่ายมาก แค่เขียนฟังก์ชัน Python ธรรมดาแล้วแปะ @tool ไว้ด้านบน Agent ก็รู้จักเรียกใช้เองว่าเมื่อไหร่ควรเรียก เมื่อไหร่ไม่ต้องเรียก

Diagram explaining the model-driven concept of the AWS Strands Agents SDK

ในโปรเจกต์นี้ผมให้ Agent มี Tool เดียวคือ web_search ที่ยิงไป DuckDuckGo ผ่านไลบรารี ddgs ข้อดีคือไม่ต้องขอ API Key เลยครับ เหมาะกับตอนที่อยากได้ Agent ที่ค้นเว็บได้แบบเริ่มต้นเร็วๆ ถ้าอยากได้ผลลัพธ์ที่แม่นขึ้นทีหลังก็มี strands_tools.tavily หรือ .exa เป็นตัวเลือกสลับได้เหมือนกัน *ข้อเสียคือความแม่นยำในการ Search ต้อง validate ให้ดีนะครับ

Agent ทั้งตัวถูกประกอบขึ้นจากฟังก์ชันเดียวใน agent/core.py ชื่อ build_agent() ประมาณนี้

def build_agent() -> Agent:
return Agent(system_prompt=SYSTEM_PROMPT, tools=[web_search], model=_select_model())

สั้นๆแค่นี้เลยครับ แล้วจุดที่สำคัญมากคือฟังก์ชันนี้ตัวเดียวถูกเรียกใช้ทั้งฝั่ง Local และฝั่งที่ Deploy ขึ้น AWS ไม่มีการเขียน Agent แยกสองชุด เพราะงั้นพฤติกรรมของ Agent จะไม่มีวันเพี้ยนกันระหว่างสองโหมดเลยครับ


แล้วทำไมต้องเป็น LINE

พอพูดถึง Chatbot คนไทยส่วนใหญ่คงนึกถึง LINE เป็นอันดับแรกใช่ไหมครับ ต่างจากประเทศอื่นที่อาจจะใช้ Telegram, WhatsApp หรือ Slack เป็นหลัก แต่บ้านเราแทบทุกคนมี LINE อยู่ในเครื่องอยู่แล้ว ถ้าอยากทำ Agent ที่คนไทยหยิบมาใช้ได้จริงโดยไม่ต้องอธิบายอะไรเพิ่ม การไปโผล่ใน LINE จึงเป็นทางเลือกที่เข้าท่าที่สุดสำหรับผมครับ

แต่ทีนี้ LINE Messaging API มีข้อจำกัดสำคัญอย่างหนึ่งที่ต้องรู้ไว้ก่อนออกแบบระบบ นั่นคือ LINE ให้ตั้งค่า Webhook URL ได้แค่ URL เดียวต่อหนึ่ง Channel และมันจะยิง JSON แบบ HTTPS POST มาตรงๆ ในขณะที่ Amazon Bedrock AgentCore Runtime ซึ่งเป็นที่ที่ผมจะเอา Agent ไปรันนั้น ไม่ได้เป็น Webhook Target แต่ต้องเรียกผ่าน invoke_agent_runtime ที่เซ็น SigV4 ด้วย boto3 เท่านั้น ซึ่ง LINE เรียกตรงๆแบบนี้ไม่ได้เลยครับ

เพราะงั้นสิ่งที่ต้องมีเพิ่มคือ Adapter ตัวเล็กๆที่ทำหน้าที่เป็นคนกลาง รับ Webhook จาก LINE ตรวจลายเซ็น แล้วค่อยไปเรียก Agent อีกที ไม่ว่า Agent จะรันอยู่ที่ไหนก็ตาม


โครงสร้างโปรเจกต์แบบคร่าวๆ

โปรเจกต์นี้แบ่งเป็นสองส่วนหลักที่ทำงานแยกกันชัดเจนครับ

  • agent/ เก็บตัว Agent เอง มี core.py (สร้าง Agent), search_tool.py (Tool ค้นเว็บ) และ respond.py ซึ่งเป็นจุดต่อระหว่าง Agent กับ Backend สองแบบ คือ respond_local() กับ respond_agentcore()
  • adapter/ เก็บตัว Webhook เอง มี app.py (FastAPI endpoint POST /callback) กับ line_client.py (ยิงกลับไปหา LINE ทั้ง reply, push, mark-as-read, loading animation)

จุดที่ออกแบบไว้ตั้งแต่แรกและผมว่าสำคัญมากคือเรื่อง Reply timing ครับ การให้ Agent ตอบหนึ่งครั้งอาจใช้เวลา 5 ถึง 20 วินาที เพราะมันต้องเรียก LLM แล้วอาจจะเรียก web_search แล้วเรียก LLM อีกรอบ ถ้าปล่อยให้ Webhook Handler รอจนตอบเสร็จค่อยส่ง Response กลับ LINE จะ Timeout ก่อน เพราะงั้นในโค้ดจะ Return "OK" กลับ LINE ทันทีหลังจากตรวจลายเซ็นผ่าน แล้วค่อยประมวลผล Agent เป็น Background Task แยกไปต่างหาก จากนั้นค่อยส่งคำตอบกลับไปด้วย Reply Token ถ้ายังไม่หมดอายุ หรือ Fallback ไปใช้ Push Message API แทนถ้า Token หมดอายุไปแล้วครับ

verview of LINE Messaging API Webhook usage and its limitations

วิธีการเปิด Line developer หรือ จะใช้ Line official account แนะนำให้ไปศึกษาที่นี่ครับ 👇🏽

https://developers.line.biz/en/docs/messaging-api/building-bot


การ Deploy ขึ้น AWS

เอาละ มาถึงส่วนที่ตั้งใจจะเล่าให้ละเอียดที่สุดกันดีกว่า นั่นคือการเอาโปรเจกต์นี้ขึ้นไปรันจริงบน AWS ครับ

Architecture diagram showing the Adapter acting as middleware between LINE Webhook and Amazon Bedrock AgentCore

โหมด Cloud ของโปรเจกต์นี้ใช้บริการ AWS สองตัวคู่กัน คือ

Amazon Bedrock AgentCore Runtime สำหรับรัน Agent เอง กับ AWS Lambda + Function URL สำหรับรัน Adapter ที่เป็น Webhook

สิ่งที่ต้องเตรียมก่อน

  • ติดตั้ง uv เป็นตัวจัดการ Package ของโปรเจกต์นี้
  • มี AWS Account ที่เปิดใช้ Bedrock Model Access ใน Region ที่จะ Deploy แล้ว พร้อม Credential ในเครื่อง (aws configure หรือใช้ AWS_PROFILE)
  • มี LINE Messaging API Channel จาก LINE Developers Console พร้อม Channel Secret กับ Channel Access Token
  • เปิด Docker ไว้ในเครื่อง เพราะสคริปต์ Deploy จะ Build Image เองอัตโนมัติ

ตั้งค่าเริ่มต้นก่อนครับ

uv sync
cp .env.example .env # แล้วกรอก LINE_CHANNEL_SECRET, LINE_CHANNEL_ACCESS_TOKEN, AWS_PROFILE, AWS_REGION

อยากลองว่า Agent ตอบเป็นยังไงก่อนโดยยังไม่ต้องยุ่งกับ LINE เลยก็ได้ครับ แค่รัน uv run demo.py "your question"


Deploy ตัว Agent ขึ้น Bedrock AgentCore Runtime

AWS system architecture using Amazon Bedrock AgentCore Runtime and AWS Lambda

ขั้นแรกคือเอา Agent ขึ้นไปรันบน AgentCore Runtime ก่อน ตัวนี้จะแพคเกจ Agent เป็น Docker Image สถาปัตยกรรม ARM64 (AgentCore บังคับ ARM64 เท่านั้น) แล้วห่อด้วย BedrockAgentCoreApp ซึ่งจะสร้าง Endpoint /invocations กับ /ping ให้เองอัตโนมัติ ไม่ต้องเขียน FastAPI เพิ่มเลยครับ

app = BedrockAgentCoreApp()
agent = build_agent()
@app.entrypoint
def invoke(payload: dict) -> dict:
prompt = payload.get("prompt", "")
result = agent(prompt)
return {"result": str(result)}

รันคำสั่งเดียวจบครับ

uv run scripts/deploy.py
Deployment process of an AI Agent to Amazon Bedrock AgentCore Runtime

สคริปต์ตัวนี้ Idempotent คือรันซ้ำได้เรื่อยๆอย่างปลอดภัย เบื้องหลังมันจะทำให้ครบทุกอย่างที่ต้องใช้ คือสร้าง IAM Role สำหรับ AgentCore, สร้าง ECR Repository ถ้ายังไม่มี, Build Docker Image แบบ --platform linux/arm64, Push ขึ้น ECR แล้วสร้างหรืออัปเดต AgentCore Runtime ให้ ถ้ามีของเดิมอยู่แล้วก็แค่อัปเดต Image ในตัวเดิม ไม่สร้างซ้ำ พอเสร็จมันจะเขียน ARN ของ Runtime กลับเข้าไปใน .env ให้อัตโนมัติเป็นตัวแปร AGENTCORE_AGENT_RUNTIME_ARN ด้วย สะดวกมากครับ ไม่ต้องมานั่งก็อปวางเอง


Deploy ตัว Adapter ขึ้น Lambda

ขั้นต่อมาคือ Adapter ตัวที่รับ Webhook จาก LINE ต้องมี Public URL ให้ LINE ยิงมาได้จริง ตัวนี้เลือกใช้ AWS Lambda คู่กับ Function URL ครับ เพราะเบามาก ไม่ต้องมี API Gateway ให้ยุ่งยาก ใช้ Mangum ห่อ FastAPI App ตัวเดิมให้กลายเป็น Lambda Handler ได้เลยแบบไม่ต้องแก้โค้ด Adapter อะไรเพิ่ม

from mangum import Mangum
from adapter.app import app

handler = Mangum(app)

แล้วรันอีกคำสั่งเดียว

uv run scripts/deploy_adapter.py

ตัวนี้จะอ่าน ARN ของ AgentCore Runtime ที่ Deploy ไปก่อนหน้านี้จาก .env มาใช้เลย เบื้องหลังจะสร้าง IAM Role ที่มีสิทธิ์เรียก invoke_agent_runtime เฉพาะ Runtime ตัวนี้เท่านั้น (ไม่ได้ให้สิทธิ์กว้างเกินจำเป็น)

สร้าง ECR Repository แยกสำหรับ Adapter, Build Image จาก Dockerfile.lambda ซึ่งเป็นคนละไฟล์กับ Dockerfile ตัวแรก เพราะ Lambda ใช้ Runtime API ไม่ใช่ Server แบบ :8080 ที่รันค้างตลอดเวลาแบบ AgentCore

Configuration example for setting up an AWS Lambda Function URL as a public endpoint

แล้วก็สร้าง Lambda Function พร้อม Function URL แบบ Public ให้เสร็จสรรพ Adapter ตัวนี้ถูกตั้งค่าให้ใช้ AGENT_BACKEND=agentcore ตายตัวเลย เพราะ Lambda แต่ละ Invocation อาจรันคนละ Execution Environment กัน จะเก็บ Session แบบ In-memory ต่อ User เหมือนโหมด Local ไม่ได้อยู่แล้วครับ

Configuring the AWS Lambda Function URL as a Webhook in the LINE Developers Console

ระหว่างทำจริงผมเจอบั๊กเล็กๆสามตัวที่น่าเก็บไว้เตือนใจคนอื่นด้วย

  • Docker Builder ตัวใหม่แนบ OCI Attestation/SBOM Manifest มาด้วยโดยอัตโนมัติ ซึ่ง Lambda ไม่รองรับ Format นี้ ต้อง Build ด้วย --provenance=false --sbom=false ถึงจะผ่าน
  • ไฟล์ __init__.py บางไฟล์ติด Permission แบบ 600 มาจากตอนสร้างไฟล์ พอ Copy เข้า Docker Image แล้ว Lambda รันด้วย User ที่ไม่ใช่ Root เลยอ่านไฟล์ไม่ได้ ต้อง chmod -R a+rX ใน Dockerfile กันไว้เลย
  • Lambda Function URL ที่สร้างหลังช่วงปลายปี 2025 ต้องมีทั้ง lambda:InvokeFunctionUrl และ Resource Policy แยกสำหรับ lambda:InvokeFunction ถึงจะเปิด Public แบบ AuthType=NONE ได้จริง มีแค่ตัวแรกจะเจอ 403 อยู่ดี

พอ Deploy เสร็จสคริปต์จะ Print Function URL ออกมาให้ครับ เอา URL นั้นต่อท้ายด้วย /callback แล้วเอาไปวางใน LINE Developers Console ช่อง Webhook URL จบแล้วครับ ทีนี้ก็ไปเปิด LINE แล้วลองพิมพ์คุยกับ Bot ได้เลย จุดดีของโหมด Cloud คือ URL นี้เป็น URL ถาวร ไม่เหมือนโหมด Local ที่ URL เปลี่ยนทุกครั้งที่รันใหม่

hat interface screenshot of a user interacting with an AI Agent on the LINE app for a web search

ที่นี้เราก็มาลอง Chat ได้เลย


ค่าใช้จ่ายและการทำลายทิ้ง

ทั้งสองบริการเป็นแบบจ่ายตามการใช้งานจริงครับ ไม่ได้แพงอะไร แต่ถ้าอยากหยุดจ่ายชั่วคราวก็มีสคริปต์ Teardown ให้ครบ

uv run scripts/teardown.py           # ลบ AgentCore Runtime ตัวที่คิดเงินตาม Session
uv run scripts/teardown_adapter.py   # ลบ Lambda + Function URL

ทั้งสองสคริปต์จะเก็บ ECR Repository กับ IAM Role ไว้ตามเดิมโดยดีฟอลต์ เพราะของสองอย่างนี้แทบไม่มีค่าใช้จ่ายตอน Idle และช่วยให้ Deploy รอบหน้าเร็วขึ้น ถ้าอยากลบทิ้งให้หมดจริงๆก็ใส่ Flag --all เพิ่มเข้าไปได้ครับ


ส่วนของโหมด Local สั้นๆ

นอกจากขึ้น AWS แล้ว โปรเจกต์นี้ยังรันบนเครื่องตัวเองล้วนๆได้เหมือนกัน เอาไว้ใช้ตอนพัฒนาหรือทดลองไอเดียเร็วๆ โดยรัน Agent ในโปรเซสเดียวกับ Adapter เลย แล้วเปิด Public ผ่าน Cloudflare Quick Tunnel (ไม่ต้องสมัครอะไรเพิ่ม) ด้วยคำสั่งเดียว

./scripts/dev-up.sh

ข้อเสียคือ URL จาก Tunnel จะเปลี่ยนทุกครั้งที่รันใหม่ เลยต้องไปวางใน LINE Console ใหม่ทุกรอบ เหมาะกับตอนกำลังพัฒนาหรือทดสอบไอเดียเร็วๆเท่านั้น ถ้าจะใช้งานจริงหรือปล่อยให้รันค้างไว้ยาวๆ แนะนำให้ไปทางโหมด Cloud ที่เล่าไปข้างบนดีกว่าครับ


สรุป

ถ้าใครอยากลองสร้าง AI Agent ตัวแรกของตัวเองแบบจับต้องได้จริง ไม่ใช่แค่ Demo ใน Notebook โปรเจกต์แนวนี้เหมาะมากครับ เพราะ Strands ทำให้ส่วนของ Agent เขียนง่าย ไม่ต้องยุ่งกับรายละเอียดของ LLM Provider เยอะ แล้วพอเลือกต่อกับ LINE คนที่มาทดสอบก็ไม่ต้องเรียนรู้อะไรใหม่เลย เปิดแชทที่มีอยู่แล้วก็คุยได้ทันที

ส่วนเรื่อง Deploy ถ้าจะทำให้จริงจังและปล่อยให้ใช้งานได้ต่อเนื่อง แนะนำให้ไปทางโหมด Cloud เพราะได้ URL ถาวร ไม่ต้องมานั่งเปลี่ยน Webhook ทุกวัน แถมยัง Scale ตามการใช้งานแบบ Pay-per-use ด้วย ส่วนโหมด Local เก็บไว้ใช้ตอน Dev หรือ Debug เร็วๆพอครับ

บทความนี้เป็นตอนแรกของซีรีส์ Strands Agent นะครับ ตอนต่อไปจะพาไปดูวิธีใช้พลังของ Agent ตัวนี้ให้เก่งขึ้นไปอีก โดยจะเจาะลึกฝั่ง Amazon Bedrock AgentCore กันมากขึ้น ทั้งเรื่อง Memory, Identity, Gateway และความสามารถอื่นๆที่ AgentCore มีให้ ใครสนใจเรื่อง AI Agent บน AWS รอติดตามตอนต่อไปได้เลยครับ 🚀

Comic-style illustration showing LINE, AWS Strands, and an AI Agent working together in production

GitHub:
https://github.com/kriangsak-puk/strands-agent-line-application

ref:
https://strandsagents.com/
https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-getting-started.html
https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-permissions.html
https://developers.line.biz/en/docs/messaging-api/
https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/

Leave a comment