| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 파워포인트) #집(zip)파일 #아래한글(HWP) #brute-force(무차별 대입)
- 시스템 프롬프트
- LLM
- LLM 종료이유 처리
- TOOL ROUTER
- # 암호(비밀번호) 분실 # 암호(비밀번호) 찾기 #오피스(doc
- JSON 문자열
- fake model
- JSON parsing 파싱
- #다산 정약용 #유배지에서 보낸 편지 #도덕 #용기 #염 #주역 #호연지기 #효제 #근검
- Markdown 코드블록
- trim()
- #독점의 저주 #Curse of Monopoly #카카오 #카카오택시 #마이크로소프트 #Internet Explorer(IE) #지갑 #독점 #사랑
- #Netflix #넷플릭스 #자기잠식(Self-Cannibalization) #해법 중심 경영 #Disney Disney+ #Amazon Prime Video # Apple Apple TV+
- AI
- TOOL 실행과 안전장치
- 에이전트
- 숫자 formatting
- ppt) 파일 #오피스(워드
- xls
- context window
- Agent loop
- 티스토리챌린지
- 인공지능
- 파이썬
- AI Agent
- 엑셀
- list comprehension
- #선진국 대한민국 #선진국 #대한민국 #아이들 #청소년 #고민 #해결 #심리
- 오블완
- Today
- Total
아톨러브
VI. Python의 반복문과 자료구조를 이용해 AI Agent의 실제 실행 흐름을 만드는 과정 본문
VI. Python의 반복문과 자료구조를 이용해 AI Agent의 실제 실행 흐름을 만드는 과정
아톨 2026. 8. 23. 13:41
#-------- 1. history 순회 + range()
history = [
{"role":"user","content":"제품번호 5001의 재고를 확인해라."},
{"role":"assistant", "content":"지금 찾아보고 있어요."},
{"role":"user", "content":"그 제품이 어느 창고로부터 배송되는지 확인해라."},
]
for message in history:
print(f"{message['role']}: {message['content']}")
print()
for i in range(3):
print(f"step {i}")
#-------- 2. enumerate() + zip()
tool_names = ["search", "calculator", "read_file"]
durations = [1.4, 0.02, 0.31]
for position, name in enumerate(tool_names, start=1):
print(f"{position}. {name}")
print()
for name, seconds in zip(tool_names, durations):
print(f"{name:<12} took {seconds:>5.2f}s")
#-------- 3. while + Agent 종료 조건
step = 0
max_steps = 4
done = False
while not done and step < max_steps:
step += 1
print(f"Step {step}: 모델을 호출하세요.")
if step == 3:
done = True
if done:
print(f"{step} 단계 이후에 자연스럽게 종료되었습니다.")
else:
print(f"종료되지 않고, {max_steps} 단계 제한에 도달하였습니다.")
#-------- 4. continue + break
results = [
{"tool": "search", "ok":True, "data":"3개 일치"},
{"tool": "search", "ok":False, "data": "시간 초과"},
{"tool": "finish", "ok":True, "data": "답변 준비 완료"},
{"tool":"search", "ok":True, "data":"도달하지 못함"},
]
for r in results:
if not r["ok"]:
print(f"실패한 {r['tool']}를/을 건너뜁니다.")
continue
if r["tool"] == "finish":
print(f"종료: {r['data']}")
break
print(f"{r['tool']}: {r['data']} 진행되었습니다.")
#-------- 5. List Comprehension으로 history 분석
history=[
{"role":"user","content":"안녕"},
{"role":"assistant","content":"안녕하세요"},
{"role":"user", "content":"재고 확인해"},
{"role":"tool","content":"12개"},
]
user_texts = [m["content"] for m in history if m["role"]=="user"]
roles = [m["role"] for m in history]
lengths = [len(m["content"]) for m in history]
print(user_texts)
print(roles)
print(lengths)
print("총 글자수: ", sum(lengths),"개")
#-------- 6. 가짜 모델(fake_model) + Tool + Agent Loop
MAX_STEPS = 5
SCRIPT = [
{"tool":"search", "args":{"query":"unit price를 검색하세요."}},
{"tool":"calculator", "args":{"expr":"24 * 7을 계산하세요."}},
{"tool":"finish", "args":{"answer":"이제 답을 드려요. 합계는 168입니다.."}},
]
def fake_model(history):
turn = len([m for m in history if m["role"]=="assistant"])
if turn < len(SCRIPT):
return SCRIPT[turn]
return {"tool":"finish", "args":{"answer":"아이디어가 없어요."}}
def run_tool(name, args):
if name=="search":
return f"{args['query']}에 대해 3개의 결과를 발견했어요."
if name == "calculator":
return f"{args['expr']} = 168"
return "unknown tool"
history = [{"role":"user", "content":"24개에 대한 총액이 얼마야? "}]
step = 0
answer = None
while answer is None and step < MAX_STEPS:
step += 1
call = fake_model(history)
history.append({"role":"assistant", "content":str(call)})
print(f"Step {step}: 모델이 {call['tool']}을 선택했습니다.")
if call["tool"] == "finish":
answer = call["args"]["answer"]
break
observation = run_tool(call["tool"], call["args"])
history.append({"role":"tool", "content":observation})
print(f" observed: {observation}")
print("-"*50)
print("answer: ", answer if answer else "step limit reached")
print("message in history: ", len(history))
1. 첫 번째 코드
history 순회와 range()
history = [
{"role":"user","content":"제품번호 5001의 재고를 확인해라."},
{"role":"assistant", "content":"지금 찾아보고 있어요."},
{"role":"user", "content":"그 제품이 어느 창고로부터 배송되는지 확인해라."},
]
이 history는 이전에 배운 Agent의 대화 기록입니다.
구조를 보면:
history
│
├── 0번째 메시지
│ role: user
│ content: 제품번호 5001의 재고를 확인해라.
│
├── 1번째 메시지
│ role: assistant
│ content: 지금 찾아보고 있어요.
│
└── 2번째 메시지
role: user
content: 그 제품이 어느 창고로부터 배송되는지 확인해라.
즉:
history[0]
은:
{"role":"user","content":"제품번호 5001의 재고를 확인해라."}
입니다.
for message in history
for message in history:
print(f"{message['role']}: {message['content']}")
history 안에 있는 메시지를 하나씩 꺼냅니다.
첫 번째 반복:
message = {
"role":"user",
"content":"제품번호 5001의 재고를 확인해라."
}
따라서:
message["role"]
↓
user
그리고:
message["content"]
↓
제품번호 5001의 재고를 확인해라.
출력:
user: 제품번호 5001의 재고를 확인해라.
assistant: 지금 찾아보고 있어요.
user: 그 제품이 어느 창고로부터 배송되는지 확인해라.
range(3)
for i in range(3):
print(f"step {i}")
range(3)은:
0, 1, 2
입니다.
따라서:
step 0
step 1
step 2
가 출력됩니다.
중요한 점
Python은 보통 숫자를 0부터 시작합니다.
range(3)
은:
0
1
2
이지:
1
2
3
이 아닙니다.
2. 두 번째 코드
enumerate()와 zip()
tool_names = ["search", "calculator", "read_file"]
durations = [1.4, 0.02, 0.31]
두 개의 리스트가 있습니다.
tool_names durations
search ←→ 1.4
calculator ←→ 0.02
read_file ←→ 0.31
2-1. enumerate()
for position, name in enumerate(tool_names, start=1):
print(f"{position}. {name}")
enumerate()는:
리스트의 값 + 번호
를 함께 줍니다.
결과:
1. search
2. calculator
3. read_file
내부적으로 생각하면
대략 이렇게 됩니다.
position = 1
name = search
position = 2
name = calculator
position = 3
name = read_file
start=1
enumerate(tool_names, start=1)
때문에 번호가:
1, 2, 3
으로 시작합니다.
만약:
enumerate(tool_names)
라고 하면:
0. search
1. calculator
2. read_file
가 됩니다.
2-2. zip()
for name, seconds in zip(tool_names, durations):
zip()은 두 개의 리스트를 묶어줍니다.
tool_names durations
search 1.4
calculator 0.02
read_file 0.31
결과적으로:
("search", 1.4)
("calculator", 0.02)
("read_file", 0.31)
처럼 됩니다.
출력
print(f"{name:<12} took {seconds:>5.2f}s")
예:
search took 1.40s
calculator took 0.02s
read_file took 0.31s
여기서:
{name:<12}
는 12칸 확보 후 왼쪽 정렬.
{seconds:>5.2f}
는:
- > 오른쪽 정렬
- 5 최소 5칸
- .2f 소수점 둘째 자리까지
입니다.
예:
1.4
↓
1.40

3. 세 번째 코드
while과 Agent 종료 조건
step = 0
max_steps = 4
done = False
현재 상태:
step = 0
최대 단계 = 4
완료 여부 = False
핵심 while 조건
while not done and step < max_steps:
이 부분이 아주 중요합니다.
Agent Loop에서도 거의 같은 구조를 사용합니다.
의미는:
아직 완료되지 않았고
그리고
최대 단계에 도달하지 않았다면
계속 반복해라
그림으로 보면:
done = False?
│
Yes
↓
step < max_steps?
│
Yes
↓
반복 계속
Step 증가
step += 1
처음:
step = 0
↓
step = 1
모델 호출
print(f"Step {step}: 모델을 호출하세요.")
실제 Agent라면 나중에:
response = call_model(history)
같은 코드가 들어갈 자리입니다.
현재는 단순히:
모델을 호출한다고 가정
하는 예제입니다.
Step 3에서 완료
if step == 3:
done = True
실행 흐름:
Step 1 → done False
Step 2 → done False
Step 3 → done True
다음 반복 시:
not done
은:
not True
↓
False
따라서 while 종료.
결과
if done:
print(f"{step} 단계 이후에 자연스럽게 종료되었습니다.")
else:
print(...)
결과:
3 단계 이후에 자연스럽게 종료되었습니다.
Agent 관점에서 매우 중요
실제 Agent는:
Step 1
LLM 호출
↓
Tool 실행
Step 2
LLM 호출
↓
Tool 실행
Step 3
LLM 호출
↓
최종 답변
↓
done = True
↓
종료
또는:
Step 1
Step 2
Step 3
Step 4
↓
아직 답 없음
↓
MAX_STEPS 도달
↓
강제 종료
두 가지 종료 방법이 있습니다.
정상 종료
done = True
안전 종료
step >= max_steps
입니다.
4. 네 번째 코드
continue와 break
results = [
{"tool": "search", "ok":True, "data":"3개 일치"},
{"tool": "search", "ok":False, "data": "시간 초과"},
{"tool": "finish", "ok":True, "data": "답변 준비 완료"},
{"tool":"search", "ok":True, "data":"도달하지 못함"},
]
여러 Tool 실행 결과가 있습니다.
첫 번째 결과
{"tool": "search", "ok":True, "data":"3개 일치"}
정상입니다.
따라서:
search: 3개 일치 진행되었습니다.
4-1. continue
if not r["ok"]:
print(f"실패한 {r['tool']}를/을 건너뜁니다.")
continue
두 번째 결과:
{
"tool":"search",
"ok":False,
"data":"시간 초과"
}
여기서:
not r["ok"]
↓
not False
↓
True
따라서:
continue
가 실행됩니다.
continue는:
현재 반복의 나머지 작업을 건너뛰고 다음 반복으로 가라.
입니다.
흐름
현재 search 실패
↓
"건너뜁니다" 출력
↓
continue
↓
다음 결과로 이동
4-2. break
if r["tool"] == "finish":
print(f"종료: {r['data']}")
break
세 번째 결과:
{
"tool":"finish",
"ok":True,
"data":"답변 준비 완료"
}
따라서:
종료: 답변 준비 완료
출력 후:
break
실행.
break는:
반복문 자체를 완전히 종료해라.
입니다.
따라서 네 번째:
{"tool":"search", "ok":True, "data":"도달하지 못함"}
는 실행되지 않습니다.
continue와 break 차이
continue
│
└── 이번 반복만 건너뛰고
다음 반복 계속
break
│
└── 반복문 전체 종료
Agent에서:
Tool 실패
↓
continue
↓
다음 작업
FINISH
↓
break
↓
Agent Loop 종료
라고 생각하면 됩니다.
5. 다섯 번째 코드
List Comprehension으로 history 분석
history=[
{"role":"user","content":"안녕"},
{"role":"assistant","content":"안녕하세요"},
{"role":"user", "content":"재고 확인해"},
{"role":"tool","content":"12개"},
]
사용자 메시지만 추출
user_texts = [
m["content"]
for m in history
if m["role"]=="user"
]
결과:
[
"안녕",
"재고 확인해"
]
일반 for문으로 풀면
user_texts = []
for m in history:
if m["role"] == "user":
user_texts.append(m["content"])
입니다.
List Comprehension은 이것을 짧게 쓴 것입니다.
모든 role 추출
roles = [m["role"] for m in history]
결과:
[
"user",
"assistant",
"user",
"tool"
]
메시지 길이 계산
lengths = [len(m["content"]) for m in history]
예:
안녕 → 2
안녕하세요 → 5
재고 확인해 → 6
12개 → 3
대략 이런 식입니다.
그리고:
sum(lengths)
는 모두 더합니다.
2 + 5 + 6 + 3
↓
16
실제 Agent에서 왜 중요할까?
LLM은 대화가 길어질수록 history가 계속 늘어납니다.
history
user
assistant
tool
assistant
tool
assistant
user
...
따라서 Agent는 종종:
len(history)
또는:
sum(len(m["content"]) for m in history)
같은 방식으로 대화량을 관리합니다.
실제 LLM에서는 이것보다 정확한 token 계산을 해야 합니다.
6. 마지막 코드
⭐ 지금까지 배운 내용을 합친 Mini Agent
이 부분이 가장 중요합니다.
6-1. 최대 Step
MAX_STEPS = 5
Agent가 최대 5번까지만 생각하고 행동하도록 제한합니다.
무한 반복 방지
입니다.
6-2. Agent의 미리 작성된 행동 계획
SCRIPT = [
{"tool":"search", "args":{"query":"unit price를 검색하세요."}},
{"tool":"calculator", "args":{"expr":"24 * 7을 계산하세요."}},
{"tool":"finish", "args":{"answer":"이제 답을 드려요. 합계는 168입니다.."}},
]
이것은 실제 AI가 아닙니다.
Agent가 어떤 행동을 할지 미리 정해놓았습니다.
순서:
1단계
search
↓
2단계
calculator
↓
3단계
finish
입니다.
6-3. fake_model()
def fake_model(history):
이 함수는 실제 OpenAI API 대신 사용하는 가짜 AI 모델입니다.
Assistant 메시지 개수 계산
turn = len([
m for m in history
if m["role"]=="assistant"
])
현재 history에서 assistant 메시지가 몇 개인지 셉니다.
예를 들어 처음에는:
history = [
{
"role":"user",
"content":"24개에 대한 총액이 얼마야?"
}
]
assistant 메시지가 없습니다.
따라서:
turn = 0
첫 번째 행동 반환
if turn < len(SCRIPT):
return SCRIPT[turn]
현재:
turn = 0
따라서:
SCRIPT[0]
반환.
즉:
{
"tool":"search",
"args":{
"query":"unit price를 검색하세요."
}
}
두 번째 호출
첫 번째 호출 후:
history.append({
"role":"assistant",
"content":str(call)
})
assistant 메시지가 하나 생깁니다.
따라서 다음 호출에서는:
turn = 1
그리고:
SCRIPT[1]
을 반환합니다.
calculator
입니다.
세 번째 호출
assistant 메시지가 2개.
turn = 2
따라서:
SCRIPT[2]
↓
finish
를 반환합니다.
7. run_tool()
def run_tool(name, args):
Agent가 선택한 Tool을 실제로 실행합니다.
search
if name=="search":
return f"{args['query']}에 대해 3개의 결과를 발견했어요."
예:
args = {
"query":"unit price를 검색하세요."
}
결과:
unit price를 검색하세요.에 대해 3개의 결과를 발견했어요.
calculator
if name == "calculator":
return f"{args['expr']} = 168"
예:
{
"expr":"24 * 7을 계산하세요."
}
결과:
24 * 7을 계산하세요. = 168
8. Agent 초기 상태
history = [
{
"role":"user",
"content":"24개에 대한 총액이 얼마야?"
}
]
step = 0
answer = None
현재 상태:
history
│
└── user
"24개에 대한 총액이 얼마야?"
step = 0
answer = None
9. ⭐ Agent Loop
while answer is None and step < MAX_STEPS:
이 코드가 Mini Agent의 심장입니다.
의미:
아직 최종 답변이 없고
그리고
최대 Step에 도달하지 않았다면
계속 Agent를 실행한다.
10. Step 1
step += 1
↓
step = 1
모델 호출
call = fake_model(history)
현재 assistant 메시지 수:
0개
따라서:
SCRIPT[0]
반환:
{
"tool":"search",
"args":{
"query":"unit price를 검색하세요."
}
}
모델의 결정 저장
history.append({
"role":"assistant",
"content":str(call)
})
history는 이제:
user
↓
24개에 대한 총액이 얼마야?
assistant
↓
search Tool을 사용하겠다
출력
Step 1: 모델이 search을 선택했습니다.
11. finish인지 확인
if call["tool"] == "finish":
현재:
search
이므로 False.
12. Tool 실행
observation = run_tool(
call["tool"],
call["args"]
)
즉:
run_tool(
"search",
{"query":"unit price를 검색하세요."}
)
결과:
unit price를 검색하세요.에 대해 3개의 결과를 발견했어요.
13. Tool 결과를 history에 저장
history.append({
"role":"tool",
"content":observation
})
현재 history:
USER
"24개에 대한 총액이 얼마야?"
↓
ASSISTANT
"search를 사용하겠다"
↓
TOOL
"3개의 결과를 발견했어요."
이제 다음 Step에서 모델은 이 Tool 결과를 보고 판단할 수 있습니다.
실제 Agent의 핵심입니다.
14. Step 2
이번에는 assistant 메시지가 하나 있습니다.
따라서:
turn = 1
↓
SCRIPT[1]
↓
{
"tool":"calculator",
"args":{
"expr":"24 * 7을 계산하세요."
}
}
계산기 실행:
24 * 7 = 168
그리고 Tool 결과를 history에 저장합니다.
15. Step 3
이번에는 assistant 메시지가 두 개입니다.
turn = 2
따라서:
SCRIPT[2]
↓
{
"tool":"finish",
"args":{
"answer":"이제 답을 드려요. 합계는 168입니다.."
}
}
16. FINISH 처리
if call["tool"] == "finish":
answer = call["args"]["answer"]
break
여기가 Agent의 종료 지점입니다.
finish 발견
↓
answer에 최종 답 저장
↓
break
↓
while 종료
17. 전체 Agent 실행 흐름
이 코드를 그림으로 보면 아주 명확합니다.
┌────────────────────────────┐
│ 사용자 질문 │
│ "24개에 대한 총액이 얼마야?" │
└──────────────┬─────────────┘
↓
┌──────────────┐
│ Agent Loop │
└──────┬───────┘
↓
Step 1
↓
fake_model()
↓
search 선택
↓
run_tool()
↓
검색 결과
↓
history 저장
↓
Step 2
↓
fake_model()
↓
calculator 선택
↓
run_tool()
↓
168
↓
history 저장
↓
Step 3
↓
fake_model()
↓
finish
↓
answer 저장
↓
종료
18. 마지막 출력
print("-"*50)
구분선 출력:
--------------------------------------------------
print("answer: ", answer if answer else "step limit reached")
answer가 있으면:
answer: 이제 답을 드려요. 합계는 168입니다..
없으면:
answer: step limit reached
를 출력합니다.
이 역시 삼항 연산자입니다.
풀어쓰면:
if answer:
print("answer:", answer)
else:
print("answer: step limit reached")
입니다.
history 길이
print("message in history: ", len(history))
최종 history를 세어보면:
1. user
2. assistant → search 선택
3. tool → search 결과
4. assistant → calculator 선택
5. tool → calculator 결과
6. assistant → finish 선택
따라서:
message in history: 6
이 됩니다.
🌟 이번 코드에서 가장 중요한 핵심
마지막 코드는 지금까지 공부하신 내용을 합친 Mini Agent의 원형입니다.
사용자 질문
↓
history 생성
↓
while loop 시작
↓
모델 호출
↓
모델이 Tool 선택
↓
assistant의 결정 저장
↓
finish인가?
┌──────┴──────┐
Yes No
↓ ↓
answer 저장 Tool 실행
↓ ↓
break Tool 결과(observation)
↓ ↓
종료 history에 저장
↓
다시 모델 호출
이 흐름이 바로 앞으로 실제 OpenAI API를 연결해서 만들게 될 Agent의 기본 구조입니다.
특히 지금까지 배우신 코드와 연결하면 다음 역할로 이해하시면 좋습니다.
| history | Agent의 기억 |
| fake_model() | AI 모델의 판단 |
| SCRIPT | 가짜 AI의 미리 정해진 행동 |
| run_tool() | 실제 Tool 실행 |
| observation | Tool 실행 결과 |
| while | Agent의 반복 사고 |
| MAX_STEPS | 무한 반복 방지 |
| finish | 작업 완료 신호 |
| answer | 최종 답변 |
한 문장으로 정리하면
이번 코드는 AI Agent가 "생각(모델 호출) → 행동(Tool 선택) → 관찰(Tool 결과) → 다시 생각 → 최종 답변"하는 전체 Agent Loop를 Python으로 직접 구현한 코드입니다.
'AI, 클라우드, 문서, 자동화 > AI_AGENT' 카테고리의 다른 글
| V. AI Agent의 핵심 부품들 하나씩 만들어보기 (0) | 2026.08.23 |
|---|---|
| IV. AI Agent가 LLM의 응답을 JSON으로 받고, 그 안에서 Tool 이름과 인자(arguments)를 꺼내서 실제 Tool 실행으로 연결하기 위한 기초 (0) | 2026.08.22 |
| III. 대화 기록(history)을 어떻게 Python으로 관리하는가 (0) | 2026.08.22 |
| II. AI Agent를 만들 때 자주 사용하는 패턴들 (0) | 2026.08.22 |
| I. Python 실행 환경이 제대로 갖춰져 있는지 확인하는 간단한 점검 프로그램 (0) | 2026.08.22 |
