CodeGym /행동 /ChatGPT Apps /에이전트 소개: 역할, run 사이클, 결정성, 멱등성

에이전트 소개: 역할, run 사이클, 결정성, 멱등성

ChatGPT Apps
레벨 12 , 레슨 0
사용 가능

1. 에이전트란 무엇이며 왜 필요한가

에이전트는 ChatGPT App의 필수 구성요소가 아닙니다. LLM 에이전트를 사용하지 않고도 멋진 앱을 원하는 만큼 만들 수 있습니다. 그럼에도 불구하고, 제가 에이전트를 소개하려는 데에는 세 가지 충분한 이유가 있습니다.

에이전트는 앱의 백엔드에 지능을 더하는 훌륭한 방법입니다. 예를 들면, 똑똑한 선물 추천이나 사용자의 텍스트 요구사항 분석 등입니다. 검색·분석·처리·요약 같은 복잡한 시나리오는 LLM 에이전트를 통해 매우 쉽게 구현할 수 있습니다.

ChatGPT는 TS와 Python용 AgentsSDK를 공개했습니다. 매우 잘 만들어졌습니다. 에이전트 오케스트레이션이 기본 제공됩니다. 하나의 복잡한 문제를 한 명의 에이전트가 아니라 팀 단위로 처리하게 할 수 있습니다. 매우 유망한 방향입니다.

그리고 학습적인 목적도 있습니다. ChatGPT는 mcp-tools를 호출하는 방식이 LLM 에이전트가 자신의 tools를 호출하는 것과 동일합니다. LLM 에이전트가 어떻게 동작하는지 이해하면, 예를 들어 앱에서 모델 측 상태 머신을 어떻게 만들 수 있는지도 자연스럽게 이해할 수 있습니다. 또한 AgentsSDK를 공부하면 향후 ChatGPT SDK가 어떻게 동작할지에 대한 감도 얻게 됩니다.

그럼 시작해 봅시다.

LLM 에이전트란?

ChatGPT App이 ChatGPT 내부에서 여러분의 서비스를 위한 멋지고 편리한 “프론트”이고, MCP 서버가 도구와 비즈니스 로직을 담은 “엔진”이라면, 에이전트는 다음을 할 수 있는 똑똑한 디스패처와 같습니다.

  • 목표를 읽고 이해한다.
  • 어떤 도구를 어떤 순서로 호출할지 스스로 결정한다.
  • 필요하다면 추가 데이터를 요청한다.
  • 오류가 있으면 단계를 반복한다.
  • 명확한 최종 결과에 도달한다.

Agents SDK의 공식 문서에 가까운 표현으로, 에이전트는 하나의 프로그램입니다. LLM과 도구 집합에 접근권을 가지면, 목표 달성을 위한 단계를 스스로 계획하고 tool‑calls를 통해 이를 실행할 수 있습니다.

이미 가지고 있는 것과 비교해 보겠습니다.

  • 일반적인 ChatGPT App에서는 ChatGPT 모델이 여러분의 MCP 도구 호출을 직접 오케스트레이션합니다.
  • 백엔드의 LLM 에이전트도 마찬가지로 작업 설명과 도구 집합을 가지고, 어떤 tools를 호출할지, 몇 단계로 진행할지, 언제 멈출지, 어떤 결과를 반환할지 스스로 결정합니다.

우리의 GiftGenius 맥락에서 보면 다음처럼 보일 수 있습니다.

  • 에이전트 없는 앱: 모델이 searchGifts를 직접 호출하고, 이어서 filterByBudget, 그 다음 getDetails를 호출합니다 — 매번 처음부터 다시 생각합니다.
  • 에이전트가 있는 앱: ChatGPT가 mcp‑tool을 호출하고, 백엔드는 에이전트에게 “이런 프로필에 맞는 상위 5개의 선물을 찾아라.”라는 과제를 부여합니다. 에이전트는 여러 단계를 수행합니다: 추가 정보를 수집하고, 다양한 검색 도구를 호출하고, 필터링·정렬하고, 최종 카드들을 구성하여 이미 구조화된 완성 답변을 반환합니다.

ChatGPT와 백엔드의 LLM 에이전트는 회사의 이사와 직원 같습니다. ChatGPT는 훨씬 더 큰 자유를 갖고 사용자와 대화하며 어떤 전략적 작업을 시작할지(mcp tools 호출) 결정합니다. 반면 LLM 에이전트는 백엔드에서만 동작하고 사용자와 직접 상호작용하지 않지만, 역시 “생각”하며 자신의 tool을 호출할 수 있습니다. 일종의 “미니 ChatGPT”라고 볼 수 있죠.

2. 에이전트 구성 요소: LLM, 지시문, 도구, 상태

에이전트를 여러 층으로 생각하면 편합니다.

첫째, 내부에는 여전히 같은 LLM이 있습니다. Agents SDK가 사용하는 GPT‑5.1이나 다른 모델일 수 있습니다. 텍스트를 생성하고, 단계를 계획하고, 도구를 선택합니다 — 즉, 오케스트레이션 맥락에서 “생각”을 담당합니다.

둘째, 모델 위에는 지시문이 있습니다. 이는 에이전트의 시스템 프롬프트로, 역할, 경계, 말투, 도구 사용 방법 등을 규정합니다. 이미 ChatGPT App에서 비슷한 것을 해 보셨겠지만, 이제는 개별 에이전트에 적용됩니다.

셋째, 에이전트의 도구 집합이 있습니다. 예를 들면:

  • TypeScript 함수(고전적 function calling);
  • HTTP/REST 도구;
  • 에이전트가 ChatGPT App과 동일한 백엔드를 사용할 수 있도록 MCP‑tools에 대한 래퍼;
  • OpenAI가 제공하는 내장 “hosted” 도구(예: 웹 검색을 연결한 경우).

마지막으로, 상태와 단계 처리 규칙이 있습니다. 세션 상태(session state)를 어떻게 저장할지, 중간 결과를 어떻게 보관할지, 루프를 어떻게 제한할지 등입니다. 이에 대해서는 다음 강의(메모리와 상태)에서 더 깊이 다루겠지만, 에이전트는 “한 번짜리 요청”이 아니라 진행 상황을 저장하는 잠재적으로 긴 프로세스라는 점을 지금부터 염두에 두면 좋습니다.

TypeScript 개발자의 눈으로 보면, 마음속에서는 대략 다음과 같은 객체가 떠오를 것입니다(Agents SDK TS에 가까운 의사코드):

const giftAgent = new Agent({
  model: "gpt-5.1",
  systemPrompt: giftAgentPrompt,
  tools: { searchGifts, filterGifts, checkoutDraft },
  // 여기에서 메모리, 스텝 제한 등의 설정도 함께 지정
});

지금은 정확한 API에 파고들기보다, 하나의 장소에 모델, 지시문, 도구, 동작 설정이 함께 모여 있다는 이미지를 잡는 것이 더 중요합니다.

3. 메시지 역할: system / user / assistant / tool의 세계

여러분은 이미 Chat Completions의 고전적 역할인 system, user, assistant, tool에 익숙하실 겁니다. Agents SDK에서도 이 역할들이 유지되며, 다만 좀 더 실무적인 의미를 갖게 됩니다.

system 역할은 에이전트의 인격과 미션 자체를 규정합니다. 예를 들어 GiftGenius 에이전트라면 이렇게 설정할 수 있습니다: “너는 선물 추천 에이전트다. 최소 단계로 수신자 프로필과 예산을 바탕으로 3–7개의 관련 선물 옵션을 고르고, 위젯을 위한 구조화된 JSON을 준비하라.” 이곳에는 그가 해서는 안 되는 일(예: 별도 단계 없이 실제 구매 수행 금지)과 도구 사용 방식에 대한 제한도 함께 명시합니다.

에이전트 맥락의 user 역할은 꼭 “실제 사람”을 의미하지 않습니다. 대부분 에이전트에게 주어지는 “과제”, 즉 여러분의 App·서비스·다른 에이전트가 구성한 목표입니다. 예를 들어 ChatGPT App이 다음과 같은 user 메시지로 에이전트를 호출할 수 있습니다: “동료 개발자를 위한 선물 5가지를 골라줘. 예산은 50달러, 사유는 생일.”

assistant 역할은 에이전트 내부의 모델이 “말하는” 내용입니다. 중간 추론과 계획, 또는 최종 답변이 포함될 수 있습니다. 여러분의 과제는 시스템 프롬프트를 조정해 이러한 메시지가 유용하고 필요한 경우 로깅되도록 만드는 것입니다.

tool 역할(또는 사용 중인 SDK의 유사 역할)은 도구 호출 결과를 설명합니다: “MCP를 통해 50개의 상품을 찾음”, “API가 타임아웃 오류를 반환함”, “DB가 사용자 프로필을 반환함”. 이러한 메시지는 assistant 메시지와 함께 에이전트의 run 사이클 기록을 구성합니다.

이를 작은 표로 정리하면 편리합니다.

역할 발화 주체 GiftGenius 맥락의 예
system
당신(에이전트 개발자) “너는 선물 추천 에이전트…”
user
외부 호출(App, 다른 에이전트) “50달러 이하 선물 5개를 골라줘…”
assistant
에이전트 내부의 모델 “계획: 1) 세부사항을 묻기…”
tool
호출된 도구의 결과 “searchGifts가 20개 후보를 반환…”

이 구조가 중요한 이유는 바로 이를 바탕으로 run 사이클이 구축되기 때문입니다 — 오늘 강의의 핵심 주제입니다.

4. LLM이 백엔드 함수를 호출하는 방법

문답 모드에 익숙해지면 LLM이 단순한 방식으로 동작하는 것처럼 느껴집니다: 텍스트가 들어옴 → 모델이 텍스트로 답함. 실제로는 내부에서 더 복잡하게 작동하며, 그래서 function calling이 가능한 것입니다.

모델은 하나의 질문만 받지 않습니다. 메시지 목록 — 대화 기록을 받습니다. 여기에는 이미 시스템 지침(“너는 누구이고 무엇을 할 수/없나”), 여러분의 메시지, 과거 모델의 답, 도구 결과가 모두 들어 있습니다. 매 단계에서 모델은 이 모든 타임라인, 즉 채팅 로그 전체를 보고 “다음으로 추가해야 할 메시지는 무엇인가?”를 결정합니다.

핵심은 이것입니다: LLM은 항상 한 번에 한 단계 — 대화 맨 끝에 다음 메시지를 추가합니다. “과거를 수정”하거나 이전 메시지를 편집하지 않고, 단지 목록을 이어 갑니다. 여러분이 질문을 쓰면 모델이 답합니다. 여러분이 두 번째 질문을 추가하면 모델은 다시 답하되 전체 대화 기록(모든 메시지)을 고려합니다.

Function calling도 같은 원리입니다. 모델은 직접 “함수를 실행”하는 대신 다음을 수행합니다.

  • 사용 가능한 도구/tools 목록과 그 설명, 그리고 대화 기록을 본다.
  • “지금은 단순히 텍스트로 답하기보다, 먼저 특정 도구를 호출하는 것이 합리적이다”라고 결정한다.
  • 그리고 다음 메시지로 일반 텍스트 응답이 아니라 “이런 tool을 이런 인자로 호출하고 싶다”는 특수 메시지를 기록의 끝에 추가한다.

그다음에는 모델이 아니라 여러분의 백엔드가 이 새 메시지를 읽고, 함수 호출 요청임을 파악하여 적절한 도구를 호출합니다. 그리고 또 다른 메시지 — 도구 결과를 기록에 추가하고, 다시 전체 메시지 목록을 모델에 전달합니다. 모델은 다시 전체 타임라인을 보고 다음 단계를 추가합니다: 또 다른 호출이거나, 최종 사용자 친화적 답변이거나.

정리하면:

  • 일반 Q&A에서는 “다음 메시지” = 텍스트 응답;
  • function calling에서는 “다음 메시지” = 함수 호출 지시 또는 함수 사용 후의 응답.

함수를 호출하는 어떤 마법 같은 “별도 명령”은 없습니다 — 모델이 체인의 끝에 추가하는 메시지의 특수한 타입일 뿐입니다.

모델은 여러분의 백엔드 함수를 퍼블릭 API로 직접 호출하지 않습니다. 단지 “채팅에 쓴다” — 어떤 파라미터로 어떤 함수를 호출하고 싶다고. 그리고 여러분의 백엔드가 로컬 함수를 호출해 그 응답을 채팅에 다시 추가합니다. 다시 반복됩니다.

5. 에이전트의 run 사이클: 한 걸음씩 “생각”하는 방법

사실 LLM 에이전트는 여러분의 서버에 있는 어떤 객체/알고리즘이며, 에이전트의 run 사이클을 수행합니다 — “질문 → 생각 → 필요하면 행동 → 다시 생각 → … → 최종 답변”으로 이어지는 확장 루프입니다. OpenAI 문서에서는 이를 agent loop 또는 ReAct 패턴(Reason + Act + Observe)이라고 부르기도 합니다.

개념적으로 하나의 에이전트 run은 다음과 같습니다.

  1. 에이전트가 입력을 받는다: 시스템 지침, 과제(user 메시지), 그리고 필요하면 현재 상태.
  2. 모델이 한 단계를 생성한다: 텍스트 답변이거나, 계획과 함께 하나 이상의 도구 호출 결정.
  3. 모델이 tool‑call을 선택했다면, 에이전트가 코드에서 해당 도구를 호출한다(로컬 함수, MCP‑tool, HTTP 요청, DB 접근 등).
  4. 도구 결과는 tool 메시지로 기록에 추가된다.
  5. 루프는 새로운 컨텍스트로 모델에게 돌아간다. 모델은 다음에 할 일을 결정한다: 계획을 계속할지, 다른 도구를 호출할지, 최종 답으로 종료할지.
  6. 모델이 명시적으로 또는 종료 조건에 따라 run을 끝내면, 에이전트는 최종 결과를 호출 측에 반환한다.

작은 다이어그램으로 표현하면 다음과 같습니다.

flowchart TD
    A[run 시작: 목표 + system] --> B[모델 호출]
    B --> C{모델이
텍스트로 답할지
tool을 호출할지?} C --> D["텍스트 응답
(assistant)"] D --> E{작업이 완료되었나?} E -->|예| F[최종 결과] E -->|아니오| B C --> G["Tool-call
(호출 설명)"] G --> H[함수 / MCP / HTTP 호출] H --> I["Tool 결과
(tool message)"] I --> B

이를 단순한 TypeScript 의사코드(실제 API와는 거리가 있지만, 논리적으로는 타당)로 옮기면 대략 다음과 같습니다.

async function runAgent(goal: string) {
  let context = buildInitialContext(goal);

  while (!isFinished(context)) {
    const decision = await callLLM(context); // 에이전트의 한 단계

    if (decision.type === "tool_call") {		// 함수를 호출할까?
      const toolResult = await callTool(decision.tool, decision.args);	// 로컬 함수를 호출
      context = appendToolResult(context, toolResult);		// 결과를 목록 끝에 추가
    } else {
      context = appendAssistantMessage(context, decision.message); 
    }

    enforceLimits(context); // 단계/시간/루프 제한
  }

  return extractFinalResult(context);
}

Agents SDK는 이 수고의 상당 부분(기록 저장, tool‑call 마샬링, 재시도 로직 등)을 대신 처리해 줍니다. 여러분은 설정을 구성하고 실제 도구를 구현하기만 하면 됩니다.

Run vs step

두 개념을 구분하는 것이 중요합니다.

  • run — 어떤 목표를 위해 에이전트를 한 번 실행하는 것: “이 경우에 맞는 선물을 골라라”.
  • step — run 사이클의 한 단계: 모델 호출 한 번으로, 텍스트 응답 또는 tool‑call로 이어질 수 있음.

모니터링에서는 하나의 run 안에 여러 단계를 보게 됩니다. 보안·비용 관련 한도는 종종 “run 단위” 또는 “단계 단위”로 설정됩니다.

이제 에이전트가 하나의 run에서 어떻게 살아 움직이며 run 사이클을 따라 걷는지 이해했으니, 어디에 에이전트를 쓰는 것이 유의미하고 어디에는 단순한 tools만으로 충분한지 살펴보겠습니다.

5. GiftGenius에서 어디에 에이전트가 필요하고 어디엔 불필요한가

모든 것에 에이전트를 쓰기 전에, 스스로에게 솔직하게 물어보는 것이 좋습니다: “정말 여기 에이전트가 필요한가?”.

에이전트에 적합한 시나리오는 가지치기·재시도·로직이 많은 다단계 과제이며, 그것을 프롬프트만으로 유지하기 불편한 경우입니다.

GiftGenius에서는 다음과 같은 “스마트 선물 추천 마법사”가 해당될 수 있습니다.

  • 중요한 세부사항(수신자 성별, 취미, 친밀도 등)을 되묻는다.
  • 여러 상품 소스에 접근한다(MCP‑tools를 통한 다양한 벤더).
  • 결과를 필터링하고 랭크한다.
  • 소스 오류 시 재시도하거나 우회 경로로 진행한다.
  • 단순 텍스트 목록이 아니라, 설명과 product feed의 SKU 링크가 포함된 구조화된 후보 목록을 반환한다.

여기서 에이전트는 “오케스트레이터”로서 진짜 빛을 발합니다. 나중에 음성/Realtime 시나리오나 복잡한 상거래(ACP)를 추가하고 싶을 때 특히 그렇습니다.

반면 getGiftDetails(giftId) 같은 단순 호출에는 에이전트가 필요 없습니다. ChatGPT에서 직접 호출되는 보통의 MCP‑tool이면 충분합니다. “상품 카드 텍스트로 이 선물의 설명을 말해줘” 같은 단일 단계 시나리오도 마찬가지입니다.

일반적인 상식은 이렇습니다: 시나리오를 “하나의 정상적 tool”로 설명할 수 있다면, 에이전트는 아마 필요 없을 겁니다. 반대로 여러 단계의 워크플로를 명시적으로 나열하기 시작하고 검증·재시도가 눈에 띄면, 에이전트가 큰 도움이 될 가능성이 높습니다.

6. 결정성: 에이전트 동작을 예측 가능하게 만드는 방법

LLM 에이전트 세계에서 결정성은 까다로운 주제입니다. 이론적으로는 동일한 입력과 동일한 설정이면 같은 행동 계획과 같은 도구 호출 시퀀스를 얻고 싶습니다. 실제로 모델은 확률적이지만, 예측 가능성을 제어할 수 있는 몇 가지 레버가 있습니다.

첫째, 고전적인 것: 온도(temperature)와 기타 생성 파라미터입니다. 온도가 낮을수록 크리에이티브가 줄고 모델이 “말 잘 듣게” 됩니다. 선물 추천 에이전트에는 대개 0은 아니되 너무 높지도 않은 자유도를 주고 싶을 겁니다. 그렇지 않으면 모델이 매번 같은 도구를 다른 방식으로 호출하려고 할 수 있습니다.

둘째, 명확한 시스템 지시문입니다. “여러 도구를 부르고 하고 싶은 대로 해도 된다”처럼 모호하게 서술하면, 에이전트가 어느 날은 API를 이리저리 뛰어다니고, 어느 날은 “추측”으로 답하려 들 수 있습니다. 언제 어떤 도구를 호출해야 하는지, 허용되는 파라미터는 무엇인지, 오류를 어떻게 해석해야 하는지, 어떤 경우에 작업을 종료해야 하는지를 명시하는 편이 훨씬 좋습니다.

예컨대 GiftGenius 에이전트의 시스템 프롬프트는 다음과 같은 부분을 포함할 수 있습니다.

수신자 프로필(나이, 성별, 사유, 대략적인 예산)이 완전하지 않으면
먼저 사용자 채널로 확인 질문을 하고, 답을 받을 때까지 기다려라.
그 후에야 search_gifts 도구를 프로필이 채워진 상태로 호출하라.
상품을 지어내지 말고, 항상 도구의 결과에 의존하라.

이런 지침은 의사결정의 가변성을 낮추고 동작을 더 결정적으로 만듭니다.

셋째, 도구 자체의 설계입니다. 선물을 “대충 비슷하게” 찾는 도구가 셋이나 있다면, 모델은 어떤 때는 이걸, 어떤 때는 저걸 고르게 됩니다. 책임 영역이 명확하고 겹치지 않게 도구를 설계하고, 그 설명에도 이를 명시하는 것이 좋습니다.

마지막으로 가드레일(guardrails)을 사용할 수 있습니다 — 에이전트의 행동과 모델 결과를 검증하는 규칙과 스키마입니다. Agents SDK에는 출력 데이터 구조에 대한 제약을 포함해 내장 검사·제한 기능이 있습니다. 모델이 스키마에 맞지 않는 결과를 생성하려 들면, 부드럽게 수정하거나 심지어 해당 단계를 다시 시도할 수 있습니다.

미니 예시: 결과 포맷 고정

에이전트가 항상 gifts 필드를 가진 JSON을 반환하고, 그 안에 id, title, score를 가진 객체들이 있도록 해야 한다고 가정해 봅시다. 다음을 할 수 있습니다.

  • 이 스키마를 에이전트 수준에서 기술한다.
  • 최종 출력이 반드시 스키마를 준수하도록 지정한다.
  • 위반 시 단계를 재시도하거나 안전한 오류를 반환한다.

의사코드:

const giftResultSchema = z.object({
  gifts: z.array(z.object({
    id: z.string(),
    title: z.string(),
    score: z.number().min(0).max(1),
  }))
});

// 에이전트 설정에서
const agent = new Agent({
  /* ... */
  outputSchema: giftResultSchema,
});

모델이 이상한 것을 반환하려 하면, 러너가 검증 오류를 알려 줄 것이고, 여러분은 모델에게 다시 요청하거나 사건을 로깅할 수 있습니다.

7. 멱등성: 왜 에이전트가 여러분의 API를 두 번 호출할 수 있는가

결정성이 “같은 입력에 같은 계획”이라면, 멱등성은 재시도의 안전성에 관한 것입니다. 에이전트 맥락에서 이는 두 가지 이유로 매우 중요합니다.

첫째, 재시도 레이어가 하나 더 생깁니다. HTTP 클라이언트와 로드 밸런서뿐 아니라, 에이전트 자체가 오류나 불완전한 결과를 받으면 도구 호출을 반복할 수 있습니다. 둘째, 실제 프로덕션 시나리오에서는 웹훅·큐·스트리밍 채널이 추가되며, 동일한 논리 단계를 실수로 여러 번 처리할 수 있습니다.

여러분은 이미 MCP‑tools 레벨의 멱등성에 대해 다뤘습니다: 이중 결제 금지, 같은 주문을 두 번 생성하지 않기, 요청에 idempotency key 사용 등입니다. 이제는 같은 개념이 에이전트의 다단계 특성만큼 확대됩니다.

GiftGenius에 선택된 선물 목록으로 ACP/Stripe에서 draft 체크아웃을 만드는 create_checkout_session 도구가 생겼다고 합시다. 네트워크 오류 때문에 에이전트가 이 호출을 재시도한다면, 서로 다른 두 주문과 두 번의 결제를 원하지 않을 것입니다.

따라서 다음이 필요합니다.

  • 각 논리적 작업마다 외부 idempotency key를 고안한다(예: runId + stepIndex 또는 명시적으로 생성한 checkoutDraftId).
  • 이를 여러분의 백엔드/ACP 엔드포인트에 전달한다.
  • 백엔드에서 해당 키로 이미 처리했는지 확인하고, 이미 처리했다면 새로운 실행 대신 저장된 결과를 반환한다.

TypeScript 의사 예시:

async function createCheckoutDraft(runId: string, payload: DraftPayload) {
  const key = `gift-checkout-${runId}`;

  const existing = await findDraftByKey(key);
  if (existing) return existing;

  const draft = await stripe.checkout.sessions.create({
    /* ... */,
    idempotencyKey: key, // 또는 별도의 멱등성 레이어를 사용
  });

  await saveDraftWithKey(key, draft);
  return draft;
}

이제 어떤 이유로든 에이전트가 동일한 runId로 이 도구를 두 번 호출하더라도, 여러분의 코드는 멱등성을 유지합니다: 동일한 논리 단계 → 동일한 실제 결과.

“먼저 확인, 그다음 실행”

멱등성의 또 다른 흔한 패턴은, 먼저 상태를 확인하고 그다음 행동하는 것입니다. 예를 들어 주문을 생성하기 전에, 동일한 clientReferenceId나 같은 파라미터 집합의 주문이 이미 존재하는지 확인합니다. 에이전트가 이전 단계에서 무엇을 했는지 “잊을” 수 있는 긴 워크플로에서 특히 유용합니다.

세이프 모드/페이크 모드

개발 단계에서는 위험한 도구를 위한 “안전 모드”가 유용합니다. 실제 행동 대신 무엇을 할 예정이었는지 로깅만 하고, 가짜 결과를 반환합니다. 에이전트에서는 돈이나 데이터를 위험에 빠뜨리지 않고 실환경에서 run 사이클을 돌려 보는 편리한 방법입니다.

8. 미니 실습: GiftGenius 에이전트를 사람 말로 설명하기

우리는 이미 run 사이클, 결정성, 도구의 멱등성에 대해 이야기했습니다. 잠시 코드를 떠나, 이 모든 것이 실제 시나리오에서 어떻게 엮이는지 확인해 봅시다.

지금은 코드 없이, 종이(또는 머릿속)로 작은 연습을 해 보는 것이 좋습니다.

간단한 에이전트를 설명한다고 상상해 보세요.

  • system
    : 너는 선물 추천 도우미다; 중요한 세부사항을 항상 확인하고, 상품을 지어내지 않으며, 도구 결과만 사용한다.
  • user
    : 동료에게 줄 50달러 이하의 선물을 원해.

이런 에이전트가 어떤 단계를 수행해야 하는지 말로 적어 보세요.

전형적인 시나리오는 다음과 같습니다.

  1. 먼저 에이전트는 정보가 충분한지 확인합니다. 부족하면 확인 질문을 합니다: 동료가 대략 무엇을 하는지(디자이너, 개발자, 매니저), 어떤 금기(주류, 장난감 선물)가 있는지, 배송 제한이 있는지 등. 답변은 session state 또는 도구 호출 파라미터에 저장됩니다.
  2. 그런 다음 에이전트는 채워진 프로필로 search_gifts 도구를 호출합니다: “동료 개발자, 예산 50, 카테고리 — 가젯과 오피스”. 이 도구는 가격·카테고리·상품 ID가 포함된 후보 목록을 반환합니다.
  3. 이후 필요한 경우 filter_gifts_by_constraints 같은 추가 도구를 호출해 특정 지역에 배송할 수 없는 상품을 제외하거나, 프롬프트에서 수동으로 필터링할 수 있습니다. 그런 뒤 관련성과 가격을 기준으로 정렬하고, “커피를 좋아한다면 적합”, “재택근무에 좋은 옵션” 같은 코멘트를 덧붙일 수 있습니다.
  4. 마지막으로 ChatGPT App을 위한 최종 구조화 응답을 준비합니다: 5–7개의 선물과 간단한 설명, 사용 팁, 체크아웃 링크(또는 다음 단계인 draft 체크아웃 생성 링크) 등.

어디에서 tool‑call이 필요할까요? 분명히 상품 검색과 필터링, 가용성 확인, draft 체크아웃 생성에서 필요합니다. 어떤 단계가 멱등적이어야 할까요? 무엇보다 주문과 돈이 관련된 모든 작업 — draft 체크아웃 생성, 필요하다면 DB에 히스토리 기록 등이 우선입니다.

9. 에이전트 첫걸음에서 자주 하는 실수

실수 1: 제한 없는 “두 번째 ChatGPT”로서의 에이전트.
가끔은 모델에 프롬프트를 하나 더 주고 그걸 “에이전트”라고 부르고 싶을 때가 있습니다. 그 결과, 텍스트를 잔뜩 생성하고 도구를 무작위로 호출하며 통제가 어려운 무언가가 나옵니다. 이를 피하려면 system에서 에이전트의 역할을 명확히 기술하고, 도구 목록을 제한하며, 에이전트를 막연한 “또 하나의 텍스트 생성 세계”가 아니라 구체적인 미션을 가진 오케스트레이터로 봐야 합니다.

실수 2: 도구에 멱등성이 없음.
런너가 이제 자동으로 호출을 재시도할 수 있다는 점을 고려하지 않고, 예전 HTTP 핸들러를 그대로 에이전트 아래로 옮겨 오는 경우가 많습니다. 결제·주문의 경우 이는 매우 좋지 않은 결과로 이어질 수 있습니다. 올바른 접근은 논리적으로 동일한 키로 다시 호출되더라도 실제 행동이 중복되지 않도록 처음부터 도구를 설계하는 것입니다.

실수 3: 지나치게 크리에이티브한 모델 설정.
높은 온도는 건배사나 시를 짓는 데에는 좋지만, 다단계 프로세스를 안정적으로 오케스트레이션해야 하는 에이전트에게는 예측 불가능한 동작으로 이어질 수 있습니다. 매번 다른 도구를 고르고, 다른 계획을 만들어 내며, 심지어 도구가 있다는 사실을 잊어버리기도 합니다. 에이전트를 “업무용” 개체로 보고, 더 엄격한 모드로 운용하는 편이 좋습니다.

실수 4: “만능 도구”.
가끔은 execute_any_sql이나 do_anything_with_orders 같은 범용 도구 하나를 만들고, 그걸 에이전트 손에 쥐여 주고 싶을 수 있습니다. LLM의 창의성과 결합하면 거의 보안 위협이 보장됩니다. 모든 권한을 가진 “전능 도구” 하나보다, 계약과 권한이 명확한 몇 가지 특화 도구를 갖는 것이 훨씬 낫습니다.

실수 5: run 종료 기준이 명확하지 않음.
에이전트에게 언제 멈춰야 하는지 설명하지 않으면, 무한 또는 준무한 루프에 빠질 수 있습니다: 결과를 또 확인하고, 사용자를 또 되묻고, 같은 오류에서 또 도구를 호출하려 시도하는 식입니다. 이는 의존성 중 하나가 불안정할 때 부하 상황에서 종종 드러납니다. 올바른 방법은 단계 수·run 시간·동일 오류에서의 재시도 횟수에 대한 한도를 설정하고, system에도 합리적 옵션을 소진하면 “정직하게 포기”해야 함을 명시하는 것입니다.

실수 6: 상태에 모든 것을 저장.
Agents SDK가 session state 다루기를 쉽게 해 주기 때문에, 큰 문서·가공되지 않은 로그·민감한 데이터 등 뭐든지 집어넣고 싶어질 수 있습니다. 이는 컨텍스트를 비대하게 하고 비용을 늘리며 보안 위험을 만듭니다. 에이전트 상태에는 정말로 작업 지속에 필요한 것만 저장하고, 나머지는 DB·로그·다른 레이어에, 프라이버시를 고려해 보관해야 합니다.

실수 7: 단순 MCP‑tool만으로 충분한 곳에 에이전트를 사용.
가끔은 작업이 단지 함수를 하나 호출하고 결과를 반환하는 것뿐인데도 에이전트로 시작하려는 경우가 있습니다. 필요 없는 복잡성이 추가됩니다: run 사이클·상태·추가 로그·잠재적 실패 지점 등. 시나리오가 복잡한 워크플로 없이 하나의 tool‑call로 충분하다면 그렇게 유지하고, 진짜로 다단계가 필요해질 때 에이전트를 도입하는 것이 좋습니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION