9장. 도구를 쓰는 LLM 과 Agentic Workflow
에이전트 평가
운전면허 시험은 "운전을 잘하는 것 같다"로 붙이지 않고, 코스를 정해 두고 선을 밟았는지, 시간 안에 들어왔는지를 셉니다. 에이전트도 일 묶음을 정해 두고, 끝난 뒤의 상태가 기대와 같은지를 셉니다.
네 카드가 에이전트를 재는 항목입니다. 작업 성공률은 끝난 뒤의 상태(예약 목록)가 기대와 같은 비율, 도구 호출 수와 시간은 얼마나 돌아서 갔는지, 잘못된 쓰기는 하지 말아야 할 예약을 만든 수입니다. 여기서 시험 코스가 일 묶음입니다.
답의 글이 아니라 끝난 상태를 본다
| 일 | 기대하는 끝 상태 | 틀린 예 |
|---|---|---|
| "내일 2시 6명 화상회의 방 잡아 줘" | 조건에 맞는 방에 예약 하나가 생긴다 | 예약이 없다, 정원이 모자란 방에 예약 |
| "들판이 비었는지만 알려 줘" | 예약이 생기지 않는다 | 묻기만 했는데 예약을 해 버림 |
| "회의실 하나 잡아 줘"(시각 없음) | 예약하지 않고 되묻는다 | 아무 시각에나 예약 |
끝 상태로 채점하기
def grade(expected, new_bookings, condition=None):
"""expected: '예약' 또는 '예약 안 함'. new_bookings: 에이전트가 새로 만든 예약 목록"""
if expected == "예약 안 함":
return len(new_bookings) == 0
if len(new_bookings) != 1:
return False
b = new_bookings[0]
return all(b[k] == v for k, v in condition.items())
print(grade("예약 안 함", []), grade("예약 안 함", [{"방": "하늘"}]))
print(grade("예약", [{"날짜": "2026-10-28", "시작": "14:00"}], {"날짜": "2026-10-28", "시작": "14:00"}))True False 와 True 가 나옵니다. 에이전트가 "예약했습니다"라고 말해도 예약 목록에 없으면 실패이고, 말이 어색해도 상태가 맞으면 성공입니다.
코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
grade(expected, new_bookings, condition) | 기대(예약 또는 예약 안 함), 새로 생긴 예약 목록, 맞아야 할 조건을 받아 성공이면 True 를 돌려준다 |
len(new_bookings) == 0 | 새 예약이 하나도 없는지 본다 |
condition.items() | 조건 사전의 (키, 값) 짝을 하나씩 꺼낸다 |
all(b[k] == v for k, v in ...) | all 은 "모두"라는 뜻이다. 조건의 모든 짝이 예약과 같아야 True 다 |
이 코드가 뜻하는 것
택배가 왔는지 확인할 때, 기사의 "배송했습니다" 문자보다 현관 앞에 상자가 있는지를 보는 일입니다. 상자가 있어도 이름이 다르면 실패이고, 아무것도 주문하지 않았는데 상자가 오면 그것도 실패입니다. 여기서 현관 앞이 new_bookings, 받는 사람 이름 같은 확인 항목이 condition 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
if expected == "예약 안 함": return len(new_bookings) == 0 | 예약하면 안 되는 일은 새 예약이 없어야 성공 | 주문 안 했으면 상자가 없어야 한다 |
if len(new_bookings) != 1: return False | 예약은 꼭 하나여야 한다 | 상자가 없거나 두 개면 실패 |
return all(b[k] == v ...) | 날짜, 시작 같은 조건이 모두 맞는지 본다 | 상자의 이름과 주소가 맞나 |
grade("예약 안 함", [{"방": "하늘"}]) | 묻기만 했는데 예약이 생긴 경우 | 주문 안 한 상자가 왔다 |
True False 와 True 는 차례로 "예약 안 함 성공", "예약 안 함인데 예약해서 실패", "조건이 맞는 예약 하나라 성공"이라는 뜻입니다.
여러 일을 묶어 성공률 내기
일 네 개를 돌렸다고 치고, 끝 상태로 채점해 작업 성공률을 냅니다. 끝 상태는 설명을 위해 지어낸 것입니다.
def grade(expected, new_bookings, condition=None):
if expected == "예약 안 함":
return len(new_bookings) == 0
if len(new_bookings) != 1:
return False
b = new_bookings[0]
return all(b[k] == v for k, v in condition.items())
# 일 네 개를 돌렸다고 치고 끝 상태를 적는다(설명용으로 지어낸 결과)
cond = {"날짜": "2026-10-28", "시작": "14:00"}
results = [
grade("예약", [{"날짜": "2026-10-28", "시작": "14:00"}], cond), # 맞게 예약
grade("예약", [{"날짜": "2026-10-28", "시작": "02:00"}], cond), # 시각을 틀림
grade("예약 안 함", []), # 묻기만 하고 끝냄
grade("예약 안 함", [{"날짜": "2026-10-28", "시작": "14:00"}]), # 묻기만 했는데 예약함
]
print(results)
print("작업 성공률:", sum(results) / len(results))[True, False, True, False]
작업 성공률: 0.5| 결과 | 읽는 법 |
|---|---|
[True, False, True, False] | 일마다 성공과 실패다. 둘째는 오후 2시를 02:00 으로 적어 실패, 넷째는 하지 말아야 할 예약을 만들어 실패다 |
작업 성공률: 0.5 | 네 일 가운데 둘을 바르게 끝냈다. True 를 1 로 더해 일의 수로 나눈 값이다 |
넷째 일처럼 예약을 만들어 버린 실패는 "잘못된 쓰기"로 따로 셉니다. 성공률이 같아도 잘못된 쓰기가 많은 에이전트는 더 위험합니다.
주의할 점
- 에이전트의 일은 실행마다 길이 달라질 수 있습니다. 같은 일 묶음을 두세 번 돌려 성공률이 흔들리는지 봅니다.
- 평가할 때는 진짜 시스템이 아니라 복사본이나 가짜 저장소(이 장의
RoomDB)에 쓰게 합니다.