logo

OpenRouter API로 함수 호출

모델이 알 수 없는 현재 재고를 외부 함수로 조회하고, 호출 요청·실행·결과 반환을 셀별로 확인한다.

이번 실습의 흐름

  • 질문: “무선 마우스는 지금 몇 개 남아 있나요?”
  • 도구: get_stock 하나.
  • 현재 재고는 매장의 운영 자료. 모델의 학습된 지식이나 질문의 문장만으로 확인할 수 없는 정보.
  • 먼저 파이썬으로 재고를 조회한 뒤 같은 함수를 모델에 연결.
  • 실습에서는 실제 매장 대신 노트북의 사전을 재고표로 사용. 재고 수량은 모델의 질문·도구 설명에 넣지 않고 함수 실행 결과로만 전달.
  • 한 번의 함수 호출과 최종 답변만 다룸. 반복 실행 구조를 만들 필요 없음.

함수 호출의 요청과 응답

Mermaid 다이어그램 원문

sequenceDiagram
    autonumber
    participant L as LLM
    participant A as 애플리케이션
    participant F as 도구 함수
    A->>L: 작업 요청과 도구 정의
    L-->>A: 도구 호출 요청(함수 이름·인자)
    A->>F: 함수 실행(인자)
    F-->>A: 실행 결과
    A->>L: 호출 기록과 실행 결과
    L-->>A: 최종 답변

대화형 다이어그램을 불러오기 전에는 원문으로 표시됩니다.

  • 애플리케이션: 사용자 요청을 받고 모델·도구 호출과 결과 전달을 관리하는 프로그램.
  • 위에서 아래로 진행되는 시간 순서. 실선은 요청·실행, 점선은 반환.
  • 모델에 도구를 알려 주는 첫 요청과, 도구 실행 결과를 전달하는 두 번째 요청을 구분.

실습 준비

  • 환경: Colab 또는 Jupyter. 증강 섹션의 실습은 같은 노트북·런타임에서 순서대로 실행.

  • 설치·불러오기·API 연결은 여기서 한 번만 준비. 뒤 자료는 client, model, json, Client, FastMCP, Monty를 그대로 사용. 런타임을 재시작했다면 이 준비 코드부터 다시 실행.

  • OpenRouter API 키와 사용 가능한 크레딧 필요. API 호출에는 사용량에 따른 비용 발생.

  • 모형: openai/gpt-4.1-mini. 함수 호출을 지원하는 고정 모형을 사용하며, 다른 모형으로 바꿀 때는 도구 호출 지원 여부 확인.

  • 키 입력란에는 OpenRouter에서 발급받은 키 입력. 입력한 키를 화면·코드·공유 노트북에 출력하지 않기.

  • openai: OpenRouter API 호출. fastmcp: MCP 도구 연결. pydantic-monty: 코드 실행 샌드박스.

%pip install -q openai fastmcp==4.1.4 pydantic-monty==1.4.0
import json
from getpass import getpass
from openai import OpenAI
from fastmcp import Client, FastMCP
from pydantic_monty import Monty
client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key=getpass("OpenRouter API 키: "),
)
model = "openai/gpt-4.1-mini"

재고 조회 함수 만들기

  • inventory: 상품 이름과 현재 재고 수량을 짝지은 파이썬 사전.
  • def: 함수 정의. product_name: 조회할 상품 이름.
  • str, dict: 입력은 문자열, 반환값은 사전이라는 설명.
  • inventory.get(): 상품 이름에 해당하는 수량 조회. 등록되지 않은 이름이면 None 반환.
  • return: 상품 이름과 조회 결과를 함수 밖으로 반환.
inventory = {
    "무선 마우스": 7,
    "기계식 키보드": 0,
    "USB 허브": 12,
}

def get_stock(product_name: str) -> dict:
    """실습용 재고표에서 상품의 현재 재고를 조회."""
    return {
        "product_name": product_name,
        "stock": inventory.get(product_name),
    }
get_stock("무선 마우스")  # 상품 이름으로 현재 재고 조회
실행 결과
{'product_name': '무선 마우스', 'stock': 7}
  • 위 값은 파이썬 함수를 직접 실행한 결과. 아직 모델을 사용하지 않은 단계.
get_stock("기계식 키보드")
실행 결과
{'product_name': '기계식 키보드', 'stock': 0}
  • 수량 0: 등록된 상품이지만 현재 품절.
get_stock("웹캠")
실행 결과
{'product_name': '웹캠', 'stock': None}
  • None: 재고표에 등록되지 않은 상품. 수량 0과 구분.
  • 실제 서비스에서는 이 함수 안에서 데이터베이스나 재고 관리 API를 조회하도록 연결.

도구 설명 전달하기

  • 함수의 파이썬 코드를 모델에 보내는 대신 이름·설명·입력 형식을 전달.
  • parameters: 함수가 받을 인자의 JSON Schema. 이미 배운 자료형·필수 항목 표기를 사용.
  • description: 조회할 정보와 입력할 상품 이름을 설명. 재고 수량 자체는 포함하지 않음.
  • 아래 블록은 그대로 실행하고 name, description, properties만 우선 확인.
stock_tool = {
    "type": "function",
    "function": {
        "name": "get_stock",
        "description": "상품의 현재 재고를 조회한다. stock이 0이면 품절, null이면 등록되지 않은 상품이다.",
        "parameters": {
            "type": "object",
            "properties": {
                "product_name": {
                    "type": "string",
                    "description": "조회할 상품 이름. 예: 무선 마우스, 기계식 키보드, USB 허브"
                }
            },
            "required": [
                "product_name"
            ],
            "additionalProperties": False
        }
    }
}

모델에 호출 요청하기

  • tool_choice: 이 단계에서는 지정한 함수 호출을 요청. 모델의 자유로운 도구 선택보다 호출 흐름을 먼저 확인하기 위한 설정.
  • parallel_tool_calls=False: 이 실습에서는 한 번에 하나의 도구 호출만 요청.
messages = [
    {"role": "system", "content": "재고는 도구 결과로 답하세요. stock이 null이면 등록되지 않은 상품이라고 안내하세요."},
    {"role": "user", "content": "무선 마우스는 지금 몇 개 남아 있나요?"},
]
response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=[stock_tool],
    tool_choice={"type": "function", "function": {"name": "get_stock"}},
    parallel_tool_calls=False,
)
assistant_message = response.choices[0].message
tool_call = assistant_message.tool_calls[0]  # 첫 번째 함수 호출 요청
tool_call.function.name  # 요청한 함수 이름
tool_call.function.arguments  # JSON 문자열로 전달된 인자
  • 확인: 함수 이름이 get_stock인지, 상품 이름이 질문의 대상을 반영했는지 살펴보기.
  • 호출 요청이 반환됐다고 재고 조회가 실행된 것은 아님.

인자를 읽고 실제 실행하기

  • json.loads(): JSON 문자열을 파이썬 사전으로 변환.
  • arguments["product_name"]: 인자 사전에서 조회할 상품 이름을 꺼내는 표현.
  • 실제 실행할 함수는 이 실습에서 정의한 get_stock으로 고정.
arguments = json.loads(tool_call.function.arguments)
arguments
result = get_stock(arguments["product_name"])
result

결과를 모델에 돌려주기

  • 대화에는 사용자의 질문뿐 아니라 모델의 호출 요청도 포함.
  • role="tool": 도구에서 반환된 결과.
  • tool_call_id: 어떤 호출 요청에 대한 결과인지 연결하는 식별자.
  • json.dumps(): 파이썬 값을 결과 메시지에 넣을 JSON 문자열로 변환.
messages.append(assistant_message.model_dump(exclude_none=True))
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,
    "content": json.dumps(result, ensure_ascii=False),
})
final_response = client.chat.completions.create(
    model=model,
    messages=messages,
    tools=[stock_tool],
    tool_choice="none",  # 조회 결과로 답하고 추가 도구 호출은 하지 않음
)
final_response.choices[0].message.content
  • 확인: 상품 이름과 재고 수량이 result와 일치하는가? 수량을 추측하거나 없는 상품을 품절로 설명하지 않는가?
  • 답변 문장은 실행마다 달라질 수 있음. 문장 모양보다 조회 결과의 반영 여부 확인.

재고가 바뀌면 답변도 바뀔까?

  • 재고표에서 무선 마우스 수량을 변경. 이 셀은 매장의 재고가 바뀐 상황을 단순화한 예.
inventory["무선 마우스"] = 2  # 재고표의 현재 수량 변경
get_stock("무선 마우스")
실행 결과
{'product_name': '무선 마우스', 'stock': 2}
  • 질문을 그대로 두고 messages를 만드는 셀부터 최종 답변 셀까지 다시 실행.
  • 모델을 다시 학습하거나 질문에 새 수량을 적지 않아도, 새 조회 결과를 전달해 달라진 재고를 안내 가능.
  • 확인: 최종 답변이 이전 수량 대신 변경된 수량을 반영하는가?

다른 상품으로 바꿔 보기

  • 질문을 “기계식 키보드는 지금 몇 개 남아 있나요?”로 변경.
  • 이어서 “웹캠은 지금 몇 개 남아 있나요?”로 변경.
  • 질문을 바꿀 때마다 messages를 만드는 셀부터 다시 실행. 이전 질문·호출 기록을 새 실험에 섞지 않기.
  • 함수 이름 → 상품 이름 → 실행 결과 → 최종 답변 순서로 비교. 품절과 미등록 상품을 구분하는지 확인.

출처: OpenRouter 함수 호출.

퀴즈

문제 1 / 4맞음: 0힌트: 0틀림: 0채점중: 0남음: 4

현재 재고를 안내할 때 조회 도구가 필요한 이유는?

  • ○매장의 현재 재고는 모델의 학습된 지식이나 질문만으로 알 수 없기 때문에
  • ○상품 이름을 이해하려면 반드시 파이썬 실행이 필요하기 때문에
  • ○JSON 형식의 답변은 도구 없이는 생성할 수 없기 때문에
  • ○도구를 호출하면 모델의 파라미터가 현재 재고로 갱신되기 때문에

퀴즈를 풀려면 대화형 기능을 불러와야 합니다.