5장. LLM 외부 도구 연동
bind_tools 로 도구 목록 건네기
식당 직원에게 "주방에서 되는 메뉴"를 적은 메뉴판을 건네 두면, 직원은 손님 주문을 메뉴판에 있는 이름으로만 적습니다. 메뉴판에 없는 요리는 주문서에 쓰지 않습니다. bind_tools 는 모델에게 이 메뉴판을 묶어 두는 일입니다. 묶어 두면 요청마다 도구 설명서가 질문과 함께 모델에게 갑니다.
세 상자는 model.bind_tools([find_products, calc_price]) 한 줄이 하는 일입니다. 도구 목록을 받아(1), 도구마다 이름, 설명, 인자를 JSON 스키마(4장에서 본 설계도)로 바꾸고(2), 그 뒤로 이 모델을 부를 때마다 설명서를 함께 보냅니다(3). 여기서 메뉴판이 도구 설명서, 메뉴판을 든 직원이 bind_tools 로 만든 모델입니다. 비유와 다른 점은, 메뉴판이 길수록 직원은 그냥 들고 다니면 되지만 모델은 설명서도 토큰으로 읽으므로 도구가 많을수록 입력 토큰과 요금이 늘어난다는 것입니다.
bind_tools 에 넘기는 것
| 인자 | 원래 말 | 뜻 | 이 장의 값 |
|---|---|---|---|
| 첫째 인자 | tools | 도구 목록 | [find_products, calc_price] |
tool_choice | 도구 고르기 | "auto"(모델이 고른다), "required"(반드시 하나는 부른다), 도구 이름(그 도구를 부른다) | 주지 않는다(모델이 고른다) |
parallel_tool_calls | 나란한 도구 호출 | 한 번에 여러 도구 호출을 허락할지 | 주지 않는다(허락) |
짧은 예시
bind_tools 가 모델에게 보내는 설명서를 convert_to_openai_tool 로 미리 들여다봅니다. 그리고 가짜 모델에는 도구를 묶을 수 없다는 것도 확인합니다.
import json
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.tools import tool
from langchain_core.utils.function_calling import convert_to_openai_tool
@tool
def find_products(region: str, max_price: int = 0) -> str:
"""구름다리여행 상품을 지역으로 찾는다. max_price 는 어른 1명 가격의 상한(만 원)이고 0 이면 따지지 않는다."""
return "찾은 상품"
# convert_to_openai_tool: 도구를 OpenAI 에 보내는 설명서(사전)로 바꾼다
spec = convert_to_openai_tool(find_products)
fn = spec["function"]
print("type:", spec["type"])
print("name:", fn["name"])
print("description:", fn["description"])
print("parameters:", json.dumps(fn["parameters"], ensure_ascii=False))
try:
# 1차시의 가짜 모델은 도구 묶기를 지원하지 않는다
FakeListChatModel(responses=["안녕"]).bind_tools([find_products])
except NotImplementedError:
print("가짜 모델: bind_tools 를 지원하지 않음(NotImplementedError)")type: function
name: find_products
description: 구름다리여행 상품을 지역으로 찾는다. max_price 는 어른 1명 가격의 상한(만 원)이고 0 이면 따지지 않는다.
parameters: {"properties": {"region": {"type": "string"}, "max_price": {"default": 0, "type": "integer"}}, "required": ["region"], "type": "object"}
가짜 모델: bind_tools 를 지원하지 않음(NotImplementedError)설명서에는 type: "function" 아래에 이름, 설명, 인자(parameters)가 들어 있습니다. required 에 region 만 있는 것은 max_price 에 기본값을 주었기 때문입니다. 모델이 보는 것은 이 사전뿐이고, 함수 안의 코드는 보지 못합니다. 가짜 모델은 도구를 묶을 수 없으므로, 진짜 모델로 도구를 부르는 것은 실습 3에서 합니다.
코드가 의미하는 것
이 코드는 직원에게 건넬 메뉴판을 미리 인쇄해 보는 것입니다. 도구 하나를 설명서 사전으로 바꿔 칸마다 찍고, 메뉴판을 받을 수 없는 가짜 직원(가짜 모델)에게도 건네 봅니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
convert_to_openai_tool(find_products) | 도구를 OpenAI 에 보내는 설명서 사전으로 바꾼다 | bind_tools 가 모델에게 보내는 설명서를 미리 꺼내 보는 것입니다. 모델 서비스가 받는 메뉴판의 실제 모양입니다 |
spec["function"]["parameters"] | 인자 스키마를 꺼낸다 | 4장에서 본 JSON 스키마와 같은 꼴입니다. 모델은 이 설계도에 맞춰 인자 사전을 채웁니다 |
json.dumps(..., ensure_ascii=False) | 사전을 JSON 글로 찍는다 | 서비스로 오가는 것이 이런 JSON 글이라는 것을 보여 줍니다 |
FakeListChatModel(...).bind_tools(...) 와 except NotImplementedError | 가짜 모델에 도구를 묶어 보고 오류를 받는다 | 도구 묶기는 모델 쪽이 Tool Calling 을 지원해야 됩니다. 키 없이 도는 이 장의 예시가 AIMessage 를 손으로 만드는 까닭입니다 |
결과에서 읽을 것은 두 가지입니다.
- 꼭 넣을 인자는 기본값으로 정해집니다.
required에는region만 있고, 기본값 0 을 준max_price는 빠졌습니다. 모델은 지역만 알아도 이 도구를 부를 수 있습니다. - 메뉴판에는 요리법이 없습니다. 설명서에는
return "찾은 상품"같은 함수 몸통이 없습니다. 모델은 이름, 설명, 인자만 보고 고르므로 설명이 곧 도구의 얼굴입니다.
주의할 점
bind_tools는 모델을 바꾸지 않고 "도구를 묶은 새 모델"을 돌려줍니다.model_with_tools = model.bind_tools(tools)처럼 새 이름에 받아 씁니다.- 도구 이름은 영어 글자, 숫자, 밑줄로 짓습니다. 한글이나 빈칸이 든 이름은 모델 서비스가 받지 않을 수 있습니다.
- 3장의 체인처럼
prompt | model_with_tools로 이어 쓸 수 있습니다. 다만 결과가 글이 아니라tool_calls가 든AIMessage일 수 있으므로 끝에StrOutputParser()를 바로 붙이지 않습니다.