
AI Agent: 자율적으로 작업을 수행하는 AI의 구조
ChatGPT는 질문에 답하지만, AI Agent는 스스로 계획하고 도구를 사용해 작업을 완료한다. 이 차이가 왜 중요한지 정리했다.

ChatGPT는 질문에 답하지만, AI Agent는 스스로 계획하고 도구를 사용해 작업을 완료한다. 이 차이가 왜 중요한지 정리했다.
왜 CPU는 빠른데 컴퓨터는 느릴까? 80년 전 고안된 폰 노이만 구조의 혁명적인 아이디어와, 그것이 남긴 치명적인 병목현상에 대해 정리했습니다.

둘 다 같은 Transformer 자식인데 왜 다를까? '빈칸 채우기'와 '이어 쓰기' 비유로 알아보는 BERT와 GPT의 결정적 차이. 프로젝트에서 겪은 시행착오와 선택 가이드.

결제 API 연동이 끝이 아니었다. 중복 결제 방지, 환불 처리, 멱등성까지 고려하니 결제 시스템이 왜 어려운지 뼈저리게 느꼈다.

직접 가기 껄끄러울 때 프록시가 대신 갔다 옵니다. 내 정체를 숨기려면 Forward Proxy, 서버를 보호하려면 Reverse Proxy. 같은 대리인인데 누구 편이냐가 다릅니다.

ChatGPT에게 "블로그 글 써줘"라고 하면 글을 써준다. 하지만 그게 다다. 파일로 저장하지도, 이미지를 만들지도, 내 블로그에 올리지도 않는다. 그냥 답변 창에 텍스트를 출력할 뿐이다.
반면 Claude Code에게 같은 요청을 하면? 파일을 만들고, 필요하면 디렉토리 구조를 확인하고, 이미지 경로를 설정하고, 심지어 git commit까지 해준다. 이 차이가 바로 챗봇과 Agent의 차이다.
처음 이 차이를 체감했을 때, 너무 신기했다. "아, LLM이 답변만 하는 게 아니라 실제로 뭔가를 '할 수 있구나'라는 걸 깨달았다. 그리고 이게 가능한 구조가 궁금해졌다. Agent는 어떻게 스스로 계획하고, 도구를 쓰고, 작업을 완료하는 걸까?
결국 핵심은 루프였다. Agent는 한 번 답하고 끝이 아니라, 관찰하고 → 생각하고 → 행동하고 → 다시 관찰하는 사이클을 반복한다. 이 구조를 이해하니 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가 챗봇과 다른 결정적 이유는 도구를 사용할 수 있다는 점이다. 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은 뇌, 도구는 손과 발인 셈이다.
"블로그를 만들어줘"라는 요청을 받으면 Agent는 이걸 한 번에 처리할 수 없다. 대신 작은 단계들로 쪼갠다.
이런 계획을 세우는 방법은 크게 두 가지다.
방법 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: 목표를 달성했다.
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는 "지난번에 이렇게 했었지"라고 참고할 수 있다. 반복 작업에서 특히 강력하다.
코딩 작업을 자동화한다. "테스트 추가해줘"라고 하면:
소프트웨어 엔지니어 Agent. GitHub issue를 주면:
범용 목표 달성 Agent. "경쟁사 조사해서 보고서 만들어줘"라고 하면:
차이점은 도메인 특화 정도다. Claude Code는 코딩에 최적화된 도구들(파일 읽기/쓰기, git, 터미널)을 쓰고, AutoGPT는 범용 도구(웹 브라우징, 파일 시스템)를 쓴다.
개념을 확실히 이해하려면 직접 만들어봐야 한다. 아주 간단한 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도 만들 수 있다.
Agent를 쓰면서 부딪힌 문제들이 있다.
HallucinationLLM은 거짓말을 한다. 존재하지 않는 함수를 호출하려 하거나, 잘못된 파라미터를 넣는다.
# 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를 쓸 때:비유하자면, "1+1이 뭐야?"에 계산기 Agent를 쓰는 건 오버킬이다. 그냥 LLM한테 물어보면 된다. 하지만 "우리 회사 재무제표 분석해서 투자 보고서 만들어줘"는 Agent가 적합하다.
Agent를 공부하면서 가장 중요하게 깨달은 건 이거다.
Agent = LLM + Tools + LoopLLM은 뇌다. 생각하고 판단한다. 하지만 행동은 못한다. Tools는 손과 발이다. 파일을 쓰고, API를 호출하고, 계산한다. Loop는 끈기다. 한 번 시도하고 끝이 아니라, 성공할 때까지 반복한다.
이 세 가지가 합쳐지면 "글 써줘"가 아니라 "글 써서 저장하고 배포해줘"가 가능해진다.
ChatGPT를 쓸 때는 "아 이거 편하네"였지만, Agent를 쓸 때는 "아 이제 AI가 실제로 일을 하는구나"였다. 이 차이가 앞으로 몇 년간 엄청난 변화를 만들 거라고 생각한다.
물론 Agent가 완벽하진 않다. 헛소리도 하고, 무한루프도 돌고, 돈도 많이 든다. 하지만 방향은 맞다. 앞으로 Agent는 더 똑똑해지고, 더 저렴해지고, 더 신뢰할 수 있게 될 거다.
그리고 그 Agent를 어떻게 설계하고 제어하느냐가 곧 AI 시대의 엔지니어링이 될 거라는 확신이 든다.