에이전트 루프 만들기
함수 호출을 while 루프로 감싸 모델이 도구를 여러 번 호출하며 작업을 끝내는 고객 지원 에이전트를 만들고, 종료 조건과 최대 단계를 정한다.
- 함수 호출에서는 도구 호출 요청 → 실행 → 결과 전달 → 최종 답변을 한 번씩 진행.
- 실제 작업은 앞 도구의 결과를 보고 다음 도구를 골라야 하는 경우가 많음.
- 에이전트 루프(agent loop): 모델이 "이제 답할 수 있다"고 판단할 때까지 모델 호출과 도구 실행을 반복하는 구조.
- "An agent is a
whileloop": 대화 기록을 매 턴 다시 넣으며 도는while루프가 에이전트의 뼈대(Breunig, 2026).
출처: Drew Breunig(2026), The State of Context Engineering.
생각-행동-관찰 루프
Mermaid 다이어그램 원문
flowchart LR
G[임무 수령] --> S[상황 파악<br/>대화·기억·도구 목록]
S --> T[생각<br/>모델이 다음 행동 결정]
T -->|도구 호출| A[행동<br/>애플리케이션이 도구 실행]
A --> O[관찰<br/>결과를 대화에 추가]
O --> T
T -->|도구 호출 없음| F[최종 답변]대화형 다이어그램을 불러오기 전에는 원문으로 표시됩니다.
- 임무 수령 → 상황 파악 → 생각 → 행동 → 관찰 → 다시 생각(Google, 2025). 뒤의 세 단계 반복이 생각-행동-관찰(Think-Act-Observe) 주기.
- 루프를 도는 동안 바뀌는 것은 모델이 아니라
messages. 매 턴 쌓인 대화 기록 전체가 다음 입력.
출처: Google(2025), Introduction to Agents.
실습 준비
!pip install -q openai
import json
from pathlib import Path
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=Path("openrouter-key.txt").read_text().strip(),
)
model = "openai/gpt-4.1-mini"
고객 지원 도구 만들기
- 예제: "내 주문 #12345는 어디 있나요?"에 답하는 고객 지원 에이전트.
- 필요한 일: ① 주문 정보에서 운송장 번호 찾기 → ② 운송장 번호로 배송 상태 조회 → ③ 종합해 답변.
- ②의 입력은 ①의 결과를 봐야 알 수 있음. 함수 호출 한 번으로는 첫 응답에서
find_order밖에 요청할 수 없음.
orders = {
"12345": {"product": "무선 마우스", "status": "출고 완료", "tracking_number": "ZYX987"},
"12346": {"product": "기계식 키보드", "status": "상품 준비 중", "tracking_number": None},
}
shipments = {"ZYX987": {"status": "배송 중", "location": "서울 강서 허브", "eta": "2026-11-03"}}
functions = {
"find_order": lambda order_id: orders.get(order_id.lstrip("#"), {"error": "없는 주문"}),
"get_shipping_status": lambda tracking_number: shipments.get(tracking_number, {"error": "없는 운송장"}),
}
tools = [{"type": "function", "function": {"name": name, "description": desc, "parameters": {
"type": "object", "properties": {param: {"type": "string"}}, "required": [param]}}}
for name, desc, param in [("find_order", "주문 조회. 출고 전이면 tracking_number가 null", "order_id"),
("get_shipping_status", "운송장 번호로 배송 상태 조회", "tracking_number")]]
- 없는 번호는 예외 대신
error항목으로 돌려줌. 모델이 결과를 읽고 다음 행동(번호 재확인 요청 등)을 정할 수 있게 하기 위함. functions: 모델이 요청한 이름으로 실행할 함수를 찾는 표.tools: 같은 도구들의 JSON Schema 정의(함수 호출과 같은 형식).
에이전트 루프
- 종료 조건(stop condition): 모델이 도구를 호출하지 않고 답변만 돌려줄 때.
- 최대 단계(max steps): 종료 조건이 끝내 오지 않을 때를 대비한 안전장치.
def run_agent(messages, tools, max_steps=5):
step = 0
while step < max_steps:
step += 1
message = client.chat.completions.create(
model=model, messages=messages, tools=tools, parallel_tool_calls=False).choices[0].message
messages.append(message.model_dump(exclude_none=True)) # 생각
if not message.tool_calls: # 종료 조건
return message.content
for call in message.tool_calls:
result = functions[call.function.name](**json.loads(call.function.arguments)) # 행동
print(f"[{step}단계] {call.function.name} → {result}")
messages.append({"role": "tool", "tool_call_id": call.id, # 관찰
"content": json.dumps(result, ensure_ascii=False)})
return "(최대 단계 도달)"
- 함수 호출 실습과 같은 부품(
model_dump,tool역할 메시지,tool_call_id)을while로 감쌌을 뿐. 실제로는 도구 실행 오류(없는 도구, 잘못된 인자)도try로 잡아 결과로 돌려주는 것이 좋음. - 실행 후
messages에 전체 궤적(trajectory)이 남음. 12주 하네스 실습에서 이run_agent에 승인·로그·예산 장치를 덧붙임.
system = "쇼핑몰 고객 지원 에이전트입니다. 주문과 배송 정보는 도구로 조회한 결과로만 답하세요."
def ask(question):
return run_agent([{"role": "system", "content": system}, {"role": "user", "content": question}], tools)
print(ask("내 주문 #12345는 어디 있나요?"))
실행 결과
[1단계] find_order → {'product': '무선 마우스', 'status': '출고 완료', 'tracking_number': 'ZYX987'}
[2단계] get_shipping_status → {'status': '배송 중', 'location': '서울 강서 허브', 'eta': '2026-11-03'}
고객님 주문하신 무선 마우스(#12345)는 현재 서울 강서 허브에서 배송 중입니다. 예상 도착일은 2026년 11월 3일입니다. 추가로 궁금하신 점 있으시면 알려주세요.
- 위 실행에서는 1단계에
find_order로 운송장 번호를 얻고, 2단계에 그 번호로get_shipping_status를 호출. 3단계에서 도구 없이 답변해 루프 종료. - 도구 순서는 코드에 적지 않았음. 모델이 앞 결과를 보고 정함.
- 대화 기록에는 모델 응답 3번(
assistant)과 도구 결과 2번(tool)이 쌓이고, 다음 호출 때 전체가 입력이 됨.
상황에 따라 달라지는 단계 수
출고 전 주문: 운송장 번호가 없으므로 배송 조회가 필요 없음.
print(ask("주문 12346은 언제 와요?"))
실행 결과
[1단계] find_order → {'product': '기계식 키보드', 'status': '상품 준비 중', 'tracking_number': None}
주문 번호 12346번은 현재 상품 준비 중인 상태입니다. 아직 출고되지 않아 운송장 번호가 없습니다. 상품이 출고되는 대로 배송 상태를 안내해 드리겠습니다.
- 위 실행에서는
tracking_number가None이라 배송 조회를 건너뛰고 1단계 만에 답변. 같은 루프가 질문에 따라 1단계로도, 2단계로도 끝남. LLM 출력이므로 문장은 실행마다 달라짐. - 더 해 볼 것: 없는 주문 번호(
99999)를 물으면error를 관찰한 뒤 어떻게 답하는지,run_agent(..., max_steps=1)이면 2단계가 필요한 질문에서 답하기 전에 멈추는지 확인. max_steps는 너무 작으면 일을 못 끝내고, 너무 크면 헛도는 루프의 비용을 키움. 멈췄을 때 무엇을 돌려줄지(중간 결과, 사람에게 넘기기)도 설계 대상. 예산·시간 한도, 사람의 중단 같은 다른 종료 방식은 12주 하네스에서 다룸.
워크플로와 에이전트
| 구분 | 워크플로(workflow) | 에이전트(agent) |
|---|---|---|
| 도구 순서 | 코드에 미리 정함 | 모델이 매 단계 결정 |
| 단계 수 | 고정 | 작업에 따라 달라짐 |
| 장점 | 예측 가능, 디버깅 쉬움 | 예상하지 못한 상황에 대응 |
| 단점 | 정해진 경로 밖의 요청에 약함 | 비용·지연이 들쭉날쭉, 엉뚱한 행동 가능 |
- 단계가 늘 같다면 워크플로가 더 싸고 안정적. 다음 행동이 앞 결과에 따라 달라질 때 에이전트 루프가 필요.
출처: Anthropic(2024), Building effective agents.
퀴즈
주문 #12345의 배송 위치를 묻는 질문을 함수 호출 한 번으로 끝낼 수 없는 이유는?
퀴즈를 풀려면 대화형 기능을 불러와야 합니다.