9장. 에이전트 루프 직접 구현
ReAct — 생각, 행동, 관찰을 되풀이한다
한 줄 요약
에이전트 루프의 한 바퀴는 생각(Thought) → 행동(Action) → 관찰(Observation) 세 단계로 나눌 수 있습니다. 이 방식을 ReAct라고 부릅니다. 생각은 모델이 하고, 행동은 우리 코드가 실행하고, 관찰은 우리 코드가 모델에게 돌려줍니다.
1. 사람이 일하는 방식
주문 내역을 대신 찾아 주는 직원을 떠올립니다.
- "주문 목록부터 봐야겠다." — 생각
- 주문 목록 화면을 연다. — 행동
- "배송중이 세 건 있네." — 관찰
- "세 건의 송장을 하나씩 봐야겠다." — 다음 생각
- …
한 번 보고 끝나지 않습니다. 본 것을 근거로 다음에 할 일을 정하고, 더 볼 것이 없을 때 답합니다.
2. ReAct라는 이름
이 되풀이에 붙은 이름이 ReAct입니다. Reasoning(추론)과 Acting(행동)을 합친 말로, 2022년에 발표된 논문에서 제안되었습니다. 이름을 알아 두면 자료를 찾을 때 검색어로 쓸 수 있습니다.
| 단계 | 뜻 | 누가 하나 | 코드에서 |
|---|---|---|---|
| Thought (생각) | 지금 무엇을 해야 하는지 정한다 | 모델 | generate_content 호출 한 번 |
| Action (행동) | 정한 대로 도구를 실행한다 | 우리 코드 | fn(**fc.args) |
| Observation (관찰) | 실행 결과를 모델이 볼 수 있게 한다 | 우리 코드 | 결과를 contents에 추가 |
5장에서 배운 결정과 실행의 분리가 그대로입니다. 모델은 "이 도구를 이 인자로 불러 달라"고 요청할 뿐이고, 실행은 우리 코드가 합니다. ReAct는 그 왕복을 여러 번 잇는 것입니다.
3. 생각은 어디에 보이는가
"생각" 단계에서 모델이 속으로 한 추론을 우리가 글로 받는 것은 아닙니다. 우리에게 보이는 것은 생각의 결과입니다.
| 모델의 응답 | 뜻 |
|---|---|
function_call이 들어 있다 |
"아직 더 알아봐야 한다. 이 도구를 불러 달라" |
function_call이 없고 글만 있다 |
"알아볼 것이 끝났다. 이것이 답이다" |
그래서 루프의 갈림길은 한 줄입니다.
if not response.function_calls:
return response.text # 도구 요청이 없다 → 최종 답변
response.function_calls는 응답에 들어 있는 도구 요청의 목록입니다. 없으면 None입니다.
4. 한 번의 생각에 행동이 여러 개일 수 있다
모델은 한 응답에 도구 요청을 여러 개 담을 수 있습니다. "이 세 주문을 각각 조회해 달라"를 한 번에 말하는 것입니다. 이것을 병렬 함수 호출(parallel function calling) 이라고 합니다.
그래서 행동 단계는 요청 하나가 아니라 목록 전체를 돌며 실행합니다.
observation_parts = []
for fc in response.function_calls: # 요청이 여러 개일 수 있다
fn = tools.get(fc.name) # 이름으로 도구 함수를 찾는다
result = fn(**fc.args) if fn else {"error": f"없는 도구: {fc.name}"}
observation_parts.append(
types.Part.from_function_response(name=fc.name, response={"result": result}))
| 코드 | 뜻 |
|---|---|
fc.name |
모델이 요청한 도구 이름 |
fc.args |
모델이 채운 인자 (딕셔너리) |
tools.get(fc.name) |
8장의 도구 딕셔너리에서 그 이름의 함수를 꺼낸다 |
fn(**fc.args) |
인자 딕셔너리를 풀어 넣어 실행한다 |
**fc.args는 딕셔너리를 키=값 인자로 풀어 넣는 파이썬 문법입니다. fc.args가 {"order_id": "HR20260725002"}이면 fn(order_id="HR20260725002")와 같습니다.
8장에서 make_tools가 도구를 이름이 붙은 딕셔너리로 돌려준 이유가 여기 있습니다. 모델이 말한 이름으로 함수를 바로 찾을 수 있습니다.
5. 몇 바퀴를 돌지는 누가 정하는가
우리 코드에는 "먼저 목록을 조회하고, 다음에 상세를 조회하라"는 순서가 없습니다.
| 정하는 것 | 누가 |
|---|---|
| 어떤 도구를 부를지 | 모델 |
| 한 번에 몇 개를 부를지 | 모델 |
| 몇 바퀴 만에 답할지 | 모델 |
| 도구를 실제로 실행하는 것 | 우리 코드 |
| 최대 몇 바퀴까지 허락할지 | 우리 코드 |
| 어떤 도구를 쓸 수 있게 할지 | 우리 코드 |
모델에게 맡긴 것이 많습니다. 그래서 마지막 두 줄, 우리가 쥐고 있는 한도가 중요합니다. 이 한도를 다듬는 일이 10장으로 이어집니다.
핵심 정리
- 루프의 한 바퀴는 생각 → 행동 → 관찰입니다. 이 방식의 이름이 ReAct입니다.
- 생각은 모델, 행동(실행)과 관찰(결과 전달)은 우리 코드가 합니다.
- 응답에
function_call이 있으면 계속, 없으면 최종 답변입니다. - 모델은 한 응답에 도구 요청을 여러 개 담을 수 있습니다. 행동 단계는 목록 전체를 실행합니다.
- 도구의 순서와 바퀴 수는 모델이 정하고, 한도와 쓸 수 있는 도구는 우리가 정합니다.