📚 Agentic AI - 기업용 자율 에이전트 개발 7장 · 기억하는 에이전트 Agentic AI란

따라하기 — "그 상품 지난달은?" 이어서 묻기

한 줄 요약

대화를 다섯 턴으로 늘려 봅니다. 상품명 없이 "그거"·"그 상품" 만으로 계속 물어도 답이 이어집니다. 그리고 쌓인 메시지를 세어 비용이 어떻게 늘어나는지 확인합니다.


1. 다섯 턴 대화

ch07_memory.py[3] 질문 목록을 이렇게 늘려 보세요.

직접 고쳐 보기agentic_ai/code/ch07_memory.py 를 열어 [3] 의 질문 목록에 두 줄을 덧붙인 뒤 다시 실행하세요.

    for q in ["P0003 스마트워치 재고가 얼마나 남았나요?",
              "그거 재주문 기준은요?",
              "그럼 지금 발주해야 하나요?",
              "몇 개를 발주해야 하죠?",              # ← 추가
              "그 상품 리뷰 평점은 어떤가요?"]:        # ← 추가
        print(f"\n  손님: {q}")
        print(f"  봇  : {ask_analyst(q, thread_id='재고담당-1')}")

실행 결과입니다.

손님: P0003 스마트워치 재고가 얼마나 남았나요?
봇  : P0003 스마트워치 재고는 3개 남았습니다.

손님: 그거 재주문 기준은요?
봇  : P0003 스마트워치의 재주문 기준은 30개입니다.

손님: 그럼 지금 발주해야 하나요?
봇  : 네, 발주해야 합니다.

손님: 몇 개를 발주해야 하죠?
봇  : 27개를 발주해야 합니다.

손님: 그 상품 리뷰 평점은 어떤가요?
봇  : P0003 스마트워치의 리뷰 평점은 3.8점입니다.

2. 네 번째 질문이 흥미롭습니다

"몇 개를 발주해야 하죠?"  →  "27개를 발주해야 합니다."

질문에 숫자가 하나도 없습니다. 그런데 27이 나왔습니다.

30 (재주문 기준) - 3 (현재 재고) = 27

두 숫자 모두 앞선 대화에 있습니다. 모델이 기록에서 꺼내 뺀 것입니다.

그런데 잠깐 — 5장에서 "숫자는 반드시 도구로 계산한다" 고 했는데, 이건 모델이 머릿속으로 뺀 걸까요, 도구를 쓴 걸까요? 확인해 봅시다. (같은 절 4번에서 방법을 씁니다)


3. 다섯 번째 질문 — "그 상품"

"그 상품 리뷰 평점은 어떤가요?"  →  "P0003 스마트워치의 리뷰 평점은 3.8점입니다."

네 턴 전에 나온 상품을 여전히 기억합니다. 그리고 이번엔 새로운 계산(리뷰 평점)을 합니다.

reviews.csv 를 조회한 것이니 도구를 새로 불렀습니다. 기억은 "무엇에 대해 묻는지"를 알려 줬고, 계산은 도구가 했습니다.

역할 분담이 유지되고 있습니다.


4. 쌓인 것을 세어 봅시다

아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.

config = {"configurable": {"thread_id": "재고담당-1"}}
snapshot = analyst_with_memory.get_state(config)

from collections import Counter
print("쌓인 메시지:", len(snapshot.values["messages"]))
print(Counter(type(m).__name__ for m in snapshot.values["messages"]))
쌓인 메시지: 20
Counter({'AIMessage': 10, 'HumanMessage': 5, 'ToolMessage': 5})

다섯 번 물었는데 20개가 쌓였습니다.

손님이 한 번 물으면 메시지 4개가 쌓입니다.

  ① HumanMessage   "그거 재주문 기준은요?"
        │
        ▼
  ② AIMessage      (run_pandas 를 불러 주세요)      ← 모델의 첫 번째 말
        │
        ▼
  ③ ToolMessage    30                              ← 우리 코드가 실행한 결과
        │
        ▼
  ④ AIMessage      "재주문 기준은 30개입니다"        ← 모델의 두 번째 말

②와 ④가 둘 다 AIMessage 입니다. 모델이 한 턴에 두 번 말하는 것이죠 — 한 번은 "도구를 불러 주세요", 한 번은 "답은 이렇습니다". 사람 눈에는 답이 하나뿐이라 이상해 보이지만, 도구를 쓰는 에이전트는 항상 이렇게 두 번 말합니다.

  1턴  ████                    4개
  2턴  ████████                8개
  3턴  ████████████            12개
  4턴  ████████████████        16개
  5턴  ████████████████████    20개

턴마다 4개씩, 다섯 턴이면 20개. 뜯어보면 이렇습니다.

종류 개수 무엇
HumanMessage 5 손님의 질문 5개
AIMessage 10 도구 호출 요청 5개 + 최종 답변 5개
ToolMessage 5 도구 실행 결과 5개

AIMessage 가 10개인 것을 보세요. 질문 하나마다 모델이 두 번 말합니다 — "도구를 불러 주세요"와 "답은 이렇습니다".

4장에서 history 에 기록을 쌓던 작업이 자동으로 이루어지고 있는 것입니다.

ToolMessage 가 5개 = 다섯 질문 모두 도구를 썼다는 뜻입니다. 네 번째 질문(27개)도 도구를 썼습니다. 2번의 궁금증이 풀렸네요 — 시스템 지시의 "숫자는 반드시 도구로 계산한다"가 지켜졌습니다.


5. 비용은 어떻게 늘어나나

2장 7절의 그 이야기가 여기서 실제 수치로 드러납니다.

1턴째 질문: 모델이 받는 것 = 시스템 지시 + 메시지 0개 + 이번 질문
2턴째 질문: 시스템 지시 + 메시지 4개 + 이번 질문
3턴째 질문: 시스템 지시 + 메시지 8개 + 이번 질문
4턴째 질문: 시스템 지시 + 메시지 12개 + 이번 질문
5턴째 질문: 시스템 지시 + 메시지 16개 + 이번 질문

턴마다 4개씩 늘어납니다. (질문 1 + AI 2 + 도구 1)

그리고 한 질문당 모델 호출이 2번입니다(도구를 쓰므로). 그러니 실제로는 이보다 더 나갑니다.

직접 재 보려면 마지막 AIMessage 의 사용량을 보면 됩니다.

아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.

last_message = snapshot.values["messages"][-1]
print(last_message.usage_metadata)

6. 그래서 어디까지 기억해야 하나

정답이 없습니다. 상황에 따라 다릅니다.

상황 얼마나
고객 상담 (한 문의 처리) 그 문의가 끝날 때까지
데이터 분석 대화 주제가 바뀔 때까지
장시간 어시스턴트 최근 N개 + 요약

가장 단순한 방법은 앞 절에서 본 자르기입니다.

개선안 — 원하면 고쳐 보세요 · agentic_ai/code/ch07_memory.py
실습 기본 코드에는 없는 내용입니다. 지금 안 고쳐도 다음 절로 넘어갈 수 있습니다.

def counselor(state: State) -> dict:
    recent = state["messages"][-10:]
    answer = llm.invoke([{"role": "system", "content": SYSTEM}] + recent)
    return {"messages": [answer]}

주의할 점 — 도구를 쓰는 에이전트에서는 자를 때 조심해야 합니다. AIMessage(도구 호출 요청)와 ToolMessage(결과)가 짝인데, 중간에서 자르면 짝이 깨져 오류가 날 수 있습니다.

메시지 20개 — 한 턴에 4개(이어서)Human 턴 1 질문AI · 도구 호출Tool · 결과AI · 답변Human 턴 2 질문AI · 도구 호출Tool · 결과AI · 답변Human 턴 3 질문AI · 도구 호출Tool · 결과AI · 답변Human 턴 4 질문AI · 도구 호출Tool · 결과AI · 답변Human 턴 5 질문AI · 도구 호출Tool · 결과AI · 답변안전 — 턴 경계짝이 깨지지 않는다여기서 자르면도구 호출만 남고 결과가 없다→ 오류[-10:]은 도구 호출과 결과의 짝을 보장하지 않는다대화 기록을 자를 때는 경계를 확인해야 합니다. 개수만 기준으로 자르면 도구 호출과 결과가 분리될 수 있습니다.
도구 호출과 결과는 짝이라, 그 사이를 자르면 오류가 난다

이것도 사람이 판단하는 자리입니다. 라이브러리는 자동으로 안 잘라 줍니다. 어디서 자를지, 무엇을 남길지는 우리가 정합니다.


7. 실제 서비스라면

이 실습에서 안 다룬 것들입니다. 알아만 두세요.

문제 실무에서는
프로그램을 끄면 사라짐 SqliteSaver·PostgresSaver 로 교체
대화가 무한정 길어짐 자르기 + 요약
손님이 "새 대화" 원함 thread_id 발급
오래된 대화 정리 일정 기간 후 삭제 (개인정보)
대화 내용이 개인정보 저장 위치·기간·암호화를 정책으로

마지막 줄이 특히 중요합니다. 고객과의 대화를 저장한다는 것은 개인정보를 저장한다는 뜻입니다. 실제 서비스라면 무엇을 얼마나 보관할지가 기술 문제가 아니라 정책 문제가 됩니다.


스스로 해 보기

  1. 위의 Counter 코드로 메시지 구성을 확인하세요. ToolMessage 가 5개인가요?
  2. 대화를 열 턴으로 늘려 보세요. 메시지가 몇 개까지 쌓이나요?
  3. 마지막 답변의 usage_metadata 를 찍어, 첫 턴과 마지막 턴의 입력 토큰을 비교해 보세요.
  4. 중간에 다른 상품을 물어 보세요.
    text "P0003 재고는?" → "P0009 재고는?" → "그거 재주문 기준은?"
    "그거"가 어느 상품을 가리키나요? 예측하고 확인하세요.

4번이 중요합니다. 기억이 꼬이는 상황을 직접 만들어 보는 것입니다. 사람도 헷갈릴 만한 대화라면 모델도 헷갈립니다.


핵심 정리

  • 다섯 턴 대화에서 "그거"·"그 상품" 만으로 계속 답이 이어집니다.
  • "몇 개를 발주해야 하죠?" — 숫자 없는 질문에 27개가 나옵니다. 앞선 두 숫자를 도구로 계산했습니다.
  • 다섯 번 물으면 메시지 20개가 쌓입니다 — 질문 5 + AI 10 + 도구 결과 5.
  • AIMessage 가 두 배인 이유 — 질문마다 모델이 "도구 불러 주세요"와 "답은 이렇습니다"를 따로 말하기 때문입니다.
  • 턴마다 4개씩 늘어납니다. 대화가 길어질수록 입력 토큰 수가 늘어납니다.
  • 어디까지 기억할지는 정답이 없습니다. 자르기·요약·thread_id 교체 중에 상황에 맞게 고릅니다.
  • 도구를 쓰는 에이전트에서 자를 때는 AIMessageToolMessage 의 짝이 깨지지 않게 조심합니다.
  • 대화를 저장한다는 것은 개인정보를 저장한다는 뜻입니다. 실제 서비스에서는 정책 문제입니다.
← 이전 절05. 따라하기 CSV 분석 에이전트에 기억 붙이기다음 절 →07. thread_id 사용자별로 대화를 갈라 두기