DEV Community

matias yoon
matias yoon

Posted on

터미널 AI 에이전트 구축 (v22)

터미널 AI 에이전트 구축 (v22)

터미널에서 작동하는 AI 에이전트를 구축하는 것은 현대 개발 워크플로우에서 점점 더 중요해지고 있습니다. 이 가이드에서는 개발자들이 실제 사용할 수 있는 터미널 AI 에이전트를 구축하고 최적화하는 방법을 설명합니다.

1. CLI AI 에이전트 랜드스케이프

현재 CLI AI 에이전트 시장에는 여러 선택지가 있습니다:

Aider: GitHub의 코드 리뷰 도우미로, 명령줄에서 직접 코드를 생성하고 수정할 수 있습니다.

pip install aider
aider --help
Enter fullscreen mode Exit fullscreen mode

Continue.dev: VS Code 확장이지만 터미널에서도 사용 가능하며, GitHub Copilot과 유사한 기능을 제공합니다.

OpenCode: 오픈소스 CLI 에이전트로, 사용자 정의 기능을 쉽게 추가할 수 있습니다.

커스텀 스크립트: 직접 작성한 Python 스크립트로 모든 기능을 제어할 수 있습니다.

2. 로컬 LLM API 엔드포인트 설정

로컬에서 LLM을 실행하기 위해는 FastAPI 또는 Flask를 사용한 간단한 API 서버가 필요합니다:

# llm_server.py
from fastapi import FastAPI, Request
import asyncio
from transformers import AutoTokenizer, AutoModelForCausalLM

app = FastAPI()
model_name = "gpt2"  # 또는 local model 경로
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

@app.post("/generate")
async def generate_text(request: Request):
    data = await request.json()
    prompt = data["prompt"]
    inputs = tokenizer.encode(prompt, return_tensors="pt")
    outputs = model.generate(inputs, max_length=100)
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return {"response": response}
Enter fullscreen mode Exit fullscreen mode

실행:

pip install fastapi uvicorn transformers
uvicorn llm_server:app --host 0.0.0.0 --port 8000
Enter fullscreen mode Exit fullscreen mode

3. 간단한 Python CLI 에이전트 구축

다음은 함수 호출 기능을 포함한 기본 CLI 에이전트입니다:

# cli_agent.py
import openai
import json
import subprocess
from typing import List, Dict

class TerminalAgent:
    def __init__(self, api_key: str, model: str = "gpt-4"):
        self.client = openai.OpenAI(api_key=api_key)
        self.model = model
        self.tools = {
            "run_command": self.run_command,
            "read_file": self.read_file,
            "write_file": self.write_file
        }

    def run_command(self, command: str) -> str:
        """명령어 실행"""
        try:
            result = subprocess.run(
                command, 
                shell=True, 
                capture_output=True, 
                text=True,
                timeout=30
            )
            return f"stdout: {result.stdout}\nstderr: {result.stderr}"
        except subprocess.TimeoutExpired:
            return "Command timed out"

    def read_file(self, filename: str) -> str:
        """파일 읽기"""
        try:
            with open(filename, 'r') as f:
                return f.read()
        except Exception as e:
            return f"Error reading file: {str(e)}"

    def write_file(self, filename: str, content: str) -> str:
        """파일 쓰기"""
        try:
            with open(filename, 'w') as f:
                f.write(content)
            return f"File {filename} written successfully"
        except Exception as e:
            return f"Error writing file: {str(e)}"

    def call_tool(self, tool_name: str, arguments: Dict) -> str:
        """도구 호출"""
        if tool_name in self.tools:
            return self.tools[tool_name](**arguments)
        return f"Unknown tool: {tool_name}"

    def chat(self, message: str, context: str = "") -> str:
        """채팅 메시지 처리"""
        system_prompt = """
        You are a helpful AI coding assistant that can execute commands, 
        read/write files, and help with code development.
        Available tools:
        1. run_command(command) - 실행 가능한 명령어
        2. read_file(filename) - 파일 내용 읽기
        3. write_file(filename, content) - 파일 내용 쓰기
        """

        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": f"Context: {context}\nQuestion: {message}"}
            ],
            functions=[
                {
                    "name": "run_command",
                    "description": "Execute shell command",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "command": {"type": "string"}
                        },
                        "required": ["command"]
                    }
                },
                {
                    "name": "read_file",
                    "description": "Read file content",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "filename": {"type": "string"}
                        },
                        "required": ["filename"]
                    }
                },
                {
                    "name": "write_file",
                    "description": "Write content to file",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "filename": {"type": "string"},
                            "content": {"type": "string"}
                        },
                        "required": ["filename", "content"]
                    }
                }
            ],
            function_call="auto"
        )

        message = response.choices[0].message
        if message.function_call:
            tool_name = message.function_call.name
            arguments = json.loads(message.function_call.arguments)
            result = self.call_tool(tool_name, arguments)
            return result

        return message.content

# 사용 예시:
if __name__ == "__main__":
    agent = TerminalAgent("your-api-key")
    response = agent.chat("Show me the current directory contents")
    print(response)
Enter fullscreen mode Exit fullscreen mode

4. tmux와 통합

터미널 multiplexer를 활용해 에이전트와 상호작용할 수 있습니다:

# tmux 세션 생성
tmux new-session -s ai-agent -d

# 세션에 명령어 실행
tmux send-keys -t ai-agent "python cli_agent.py" Enter

# 세션에 명령어 전송
tmux send-keys -t ai-agent "ls -la" Enter
Enter fullscreen mode Exit fullscreen mode

5. 커스텀 도구 개발

다음은 코드 검색 및 Git 연동 도구입니다:


python
# custom_tools.py
import os
import re
from pathlib import Path

class CodeSearchTool:
    def __init__(self, project_root: str = "."):
        self.project_root = Path(project_root)

    def find_files(self, pattern: str, extensions: List[str] = None) -> List[str]:
        """파일 검색"""
        results = []
        for file_path in self.project_root.rglob("*"):
            if file_path.is_file() and (not extensions or file_path.suffix in extensions):
                if re.search(pattern, str(file_path)):
                    results.append(str(file_path))
        return results

    def search_code(self, pattern: str, file_extensions: List[str] = None) -> List[Dict]:
        """코드 검색"""
        results = []
        extensions = file_extensions or [".py", ".js", ".ts", ".java"]

        for file_path in self.project_root.rglob("*"):
            if file_path.is_file() and file_path.suffix in extensions:
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                        matches = re.finditer(pattern, content)
                        for match in matches:
                            results.append({
                                "file": str(file_path),
                                "line": content[:match.start()].count('\n') + 1,
                                "context": self.get_context(content, match.start())
                            })
                except Exception:
                    continue
        return results

    def get_context(self, content: str, position: int, context_lines: int = 3) -> str:
        """문맥 가져오기"""
        lines = content.split('\n')
        line_num = content[:position].count('\n')
        start = max(0, line_num - context_lines)
        end = min(len(lines), line_num + context_lines + 1)
        return '\n'.join(lines[start:end])

class GitTool:
    @staticmethod
    def get_status() -> str:
        """Git 상태 조회"""
        result = subprocess.run(['git', 'status', '--porcelain'], 
                              capture_output=True, text=True)
        return result.stdout

    @staticmethod
    def get_diff() -> str:
        """Git 변경 사항"""
        result = subprocess.run(['git', 'diff'], 
                              capture_output=True, text=True)
        return result.stdout

    @staticmethod
    def commit_changes(message: str) -> str:
        """Git 커밋"""
        subprocess.run(['git

---

📥 **Get the full guide on Gumroad**: https://gumroad.com/l/auto ($5)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)