LLM Tool Call ๊ณต๋ถ
Chain ์์
์ ์ฒด ์ฝ๋
from datetime import datetime
from langchain_core.prompts import ChatPromptTemplace
from langchain_core.runnables import RunnableConfig, chain
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
import pprint
# ์ค๋ ๋ ์ง ์ค์
today = datetime.today().strftime("%Y-%m-%d")
# ํ๋กฌํํธ ํ
ํ๋ฆฟ
prompt = ChatPromptTemplace([
("system", f"You are a helpful AI assistant. Today's date is {today}"),
("human", "{user_input}"),
("placeholder", "{messages}"),
])
# ChatOpenAI ๋ชจ๋ธ ์ด๊ธฐํ
llm = ChatOpenAI(model="gpt-4o-mini")
# Tavily Search ๋๊ตฌ ์ด๊ธฐํ (์ต๋ 2๊ฐ์ ๊ฒฐ๊ณผ ๋ฐํ)
web_search = TavilySearchResults(max_results=2)
# LLM์ ๋๊ตฌ๋ฅผ ๋ฐ์ธ๋ฉ
llm_with_tools = llm.bind_tools(tools=[web_search])
# LLM ์ฒด์ธ ์์ฑ
llm_chain = prompt | llm_with_tools
# ๋๊ตฌ ์คํ ์ฒด์ธ ์ ์
@chain
def web_search_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 = web_search.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 = web_search_chain.invoke("์ค๋ ๋ชจ์ฃ์น๋ ์ดํ์ธ์ ๊ฐ๊ฒฉ์ ์ผ๋ง์ธ๊ฐ์?")
# ์๋ต ์ถ๋ ฅ
pprint(response.content)โ 1. ํ๋กฌํํธ ํ ํ๋ฆฟ ์ ์
prompt = ChatPromptTemplate([
("system", f"You are a helpful AI assistant. Today's date is {today}"),
("human", "{user_input}"),
("placeholder", "{messages}"),
])- LangChain์
ChatPromptTemplate์ ์ฌ์ฉํด multi-message ํ๋กฌํํธ๋ฅผ ์ ์. "system"์ญํ ๋ก ์ค๋ ๋ ์ง ํฌํจํ ์์คํ ๋ฉ์์ง๋ฅผ ์ ๊ณต."human"์ญํ ๋ก ์ ์ ์ ๋ ฅ์ ๋ฐ์."placeholder"๋ LLM์ ์๋ต์ด๋ ๋๊ตฌ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ์ถ๊ฐ๋ก ๋ผ์๋ฃ๊ธฐ ์ํด ๋น์๋ .
โ 2. LLM ๋ฐ ๋๊ตฌ ๋ฐ์ธ๋ฉ
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools=[web_search])- OpenAI์
gpt-4o-mini๋ชจ๋ธ์ ์ฌ์ฉ. - ์ฌ๊ธฐ์
web_search๋๊ตฌ๋ฅผ ๋ฐ์ธ๋ฉํด์ ๋ชจ๋ธ์ด ๋์ค์"tool_call"์ ํ ์ ์๋๋ก ์ค์ .
โ 3. LLM ์ฒด์ธ ๊ตฌ์ฑ
llm_chain = prompt | llm_with_tools- ์์ ๋ง๋ ํ๋กฌํํธ์ ๋๊ตฌ๊ฐ ๋ฐ์ธ๋ฉ๋ LLM์ ํ์ดํ๋ผ์ธ ํํ๋ก ์ฐ๊ฒฐํด์ ํ๋์ ์ฒด์ธ์ผ๋ก ๋ง๋ฆ.
- ์ด ์ฒด์ธ์ ์คํํ๋ฉด, ํ๋กฌํํธ โ ๋ชจ๋ธ โ ๋๊ตฌ ํธ์ถ ๊ฐ๋ฅ ์ํ๊น์ง ์ด์ด์ง.
โ 4. ๋๊ตฌ ์คํ์ ํฌํจํ ์ ์ฒด ์ฒด์ธ
@chain
def web_search_chain(user_input: str, config: RunnableConfig):
input_ = {"user_input": user_input}
ai_msg = llm_chain.invoke(input_, config=config)
tool_msgs = web_search.batch(ai_msg.tool_calls, config=config)
return llm_chain.invoke({**input_, "messages": [ai_msg, *tool_msgs]}, config=config)- ์ ์ ์ ์ง๋ฌธ์ ๋ฐ์ ์ฒด์ธ์ ์คํํ๋ ๋ํผ ํจ์.
- 1๋จ๊ณ: LLM์ด ๋จผ์ ์ง๋ฌธ์ ๋ํด tool ํธ์ถ์ด ํ์ํ๋ค๊ณ ํ๋จํ๋ฉด,
ai_msg.tool_calls๋ฅผ ํฌํจํ ์๋ต์ ์์ฑ. - 2๋จ๊ณ:
web_search.batch(...)๋ฅผ ํตํด tool call๋ค์ ์ค์ ๋ก ์คํ. - 3๋จ๊ณ: ์คํ๋ tool ๊ฒฐ๊ณผ(
tool_msgs)๋ฅผ ๋ค์ ํ๋กฌํํธ์ ๋ฃ์ด LLM์๊ฒ ์ต์ข ์๋ต์ ์์ฑํ๋๋ก ํจ.
โ 5. ์คํ ๋ฐ ๊ฒฐ๊ณผ ์ถ๋ ฅ
response = web_search_chain.invoke("์ค๋ ๋ชจ์ฃ์น๋ ์ดํ์ธ์ ๊ฐ๊ฒฉ์ ์ผ๋ง์ธ๊ฐ์?")
pprint(response.content)"์ค๋ ๋ชจ์ฃ์น๋ ์ดํ์ธ์ ๊ฐ๊ฒฉ"์ด๋ผ๋ ์ง๋ฌธ์ ์ ๋ ฅ์ผ๋ก ์ฃผ๊ณ ์ฒด์ธ ์ ์ฒด ์คํ.- LLM์ด ๋๊ตฌ ์ฌ์ฉ์ ์ ์ํ๊ณ โ ๋๊ตฌ ์คํ โ ๋๊ตฌ ๊ฒฐ๊ณผ ํฌํจํ์ฌ ์ต์ข ์๋ต ์์ฑ โ ์ถ๋ ฅ.
๐ก ์์ฝ
์ด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ๋์ํจ:
- ์ ์ ์ง๋ฌธ ์ ๋ ฅ
- LLM์ด ํ๋จ โ ๋๊ตฌ ํธ์ถ ํ์ํจ์ ๊ฐ์ง (LLM 1์ฐจ ํธ์ถ)
- ๋๊ตฌ ํธ์ถ ์ ์ (tool_calls)
- ์ ์๋ ๋๊ตฌ ์ค์ ๋ก ์คํ (
web_search.batch) (๋ง์ฝ, ๋๊ตฌ ์์ฒด๊ฐ ๋ด๋ถ์ ์ผ๋ก ๋ LLM์ ํธ์ถํ๋ค๋ฉด, ์ฌ๊ธฐ์๋ ์กฐ๊ฑด๋ถ ํธ์ถ) - ๋๊ตฌ ๊ฒฐ๊ณผ์ ํจ๊ป ๋ค์ ํ๋กฌํํธ์ ๋ฃ์ด์ ์ต์ข ์๋ต ์์ฑ (LLM 2์ฐจ ํธ์ถ)
- ์๋ต ์ถ๋ ฅ
@tool vs. Runnable.as_tool()
1. @tool decorator ๋ฐฉ์
- โ๊ฐํธํ๊ณ ๋น ๋ฅด๊ฒโ ์ฌ์ฉ์ ์ ์ Tool์ ๋ง๋ค๊ธฐ ์ํ ๊ณ ์ ์ ๋ฐฉ๋ฒ.
- ํจ์์ ์ง์
@tool์ ๋ฌ์์, LangChain Tool ๊ฐ์ฒด๋ก ์๋ ๋ณํ. - ๋ด๋ถ์ ์ผ๋ก๋
StructuredTool๋๋Toolํด๋์ค๋ฅผ ์์ฑํ๋ ํธ์ ๊ธฐ๋ฅ. - Signature(ํจ์ ์๊ทธ๋์ฒ) ๋ฅผ ์๋ ํ์ ํด์ ํ๋ผ๋ฏธํฐ ์คํค๋ง๋ฅผ ๋ง๋ค์ด์ค.
from langchain.tools import tool
@tool
def add_numbers(a: int, b: int) -> int:
"""๋ ์๋ฅผ ๋ํ๋ ๋๊ตฌ."""
return a + b ์ด๊ฑธ ์ฐ๋ฉด ๋ฐ๋ก Tool ๊ฐ์ฒด์ฒ๋ผ ์ธ ์ ์๊ณ , ๋ฑ๋ก๋ ๋ฐ๋ก ๊ฐ๋ฅ.
2. Runnable์ .as_tool()๋ก ๋ณํํ๋ ๋ฐฉ์
- โํจ์ฌ ๋ ์ ์ฐํ๊ณ ๋ณตํฉ์ ์ธโ ๋์์ ๋ค๋ฃจ๊ธฐ ์ํด ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ.
Runnable์ LangChain v0.1 ์ดํ ํต์ฌ ์ํคํ ์ฒ๋ก streaming, batch, composability ๊ฐ์ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ๋ค๋ฃจ๊ธฐ ์ํด ๋ง๋ค์ด์ง ํ๋ ์์ํฌ.Runnable๊ฐ์ฒด๋ ์๋ chain, graph, model call, custom logic ์ ๋ถ ๊ฐ์ ์ ์์..as_tool()์ ํธ์ถํ๋ฉด ์ด ๋ณต์กํRunnable๊ฐ์ฒด๋ฅผ Tool ์ธํฐํ์ด์ค์ ๋ง๊ฒ โํฌ์ฅโ ํ๋ ๊ฒ.
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel, Field
def add_fn(inputs: dict) -> int:
"""๋ ์๋ฅผ ๋ํด์ ๋ฐํํ๋ ๋๊ตฌ"""
return inputs["a"] + inputs["b"]
class add_fn_Schema(BaseModel):
"""Input schema for add_fn."""
a: int = Field(..., description="Number 1 for addition")
b: int = Field(..., description="Number 2 for addition")
runnable = RunnableLambda(add_fn)
tool = runnable.as_tool(
name="add_numbers",
description="๋ ์๋ฅผ ๋ํ๋ ๋๊ตฌ",
args_schema=add_fn_Schema,
) ์ด๋ ๊ฒ ํ๋ฉด Runnable์ ๊ฐ์ง ์ํ์์๋ Tool์ฒ๋ผ ์ธ ์ ์๊ฒ ๋ณํ.
Tip
args_schema ์ธ์์ class๋ก ์ ์ํ
add_fn_Schema๋ฅผ ๋ฃ์ด์ฃผ์๋๋ฐ, ์ด๋ ํ์๋ ์๋์ง๋ง, ์ด๊ฒ๋ค ๋ํ prompt์ ์ ๋ ฅ๋๋ ์ ๋ณด์ด๋ฏ๋ก, ์ข์ ์ฑ๋ฅ์ ์ํด์๋ผ๋ฉด ์ ์ํด์ ๋ฃ์ด์ฃผ์.
์์ฝ ๋น๊ต
| ํญ๋ชฉ | @tool ๋ฐฉ์ | Runnable.as_tool() ๋ฐฉ์ |
|---|---|---|
| ์ฃผ ์ฉ๋ | ๋จ์ ํจ์ Toolํ | ๋ณต์กํ Runnable โ Tool ๋ณํ |
| ์ ์ฐ์ฑ | ์ ํ์ (ํจ์ ๊ธฐ๋ฐ) | ๋งค์ฐ ๋์ (๋ชจ๋ Runnable ๊ฐ๋ฅ) |
| ๋ด๋ถ ์ฒ๋ฆฌ | StructuredTool ์์ฑ | RunnableTool ์์ฑ |
| ์ฌ์ฉ ๋์ | ์์ Python ํจ์ | Runnable ๊ฐ์ฒด ์ ์ฒด |
| ํน์ง | ๊ฐํธํ๊ณ ๋น ๋ฆ | ์ ์ฐํ๊ณ ๊ฐ๋ ฅ |
Tip
- LangChain ์ต์ ๋ฒ์ ์์๋ ๊ฐ๋ฅํ๋ฉด Runnable ๊ธฐ๋ฐ์ ๊ถ์ฅํ๋ ํ๋ฆ.
- ์ด์ ๋ LangGraph ๊ฐ์ ๋ฉํฐ์คํ ํ๋ก์ฐ๋ ์คํธ๋ฆฌ๋ฐ ์ฒ๋ฆฌ๋ฅผ ์๊ฐํ๋ฉด, ๋จ์
@tool๋ก๋ ํ๊ณ๊ฐ ์์.- ํนํ ๋์ ์ผ๋ก chain์ ๋ง๋ค๊ฑฐ๋, runtime์ ๊ตฌ์กฐ๋ฅผ ๋ฐ๊พธ๋ ๊ฒ๊น์ง ์๊ฐํ๋ค๋ฉด ๋ฌด์กฐ๊ฑด Runnable-as-tool์ด ๋ ์ข์!