Qwen Function Calling: 모델을 외부 도구에 연결하기

Qwen Function Calling: 모델을 외부 도구에 연결하기

LLM은 실시간 데이터나 외부 시스템에 직접 접근하지 못해요. Function Calling은 이를 돌파하는 방식으로, API·데이터베이스·사용자 정의 함수 같은 도구를 모델이 호출하게 해서 정보를 끌어오거나 실제 행동을 수행하게 합니다. 개발자가 도구를 정의하면 모델이 호출 시점을 결정하고, 어플리케이션이 실제 호출을 실행하죠. 이 문서는 그 전 과정을 날씨 조회 예시로 안내해요.

출처: QwenCloud 공식 문서 - Function Calling

동작 원리

Function Calling은 어플리케이션과 LLM 사이의 다단계 상호작용으로 동작해요.

  1. 첫 모델 호출: 어플리케이션이 사용자 질문과 도구 목록을 LLM에 보냅니다.
  2. 도구 호출 지시 수신: 모델이 도구를 쓰기로 하면 함수 이름과 입력 파라미터를 담은 JSON 지시를 반환해요. 도구를 쓰지 않기로 하면 자연어 답변을 반환하죠.
  3. 어플리케이션에서 도구 실행: 지정된 도구를 실행해 출력을 얻습니다.
  4. 두 번째 모델 호출: 도구 출력을 messages 배열(컨텍스트)에 추가하고 다시 모델을 호출해요.
  5. 최종 응답 수신: 모델이 도구 출력과 사용자 질문을 조합해 자연어 답변을 냅니다.

지원 모델

모든 범용 텍스트 생성 모델이 함수 호출을 지원하고, 서드파티 모델(DeepSeek, Kimi, GLM, MiniMax)과 Qwen3.8 오픈소스 계열(qwen3.8-2.4t-a95b)도 포함돼요. 비전 모델 중에는 Qwen3-VL, qwen3.5-omni-plus, qwen3.5-omni-flash, qwen3-omni-flash도 지원해요.

⚠️ GLM 계열과 함수 호출을 쓸 때는 요청에 반드시 extra_body={"tool_stream": True}를 넣어야 해요. 그렇지 않으면 모델이 tool_calls를 반환하지 않아 도구 호출이 동작하지 않습니다.

시작하기 (OpenAI 호환 API)

날씨 조회 시나리오로 보는 핵심 구조예요. 요청에 tools 배열을 넘기고, 모델이 반환한 tool_calls를 읽어 도구를 실행한 뒤 결과를 role: "tool" 메시지로 다시 넣는 흐름입니다. 엔드포인트는 https://dashscope-intl.aliyuncs.com/compatible-mode/v1, 인증은 DASHSCOPE_API_KEY예요.

from openai import OpenAI
from datetime import datetime
import json, os, random

client = OpenAI(
  api_key=os.getenv("DASHSCOPE_API_KEY"),
  base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
)

# 도구 정의
tools = [
  {
    "type": "function",
    "function": {
      "name": "get_current_weather",
      "description": "Useful when you want to check the weather for a specific city.",
      "parameters": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "City or county, such as Singapore or New York.",
          }
        },
        "required": ["location"],
      },
    },
  },
]

# 도구 실행 시뮬레이션
def get_current_weather(arguments):
  weather_conditions = ["sunny", "cloudy", "rainy"]
  random_weather = random.choice(weather_conditions)
  location = arguments["location"]
  return f"{location} is {random_weather} today."

def get_response(messages):
  return client.chat.completions.create(
    model="qwen3.8-max",
    messages=messages,
    tools=tools,
  )

messages = [{"role": "user", "content": "What's the weather in Singapore?"}]
response = get_response(messages)
assistant_output = response.choices[0].message
if assistant_output.content is None:
  assistant_output.content = ""
messages.append(assistant_output)

# 도구가 필요 없으면 직접 답변
if assistant_output.tool_calls is None:
  print(f"No weather tool call needed. Direct reply: {assistant_output.content}")
else:
  # 도구 호출 루프
  while assistant_output.tool_calls is not None:
    tool_call = assistant_output.tool_calls[0]
    tool_call_id = tool_call.id
    func_name = tool_call.function.name
    arguments = json.loads(tool_call.function.arguments)
    print(f"Calling tool [{func_name}], parameters: {arguments}")
    tool_result = get_current_weather(arguments)
    tool_message = {
      "role": "tool",
      "tool_call_id": tool_call_id,
      "content": tool_result,
    }
    messages.append(tool_message)
    response = get_response(messages)
    assistant_output = response.choices[0].message
    if assistant_output.content is None:
      assistant_output.content = ""
    messages.append(assistant_output)
  print(f"Final assistant reply: {assistant_output.content}")

핵심은 이 두 가지예요. 첫째, 모델의 tool_calls가 실제 도구 호출 명령이고 tool_call_id로 결과를 연결해야 해요. 둘째, role: "tool" 메시지를 우회하지 말고 그대로 컨텍스트에 넣어 모델이 최종 자연어 답변을 합성하게 해야 합니다.

상용/네이티브 DashScope를 쓰는 경우 qwen3.8-maxMultiModalConversation 인터페이스를 쓰고, qwen-plus·qwen3-max 같은 모델은 Generation 인터페이스를 사용해요.

더 알아보기