본문으로 건너뛰기
페이지 목차
이 페이지에서 검색

멀티 에이전트

이 페이지에 묻기

멀티 에이전트 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.

생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.

MULTI · AGENT

멀티 에이전트

생성 수정

도구 실행·검색·기억·그래프 흐름·전문 에이전트의 협업을 구성하고 권한·평가·사람의 확인으로 통제합니다.

26개 문서 · 2일차 · 10 / 26

9차시 · 에이전트 루프 직접 구현

지도 위치 : 2일차 · README 4장의 ② 행동 층 — 도구를 한 번 부르던 앱이 스스로 반복 하기 시작하는 자리 | 이전: 8차시 보안 — 안전한 도구 실행 | 다음: 10차시 루프 제어와 가드레일 키워드 : Agent Loop, ReAct 패턴

이 차시를 다 읽으면

  1. 에이전트 루프를 유와 종차로 정의하고, 앞에서 배운 "도구 한 번 호출"(5차시)과 누가 흐름을 정하는가 축으로 가를 수 있다.
  2. ReAct(생각 → 행동 → 관찰의 반복)를 그림으로 그리고, 오피스봇 질문 하나에 대입할 수 있다.
  3. 수동 루프 코드에서 멈추는 조건 두 가지 (stop_reason, MAX_STEPS)를 찾아 짚을 수 있다.
  4. API는 무상태인데 루프가 앞 단계 결과를 "기억"하는 이유를 축 ⑦로 설명할 수 있다.
  5. 루프 로그를 읽고 "몇 번 돌았나, 무엇을 불렀나, 왜 멈췄나"를 말할 수 있다.

0. 이 차시의 판별 축

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것큰 축 (README 5장)
1⭐ 누가 흐름을 정하는가코드 — 미리 짠 순서대로 도구를 부름모델 — 결과를 보고 다음 행동을 정함워크플로우 ↔ 에이전트 루프중심 축 (README 5.2), 부모 ⑦
2무상태 ↔ 상태API 호출 한 번은 이전을 모름messages 리스트에 관찰이 쌓임루프가 앞 단계를 "기억"하는 방식⑦ 무상태 ↔ 상태
3한 번 ↔ 반복도구 호출 1회 → 답목표에 닿을 때까지 여러 바퀴도구를 쓰는 앱 ↔ 에이전트이 차시의 축
4누가 멈추는가모델 — 답을 냄(end_turn)코드 — 상한에 닿으면 끊음(MAX_STEPS)정상 종료 ↔ 강제 종료부탁 ↔ 강제 (10차시에서 확장)

이 차시의 질문 — 직원이 "다음 주 월~수 휴가 쓸 수 있어?"라고 물으면 잔여 연차 조회 와 휴가 규정 확인 이 둘 다 필요합니다. 앞에서 배운 도구 호출(5차시)은 "요청 → 실행 → 결과 → 답"의 한 바퀴였고, 다중 도구 라우팅(7차시)은 여러 도구 중 하나를 고르는 일이었습니다. 이번 차시는 한 바퀴를 여러 번 돌리고, 몇 바퀴를 돌지 모델이 정하게 합니다. 1일차가 대부분 중심 축의 왼쪽(코드가 흐름을 정함)이었다면, 이 차시에서 처음으로 오른쪽 으로 건너갑니다.


1. ⭐ 누가 흐름을 정하는가 — 한 번에서 반복으로

1.1 앞에서 배운 한 바퀴 (5차시 회수)

짧게 다시 짚습니다. 앱이 tools=[...](도구 선언: 이름 · 설명 · 인자 형식)를 붙여 모델을 부르면, 모델은 답 대신 "이 도구를 이 인자로 불러 달라"는 tool_use 블록 을 낼 수 있습니다. 이때 stop_reason은 "tool_use"입니다. ⭐ 모델은 실행하지 않고 요청만 합니다. 앱이 함수를 실행해 그 결과를 tool_result로 돌려주고 다시 부르면, 모델이 결과를 읽고 답합니다.

질문필요한 도구 호출한 바퀴로 끝나는가
"올해 연차 며칠 남았어?"잔여 조회 1번⭕
"다음 주 월~수 휴가 쓸 수 있어?"잔여 조회 → (결과를 보고) 규정 확인 → 답❌ 두 번째 호출이 첫 결과에 따라 달라짐
"IT 티켓 상태 보고, 아직 미처리면 새로 접수해 줘"조회 → (미처리면) 접수❌ 다음 행동이 관찰에 달려 있음

⭐ 아래 두 줄이 핵심입니다. 다음에 무엇을 할지가 앞 결과를 봐야 정해지면 , 순서를 미리 코드로 짤 수 없습니다. 그 판단을 모델에게 넘기는 구조가 에이전트 루프입니다.

1.2 정의와 경계

에이전트 루프 (Agent Loop) — [LLM 응용의 실행 구조]인데, [ 모델의 응답이 도구 요청인 동안 "호출 → 실행 → 결과 돌려주기"를 반복하고, 모델이 답을 내거나 코드가 정한 상한에 닿으면 멈춘다 ]는 점에서 코드가 정한 순서대로 도구를 부르는 워크플로우와 구별된다.

흐름으로 이해하기 · 설명용 예시

에이전트 루프에는 ‘다음 행동’과 ‘멈춤’이 있습니다

작업이 끝나지 않았다고 무한히 반복하게 두지 않습니다.

1 / 4 · 관찰
이 단계에서는

사용자 요청과 이전 도구 결과 확인

단계를 누르면 해당 설명에서 멈춥니다.

반복 횟수·시간·토큰 예산 같은 상한은 코드로 적용합니다. 애니메이션 반복은 실제 에이전트 실행이 아닙니다.

워크플로우 (코드가 정함)에이전트 루프 (모델이 정함)
다음 행동개발자가 짠 if · 순서모델이 직전 관찰을 보고 결정
도구 호출 횟수미리 정해짐실행해 봐야 앎 (0번일 수도, 6번일 수도)
예측 · 시험쉬움어려움 — 같은 질문에 경로가 달라질 수 있음
비용 · 맞는 일고정에 가까움 · 순서가 늘 같은 일바퀴 수만큼 늘어남 (10차시) · 순서를 미리 정할 수 없는 일

1.3 반례 — 이건 아직 에이전트 루프가 아닙니다

대상왜 아닌가
도구 목록을 준 한 번의 호출반복이 없음. 5차시의 한 바퀴
코드가 도구 셋을 정해진 순서로 부르고 마지막에 LLM이 요약반복은 있지만 순서를 코드가 정함 . 워크플로우
응답이 깨졌을 때 같은 요청을 다시 보내는 재시도반복이지만 관찰을 보고 다음 행동을 고르지 않음
한 응답 안에 tool_use가 셋 (병렬 도구 호출)한 바퀴 안에서 여럿을 동시에 요청한 것. 바퀴 수는 1

💡 비유(기억 보조용): 체크리스트를 위에서부터 그대로 따르는 신입 ↔ 서류를 하나 확인할 때마다 다음에 어디를 볼지 스스로 정하는 담당자. 다만 이 담당자에게는 "최대 여덟 군데까지만 확인"이라는 규칙을 회사(코드)가 걸어 둡니다(5절).


2. ReAct 패턴 — 생각 · 행동 · 관찰

2.1 정의

ReAct (Reason + Act) — [에이전트 루프의 설계 패턴]인데, [ 생각(Reason) → 행동(Act, 도구 호출) → 관찰(Observation, 도구 결과)을 한 바퀴로 삼고, 관찰을 다음 생각의 재료로 쓴다 ]는 점에서 생각만 길게 하고 한 번에 답하는 방식이나, 생각 없이 도구만 연달아 부르는 방식과 구별된다.

  ┌─▶ 생각 "잔여부터 봐야겠다" ─▶ 행동 tool_use: 잔여 조회 ─▶ 관찰 tool_result: "5일 남음" ─┐
  └──────────────────────────────── 관찰을 들고 다시 생각 ◀────────────────────────────────┘
        │ 더 할 일이 없다고 판단하면
        ▼
      답 (stop_reason = end_turn)

2.2 Claude API에서는 어디에 해당하는가

ReAct가 처음 나왔을 때는 프롬프트에 "Thought: … Action: … Observation: …" 형식을 글로 쓰게 했습니다. 지금은 API의 도구 호출 구조가 그 자리를 대신합니다.

ReAct의 칸Claude API에서의 모습누가 만드나
생각모델 내부의 생각(thinking) · 설명 text 블록모델
행동tool_use 블록 (name, input)모델이 요청
관찰tool_result 블록⭐ 앱이 실행해서 돌려줌
답stop_reason == "end_turn"인 응답의 text 블록모델

💡 최신 Claude 모델은 적응형 생각(adaptive thinking)이 기본으로 켜져 있어 "단계적으로 생각해"라고 따로 부탁할 필요가 줄었습니다. 그래서 응답 content에는 text · tool_use 외에 생각 블록이 섞여 올 수 있습니다. ⚠️ 이 때문에 루프는 응답을 골라 담지 않고 response.content를 통째로 다음 요청에 넣습니다(3.1).


3. 직접 구현 — 수동 루프

3.1 루프 골격

import anthropic
client = anthropic.Anthropic()            # 키는 환경변수 ANTHROPIC_API_KEY에서 자동으로 읽음

MAX_STEPS = 8
messages = [{"role": "user", "content": "다음 주 월~수 휴가 쓸 수 있어?"}]

for step in range(MAX_STEPS):
    response = client.messages.create(
        model="claude-opus-5-5", max_tokens=16000,
        system=SYSTEM_PROMPT, tools=tools, messages=messages,
    )
    if response.stop_reason != "tool_use":
        break                                              # 모델이 더 부를 도구가 없다 → 루프 끝
    messages.append({"role": "assistant", "content": response.content})
    results = []
    for block in response.content:
        if block.type == "tool_use":
            content, is_error = run_tool(block.name, block.input)
            results.append({"type": "tool_result", "tool_use_id": block.id,
                            "content": content, "is_error": is_error})
    messages.append({"role": "user", "content": results})
else:
    print("MAX_STEPS 도달 — 답을 내지 못하고 코드가 멈춤")   # 강제 종료

🔍 읽는 포인트

  • for step in range(MAX_STEPS) — ⭐ 반복 상한 . 모델이 멈추지 않아도 코드가 여덟 바퀴에서 끊습니다. while True로 쓰지 않습니다. 맨 아래 else:는 break 없이 끝까지 돌았을 때, 즉 "상한에 닿았다"는 뜻입니다.
  • if response.stop_reason != "tool_use": break — 모델이 도구를 더 요청하지 않으면 나갑니다. 다만 이 줄은 정상 답(end_turn)과 잘림(max_tokens) · 거절(refusal)을 구별하지 않습니다 → 5절에서 나눕니다.
  • messages.append(... response.content) — 모델의 요청을 먼저 , 그 뒤에 tool_result를 넣습니다. 둘은 tool_use_id로 묶인 짝 입니다.
  • 안쪽 for block — 한 응답에 tool_use가 여럿이면 결과를 모두 한 user 메시지에 담습니다.

3.2 도구 실행부 — 1일차의 안전 장치가 그대로 들어가는 자리

TOOL_FUNCS = {"get_leave_balance": get_leave_balance,
              "get_leave_policy": get_leave_policy}

def run_tool(name, args):
    func = TOOL_FUNCS.get(name)
    if func is None:
        return f"오류: 알 수 없는 도구 {name}", True
    try:
        return func(employee_id=session.employee_id, **args), False   # 신원은 로그인 세션에서
    except Exception as e:
        return f"오류: {e}", True                                      # 실패도 결과로 돌려줌

🔍 읽는 포인트

  • TOOL_FUNCS.get(name) — 모델이 없는 도구 이름을 지어내도 실행되지 않습니다. 허용 도구 목록 입니다(10차시에서 가드레일로 다시 봄).
  • employee_id=session.employee_id — 앞에서 배운 권한 경계 (8차시): 누구의 데이터인지는 모델이 준 인자가 아니라 로그인 세션 에서 가져옵니다.
  • return ..., True — 실패를 예외로 터뜨리지 않고 is_error가 붙은 결과로 돌려줍니다. 모델이 읽고 다른 방법을 고를 기회 가 되며, 이것도 관찰의 하나입니다.
  • ⭐ 루프는 새 안전 장치를 만들지 않습니다. 1일차의 권한 검사 · 마스킹 · 예외 처리가 run_tool 안에서 매 바퀴 똑같이 돌 뿐입니다.

4. 축 ⑦ — messages가 쌓이며 상태가 된다

4.1 API는 기억하지 않는다

앞에서 배운 대로(2차시) LLM API는 무상태 입니다. 두 번째 호출은 첫 번째 호출을 모릅니다. 그런데 루프의 3바퀴째에 모델은 1바퀴째 결과("5일 남음")를 압니다. 이유는 하나입니다 — 앱이 매번 지금까지의 messages 전체를 다시 보내기 때문입니다.

1바퀴 보냄: [user 질문]
     받음:  assistant [생각, tool_use 잔여조회]
2바퀴 보냄: [user 질문, assistant tool_use, user tool_result "5일"]
     받음:  assistant [생각, tool_use 규정확인]
3바퀴 보냄: [user 질문, assistant tool_use, user tool_result "5일",
             assistant tool_use, user tool_result "3일 전까지 신청"]
     받음:  assistant [text 답]  → stop_reason = end_turn
무엇이 상태를 들고 있나어디에 있나언제 사라지나
messages 리스트앱의 메모리 (축 ①)루프 · 요청이 끝나면
대화 이력 · 세션 저장소메모리 또는 DB16차시에서 설계

⚠️ 두 가지가 따라옵니다.

  • 매 바퀴 입력 토큰이 늘어납니다. 3바퀴째는 1 · 2바퀴의 내용을 다시 보냅니다. 바퀴가 늘수록 비용은 더하기가 아니라 누적의 누적 으로 늘어납니다 → 10차시의 비용 상한.
  • tool_use와 tool_result의 짝이 깨지면 400 에러 가 납니다. 이력을 잘라 줄일 때 이 짝을 같이 다뤄야 합니다 → 16차시.

앞으로 다시 만납니다 — 16차시(멀티턴 대화)에서 이 messages를 요청을 넘어 세션 단위로 저장하고, 18차시(그래프 워크플로우)에서는 같은 생각을 그래프의 State 라는 이름으로 다시 만납니다.


5. 멈춤 조건 — 누가 멈추는가

5.1 모델이 멈추는 경우 — stop_reason 읽기

stop_reason뜻루프가 할 일
tool_use도구를 요청함실행하고 한 바퀴 더
end_turn답을 다 씀⭕ 정상 종료. text 블록을 답으로
max_tokens출력이 max_tokens에서 잘림⚠️ 답이 중간에 끊김. 그대로 보여 주지 말 것
refusal안전상 거절⚠️ content를 읽기 전에 확인하고 거절 안내로
pause_turn · stop_sequence긴 작업의 일시 멈춤 · 지정한 멈춤 문자열용도에 따라 처리 (세부는 공식 문서 확인)

5.2 코드가 멈추는 경우 — 상한

모델이 "이제 답하겠다"고 판단해 주기를 기대 하는 것은 부탁입니다. 같은 도구를 계속 부르거나, 결과가 계속 실패해 헛도는 경우는 언제든 생깁니다. 그래서 MAX_STEPS로 강제 합니다. README 6장의 문장 2(모델은 판단하고, 코드는 지킨다)가 루프에서 처음 나타나는 자리입니다.

💡 MAX_STEPS 값은 "평소 가장 긴 정상 경로 + 여유"로 잡습니다. 오피스봇의 휴가 질문은 보통 2~3바퀴이므로 8이면 넉넉합니다. 상한에 닿았을 때 사용자에게 보여 줄 문장("확인이 길어져 담당자에게 넘깁니다" 등)도 미리 정해 둡니다.

앞으로 다시 만납니다 — 10차시에서 멈춤 조건이 넷으로 늘어납니다: 최대 반복, 토큰 · 비용 누적 상한, 같은 호출 반복 감지, 시간 제한.


6. 오피스봇 적용 — "다음 주 휴가 가능해?"

6.1 한 질문의 세 바퀴

도구는 둘입니다. get_leave_balance(year) — 잔여 연차 조회(6차시에서 만든 것), get_leave_policy(topic) — 휴가 규정 조회(지금은 고정된 규정 문구를 돌려주는 가짜 함수, 12~14차시에서 문서 검색으로 바뀜).

바퀴생각 (모델)행동 (tool_use)관찰 (tool_result)
13일을 쓰려면 잔여부터 봐야 한다get_leave_balance({"year": 2026})"남은 연차: 5일"
2일수는 충분. 신청 기한 규정이 있을 것이다get_leave_policy({"topic": "연차 신청 기한"})"연차는 사용 3일 전까지 신청"
3둘 다 확인됨. 답할 수 있다(없음) → end_turn—

최종 답(예시): "남은 연차가 5일이라 월~수 3일은 쓸 수 있습니다. 다만 규정상 사용 3일 전까지 신청해야 하니, 이번 주 목요일까지 신청하세요."

⚠️ 같은 질문이라도 모델은 순서를 바꾸거나(규정 먼저), 2바퀴에 두 도구를 한꺼번에 요청할 수도 있습니다. 경로가 고정되지 않는 것이 에이전트 루프의 본성입니다. 그래서 "어떤 경로든 답이 맞고 상한 안에서 끝나는가 "를 검사합니다.

6.2 루프 로그를 사람이 읽는 법

[REQ-7c21] step=1 stop=tool_use tool=get_leave_balance input={"year":2026} → "남은 연차: 5일"   in=1,240 out=88
[REQ-7c21] step=2 stop=tool_use tool=get_leave_policy  input={"topic":"연차 신청 기한"} → "3일 전까지 신청"  in=1,410 out=95
[REQ-7c21] step=3 stop=end_turn 답=212자                                                     in=1,690 out=240
[REQ-7c21] 종료=정상(end_turn) 바퀴=3 누적 in=4,340 out=423

(예시 — 실측 아님)

🔍 읽는 포인트 — 코드를 몰라도 이 네 줄은 읽을 수 있어야 합니다.

볼 것정상 신호이상 신호
종료= · 바퀴 수정상(end_turn), 질문 난이도에 맞음 (2~3)MAX_STEPS · max_tokens · refusal, 단순 질문에 6바퀴 이상
tool= · input= · → 결과질문과 관련된 도구, 질문에 있는 값, 결과에 값이 있음같은 도구 · 같은 인자가 연속, 오류:인데 같은 호출 반복 → 헛돎
in=바퀴마다 조금씩 증가한 바퀴에 크게 뜀 (긴 도구 결과가 들어옴)

⭐ 요청 ID(REQ-7c21)로 한 질문의 모든 바퀴를 묶어 둡니다. 로그에 직원 정보가 찍히므로 앞에서 배운 마스킹 (8차시: 외부로 나가거나 기록되기 전에 주민번호 · 전화번호 등을 가리는 것)을 로그에도 적용합니다.


7. 이런 도우미도 있다 — Tool Runner

SDK에는 위 루프를 대신 돌려 주는 Tool Runner (베타)가 있습니다. 함수에 데코레이터를 붙이면 도구 선언을 만들어 주고, 호출 → 실행 → 결과 돌려주기를 알아서 반복합니다.

from anthropic import beta_tool

@beta_tool
def get_leave_balance(year: int) -> str:
    """직원의 남은 연차 일수를 조회한다. 휴가 잔여를 물을 때 사용."""
runner = client.beta.messages.tool_runner(
    model="claude-opus-5-5", max_tokens=16000,
    tools=[get_leave_balance], messages=messages,
)
for message in runner:          # 바퀴마다 모델의 응답이 하나씩 나옴
    ...

🔍 읽는 포인트 : @beta_tool이 함수의 이름 · 설명(docstring) · 인자 타입으로 도구 선언을 만들고, for message in runner가 3.1의 루프를 대신합니다. 베타 기능이라 이름 · 옵션이 바뀔 수 있으니 공식 문서 확인 .

⭐ 도우미는 코드가 짧은 대신 루프 안을 감춥니다. 직접 구현을 먼저 하는 이유는, 감춰진 루프 안에서 무엇을 어디에 끼워야 하는지 (10차시의 비용 상한 · 반복 감지 같은 가드레일)를 알기 위해서입니다.

앞으로 다시 만납니다 — 11차시에서 같은 루프를 LangChain 프레임워크로 옮기며 "직접 구현 ↔ 프레임워크: 무엇을 얻고 잃나"를 다시 비교합니다.


8. 실습 — 맡기고 검수하기

지시

오피스봇에 에이전트 루프를 직접 구현해줘. (Tool Runner·프레임워크 쓰지 말고 messages.create로 직접)
- 참고: Claude API 공식 문서의 tool use 페이지 (최신 SDK 기준으로 확인하고, 확인한 문서 주소를 보고에 적을 것)
- 도구 2개: get_leave_balance(year) — 가짜 DB(직원 3명), get_leave_policy(topic) — 고정 규정 문구 3개
- 직원 신원은 함수 인자가 아니라 session 객체에서 가져올 것 (모델이 준 사번은 무시)
- MAX_STEPS = 8. stop_reason을 end_turn / max_tokens / refusal / 상한 도달로 나눠 처리
- 도구 실패는 is_error=True 인 tool_result로. 바퀴마다 [요청ID, step, stop_reason, 도구, 입력, 결과 앞 50자, input/output 토큰] 한 줄 로그
- API 키는 환경변수로만 (코드·로그에 키 금지)
테스트 질문: ① "안녕" ② "올해 연차 며칠 남았어?" ③ "다음 주 월~수 휴가 쓸 수 있어?"
 ④ get_leave_policy가 항상 오류를 내게 바꾼 뒤 ③  ⑤ MAX_STEPS=1로 바꾼 뒤 ③
추측하지 말고 실행 출력 원문을 붙여.
보고 형식: [질문 | 바퀴 수 | 부른 도구 순서 | 종료 이유 | 누적 토큰 | 최종 답 요약] 표

받을 증거

  • 질문 ①~⑤의 루프 로그 원문 과 위 형식의 표, ⑤에서 상한 도달로 코드가 멈췄다는 로그 줄, 참고한 공식 문서 주소

내가 직접 확인할 것

볼 것이걸로 아는 것
①의 바퀴 수가 1이고 도구 호출이 0번인가루프가 필요 없을 때 도구를 남발하지 않는가 (1.2)
③의 로그에 도구 두 개가 나뉘어 또는 함께 나타나는가모델이 관찰을 보고 다음 행동을 정했는가 (2절, 6.1)
③의 in= 숫자가 바퀴마다 늘어나는가messages 누적 = 상태 (4.1)
④에서 모델이 오류를 숨기지 않고 "규정을 확인하지 못했다"고 말하는가is_error 관찰이 전달됐는가 (3.2)
⑤에서 사용자에게 보이는 문장이 정해 둔 안내인가강제 종료 처리 (5.2)

부록 A. 스스로 확인하기

#질문답이 있는 곳
1에이전트 루프를 유와 종차로 정의하면? 워크플로우와 무엇으로 갈리나?1.2
2ReAct의 생각 · 행동 · 관찰은 Claude API의 어떤 블록에 해당하나?2.2
3응답을 골라 담지 않고 response.content를 통째로 넣는 이유는?2.2, 3.1
4수동 루프의 멈춤 조건 두 가지는?3.1, 5절
5무상태 API에서 루프가 앞 결과를 아는 이유는?4.1
6루프 로그에서 헛돎을 알아보는 신호는?6.2
7Tool Runner가 있는데도 직접 구현을 먼저 하는 이유는?7절

부록 B. 자주 하는 실수

실수바로잡기
while True로 루프를 돌림❌ 모델이 멈춰 주길 기대하는 것은 부탁. ⭕ MAX_STEPS로 강제
stop_reason != "tool_use"면 무조건 정상 답으로 처리⚠️ max_tokens(잘림) · refusal(거절)을 나눠 처리
응답에서 text 블록만 골라 assistant 메시지에 넣음❌ tool_use · 생각 블록이 빠져 짝이 깨짐. ⭕ response.content 통째로
도구 실패를 예외로 터뜨려 루프가 죽음⭕ is_error: True 결과로 돌려줘 모델이 대응하게
"루프를 쓰면 더 똑똑해진다"⚠️ 한 바퀴로 되는 질문에 루프는 비용만 늘림 (README 문장 1)

부록 C. NotebookLM 복습 프롬프트

이 9차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"누가 흐름을 정하는가(워크플로우 ↔ 에이전트 루프)", "ReAct의 세 칸", "messages 누적이 곧 상태", "멈춤 조건 두 가지"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.