| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |
- Mutable Default Argument
- __repr()
- 파워포인트) #집(zip)파일 #아래한글(HWP) #brute-force(무차별 대입)
- #선진국 대한민국 #선진국 #대한민국 #아이들 #청소년 #고민 #해결 #심리
- #다산 정약용 #유배지에서 보낸 편지 #도덕 #용기 #염 #주역 #호연지기 #효제 #근검
- AI Agent
- agent history
- try / except / else / finally
- LLM
- list comprehension
- 재시도 retry
- key error
- 예외처리 exception
- 파이썬
- 티스토리챌린지
- dictionary vs class
- JSON
- 오블완
- xls
- pathlib.Path
- # 암호(비밀번호) 분실 # 암호(비밀번호) 찾기 #오피스(doc
- ppt) 파일 #오피스(워드
- 에이전트
- 인공지능
- Agent Persistence
- AI
- 엑셀
- 로그 파일(LOG FILE)
- Agent class
- keyword argument
- Today
- Total
아톨러브
X. Agent의 여러 요소를 하나의 class Agent로 묶어가는 과정. 본문
#-------- 1. Dictionary와 Class의 차이
# 다음과 같은 경우 딕셔너리를 사용하세요:
# 단순히 데이터일 때
# JSON에 바로 전달할 때
# 별도의 규칙을 적용할 필요가 없을 때
# 다음과 같은 경우 클래스를 사용하세요:
# 데이터와 동작이 함께 있어야 할 때
# 여러 개의 독립적인 복사본이 필요할 때
# 유효하지 않은 상태를 방지해야 할 때
#-------- 2. 첫 번째 Agent 클래스(Agent라는 새로운 자료형 만들기): __init__()와 self
class Agent:
def __init__(self, name, max_steps=5):
self.name = name
self.max_steps = max_steps
self.history = []
self.steps = 0
def add_message(self, role, content):
self.history.append({"role":role, "content":content})
return len(self.history)
def budget_left(self):
return self.max_steps - self.steps
scout = Agent("Scout", max_steps=3)
scout.add_message("user","송장의 총금액을 확인하세요.")
scout.steps += 1
print(scout.name, "는", scout.budget_left(), "단계가 남았습니다.")
print("history: ", scout.history)
#-------- 3. 여러 개의 독립적인 Agent: 객체마다 독립적인 상태 유지
class Agent:
def __init__(self, name):
self.name = name
self.history = []
def add_message(self, role, content):
self.history.append({"role":role, "content":content})
support = Agent("support")
research = Agent("research")
support.add_message("user", "내 주문서는 어디에 있죠?")
support.add_message("user", "이 보고서를 요약해주세요.")
research.add_message("assistant", "세개의 주요사항을 찾았습니다.")
print(support.name, len(support.history))
print(research.name, len(research.history))
#-------- 4. 매우 중요한 Class 변수 vs Instance 변수 차이
class Bad:
history = []
def add(self, item):
self.history.append(item)
class Good:
def __init__(self):
self.history = []
def add(self, item):
self.history.append(item)
a, b = Bad(), Bad()
a.add("one")
b.add("two")
print("Bad: ", a.history, b.history)
c, d = Good(), Good()
c.add("one")
d.add("two")
print("Good: ", c.history, d.history)
#-------- 5. __repr__()
class Agent:
def __init__(self, name, max_steps=5):
self.name=name
self.max_steps=max_steps
self.steps=0
self.history=[]
def __repr__(self):
return f"Agent(name={self.name!r}, steps={self.steps}/{self.max_steps}, msgs={len(self.history)})"
a = Agent("Scout")
a.steps = 2
a.history = [{"role":"user", "content":"안녕!"}]
print(a)
print([a, Agent("Backup", max_steps=2)])
#-------- 6. dataclass
from dataclasses import dataclass, field
@dataclass
class ToolResult:
name:str
ok:bool
output:str=""
meta:dict = field(default_factory=dict)
r1 = ToolResult("search", True, "3 matches", {"ms":150})
r2 = ToolResult("read_file", False)
print(r1)
print(r2)
print(r1.output, "|", r1.meta["ms"], "ms")
print("both empty by default: ", r2.meta, r2.output=="")
#-------- 7. 진짜 Agent에 가까운 Agent클래스: 작은 Agent 설계도
class Agent:
def __init__(self, name, tools, max_steps=5):
self.name=name
self.tools=tools
self.max_steps=max_steps
self.steps=0
self.history=[]
def __repr__(self):
return f"Agent({self.name!r}, steps={self.steps}/{self.max_steps})"
def add_message(self, role, content):
self.history.append({"role":role, "content":content})
def can_continue(self):
return self.steps < self.max_steps
def run_tool(self, name, args):
self.steps +=1
if name not in self.tools:
result = f"unknown tool '{name}'"
else:
try:
result = str(self.tools[name](**args))
except Exception as e:
result = f"tool failed: {type(e).__name__}: {e}"
self.add_message("tool", f"{name} -> {result}")
return result
def reset(self):
self.steps=0
self.history=[]
def transcript(self):
return "\n".join(f"{m['role']:>9} | {m['content']}" for m in self.history)
def calculator(expr):
return eval(expr)
def greet(name):
return f"안녕, {name}!"
bot = Agent("Scout", tools={"calculator":calculator, "greet":greet}, max_steps=3)
bot.add_message("user", "25 곱하기 4를 구하세요, 그리고 스티븐에게 인사하세요.")
bot.run_tool("calculator", {"expr":"25*4"})
bot.run_tool("greet", {"name":"스티븐"})
bot.run_tool("emailer", {"to":"스티븐"})
print(bot)
print("can_continue: ", bot.can_continue())
print("-"*50)
print(bot.transcript())
1. Dictionary를 쓸 때 vs Class를 쓸 때
주석부터 보겠습니다.
# 다음과 같은 경우 딕셔너리를 사용하세요:
# 단순히 데이터일 때
# JSON에 바로 전달할 때
# 별도의 규칙을 적용할 필요가 없을 때
해석하면:
Dictionary는 데이터 자체만 중요할 때 사용한다.
예를 들어:
message = {
"role": "user",
"content": "안녕하세요"
}
이 데이터는 특별한 행동이 필요 없습니다.
role
content
라는 데이터를 저장하는 것이 목적입니다.
또 JSON으로 바로 변환하기 좋습니다.
json.dumps(message)
반면:
# 다음과 같은 경우 클래스를 사용하세요:
# 데이터와 동작이 함께 있어야 할 때
# 여러 개의 독립적인 복사본이 필요할 때
# 유효하지 않은 상태를 방지해야 할 때
Class는:
데이터와 행동(기능)이 함께 있을 때 사용한다.
예를 들어 Agent는 단순한 데이터가 아닙니다.
Agent에게는:
이름
최대 단계
현재 단계
대화 기록
이라는 데이터가 있고,
또:
메시지 추가
툴 실행
계속 실행 가능한지 확인
초기화
대화 기록 출력
같은 행동이 있습니다.
그래서 Agent는 Dictionary보다는 Class가 잘 어울립니다.
2. 첫 번째 Agent 클래스
class Agent:
이제 Agent라는 새로운 자료형을 만드는 것입니다.
Python에는 기본적으로:
int
str
list
dict
같은 자료형이 있습니다.
우리가 직접:
Agent
라는 새로운 자료형을 만드는 것입니다.
__init__()
def __init__(self, name, max_steps=5):
객체가 만들어질 때 자동으로 실행되는 초기화 함수입니다.
예를 들어:
scout = Agent("Scout", max_steps=3)
이 실행되면 내부적으로 대략:
Agent.__init__(scout, "Scout", 3)
처럼 동작합니다.
여기서 self는:
지금 만들어지고 있는 자기 자신(객체)
입니다.
객체 속성 저장
self.name = name
예:
scout.name
↓
Scout
self.max_steps = max_steps
↓
scout.max_steps
↓
3
self.history = []
각 Agent마다 자신의 대화 기록을 가집니다.
Scout
└── history
Research Agent
└── history
Support Agent
└── history
각각 독립적입니다.
self.steps = 0
현재 실행 단계를 기록합니다.
add_message()
def add_message(self, role, content):
self.history.append({
"role":role,
"content":content
})
return len(self.history)
예:
scout.add_message(
"user",
"송장의 총금액을 확인하세요."
)
결과:
scout.history
↓
[
{
"role": "user",
"content": "송장의 총금액을 확인하세요."
}
]
왜 self.history인가?
Agent마다 다른 history를 가져야 하기 때문입니다.
scout.history
support.history
research.history
각각 독립적으로 존재합니다.
budget_left()
def budget_left(self):
return self.max_steps - self.steps
Agent가 앞으로 몇 번 더 실행할 수 있는지 계산합니다.
예:
max_steps = 3
steps = 1
그러면:
3 - 1 = 2
즉:
scout.budget_left()
↓
2
실행 과정
scout = Agent("Scout", max_steps=3)
현재 상태:
Scout Agent
name = Scout
max_steps = 3
steps = 0
history = []
scout.add_message(...)
↓
history
[
user 메시지
]
scout.steps += 1
↓
steps = 1
따라서:
최대 3단계
현재 1단계 사용
남은 2단계
입니다.

3. 여러 개의 독립적인 Agent
support = Agent("support")
research = Agent("research")
두 개의 객체를 만듭니다.
Agent
│
├── support
│
└── research
각각:
support.history
와
research.history
를 따로 가지고 있습니다.
support.add_message(...)
를 해도:
research.history
에는 영향을 주지 않습니다.
이것이 객체지향의 중요한 개념입니다.
하나의 Class를 이용해 여러 개의 독립적인 객체를 만들 수 있다.
4. 매우 중요한 Class 변수 vs Instance 변수
이 부분은 꼭 이해하시면 좋습니다.
Bad
class Bad:
history = []
여기서 history는 Class에 소속되어 있습니다.
즉:
Bad 클래스
history = []
하나를 모든 객체가 공유합니다.
a, b = Bad(), Bad()
두 객체를 만들었습니다.
Bad
│
history []
/ \
/ \
a b
둘 다 같은 history를 봅니다.
a.add("one")
↓
history = ["one"]
그 다음:
b.add("two")
↓
history = ["one", "two"]
그래서:
print(a.history)
↓
["one", "two"]
그리고:
print(b.history)
도:
["one", "two"]
입니다.
왜냐하면 공유하기 때문입니다.
Good
class Good:
def __init__(self):
self.history = []
이번에는 객체를 만들 때마다 새로운 List를 만듭니다.
c
└── history []
d
└── history []
c.add("one")
d.add("two")
결과:
c.history = ["one"]
d.history = ["two"]
입니다.
Agent에서는 반드시 Good 방식
Agent는 각각 독립적인 대화를 해야 합니다.
Agent A
└── 자신의 history
Agent B
└── 자신의 history
따라서:
self.history = []
를 __init__() 안에 넣는 것입니다.
이 부분은 실제 Agent 개발에서도 상당히 중요합니다.
5. __repr__()
def __repr__(self):
return f"Agent(name={self.name!r}, steps={self.steps}/{self.max_steps}, msgs={len(self.history)})"
__repr__()은:
객체를 출력할 때 어떻게 보여줄 것인가
를 정의하는 특수 메서드입니다.
보통:
print(a)
를 하면 객체는 기본적으로 이상한 형태로 나옵니다.
예:
<__main__.Agent object at 0x000001A3...>
하지만 __repr__()을 만들면:
Agent(name='Scout', steps=2/5, msgs=1)
처럼 사람이 이해하기 쉽게 나옵니다.
!r
self.name!r
은 Python의 repr() 표현을 사용합니다.
예:
name = "Scout"
print(f"{name}")
↓
Scout
하지만:
print(f"{name!r}")
↓
'Scout'
문자열이라는 것이 명확하게 보입니다.
List에서도 잘 작동
print([a, Agent("Backup", max_steps=2)])
각 객체를 출력할 때도:
__repr__()
가 사용됩니다.
예:
[
Agent(name='Scout', steps=2/5, msgs=1),
Agent(name='Backup', steps=0/2, msgs=0)
]
디버깅할 때 매우 좋습니다.
6. dataclass
from dataclasses import dataclass, field
dataclass는:
데이터를 저장하는 클래스를 쉽게 만들기 위한 도구
입니다.
일반 클래스로 쓰면:
class ToolResult:
def __init__(self, name, ok, output="", meta=None):
self.name = name
self.ok = ok
self.output = output
self.meta = meta if meta else {}
이렇게 써야 합니다.
하지만 dataclass:
@dataclass
class ToolResult:
name: str
ok: bool
output: str = ""
meta: dict = field(default_factory=dict)
훨씬 간단합니다.
ToolResult 만들기
r1 = ToolResult(
"search",
True,
"3 matches",
{"ms":150}
)
결과:
name = search
ok = True
output = 3 matches
meta = {"ms":150}
두 번째:
r2 = ToolResult("read_file", False)
기본값이 적용됩니다.
name = read_file
ok = False
output = ""
meta = {}
왜 default_factory=dict를 사용할까?
매우 중요합니다.
잘못 쓰면:
@dataclass
class ToolResult:
meta: dict = {}
이런 식으로 쓰고 싶을 수 있습니다.
하지만 mutable 객체인 Dictionary/List는 공유 문제가 발생할 수 있습니다.
그래서:
field(default_factory=dict)
를 사용합니다.
의미:
객체를 만들 때마다 새로운 빈 Dictionary를 만들어라.
즉:
r1.meta → {}
r2.meta → {}
각각 독립적입니다.
7. 마지막: 진짜 Agent에 가까운 클래스
이제 지금까지 배운 것을 모두 합칩니다.
class Agent:
Agent는 다음을 가지고 있습니다.
Agent
│
├── name
├── tools
├── max_steps
├── steps
├── history
│
├── add_message()
├── can_continue()
├── run_tool()
├── reset()
└── transcript()
이제 상당히 Agent답습니다.
초기화
def __init__(self, name, tools, max_steps=5):
Agent를 만들려면:
이름
사용 가능한 도구
최대 실행 단계
가 필요합니다.
self.tools = tools
예:
{
"calculator": calculator,
"greet": greet
}
즉:
도구 이름 → 실제 함수
연결입니다.
can_continue()
def can_continue(self):
return self.steps < self.max_steps
예:
steps = 2
max_steps = 3
↓
True
하지만:
steps = 3
max_steps = 3
↓
False
Agent Loop에서:
while agent.can_continue():
같은 방식으로 사용할 수 있습니다.
가장 중요한 run_tool()
def run_tool(self, name, args):
Agent가 Tool을 실행하는 핵심 메서드입니다.
Step 증가
self.steps += 1
Tool을 한 번 실행할 때마다:
steps + 1
Tool 존재 확인
if name not in self.tools:
예:
name = "emailer"
하지만:
self.tools = {
"calculator": calculator,
"greet": greet
}
에는 없습니다.
그러면:
result = f"unknown tool '{name}'"
↓
unknown tool 'emailer'
Tool 실행
result = str(self.tools[name](**args))
이 부분은 매우 중요합니다.
예:
name = "calculator"
args = {
"expr": "25*4"
}
그러면:
self.tools[name]
↓
calculator
그리고:
(**args)
↓
calculator(expr="25*4")
결과:
100
예외 처리
except Exception as e:
Tool 실행 중 문제가 발생하면 Agent 전체가 죽지 않게 합니다.
예:
calculator(expr="잘못된 계산")
오류 발생.
그러면:
result = f"tool failed: {type(e).__name__}: {e}"
예:
tool failed: SyntaxError: invalid syntax
Tool 결과를 History에 저장
self.add_message(
"tool",
f"{name} -> {result}"
)
예:
tool | calculator -> 100
이제 Agent History는:
user
↓
assistant/tool 선택
↓
tool 결과
↓
다음 판단
구조로 발전할 수 있습니다.
reset()
def reset(self):
self.steps=0
self.history=[]
Agent 상태를 초기화합니다.
기존 대화 삭제
단계 초기화
transcript()
def transcript(self):
return "\n".join(
f"{m['role']:>9} | {m['content']}"
for m in self.history
)
History를 사람이 보기 좋은 대화 기록으로 바꿉니다.
예:
user | 25 곱하기 4를 구하세요
tool | calculator -> 100
tool | greet -> 안녕, 스티븐!
tool | emailer -> unknown tool 'emailer'
:>9
{m['role']:>9}
오른쪽 정렬 9칸입니다.
예:
user
assistant
tool
세로가 깔끔하게 맞습니다.
실제 Tool 함수
Calculator
def calculator(expr):
return eval(expr)
예:
calculator("25*4")
↓
100
⚠️ 참고로 실제 서비스에서는 사용자나 모델이 만든 문자열을 eval()로 직접 실행하는 것은 위험할 수 있습니다. 지금은 학습용으로 이해하시면 됩니다.
greet
def greet(name):
return f"안녕, {name}!"
예:
greet("스티븐")
↓
안녕, 스티븐!
Agent 생성
bot = Agent(
"Scout",
tools={
"calculator":calculator,
"greet":greet
},
max_steps=3
)
현재 상태:
Agent Scout
steps = 0
max_steps = 3
tools
├── calculator
└── greet
history = []
사용자 메시지
bot.add_message(
"user",
"25 곱하기 4를 구하세요, 그리고 스티븐에게 인사하세요."
)
History:
user
│
└── 25 곱하기 4를 구하세요...
첫 번째 Tool
bot.run_tool(
"calculator",
{"expr":"25*4"}
)
내부 과정:
steps
0 → 1
calculator 존재?
↓ YES
calculator(expr="25*4")
↓
100
history에 저장
두 번째 Tool
bot.run_tool(
"greet",
{"name":"스티븐"}
)
과정:
steps
1 → 2
greet(name="스티븐")
↓
안녕, 스티븐!
history 저장
세 번째 Tool
bot.run_tool(
"emailer",
{"to":"스티븐"}
)
하지만:
TOOLS
calculator ⭕
greet ⭕
emailer ❌
따라서:
unknown tool 'emailer'
을 결과로 History에 기록합니다.
최종 Step
3개의 Tool을 실행했으므로:
steps = 3
max_steps = 3
따라서:
bot.can_continue()
↓
3 < 3
↓
False
전체 흐름
이번 마지막 Agent는 이렇게 동작합니다.
┌─────────────┐
│ Agent │
└──────┬──────┘
│
┌───────────────┼────────────────┐
│ │ │
▼ ▼ ▼
history tools steps
│ │ │
▼ ▼ ▼
대화 기록 calculator 실행 제한
greet
│
▼
transcript()
그리고 Tool 실행 시:
run_tool()
│
▼
steps += 1
│
▼
Tool 존재?
│
┌──┴─────┐
│ │
YES NO
│ │
▼ ▼
실행 unknown tool
│ │
└───┬────┘
▼
history 기록
▼
결과 반환
⭐ 이번 코드에서 가장 중요한 깨달음
지금까지는 우리가 이런 것들을 따로 만들었습니다.
history
run_tool()
steps
max_steps
tools
transcript()
reset()
하지만 이제:
class Agent:
안에 모두 넣었습니다.
즉:
이전 방식
history 따로
tools 따로
steps 따로
함수 따로
에서:
Agent
│
├── history
├── tools
├── steps
├── run_tool()
├── reset()
└── transcript()
로 바뀐 것입니다.
이것이 바로 객체지향 프로그래밍(OOP)의 가장 중요한 장점입니다.
관련된 데이터와 행동을 하나의 객체로 묶는다.
그리고 지금 여러분이 공부하는 흐름을 보면, 다음 단계는 아주 자연스럽습니다.
Python 기본 문법
↓
함수
↓
Dictionary / JSON
↓
History
↓
Tool Dispatcher
↓
Agent Loop
↓
Error / Retry
↓
File / Logging
↓
Class Agent ← 지금 여기
↓
실제 OpenAI API
↓
Model → Tool 선택
↓
Tool Result → Model
↓
Final Answer
즉, 지금부터는 단순한 Python 문법 공부에서 실제 AI Agent 구조를 조립하는 단계로 넘어왔다고 보시면 됩니다.
'AI, 클라우드, 문서, 자동화 > AI_AGENT' 카테고리의 다른 글
| IX. AI Agent에 “기억과 기록” 기능을 붙이는 단계 (0) | 2026.08.23 |
|---|---|
| VIII. Agent Loop + Tool Dispatcher 다음 단계인 예외 처리(Exception), 재시도(Retry), Logging 스터디 (0) | 2026.08.23 |
| VII. Python 함수 → Tool 함수 → Agent의 Tool Dispatcher 구조 (0) | 2026.08.23 |
| VI. Python의 반복문과 자료구조를 이용해 AI Agent의 실제 실행 흐름을 만드는 과정 (0) | 2026.08.23 |
| V. AI Agent의 핵심 부품들 하나씩 만들어보기 (0) | 2026.08.23 |
