DEV Community

matias yoon
matias yoon

Posted on

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

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

터미널 기반 AI 에이전트는 개발자에게 매우 실용적인 도구로 자리 잡았습니다. 다양한 CLI 기반 AI 도구들 중에서 가장 효율적인 방식으로 개발자 워크플로우를 개선할 수 있는 방법을 소개합니다.

1. CLI AI 에이전트 생태계

현재 CLI AI 에이전트 시장은 다음과 같은 주요 도구들로 구성되어 있습니다:

Aider

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

Continue.dev

npm install -g continue
Enter fullscreen mode Exit fullscreen mode

OpenCode

git clone https://github.com/opencode-dev/opencode
cd opencode && pip install -e .
Enter fullscreen mode Exit fullscreen mode

이들 도구들은 모두 자체적으로 파일 편집, 코드 생성, git 통합 기능을 제공하지만, 커스터마이징과 성능 최적화에 있어 한계가 있습니다.

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

로컬 LLM을 사용하면 보안성과 성능을 높일 수 있습니다:

Ollama 설치

# Ubuntu/Debian
curl -fsSL https://ollama.com/install.sh | sh

# macOS
brew install ollama

# 시작
ollama serve
Enter fullscreen mode Exit fullscreen mode

모델 다운로드 및 실행

# llama3.2 모델 다운로드
ollama pull llama3.2

# 로컬 API 시작
ollama run llama3.2
Enter fullscreen mode Exit fullscreen mode

API 테스트

# curl로 테스트
curl http://localhost:11434/api/generate \
  -H "Content-Type: application/json" \
  -d '{
    "model": "llama3.2",
    "prompt": "Hello, how are you?",
    "stream": false
  }'
Enter fullscreen mode Exit fullscreen mode

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

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

class TerminalAgent:
    def __init__(self, model="llama3.2"):
        self.model = model
        self.client = openai.OpenAI(
            base_url="http://localhost:11434/v1",
            api_key="ollama"
        )
        self.context = []

    def call_llm(self, prompt: str) -> str:
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7
        )
        return response.choices[0].message.content

    def execute_command(self, command: str) -> str:
        try:
            result = subprocess.run(
                command, 
                shell=True, 
                capture_output=True, 
                text=True
            )
            return result.stdout + result.stderr
        except Exception as e:
            return f"Error: {str(e)}"

    def get_file_content(self, filepath: str) -> str:
        try:
            with open(filepath, 'r') as f:
                return f.read()
        except Exception as e:
            return f"Error reading file: {str(e)}"

    def save_file(self, filepath: str, content: str) -> bool:
        try:
            with open(filepath, 'w') as f:
                f.write(content)
            return True
        except Exception as e:
            print(f"Error saving file: {str(e)}")
            return False

# 사용 예시
if __name__ == "__main__":
    agent = TerminalAgent()

    # 코드 생성 예시
    prompt = """
    Create a Python function that calculates the fibonacci sequence up to n terms.
    Return the list of numbers.
    """

    response = agent.call_llm(prompt)
    print("Generated code:")
    print(response)
Enter fullscreen mode Exit fullscreen mode

4. tmux와의 통합

터미널 에이전트는 tmux와 함께 사용될 때 더욱 강력합니다:

# tmux 세션 생성
tmux new-session -s ai_agent -d

# 세션에 명령 실행
tmux send-keys -t ai_agent "python aider_agent.py" Enter

# 세션에 파일 생성
tmux send-keys -t ai_agent "touch test.py" Enter

# 세션 정보 확인
tmux list-sessions
Enter fullscreen mode Exit fullscreen mode

tmux 자동화 스크립트

#!/bin/bash
# setup_tmux.sh

SESSION_NAME="ai_dev"
tmux new-session -s $SESSION_NAME -d

# 메인 창 생성
tmux new-window -t $SESSION_NAME:1 -n "main"
tmux send-keys -t $SESSION_NAME:1 "cd ~/projects/myproject" Enter

# 터미널 창 생성
tmux new-window -t $SESSION_NAME:2 -n "terminal"
tmux send-keys -t $SESSION_NAME:2 "python aider_agent.py" Enter

# 에이전트 창 생성
tmux new-window -t $SESSION_NAME:3 -n "agent"
tmux send-keys -t $SESSION_NAME:3 "watch -n 1 'ls -la'" Enter

# 세션에 연결
tmux attach -t $SESSION_NAME
Enter fullscreen mode Exit fullscreen mode

5. 커스텀 도구 개발

코드 검색 도구

# tools/code_search.py
import os
import re

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

    def search_in_files(self, pattern: str, file_extensions: list = None) -> List[Dict]:
        results = []
        if file_extensions is None:
            file_extensions = ['.py', '.js', '.ts', '.java', '.cpp']

        for root, dirs, files in os.walk(self.project_root):
            for file in files:
                if any(file.endswith(ext) for ext in file_extensions):
                    filepath = os.path.join(root, file)
                    try:
                        with open(filepath, 'r') as f:
                            content = f.read()
                            matches = re.finditer(pattern, content)
                            for match in matches:
                                results.append({
                                    'file': filepath,
                                    'line': content[:match.start()].count('\n') + 1,
                                    'context': self.get_context(content, match.start())
                                })
                    except Exception as e:
                        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])

# 사용 예시
searcher = CodeSearchTool("/path/to/project")
results = searcher.search_in_files(r"def.*function_name")
Enter fullscreen mode Exit fullscreen mode

Git 통합 도구

# tools/git_tool.py
import subprocess
import json

class GitTool:
    def __init__(self):
        pass

    def get_status(self) -> Dict:
        result = subprocess.run(['git', 'status', '--porcelain'], 
                               capture_output=True, text=True)
        return {"status": result.stdout.strip()}

    def get_diff(self) -> str:
        result = subprocess.run(['git', 'diff'], 
                               capture_output=True, text=True)
        return result.stdout

    def commit_changes(self, message: str) -> bool:
        try:
            subprocess.run(['git', 'add', '.'], check=True)
            subprocess.run(['git', 'commit', '-m', message], check=True)
            return True
        except subprocess.CalledProcessError:
            return False

    def get_branch_info(self) -> Dict:
        result = subprocess.run(['git', 'branch', '--show-current'], 
                               capture_output=True, text=True)
        return {"branch": result.stdout.strip()}

# 사용 예시
git_tool = GitTool()
status = git_tool.get_status()
print(json.dumps(status, indent=2))
Enter fullscreen mode Exit fullscreen mode

6. 컨텍스트 윈도우 관리

대규모 코드베이스에서는 컨텍스트 윈도우 관리가 중요합니다:


python
# context_manager.py
import tiktoken
from typing import List, Dict

class ContextManager:
    def __init__(self, model_name: str = "gpt-4"):
        self.encoding = tiktoken.encoding_for_model(model_name)
        self.max_tokens = 8192  # 8K 토큰 제한

    def count_tokens(self, text: str) -> int:
        return len(self.encoding.encode(text))

    def truncate_context(self, context: List[Dict], max_tokens: int = None) -> List[Dict]:
        if max_tokens is None:
            max_tokens = self.max_tokens

        truncated = []
        current_tokens = 0

        # 최근 항목부터 처리
        for item in reversed(context):
            item_tokens = self.count_tokens(str(item))
            if current_tokens + item_tokens <= max_tokens:
                truncated.append(item)
                current_tokens += item_tokens
            else:
                break

        return list(reversed(truncated))

    def add_context

---

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

Top comments (0)