LLM 엔지니어링9장 · 도구를 쓰는 LLM 과 Agentic Workflow19 / 21← 이전목차다음 →MetaContents

9장. 도구를 쓰는 LLM 과 Agentic Workflow

프로젝트 1 — 회의실 예약 에이전트

가상 회사 "솔내기술"의 회의실 6개와 기존 예약 10건이 있습니다. 직원이 말로 회의실을 예약하는 에이전트를 만들어, 요청 10개를 처리하게 해 보세요. 요청에는 예약해야 하는 것, 조회만 하는 것, 빈 방이 없는 것, 시각이 빠진 것이 섞여 있습니다. 오늘은 2026년 10월 27일(화)로 둡니다. 회사, 방, 예약은 모두 지어낸 것입니다.

파일생김새
rooms.csv방, 층, 정원, 장비(화상회의, TV, 화이트보드를 쉼표로). 6행
room_bookings.csv날짜, 방, 시작, 끝, 제목. 10월 28일~30일의 기존 예약 10건
agent_tasks.jsonl요청, 기대("예약" 또는 "예약 안 함"), 조건(예약일 때 날짜, 시작, 끝, 인원, 장비, 방)

요구사항은 다섯 가지입니다.

  1. 예약 저장소(RoomDB)를 만들고, 빈 방 찾기(find_free_rooms)와 예약(book_room) 도구를 붙입니다. 예약 도구는 없는 방, 틀린 시각, 겹치는 예약을 막습니다.
  2. 짧은 지시문으로 만든 에이전트(판 1)를 요청 10개로 채점합니다. 채점은 에이전트의 답이 아니라 끝난 뒤의 예약 목록으로 합니다.
  3. 지시문에 예약 순서를 적고 도구의 오류 문구에 고치는 법을 넣은 에이전트(판 2)를 같은 요청으로 채점합니다.
  4. LLM 은 조건만 JSON 으로 뽑고 나머지는 코드가 하는 워크플로(판 3)를 만들어 채점합니다. LLM 이 뽑은 값은 코드로 검증합니다.
  5. 세 판의 성공 수, 도구 호출 수(또는 LLM 호출 수), 걸린 시간을 표로 견주고, 어느 판을 쓸지 정합니다.

단계별 힌트입니다.

  1. 시각은 "14:00" 같은 글자로 두면 글자끼리 크기를 견줘도 순서가 맞습니다. 두 예약이 겹치는지는 시작1 < 끝2 and 시작2 < 끝1 입니다.
  2. 요청마다 새 RoomDB 를 만들어, 앞 요청의 예약이 뒤 요청에 남지 않게 합니다.
  3. 판 2의 지시문에는 "1) find_free_rooms 로 빈 방을 찾는다 2) 방을 고른다 3) book_room 으로 예약한다", "시각은 24시간 꼴", "오류가 오면 고쳐 다시 시도한다"를 적습니다.
  4. 판 3의 조건 뽑기는 response_format={"type": "json_object"} 로 JSON 만 받습니다. 사용자의 말에 없는 방 이름과 장비는 지우고, 시각이 말에 없으면(숫자가 없으면) 예약하지 않고 되묻습니다.
  5. 작은 모델은 같은 설정이어도 실행마다 결과가 하나둘 달라질 수 있습니다. 두 번 돌려 보면 좋습니다.

확인할 것: 다 풀었으면 항목마다 스스로 확인해 체크해 보세요.

실제 데이터로 해 보려면

회의실 예약은 회사의 그룹웨어나 캘린더 서비스가 맡고 있는 경우가 많습니다. 실제 시스템에 붙일 때는 반드시 시험용 달력이나 복사본에서 먼저 해 봅니다.

자료구하는 곳내용
캘린더 공식 API쓰고 있는 캘린더·그룹웨어 서비스의 개발자 문서일정 조회와 생성 API. 인증키는 발급받은_인증키 처럼 환경 변수에서 읽는다
공공 시설 예약 정보공공데이터포털(data.go.kr)에서 "공공시설 예약"으로 검색지자체의 회의실·강당 예약 정보. 조회 위주로 연습하기 좋다
우리 팀의 예약 기록그룹웨어에서 내려받은 표(개인정보를 지운 뒤)실제 요청의 말투와 자주 쓰는 조건을 알 수 있다

실제 예약을 만드는 쓰기 도구는 사람 확인(실습 6)을 거치게 하고, 처음에는 조회만 되는 권한의 인증키로 시작합니다.

코드의 흐름

예시 풀이의 코드가 어떤 차례로 흘러가는지 한 장으로 그렸습니다. 풀기 전에 큰 줄기를 먼저 보고, 풀다가 막히면 지금 어느 상자에 있는지 확인해 보세요.

저장소, 판 1 채점, 판 2 지시, 워크플로, 세 판 견주기의 다섯 상자를 화살표로 이은 회의실 예약 에이전트 프로젝트의 코드 흐름도

주황 화살표가 실행 차례이고, 상자의 번호는 예시 풀이의 단계 번호와 같습니다. 1단계(청록)에서 예약 저장소 RoomDB 와 find_free_rooms, book_room 도구를 만들고, 2단계(마젠타)에서 run_agent 로 짧은 지시문의 판 1 을 돌려 grade 로 끝난 뒤의 새 예약 목록을 채점합니다. 3단계(다홍)는 SYSTEM_2 에 예약 순서와 친절한 오류 문구를 더한 판 2, 4단계(빨강)는 LLM 이 조건 뽑기만 하고 나머지를 코드가 하는 워크플로 workflow 의 판 3 입니다. 5단계(짙은 청록)는 row1~row3 의 성공 수와 호출 수를 한 표로 견줍니다.

결과 보기 (예시 풀이)

1단계. 예약 저장소와 도구

Python
import json, re, time, urllib.request
import pandas as pd
from openai import OpenAI, APIError

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
MODEL = "qwen2.5:3b"
rooms = pd.read_csv("rooms.csv")
tasks = [json.loads(l) for l in open("agent_tasks.jsonl", encoding="utf-8")]
NAMES = ", ".join(rooms["방"])

def overlap(s1, e1, s2, e2):
    return s1 < e2 and s2 < e1                       # 두 시간이 겹치나

class RoomDB:
    """요청마다 새로 만드는 가짜 예약 저장소. new 에 새로 만든 예약이 쌓인다"""
    def __init__(self, helpful=False):
        self.bookings = pd.read_csv("room_bookings.csv").to_dict("records")
        self.new, self.helpful = [], helpful           # helpful: 오류 문구에 고치는 법을 넣을지(판 2)
    def is_free(self, room, date, start, end):
        return not any(b["방"] == room and b["날짜"] == date and overlap(start, end, b["시작"], b["끝"]) for b in self.bookings)
    def find_free_rooms(self, date, start, end, people=1, equipment="", **_):
        ok = rooms[(rooms["정원"] >= int(people or 1)) & rooms["장비"].str.contains(equipment or "")]
        return {"빈 방": [r for r in ok["방"] if self.is_free(r, date, start, end)]}
    def book_room(self, room, date, start, end, title="회의", **_):
        tip = lambda s: s if self.helpful else ""
        if room not in set(rooms["방"]):
            return {"오류": f"'{room}' 라는 방은 없습니다." + tip(f" 방 이름은 {NAMES} 가운데 하나입니다. find_free_rooms 로 먼저 찾으세요.")}
        if not start < end:
            return {"오류": f"시작 {start} 가 끝 {end} 보다 늦거나 같습니다." + tip(" 24시간 꼴(오후 3시 = 15:00)로 다시 적으세요.")}
        if not self.is_free(room, date, start, end):
            return {"오류": f"{room} 은 그 시간에 이미 예약이 있습니다." + tip(" find_free_rooms 로 다른 방을 찾으세요.")}
        rec = {"날짜": date, "방": room, "시작": start, "끝": end, "제목": title}
        self.bookings.append(rec)
        self.new.append(rec)
        return {"예약됨": rec}

db = RoomDB()
print(db.find_free_rooms("2026-10-28", "14:00", "15:00", 6, "화상회의"))
print(db.book_room("숲", "2026-10-28", "14:00", "15:00", "시험"))
print(db.book_room("별빛", "2026-10-28", "14:00", "15:00", "시험"))
출력
{'빈 방': ['별빛']}
{'오류': '숲 은 그 시간에 이미 예약이 있습니다.'}
{'예약됨': {'날짜': '2026-10-28', '방': '별빛', '시작': '14:00', '끝': '15:00', '제목': '시험'}}

2단계. 에이전트와 채점(판 1)

Python
def fn(name, desc, props, req):
    return {"type": "function", "function": {"name": name, "description": desc,
            "parameters": {"type": "object", "properties": props, "required": req}}}
P = {"room": {"type": "string", "description": "방 이름. 예: 숲"}, "date": {"type": "string", "description": "날짜 YYYY-MM-DD"},
     "start": {"type": "string", "description": "시작 시각 HH:MM (24시간)"}, "end": {"type": "string", "description": "끝 시각 HH:MM (24시간)"},
     "people": {"type": "integer", "description": "참석 인원"}, "title": {"type": "string", "description": "회의 제목"},
     "equipment": {"type": "string", "description": "화상회의, TV, 화이트보드 가운데 하나. 없으면 빈 글자"}}
TOOLS = [fn("find_free_rooms", "날짜와 시간에 비어 있고 인원과 장비 조건에 맞는 회의실 이름 목록을 돌려준다",
            {k: P[k] for k in ["date", "start", "end", "people", "equipment"]}, ["date", "start", "end", "people"]),
         fn("book_room", "회의실을 예약한다", {k: P[k] for k in ["room", "date", "start", "end", "title"]},
            ["room", "date", "start", "end", "title"])]
SYSTEM_1 = ("당신은 솔내기술의 회의실 예약 도우미입니다. 오늘은 2026-10-27(화)입니다. 도구로 확인한 사실만 말하세요. "
            "날짜, 시작과 끝 시각, 인원 가운데 빠진 것이 있으면 예약하지 말고 되물으세요. "
            "조건에 맞는 빈 방이 없으면 예약하지 말고 알리세요. 사용자가 예약하지 말라고 하면 예약하지 마세요.")

def unload(model):
    body = json.dumps({"model": model, "keep_alive": 0}).encode()
    urllib.request.urlopen(urllib.request.Request("http://localhost:11434/api/generate", data=body,
                                                  headers={"Content-Type": "application/json"})).read()

def chat(**kw):
    """7차시의 ask 처럼, 오류가 나면 모델을 내렸다가 다시 보낸다"""
    for _ in range(3):
        try:
            return client.chat.completions.create(model=MODEL, temperature=0, seed=42, **kw).choices[0].message
        except APIError:
            unload(MODEL)
            time.sleep(2)
    raise RuntimeError("Ollama 가 계속 오류를 냅니다")

def run_agent(request, system, db, max_steps=6):
    messages = [{"role": "system", "content": system}, {"role": "user", "content": request}]
    calls = []
    for _ in range(max_steps):
        msg = chat(messages=messages, tools=TOOLS)
        if not msg.tool_calls:
            return msg.content, calls
        messages.append({"role": "assistant", "content": msg.content or "", "tool_calls": [tc.model_dump() for tc in msg.tool_calls]})
        for tc in msg.tool_calls:
            try:
                args = json.loads(tc.function.arguments)
                result = getattr(db, tc.function.name)(**args)
            except Exception as e:
                args, result = {}, {"오류": str(e)[:80]}
            calls.append((tc.function.name, args, result))
            messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(result, ensure_ascii=False)})
    return "[걸음 한도]", calls

def grade(task, db):
    """끝난 뒤의 예약 목록으로 채점한다(본문 '에이전트 평가')"""
    if task["기대"] == "예약 안 함":
        return len(db.new) == 0
    if len(db.new) != 1:
        return False
    b, c = db.new[0], task["조건"]
    room = rooms[rooms["방"] == b["방"]].iloc[0]
    return (b["날짜"], b["시작"], b["끝"]) == (c["날짜"], c["시작"], c["끝"]) and room["정원"] >= c["인원"] \
        and c["장비"] in room["장비"] and c.get("방", b["방"]) == b["방"]

def evaluate_agent(system, helpful, label):
    start, ok, n_calls = time.time(), 0, 0
    for t in tasks:
        db = RoomDB(helpful=helpful)
        answer, calls = run_agent(t["요청"], system, db)
        good = grade(t, db)
        ok, n_calls = ok + good, n_calls + len(calls)
        print(" ", "O" if good else "X", t["요청"][:28], "|", [(c[0], c[1].get("room", "")) for c in calls], "| 새 예약:", [b["방"] for b in db.new])
    row = {"판": label, "성공(/10)": ok, "호출 수": n_calls, "초": round(time.time() - start)}
    print(row)
    return row

row1 = evaluate_agent(SYSTEM_1, helpful=False, label="1. 짧은 지시문")
출력
  X 내일 오후 2시부터 3시까지 6명이 화상회의를 할  | [('find_free_rooms', ''), ('book_room', '화상회의실')] | 새 예약: []
  O 10월 29일 오전 10시부터 11시까지 4명 회의 | [('find_free_rooms', ''), ('book_room', '하늘')] | 새 예약: ['하늘']
  O 들판이 10월 28일 오후 4시부터 5시까지 비어  | [('find_free_rooms', '')] | 새 예약: []
  O 10월 29일 오후 2시부터 3시까지 20명이 들어 | [('find_free_rooms', '')] | 새 예약: []
  O 회의실 하나 잡아 줘. | [] | 새 예약: []
  X 10월 30일 오전 9시부터 10시까지 화이트보드가 | [('find_free_rooms', ''), ('book_room', '화이트보드 있는 방')] | 새 예약: []
  X 10월 28일 오후 1시부터 2시까지 10명 회의실 | [('book_room', '10명 회의실')] | 새 예약: []
  O 10월 29일 오전 9시부터 오후 6시까지 들판을  | [] | 새 예약: []
  X 내일 오전 11시부터 12시까지 8명이 TV 있는  | [('find_free_rooms', '')] | 새 예약: []
  O 10월 30일 오후 3시부터 4시까지 숲을 예약해  | [('book_room', '숲')] | 새 예약: ['숲']
{'판': '1. 짧은 지시문', '성공(/10)': 6, '호출 수': 11, '초': 38}

3단계. 순서를 적고 오류 문구를 고친 에이전트(판 2)

Python
SYSTEM_2 = SYSTEM_1 + (" 예약은 늘 이 순서로 합니다. 1) find_free_rooms 로 조건에 맞는 빈 방을 찾는다. "
                       "2) 사용자가 방을 정했으면 그 방이 목록에 있는지 보고, 정하지 않았으면 목록의 첫 방을 고른다. "
                       "3) book_room 으로 예약한다. 시각은 24시간 꼴(오후 2시 = 14:00)로 적는다. "
                       "도구가 오류를 돌려주면 오류 문구대로 고쳐 다시 시도한다.")
row2 = evaluate_agent(SYSTEM_2, helpful=True, label="2. 순서와 친절한 오류")
출력
  O 내일 오후 2시부터 3시까지 6명이 화상회의를 할  | [('find_free_rooms', ''), ('book_room', '숲'), ('find_free_rooms', ''), ('book_room', '별빛')] | 새 예약: ['별빛']
  X 10월 29일 오전 10시부터 11시까지 4명 회의 | [('find_free_rooms', '')] | 새 예약: []
  O 들판이 10월 28일 오후 4시부터 5시까지 비어  | [('find_free_rooms', '')] | 새 예약: []
  O 10월 29일 오후 2시부터 3시까지 20명이 들어 | [('find_free_rooms', '')] | 새 예약: []
  O 회의실 하나 잡아 줘. | [('find_free_rooms', '')] | 새 예약: []
  X 10월 30일 오전 9시부터 10시까지 화이트보드가 | [('find_free_rooms', '')] | 새 예약: []
  X 10월 28일 오후 1시부터 2시까지 10명 회의실 | [] | 새 예약: []
  X 10월 29일 오전 9시부터 오후 6시까지 들판을  | [('find_free_rooms', ''), ('book_room', '하늘')] | 새 예약: ['하늘']
  O 내일 오전 11시부터 12시까지 8명이 TV 있는  | [('find_free_rooms', ''), ('book_room', '숲')] | 새 예약: ['숲']
  X 10월 30일 오후 3시부터 4시까지 숲을 예약해  | [] | 새 예약: []
{'판': '2. 순서와 친절한 오류', '성공(/10)': 5, '호출 수': 13, '초': 37}

4단계. 조건만 뽑는 워크플로(판 3)

Python
EXTRACT = ("오늘은 2026-10-27(화)입니다. 사용자의 말에서 회의실 예약 조건을 뽑아 JSON 하나만 쓰세요. 키: "
           "\"의도\"(예약/조회/모름), \"날짜\"(YYYY-MM-DD), \"시작\"(HH:MM, 24시간), \"끝\"(HH:MM), \"인원\"(숫자), "
           "\"장비\"(화상회의/TV/화이트보드/빈 글자), \"방\"(사용자가 정한 방 이름 또는 빈 글자), \"제목\". 말에 없는 값은 null 로 둡니다.")

def workflow(request, db):
    """LLM 은 조건 뽑기 한 번만. 검증, 빈 방 찾기, 예약은 코드가 한다"""
    s = json.loads(chat(messages=[{"role": "system", "content": EXTRACT}, {"role": "user", "content": request}],
                        response_format={"type": "json_object"}).content)
    # 코드로 검증: 말에 없는 방과 장비는 지우고, 말에 나온 방 이름은 규칙으로도 찾는다
    named = [r for r in rooms["방"] if r in request]
    s["방"] = named[0] if named else ""
    if s.get("장비") not in ("화상회의", "TV", "화이트보드") or s.get("장비") not in request:
        s["장비"] = ""
    try:
        s["인원"] = int(s.get("인원") or 1)
    except (TypeError, ValueError):
        s["인원"] = 1
    if s.get("의도") != "예약":
        return s, f"예약하지 않음(의도: {s.get('의도')})"
    if not re.search(r"\d", request) or not all(s.get(k) for k in ["날짜", "시작", "끝"]):
        return s, "되묻기: 날짜와 시각을 알려 주세요"              # 말에 숫자가 없으면 시각을 지어낸 것이다
    free = db.find_free_rooms(s["날짜"], s["시작"], s["끝"], s["인원"], s["장비"])["빈 방"]
    if s["방"]:
        free = [r for r in free if r == s["방"]]
    if not free:
        return s, "빈 방 없음"
    return s, db.book_room(free[0], s["날짜"], s["시작"], s["끝"], s.get("제목") or "회의")

start, ok = time.time(), 0
for t in tasks:
    db = RoomDB()
    slots, result = workflow(t["요청"], db)
    good = grade(t, db)
    ok += good
    print(" ", "O" if good else "X", t["요청"][:28], "|", {k: slots.get(k) for k in ["의도", "날짜", "시작", "인원", "방"]}, "|", str(result)[:40])
row3 = {"판": "3. 워크플로", "성공(/10)": ok, "호출 수": len(tasks), "초": round(time.time() - start)}
print(row3)
출력
  O 내일 오후 2시부터 3시까지 6명이 화상회의를 할  | {'의도': '예약', '날짜': '2026-10-28', '시작': '14:00', '인원': 6, '방': ''} | {'예약됨': {'날짜': '2026-10-28', '방': '별빛',
  O 10월 29일 오전 10시부터 11시까지 4명 회의 | {'의도': '예약', '날짜': '2026-10-29', '시작': '10:00', '인원': 4, '방': ''} | {'예약됨': {'날짜': '2026-10-29', '방': '하늘',
  O 들판이 10월 28일 오후 4시부터 5시까지 비어  | {'의도': '조회', '날짜': '2026-10-28', '시작': '16:00', '인원': 1, '방': '들판'} | 예약하지 않음(의도: 조회)
  O 10월 29일 오후 2시부터 3시까지 20명이 들어 | {'의도': '예약', '날짜': '2026-10-29', '시작': '14:00', '인원': 20, '방': ''} | 빈 방 없음
  O 회의실 하나 잡아 줘. | {'의도': '예약', '날짜': '2026-10-27', '시작': '10:00', '인원': 10, '방': ''} | 되묻기: 날짜와 시각을 알려 주세요
  O 10월 30일 오전 9시부터 10시까지 화이트보드가 | {'의도': '예약', '날짜': '2026-10-30', '시작': '09:00', '인원': 3, '방': ''} | {'예약됨': {'날짜': '2026-10-30', '방': '들판',
  O 10월 28일 오후 1시부터 2시까지 10명 회의실 | {'의도': '예약', '날짜': '2026-10-28', '시작': '13:00', '인원': 10, '방': ''} | {'예약됨': {'날짜': '2026-10-28', '방': '별빛',
  O 10월 29일 오전 9시부터 오후 6시까지 들판을  | {'의도': '예약', '날짜': '2026-10-29', '시작': '09:00', '인원': 1, '방': '들판'} | 빈 방 없음
  O 내일 오전 11시부터 12시까지 8명이 TV 있는  | {'의도': '예약', '날짜': '2026-10-28', '시작': '11:00', '인원': 8, '방': ''} | {'예약됨': {'날짜': '2026-10-28', '방': '숲', '
  O 10월 30일 오후 3시부터 4시까지 숲을 예약해  | {'의도': '예약', '날짜': '2026-10-30', '시작': '15:00', '인원': 1, '방': '숲'} | {'예약됨': {'날짜': '2026-10-30', '방': '숲', '
{'판': '3. 워크플로', '성공(/10)': 10, '호출 수': 10, '초': 22}

5단계. 세 판 견주기

Python
print(pd.DataFrame([row1, row2, row3]).to_string(index=False))
출력
            판  성공(/10)  호출 수  초
    1. 짧은 지시문        6    11 38
2. 순서와 친절한 오류        5    13 37
      3. 워크플로       10    10 22

예약 도구는 겹치는 숲 예약을 막고 별빛 예약은 받아들였습니다. 짧은 지시문의 에이전트(판 1)는 10개 가운데 6개를 처리했습니다. 실패한 요청을 보면 빈 방을 찾아 놓고도 book_room 에 "화상회의실", "화이트보드 있는 방", "10명 회의실"처럼 있지도 않은 방 이름을 넣었고, 오류를 받은 뒤 다시 시도하지 않았습니다. 순서를 적고 오류 문구에 고치는 법을 넣은 판 2는 5개로 오히려 하나 줄었습니다. 첫 요청에서는 오류를 보고 별빛으로 고쳐 예약하는 데 성공했지만, "10월 29일 4명"처럼 빈 방을 찾고 예약하지 않고 끝내거나, 들판을 원한 요청에 하늘을 예약해 버린 요청이 생겼습니다. 교안을 만들며 먼저 돌렸을 때는 판 1이 3~4개, 판 2가 8개였으니, 작은 모델의 에이전트는 지시문을 고쳐도 실행마다 결과가 크게 흔들립니다. LLM 은 조건만 뽑고 검증, 빈 방 찾기, 예약을 코드가 한 워크플로(판 3)는 10개를 모두 처리했고, LLM 호출도 요청마다 한 번이라 22초로 가장 빨랐습니다. 다만 판 3도 코드의 검증 덕분입니다. LLM 은 시각이 없는 "회의실 하나 잡아 줘"에 10시를 지어 넣었지만, "말에 숫자가 없으면 되묻는다"는 규칙이 막았습니다. 순서가 정해진 예약 같은 일에는 워크플로를 쓰고, 에이전트는 무엇을 할지 미리 알 수 없는 열린 요청에 아껴 씁니다.

이 프로젝트가 뜻하는 것

이 프로젝트는 회의실 예약 창구에 일하는 방식이 다른 세 자리를 차례로 앉혀 보는 일과 같습니다. 먼저 예약 장부와 "빈 방 찾기", "예약하기" 두 업무를 만들고, 장부가 겹치는 예약을 막는지 봅니다(1단계). 첫째 직원에게는 짧은 업무 지침만 주고 요청 10개를 맡긴 뒤, 직원의 말이 아니라 장부에 남은 새 예약으로 채점합니다(2단계). 둘째 직원에게는 업무 순서를 적은 지침과, 실수하면 고치는 법을 알려 주는 오류 안내를 줍니다(3단계). 셋째 자리에서는 직원이 요청에서 날짜, 시각, 인원만 받아 적고, 확인과 예약은 정해진 절차가 합니다(4단계). 끝으로 세 자리의 성적을 한 표로 견줍니다(5단계). 여기서 장부가 RoomDB, 두 업무가 find_free_rooms·book_room, 첫째와 둘째 직원이 에이전트 판 1·판 2, 셋째 자리가 워크플로 판 3, 직원이 LLM(Large Language Model, 대규모 언어 모델)인 qwen2.5:3b, 정해진 절차가 코드입니다. 비유와 다른 점은, 사람 직원은 같은 지침이면 대체로 같게 일하지만 작은 모델은 같은 설정에서도 실행마다 결과가 하나둘 달라진다는 것입니다.

단계출력뜻
1. 빈 방 찾기{'빈 방': ['별빛']}10월 28일 14시~15시에 6명, 화상회의 조건에 맞는 빈 방은 별빛 하나입니다
1. 숲 예약"숲 은 그 시간에 이미 예약이 있습니다."겹치는 예약을 장부가 막았습니다
1. 별빛 예약{'예약됨': …}비어 있는 방은 예약이 들어갑니다
2. 판 1성공 6/10, 호출 11, 38초"화상회의실", "10명 회의실"처럼 없는 방 이름을 넣고, 오류를 받고도 다시 시도하지 않은 요청이 실패했습니다
3. 판 2성공 5/10, 호출 13, 37초첫 요청은 오류를 보고 별빛으로 고쳐 성공했지만, 빈 방을 찾고도 예약하지 않거나 들판 대신 하늘을 예약한 요청이 생겨 하나 줄었습니다
4. "회의실 하나 잡아 줘."시작 10:00 을 지어냄 → 되묻기LLM 이 말에 없는 시각을 지어 넣었지만, "말에 숫자가 없으면 되묻는다"는 코드 규칙이 막았습니다
4. 판 3성공 10/10, 호출 10, 22초요청마다 LLM 을 한 번만 불러 가장 빠르고, 10개를 모두 처리했습니다
5. 세 판 표성공 6, 5, 10지침을 늘린 판 2가 판 1보다 낫지 않았고, 일의 순서를 코드가 쥔 판 3이 가장 나았습니다

한 줄로 줄이면, 순서가 정해진 예약 같은 일은 LLM 에게 조건 받아 적기만 맡기고 나머지를 코드가 하는 워크플로가 더 정확하고 빠르다는 것을 보인 프로젝트입니다. 요청 10개로 한 번 돌린 결과이고, 교안을 만들며 먼저 돌렸을 때는 판 1이 3~4개, 판 2가 8개였으므로 판 1과 판 2의 순서는 실행마다 뒤집힐 수 있습니다. 판 3의 10개도 코드의 검증 규칙 덕분이라, 새로운 말투의 요청에는 규칙을 더 다듬어야 할 수 있습니다. 에이전트는 무엇을 할지 미리 알 수 없는 열린 요청에 아껴 씁니다.

← 이전 절9장 · 실습 6 — 안전장치 붙이기다음 절 →9장 · 정리