도구 사용

도구 사용(종종 함수 호출이라고 불림)은 언어 모델이 자신의 텍스트 상자를 벗어나는 방법입니다: 모델이 실행할 수 있는 함수 목록을 제공하고, 모델이 언제 사용할지 결정합니다. 모델은 절대 직접 실행하지 않으며, 호출을 요청할 뿐이고, 당신의 코드가 실제 작업을 수행합니다.
모델 자체는 상자 안에 갇혀있습니다. 오늘의 날씨를 확인하거나, 데이터베이스에서 사용자를 조회하거나, 이메일을 보낼 수 없습니다. 학습 시점에 동결된 것에서만 텍스트를 예측할 뿐으로, LLM의 작동 방식의 동일한 그림입니다. 도구 사용은 상자 밖으로 나가도록 하는 방법이며, 에이전트가 구축되는 기반입니다.
기억할 핵심 단어는 "요청"입니다. 모델이 어떤 함수를 원하는지, 어떤 인수를 사용하는지 알려줍니다. 당신의 코드가 실행하고 결과를 돌려줍니다. 당신은 항상 제어권을 유지합니다.
루프
도구 사용은 단일 호출이 아닌 **왕복**입니다:
- 사용자의 메시지를 모델이 사용할 수 있는 도구 목록과 함께 보냅니다.
- 모델이 두 가지 방식 중 하나로 응답합니다: 일반 답변, 또는 도구 호출 요청(원하는 인수 포함).
- 도구를 요청했다면, 당신의 코드가 그 함수를 실행하고 결과를 모델로 다시 보냅니다.
- 모델이 결과를 사용하여 최종 답변을 작성합니다.
이것이 전체 패턴입니다. 모델이 요청하고, 당신의 코드가 실행합니다. 단계 2와 3은 모델이 여러 도구가 필요한 경우 반복할 수 있으며, 이것이 몇 장 뒤의 에이전트의 씨앗입니다.
실제로 일어나는 것
도구 사용은 모델이 새로운 힘을 얻은 것처럼 느껴질 수 있으며, 그렇지 않았습니다. 내부적으로, 모델은 항상 하는 한 가지를 하고 있습니다: 텍스트를 예측합니다. 그것이 사는 상자에 대해 아무것도 변하지 않았습니다.
요청에 도구 정의를 포함하면, 모델이 예측하는 컨텍스트에 추가합니다. 모델은 이러한 도구가 주어졌을 때, 올바른 계속은 때때로 "Oslo의 get_weather 호출"을 의미하는 특별히 형식된 메시지가 아닌 일반 문장인 예시로 훈련되었습니다. 따라서 당신의 질문이 도구를 유용해 보이게 할 때, 가장 가능성 높은 다음 출력은 그 구조화된 **도구 호출 메시지**이며, API는 이를 tool_calls로 표면화합니다.
모델이 나가서 뭔가를 실행한 것이 아닙니다. 도구 호출이 올바른 움직임이라고 예측하고 당신의 코드가 읽는 방법을 아는 형식으로 호출 요청을 작성했습니다.
그러면 모델이 아닌 당신이 함수를 실행합니다. 실제 결과를 가져와 메시지에 더 많은 컨텍스트로 넣고, 모델이 그 확대된 컨텍스트에서 최종 답변을 예측합니다. 전체 기능은 당신의 코드가 중간의 실제 작업을 하는 두 가지 일반적인 예측입니다.
당신이 모델의 텍스트 상자와 현실 세계 사이의 다리입니다. 그 그림을 유지하고 장의 나머지, 에이전트 포함이 더 이상 신비로워 보이지 않습니다: AI가 취하는 모든 행동은 당신의 코드가 수행하기로 선택하는 예측된 요청입니다.
tool_calls로 반환합니다. 모델은 절대 아무것도 실행하지 않으며, 요청을 예측합니다. 당신의 코드가 함수를 실행하고 결과를 다음 예측을 위해 더 많은 컨텍스트로 피드백하므로, 당신이 모델과 현실 세계 사이의 다리입니다. 도구 정의
각 도구를 모델에 설명합니다: 이름, 기능, 사용하는 인수. 설명은 당신을 위한 문서가 아니라, 언제 도구를 사용하는지에 대한 모델 지시이므로 프롬프트처럼 작성합니다.
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "현재 도시의 날씨를 가져옵니다. 사용자가 날씨에 대해 물을 때 사용합니다.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "도시 이름, 예: 'Seoul'"},
},
"required": ["city"],
},
},
},
]parameters 블록은 **스키마**로, 구조화된 출력과 동일한 아이디어입니다: 모델이 생성해야 하는 인수를 정의합니다. 모델이 get_weather 호출을 결정하면, 이 형태와 일치하는 city 인수를 다시 보냅니다. 모호한 설명("날씨를 가져옵니다")은 모델이 잘못된 순간에 도구를 사용하도록 합니다. 명확한 것("사용자가 날씨에 대해 물을 때 사용합니다")은 잘 안내합니다.
parameters 스키마를 가집니다. 설명은 정말 프롬프트입니다: 도구를 언제 호출할지 결정하도록 모델에 알려주므로, 그것처럼 작성합니다. parameters 스키마는 구조화된 출력과 동일한 메커니즘으로, 모델이 보내는 인수를 제약합니다. 호출 처리
모델이 도구를 원하면, 응답이 최종 답변 대신 tool_calls를 포함합니다. 요청된 함수와 인수를 읽고, 실제 함수를 실행하고, 결과를 tool 메시지로 다시 보냅니다. 그러면 모델을 다시 호출하여 완료할 수 있습니다.
import json
# 도구의 실제 구현
def get_weather(city):
# 실제 앱에서는 날씨 API를 호출합니다; 여기서는 가짜입니다
return {"city": city, "tempC": 18, "condition": "cloudy"}
messages = [{"role": "user", "content": "Seoul의 날씨는 어떨까요?"}]
# 1. 첫 호출: 도구 제공
response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
tool_calls = response.choices[0].message.tool_calls
tool_call = tool_calls[0] if tool_calls else None
if tool_call:
# 2. 모델의 인수로 요청된 함수 실행
args = json.loads(tool_call.function.arguments)
result = get_weather(args["city"])
# 3. 모델의 요청과 당신의 결과를 다시 보냅니다
messages.append(response.choices[0].message) # 어시스턴트의 도구 요청
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result),
})
# 4. 모델이 결과를 사용하여 답변할 수 있도록 다시 호출
response = client.chat.completions.create(model=MODEL, messages=messages)
print(response.choices[0].message.content)
# "Seoul은 현재 18도이고 흐립니다."인수는 **JSON 문자열**로 도착하므로, json.loads를 사용하여 실제 객체로 변환한 다음, 사용하기 전에 검사합니다. 히스토리에 두 메시지를 추가합니다: 모델의 도구 요청과 당신의 tool 결과, tool_call_id로 연결된. 최종 호출은 원시 날씨 데이터를 자연스러운 문장으로 바꿉니다.
tool_calls를 전달합니다. JSON 문자열에서 인수를 구문 분석하고, 실제 함수를 실행하고, 일치하는 tool_call_id로 태그된 두 메시지를 다시 푸시합니다: 모델의 요청과 당신의 결과. 최종 모델 호출이 원시 결과를 자연스러운 답변으로 바꿉니다. 실제로
재사용 가능한 함수와 동일한 흐름. 모델과 당신의 시스템 사이에 있는 유일한 것은 당신이 작성하고 제어하는 코드입니다:
import json
tool_impls = {"get_weather": lambda args: get_weather(args["city"])}
def answer_with_tools(user_text):
messages = [{"role": "user", "content": user_text}]
response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
calls = response.choices[0].message.tool_calls
if not calls:
return response.choices[0].message.content # 모델이 직접 답변함
call = calls[0]
args = json.loads(call.function.arguments)
result = tool_impls[call.function.name](args)
messages.append(response.choices[0].message)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
response = client.chat.completions.create(model=MODEL, messages=messages)
return response.choices[0].message.content이것이 하나의 도구 호출을 처리합니다. 모델은 또한 한 번에 여러 개를 요청할 수 있으며, **병렬 도구 호출**이라고 불리며, tool_calls의 모든 항목을 루핑하여 처리합니다. 그리고 모델이 도구를 호출하는 루프에서 그것을 유지하면, 매 턴마다 다음 단계를 결정하고, 당신은 에이전트를 얻으며, 정확히 다음 장이 가는 곳입니다.

