[영상] ReAct 패턴과 루프 비용 줄이기
Day 3: ReAct 에이전트
[영상] ReAct 패턴과 루프 비용 줄이기
AI Agent 기초 > Day 3: ReAct 에이전트
- ReAct 패턴의 Thought-Action-Observation 루프 구조를 설명한다
- 루프 회차가 늘 때 토큰이 어디서 불어나는지 messages 구조로 짚는다
- 도구 결과 캐싱과 프롬프트 캐싱이 서로 다른 것임을 구분한다
- 루프 무한 반복을 막는 Max Step과 중복 호출 차단을 구현한다
영상 핵심 정리 — ReAct 패턴과 루프 비용
ReAct는 2022년 Google이 발표한 Agent 설계 패턴으로, LLM이 '생각하고 → 행동하고 → 결과를 관찰하고 → 다시 생각하는' 루프를 반복하여 복잡한 태스크를 완수한다.
1. ReAct 루프 구조
Goal: 라인 2의 불량률이 어제보다 2% 상승한 원인을 찾아라
Thought 1: 라인 2 센서 데이터를 먼저 조회해야 한다
Action 1: get_sensor_data(line=2, period='24h')
Observation 1: 온도 87°C (임계 85°C 초과), 진동 정상
Thought 2: 온도 이상이 원인일 수 있다. 냉각 시스템 상태 확인이 필요하다
Action 2: get_cooling_system_status(line=2)
Observation 2: 냉각수 유량 15% 감소
Thought 3: 냉각수 유량 감소 → 온도 상승 → 불량률 증가 연결이 확인됐다
Action 3: create_maintenance_ticket(issue='냉각수 유량 저하', priority='HIGH')
Answer: 냉각 시스템 이상으로 인한 열 변형이 불량률 상승 원인2. 비용은 어디서 늘어나는가
| 비용 요인 | 왜 늘어나는가 | 줄이는 방법 |
|---|---|---|
| 루프 반복 | 회차마다 messages 전체를 다시 보낸다. n회를 돌면 보낸 토큰의 합이 대략 n의 제곱에 비례한다 | Max Step 제한, 중복 호출 차단 |
| Tool 결과 누적 | 결과 문자열이 길수록 이후 모든 회차에 그대로 실려 간다 | 결과를 요약해 반환, 오래된 결과 잘라내기 |
| System Prompt 반복 | 매 회차 앞에 똑같이 붙는다 | 프롬프트 캐싱(공급자 기능) |
| 불필요한 Tool 호출 | 회차 자체가 늘어난다 | 프롬프트에 행동 순서·금지 규칙 명시 |
Day 1의 Agent 실행 파이프라인 해부에서 messages 배열이 회차마다 어떻게 길어지는지 봤다.
그 그림이 이 표의 첫 줄이다. 절감 폭은 대화 길이와 Tool 결과 크기에 따라 달라지므로,
"몇 % 줄었다"는 자기 워크로드에서 재기 전에는 말할 수 없다.
3. 두 캐싱은 다른 것이다
| 무엇을 저장하나 | 누가 하나 | 이 주차에서 | |
|---|---|---|---|
| 프롬프트 캐싱 | 매 요청 앞에 똑같이 붙는 프롬프트 접두부 | LLM 공급자 | 다루지 않는다 |
| 도구 결과 캐싱 | Tool이 돌려준 문자열 | 우리 코드(ToolCache) | Day 2·3·4 |
이름이 비슷해 같은 것으로 배우기 쉽다. Day 4의 ToolCache는 MES를 다시 부르지 않게 할 뿐,
모델에 보내는 토큰을 줄여 주지 않는다. 캐시에서 꺼낸 결과도 messages에는 그대로 실린다.
4. 무한 루프 방지
MAX_STEPS = 10
for step in range(MAX_STEPS):
response = llm_call(messages)
message = response.choices[0].message
if not message.tool_calls: # 모델이 더 부를 도구가 없다 -> 최종 답변
break
if is_duplicate_action(message, history): # 같은 (도구, 인자)의 되풀이
break
# Tool 실행 후 결과를 messages에 추가
else:
return 'Max step 초과: 수동 검토 필요'
finish_reason을 쓰고 싶다면 위치는 response.choices[0].finish_reason이고,
도구를 부른 턴에서는 값이 'tool_calls', 최종 답변 턴에서는 'stop'이다.
판정 기준이 둘로 갈리면 헷갈리므로 이 주차는 tool_calls 유무 하나로 통일한다.
그리고 루프가 끝난 것이 질문이 풀렸다는 뜻은 아니다. 모델이 더 부르지 않기로 했을 뿐이다. 목표 달성 여부를 따로 확인하는 분기는 Week 5의 LangGraph에서 다룬다.
5. 비용은 재야 안다
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=tools)
u = response.usage
print(u.prompt_tokens, u.completion_tokens)
# gpt-4o-mini 공개 단가(2026-07 확인): 100만 토큰당 입력 $0.15 / 출력 $0.60
cost = u.prompt_tokens / 1_000_000 * 0.15 + u.completion_tokens / 1_000_000 * 0.60
루프 회차마다 이 값을 더하면 질문 하나의 비용이 나온다. 단가는 바뀌므로 공급자 가격표를 확인하고 쓴다.
함정 주의: ReAct 루프에서 Observation이 모호하면 LLM이 같은 Action을 반복하는 '무한 루프'에 빠진다. 각 Tool의 반환값은 '다음 행동을 결정하기에 충분한 정보'를 포함해야 하며, 오류 발생 시도 오류 코드+설명을 명확히 반환해야 한다.
다음 task와의 연결
'ReAct 루프: Thought-Action-Observation' 읽기 자료에서 ReAct를 LangGraph로 구현하는 방법과 각 단계를 시각화하여 디버깅하는 기법을 학습한다.
루프 회차가 늘 때 토큰이 어떻게 불어나는지 계산으로 확인하고, 본인 코드에서 response.usage로 실제 값과 맞춰 보세요.
ReAct 루프를 10회 도는 에이전트에서, 회차마다 messages 전체를 다시 보낼 때 누적 입력 토큰이 어떻게 늘어나는지 계산식으로 보여줘. system 500토큰, 질문 50토큰, 회차마다 assistant 80 + tool 결과 300토큰이 쌓인다고 가정해줘. 그다음 (1) 오래된 tool 결과를 잘라낼 때, (2) 결과를 요약해 반환할 때 각각 누적 토큰이 어떻게 달라지는지 같은 가정으로 비교해줘.
- • ReAct = Reasoning + Acting: 추론(Thought)과 행동(Action)을 교차 반복하여 목표에 도달한다
- • 루프는 회차마다 messages 전체를 다시 보낸다. n회를 돌면 보낸 토큰의 합이 대략 n의 제곱에 비례해 늘어난다
- • 히스토리를 자르거나 요약하면 회차가 늘어도 토큰이 선형에 가깝게 유지된다. 절감 폭은 대화 길이와 Tool 결과 크기에 달렸으므로 response.usage로 직접 재야 안다
- • Max Step(예: 10회) 제한과 같은 (도구, 인자)의 되풀이 감지로 무한 반복을 막는다
