5장. LLM 외부 도구 연동
Tool Calling 의 흐름
식당에서 손님이 주문하면, 직원은 직접 요리하지 않고 주문서에 "김치찌개 1, 공깃밥 2"라고 적어 주방에 넘깁니다. 주방이 요리를 내주면 직원이 그것을 손님 상에 올리며 한마디 덧붙입니다. Tool Calling 에서 모델은 주문서를 쓰는 직원입니다. 모델은 도구를 실행하지 않고, "이 도구를 이 값으로 불러 달라"는 주문서(tool_calls)만 돌려줍니다.
네 상자는 질문 하나에 일어나는 차례입니다. 질문과 도구 목록을 모델에 보내면(1), 모델이 글 답 대신 도구 호출 요청을 돌려주고(2), 우리 코드가 그 요청대로 함수를 실행해(3), 결과를 ToolMessage 로 돌려주며 모델을 한 번 더 부르면 최종 답이 나옵니다(4). 모델을 두 번 부른다는 점을 기억해 두세요. 여기서 주문서가 tool_calls, 주방이 우리 코드의 함수, 상에 올린 요리가 ToolMessage 입니다. 비유와 다른 점은, 주문서를 받은 주방은 늘 요리를 하지만 우리 코드는 요청을 검사해서 이상한 요청(없는 도구, 위험한 값)은 실행하지 않을 수 있다는 것입니다.
누가 무엇을 하나
| 하는 일 | 맡는 쪽 | 이 장에서 쓰는 것 |
|---|---|---|
| 도구 만들기(이름, 설명, 인자) | 우리 코드 | @tool |
| 도구 목록 알려 주기 | 우리 코드 | model.bind_tools([...]) |
| 부를 도구와 인자 고르기 | 모델 | AIMessage.tool_calls |
| 도구 실행하기 | 우리 코드 | 도구.invoke(...) |
| 결과 돌려주기 | 우리 코드 | ToolMessage |
| 결과를 읽고 답 쓰기 | 모델 | 둘째 model.invoke |
짧은 예시
모델이 돌려줄 법한 "도구 호출 요청"을 손으로 만들어, 그 안에 무엇이 들어 있는지 봅니다. 모델은 부르지 않습니다.
from langchain_core.messages import AIMessage
# 모델이 "calc_price 를 이 값으로 불러 달라"고 답했다고 보고 손으로 만든 메시지
ai = AIMessage(
content="", # 도구를 부를 때는 글 답이 비어 있는 일이 많다
tool_calls=[{"name": "calc_price", "args": {"code": "P01", "adults": 2, "children": 1}, "id": "call_1"}],
)
print("글 답:", repr(ai.content))
for call in ai.tool_calls:
print("도구:", call["name"], "| 인자:", call["args"], "| 요청 번호:", call["id"], "| 종류:", call["type"])글 답: ''
도구: calc_price | 인자: {'code': 'P01', 'adults': 2, 'children': 1} | 요청 번호: call_1 | 종류: tool_callcontent 는 빈 글자열이고, 할 일은 tool_calls 에 들어 있습니다. 요청 하나에 도구 이름, 인자 사전, 요청 번호가 있고, LangChain 이 type: "tool_call" 을 붙여 줍니다. 진짜 모델이 돌려주는 id 는 call_ 로 시작하는 긴 글자로, 부를 때마다 바뀝니다.
코드가 의미하는 것
이 코드는 식당 직원이 쓴 주문서 한 장을 손으로 만들어 펼쳐 보는 것입니다. 주문서를 만들고, 글 답 칸을 보고, 주문 줄마다 무엇이 적혔는지 읽는 세 동작입니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
AIMessage(content="", tool_calls=[...]) | 도구 호출 요청이 든 모델의 답을 만든다 | 진짜 모델이 도구를 부르기로 했을 때 돌려주는 메시지와 같은 꼴입니다. 실습 2에서도 이렇게 손으로 만들어 실행기를 시험합니다 |
{"name": ..., "args": ..., "id": ...} | 요청 하나에 도구 이름, 인자, 요청 번호를 적는다 | 주문서의 한 줄입니다. 모델이 고르는 것은 이 세 칸뿐이고, 함수를 실행하지는 않습니다 |
repr(ai.content) | 글 답을 따옴표째 찍는다 | 빈 글자열이 눈에 보이게 하려는 것입니다. 손님에게 보낼 말이 아직 없다는 뜻입니다 |
call["type"] | LangChain 이 붙인 종류 표시를 읽는다 | 우리가 적지 않은 tool_call 이 붙어 있습니다. LangChain 이 요청을 정해진 꼴로 맞춰 준 것입니다 |
결과에서 읽을 것은 두 가지입니다.
- 글 답이 비었습니다.
''은 모델이 "아직 답할 수 없고, 도구 결과가 먼저 필요하다"고 말한 것과 같습니다. 이 메시지를 받은 우리 코드가 다음 걸음을 이어 가야 합니다. - 요청 번호가 짝을 맞출 열쇠입니다.
call_1은 뒤의 "ToolMessage 로 결과 돌려주기" 절에서 결과 메시지에 그대로 옮겨 적힙니다.
주의할 점
tool_calls를 받았다고 끝난 것이 아닙니다. 우리 코드가 실행하고 결과를 돌려주지 않으면 손님은 답을 받지 못합니다.- 모델은 도구 이름과 인자를 틀리게 고를 수도 있습니다. 없는 도구 이름이나 잘못된 자료형의 인자가 오면 오류를 내지 말고 "오류: …"를 결과로 돌려주어 모델이 고쳐 부르게 합니다. 실습 2에서 해 봅니다.