[Tool Calling 이란?]
Tool Calling(도구 호출)은 LLM이 단순 텍스트 응답을 넘어서, 등록된 함수나 프로그램을 직접 실행하고 그 결과를 응답에 반영하는 기능이다. 즉, LLM이 사용자의 질문을 분석해 필요한 툴을 스스로 판단하고 실행하는 구조이다.
예시 질문필요한 툴
LLM에게 질문 | LLM이 선택한 필요한 툴 |
“서울 오늘 날씨 어때?” | 날씨 API |
“파스타 요리법 알려줘” | 위키백과 검색 도구 |
“삼성전자 주가 알려줘” | 주식 데이터 API |
“23 + 45는?” | 계산기 함수 |
“내 회사 문서에서 찾은 요약 보여줘” | 벡터 DB 툴 (RAG) |
- Tool Calling 흐름
Tool Calling 흐름
[User 입력]
↓
[LLM 판단: "이건 툴이 필요하군!"]
↓
[툴 이름 + 인자 → Tool 호출]
↓
[결과를 받아서 → 다시 자연어 응답 생성]
구성요소 | 역할 |
@tool 또는 .as_tool() | 툴 등록 |
args_schema | 툴이 요구하는 입력값 스키마 정의 |
description | 어떤 상황에서 이 툴을 쓸지 설명 |
llm.bind_tools() | LLM에게 툴들을 연결해주는 함수 |
ToolCall, ToolMessage | 툴 호출 요청 / 응답 메시지 포맷 |
[Tool 만들고 사용하는 방법]
방법 1. RunnableLambda(...).as_tool(...) 사용
- RunnableLambda는 일반 Python 함수를 LangChain에서 사용할 수 있도록 Runnable 객체로 감싸주는 기능입니다.
- RunnableConfig는 LangChain에서 어떤 Runnable 객체 (예: 체인, 툴, 람다 등)를 실행할 때, "이 실행은 어떻게 할 건지" 를 설정해주는 "실행 옵션" 객체입니다.
1. search_wiki: 툴의 로직 정의
def search_wiki(input_data: dict):
- 입력으로 {"query": "검색어"} 형태의 딕셔너리를 받아
- 한국어 위키백과에서 관련 문서 2개를 검색해서 반환합니다.
- WikipediaLoader를 사용한 기본적인 문서 검색 함수입니다.
2. WikiSearchSchema: 툴 인자 스키마
class WikiSearchSchema(BaseModel): query: str = Field(..., description="검색어")
- pydantic을 사용해서 툴에 전달할 인자를 명확하게 정의합니다.
- LLM이 툴을 호출할 때 어떤 키를 넣어야 하는지 알려주는 입력 명세 역할
3. RunnableLambda(...): 함수 → 체인 실행 객체로 래핑
runnable = RunnableLambda(search_wiki)
- LangChain의 체인 시스템에서 실행할 수 있도록 일반 함수를 감쌉니다.
- .invoke() 등 체인 관련 메서드가 사용 가능해집니다.
4. .as_tool(...): 툴 등록
wiki_tool = runnable.as_tool( name="wiki_search", description="위키백과 문서를 검색합니다.", args_schema=WikiSearchSchema )
- 툴의 이름, 설명, 입력 구조를 지정해서 LLM이 사용할 수 있게 등록합니다.
- 이 wiki_tool은 이후에 llm.bind_tools([wiki_tool])로 연결해 주면
GPT처럼 "필요할 때 직접 검색 툴을 호출"할 수 있게 됩니다.
전체소스
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel, Field
# WikipediaLoader를 사용하여 위키피디아 문서를 검색하는 함수
def wiki_search(input_data: dict) :
"""Search Wikipedia documents based on user input (query) and return k documents"""
query = input_data["query"]
wiki_loader = WikipediaLoader(query=query, load_max_docs=2, lang="ko")
wiki_docs = wiki_loader.load()
return wiki_docs
# 도구 호출에 사용할 입력 스키마 정의
class WikiSearchSchema(BaseModel):
"""Input schema for Wikipedia search."""
query: str = Field(..., description="검색어")
runnable = RunnableLambda(wiki_search)
wiki_tool = runnable.as_tool(
name="wiki_search",
description="위키백과 문서를 검색합니다.",
args_schema=WikiSearchSchema
)
wiki_tool.invoke({"query":"서울에 대해 알려줘"})
방법 2. @tool decorator 사용
@tool 데코레이터를 사용해 LLM이 호출할 수 있는 툴을 만든 것
전체소스
from langchain_core.tools import tool
from langchain_community.document_loaders import WikipediaLoader
from pydantic import BaseModel, Field
from typing import List
from langchain_core.documents import Document
# 입력 스키마 정의 (같이 재사용 가능!)
class WikiSearchSchema(BaseModel):
"""Input schema for Wikipedia search."""
query: str = Field(..., description="검색어")
# @tool 데코레이터로 툴 등록
@tool(args_schema=WikiSearchSchema)
def wiki_search(query: str) -> List[Document]:
"""Search Wikipedia documents based on a query."""
wiki_loader = WikipediaLoader(query=query, load_max_docs=2, lang="ko")
wiki_docs = wiki_loader.load()
return wiki_docs
wiki_search.invoke({"query":"서울에 대해 알려줘"})
3. Tool 도구 속성 보는 방법
print("자료형: ")
print(type(wiki_search))
print("-"*100)
print("name: ")
print(wiki_search.name)
print("-"*100)
print("description: ")
pprint(wiki_search.description)
print("-"*100)
print("schema: ")
pprint(wiki_search.args_schema.schema())
print("-"*100)
4. LLM에 tool 바인딩 방법 - bind_tools 사용
bind_tools는 LLM에게 "너는 이 도구들을 사용할 수 있어!" 하고 툴 목록을 등록해 주는 메서드입니다.
llm에 bind_toos를 객체를 invoke 하여 실행시키면 LLM이 필요한 툴을 판단하고 langchain_community.tools를 반환하는데 해당 tools안에 tool_calls는 실질적인 반환 tool을 확인할 수 있습니다. tool은 한 개 이상일 수 있습니다.
단계 | 설명 | content 값 | too_calls값 |
1단계 | LLM이 툴 필요하다고 판단함 | '' (비어있음) | ✅ 있음 |
2단계 | 툴 실행함 | (아직 없음) | — |
3단계 | 툴 결과와 함께 다시 LLM에 넣음 | ✅ 최종 응답 생김 | ❌ 없음 |
from langchain_ollama.chat_models import ChatOllama
from pprint import pprint
llm = ChatOllama(model= "llama3.1:latest")
llm_with_tools = llm.bind_tools(tools=[wiki_tool, wiki_search])
# 도구 호출이 필요한 LLM 호출을 수행
query = "서울에 대해 알려줘. "
ai_msg = llm_with_tools.invoke(query)
# LLM의 전체 출력 결과 출력
pprint(ai_msg)
print("-" * 100)
# 메시지 content 속성 (텍스트 출력)
pprint(ai_msg.content)
print("-" * 100)
# LLM이 호출한 도구 정보 출력
pprint(ai_msg.tool_calls)
print("-" * 100)
5. LLM에 tool_calls 결과 실행하기
LLM이 Tool Calling을 통해 도구를 호출했을 때 이름으로 나옵니다. 어떤 툴을 호출했는지 이름을 기준으로 분기해서 실행 시킬 수 있습니다.
tool_calls에 args를 툴에 전달하여 실행시킨다.
tool_call = ai_msg.tool_calls[0]
tool_args = tool_call["args"]
if tool_call["name"] == "wiki_search":
tool_result = wiki_search.invoke(tool_args) # args만 전달
else:
tool_result = wiki_tool.invoke(tool_args)
print(tool_result)
[LCEL체인을 Tool로 래핑 하여 사용하는 방법]
LCEL( LangChain Expression Language )이란 : LangChain에서 체인을 쉽게 연결하는 방법으로 한 줄로 프롬프트, 모델, 도구, 결과처리까지 쭉 이어서 연결할 수 있게 만든 LangChain 문법입니다.
툴 구성 함수 (LCEL 체인을 툴로 변환)
def wiki_search_and_summarize(input_data: dict): ...
- 입력된 query로 위키백과에서 2개 문서 검색
- 문서를 포맷팅 해서 반환
wiki_search_and_summarize 함수를 기반으로 아래 LCEL 체인을 구성합니다:
summary_chain = (
{"context": RunnableLambda(wiki_search_and_summarize)}
| summary_prompt
| llm
| StrOutputParser()
)
- 검색 결과 → 프롬프트에 삽입 → 요약 요청 → 문자열로 결과 파싱
이 체인을 .as_tool(...)을 통해 툴로 바꿉니다:
wiki_summary = summary_chain.as_tool(...)
- 이름, 설명, 입력 스키마가 포함된 툴 객체가 됩니다.
- 이제 LLM이 이 툴을 자동으로 호출할 수 있게 됨
user_input → LLM → 툴 결정(tool_calls) → 툴 실행 → 결과
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
from langchain_community.document_loaders import WikipediaLoader
from langchain_ollama.chat_models import ChatOllama
llm = ChatOllama(model= "llama3.1:latest")
# 도구 호출에 사용할 입력 스키마 정의
class WikiSummarySchma(BaseModel):
"""위키백과 검색을 위한 입력 스키마."""
query: str = Field(..., description="위키백과에서 검색할 쿼리")
def wiki_search_and_summarize(input_data:dict):
wiki_loader = WikipediaLoader(input_data["query"], load_max_docs=2, lang="ko")
wiki_docs = wiki_loader.load()
format_docs =[
f'<Document source="{doc.metadata["source"]}"/>\n{doc.page_content}\n</Document>'
for doc in wiki_docs
]
return format_docs
summary_prompt = ChatPromptTemplate.from_template(
"다음 텍스트를 요약해줘. :\n\n{context}\n\n요약:"
)
summary_chain = ({"context" : RunnableLambda(wiki_search_and_summarize)}
| summary_prompt | llm | StrOutputParser())
# summarized_text = summary_chain.invoke({"query":"서울에 대해 설명해줘"})
# pprint(summarized_text)
# as_tool 메소드를 사용하여 도구 객체로 변환
wiki_summary = summary_chain.as_tool(
name="wiki_summary",
description=dedent("""위키백과에서 정보를 검색해야 할 때 이 도구를 사용하세요.
사용자의 쿼리와 관련된 위키백과 문서를 검색하여 반환합니다
요약된 텍스트입니다. 이 도구는 일반적인 지식이 있을 때 유용합니다
또는 배경 정보가 필요합니다."""),
args_schema=WikiSummarySchma,
)
# wiki_summary.invoke({"query":"서울에 대해 설명해줘"})
# LLM에 도구를 바인딩
llm_with_tools = llm.bind_tools(tools=[wiki_summary])
query = "서울에 대해 설명해줘 "
ai_msg = llm_with_tools.invoke(query)
pprint(ai_msg.tool_calls)
tool_message = wiki_summary.invoke(ai_msg.tool_calls[0])
pprint(tool_message)
[LCEL체인을 @chain 데코레이터 사용]
- llm_chain.invoke(...) 첫 번째: 어떤 툴 쓸지 판단
- 툴 실행 결과 (tool_msgs)
- 그 결과들을 다시 넣어서 두 번째 llm_chain.invoke(...) 호출 → 최종 응답
user_input → LLM(tool 선택) → 툴 실행 → 툴 결과와 함께 다시 LLM 호출 → 최종 응답
from datetime import datetime
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig, chain
# 프롬프트 템플릿
prompt = ChatPromptTemplate([
("system", f"You are a helpful AI assistant."),
("human", "{user_input}"),
("placeholder", "{messages}"),
])
llm_width_tools = llm.bind_tools(tools=[wiki_summary])
llm_chain = prompt | llm_width_tools
@chain
def wiki_summary_chain(user_input:str, config: RunnableConfig):
input = {"user_input" : user_input}
ai_msg = llm_chain.invoke(input, config=config)
print("ai_msg: \n", ai_msg)
print("-"*100)
tool_msgs = wiki_summary.batch(ai_msg.tool_calls, config=config)
print("tool_msgs: \n", tool_msgs)
print("-"*100)
return llm_chain.invoke({**input, "messages": [ai_msg, *tool_msgs]}, config=config)
response = wiki_summary_chain.invoke("서울에 대해 설명해줘")
pprint(response)
'AI' 카테고리의 다른 글
[AI] RAG 기법 (0) | 2025.04.15 |
---|---|
[AI] LangChain - LangGraph ( State, Message ) (0) | 2025.04.08 |
[AI] 대형 언어 모델 파인튜닝 기법 정리 (SFT, PEFT, RLHF, DPO, RL) (1) | 2025.03.26 |
[AI] LoRA 기반 PEFT 파인튜닝과 용어정리 (1) | 2025.02.27 |
[AI] Huggingface모델 다운받아 Ollama에 올려서 RAG와 Memory사용하기 (0) | 2024.06.27 |