Skip to content

pure-agent-dev #63

Description

@zyntromedia

Code Guide: pure-agent-dev

โครงสร้างนี้เหมาะกับการทำ Agent + FastAPI + Provider Adapter + BytePlus ECS โดยแยก responsibility ชัดเจนระหว่าง API, AI logic, cloud infrastructure และ validation. จุดสำคัญคือ Agent ไม่ควรรู้ว่า BytePlus SDK ทำงานอย่างไร เพราะวันหนึ่ง provider เปลี่ยนแล้วเราไม่ควรต้องผ่าตัดสมอง Agent กันใหม่

This architecture is suitable for Agent + FastAPI + Provider Adapter + BytePlus ECS. The key principle is that the Agent should never depend directly on the BytePlus SDK.

pure-agent-dev/

├── app/
│ ├── init.py
│ │
│ ├── main.py
│ │
│ ├── api/
│ │ ├── init.py
│ │ └── routes/
│ │ ├── init.py
│ │ ├── health.py
│ │ ├── tasks.py
│ │ └── compute.py
│ │
│ ├── agents/
│ │ ├── init.py
│ │ ├── planner.py
│ │ └── executor.py
│ │
│ ├── providers/
│ │ ├── init.py
│ │ ├── base.py
│ │ │
│ │ └── byteplus/
│ │ ├── init.py
│ │ ├── client.py
│ │ └── ecs.py
│ │
│ ├── schemas/
│ │ ├── init.py
│ │ ├── agent.py
│ │ ├── compute.py
│ │ └── task.py
│ │
│ └── services/
│ ├── init.py
│ ├── task_service.py
│ └── compute_service.py

├── schemas/
│ └── agent-task.schema.json

├── tests/
│ ├── init.py
│ ├── test_health.py
│ ├── test_planner.py
│ ├── test_executor.py
│ ├── test_compute_service.py
│ └── providers/
│ └── test_byteplus_ecs.py

├── agent.yaml

├── requirements.txt
├── pyproject.toml
├── Dockerfile
├── docker-compose.yml
├── .env.example

├── .github/
│ └── workflows/
│ └── ci.yml

└── README.md

  1. Layer Architecture

┌──────────────────┐
│ Client │
└────────┬─────────┘
│ HTTP

┌──────────────────┐
│ FastAPI │
│ API Routes │
└────────┬─────────┘


┌──────────────────┐
│ Services │
│ Business Logic │
└────────┬─────────┘


┌──────────────────┐
│ Agent │
│ Planner/Executor │
└────────┬─────────┘


┌─────────────────────────┐
│ Provider Interface │
│ ComputeProvider │
└────────────┬────────────┘


┌──────────────────┐
│ BytePlus Adapter │
│ ECS API │
└──────────────────┘

The dependency direction should remain:

API

Services

Agents

Provider Interface

Provider Implementation

External Cloud SDK

Avoid this:

Agent

BytePlus SDK

That would tightly couple the AI layer to infrastructure.


  1. app/main.py

เป็น entry point ของ FastAPI application.

from fastapi import FastAPI

from app.api.routes import compute, health, tasks

app = FastAPI(
title="Pure Agent API",
version="1.0.0",
)

app.include_router(health.router)
app.include_router(tasks.router, prefix="/v1")
app.include_router(compute.router, prefix="/v1")

Run:

uvicorn app.main:app --reload

Production:

uvicorn app.main:app --host 0.0.0.0 --port 8000


  1. providers/base.py

นี่คือ abstraction สำคัญที่สุดของ architecture.

from abc import ABC, abstractmethod

class ComputeProvider(ABC):

@abstractmethod
async def list_instances(self):
    raise NotImplementedError

@abstractmethod
async def start_instance(self, instance_id: str):
    raise NotImplementedError

@abstractmethod
async def stop_instance(self, instance_id: str):
    raise NotImplementedError

@abstractmethod
async def reboot_instance(self, instance_id: str):
    raise NotImplementedError

The Agent only depends on ComputeProvider.

It does not care whether the implementation is:

BytePlus
AWS
Azure
GCP
Mock
Local Docker
Kubernetes


  1. providers/byteplus/client.py

ไฟล์นี้รับผิดชอบเฉพาะการสร้าง BytePlus client และ credential configuration.

import os

class BytePlusClient:
def init(self):
self.access_key = os.environ["BYTEPLUS_ACCESS_KEY"]
self.secret_key = os.environ["BYTEPLUS_SECRET_KEY"]
self.region = os.getenv(
"BYTEPLUS_REGION",
"ap-southeast-1",
)

def get_region(self) -> str:
    return self.region

ไม่ควรใส่ business logic ในไฟล์นี้.

client.py

credentials
region
SDK initialization
connection configuration


  1. providers/byteplus/ecs.py

ไฟล์นี้ implements ComputeProvider.

from app.providers.base import ComputeProvider
from app.providers.byteplus.client import BytePlusClient

class BytePlusECSProvider(ComputeProvider):

def __init__(self, client: BytePlusClient):
    self.client = client

async def list_instances(self):
    # Call BytePlus ECS DescribeInstances
    return []

async def start_instance(self, instance_id: str):
    # Call BytePlus ECS StartInstance
    return {
        "instance_id": instance_id,
        "status": "starting",
    }

async def stop_instance(self, instance_id: str):
    return {
        "instance_id": instance_id,
        "status": "stopping",
    }

async def reboot_instance(self, instance_id: str):
    return {
        "instance_id": instance_id,
        "status": "rebooting",
    }

ตัว implementation จริงค่อยเสียบ BytePlus SDK ตรงนี้.

The rest of the application remains unchanged.


  1. schemas/compute.py

ใช้ Pydantic สำหรับ runtime validation.

from pydantic import BaseModel, Field

class InstanceRequest(BaseModel):
instance_id: str = Field(min_length=1)

class InstanceResponse(BaseModel):
instance_id: str
status: str


  1. schemas/task.py

Agent task ควรเป็น structured data ไม่ใช่ free-form string อย่างเดียว.

from typing import Literal

from pydantic import BaseModel, Field

class AgentTask(BaseModel):
task_id: str
action: Literal[
"list_instances",
"start_instance",
"stop_instance",
"reboot_instance",
]
instance_id: str | None = Field(
default=None,
)

ตัวนี้ต้องสอดคล้องกับ:

schemas/agent-task.schema.json

ดังนั้นจะมีสองระดับ:

JSON Schema

External contract

Pydantic

Runtime validation


  1. agents/planner.py

Planner แปลง intent → structured task.

from app.schemas.task import AgentTask

class AgentPlanner:

def plan(
    self,
    action: str,
    instance_id: str | None = None,
) -> AgentTask:
    return AgentTask(
        task_id="generated-task",
        action=action,
        instance_id=instance_id,
    )

Planner ไม่ควรเรียก BytePlus.

User intent

Planner

AgentTask

ไม่ใช่:

User intent

Planner

BytePlus API


  1. agents/executor.py

Executor เป็นคนเอา task ไป execute ผ่าน provider.

from app.providers.base import ComputeProvider
from app.schemas.task import AgentTask

class AgentExecutor:

def __init__(self, provider: ComputeProvider):
    self.provider = provider

async def execute(self, task: AgentTask):

    if task.action == "list_instances":
        return await self.provider.list_instances()

    if task.action == "start_instance":
        return await self.provider.start_instance(
            task.instance_id
        )

    if task.action == "stop_instance":
        return await self.provider.stop_instance(
            task.instance_id
        )

    if task.action == "reboot_instance":
        return await self.provider.reboot_instance(
            task.instance_id
        )

    raise ValueError(
        f"Unsupported action: {task.action}"
    )

Flow:

Planner

AgentTask

Executor

ComputeProvider

BytePlusECSProvider

BytePlus ECS


  1. services/compute_service.py

Service เป็น application/business layer.

from app.agents.executor import AgentExecutor
from app.agents.planner import AgentPlanner

class ComputeService:

def __init__(
    self,
    planner: AgentPlanner,
    executor: AgentExecutor,
):
    self.planner = planner
    self.executor = executor

async def execute(
    self,
    action: str,
    instance_id: str | None = None,
):
    task = self.planner.plan(
        action=action,
        instance_id=instance_id,
    )

    return await self.executor.execute(task)

  1. api/routes/compute.py

FastAPI route ควรบางที่สุด.

from fastapi import APIRouter

from app.agents.executor import AgentExecutor
from app.agents.planner import AgentPlanner
from app.providers.byteplus.client import BytePlusClient
from app.providers.byteplus.ecs import BytePlusECSProvider
from app.services.compute_service import ComputeService

router = APIRouter(prefix="/compute", tags=["compute"])

def get_service() -> ComputeService:
client = BytePlusClient()
provider = BytePlusECSProvider(client)

executor = AgentExecutor(provider)
planner = AgentPlanner()

return ComputeService(
    planner=planner,
    executor=executor,
)

@router.post("/instances/{instance_id}/start")
async def start_instance(instance_id: str):

service = get_service()

return await service.execute(
    action="start_instance",
    instance_id=instance_id,
)

ใน production ควรเปลี่ยน get_service() เป็น FastAPI Dependency Injection และจัด lifecycle ของ SDK client ให้เหมาะสม.


  1. agent.yaml

ใช้เป็น declarative configuration.

name: pure-agent
version: "1.0"

runtime:
language: python
framework: fastapi

agent:
planner: app.agents.planner.AgentPlanner
executor: app.agents.executor.AgentExecutor

providers:
compute:
default: byteplus

byteplus:
  type: ecs
  region: ${BYTEPLUS_REGION}

tasks:
allowed_actions:
- list_instances
- start_instance
- stop_instance
- reboot_instance

แนวคิดคือ:

agent.yaml

Configuration

Application

ไม่ควร hard-code provider selection ไว้ใน Agent.


  1. schemas/agent-task.schema.json

ควรเป็น contract ระหว่าง Agent components.

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "AgentTask",
"type": "object",
"required": [
"task_id",
"action"
],
"properties": {
"task_id": {
"type": "string",
"minLength": 1
},
"action": {
"type": "string",
"enum": [
"list_instances",
"start_instance",
"stop_instance",
"reboot_instance"
]
},
"instance_id": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}

JSON Schema เป็น external contract ส่วน Pydantic เป็น runtime model.


  1. Environment

.env.example

BYTEPLUS_ACCESS_KEY=
BYTEPLUS_SECRET_KEY=
BYTEPLUS_REGION=ap-southeast-1

APP_ENV=development
LOG_LEVEL=INFO

Secret จริงไม่ควร commit:

.env
*.secret

GitHub Actions ใช้:

BYTEPLUS_ACCESS_KEY
BYTEPLUS_SECRET_KEY
BYTEPLUS_REGION

จาก GitHub Secrets/Environment.


  1. Dockerfile

FROM python:3.12-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app ./app
COPY schemas ./schemas
COPY agent.yaml .

EXPOSE 8000

CMD [
"uvicorn",
"app.main:app",
"--host",
"0.0.0.0",
"--port",
"8000"
]


  1. docker-compose.yml

services:
api:
build: .
ports:
- "8000:8000"
env_file:
- .env
restart: unless-stopped

Architecture ตอน local development:

Docker Compose


FastAPI


Agent


BytePlus ECS


  1. Testing Strategy

ไม่ควรให้ unit tests ยิง BytePlus จริงทุกครั้ง เพราะ CI จะกลายเป็นเครื่องสล็อตแมชชีนที่มี cloud bill เป็นรางวัล

ใช้ mock provider:

class MockComputeProvider:

async def list_instances(self):
    return []

async def start_instance(self, instance_id):
    return {
        "instance_id": instance_id,
        "status": "starting",
    }

async def stop_instance(self, instance_id):
    return {
        "instance_id": instance_id,
        "status": "stopping",
    }

async def reboot_instance(self, instance_id):
    return {
        "instance_id": instance_id,
        "status": "rebooting",
    }

Test:

Unit Test

├── Planner
├── Executor
├── Service
└── Provider interface


Mock Provider

Integration Test


BytePlus sandbox/test account


  1. GitHub Actions

.github/workflows/ci.yml

name: CI

on:
push:
branches:
- main
- develop

pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest

steps:
  - name: Checkout
    uses: actions/checkout@v4

  - name: Setup Python
    uses: actions/setup-python@v5
    with:
      python-version: "3.12"
      cache: pip

  - name: Install dependencies
    run: |
      python -m pip install --upgrade pip
      pip install -r requirements.txt
      pip install pytest ruff

  - name: Lint
    run: |
      ruff check .

  - name: Test
    run: |
      pytest -q

CI ไม่จำเป็นต้องมี BytePlus credentials สำหรับ unit tests.


  1. Dependency Graph

สุดท้าย dependency graph ควรเป็นแบบนี้:

┌──────────────┐
│ FastAPI │
└──────┬───────┘


┌──────────────┐
│ Services │
└──────┬───────┘


┌──────────────┐
│ Agents │
└──────┬───────┘


┌────────────────────┐
│ ComputeProvider │
│ Interface │
└─────────┬──────────┘

┌─────────────┴─────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ BytePlus ECS │ │ Mock Provider │
│ Provider │ │ │
└────────┬─────────┘ └──────────────────┘


┌──────────────────┐
│ BytePlus SDK/API │
└──────────────────┘

Core rule

FastAPI = HTTP/API
Services = Business orchestration
Planner = Intent → Task
Executor = Task → Provider
Provider = Infrastructure abstraction
BytePlus ECS = Infrastructure implementation
Pydantic = Runtime validation
JSON Schema = Contract
agent.yaml = Configuration
Docker = Runtime packaging
GitHub CI = Verification

ไทย: โครงสร้างนี้ทำให้ pure-agent-dev สามารถเริ่มจาก BytePlus ECS ได้ แต่ยังคงเปลี่ยน provider, เพิ่ม Agent, เพิ่ม task หรือย้าย deployment โดยไม่ต้องรื้อ architecture ทั้งระบบ

English: This structure lets pure-agent-dev start with BytePlus ECS while remaining provider-agnostic, testable, containerized, and suitable for adding additional agents or cloud providers later.

ผมสามารถต่อจาก guide นี้เป็น ชุดไฟล์ production-ready ทั้ง repository พร้อม requirements.txt, Pydantic v2, dependency injection, BytePlus SDK integration, validators, planner/executor tests และ CI ได้ครับ.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions