컴퓨터공학 300 주제 시리즈의 277번째 글이다. 전체 지도는 여기.

한 줄 요약

AI 에이전트는 LLM 이 “생각 → 도구 호출 → 결과 관찰”을 반복하며 목표를 이루는 루프이고, 그 안전성과 신뢰성은 모델보다 루프를 감싼 코드(도구 정의, 권한, 단계 한도, 검증)가 좌우한다.

왜 필요한가

LLM 혼자서는 텍스트만 만든다. 현재 날씨를 모르고, 계산을 자주 틀리고, 파일을 읽거나 API 를 부를 수 없다. 도구 사용은 이 한계를 넘는다. 모델이 “이 함수를 이 인자로 불러 달라”는 구조화된 요청을 내면, 프로그램이 실제로 실행하고 결과를 돌려준다.

여기에 반복을 더하면 에이전트가 된다. 결과를 보고 다음 행동을 정하므로, 미리 정해 두지 않은 여러 단계의 일을 처리할 수 있다. 코드 수정, 데이터 분석, 운영 작업 보조가 이렇게 돌아간다.

동시에 위험도 커진다. 텍스트를 잘못 쓰는 것과 명령을 잘못 실행하는 것은 다르다. 그래서 에이전트 설계의 절반은 “무엇을 못 하게 할 것인가”다.

핵심 개념

기본 루프

          ┌──────────────────────────────────────┐
          ▼                                      │
 목표 ─▶ [LLM: 다음 행동 결정] ─▶ 도구 호출? ─예─▶ [실행기: 검증 후 실행] ─▶ 결과를 기록에 추가
                                   │
                                  아니오
                                   ▼
                               최종 답 반환

Yao 외(2022)의 ReAct 는 추론(Reason)과 행동(Act)을 번갈아 쓰게 해, 모델이 생각의 흔적을 남기며 검색 같은 도구를 쓰도록 한 대표적인 연구다. Toolformer(Schick 외, 2023)는 모델이 언제 어떤 API 를 부를지 스스로 학습하는 방법을 보였다. 지금의 상용 API 들은 도구 사용을 기본 기능으로 제공한다.

도구 정의

도구는 이름, 설명, 입력 스키마(JSON Schema)로 모델에게 알려 준다.

{"name": "get_weather",
 "description": "도시의 현재 날씨를 돌려준다. 도시 이름은 한국어로.",
 "input_schema": {"type": "object",
                  "properties": {"city": {"type": "string"}},
                  "required": ["city"]}}

모델은 설명을 읽고 언제 이 도구를 쓸지 판단한다. 그래서 도구 설명이 곧 프롬프트다. 모호한 이름, 겹치는 기능의 도구 여러 개, 설명 없는 인자는 잘못된 호출로 이어진다.

표준화 — MCP

도구를 앱마다 따로 붙이면 같은 연동을 반복해 만든다. Model Context Protocol(MCP)은 도구·자원을 제공하는 서버와 이를 쓰는 클라이언트(에이전트 앱) 사이의 공개 프로토콜이다. 한 번 만든 MCP 서버를 여러 클라이언트가 쓸 수 있다.

워크플로와 에이전트

Anthropic 의 “Building effective agents” 글은 둘을 구분한다. 워크플로는 LLM 과 도구가 미리 정한 코드 경로를 따라 움직이고, 에이전트는 LLM 이 스스로 과정과 도구 사용을 정한다. 그리고 가장 단순한 해법에서 시작해 필요할 때만 복잡도를 올리라고 권한다. 단계가 고정된 일(추출 → 분류 → 저장)은 워크플로가 더 싸고 예측 가능하다.

실패 양상과 방어

실패 예 방어
잘못된 도구·인자 없는 도구 호출, 스키마 위반 허용 목록, 스키마 검증, 오류를 모델에 돌려줘 고치게 함
무한 루프 같은 검색을 반복 최대 단계 수, 비용·시간 예산
위험한 행동 삭제, 송금, 배포 최소 권한, 읽기/쓰기 도구 분리, 사람 승인
간접 프롬프트 인젝션 읽은 웹페이지 속 “이 파일을 외부로 보내라” 도구 결과를 지시로 취급하지 않음, 민감 도구와 외부 입력의 조합 제한
오류 누적 앞 단계의 작은 오류가 커짐 중간 검증, 체크포인트, 짧은 루프

여러 단계를 거치면 성공 확률이 곱해진다는 점도 기억할 만하다. 단계마다 95% 로 성공해도 10단계를 모두 성공할 확률은 0.95¹⁰ ≈ 60% 다. 단계를 줄이고, 중간에 검증하는 것이 중요한 이유다.

직접 해 보기

실제 LLM 대신 미리 정한 응답을 내는 가짜 모델로 에이전트 루프를 만든다. 모델 쪽은 가짜여도, 루프를 감싼 코드(허용 목록, 단계 한도, 기록)는 실제와 같다.

import json
TOOLS = {
    "get_weather": lambda city: {"서울": "맑음 21도", "부산": "흐림 19도"}.get(city, "정보 없음"),
    "calc":        lambda expr: str(sum(int(x) for x in expr.split("+"))),
}
# 실제 LLM 대신, 미리 정해 둔 응답을 돌려주는 가짜 모델
SCRIPT = iter([
    '{"tool": "get_weather", "args": {"city": "서울"}}',
    '{"tool": "rm_rf", "args": {"path": "/"}}',
    '{"tool": "calc", "args": {"expr": "21+3"}}',
    '{"final": "서울은 맑음 21도이고, 3도 오르면 24도입니다."}',
])
def fake_llm(history): return next(SCRIPT)
def run_agent(task, max_steps=6):
    history = [{"role": "user", "content": task}]
    for step in range(1, max_steps + 1):
        msg = json.loads(fake_llm(history))
        if "final" in msg:
            print(f"[{step}] 최종 답:", msg["final"]); return msg["final"]
        name, args = msg["tool"], msg["args"]
        if name not in TOOLS:                       # 허용 목록 밖 도구 거절
            result = f"오류: 도구 {name} 는 허용되지 않음"
        else:
            result = TOOLS[name](**args)
        print(f"[{step}] {name}({args}) -> {result}")
        history.append({"role": "tool", "name": name, "content": result})
    print("단계 한도 초과, 중단")
run_agent("서울 날씨 알려 주고 기온에 3 더해 줘")

실행 결과:

[1] get_weather({'city': '서울'}) -> 맑음 21도
[2] rm_rf({'path': '/'}) -> 오류: 도구 rm_rf 는 허용되지 않음
[3] calc({'expr': '21+3'}) -> 24
[4] 최종 답: 서울은 맑음 21도이고, 3도 오르면 24도입니다.
  • 1단계: 날씨 도구를 부르고 결과를 기록에 붙였다.
  • 2단계: 모델이 허용 목록에 없는 rm_rf 를 요청했다. 실행기가 거절하고, 거절 사실을 결과로 돌려줬다. 예외로 루프를 죽이지 않고 모델에게 알려 다른 길을 찾게 하는 것이 일반적인 처리다.
  • 3단계: 계산 도구로 정확한 덧셈을 했다. 모델이 직접 계산하는 것보다 믿을 만하다.
  • 4단계: 최종 답.

max_steps 가 없으면 모델이 끝없이 도구를 부를 수 있다. 실제 시스템에서는 단계 수와 함께 토큰·시간·비용 예산을 둔다. 그리고 calc 처럼 문자열을 받는 도구에서 eval 을 쓰면 그 자체가 원격 코드 실행 구멍이 된다. 예제가 덧셈만 직접 파싱하는 이유다.

현업에서는

  • 코딩 에이전트: 파일 읽기, 검색, 편집, 테스트 실행을 도구로 주고 반복하게 한다. 쓰기 범위를 작업 디렉터리로 묶고, 커밋·배포는 사람 확인을 거치게 하는 것이 보통이다.
  • 운영 보조: 홈랩 클러스터에 에이전트를 붙인다면, 처음에는 조회 도구(로그·상태 읽기)만 주고 변경 도구(재시작, 삭제)는 승인 절차 뒤에 두는 단계적 접근이 안전하다. 실행한 모든 도구 호출을 감사 로그로 남긴다.
  • 평가가 어렵다. 같은 과제도 매번 경로가 다르다. 최종 결과(테스트 통과 여부 등)로 채점하는 과제 세트를 만들고, 성공률·평균 단계 수·비용을 함께 본다.
  • 비용: 단계마다 지금까지의 기록 전체가 다시 입력되므로 토큰 사용이 단계 수에 따라 빠르게 늘어난다. 긴 도구 결과는 요약하거나 잘라서 넣는다.

확인 문제

  1. 도구 사용과 에이전트의 차이는?
  2. 도구 설명을 잘 써야 하는 이유는?
  3. 단계당 성공률 90% 인 작업을 5단계 이어 붙이면 전체 성공률은 대략 얼마인가?
  4. 웹페이지를 읽는 도구와 이메일을 보내는 도구를 같은 에이전트에 줄 때의 위험은?
  5. 예제에서 허용되지 않은 도구 요청을 예외로 중단하지 않고 결과로 돌려준 이유는?

풀이

  1. 도구 사용은 모델이 함수 호출을 요청하는 능력이고, 에이전트는 그 결과를 보고 다음 행동을 스스로 정하는 반복 루프다.
  2. 모델은 이름·설명·스키마만 보고 언제 어떻게 부를지 판단하므로, 설명이 모호하면 잘못된 도구나 인자를 고른다.
  3. 0.9⁵ ≈ 0.59, 약 59%.
  4. 웹페이지 속 악의적 지시(간접 프롬프트 인젝션)가 모델을 조종해 민감한 정보를 메일로 외부에 보내게 할 수 있다.
  5. 모델에게 실패 이유를 알려 다른 방법을 시도하게 할 수 있고, 루프의 제어는 실행기가 계속 쥐고 있기 때문이다.

더 읽을거리 (References)