DEV Community

matias yoon
matias yoon

Posted on

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

터미널 AI 에이전트 구축 (v9): 로컬 LLM 기반 개발자용 CLI AI 에이전트 만들기

터미널에서 직접 작동하는 AI 에이전트를 구축하는 것은 개발자에게 큰 생산성 향상을 제공합니다. 이번 가이드에서는 로컬 LLM을 기반으로 한 커스텀 CLI AI 에이전트를 구축하는 방법을 실습 중심으로 설명합니다.

1. CLI AI 에이전트 생태계 분석

현재 CLI AI 에이전트 시장에는 여러 솔루션이 존재합니다:

주요 도구들:

  • Aider: GitHub Copilot 기반, 실시간 코드 수정 기능
  • Continue.dev: VSCode 기반, 복잡한 작업 처리
  • OpenCode: 오픈소스, 간단한 코딩 도움
  • Custom Scripts: 직접 제작한 스크립트로 커스터마이징

현재 문제점:

  • 대부분의 도구는 클라우드 API에 의존
  • 로컬 실행 시 성능 저하
  • 복잡한 툴링 기능 부족
  • 비용 문제

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

로컬에서 LLM을 실행하려면 다음과 같은 단계를 따릅니다:

1. LM Studio 설치:

# macOS
brew install lm-studio

# 또는 직접 다운로드
wget https://github.com/lmstudio-ai/LMStudio/releases/latest/download/LMStudio-MacOS.dmg
Enter fullscreen mode Exit fullscreen mode

2. 로컬 모델 실행:

# 모델 다운로드 후 실행
lm-studio --model "Nous-Hermes-2-Mistral-7B-DPO.Q4_K_M.gguf"
Enter fullscreen mode Exit fullscreen mode

3. API 서버 설정:

# Ollama 설치
curl -fsSL https://ollama.com/install.sh | sh

# 모델 다운로드
ollama pull mistral

# API 서버 실행
ollama serve
Enter fullscreen mode Exit fullscreen mode

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

이제 기초적인 CLI 에이전트를 만들어보겠습니다:

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

class TerminalAIAgent:
    def __init__(self, model="ollama/mistral"):
        self.model = model
        self.client = openai.OpenAI(
            base_url="http://localhost:11434/v1",
            api_key="ollama"
        )
        self.conversation_history = []

    def run_command(self, command: str) -> str:
        """명령어 실행 및 결과 반환"""
        try:
            result = subprocess.run(
                command, 
                shell=True, 
                capture_output=True, 
                text=True,
                timeout=30
            )
            return result.stdout + result.stderr
        except Exception as e:
            return f"Error: {str(e)}"

    def get_context(self) -> str:
        """현재 작업 디렉토리 정보 수집"""
        pwd = os.getcwd()
        files = os.listdir(pwd)
        return f"Working directory: {pwd}\nFiles: {', '.join(files[:10])}"

    def chat(self, user_input: str) -> str:
        """AI와 대화"""
        self.conversation_history.append({
            "role": "user",
            "content": user_input
        })

        # 시스템 프롬프트
        system_prompt = """
        You are a helpful AI assistant that helps developers with coding tasks.
        Your responses should be concise and actionable.
        You can execute shell commands and modify files.
        """

        messages = [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"Context: {self.get_context()}"},
        ] + self.conversation_history

        response = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            temperature=0.3
        )

        ai_response = response.choices[0].message.content
        self.conversation_history.append({
            "role": "assistant",
            "content": ai_response
        })

        return ai_response

# 사용법
if __name__ == "__main__":
    agent = TerminalAIAgent()
    print("AI Agent 시작 (종료는 'exit' 입력)")

    while True:
        user_input = input("\n> ")
        if user_input.lower() == 'exit':
            break
        response = agent.chat(user_input)
        print(response)
Enter fullscreen mode Exit fullscreen mode

4. tmux와 통합

터미널 멀티플렉서와 통합하여 작업 흐름을 개선합니다:

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

# 세션 내에서 에이전트 실행
tmux send-keys -t ai_agent "python ai_agent.py" Enter
Enter fullscreen mode Exit fullscreen mode

tmux 스크립트:

# tmux_ai.sh
#!/bin/bash
SESSION="ai_agent"

# 세션 존재 여부 확인
if ! tmux has-session -t $SESSION 2>/dev/null; then
    tmux new-session -d -s $SESSION
    tmux send-keys -t $SESSION "python ai_agent.py" Enter
fi

tmux attach -t $SESSION
Enter fullscreen mode Exit fullscreen mode

5. 사용자 정의 도구 개발

코드 검색 도구:

# tools/code_search.py
import os
import re
from typing import List, Dict

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

    def search_in_files(self, pattern: str, file_extensions: List[str] = None) -> List[Dict]:
        """파일 내에서 패턴 검색"""
        results = []
        if file_extensions is None:
            file_extensions = ['.py', '.js', '.ts', '.java', '.cpp']

        for root, dirs, files in os.walk(self.root_dir):
            for file in files:
                if any(file.endswith(ext) for ext in file_extensions):
                    file_path = os.path.join(root, file)
                    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': 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])

# 사용 예제
search_tool = CodeSearchTool()
results = search_tool.search_in_files(r"def\s+(\w+)\s*\(")
print(json.dumps(results, indent=2))
Enter fullscreen mode Exit fullscreen mode

Git 도구:

# tools/git_tool.py
import subprocess
import json
from typing import Dict, List

class GitTool:
    def get_status(self) -> Dict:
        """Git 상태 확인"""
        try:
            result = subprocess.run(['git', 'status', '--porcelain'], 
                                  capture_output=True, text=True)
            return {
                'status': result.stdout.strip(),
                'has_changes': bool(result.stdout.strip())
            }
        except Exception as e:
            return {'error': str(e)}

    def get_branch_info(self) -> Dict:
        """브랜치 정보"""
        try:
            branch = subprocess.run(['git', 'branch', '--show-current'], 
                                   capture_output=True, text=True).stdout.strip()
            return {'branch': branch}
        except Exception as e:
            return {'error': str(e)}

    def commit_changes(self, message: str) -> Dict:
        """변경사항 커밋"""
        try:
            subprocess.run(['git', 'add', '.'], capture_output=True)
            result = subprocess.run(['git', 'commit', '-m', message], 
                                  capture_output=True, text=True)
            return {
                'success': True,
                'output': result.stdout,
                'error': result.stderr
            }
        except Exception as e:
            return {'success': False, 'error': str(e)}

# 사용 예제
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 os
import hashlib
from typing import List, Dict, Set

class ContextManager:
    def __init__(self, max_tokens: int = 8000):
        self.max_tokens = max_tokens
        self.token_cache = {}

    def calculate_tokens(self, text: str)

---

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

Top comments (0)