← 개발 로그 목록

local-agent: Ollama 기반 로컬 에이전트 초기 버전 구현

/ 10분 분량 / 개발 로그

이번 커밋은 Ollama를 백엔드로 사용하여 개인 에이전트 루프를 구축하는 초기 버전을 구현했습니다. `qwen2:7b` 모델을 활용하며, 쉘, HTTP, 데이터베이스 도구를 사용할 수 있습니다. 특히 위험한 작업(쉘 실행, DB 쓰기, GET이 아닌 HTTP 호출)에 대해서는 사용자 확인 단계를 추가하여 안전성을 높였습니다.

요약

이 커밋은 Ollama를 통해 로컬에서 실행되는 AI 에이전트의 첫 번째 버전을 선보입니다. qwen2:7b 모델을 사용하며, 쉘, HTTP, 데이터베이스 접근 기능을 제공합니다. 중요한 변경사항은 local_agent/agent.py 파일에 새로운 에이전트 로직이 추가되었고, README.md에 프로젝트 설명과 사용법이 상세히 기재되었다는 점입니다. 총 572라인의 코드가 추가되었습니다.

배경 및 목적

개인적인 프로젝트나 업무 자동화를 위해 로컬 환경에서 강력한 AI 에이전트를 구축하고자 했습니다. 외부 API에 의존하지 않고, 사용자의 컴퓨터에서 직접 작동하며, 다양한 작업을 수행할 수 있는 에이전트가 필요했습니다. 특히 AI가 임의의 코드를 실행하거나 중요한 데이터를 변경할 수 있는 가능성을 고려하여, 안전 장치를 마련하는 것이 중요한 목적이었습니다.

구현 내용

이번 작업에서는 주로 local_agent 디렉토리 내 여러 파일들을 수정하고 새로운 기능을 추가했습니다.

주요 변경사항

  • 에이전트 핵심 로직 구현 (local_agent/agent.py):
    • Ollama 클라이언트와 연동하여 사용자 프롬프트에 대한 응답을 생성하고, 필요한 경우 도구를 호출하는 로직을 구현했습니다.
    • run_agent_turn 함수는 사용자 턴을 처리하며, 최대 도구 호출 횟수를 제한하고, JSON 파싱 오류를 여러 번 연속으로 발생할 경우 대처하는 로직을 포함합니다.
    • confirm 함수를 사용하여 쉘 실행, DB 쓰기, GET 외 HTTP 호출과 같은 위험한 작업에 대한 사용자 확인 절차를 추가했습니다.
  • README 문서 작성 (README.md):
    • 프로젝트의 목표, 설정 방법, 실행 방법, 안전 모델(Confirmation model), 제공되는 도구 목록, 알려진 한계점, 확장 방법에 대한 상세한 설명을 추가했습니다.
  • CLI 인터페이스 구현 (local_agent/cli.py):
    • argparse를 사용하여 --yes (auto-approve), --model, --host, --max-hops 등의 커맨드 라인 인자를 지원하도록 구현했습니다.
    • Windows 환경에서 한글 깨짐을 방지하기 위해 표준 입출력 스트림의 인코딩을 UTF-8로 설정하는 로직을 추가했습니다.
  • 설정 로딩 및 관리 (local_agent/config.py):
    • 기본 설정값, YAML 설정 파일, 환경 변수, CLI 인자를 순서대로 적용하여 최종 설정을 로드하는 로직을 구현했습니다.
  • 도구 등록 및 관리 (local_agent/tools/__init__.py):
    • ToolSpec 데이터 클래스와 tool 데코레이터를 정의하여 새로운 도구를 쉽게 등록하고 관리할 수 있도록 했습니다.
    • Ollama API에서 요구하는 형식으로 도구 정의를 반환하는 ollama_tool_defs 함수를 추가했습니다.
    • shell, http, database 모듈을 임포트하여 해당 도구들이 자동으로 등록되도록 했습니다.

변경된 파일 목록

  • .gitignore
  • README.md
  • config.example.yaml
  • local_agent/__init__.py
  • local_agent/__main__.py
  • local_agent/agent.py
  • local_agent/cli.py
  • local_agent/config.py
  • local_agent/confirmation.py
  • local_agent/history.py
  • local_agent/ollama_client.py
  • local_agent/tools/__init__.py
  • local_agent/tools/database.py
  • local_agent/tools/http.py
  • local_agent/tools/shell.py
  • requirements.txt
  • tests/__init__.py
  • tests/test_confirmation.py
  • tests/test_registry.py

코드 라인 수

  • 총 추가 라인: 572
  • 총 삭제 라인: 0

핵심 코드 설명

local_agent/agent.py 파일의 run_agent_turn 함수는 이 커밋의 핵심 로직을 담고 있습니다.

def run_agent_turn(history: History, client: OllamaClient, auto_approve: bool, max_tool_hops: int = 8) -> bool:
    # ... (이전 부분 생략) ...

    for _hop in range(max_tool_hops):
        response = client.chat(messages=history.as_list(), tools=ollama_tool_defs())
        message = _get(response, "message")
        tool_calls = _get(message, "tool_calls")
        content = _get(message, "content") or ""

        if content:
            print(content)

        if not tool_calls:
            history.append({"role": "assistant", "content": content})
            return auto_approve

        history.append({"role": "assistant", "content": content, "tool_calls": tool_calls})

        # ... (도구 실행 및 결과 처리 로직) ...

        decision = confirm(name, args, auto_approve) # 사용자 확인
        if decision == "always":
            auto_approve = True
            decision = True

        if not decision:
            result = {"error": "user declined to execute this tool call"}
        else:
            try:
                result = spec.fn(**args) # 실제 도구 함수 실행
            except TypeError as e:
                result = {"error": f"invalid arguments for {name}: {e}"}
            except Exception as e:
                result = {"error": f"tool execution failed: {e}"}

        history.append_tool_result(name, result)

    # ... (이후 부분 생략) ...

이 코드는 Ollama 모델로부터 응답을 받고, tool_calls이 존재하면 각 도구 호출에 대해 confirm 함수를 통해 사용자 승인을 받습니다. 승인된 도구는 spec.fn(**args)를 통해 실행되며, 결과는 다시 history에 기록됩니다.

기술적 의사결정

이 단계에서는 특별히 복잡한 기술적 의사결정은 없었지만, 몇 가지 고려사항이 있었습니다.

  • LLM 백엔드 선택: Ollama를 선택한 이유는 로컬 환경에서 다양한 모델을 쉽게 실행하고 관리할 수 있다는 점 때문입니다. 외부 API 의존성을 줄이고 싶었고, Ollama는 이를 충족하는 좋은 솔루션이었습니다.
  • 도구 호출 방식: Ollama의 네이티브 tool_calling API를 활용하기로 결정했습니다. 이는 모델이 자체적으로 도구 사용을 판단하고 인자를 생성하도록 유도하는 효과적인 방법입니다.
  • 안전 기능 구현: 쉘 실행, DB 쓰기 등은 잠재적으로 위험할 수 있으므로, confirm 함수를 통해 명시적인 사용자 확인을 거치도록 구현했습니다. auto_approve 옵션을 통해 반복적인 확인을 건너뛸 수 있도록 하여 편의성도 고려했습니다.

배운 점 및 개선점

  • 배운 점:
    • LLM에게 도구 사용 능력을 부여하는 것은 매우 강력한 기능이며, 이를 통해 복잡한 작업을 자동화할 수 있다는 것을 배웠습니다.
    • 로컬 에이전트의 안전성을 확보하기 위해 사용자 확인 절차를 추가하는 것이 필수적임을 깨달았습니다.
    • Ollama와 같은 로컬 LLM 실행 환경의 편리함과 가능성을 경험했습니다.
  • 개선점:
    • 현재 query_database의 쓰기/읽기 구분은 정규식 기반으로 이루어져 있어, 복잡한 SQL 문법을 완벽하게 처리하지 못하는 한계가 있습니다. SQL 파서를 도입하여 더 정확하게 구분할 필요가 있습니다.
    • 쉘 명령 실행 시 샌드박싱이 되어있지 않아 보안에 취약할 수 있습니다. 추후 샌드박싱 기술 도입을 고려해볼 수 있습니다.
    • qwen2:7b 모델은 때때로 잘못된 도구 호출 JSON을 생성할 수 있습니다. 모델의 성능 향상이나 오류 처리 로직의 정교화가 필요할 수 있습니다.
  • 다음 단계 계획:
    • local_agent/tools 디렉토리에 더 많은 유용한 도구들을 추가할 예정입니다. (예: 파일 시스템 접근, 외부 API 호출 등)
    • 사용자 경험 개선을 위해 대화 기록 관리 기능을 더욱 강화할 것입니다.
    • 테스트 커버리지를 높여 안정성을 확보할 계획입니다.

참고 자료