Observe-Reason-Act-Evaluate 사이클
루프를 해부한다는 것
섹션 제목: “루프를 해부한다는 것”에이전틱 루프가 “잘 돌아가고 있다”는 것은 직관적으로 이해할 수 있다. 하지만 루프를 신뢰성 있게 설계하고, 어디서 잘못됐는지 진단하고, 성능을 개선하려면 한 이터레이션을 더 작은 단위로 쪼개야 한다. 이 챕터에서는 루프의 한 사이클을 Observe(관찰) → Reason(추론) → Act(행동) → Evaluate(평가) 네 단계로 해부한다.
루프 한 이터레이션의 구조──────────────────────────────────────────────────────── ┌──────────────────────────┐ │ STATE │ ← 현재까지의 메시지·상태 └────────────┬─────────────┘ │ [OBSERVE] 환경에서 관찰 수집 (도구 결과, 에러, 이전 출력) │ [REASON] LLM: 컨텍스트 처리 → 다음 행동 결정 또는 완료 선언 │ ┌────────────┴─────────────┐ │ stop_reason == end_turn? │ └────────────┬─────────────┘ 아니오 │ 예 → 완료 [ACT] │ 도구 실행 (코드, 파일, API) │ [EVALUATE] 행동 결과를 STATE에 반영 종료 조건 검사 │ 다음 이터레이션 ↺────────────────────────────────────────────────────────Observe: 관찰 단계
섹션 제목: “Observe: 관찰 단계”루프의 첫 이터레이션에서 관찰은 사용자의 초기 지시다. 이후 이터레이션에서는 이전 도구 호출의 결과, 에러 메시지, 또는 외부 이벤트가 새로운 관찰이 된다.
관찰은 항상 컨텍스트에 추가된다. 이것이 루프가 상태를 유지하는 방식이다. 파일을 읽은 결과, 코드를 실행한 출력, API가 반환한 JSON이 모두 이전 메시지 히스토리에 누적된다. 관찰의 질이 루프 전체의 품질을 좌우한다. 에러 메시지가 너무 모호하면 모델이 올바른 판단을 내릴 수 없고, 결과가 너무 길면 컨텍스트를 낭비한다.
Reason: 추론 단계
섹션 제목: “Reason: 추론 단계”추론은 LLM 호출 자체다. 모델은 지금까지 누적된 컨텍스트(초기 지시 + 모든 이전 관찰)를 처리하고, 다음에 무엇을 할지 결정한다. ReAct 패턴에서 이 단계는 “Thought”에 해당한다. 모델이 중간 추론을 텍스트로 명시하면, 나중에 어디서 잘못됐는지 추적하기가 훨씬 쉬워진다.
추론 단계의 출력은 두 가지 중 하나다.
- tool_calls: 모델이 특정 도구를 호출하고 싶다는 선언. 루프는 Act 단계로 진행한다.
- end_turn: 모델이 목표를 달성했다고 판단. 루프가 종료된다.
추론 단계의 핵심 실패 모드는 환각(hallucination) 이다. 모델이 실제 관찰 없이 결과를 가정하거나, 실행하지 않은 도구의 결과를 꾸며낼 수 있다. 이를 방지하려면 모델이 행동 결과를 추론의 근거로 쓰도록 루프 구조를 강제해야 한다.
Act: 행동 단계
섹션 제목: “Act: 행동 단계”모델이 tool_calls를 반환하면 하네스(harness)가 실제 도구를 실행한다. 파일을 읽거나, 코드를 실행하거나, 외부 API를 호출하는 등의 외부 세계와의 상호작용이 일어난다. 중요한 것은 이 행동이 모델이 아닌 코드가 실행한다는 점이다. 모델은 무엇을 할지 선언하고, 하네스가 그 선언을 실행으로 옮긴다.
행동 단계의 실패 모드는 부작용(side effect) 이다. 파일을 덮어쓰거나, 이메일을 발송하거나, 데이터베이스를 수정하는 행동은 되돌리기 어렵다. 이 때문에 프로덕션 시스템에서는 행동의 범위를 제한하고, 파괴적 행동 전에 사람의 승인을 거치는 가드레일이 필요하다.
Evaluate: 평가 단계
섹션 제목: “Evaluate: 평가 단계”행동의 결과는 상태(state)에 반영되고, 종료 조건을 검사한다. 평가는 두 층위에서 이루어진다.
- 즉각적 평가: 도구가 성공했는가, 에러가 발생했는가. 이 정보가 다음 이터레이션의 관찰이 된다.
- 전체 목표 달성 여부: 최초 목표가 충족됐는가. 이것은 모델 판단(
end_turn)이거나 외부 검증기(테스트 통과, 조건 충족)로 확인된다.
평가 단계가 약하면 루프가 멈춰야 할 때 멈추지 않거나, 충분하지 않은 결과를 완료로 착각하는 조기 종료(premature success) 가 발생한다.
ReAct와의 대응 관계
섹션 제목: “ReAct와의 대응 관계”ReAct의 Thought→Act→Observe 사이클은 이 프레임과 거의 1:1로 대응된다.
| ReAct | 이 프레임 | 역할 |
|---|---|---|
| Thought | Reason | LLM이 다음 행동을 언어로 계획 |
| Act | Act | 실제 도구 실행 |
| Observe | Observe | 도구 결과를 컨텍스트에 주입 |
| (암묵적) | Evaluate | 종료 조건 검사, 상태 업데이트 |
Evaluate 단계는 ReAct 원논문에서 명시적으로 다루지 않았지만, 프로덕션 루프에서 가장 중요한 엔지니어링 결정 중 하나가 된다.
코드로 보는 한 이터레이션
섹션 제목: “코드로 보는 한 이터레이션”개념 이해용 의사 코드이며 실제 API와 다를 수 있습니다.
def run_one_iteration(state: AgentState, tools: list) -> AgentState: # ── OBSERVE ────────────────────────────────────── # state.messages에 이전 이터레이션의 관찰이 이미 누적되어 있음
# ── REASON ─────────────────────────────────────── response = model.generate( messages=state.messages, tools=tools, )
if response.stop_reason == "end_turn": # ── EVALUATE (종료 판정) ────────────────────── return state.mark_complete(response.text)
# ── ACT ────────────────────────────────────────── observations = [] for tool_call in response.tool_calls: result = execute_tool(tool_call) observations.append({ "tool": tool_call.name, "result": result, })
# ── EVALUATE (결과 반영) ────────────────────────── new_messages = state.messages + [ {"role": "assistant", "content": response.tool_calls}, {"role": "tool", "content": observations}, ] return AgentState(messages=new_messages, complete=False)이 함수를 while not state.complete 루프 안에서 반복 호출하면 완전한 에이전틱 루프가 된다. 다음 챕터에서는 이 루프 안에서 모델이 내부적으로 어떻게 추론하는지, 그 메커니즘인 Chain-of-Thought를 살펴본다.