쿠키를 사용합니다. 쿠키와 브라우저 저장소는 로그인 상태를 유지하고 환경설정을 기억합니다. 동의하시면 사이트 이용 방식도 측정합니다. 언제든지 할 수 있습니다. 자세한 내용은 쿠키 정책을 참고하세요.

가이드: 도구 호출 에이전트 만들기

시작하기

가이드: 도구 호출 에이전트 만들기

단계 수를 제한한 루프, 병렬 도구 호출, 구조화된 최종 출력, 비용 제어를 갖춘 도구 호출 에이전트를 OpenAI 형식으로 직접 만드는 과정을 Python과 Node 코드로 따라 해 봅니다. 모델 선택 기준도 함께 짚습니다.

에이전트는 하나의 루프입니다. 모델이 대화를 읽고 하나 이상의 도구를 호출하기로 결정하면, 사용자가 이를 실행해 결과를 돌려보내고, 모델은 답할 수 있을 때까지 이를 계속합니다. api14는 도구를 지원하는 모든 모델에서 OpenAI tools 형식으로 이를 지원하므로, 모델 id만 바꾸면 같은 코드가 OpenAI, Anthropic, Google 등 여러 제공자에서 동작합니다.

이 가이드는 단계 제한, 병렬 도구 실행, 구조화된 최종 답변, 비용 제어를 갖춘 작은 에이전트를 만듭니다. 코드는 공식 openai 패키지와 base URL https://api14.com/api/v1을 사용합니다.

도구 정의

각 도구는 이름, 모델이 읽는 설명, 인자에 대한 JSON Schema로 이루어집니다. 설명은 구체적으로 작성하세요. 모델이 참고할 수 있는 문서는 이것뿐입니다.

import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api14.com/api/v1", api_key=os.environ["API14_API_KEY"])

def search_orders(customer_email: str) -> list:
    return [{"id": "A-1001", "status": "shipped"}]  # your database query

def get_shipment(order_id: str) -> dict:
    return {"order_id": order_id, "carrier": "UPS", "eta": "2026-10-09"}  # your API call

TOOLS = {"search_orders": search_orders, "get_shipment": get_shipment}

tools = [
    {"type": "function", "function": {
        "name": "search_orders",
        "description": "List a customer's orders with their status.",
        "parameters": {"type": "object", "properties": {"customer_email": {"type": "string"}},
                       "required": ["customer_email"]},
    }},
    {"type": "function", "function": {
        "name": "get_shipment",
        "description": "Carrier and estimated delivery date for one order.",
        "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
                       "required": ["order_id"]},
    }},
]

단계 제한이 있는 루프

반복할 때마다 별도의 요청으로 과금되므로 항상 단계 수에 상한을 두세요. 도구 오류는 프로그램을 중단시키지 말고 내용으로 모델에 돌려주세요. 모델은 보통 인자를 고쳐 스스로 복구합니다.

MAX_STEPS = 8

def run_tool(call):
    try:
        args = json.loads(call.function.arguments)
        result = TOOLS[call.function.name](**args)
    except Exception as e:  # bad JSON, unknown tool, or your function failed
        result = {"error": str(e)}
    return {"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)}

def agent(task, model="openai/gpt-5-mini"):
    messages = [
        {"role": "system", "content": "You are a support agent. Use tools to look things up. Be brief."},
        {"role": "user", "content": task},
    ]
    spent = 0.0
    for step in range(MAX_STEPS):
        r = client.chat.completions.create(model=model, messages=messages, tools=tools, max_tokens=1000)
        spent += r.usage.cost
        msg = r.choices[0].message
        if not msg.tool_calls:
            return msg.content, spent
        messages.append(msg)
        messages.extend(run_tool(call) for call in msg.tool_calls)
    raise RuntimeError(f"No answer after {MAX_STEPS} steps (spent ${spent:.4f})")

print(agent("Where is my order? My email is ada@example.com"))

루프 안의 모든 요청에 tools를 함께 보내세요. 요청에 도구 정의가 없으면 tool 메시지를 거부하는 제공자가 많습니다.

병렬 도구 호출

parallel_tool_calls(지원되는 곳에서는 기본으로 켜짐)를 사용하면 모델이 한 턴에 여러 호출을 요청할 수 있습니다. 예를 들어 주문 세 건에 대해 get_shipment를 한꺼번에 요청합니다. 이를 동시에 실행하고 호출마다 tool 메시지를 하나씩 추가하세요. 순서는 상관없으며, tool_call_id가 각 결과를 해당 호출에 연결합니다. Node 예시입니다.

import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api14.com/api/v1", apiKey: process.env.API14_API_KEY });
const MAX_STEPS = 8;

async function agent(messages, tools, impl, model = "openai/gpt-5-mini") {
  for (let step = 0; step < MAX_STEPS; step++) {
    const res = await client.chat.completions.create({ model, messages, tools, max_tokens: 1000 });
    const msg = res.choices[0].message;
    if (!msg.tool_calls?.length) return msg.content;

    messages.push(msg);
    const results = await Promise.all(msg.tool_calls.map(async (call) => {
      let content;
      try {
        content = JSON.stringify(await impl[call.function.name](JSON.parse(call.function.arguments)));
      } catch (err) {
        content = JSON.stringify({ error: String(err) });
      }
      return { role: "tool", tool_call_id: call.id, content };
    }));
    messages.push(...results);
  }
  throw new Error(`No answer after ${MAX_STEPS} steps`);
}

도구에 부수 효과가 있어 순서대로 실행해야 한다면(생성한 뒤 수정 등) parallel_tool_calls: false로 설정해 모델이 턴마다 호출을 하나씩만 요청하게 하세요.

구조화된 최종 출력

에이전트의 답변을 다른 코드가 사용한다면 스키마에 맞는 JSON을 요청하세요. 간단하고 이식성 높은 방법은 루프가 사실을 모두 모은 뒤 호출을 한 번 더 하는 것입니다(여기서 messages는 루프의 대화이며, agent()가 답변과 함께 반환하도록 하세요).

schema = {
    "type": "object",
    "properties": {
        "order_id": {"type": "string"},
        "status": {"type": "string", "enum": ["processing", "shipped", "delivered", "unknown"]},
        "eta": {"type": ["string", "null"]},
        "reply": {"type": "string"},
    },
    "required": ["order_id", "status", "eta", "reply"],
    "additionalProperties": False,
}

final = client.chat.completions.create(
    model="openai/gpt-5-mini",
    messages=messages + [{"role": "user", "content": "Summarize the result for our ticket system."}],
    tools=tools,
    tool_choice="none",
    response_format={"type": "json_schema", "json_schema": {"name": "ticket", "strict": True, "schema": schema}},
)
ticket = json.loads(final.choices[0].message.content)

tool_choice: "none"은 요청에 tools를 유지하면서(기록에 tool 메시지가 있으므로 필요합니다) 추가 호출은 막습니다. 파싱한 JSON은 항상 검증하세요. Structured outputs를 참고하세요.

모델 선택

  • 기능: 모델의 supported_parameters에 tools가 있어야 합니다. GET /models로 확인하세요. 모델을 참고하세요. 마지막 단계에는 structured_outputs 또는 response_format이 있는 모델을 고르세요.
  • 라우팅: provider: { "require_parameters": true }를 추가하면(Python에서는 extra_body로) 보내는 모든 파라미터를 지원하는 제공자로만 요청이 전달됩니다. 제공자 라우팅을 참고하세요.
  • 크기: openai/gpt-5-mini 같은 작은 모델은 잘 설명된 소수의 도구를 안정적이고 저렴하게 처리합니다. 에이전트의 도구가 많거나, 여러 단계에 걸친 긴 계획이 필요하거나, 자꾸 잘못된 도구를 고른다면 더 큰 모델로 옮기세요.
  • 내 도구로 테스트: 실제 작업 20~50개를 후보 모델 두세 개에 똑같이 실행해 성공률, 작업당 단계 수, 총 usage.cost를 비교하세요.
curl -s "https://api14.com/api/v1/models?category=text" \
  | jq -r '.data[] | select(.supported_parameters | index("tools")) | "\(.id)  \(.pricing.prompt)"'

비용 제어

에이전트는 작업 하나에 요청을 여러 번 보낼 수 있고, 단계마다 전체 대화가 다시 전송됩니다. 여러 수준에서 지출을 제한하세요.

  • 위 예시처럼 단계 제한(MAX_STEPS)과 요청당 max_tokens.
  • 작업별 예산: 단계마다 usage.cost를 합산하고 기준을 넘으면 중단합니다.
  • 짧은 도구 결과: 모델에 필요한 필드만 반환하세요. 큰 도구 출력은 이후 모든 단계에서 입력 토큰으로 과금됩니다.
  • 키별 일일 USD 한도: API 키에서 에이전트 전용 키를 만들고 일일 한도를 설정하세요. 한도에 도달하면 다음 UTC 날짜까지 요청이 429 daily_limit_reached를 반환합니다. 이 코드는 재시도하지 말고 에이전트를 중단하는 방식으로 처리하세요.
  • 모델 허용 목록: 키를 테스트한 모델로만 제한하세요. 그 외 모델은 403 model_not_allowed를 반환합니다.
  • 프롬프트 캐싱: 시스템 프롬프트와 도구 정의를 매 요청의 맨 앞에 동일하게 유지하면 캐시된 접두부 할인이 적용됩니다. 프롬프트 캐싱을 참고하세요.

도구 호출, 속도 제한, 오류도 참고하세요.