AI Agent를 직접 만들어봐야 하는 이유
You should write an agent
TL;DR Highlight
LLM agent가 놀라울 정도로 간단한 코드로 만들 수 있다는 걸 보여주는 실전 튜토리얼. 직접 만들어봐야 제대로 이해하고 판단할 수 있다는 게 핵심 메시지.
Who Should Read
LLM agent가 뭔지 개념적으로만 알고 있고, 직접 만들어본 적은 없는 개발자. 특히 agent 기술을 도입할지 말지 판단해야 하는 위치에 있는 사람.
Core Mechanics
- LLM agent의 핵심은 놀랍도록 단순하다. OpenAI API 호출 하나에 context 리스트를 관리하는 것만으로 ChatGPT와 동일한 대화형 앱을 15줄 안에 만들 수 있다.
- 흔히 복잡하게 느껴지는 'context window'는 사실 문자열 리스트에 불과하다. 사용자 입력과 LLM 응답을 리스트에 쌓아서 매번 통째로 보내는 게 전부고, LLM 자체는 stateless한 블랙박스다. 대화가 이어지는 건 우리가 만든 환상이다.
- Simon Willison의 정의에 따르면 agent는 (1) 루프 안에서 돌아가는 LLM + (2) 도구 사용, 이 두 가지를 만족해야 한다. 도구 정의도 JSON 스키마 하나로 끝나서 추가하기 매우 쉽다.
- 예제에서 ping 같은 시스템 명령을 tool로 정의해서 LLM이 '이 호스트에 ping 쳐줘'라고 판단하면 실제로 subprocess를 실행하는 방식을 보여준다. tool calling의 구조가 직관적이다.
- system prompt로 성격을 바꾸는 것도 간단하다. 예제에서 '진실만 말하는 Alph'와 '거짓만 말하는 Ralph' 두 context를 랜덤으로 전환하는 다중 성격 agent를 몇 줄로 구현했다.
- 글의 핵심 주장은 agent를 좋아하든 싫어하든, 직접 만들어봐야 제대로 판단할 수 있다는 것. 자전거 타기처럼 머리로만 이해할 수 없는 기술이라는 비유를 든다.
- GPT-5를 예제에 사용했지만, OpenAI 호환 API(OpenRouter 등)를 쓰면 모델과 프로바이더를 런타임에 자유롭게 교체할 수 있다. 인터페이스가 사실상 업계 표준이 됐다.
Evidence
- 2년 전에 PHP 25줄로 agent를 만들었는데 GPT-3.5에서도 잘 동작했다는 경험 공유가 있었다. LLM을 sed나 awk 같은 UNIX 텍스트 처리 도구로 보면 자연스럽게 합성·루프·분기를 적용할 수 있다는 관점이 공감을 얻었다.
- '직접 만드는 건 재밌지만 디버깅은 아무도 안 좋아한다'는 현실적 반론이 나왔다. 처음엔 마법 같지만 step 17에서 실패하고, 확률적이라 재현도 안 되고, 한 스텝에 0.5초씩 걸려서 실패 확인에 10-20분을 기다려야 한다는 경험담.
- agent가 결국 개발자를 대체할 도구인데 왜 스스로 더 잘 만들도록 도와야 하냐는 회의적 시각도 있었다. 지속 가능한 수익 모델 없이 AI 프로바이더들이 적자로 운영하는 상황에서 agent 기반 프로젝트를 시작하는 게 현실적인지 의문을 제기.
- OpenAI API가 tool calling을 깔끔하게 감싸주지만, 본질은 프롬프트에 '이런 도구가 있으니 쓰고 싶으면 특정 포맷으로 응답하고 멈춰라'라고 지시하는 것에 불과하다는 분석이 나왔다. 결국 토큰 입출력이 전부라는 점이 가려진다는 지적.
- 직접 코딩 agent를 만들고 스스로 개선하게 한 경험도 공유됐다. Cerebras처럼 빠른 추론 서비스를 쓰면 multi-turn tool call이 체감상 크게 개선되고, 메모리·음성 전사 등 기능을 각각 몇 분 만에 추가할 수 있었다고 한다.
How to Apply
- LLM agent 도입을 검토 중이라면, 글의 15줄 예제를 그대로 따라 치고 tool 하나(파일 읽기, DB 쿼리 등)를 붙여보면 agent의 실제 동작 원리와 한계를 30분 안에 체감할 수 있다.
- 이미 OpenAI API를 쓰고 있다면 OpenRouter를 연결해서 모델을 런타임에 교체하는 구조로 바꾸면, 비용이 민감한 작업에는 저렴한 모델, 정확도가 필요한 작업에는 frontier 모델을 쓰는 식으로 최적화할 수 있다.
- 사내 반복 작업(로그 분석, 배포 상태 확인 등)을 agent로 자동화할 때, tool을 UNIX 철학처럼 '한 가지만 잘하는' 단위로 쪼개면 보안 측면에서도 유리하고 디버깅도 쉬워진다.
- agent 디버깅이 어렵다는 점을 감안해서, 각 tool call의 입출력을 로깅하고 step별로 재현할 수 있는 구조를 처음부터 설계해두면 '확률적 실패'에 대응하기 훨씬 수월하다.
Code Example
snippet
from openai import OpenAI
client = OpenAI()
context = []
def call():
return client.responses.create(model="gpt-5", input=context)
def process(line):
context.append({"role": "user", "content": line})
response = call()
context.append({"role": "assistant", "content": response.output_text})
return response.output_text
# Tool 정의 예시
tools = [{
"type": "function",
"name": "ping",
"description": "ping some host on the internet",
"parameters": {
"type": "object",
"properties": {
"host": {"type": "string", "description": "hostname or IP"}
},
"required": ["host"]
}
}]Terminology
Context WindowLLM에게 보내는 대화 기록 전체. 채팅창에 이전 대화를 복붙해서 보내는 것과 같은 원리로, 이 창 크기에 따라 기억할 수 있는 대화 양이 정해진다.
Tool CallingLLM이 '이 함수를 이 인자로 실행해줘'라고 구조화된 형태로 요청하는 기능. 개발자가 도구 목록을 JSON으로 정의하면 LLM이 알아서 골라 쓴다.
Stateless이전 요청을 전혀 기억하지 못하는 상태. LLM API는 매번 새로운 요청처럼 처리하기 때문에, 대화 맥락은 호출하는 쪽에서 매번 붙여서 보내야 한다.
System PromptLLM에게 역할과 행동 규칙을 지정하는 숨겨진 지시문. 채팅 시작 전에 '너는 ~야'라고 알려주는 것.
OpenRouter여러 LLM 프로바이더(OpenAI, Anthropic, DeepSeek 등)를 하나의 통합 API로 접근할 수 있게 해주는 서비스. 모델을 런타임에 바꿀 수 있다.