VI. Python의 반복문과 자료구조를 이용해 AI Agent의 실제 실행 흐름을 만드는 과정
#-------- 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으로 직접 구현한 코드입니다.