Prologue: ChatGPT에게 "블로그 글 써줘"라고 했을 때
ChatGPT에게 "블로그 글 써줘"라고 하면 글을 써준다. 하지만 그게 다다. 파일로 저장하지도, 이미지를 만들지도, 내 블로그에 올리지도 않는다. 그냥 답변 창에 텍스트를 출력할 뿐이다.
반면 Claude Code에게 같은 요청을 하면? 파일을 만들고, 필요하면 디렉토리 구조를 확인하고, 이미지 경로를 설정하고, 심지어 git commit까지 해준다. 이 차이가 바로 챗봇과 Agent의 차이다.
처음 이 차이를 체감했을 때, 너무 신기했다. "아, LLM이 답변만 하는 게 아니라 실제로 뭔가를 '할 수 있구나'라는 걸 깨달았다. 그리고 이게 가능한 구조가 궁금해졌다. Agent는 어떻게 스스로 계획하고, 도구를 쓰고, 작업을 완료하는 걸까?
결국 핵심은 루프였다. Agent는 한 번 답하고 끝이 아니라, 관찰하고 → 생각하고 → 행동하고 → 다시 관찰하는 사이클을 반복한다. 이 구조를 이해하니 Agent가 왜 강력한지, 언제 쓰고 언제 쓰지 말아야 하는지가 명확해졌다.
Aha! 순간 - Agent는 루프다
가장 와닿았던 순간은 이거였다. Agent는 함수가 아니라 While문이다.
일반적인 LLM 호출은 이렇다.
# 챗봇 - 한 번 호출, 한 번 응답
response = llm.chat("블로그 글 써줘")
print(response) # 끝
하지만 Agent는 이렇게 동작한다.
# Agent: 목표 달성까지 반복
goal = "블로그 글을 작성하고 파일로 저장해줘"
max_iterations = 10
for i in range(max_iterations):
# 1. Observe: 현재 상황 파악
observation = get_current_state()
# 2. Think: 다음에 뭘 해야 할지 계획
action = llm.plan(goal, observation, history)
# 3. Act: 도구 실행
result = execute_tool(action)
# 4. Check: 목표 달성했나?
if is_goal_achieved(goal, result):
break
이 루프 구조가 Agent의 본질이다. 단순히 질문-답변이 아니라, 목표를 향해 반복적으로 행동하는 시스템이다.
마치 길을 찾을 때처럼. 챗봇은 "지하철 3번 출구로 나가서 직진하세요"라고 답하지만, Agent는 실제로 걸으면서 "아, 여기는 막혔네. 다른 길로 가야겠다"라고 판단하고 경로를 수정한다.
Agent를 구성하는 핵심 요소들 뜯어보기
1. Tool Use: Agent가 세상과 상호작용하는 방법
Agent가 챗봇과 다른 결정적 이유는 도구를 사용할 수 있다는 점이다. LLM은 텍스트만 출력하지만, Agent는 함수를 호출하고, API를 쓰고, 파일을 읽고, 코드를 실행한다.
이게 가능한 건 Function Calling 덕분이다. OpenAI나 Anthropic 같은 LLM 제공자들은 모델이 "지금 이 함수를 이 파라미터로 호출해야 해"라고 structured output을 내보낼 수 있게 만들었다.
# Agent에게 제공하는 도구 정의
tools = [
{
"name": "write_file",
"description": "파일에 내용을 작성합니다",
"parameters": {
"path": "string",
"content": "string"
}
},
{
"name": "search_web",
"description": "웹에서 정보를 검색합니다",
"parameters": {
"query": "string"
}
}
]
# LLM이 도구 호출을 결정
response = llm.chat(
"블로그 글 써서 post.mdx에 저장해줘",
tools=tools
)
# LLM 응답 예시
{
"tool_call": {
"name": "write_file",
"arguments": {
"path": "post.mdx",
"content": "# My Blog Post\n..."
}
}
}
# 실제로 도구 실행
result = execute_tool(response.tool_call)
핵심은 LLM이 도구를 직접 실행하는 게 아니라, "이 도구를 이렇게 써야 한다"고 결정만 한다는 점이다. 실제 실행은 Agent 프레임워크가 한다. LLM은 뇌, 도구는 손과 발인 셈이다.
2. Planning: 복잡한 작업을 어떻게 쪼개는가
"블로그를 만들어줘"라는 요청을 받으면 Agent는 이걸 한 번에 처리할 수 없다. 대신 작은 단계들로 쪼갠다.
- 블로그 구조 파악 (디렉토리 읽기)
- 글 작성 계획 수립
- MDX 파일 생성
- 이미지 경로 확인
- 파일 저장
- 결과 검증
이런 계획을 세우는 방법은 크게 두 가지다.
방법 1: Zero-shot Planning - LLM이 한 번에 전체 계획을 세움
plan = llm.chat("""
목표: 블로그를 만들어줘
현재 상황: src/content/posts/ 디렉토리 존재
다음 단계들을 JSON으로 나열해줘:
""")
# 응답 예시
[
{"step": 1, "action": "read_directory", "args": {"path": "src/content/posts"}},
{"step": 2, "action": "write_file", "args": {"path": "new-post.mdx"}},
...
]
방법 2: ReAct Pattern - 생각(Reasoning)과 행동(Acting)을 번갈아 함
ReAct는 내가 가장 좋아하는 패턴이다. Agent가 매 단계마다 "왜 이걸 하는지" 설명하게 만든다.
# ReAct 루프
thoughts = []
for step in range(max_steps):
# Reasoning: 다음에 뭘 해야 하나?
thought = llm.chat(f"""
목표: {goal}
이전 행동들: {thoughts}
Thought: 지금 상황을 분석하고 다음 행동을 결정하세요.
""")
thoughts.append(thought)
# Acting: 도구 실행
if thought.contains_action():
action = parse_action(thought)
result = execute_tool(action)
thoughts.append(f"Observation: {result}")
ReAct의 강력한 점은 추론 과정이 명시적이라는 거다. Agent가 왜 그 선택을 했는지 볼 수 있다. 디버깅할 때 엄청 유용하다.
실제 ReAct 로그는 이렇게 생긴다.
Thought: 블로그 글을 작성하려면 먼저 기존 글들의 형식을 확인해야 한다.
Action: read_file("src/content/posts/example.mdx")
Observation: frontmatter에 title, date, tags가 필요하다는 걸 확인했다.
Thought: 이제 같은 형식으로 새 글을 작성할 수 있다.
Action: write_file("src/content/posts/new.mdx", content)
Observation: 파일이 성공적으로 생성되었다.
Thought: 목표를 달성했다.
3. Memory: Agent는 무엇을 기억하는가
Agent는 두 종류의 메모리를 쓴다.
Short-term Memory (대화 히스토리)
현재 작업의 맥락이다. "아까 뭐 했지?" 같은 거.
conversation_history = [
{"role": "user", "content": "블로그 글 써줘"},
{"role": "assistant", "content": "어떤 주제로 쓸까요?"},
{"role": "user", "content": "AI Agent에 대해"},
{"role": "assistant", "content": "Thought: AI Agent 주제로 글 작성 시작..."}
]
문제는 이게 계속 늘어난다는 거다. GPT-4의 context window가 128k 토큰이라고 해도, 긴 작업에서는 금방 찬다. 그래서 요약하거나 오래된 대화를 지운다.
Long-term Memory (벡터 DB)
과거 작업이나 지식을 저장한다. RAG(Retrieval Augmented Generation)를 쓴다.
# 과거 작업 저장
vector_db.store({
"content": "블로그 글 작성 시 frontmatter 형식",
"metadata": {"date": "2026-01-10", "task": "blog_writing"}
})
# 관련 정보 검색
relevant_memories = vector_db.search(
query="블로그 글 쓰는 방법",
top_k=3
)
# Agent에게 제공
context = "\n".join([m.content for m in relevant_memories])
prompt = f"관련 정보:\n{context}\n\n목표: {goal}"
Long-term memory 덕분에 Agent는 "지난번에 이렇게 했었지"라고 참고할 수 있다. 반복 작업에서 특히 강력하다.
4. Real Examples: 실제로 어떻게 쓰이나
Claude Code (내가 지금 쓰는 거)
코딩 작업을 자동화한다. "테스트 추가해줘"라고 하면:
- 코드베이스 읽기
- 테스트 파일 구조 파악
- 테스트 작성
- 실행해서 확인
- 실패하면 수정
- 성공할 때까지 반복
Devin
소프트웨어 엔지니어 Agent. GitHub issue를 주면:
- 코드베이스 이해
- 버그 원인 찾기
- 수정안 작성
- 테스트 실행
- PR 생성
AutoGPT
범용 목표 달성 Agent. "경쟁사 조사해서 보고서 만들어줘"라고 하면:
- 웹 검색
- 정보 수집
- 요약
- 문서 작성
- 저장
차이점은 도메인 특화 정도다. Claude Code는 코딩에 최적화된 도구들(파일 읽기/쓰기, git, 터미널)을 쓰고, AutoGPT는 범용 도구(웹 브라우징, 파일 시스템)를 쓴다.
5. 간단한 Agent 직접 만들기
개념을 확실히 이해하려면 직접 만들어봐야 한다. 아주 간단한 Agent를 만들어봤다.
import openai
import json
class SimpleAgent:
def __init__(self):
self.tools = {
"search": self.search_web,
"calculate": self.calculate,
"write_file": self.write_file
}
self.history = []
def run(self, goal, max_iterations=5):
"""Agent 메인 루프"""
print(f"Goal: {goal}\n")
for i in range(max_iterations):
print(f"--- Iteration {i+1} ---")
# 1. Think: 다음 행동 결정
response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": self.get_system_prompt()},
*self.history,
{"role": "user", "content": goal}
],
tools=self.get_tool_definitions()
)
message = response.choices[0].message
# 2. Act: 도구 호출이 있으면 실행
if message.tool_calls:
for tool_call in message.tool_calls:
tool_name = tool_call.function.name
tool_args = json.loads(tool_call.function.arguments)
print(f"Action: {tool_name}({tool_args})")
# 도구 실행
result = self.tools[tool_name](**tool_args)
print(f"Result: {result}\n")
# 히스토리에 추가
self.history.append({
"role": "assistant",
"content": None,
"tool_calls": [tool_call]
})
self.history.append({
"role": "tool",
"content": str(result),
"tool_call_id": tool_call.id
})
else:
# 도구 호출 없이 답변만 한 경우 = 작업 완료
print(f"Final Answer: {message.content}")
break
def get_system_prompt(self):
return """당신은 목표를 달성하기 위해 도구를 사용하는 Agent입니다.
주어진 도구들을 활용해 단계적으로 목표를 달성하세요.
도구가 더 이상 필요 없으면 최종 답변을 제공하세요."""
def get_tool_definitions(self):
return [
{
"type": "function",
"function": {
"name": "search",
"description": "웹에서 정보를 검색합니다",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "수식을 계산합니다",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string"}
},
"required": ["expression"]
}
}
}
]
def search_web(self, query):
# 실제로는 Google API 등을 호출
return f"'{query}'에 대한 검색 결과 (mock)"
def calculate(self, expression):
return eval(expression)
def write_file(self, path, content):
with open(path, 'w') as f:
f.write(content)
return f"파일 저장 완료: {path}"
# 실행
agent = SimpleAgent()
agent.run("서울의 인구가 부산의 몇 배인지 계산해서 result.txt에 저장해줘")
실행 결과는 이렇게 나온다.
Goal: 서울의 인구가 부산의 몇 배인지 계산해서 result.txt에 저장해줘
--- Iteration 1 ---
Action: search({'query': '서울 인구'})
Result: '서울 인구'에 대한 검색 결과 (mock)
--- Iteration 2 ---
Action: search({'query': '부산 인구'})
Result: '부산 인구'에 대한 검색 결과 (mock)
--- Iteration 3 ---
Action: calculate({'expression': '9720846 / 3404423'})
Result: 2.855
--- Iteration 4 ---
Action: write_file({'path': 'result.txt', 'content': '서울은 부산의 약 2.86배'})
Result: 파일 저장 완료: result.txt
--- Iteration 5 ---
Final Answer: 작업을 완료했습니다. 서울의 인구는 부산의 약 2.86배이며, 결과를 result.txt에 저장했습니다.
100줄도 안 되는 코드지만, 핵심은 다 들어있다. 루프, 도구 호출, 히스토리 관리. 이걸 확장하면 복잡한 Agent도 만들 수 있다.
6. Limitations: Agent가 완벽하지 않은 이유
Agent를 쓰면서 부딪힌 문제들이 있다.
Hallucination
LLM은 거짓말을 한다. 존재하지 않는 함수를 호출하려 하거나, 잘못된 파라미터를 넣는다.
# Agent가 호출하려는 도구
{"tool": "delete_all_files", "args": {}} # 이런 도구는 없다!
해결법은 도구 정의를 명확히 하고, validation을 추가하는 거다.
Infinite Loops
목표를 달성 못하면 계속 반복한다. "파일을 읽어봐" → "파일이 없어" → "다시 읽어봐" → 무한 반복.
해결법은 max_iterations를 설정하고, 같은 행동 반복 감지를 추가하는 거다.
if action in recent_actions[-3:]:
print("같은 행동을 반복하고 있습니다. 전략을 바꿔야 합니다.")
break
Cost
Agent는 비싸다. 한 번의 작업에 수십 번의 LLM 호출이 일어난다. GPT-4로 복잡한 작업 시키면 몇 달러가 순식간에 나간다.
해결법은 작은 모델 먼저 쓰기(GPT-3.5로 계획, GPT-4로 실행)나 캐싱이다.
When to use (and when NOT to use)
Agent가 항상 정답은 아니다. 내가 정리한 기준은 이렇다.
Agent를 쓸 때:
- 여러 단계가 필요한 작업 ("데이터 수집 → 분석 → 보고서 작성")
- 결과에 따라 다음 행동이 달라지는 경우 ("에러나면 재시도")
- 외부 도구/API를 써야 하는 경우
그냥 LLM 쓸 때:
- 단순 질문-답변 ("이 코드 설명해줘")
- 한 번에 끝나는 작업 ("이 텍스트 요약해줘")
- 비용/속도가 중요한 경우
비유하자면, "1+1이 뭐야?"에 계산기 Agent를 쓰는 건 오버킬이다. 그냥 LLM한테 물어보면 된다. 하지만 "우리 회사 재무제표 분석해서 투자 보고서 만들어줘"는 Agent가 적합하다.
Summary: Agent의 본질은 루프와 도구다
Agent를 공부하면서 가장 중요하게 깨달은 건 이거다.
Agent = LLM + Tools + Loop
LLM은 뇌다. 생각하고 판단한다. 하지만 행동은 못한다. Tools는 손과 발이다. 파일을 쓰고, API를 호출하고, 계산한다. Loop는 끈기다. 한 번 시도하고 끝이 아니라, 성공할 때까지 반복한다.
이 세 가지가 합쳐지면 "글 써줘"가 아니라 "글 써서 저장하고 배포해줘"가 가능해진다.
ChatGPT를 쓸 때는 "아 이거 편하네"였지만, Agent를 쓸 때는 "아 이제 AI가 실제로 일을 하는구나"였다. 이 차이가 앞으로 몇 년간 엄청난 변화를 만들 거라고 생각한다.
물론 Agent가 완벽하진 않다. 헛소리도 하고, 무한루프도 돌고, 돈도 많이 든다. 하지만 방향은 맞다. 앞으로 Agent는 더 똑똑해지고, 더 저렴해지고, 더 신뢰할 수 있게 될 거다.
그리고 그 Agent를 어떻게 설계하고 제어하느냐가 곧 AI 시대의 엔지니어링이 될 거라는 확신이 든다.