Tools: Complete Guide to 터미널 AI 에이전트 구축 (v10)

Tools: Complete Guide to 터미널 AI 에이전트 구축 (v10)

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

1. CLI AI 에이전트 생태계

주요 도구 비교

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

LM Studio 설치

LocalAI 설치 (대안)

테스트 명령

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

4. tmux와 통합

5. 사용자 정의 도구 개발

코드 검색 도구

Git 통합 도구

6. 컨텍스트 윈도우 관리 터미널에서 작동하는 AI 에이전트를 직접 구축하는 것은 개발자에게 매우 실용적인 도구입니다. 이 가이드에서는 로컬 LLM을 활용한 터미널 AI 에이전트를 구축하고, 실제 개발 워크플로우에 적용하는 방법을 단계별로 안내합니다. 현재 CLI AI 에이전트 생태계는 여러 도구로 구성되어 있습니다: Aider: GitHub Copilot처럼 파일 편집 기능 제공 Continue.dev: VS Code 확장으로 개발자 경험 최적화 OpenCode: 오픈소스, 로컬 실행 가능한 에이전트 사용자 정의 스크립트: 최대한의 커스터마이징이 가능 로컬 LLM을 위한 API 서버를 구축하여 비용과 보안 문제를 해결합니다. 다음은 기능 호출을 사용하는 간단한 CLI 에이전트입니다: 터미널 multiplexer와 통합하여 에이전트를 더 효율적으로 사용합니다: Python 스크립트에서 tmux 통합: 📥 Get the full guide on Gumroad: https://gumroad.com/l/auto ($5) Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">pip -weight: 500;">install aider aider --help -weight: 500;">pip -weight: 500;">install aider aider --help -weight: 500;">pip -weight: 500;">install aider aider --help -weight: 500;">npm -weight: 500;">install -g continue -weight: 500;">npm -weight: 500;">install -g continue -weight: 500;">npm -weight: 500;">install -g continue -weight: 500;">git clone https://github.com/open-code/open-code.-weight: 500;">git cd open-code && python -m -weight: 500;">pip -weight: 500;">install -e . -weight: 500;">git clone https://github.com/open-code/open-code.-weight: 500;">git cd open-code && python -m -weight: 500;">pip -weight: 500;">install -e . -weight: 500;">git clone https://github.com/open-code/open-code.-weight: 500;">git cd open-code && python -m -weight: 500;">pip -weight: 500;">install -e . # macOS -weight: 500;">brew -weight: 500;">install lm-studio # 또는 직접 다운로드 -weight: 500;">curl -L https://github.com/lm-s Studio/lm-studio/releases/latest/download/LM-Studio-Mac.dmg -o lm-studio.dmg # macOS -weight: 500;">brew -weight: 500;">install lm-studio # 또는 직접 다운로드 -weight: 500;">curl -L https://github.com/lm-s Studio/lm-studio/releases/latest/download/LM-Studio-Mac.dmg -o lm-studio.dmg # macOS -weight: 500;">brew -weight: 500;">install lm-studio # 또는 직접 다운로드 -weight: 500;">curl -L https://github.com/lm-s Studio/lm-studio/releases/latest/download/LM-Studio-Mac.dmg -o lm-studio.dmg -weight: 500;">git clone https://github.com/go-skynet/LocalAI.-weight: 500;">git cd LocalAI -weight: 500;">docker build -t localai . -weight: 500;">docker run -p 8080:8080 -v $(pwd)/models:/models localai -weight: 500;">git clone https://github.com/go-skynet/LocalAI.-weight: 500;">git cd LocalAI -weight: 500;">docker build -t localai . -weight: 500;">docker run -p 8080:8080 -v $(pwd)/models:/models localai -weight: 500;">git clone https://github.com/go-skynet/LocalAI.-weight: 500;">git cd LocalAI -weight: 500;">docker build -t localai . -weight: 500;">docker run -p 8080:8080 -v $(pwd)/models:/models localai -weight: 500;">curl http://localhost:8080/v1/models -weight: 500;">curl http://localhost:8080/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "llama3", "prompt": "Hello, how are you?", "max_tokens": 100 }' -weight: 500;">curl http://localhost:8080/v1/models -weight: 500;">curl http://localhost:8080/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "llama3", "prompt": "Hello, how are you?", "max_tokens": 100 }' -weight: 500;">curl http://localhost:8080/v1/models -weight: 500;">curl http://localhost:8080/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "llama3", "prompt": "Hello, how are you?", "max_tokens": 100 }' # ai_agent.py import openai import subprocess import json import os from typing import List, Dict, Any class TerminalAgent: def __init__(self, api_key: str = None): self.client = openai.OpenAI( base_url="http://localhost:8080/v1", api_key="none" ) self.tools = [ { "type": "function", "function": { "name": "execute_shell", "description": "Shell 명령어 실행", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "실행할 명령어" } }, "required": ["command"] } } } ] def execute_shell(self, command: str) -> str: """Shell 명령어 실행""" 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 run(self, prompt: str) -> str: """AI 에이전트 실행""" messages = [ { "role": "user", "content": prompt } ] # 함수 호출 response = self.client.chat.completions.create( model="llama3", messages=messages, tools=self.tools, tool_choice="auto" ) # 결과 처리 tool_calls = response.choices[0].message.tool_calls if tool_calls: for tool_call in tool_calls: if tool_call.function.name == "execute_shell": args = json.loads(tool_call.function.arguments) output = self.execute_shell(args["command"]) messages.append({ "role": "tool", "content": output, "tool_call_id": tool_call.id }) # 최종 응답 요청 final_response = self.client.chat.completions.create( model="llama3", messages=messages ) return final_response.choices[0].message.content return response.choices[0].message.content # 사용법 if __name__ == "__main__": agent = TerminalAgent() result = agent.run("시스템 정보를 확인해주세요") print(result) # ai_agent.py import openai import subprocess import json import os from typing import List, Dict, Any class TerminalAgent: def __init__(self, api_key: str = None): self.client = openai.OpenAI( base_url="http://localhost:8080/v1", api_key="none" ) self.tools = [ { "type": "function", "function": { "name": "execute_shell", "description": "Shell 명령어 실행", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "실행할 명령어" } }, "required": ["command"] } } } ] def execute_shell(self, command: str) -> str: """Shell 명령어 실행""" 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 run(self, prompt: str) -> str: """AI 에이전트 실행""" messages = [ { "role": "user", "content": prompt } ] # 함수 호출 response = self.client.chat.completions.create( model="llama3", messages=messages, tools=self.tools, tool_choice="auto" ) # 결과 처리 tool_calls = response.choices[0].message.tool_calls if tool_calls: for tool_call in tool_calls: if tool_call.function.name == "execute_shell": args = json.loads(tool_call.function.arguments) output = self.execute_shell(args["command"]) messages.append({ "role": "tool", "content": output, "tool_call_id": tool_call.id }) # 최종 응답 요청 final_response = self.client.chat.completions.create( model="llama3", messages=messages ) return final_response.choices[0].message.content return response.choices[0].message.content # 사용법 if __name__ == "__main__": agent = TerminalAgent() result = agent.run("시스템 정보를 확인해주세요") print(result) # ai_agent.py import openai import subprocess import json import os from typing import List, Dict, Any class TerminalAgent: def __init__(self, api_key: str = None): self.client = openai.OpenAI( base_url="http://localhost:8080/v1", api_key="none" ) self.tools = [ { "type": "function", "function": { "name": "execute_shell", "description": "Shell 명령어 실행", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "실행할 명령어" } }, "required": ["command"] } } } ] def execute_shell(self, command: str) -> str: """Shell 명령어 실행""" 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 run(self, prompt: str) -> str: """AI 에이전트 실행""" messages = [ { "role": "user", "content": prompt } ] # 함수 호출 response = self.client.chat.completions.create( model="llama3", messages=messages, tools=self.tools, tool_choice="auto" ) # 결과 처리 tool_calls = response.choices[0].message.tool_calls if tool_calls: for tool_call in tool_calls: if tool_call.function.name == "execute_shell": args = json.loads(tool_call.function.arguments) output = self.execute_shell(args["command"]) messages.append({ "role": "tool", "content": output, "tool_call_id": tool_call.id }) # 최종 응답 요청 final_response = self.client.chat.completions.create( model="llama3", messages=messages ) return final_response.choices[0].message.content return response.choices[0].message.content # 사용법 if __name__ == "__main__": agent = TerminalAgent() result = agent.run("시스템 정보를 확인해주세요") print(result) # tmux 세션 생성 tmux new-session -d -s ai_agent # 에이전트 실행 tmux send-keys -t ai_agent "python ai_agent.py" Enter # 세션 연결 tmux attach -t ai_agent # tmux 세션 생성 tmux new-session -d -s ai_agent # 에이전트 실행 tmux send-keys -t ai_agent "python ai_agent.py" Enter # 세션 연결 tmux attach -t ai_agent # tmux 세션 생성 tmux new-session -d -s ai_agent # 에이전트 실행 tmux send-keys -t ai_agent "python ai_agent.py" Enter # 세션 연결 tmux attach -t ai_agent # tmux_integration.py import subprocess import json class TmuxAgent(TerminalAgent): def __init__(self, session_name: str = "ai_agent"): super().__init__() self.session_name = session_name def create_session(self): """tmux 세션 생성""" subprocess.run(["tmux", "new-session", "-d", "-s", self.session_name]) def send_command(self, command: str): """tmux 세션에 명령어 전송""" subprocess.run([ "tmux", "send-keys", "-t", self.session_name, command, "Enter" ]) def get_session_output(self): """세션 출력 가져오기""" result = subprocess.run([ "tmux", "capture-pane", "-p", "-t", self.session_name ], capture_output=True, text=True) return result.stdout # 사용법 agent = TmuxAgent("my_ai") agent.create_session() agent.send_command("ls -la") # tmux_integration.py import subprocess import json class TmuxAgent(TerminalAgent): def __init__(self, session_name: str = "ai_agent"): super().__init__() self.session_name = session_name def create_session(self): """tmux 세션 생성""" subprocess.run(["tmux", "new-session", "-d", "-s", self.session_name]) def send_command(self, command: str): """tmux 세션에 명령어 전송""" subprocess.run([ "tmux", "send-keys", "-t", self.session_name, command, "Enter" ]) def get_session_output(self): """세션 출력 가져오기""" result = subprocess.run([ "tmux", "capture-pane", "-p", "-t", self.session_name ], capture_output=True, text=True) return result.stdout # 사용법 agent = TmuxAgent("my_ai") agent.create_session() agent.send_command("ls -la") # tmux_integration.py import subprocess import json class TmuxAgent(TerminalAgent): def __init__(self, session_name: str = "ai_agent"): super().__init__() self.session_name = session_name def create_session(self): """tmux 세션 생성""" subprocess.run(["tmux", "new-session", "-d", "-s", self.session_name]) def send_command(self, command: str): """tmux 세션에 명령어 전송""" subprocess.run([ "tmux", "send-keys", "-t", self.session_name, command, "Enter" ]) def get_session_output(self): """세션 출력 가져오기""" result = subprocess.run([ "tmux", "capture-pane", "-p", "-t", self.session_name ], capture_output=True, text=True) return result.stdout # 사용법 agent = TmuxAgent("my_ai") agent.create_session() agent.send_command("ls -la") # code_search.py import os import re from pathlib import Path class CodeSearcher: def __init__(self, root_dir: str = "."): self.root_dir = Path(root_dir) self.supported_extensions = {'.py', '.js', '.ts', '.java', '.cpp', '.h'} def search_patterns(self, pattern: str, file_pattern: str = "*") -> List[Dict]: """정규식 패턴으로 코드 검색""" results = [] for file_path in self.root_dir.rglob(file_pattern): if file_path.suffix in self.supported_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.-weight: 500;">start()].count('\n') + 1, 'content': match.group() }) except Exception: continue return results def search_function(self, function_name: str) -> List[Dict]: """함수 정의 검색""" pattern = rf'def\s+{function_name}\s*\(' return self.search_patterns(pattern) # code_search.py import os import re from pathlib import Path class CodeSearcher: def __init__(self, root_dir: str = "."): self.root_dir = Path(root_dir) self.supported_extensions = {'.py', '.js', '.ts', '.java', '.cpp', '.h'} def search_patterns(self, pattern: str, file_pattern: str = "*") -> List[Dict]: """정규식 패턴으로 코드 검색""" results = [] for file_path in self.root_dir.rglob(file_pattern): if file_path.suffix in self.supported_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.-weight: 500;">start()].count('\n') + 1, 'content': match.group() }) except Exception: continue return results def search_function(self, function_name: str) -> List[Dict]: """함수 정의 검색""" pattern = rf'def\s+{function_name}\s*\(' return self.search_patterns(pattern) # code_search.py import os import re from pathlib import Path class CodeSearcher: def __init__(self, root_dir: str = "."): self.root_dir = Path(root_dir) self.supported_extensions = {'.py', '.js', '.ts', '.java', '.cpp', '.h'} def search_patterns(self, pattern: str, file_pattern: str = "*") -> List[Dict]: """정규식 패턴으로 코드 검색""" results = [] for file_path in self.root_dir.rglob(file_pattern): if file_path.suffix in self.supported_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.-weight: 500;">start()].count('\n') + 1, 'content': match.group() }) except Exception: continue return results def search_function(self, function_name: str) -> List[Dict]: """함수 정의 검색""" pattern = rf'def\s+{function_name}\s*\(' return self.search_patterns(pattern) # git_tools.py import subprocess import json from typing import List, Dict class GitTools: @staticmethod def get_git_status() -> str: """Git 상태 확인""" try: result = subprocess.run( ['-weight: 500;">git', '-weight: 500;">status', '--porcelain'], capture_output=True, text=True ) return result.stdout.strip() except: return "Git repository not found" @staticmethod def get_recent_commits(limit: int = 5) -> List[Dict]: """최근 커밋 정보""" try: result = subprocess.run([ '-weight: 500;">git', 'log', '--oneline', f'-{limit}' ], capture_output=True, text=True) commits = [] for line in result.stdout.strip().split('\n'): if line.strip(): parts = line.split(' ', 1) commits.append({ 'hash': parts[0], 'message': parts[1] if len(parts) > 1 else '' }) return commits except: return [] @staticmethod def get_changed_files() -> List[str]: """변경된 파일 목록""" try: result = subprocess.run([ '-weight: 500;">git', 'diff', '--name-only', 'HEAD~1' ], capture_output=True, text=True) return result.stdout.strip().split('\n') except: return [] # git_tools.py import subprocess import json from typing import List, Dict class GitTools: @staticmethod def get_git_status() -> str: """Git 상태 확인""" try: result = subprocess.run( ['-weight: 500;">git', '-weight: 500;">status', '--porcelain'], capture_output=True, text=True ) return result.stdout.strip() except: return "Git repository not found" @staticmethod def get_recent_commits(limit: int = 5) -> List[Dict]: """최근 커밋 정보""" try: result = subprocess.run([ '-weight: 500;">git', 'log', '--oneline', f'-{limit}' ], capture_output=True, text=True) commits = [] for line in result.stdout.strip().split('\n'): if line.strip(): parts = line.split(' ', 1) commits.append({ 'hash': parts[0], 'message': parts[1] if len(parts) > 1 else '' }) return commits except: return [] @staticmethod def get_changed_files() -> List[str]: """변경된 파일 목록""" try: result = subprocess.run([ '-weight: 500;">git', 'diff', '--name-only', 'HEAD~1' ], capture_output=True, text=True) return result.stdout.strip().split('\n') except: return [] # git_tools.py import subprocess import json from typing import List, Dict class GitTools: @staticmethod def get_git_status() -> str: """Git 상태 확인""" try: result = subprocess.run( ['-weight: 500;">git', '-weight: 500;">status', '--porcelain'], capture_output=True, text=True ) return result.stdout.strip() except: return "Git repository not found" @staticmethod def get_recent_commits(limit: int = 5) -> List[Dict]: """최근 커밋 정보""" try: result = subprocess.run([ '-weight: 500;">git', 'log', '--oneline', f'-{limit}' ], capture_output=True, text=True) commits = [] for line in result.stdout.strip().split('\n'): if line.strip(): parts = line.split(' ', 1) commits.append({ 'hash': parts[0], 'message': parts[1] if len(parts) > 1 else '' }) return commits except: return [] @staticmethod def get_changed_files() -> List[str]: """변경된 파일 목록""" try: result = subprocess.run([ '-weight: 500;">git', 'diff', '--name-only', 'HEAD~1' ], capture_output=True, text=True) return result.stdout.strip().split('\n') except: return []