LLM 엔지니어링18장 · Project 03. 내 서버에서 일하는 AI Agent6 / 13← 이전목차다음 →MetaContents

18장. Project 03. 내 서버에서 일하는 AI Agent

쓰기 작업은 확인을 거친다

비서가 휴가 신청서를 써도 도장은 본인이 찍습니다. 큰 금액의 이체는 은행 앱이 "정말 보내시겠습니까?"라고 다시 묻습니다. 에이전트의 쓰기 도구도 바로 실행하지 않고 작업을 "확인 대기"로 만들어 두고, 사람이 확인하면 그때 실행합니다.

제안, 확인, 실행의 세 걸음으로 쓰기 작업을 처리하는 흐름도

1번에서 에이전트의 쓰기 도구는 조건을 검사한 뒤 작업 번호를 붙인 제안만 만듭니다. 2번에서 사용자(또는 결재자)가 그 작업을 확인합니다. 3번에서 서버가 작업을 실행하고 상태를 바꿉니다. 확인하는 사람은 그 작업의 주인이어야 합니다. 여기서 휴가 신청서가 제안이고, 도장이 확인입니다.

쓰기 작업의 상태

상태뜻다음
확인 대기에이전트가 만든 제안주인이 확인하면 실행, 아니면 그대로
접수됨확인을 받아 실행했다바꿀 수 없다(취소는 따로)
결재 대기휴가처럼 다른 사람(팀장)의 결재가 필요하다결재 시스템으로 넘어간다

확인 대기 흉내 내기

Python
import itertools

ids = itertools.count(1)
actions = {}


def propose(owner, kind, detail):
    """쓰기 도구는 실행하지 않고 제안만 만든다"""
    aid = f"A-{next(ids):03d}"
    actions[aid] = {"주인": owner, "종류": kind, "내용": detail, "상태": "확인 대기"}
    return aid


def confirm(aid, who):
    """주인만 확인할 수 있고, 한 번 실행한 작업은 다시 실행하지 않는다"""
    act = actions.get(aid)
    if act is None or act["주인"] != who:
        return 403, "확인할 수 없는 작업입니다"
    if act["상태"] != "확인 대기":
        return 409, f"이미 {act['상태']}"
    act["상태"] = "접수됨"
    return 200, act


aid = propose("C01", "반품 접수", {"주문번호": "O-1001"})
print(confirm(aid, "C02"))     # 남이 확인하려 하면
print(confirm(aid, "C01"))     # 주인이 확인하면
print(confirm(aid, "C01"))     # 같은 확인을 두 번 보내면

차례로 (403, …), (200, {… '상태': '접수됨'}), (409, '이미 접수됨') 이 나옵니다. 409 Conflict 는 "지금 상태와 맞지 않는 요청"이라는 뜻입니다. 화면에서 확인 단추를 두 번 눌러도 반품이 두 번 접수되지 않습니다.

코드에 나오는 함수

코드쉬운 뜻
itertools.count(1)1 부터 하나씩 커지는 번호를 끝없이 내주는 번호표 기계를 만든다
next(ids)번호표 기계에서 다음 번호 하나를 뽑는다
f"A-{…:03d}"번호를 세 자리 정수(d = decimal, 10진수)로 맞추고 빈 앞자리를 0 으로 채운다. 1 은 A-001 이 된다
actions[aid] = {…, "상태": "확인 대기"}작업을 실행하지 않고 장부에 "확인 대기"로만 적는다
actions.get(aid)작업 번호로 작업을 찾는다. 없으면 None 이다
return 403, …403 Forbidden(금지됨)은 "권한이 없는 요청"을 뜻하는 HTTP 상태 코드다
return 200, act200 OK 는 "요청을 잘 처리했다"는 HTTP 상태 코드다

이 코드가 뜻하는 것

미용실 예약을 떠올려 봅니다. 직원이 장부에 예약을 연필로 적고 예약 번호를 알려 줍니다. 손님이 확인 문자에 답하면 그때 펜으로 확정합니다. 다른 사람이 대신 답할 수 없고, 같은 답을 두 번 보내도 예약은 하나입니다. 여기서 연필 예약이 propose, 손님의 확인 답장이 confirm, 예약 번호가 aid 입니다.

줄하는 일쉽게 말하면
aid = propose("C01", "반품 접수", …)C01 의 반품 작업을 확인 대기로 만들고 첫 번호 A-001 을 받는다연필로 예약을 적고 예약 번호를 준다
confirm(aid, "C02")주인이 아니라서 403 을 돌려준다다른 사람의 답장은 받아 주지 않는다
confirm(aid, "C01")상태를 "접수됨"으로 바꾸고 200 을 돌려준다손님의 답장을 받고 펜으로 확정한다
confirm(aid, "C01") 한 번 더이미 접수됨이라 409 를 돌려준다같은 답장이 또 와도 예약은 하나다

결과에서 상태가 바뀐 것은 둘째 줄뿐입니다. 남의 확인과 겹친 확인은 장부를 건드리지 못합니다. 비유와 다른 점은, 미용실은 답이 없으면 예약을 저절로 취소하기도 하지만 이 코드의 작업은 확인할 때까지 "확인 대기"로 남는다는 것입니다.

작게 줄여서 눈으로 보기

작업 번호를 만드는 두 부품만 떼어 봅니다.

Python
import itertools

ids = itertools.count(1)
print(next(ids), next(ids), next(ids))       # 부를 때마다 1씩 커진다
print(f"A-{7:03d}", f"A-{42:03d}")           # 세 자리로 맞추고 빈 앞자리는 0
출력
1 2 3
A-007 A-042
결과읽는 법
1 2 3같은 번호표 기계에서 뽑을 때마다 다음 번호가 나온다. 번호가 겹치지 않는다
A-007 A-042자릿수가 달라도 세 자리로 맞춰져, 글자 차례로 줄 세워도 번호 차례와 같다

주의할 점

  • 조건 검사(기한, 잔여, 권한)는 제안을 만들 때 한 번, 실행할 때 한 번 더 합니다. 제안한 뒤 확인하기까지 그사이에 상태가 바뀔 수 있습니다.
  • 확인 단계 없이 바로 실행해도 되는 쓰기 도구는 되돌리기 쉬운 것(장바구니 담기 정도)만으로 좁힙니다.
← 이전 절18장 · 권한은 코드가 지킨다다음 절 →18장 · 실행 기록 남기기