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

Labels

bugSomething isn't workingdependenciesการอัปเดตหรือจัดการ dependency ของโปรเจกต์discussionใช้สำหรับประเด็นที่ต้องการการพูดคุย/ตัดสินใจร่วมกันdocumentationImprovements or additions to documentationduplicateThis issue or pull request already existsenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is neededinvalidThis doesn't seem rightjavascriptPull requests that update javascript codeperformanceปัญหาหรือการปรับปรุงที่เกี่ยวข้องกับความเร็วและประสิทธิภาพของระบบpython:uvPull requests that update python:uv codequestionFurther information is requestedrefactorการปรับโครงสร้างโค้ดโดยไม่เปลี่ยนพฤติกรรมreleaseงานที่เกี่ยวข้องกับการเตรียมเวอร์ชันใหม่securityช่องโหว่หรือการปรับปรุงด้านความปลอดภัยtestingงานที่เกี่ยวข้องกับ unit test, integration test หรือ QAux/uiการออกแบบหน้าตาและประสบการณ์ผู้ใช้v0wontfixThis will not be worked on

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions