| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 인공지능
- #자기계발 #자기성장 #자기관리 #긍정적인삶 #마인드셋 #심리학에세이 #삶의태도
- # 암호(비밀번호) 분실 # 암호(비밀번호) 찾기 #오피스(doc
- #독점의 저주 #Curse of Monopoly #카카오 #카카오택시 #마이크로소프트 #Internet Explorer(IE) #지갑 #독점 #사랑
- 에이전트
- 엑셀
- AI Agent
- context window
- #평판관리 #좋은평가받는법 #관계심리학 #인간관계팁 #신뢰쌓기 #진심의힘
- #간단한 메모장 #개인정보처리 #Privacy Policy
- 숫자 formatting
- #선진국 대한민국 #선진국 #대한민국 #아이들 #청소년 #고민 #해결 #심리
- 네덜란드) #샤갈 미술관(니스
- xls
- 영국) #프라도 미술관(마드리드
- ppt) 파일 #오피스(워드
- 티스토리챌린지
- #환단고기 #상고사 #한국고대사 #단군 #역사학 #사료비판 #정서위서논쟁 #역사검증 #고대사연구 #정서위서논쟁 #사료비판 #역사학 #감정이아닌검증 #균형있는시각
- 파이썬
- 오블완
- 로마) #반 고흐 미술관(암스테르담
- trim()
- #다산 정약용 #유배지에서 보낸 편지 #도덕 #용기 #염 #주역 #호연지기 #효제 #근검
- #성인동화 #사람들의 심리 #100만원 수표 # 나의 가치 #구겨진 가치 #구겨진 수표
- 파워포인트) #집(zip)파일 #아래한글(HWP) #brute-force(무차별 대입)
- 스페인) #르네상스·바로크 조각 미술관(보르게세
- #데일카네기 인간관계론 (고전 필독서) #인간관계론 (도서명) #인간관계개선 (가장 핵심적인 목적) #대화의기술 (실질적인 활용법) #인간관계꿀팁 (정보성) #직장생활팁 (실용성) #자기계발서 (카테고리)
- import sys
- #Netflix #넷플릭스 #자기잠식(Self-Cannibalization) #해법 중심 경영 #Disney Disney+ #Amazon Prime Video # Apple Apple TV+
- 시스템 프롬프트
- Today
- Total
아톨러브
III. 대화 기록(history)을 어떻게 Python으로 관리하는가 본문
history = [
{"role":"system", "content":"너는 도움을 주는 에이전트야."},
{"role":"user", "content":"내 주문에 대한 배송이 늦네."},
{"role":"assistant", "content":"내가 그 주문을 확인해 볼께."},
]
print(history[1]["content"])
print("Turns: ", len(history))
for msg in history:
print(f"[{msg['role']:^9}] {msg['content']}")
#--------
response = {
"id":"steven_20",
"usage":{"input_token":1200, "output_token":605},
"content":[{"type":"text","text":"주문배송이 월요일에 시작되었습니다."},]
}
print(response["usage"]["output_token"])
print(response["content"][0]["text"])
for key, value in response["usage"].items():
print(f"{key} = {value}")
#--------
history = []
def add(role, content):
history.append({"role":role, "content":content})
# print(history)
def trim(max_turns):
system = [m for m in history if m["role"]=="system"]
rest = [m for m in history if m["role"]!="system"]
# print(system)
# print(rest)
return system + rest[-max_turns:]
def show(messages):
for m in messages:
role = m["role"].upper()
print(f"{role:<9} | {m['content']}")
add("system","너는 예약 에이전트야.")
add("user", "뉴욕까지 가는 항공기를 찾아줘.")
add("assistant", "지금 비행편을 확인합니다.")
add("user", "참, 저녁에 출발하는 비행기만 부탁해.")
add("assistant", "세 개의 항공편을 찾았습니다.")
print(f"Stored messages: {len(history)}")
print("-"*50)
show(trim(max_turns=2))
1. 첫 번째 코드: history
history = [
{"role":"system", "content":"너는 도움을 주는 에이전트야."},
{"role":"user", "content":"내 주문에 대한 배송이 늦네."},
{"role":"assistant", "content":"내가 그 주문을 확인해 볼께."},
]
가장 먼저 이것부터 이해해야 합니다.
history는 list(리스트)입니다.
그런데 리스트 안에 dictionary(딕셔너리)가 들어 있습니다.
구조를 그림으로 보면:
history
│
├── [0] → dictionary
│ role = system
│ content = 너는 도움을 주는 에이전트야.
│
├── [1] → dictionary
│ role = user
│ content = 내 주문에 대한 배송이 늦네.
│
└── [2] → dictionary
role = assistant
content = 내가 그 주문을 확인해 볼께.
즉:
history[0]
history[1]
history[2]
각각 하나의 메시지를 의미합니다.
2. 왜 role과 content로 나누는가?
각 메시지를:
{
"role": "...",
"content": "..."
}
형태로 저장하고 있습니다.
role은 누가 말했는지를 의미합니다.
대표적으로:
system
user
assistant
세 가지가 있습니다.
system
AI에게 역할이나 규칙을 알려줍니다.
너는 도움을 주는 에이전트야.
user
사용자의 말입니다.
내 주문에 대한 배송이 늦네.
assistant
AI가 이전에 했던 말입니다.
내가 그 주문을 확인해 볼께.
따라서 LLM 입장에서는:
SYSTEM:
너는 도움을 주는 에이전트야.
USER:
내 주문에 대한 배송이 늦네.
ASSISTANT:
내가 그 주문을 확인해 볼께.
라는 대화 기록이 되는 것입니다.

3. history[1]["content"]
print(history[1]["content"])
이 부분은 Python의 list + dictionary 접근을 동시에 사용합니다.
먼저:
history[1]
은 리스트의 두 번째 항목입니다.
Python은 0부터 시작하므로:
history[0] → 첫 번째
history[1] → 두 번째
history[2] → 세 번째
입니다.
따라서:
history[1]
은:
{
"role":"user",
"content":"내 주문에 대한 배송이 늦네."
}
입니다.
여기서:
["content"]
를 붙이면:
history[1]["content"]
→
내 주문에 대한 배송이 늦네.
가 됩니다.
4. 이것은 굉장히 중요한 Python 패턴
앞으로 Agent 코드를 읽을 때 이런 형태를 정말 많이 보게 됩니다.
messages[i]["role"]
messages[i]["content"]
response["usage"]
response["content"]
이것은 모두:
딕셔너리에서 특정 key의 값을 꺼내는 것
입니다.
예를 들어:
msg = {
"role": "user",
"content": "안녕하세요"
}
라면:
msg["role"]
→ "user"
msg["content"]
→ "안녕하세요"
입니다.
5. len(history)
print("Turns: ", len(history))
len()은 리스트에 몇 개의 항목이 있는지 알려줍니다.
현재:
system
user
assistant
3개의 메시지가 있으므로:
Turns: 3
이 출력됩니다.
다만 여기서 Turns라는 표현은 조금 주의해야 합니다.
현재 history에는 메시지가 3개입니다.
하지만 일반적으로 대화의 "turn"을:
user → assistant
한 쌍으로 보는 경우도 있습니다.
따라서 이 코드에서 len(history)가 정확히 의미하는 것은:
저장된 message의 개수
라고 이해하는 것이 더 정확합니다.
6. for msg in history
for msg in history:
print(f"[{msg['role']:^9}] {msg['content']}")
history에 있는 메시지를 하나씩 꺼냅니다.
첫 번째 반복:
msg = {
"role":"system",
"content":"너는 도움을 주는 에이전트야."
}
두 번째:
msg = {
"role":"user",
"content":"내 주문에 대한 배송이 늦네."
}
세 번째:
msg = {
"role":"assistant",
"content":"내가 그 주문을 확인해 볼께."
}
입니다.
7. ^9가 무엇인가?
f"[{msg['role']:^9}]"
^는 가운데 정렬(center alignment)입니다.
9는 9칸을 확보한다는 뜻입니다.
즉:
f"{role:^9}"
은:
role을 9칸짜리 공간의 가운데에 배치하라.
는 뜻입니다.
예를 들어:
system
이 가운데 정렬되어 출력됩니다.
그래서 결과가 대략:
[ system ] 너는 도움을 주는 에이전트야.
[ user ] 내 주문에 대한 배송이 늦네.
[assistant ] 내가 그 주문을 확인해 볼께.
처럼 보기 좋게 정렬됩니다.
8. 두 번째 부분: response
이제 조금 다른 데이터 구조가 나옵니다.
response = {
"id":"steven_20",
"usage":{
"input_token":1200,
"output_token":605
},
"content":[
{
"type":"text",
"text":"주문배송이 월요일에 시작되었습니다."
},
]
}
이것도 핵심은 dictionary 안에 dictionary와 list가 들어 있는 복합 구조입니다.
그림으로 보면:
response
│
├── id
│ └── "steven_20"
│
├── usage
│ ├── input_token → 1200
│ └── output_token → 605
│
└── content
│
└── [0]
├── type → "text"
└── text → "주문배송이 월요일에 시작되었습니다."
이런 구조입니다.
9. response["usage"]["output_token"]
print(response["usage"]["output_token"])
처음 보면 복잡하지만 차례대로 읽으면 쉽습니다.
먼저:
response["usage"]
→
{
"input_token":1200,
"output_token":605
}
그 다음:
["output_token"]
→
605
따라서:
response["usage"]["output_token"]
→ 605
입니다.
10. content[0]["text"]
print(response["content"][0]["text"])
이것은 조금 더 복잡합니다.
먼저:
response["content"]
→ 리스트입니다.
[
{
"type":"text",
"text":"주문배송이 월요일에 시작되었습니다."
}
]
그 다음:
[0]
첫 번째 dictionary를 꺼냅니다.
response["content"][0]
→
{
"type":"text",
"text":"주문배송이 월요일에 시작되었습니다."
}
그리고:
["text"]
를 꺼냅니다.
결과:
주문배송이 월요일에 시작되었습니다.
11. 이것을 읽는 습관
이런 복잡한 코드를 만나면 한꺼번에 읽지 마세요.
다음처럼 왼쪽부터 단계적으로 읽으면 됩니다.
response["content"][0]["text"]
↓
response
↓
content
↓
첫 번째 [0]
↓
text
즉:
response 안에서 content를 찾고 → 그중 첫 번째 것을 꺼내고 → 그 안의 text를 꺼낸다.
입니다.
12. .items()
다음입니다.
for key, value in response["usage"].items():
print(f"{key} = {value}")
먼저:
response["usage"]
는:
{
"input_token":1200,
"output_token":605
}
입니다.
여기에:
.items()
를 사용하면 key와 value를 함께 꺼낼 수 있습니다.
첫 번째:
key = "input_token"
value = 1200
두 번째:
key = "output_token"
value = 605
따라서 결과는:
input_token = 1200
output_token = 605
입니다.
13. 이제 Agent의 핵심: history 관리
여기서부터 정말 중요한 부분입니다.
history = []
빈 리스트를 만들었습니다.
즉:
현재 대화 기록 없음
입니다.
이제 여기에 메시지를 하나씩 추가할 것입니다.
14. add() 함수
def add(role, content):
history.append({"role":role, "content":content})
이 함수는 아주 간단합니다.
history에 메시지 하나를 추가하는 함수
입니다.
예를 들어:
add("user", "안녕하세요")
를 실행하면:
history.append({
"role":"user",
"content":"안녕하세요"
})
가 됩니다.
결과:
history = [
{
"role":"user",
"content":"안녕하세요"
}
]
15. .append()
history.append(...)
append()는 리스트의 맨 뒤에 새로운 항목을 추가합니다.
예:
numbers = [1, 2, 3]
numbers.append(4)
결과:
[1, 2, 3, 4]
입니다.
Agent에서는:
history.append(message)
를 통해 새로운 대화 내용을 계속 저장하는 것입니다.
16. 실제 대화 기록 만들기
다음 코드:
add("system","너는 예약 에이전트야.")
add("user", "뉴욕까지 가는 항공기를 찾아줘.")
add("assistant", "지금 비행편을 확인합니다.")
add("user", "참, 저녁에 출발하는 비행기만 부탁해.")
add("assistant", "세 개의 항공편을 찾았습니다.")
실행이 끝나면 history는 사실상 이렇게 됩니다.
history = [
{"role":"system", "content":"너는 예약 에이전트야."},
{"role":"user", "content":"뉴욕까지 가는 항공기를 찾아줘."},
{"role":"assistant", "content":"지금 비행편을 확인합니다."},
{"role":"user", "content":"참, 저녁에 출발하는 비행기만 부탁해."},
{"role":"assistant", "content":"세 개의 항공편을 찾았습니다."},
]
즉 Agent의 대화 기억입니다.
17. 왜 history가 필요한가?
이것이 Agent에서 매우 중요합니다.
사용자가 처음:
뉴욕까지 가는 항공기를 찾아줘.
라고 했습니다.
그런데 다음에:
참, 저녁에 출발하는 비행기만 부탁해.
라고 했습니다.
두 번째 말만 보면:
"무엇의 저녁 출발?"
이라고 생각할 수도 있습니다.
하지만 Agent가 history를 가지고 있으면:
USER:
뉴욕까지 가는 항공기를 찾아줘.
USER:
참, 저녁에 출발하는 비행기만 부탁해.
를 보고:
"아, 뉴욕행 항공편 중에서 저녁 출발편을 말하는 것이구나."
라고 이해할 수 있습니다.
즉 history가 문맥(context)을 유지해주는 역할을 합니다.
18. trim() 함수
이제 아주 중요한 함수가 나옵니다.
def trim(max_turns):
system = [m for m in history if m["role"]=="system"]
rest = [m for m in history if m["role"]!="system"]
return system + rest[-max_turns:]
이 함수의 목적은:
대화 기록이 너무 길어지면 필요한 만큼만 잘라서 사용한다.
입니다.
왜 필요할까요?
LLM에는 context window가 있기 때문입니다.
그리고 대화가 길어질수록:
대화 내용 증가
↓
input token 증가
↓
비용 증가
↓
context window 부담
이 발생합니다.
그래서 오래된 대화 일부를 잘라내는 것입니다.
19. system을 따로 보관
system = [m for m in history if m["role"]=="system"]
이것은 list comprehension이라는 문법입니다.
쉽게 풀어쓰면:
system = []
for m in history:
if m["role"] == "system":
system.append(m)
와 거의 같은 의미입니다.
즉:
history에서 role이 system인 메시지만 골라서 system에 넣어라.
입니다.
현재 history에는:
system
user
assistant
user
assistant
가 있으므로:
system
하나만 선택됩니다.
20. rest
rest = [m for m in history if m["role"]!="system"]
이번에는 반대입니다.
system이 아닌 메시지만 골라라.
따라서:
user
assistant
user
assistant
가 rest에 들어갑니다.
결국:
history
│
├── system
│
├── user
├── assistant
├── user
└── assistant
를
system
과
user
assistant
user
assistant
로 분리한 것입니다.
21. 왜 system을 따로 빼는가?
이 부분이 Agent 설계에서 상당히 중요합니다.
System Prompt는 Agent의 기본 역할과 규칙입니다.
예:
너는 예약 에이전트야.
이런 것은 대화가 길어졌다고 해서 함부로 버리면 안 됩니다.
그래서:
system = [...]
으로 따로 보관하고,
나머지 오래된 대화는 잘라내는 것입니다.
구조적으로:
System Prompt
↓
항상 유지
최근 대화
↓
필요한 만큼 유지
입니다.
22. rest[-max_turns:]
이 부분도 반드시 이해해야 합니다.
rest[-max_turns:]
이것은 리스트의 뒤에서부터 max_turns개를 가져오는 것입니다.
예를 들어:
rest = ["A", "B", "C", "D"]
이고:
max_turns = 2
이면:
rest[-2:]
→
["C", "D"]
입니다.
즉:
가장 최근의 2개 메시지를 가져온다.
입니다.
23. return system + rest[-max_turns:]
결국:
return system + rest[-max_turns:]
는
System 메시지는 유지하고, 나머지 메시지는 최근 것만 가져와서 합친다.
는 뜻입니다.
현재 history가:
1. system
2. user
3. assistant
4. user
5. assistant
이고:
max_turns = 2
이면:
system
+
user(최근)
assistant(최근)
가 됩니다.
즉:
system
user: 참, 저녁에 출발하는 비행기만 부탁해.
assistant: 세 개의 항공편을 찾았습니다.
정도가 남습니다.
24. 그런데 max_turns라는 이름은 조금 애매하다
여기서 하나 짚고 넘어가야 합니다.
코드는:
rest[-max_turns:]
이므로 실제로는 메시지 개수를 자르고 있습니다.
따라서:
max_turns=2
라고 해도 엄밀하게는:
최근 2개의 메시지
입니다.
일반적으로 turn을:
user → assistant
한 쌍으로 생각한다면 max_turns=2는 4개의 메시지를 의미할 수도 있습니다.
따라서 교육용 코드에서는 괜찮지만, 실전에서는 이름을:
max_messages
처럼 짓는 것이 더 정확합니다.
25. show() 함수
def show(messages):
for m in messages:
role = m["role"].upper()
print(f"{role:<9} | {m['content']}")
이 함수는:
대화 history를 사람이 보기 좋게 출력하는 함수
입니다.
26. .upper()
role = m["role"].upper()
예를 들어:
"user".upper()
→
USER
"assistant".upper()
→
ASSISTANT
입니다.
그래서 화면에:
USER
ASSISTANT
처럼 대문자로 표시됩니다.
27. <9는 왼쪽 정렬
여기:
f"{role:<9}"
에서:
<
는 왼쪽 정렬입니다.
그리고:
9
는 9칸을 확보합니다.
따라서:
USER
처럼 9칸에 맞춰 왼쪽에 붙습니다.
그래서:
print(f"{role:<9} | {m['content']}")
결과가:
SYSTEM | 너는 예약 에이전트야.
USER | 뉴욕까지 가는 항공기를 찾아줘.
ASSISTANT | 지금 비행편을 확인합니다.
처럼 정렬됩니다.
28. 마지막 부분
print(f"Stored messages: {len(history)}")
현재 history에 몇 개의 메시지가 들어 있는지 출력합니다.
5개이므로:
Stored messages: 5
입니다.
그리고:
print("-"*50)
구분선을 출력합니다.
--------------------------------------------------
29. show(trim(max_turns=2))
마지막으로:
show(trim(max_turns=2))
가 실행됩니다.
이것을 안쪽부터 읽으면 쉽습니다.
①
trim(max_turns=2)
먼저 history를 정리합니다.
현재:
SYSTEM
USER
ASSISTANT
USER
ASSISTANT
에서:
SYSTEM
은 유지하고,
나머지 중 최근 2개:
USER
ASSISTANT
만 가져옵니다.
②
그 결과를 show()에 전달합니다.
show(...)
그러면 예쁘게 출력합니다.
결과는 대략:
Stored messages: 5
--------------------------------------------------
SYSTEM | 너는 예약 에이전트야.
USER | 참, 저녁에 출발하는 비행기만 부탁해.
ASSISTANT | 세 개의 항공편을 찾았습니다.
가 됩니다.
30. 이번 코드의 핵심 구조
이번 코드는 크게 세 단계입니다.
① 메시지를 저장한다
↓
history
↓
② 필요한 메시지만 잘라낸다
↓
trim()
↓
③ 보기 좋게 출력한다
↓
show()
조금 더 Agent답게 표현하면:
┌──────────────┐
사용자 질문 ──→ │ history │
└──────┬───────┘
↓
대화 내용 축적
↓
너무 길어짐?
↓
trim()
↓
┌─────────┴─────────┐
│ │
system 유지 최근 대화만
│ │
└─────────┬─────────┘
↓
LLM 호출
↓
응답 생성
↓
history 추가
이게 Agent의 대화 기억 구조의 아주 기본적인 형태입니다.
31. 특히 기억해야 할 Python 문법
이번 코드에서 앞으로 계속 만나게 될 것들입니다.
| history = [] | 빈 리스트 |
| history.append(x) | 리스트 뒤에 추가 |
| history[1] | 리스트 두 번째 요소 |
| msg["role"] | 딕셔너리의 role 값 |
| msg["content"] | 딕셔너리의 content 값 |
| dict.items() | key/value를 함께 순회 |
| .upper() | 대문자로 변환 |
| .strip() | 앞뒤 공백 제거 |
| [-2:] | 뒤에서 2개 가져오기 |
| role:<9 | 9칸 왼쪽 정렬 |
| role:^9 | 9칸 가운데 정렬 |
| [x for x in ...] | 리스트 컴프리헨션 |
| try/except | 오류 처리 |
32. 그리고 가장 중요한 것: history는 AI의 "기억"인가?
여기서 개념적으로 하나 구분해야 합니다.
이 코드에서:
history = []
에 저장된 것은 LLM 자체의 기억(memory)이라기보다는,
애플리케이션이 LLM에게 다시 전달해주는 대화 기록
이라고 이해하는 것이 정확합니다.
예를 들어 사용자가:
뉴욕까지 가는 항공기를 찾아줘.
라고 하면 프로그램이 history에 저장합니다.
다음 질문:
저녁에 출발하는 것만 보여줘.
가 들어오면 프로그램은 이전 history와 현재 질문을 합쳐서 LLM에게 전달할 수 있습니다.
SYSTEM:
너는 예약 에이전트야.
USER:
뉴욕까지 가는 항공기를 찾아줘.
ASSISTANT:
지금 비행편을 확인합니다.
USER:
참, 저녁에 출발하는 비행기만 부탁해.
그러면 LLM은 앞의 대화 내용을 보고 "뉴욕행 항공편 중 저녁 출발"이라는 의미를 이해할 수 있습니다.
즉:
Agent의 기억 = history를 저장하고, 적절하게 잘라서 다시 LLM에게 전달하는 애플리케이션의 로직
이라고 생각하시면 상당히 정확합니다.
🌟 지금까지 공부한 코드와 연결하면
지금까지 보신 코드들을 하나로 연결하면 Agent의 구조가 상당히 선명해집니다.
[환경]
sys / os / virtual environment
↓
[Prompt]
f-string / system prompt / build_prompt()
↓
[대화]
history
↓
[대화 관리]
add()
trim()
↓
[LLM 호출]
call_model()
↓
[LLM 응답]
response
↓
[응답 분석]
strip()
lower()
split()
startswith()
↓
[Tool]
calculator
search
read_file
↓
[다시 history에 저장]
↓
[다음 Agent Loop]
그리고 바로 이 구조를 이해하면, 최근에 공부하셨던 Agent.run() → call_model() → Tool 실행 → history 추가 → MAX_STEPS까지 반복하는 코드가 훨씬 쉽게 보이기 시작합니다.
'AI, 클라우드, 문서, 자동화 > AI_AGENT' 카테고리의 다른 글
| II. AI Agent를 만들 때 자주 사용하는 패턴들 (0) | 2026.08.22 |
|---|---|
| I. Python 실행 환경이 제대로 갖춰져 있는지 확인하는 간단한 점검 프로그램 (0) | 2026.08.22 |
