Cara Memangkas Biaya Pemanggilan Agen AI Menggunakan Kontainer Docker
TuBrief 편집팀
2026년 7월 23일
0
Computing/Software원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
커뮤니티의 다른 글
댓글 (0)
Log in to leave a comment
아직 작성된 글이 없습니다
원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.
Log in to leave a comment
아직 작성된 글이 없습니다
Memberikan akses langsung ke Bash melalui subprocess atau exec() kepada agen AI sama saja seperti menyerahkan kunci rumah secara utuh. Hanya dengan satu serangan prompt injection, seluruh server host bisa ditembus, atau agen yang mengalami halusinasi dapat mengeksekusi rm -rf /. Lantas, bagaimana jika kita memecah semua alat menjadi skema OpenAPI yang terperinci dan mendaftarkannya? Menambahkan beberapa alat saja sudah bisa membuat context window LLM Anda meledak.
Pada akhirnya, solusinya adalah memasangkan kontainer Docker fana (ephemeral) yang menyala dan menghilang hanya dalam waktu 0,1 detik untuk setiap sesi. Dengan membagi kompartemen keamanan secara tegas dan memproses data berukuran besar melalui pipa pemrosesan internal di dalam kontainer, Anda dapat memangkas penggunaan token API hingga lebih dari 70%.
Bahkan jika agen AI menjalankan infinite loop atau memicu fork bomb, server host harus tetap aman. Jika Anda tidak membatasi CPU dan memori secara paksa pada tingkat Linux Kernel Cgroups, satu thread agen yang berjalan tak terkendali dapat berujung pada lonjakan biaya cloud yang membengkak.
Berikut adalah struktur sandbox terisolasi yang dibangun menggunakan Python Docker SDK.
`python
import atexit
import os
import signal
import docker
from docker.errors import DockerException
class EphemeralBashSandbox:
def init(self, workspace_host_path: str, image: str = "python:3.11-slim"):
self.client = docker.from_env()
self.image = image
self.workspace_host_path = os.path.abspath(workspace_host_path)
self.container = None
self._start_sandbox()
atexit.register(self.cleanup)
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
def _start_sandbox(self):
self.container = self.client.containers.run(
image=self.image,
command="/bin/bash",
detach=True,
stdin_open=True,
tty=True,
network_disabled=True,
read_only=True,
mem_limit="512m",
cpu_quota=50000,
pids_limit=50,
user="1000:1000",
volumes={
self.workspace_host_path: {
"bind": "/workspace",
"mode": "rw"
},
"/tmp": {
"bind": "/tmp",
"mode": "rw"
}
},
working_dir="/workspace",
environment={"HOME": "/tmp"}
)
def execute_command(self, cmd: str, timeout: int = 30) -> tuple[int, str, str]:
if not self.container:
raise RuntimeError("Sandbox container is not active.")
exec_res = self.container.exec_run(
cmd=["/bin/bash", "-c", cmd],
workdir="/workspace",
demux=True
)
exit_code = exec_res.exit_code
stdout = exec_res.output[0].decode('utf-8', errors='replace') if exec_res.output and exec_res.output[0] else ""
stderr = exec_res.output[1].decode('utf-8', errors='replace') if exec_res.output and exec_res.output[1] else ""
return exit_code, stdout, stderr
def cleanup(self):
if self.container:
try:
self.container.stop(timeout=2)
self.container.remove(force=True)
except DockerException:
pass
finally:
self.container = None
def _signal_handler(self, signum, frame):
self.cleanup()
os._exit(0)
`
Jika Anda mengeksekusi docker run baru setiap kali hendak menjalankan perintah, akan terjadi latensi cold start yang sangat buruk sebesar 4,7 detik. Ini sama sekali tidak dapat digunakan untuk layanan produksi. Sebagai gantinya, Anda harus menjalankan sandbox dalam mode daemon latar belakang (detach=True, stdin_open=True, tty=True), lalu menyuntikkan perintah menggunakan exec_run agar waktu respons turun di bawah 100 ms.
Ada tiga poin konfigurasi utama:
mem_limit="512m", cpu_quota=50000, dan pids_limit=50.network_disabled=True dan read_only=True, serta hanya me-mount ruang kerja /workspace dan /tmp secara terbatas.user="1000:1000", dan menerapkan handler sinyal POSIX agar kontainer dihancurkan secara bersih saat proses mati.Pendekatan konvensional yang mendefinisikan skema API individual sebagai JSON dan menyuntikkannya memakan sekitar 550 hingga 1.400 token per alat. Jika Anda menambahkan 20 alat saja, 20.000 token akan hangus bahkan sebelum Anda mengajukan satu pertanyaan pun.
Menurut laporan teknis dari mesin pencari You.com, saat mereka menerapkan metode eksekusi skrip berbasis Bash (CodeAct) alih-alih penyuntikan skema JSON sederhana, penggunaan token berkurang sebesar 61% dan kecepatan pemrosesan meningkat hingga 40%.
| Item Evaluasi | Penyuntikan Skema JSON | Model Context Protocol (MCP) | Pipeline Bash CLI |
|---|---|---|---|
| Token Definisi Alat | ~550–1.400 token per alat | ~550–1.400 token per alat | 1 Antarmuka Meta (~100 token) |
| Kontaminasi Konteks Data Antara | Sangat tinggi (meneruskan seluruh payload) | Tinggi (meneruskan seluruh payload) | Tidak ada (dikembalikan setelah pembersihan di dalam sandbox) |
| Jumlah Round-Trip LLM | N kali secara sekuensial | N kali secara sekuensial | 1 kali (bundel skrip bertahap) |
| Latensi Penyelesaian Tugas | Tolok ukur (baseline) | Terjadi overhead serialisasi server | Berkurang rata-rata 48,5% |
| Tingkat Penghematan Token | 0% (baseline) | 0% | Hemat 61%–98.7% |
Data analisis internal dari tim Anthropic juga menunjukkan hasil serupa. Ketika tugas analisis berkas berukuran besar dialihkan ke metode eksekusi kode, penggunaan token berkurang hingga 98,7%. Karena jumlah round-trip antara LLM dan alat berkurang menjadi hanya 1 kali, waktu latensi juga terpangkas hampir separuhnya.
System prompt yang diberikan kepada LLM harus menerapkan batasan pipeline teks CLI secara tegas.
`text
You operate inside a sandboxed Linux Bash environment.
To process data files or API responses, follow these constraints:
`
Memasukkan data mentah ke dalam konteks saat menganalisis berkas CSV berisi 100.000 baris adalah tindakan membuang-buang uang. Dorong model untuk memahami struktur data terlebih dahulu menggunakan head -n 5, melakukan agregasi dengan awk atau python di dalam sandbox, lalu hanya mengembalikan satu baris hasil akhir ke model.
Jika Anda menyerahkan Bash kepada agen AI, agen tersebut pasti akan menghasilkan Exit Code 127 (Command Not Found) atau flag opsi yang salah.
Menurut laporan dari tim analisis kerangka kerja evaluasi agen PASTE, ketika eksekusi perintah gagal, menjalankan loop perbaikan otomatis alih-alih langsung mengakhiri sesi berhasil menekan tingkat kegagalan eksekusi hingga di bawah 5%.
`python
import re
from typing import Callable, Optional
class SelfHealingBashRunner:
def init(self, sandbox: EphemeralBashSandbox, llm_repair_fn: Callable[[str, str], str]):
self.sandbox = sandbox
self.llm_repair_fn = llm_repair_fn
self.max_retries = 3
def run_with_healing(self, initial_cmd: str) -> tuple[bool, str]:
current_cmd = initial_cmd
for attempt in range(self.max_retries):
exit_code, stdout, stderr = self.sandbox.execute_command(current_cmd)
if exit_code == 0:
return True, stdout
if exit_code == 127 or "command not found" in stderr.lower():
missing_binary = self._extract_missing_command(stderr)
if missing_binary:
install_success = self._try_install_package(missing_binary)
if install_success:
continue
repair_prompt = (
f"The executed Bash command failed.\n"
f"Failed Command: {current_cmd}\n"
f"Exit Code: {exit_code}\n"
f"Stderr Output: {stderr}\n"
f"Stdout Output: {stdout}\n"
f"Analyze the error. Return ONLY a corrected single-line Bash command to fulfill the objective."
)
current_cmd = self.llm_repair_fn(stderr, repair_prompt).strip()
return False, f"Failed after {self.max_retries} attempts. Last Stderr: {stderr}"
def _extract_missing_command(self, stderr: str) -> Optional[str]:
match = re.search(r"([a-zA-Z0-9_-]+):\s*command not found", stderr) or re.search(r"command not found:\s*([a-zA-Z0-9_-]+)", stderr)
return match.group(1) if match else None
def _try_install_package(self, binary_name: str) -> bool:
install_cmd = f"apt-get update && apt-get install -y {binary_name} || pip install {binary_name}"
exit_code, _, _ = self.sandbox.execute_command(install_cmd)
return exit_code == 0
`
Alur kerja dari modul pemulihan error ini sangat sederhana:
apt-get atau pip.stderr yang dihasilkan ke LLM agar LLM memperbaiki kodenya sendiri./usr/local/bin/ di dalam kontainer dengan memberikan hak akses eksekusi (chmod +x). Untuk penggunaan berikutnya, Anda cukup memanggil biner ini secara langsung sehingga tidak perlu menghabiskan token lagi.Jika skrip di dalam sandbox mengalami infinite loop, seluruh sesi akan terkunci. Oleh karena itu, Anda harus menerapkan batas waktu (timeout) secara bertingkat. Misalnya, menghentikan perintah biasa dalam 30 detik, instalasi paket dalam 60 detik, dan seluruh sesi dalam 300 detik. Jika batas waktu terlampaui, thread pemantau harus mengirimkan SIGKILL ke PID proses terkait untuk menghentikan proses bermasalah tersebut secara bersih.
Masalah lain yang sering terjadi adalah race condition saat beberapa instansi agen mengakses berkas volume terbagi yang sama secara bersamaan. Sekadar mengeksekusi open(path, 'w') dapat menyebabkan berkas terhapus menjadi 0 byte bahkan sebelum kunci (lock) diperoleh. Penguncian berbasis panggilan sistem fcntl.flock pada tingkat kernel POSIX mutlak diperlukan.
`python
import fcntl
import os
import time
from contextlib import contextmanager
class SafeFileLockTimeout(Exception):
pass
@contextmanager
def safe_file_lock(lock_file_path: str, timeout: float = 10.0, poll_interval: float = 0.05):
lock_dir = os.path.dirname(os.path.abspath(lock_file_path))
if lock_dir:
os.makedirs(lock_dir, exist_ok=True)
fd = os.open(lock_file_path, os.O_RDWR | os.O_CREAT, 0o666)
start_time = time.time()
try:
while True:
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except (OSError, IOError):
if time.time() - start_time >= timeout:
raise SafeFileLockTimeout(
f"Timed out after {timeout} seconds waiting for lock on: {lock_file_path}"
)
time.sleep(poll_interval)
yield fd
finally:
try:
fcntl.flock(fd, fcntl.LOCK_UN)
except (OSError, IOError):
pass
os.close(fd)
`
Poin utamanya adalah membuka berkas menggunakan os.open dengan flag os.O_RDWR | os.O_CREAT. Ini mencegah fenomena pemangkasan (truncation) isi berkas sebelum kunci berhasil diperoleh. Setelah itu, sistem mencoba penguncian asinkron dengan fcntl.LOCK_EX | fcntl.LOCK_NB, dan melempar pengecualian (exception) saat mencapai batas waktu untuk mencegah thread yang menunggu berhenti secara tanpa batas.