도구 사용

도구 사용 (Tool use)

채팅 모델은 흔히 "function-calling" 또는 "tool-use"를 지원하도록 학습돼요. 도구(tool)는 사용자가 제공하는 함수로, 모델이 응답의 일부로 호출하기로 선택할 수 있어요. 예를 들어 모델이 내부적으로 계산을 수행하지 않고도 계산기를 도구로 활용할 수 있지요.

출처: 문서

본문

이 가이드에서는 도구를 정의하는 방법, 채팅 모델에 도구를 전달하는 방법, 그리고 모델이 도구를 호출했을 때 그 출력을 처리하는 방법을 살펴볼게요.

도구 전달하기 (Passing tools)

모델이 tool-use를 지원하면 apply_chat_template()의 tools 인자에 함수를 전달해요. 도구는 JSON schema나 Python 함수로 전달돼요. Python 함수를 전달하면, 인자, 인자 타입, 함수 docstring이 파싱되어 JSON schema를 자동으로 생성해요.

Python 함수를 전달하는 건 매우 편리하지만, 파서는 Google 스타일 docstring만 처리할 수 있어요. 도구로 사용할 함수를 어떻게 작성하는지는 아래 예시를 참고해 주세요.

def get_current_temperature(location: str, unit: str):
    """
    Get the current temperature at a location.

    Args:
        location: The location to get the temperature for, in the format "City, Country"
        unit: The unit to return the temperature in. (choices: ["celsius", "fahrenheit"])
    """
    return 22.  # A real function should probably actually get the temperature!

def get_current_wind_speed(location: str):
    """
    Get the current wind speed in km/h at a given location.

    Args:
        location: The location to get the wind speed for, in the format "City, Country"
    """
    return 6.  # A real function should probably actually get the wind speed!

tools = [get_current_temperature, get_current_wind_speed]

선택적으로 docstring에 Returns: 블록을 추가하고 함수 헤더에 반환 타입을 넣을 수 있지만, 대부분의 모델은 이 정보를 쓰지 않아요. 파서는 함수 안의 실제 코드도 무시해요!

정말 중요한 것은 함수 이름, 인자 이름, 인자 타입, 그리고 함수의 목적과 인자들의 목적을 설명하는 docstring이에요. 이들이 모델이 도구를 호출할지 결정할 때 사용하는 "시그니처(signature)"를 만들어요.

도구 호출 예시 (Tool-calling Example)

NousResearch/Hermes-2-Pro-Llama-3-8B처럼 tool-use를 지원하는 모델과 tokenizer를 로드해요. 하드웨어가 받쳐준다면 Command-R이나 Mixtral-8x22B 같은 더 큰 모델도 고려할 수 있어요.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

checkpoint = "NousResearch/Hermes-2-Pro-Llama-3-8B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, dtype="auto", device_map="auto")

채팅 기록을 만들어요.

messages = [
  {"role": "system", "content": "You are a bot that responds to weather queries. You should reply with the unit used in the queried location."},
  {"role": "user", "content": "Hey, what's the temperature in Paris right now?"}
]

다음으로 apply_chat_template()에 messages와 도구 리스트를 전달해요. 채팅을 토큰화하고 응답을 생성해요.

inputs = tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
outputs = model.generate(**inputs.to(model.device), max_new_tokens=128)
print(tokenizer.decode(outputs[0][len(inputs["input_ids"][0]):]))
<tool_call>
{"arguments": {"location": "Paris, France", "unit": "celsius"}, "name": "get_current_temperature"}
</tool_call><|im_end|>

채팅 모델이 docstring의 올바른 파라미터로 get_current_temperature 도구를 호출했어요. 모델은 Paris를 보고 France를 위치로 추론했고, 온도 단위로 Celsius를 써야 한다고 판단했지요.

모델은 실제로 도구 자체를 호출할 수는 없어요. 도구 호출을 요청할 뿐이고, 그 호출을 처리하고 호출과 결과를 채팅 기록에 추가하는 건 여러분의 몫이에요. 응답 파싱을 지원하는 모델의 경우 응답 파싱이 자동으로 처리되므로, parse_response()만 사용해서 도구 호출을 추출하면 돼요. 다른 모델에서는 출력 문자열을 수동으로 도구 호출 dict로 변환해야 해요.

어떤 방법을 쓰든 도구 호출은 assistant 메시지의 tool_calls 키에 들어가야 해요. 이는 권장 API이며 대부분의 도구 사용 모델의 채팅 템플릿이 지원해야 해요.

[!WARNING] tool_calls는 OpenAI API와 비슷하지만, OpenAI API는 tool_calls 형식으로 JSON 문자열을 사용해요. dict를 기대하는 Transformers에서 쓰면 오류나 이상한 모델 동작이 생길 수 있어요.

tool_call = {"name": "get_current_temperature", "arguments": {"location": "Paris, France", "unit": "celsius"}}
messages.append({"role": "assistant", "tool_calls": [{"type": "function", "function": tool_call}]})

도구 응답을 tool 역할로 채팅 기록에 추가해요.

messages.append({"role": "tool", "content": "22"})  # Note that the returned content is always a string!

마지막으로 모델이 도구 응답을 읽고 사용자에게 답하게 해요.

inputs = tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True, return_dict=True, return_tensors="pt")
out = model.generate(**inputs.to(model.device), max_new_tokens=128)
print(tokenizer.decode(out[0][len(inputs["input_ids"][0]):]))
The temperature in Paris, France right now is 22°C.<|im_end|>

[!WARNING] assistant 메시지의 키 이름이 tool_calls이지만, 대부분의 경우 모델은 한 번에 단일 도구 호출만 내보내요. 일부 오래된 모델은 동시에 여러 도구 호출을 내보내기도 하지만, 이는 훨씬 더 복잡한 과정이에요. 여러 도구 응답을 한 번에 처리하고, 흔히 도구 호출 ID를 사용해 그것들을 구분해야 하기 때문이지요. 모델이 도구 호출에 대해 정확히 어떤 형식을 기대하는지는 모델 카드를 참고해 주세요.

JSON 스키마 (JSON schemas)

도구를 정의하는 또 다른 방법은 JSON schema를 전달하는 거예요.

Python 함수를 JSON schema로 변환하는 로우레벨 함수를 수동으로 호출하고, 생성된 스키마를 확인하거나 편집할 수도 있어요. 보통은 필요 없지만, 내부 메커니즘을 이해하는 데 유용해요. 특히 도구 정의를 렌더링하려면 JSON schema에 접근해야 하는 채팅 템플릿 작성자에게 중요해요.

apply_chat_template() 메서드는 get_json_schema 함수를 사용해서 Python callable을 JSON schema로 변환해요. 메서드도 포함되는데, self와 cls는 묵시적 수신자 인자로 취급되어 무시돼요.

from transformers.utils import get_json_schema

def multiply(a: float, b: float):
    """
    A function that multiplies two numbers

    Args:
        a: The first number to multiply
        b: The second number to multiply
    """
    return a * b

schema = get_json_schema(multiply)
print(schema)
{
  "type": "function",
  "function": {
    "name": "multiply",
    "description": "A function that multiplies two numbers",
    "parameters": {
      "type": "object",
      "properties": {
        "a": {
          "type": "number",
          "description": "The first number to multiply"
        },
        "b": {
          "type": "number",
          "description": "The second number to multiply"
        }
      },
      "required": ["a", "b"]
    }
  }
}

JSON schema 자체의 상세 내용은 이미 매우 잘 문서화되어 있으므로 여기서 다루지 않을게요. 다만, apply_chat_template()의 tools 인자에 Python 함수 대신 JSON schema dict를 전달할 수 있다는 점은 언급할게요:

# A simple function that takes no arguments
current_time = {
  "type": "function",
  "function": {
    "name": "current_time",
    "description": "Get the current local time as a string.",
    "parameters": {
      'type': 'object',
      'properties': {}
    }
  }
}

# A more complete function that takes two numerical arguments
multiply = {
  'type': 'function',
  'function': {
    'name': 'multiply',
    'description': 'A function that multiplies two numbers',
    'parameters': {
      'type': 'object',
      'properties': {
        'a': {
          'type': 'number',
          'description': 'The first number to multiply'
        },
        'b': {
          'type': 'number', 'description': 'The second number to multiply'
        }
      },
      'required': ['a', 'b']
    }
  }
}

model_input = tokenizer.apply_chat_template(
    messages,
    tools = [current_time, multiply]
)

더 알아보기 (Learn more)