LangChain 챗봇5장 · LLM 외부 도구 연동6 / 21← 이전목차다음 →MetaContents

5장. LLM 외부 도구 연동

bind_tools 로 도구 목록 건네기

식당 직원에게 "주방에서 되는 메뉴"를 적은 메뉴판을 건네 두면, 직원은 손님 주문을 메뉴판에 있는 이름으로만 적습니다. 메뉴판에 없는 요리는 주문서에 쓰지 않습니다. bind_tools 는 모델에게 이 메뉴판을 묶어 두는 일입니다. 묶어 두면 요청마다 도구 설명서가 질문과 함께 모델에게 갑니다.

도구 목록, JSON 스키마의 설명서로 바꾸기, 요청마다 질문과 함께 보내기의 세 걸음을 이은 흐름도

세 상자는 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 로 미리 들여다봅니다. 그리고 가짜 모델에는 도구를 묶을 수 없다는 것도 확인합니다.

Python
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() 를 바로 붙이지 않습니다.
← 이전 절5장 · @tool 로 도구 만들기다음 절 →5장 · ToolMessage 로 결과 돌려주기