RicoCheesethe studio log · v2.0
Live · KRRead posts
목록으로
뉴스PUBLISHED · 2026년 7월 23일·11 MIN READ

AI 에이전트의 지저분한 비밀 — 프레임워크 없이 80줄로 직접 만들기

AI 에이전트는 프레임워크 없이도 만들 수 있다. 한 개발자가 Node.js와 Gemini API만으로 약 80줄짜리 코드 리뷰 에이전트를 만들며 에이전트의 실제 동작 원리를 공개했다.

#ai#llm#node#programming#tutorial
The Dirty Secret Behind AI Agents (Demo)

개요 #

AI 에이전트를 만들려면 LangChain 같은 프레임워크가 반드시 필요하다는 인식이 있다. 개발자 Sylwia Laskowska는 이 통념에 반박하며, Node.js와 Gemini API만으로 약 80줄짜리 핵심 루프를 가진 코드 리뷰 에이전트를 직접 만들어 공개했다.

핵심 주장은 단순하다. 프레임워크가 하는 일은 마법이 아니라 대화 기록 관리, 도구 실행, 재시도, 폴백 같은 편의 기능의 묶음이라는 것이다. 밑바닥 동작 원리를 이해하면 프레임워크가 실제로 필요한 시점을 스스로 판단할 수 있고, 프레임워크를 쓰더라도 내부에서 무슨 일이 벌어지는지 알 수 있게 된다.

데모 코드는 GitHub 저장소에 공개되어 있다.

15년 차 리뷰어 "Steve" #

데모 애플리케이션은 간단한 코드 리뷰 에이전트다. 작성자는 이 에이전트에 "Steve"라는 캐릭터를 부여했다. 15년 경력의 소프트웨어 엔지니어로, 코드를 절대 액면 그대로 받아들이지 않고 때로는 약간 냉소적이지만 결론에는 반박하기 어려운 리뷰어라는 설정이다.

Steve의 에이전트 코드 리뷰

Steve의 에이전트 코드 리뷰 - 2

빈 diff를 만났을 때의 반응은 이렇다.

빈 diff에 대한 리뷰

현재 Steve는 로컬 Git diff를 리뷰한다. 결국 자기 자신의 코드를 리뷰하는 셈인데, 작성자는 이를 두고 "자가 치유(self-healing) 에이전트의 프로토타입을 만든 것 같다"고 농담했다.

"에이전트는 그냥 while 루프다" #

작성자는 "AI 에이전트는 while 루프에 불과하다"는 흔한 농담을 언급하며, 자신의 에이전트는 while 루프조차 아니라고 밝혔다. 실수로 무한 루프에 빠져 토큰 비용을 낭비하지 않도록 for 루프를 썼다는 것이다.

루프 자체는 실제로 아무 일도 하지 않는다. 프로세스를 오케스트레이션하는 역할만 맡는다. 그런데 대부분의 에이전트 프레임워크가 내부에서 하는 일도 이와 놀랄 만큼 비슷하다는 게 작성자의 지적이다.

루프 작성은 사소한 일이었고, 진짜 과제는 다른 곳에 있었다. 먼저 LLM 선택. 이번에는 토큰 가격이 저렴한 Gemini API를 골랐다. 그런데 일부 Gemini 모델이 과부하 상태여서 503 응답을 계속 받는 문제에 부딪혔고, 결국 프레임워크가 기본 제공하는 기능 중 하나인 재시도(retry) 메커니즘을 직접 구현해야 했다. 데모의 재시도는 의도적으로 아주 단순하게 만들었다. 프로덕션 프레임워크는 보통 지수 백오프(exponential backoff), 지터(jitter), 다른 모델로의 자동 폴백처럼 훨씬 많은 것을 제공한다. 그다음 과제는 당연히 프롬프트 작성이었고, 그 이후로는 의외로 순조로웠다고 한다.

에이전트는 실제로 어떻게 동작하나 #

생각보다 훨씬 단순하다. 네 단계로 정리된다.

1단계: 프롬프트와 사용 가능한 도구 전송 #

모델에 두 가지를 보낸다. 사용자 메시지, 그리고 모델이 사용할 수 있는 도구 목록이다.

untitled
json
{
  "model": "gemini-2.5-flash",
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "Please review the current git diff."
        }
      ]
    }
  ],
  "config": {
    "systemInstruction": "You are Steve, a senior software engineer with 15 years of experience...",
    "tools": [
      {
        "functionDeclarations": [
          {
            "name": "getDiff",
            "description": "Get the git diff of the current repository...",
            "parameters": {
              "type": "OBJECT",
              "properties": {},
              "required": []
            }
          },
          {
            "name": "getFile",
            "description": "Read a file from the repository...",
            "parameters": {
              "type": "OBJECT",
              "properties": {
                "path": {
                  "type": "STRING",
                  "description": "Path to the file relative to the repository root"
                }
              },
              "required": ["path"]
            }
          },
          {
            "name": "listFiles",
            "description": "List files and directories at a given path...",
            "parameters": {
              "type": "OBJECT",
              "properties": {
                "path": {
                  "type": "STRING",
                  "description": "Directory path relative to the repository root"
                }
              },
              "required": ["path"]
            }
          }
        ]
      }
    ]
  }
}

2단계: 모델 응답 대기 #

모델은 세 가지 방식 중 하나로 응답한다. 일반 텍스트, 도구 호출 요청, 혹은 둘 다. 일반 텍스트가 오면 그게 최종 코드 리뷰이고 끝이다. 도구 호출을 요청하면 3단계로 넘어간다.

예를 들어 이런 응답을 받을 수 있다.

untitled
json
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "text": "Let's see what damage we're dealing with today..."
          },
          {
            "functionCall": {
              "id": "call_001",
              "name": "getDiff",
              "args": {}
            }
          }
        ]
      }
    }
  ]
}

3단계: 도구를 로컬에서 실행 #

이제 애플리케이션 차례다. 요청받은 도구를 로컬에서 실행한다. 예를 들어 git diff를 돌리거나 저장소의 파일을 읽은 뒤, 그 결과를 대화의 다음 메시지로 모델에 돌려보낸다.

이 데모가 제공하는 도구는 세 개다.

  • getDiff
  • getFile
  • listFiles

작성자는 좋은 코드 리뷰어에게 필요한 건 이게 전부라고 설명한다.

4단계: 모델이 끝낼 때까지 반복 #

그다음엔 2단계로 돌아간다. 무한 루프를 피하기 위해 최대 반복 횟수는 10회로 제한했다.

한 가지 중요한 디테일이 있다. 다음 요청을 보낼 때는 전체 대화 기록을 모델에 다시 보낸다는 점이다.

untitled
json
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "Please review the current git diff."
        }
      ]
    },
    {
      "role": "model",
      "parts": [
        {
          "text": "Let's see what damage we're dealing with today..."
        },
        {
          "functionCall": {
            "id": "call_001",
            "name": "getDiff",
            "args": {}
          }
        }
      ]
    },
    {
      "role": "user",
      "parts": [
        {
          "functionResponse": {
            "id": "call_001",
            "name": "getDiff",
            "response": {
              "result": "diff --git a/src/auth.ts b/src/auth.ts\n--- a/src/auth.ts\n+++ b/src/auth.ts\n@@ -12,7 +12,7 @@\n-  if (password === storedHash) {\n+  if (password == storedHash) {\n"
            }
          }
        }
      ]
    }
  ]
}

그게 전부다. 어떤 도구를 쓸지, 언제 끝낼지는 LLM이 결정한다. 도구 실행, 재시도 처리, 반복 횟수 제한, 루프 오케스트레이션 등 나머지는 모두 애플리케이션의 책임이다.

에이전트 동작 방식 시각화

모델은 함수를 호출해야 한다는 걸 어떻게 알까 #

한 가지 의문이 남는다. LLM은 결국 정교한 다음 토큰 예측기이지 마법의 JSON 생성기가 아닌데, JSON을 보내면 모델이 무엇을 해야 할지 어떻게 아는 걸까.

작성자에 따르면 모든 세부 사항이 공개된 것은 아니지만, GeminiGPT 같은 모델은 도구 호출(tool calling)을 네이티브로 지원한다. SDK는 요청을 올바른 형식으로 만들어 API와 통신하는 역할을 맡고, 모델 자체가 도구 선언을 이해해 필요하다고 판단하면 함수 호출을 생성한다.

바꿔 말하면, 오래된 Llama 모델을 쓰더라도 JSON을 해석하는 방법을 프롬프트로 설명하고 특정 JSON 형식으로 응답하라고 요청하는 식으로 비슷한 구조를 만들 수는 있다. 다만 모델이 유효한 JSON 대신 마크다운이나 "친절한" 부연 설명을 돌려줄 위험은 감수해야 한다. 최근에는 최신 오픈소스 모델들도 점점 도구 호출을 네이티브로 지원하는 추세다.

에이전트가 만든 에이전트 #

작성자는 이 에이전트를 상당 부분 다른 AI 에이전트가 작성했다고 솔직하게 밝혔다. AWS Community Builder 프로그램 덕분에 Claude Code 구독을 해지하고 Kiro로 갈아탔다는 것이다. Kiro는 Claude, GPT, Gemini 등 여러 LLM을 기본 지원하는 AI IDE로, VS Code 위에 에이전트 레이어를 얹은 느낌이라 바로 적응할 수 있었다고 한다.

후속 글로는 Steve를 MCP에 연결하는 이야기와, 호스팅 모델을 로컬 LLM으로 교체하는 이야기를 계획하고 있다.

정리 #

AI 에이전트가 반드시 프레임워크에서 출발할 필요는 없다. LLM, 도구, 대화 기록, 그리고 단순한 루프만 있으면 된다. 나머지는 편의 기능이자 프로덕션 강화, 삶의 질 개선일 뿐이라는 게 이 글의 결론이다.


이 글은 위 출처를 바탕으로 한국 독자를 위해 재작성한 기사입니다. 원문의 사실과 수치에 근거하며, 별도의 견해를 포함하지 않습니다.