라우팅 패턴

에이전트 업무 분기 메커니즘

라우팅 패턴 : 에이전트 업무 분기 메커니즘

프롬프트 체이닝을 통한 순차 처리는 순서대로 동작하는 워크플로를 실행하는 기초 기법이지만, 상황에 맞춰 유연하게 대응해야 하는 시나리오에서는 적용 범위가 제한됩니다. 예시로 이커머스 도우미 에이전트를 개발했다고 가정하겠습니다. 유저에게 제공해야 하는 기능이 주문 상태 확인, 제품 정보, 기술 지원 문의, 기타 문의가 있을 경우, 프롬프트 체이닝 그 자체로는 업무 분기 처리가 어렵죠.

라우팅 패턴은 에이전트에 제어 흐름에 대한 기준과 각 업무에 특화된 도구를 넘깁니다. 에이전트의 판단에 따라 특정 상황에서 특정 도구를 호출해서 업무를 분기 형태로 처리할 수 있게 만드는 것이죠.

diagram

예를 들어 이커머스 도우미 에이전트 의사 코드가 아래와 같다고 가정해보겠습니다.

  1. 사용자 질의를 분석한다.
  2. 질의 의도에 따라 라우팅한다.
  • 주문 상태 확인 : 주문 데이터베이스와 상호작용하는 하위 에이전트나 도구로 질의를 보낸다.
  • 제품 정보 : 제품 카탈로그를 검색하는 하위 에이전트나 도구로 질의를 보낸다.
  • 기술 지원 : 문제 해결 가이드에 접근하거나 상담원에게 이관하는 별도의 체인으로 보낸다.
  • 의도가 불분명한 질문 : 질문의 명확화를 위한 하위 에이전트나 프롬프트 체인으로 보내서 상세하게 질문한다.

즉 라우팅 패턴의 핵심 구성 요소는 2번의 입력을 평가하고 흐름을 분기하는 메커니즘이 됩니다. 이 메커니즘은 다양한 방식으로 구현할 수 있습니다.

  • LLM 기반 라우팅 : 모델 자체에게 라우팅 프롬프트를 제공하여 입력을 분석하고 분기 시킵니다. 예를 들어 "다음 사용자 질의를 분석하고 범주만 출력하시오 : Order Status, PRoduct Info, Technical Support, Other." 라고 프롬프트를 제공한다면 LLM은 질의를 분석한 후 출력을 저 4가지 형태로 좁혀서 리턴하게 될 것입니다.
  • 임베딩 기반 라우팅 : (1) 입력 질의를 벡터 임베딩으로 변환하고, (2) 경로마다 미리 임베딩해둔 예시 중 가장 비슷한 경로로 보냅니다. 이 방식은 키워드가 아닌 입력 의미에 따라 판단하는 시맨틱 라우팅에 유용합니다. (내 돈 돌려주세요와 환불해주세요를 같은 의미로 결정하고 필요한 경로에 보낼 수 있습니다.)

코드 예시

이 예시는 가상의 이커머스 헬퍼 에이전트를 구성한 것입니다. 코드 예시는 Claude Agent SDK로 구성되어 있고, 아래의 흐름을 따릅니다.

  • 경로별 역할 프롬프트와 도구 모음을 정의합니다.
  • 분류 에이전트가 라우팅 프롬프트에 따라 유저의 질의가 어떤 경로인지 분류합니다.
  • 분류된 경로의 역할 프롬프트와 도구를 담당 에이전트에 넣어 실행합니다.
  • 결과를 받습니다. 실패하면 안내 문구를 돌려줍니다.
import { query, tool, createSdkMcpServer, type Options, type SdkMcpToolDefinition } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

// ==================================================
// 라우터 필요 함수: 경로별 에이전트가 쓸 도구입니다.
// ==================================================

const getOrderStatus = tool(
  "get_order_status",
  "주문 번호로 배송 상태를 조회한다",
  { orderId: z.string() },
  async ({ orderId }) => ({
    content: [{ type: "text", text: `주문 ${orderId}: 배송 중, 내일 도착 예정` }],
  }),
);

const searchProducts = tool(
  "search_products",
  "키워드로 제품 카탈로그를 검색한다",
  { keyword: z.string() }, // ← 이 줄 추가
  // 그 외 실제 필요 로직
);

const searchGuide = tool(
  "search_guide",
  "증상에 맞는 문제 해결 가이드를 찾는다",
  { issue: z.string() },
  // 그 외 실제 필요 로직
);

const transferToHuman = tool(
  "transfer_to_human",
  "가이드로 해결되지 않으면 상담원에게 연결한다",
  { reason: z.string() },
  // 그 외 실제 필요 로직
);

// ==================================================
// 경로 정의: 경로마다 역할(프롬프트)과 쓸 수 있는 도구를 정합니다.
// ==================================================

type Route = { role: string; tools: SdkMcpToolDefinition<any>[] };

const ROUTES: Record<string, Route> = {
  "Order Status": {
    role: "당신은 주문 상태 확인 담당입니다. 도구로 배송 상태를 조회해 안내하세요.",
    tools: [getOrderStatus],
  },
  "Product Info": {
    role: "당신은 제품 정보 담당입니다. 도구로 제품을 검색해 안내하세요.",
    tools: [searchProducts],
  },
  "Technical Support": {
    role: "당신은 기술 지원 담당입니다. 가이드를 먼저 찾아보고, 해결이 안 되면 상담원에게 연결하세요.",
    tools: [searchGuide, transferToHuman],
  },
  "Other": {
    role: "사용자의 의도가 불분명합니다. 무엇을 원하는지 되물어보세요.",
    tools: [],
  },
};
const ROUTE_NAMES = Object.keys(ROUTES);
const FALLBACK = "일시적인 오류가 발생했어요. 잠시 후 다시 시도해 주세요.";

// ==================================================
// 라우팅
// ==================================================

// Claude에게 묻고 최종 답변만 꺼낸다 (실패하면 SDK가 예외를 던진다)
async function ask(prompt: string, options: Options): Promise<string> {
  for await (const message of query({ prompt, options })) {
    if (message.type === "result" && message.subtype === "success") {
      return message.result;
    }
  }
  return "";
}

// 1. 라우터: 질의를 범주 하나로 분류합니다.
async function classify(userQuery: string): Promise<string> {
  const answer = await ask(userQuery, {
    model: "haiku",
    tools: [],
    systemPrompt: `다음 사용자 질의를 분석하고 범주만 출력하시오: ${ROUTE_NAMES.join(", ")}`,
  });

  const route = answer.trim();
  return ROUTE_NAMES.includes(route) ? route : "Other";
}

// 2. 분류된 경로의 에이전트가 자기 도구만 들고 답합니다.
async function handle(userQuery: string): Promise<string> {
  const route = await classify(userQuery);
  console.log(`[라우터] ${route}`);

  const { role, tools } = ROUTES[route];
  return ask(userQuery, {
    systemPrompt: role,
    tools: [], // Agent SDK에서 tools는 기본 도구(Read, Bash 등)를 고르는 옵션이라 커스텀 도구는 여기에 넣을 수 없습니다.
    mcpServers: { shop: createSdkMcpServer({ name: "shop", tools, alwaysLoad: true }) }, // 커스텀 도구는 MCP로 묶어야 합니다.
    allowedTools: ["mcp__shop__*"], // shop 서버의 도구는 별도 확인 없이 실행하도록 합니다.
  });
}

handle("주문번호 A123 언제 와요?")
  .then(console.log)
  .catch(() => console.log(FALLBACK));

결론

라우팅 패턴은 유연하고 민첩하게 반응하는 에이전틱 시스템을 구축하는데 핵심적인 수단입니다. 라우팅을 구현하면 단순하고 정해진 순서대로만 진행되는 실행 흐름을 넘어 에이전트가 정보를 처리하고 사용자 입력에 응답하고 사용 가능한 도구나 하위 에이전트를 활용하는 방법을 지능적으로 결정할 수 있게 됩니다.

입력을 분석하고 조건에 따라 워크플로를 분기하는 능력이 있어야 현실 세계의 가변성을 다루는 에이전트를 구축할 수 있습니다. 이런 면에서 라우팅 패턴은 너무나도 당연한 핵심 패턴 중 하나입니다.

댓글

아직 댓글이 없습니다.