Декоратор @tool в LangChain — Определение инструментов
Инструмент — это мост для взаимодействия агента с внешним миром.
Используя@toolдекоратор, можно быстро преобразовать любую функцию Python в инструмент, вызываемый агентом.
@tool Базовый синтаксис
@tool — это декоратор, предоставляемый LangChain. Его использование чрезвычайно просто: добавьте декоратор @tool к функции, и функция становится инструментом.
Пример
# Простейший инструмент: обычная функция + декоратор @tool
@tool
def hello_tool(name: str) -> str:
"""Поприветствуй указанного человека.
Аргументы:
name: имя человека, которого нужно поприветствовать
"""
return f"Привет, {name}! Добро пожаловать в EXAMPLE Tutorial."
# Инструмент — это тоже обычная функция Python, и её можно вызывать напрямую
result = hello_tool.invoke({"name": "Xiao Ming"})
print(result)
# Инструмент содержит автоматически сгенерированную описательную информацию
print(f"\nИмя инструмента: {hello_tool.name}")
print(f"Описание инструмента: {hello_tool.description}")
Вывод:
你好,小明!欢迎来到Example.
Tool name: hello_tool
Tool description: 向指定的人打招呼。
Args:
name: 要打招呼的人的名字
Docstring функции автоматически становится описанием инструмента. Агент полагается на это описание, чтобы определить, «что умеет этот инструмент» и «когда его следует вызывать». Чем яснее docstring, тем точнее Агент использует инструмент. В описании поясните значения параметров, поведение функции и сценарии использования.
Использование различных типов параметров
@tool поддерживает несколько типов параметров, включая int, float, bool и значения enum:
Пример
from typing import Literal
@tool
def search_courses(
keyword: str,
level: Literal["Beginner", "Intermediate", "Advanced"],
max_results: int = 5,
free_only: bool = True,
) -> str:
"""Поиск курсов на EXAMPLE Tutorial.
Аргументы:
keyword: поисковый запрос
level: уровень сложности курса
max_results: максимальное количество возвращаемых результатов
free_only: показывать ли только бесплатные курсы
"""
# Симулировать результаты поиска
courses = {
("python", "Начальный"): "Учебник по основам Python3 (бесплатно)",
("python", "Средний"): "Практика анализа данных на Python (бесплатно)",
("python", "Продвинутый"): "Продвинутое машинное обучение на Python (только для участников)",
("html", "Начальный"): "Учебник по основам HTML (бесплатно)",
}
key = (keyword.lower(), level)
course = courses.get(key, f"Не найдено курсов по запросу {keyword} уровня {level}")
if free_only and "только для участников" in course:
return f"Результат поиска: {course} — для этого курса требуется членство, и он был отфильтрован для вас"
return f"Результат поиска: {course}"
# Тестовые вызовы с разными параметрами
print(search_course.invoke({
"keyword": "python",
"level": "Новичок",
}))
# Вывод: Результат поиска: Учебник по основам Python3 (бесплатно)
print(search_course.invoke({
"keyword": "python",
"level": "Продвинутый",
"free_only": False,
}))
# Вывод: Результат поиска: Продвинутое машинное обучение на Python (только для участников)
Регистрация инструментов в Agent
Передайте определённый инструмент в параметр tools функции create_agent(), и Agent сможет его использовать:
Пример
load_dotenv()
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@tool
def search_courses(keyword: str) -> str:
"""Поиск курсов на EXAMPLE Tutorial. Передайте ключевое слово и получите список связанных курсов.
Аргументы:
keyword: ключевое слово для поиска, например python, html, java
"""
courses = {
"python": "Учебник по основам Python3, Анализ данных на Python, Введение в веб-краулеры на Python",
"html": "Учебник по основам HTML, Новые возможности HTML5, Практика форм HTML",
"java": "Учебник по основам Java, Объектно-ориентированное программирование на Java, Java Spring Framework",
}
return courses.get(keyword.lower(), f"Не найдено курсов, связанных с {keyword}")
@tool
def get_course_detail(course_name: str) -> str:
"""Получить подробную информацию об указанном курсе, включая количество глав и продолжительность обучения.
Аргументы:
course_name: название курса, например, "Python3 Basics Tutorial"
"""
details = {
"python3 basics tutorial": "Всего 30 глав, ориентировочное время обучения 20 часов, подходит для абсолютных новичков",
"html basics tutorial": "Всего 25 глав, ориентировочное время обучения 15 часов, подходит для абсолютных новичков",
}
return details.get(
course_name.lower(),
f"Подробности о {course_name}: подходит для начинающих, богатое содержание, с практическими примерами"
)
# Создание агента
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[search_courses, get_course_detail],
system_prompt="Вы консультант по обучению в EXAMPLE Tutorial, помогаете пользователям находить подходящие курсы.",
)
# Запуск агента
def ask(question: str):
result = agent.invoke({"messages": [HumanMessage(content=question)]})
print(f"Пользователь: {question}")
print(f"Консультант: {result['messages'][-1].content}")
print("-" * 60)
ask("Я хочу изучать Python. Какие курсы вы рекомендуете?")
ask("Сколько времени нужно, чтобы пройти Python3 Basics Tutorial?")
Вывод:
User: 我想学 Python,有什么课程推荐? Advisor: 在Example 中有以下 Python 相关课程: Python3 基础教程、Python 数据分析、Python 爬虫入门。 这些课程很适合 Python 初学者和进阶学习者,您可以根据兴趣选择! ------------------------------------------------------------ User: Python3 基础教程学完需要多久? Advisor: Python3 基础教程共 30 章,预计学习时长 20 小时,非常适合零基础入门学习。 ------------------------------------------------------------
Совместная работа нескольких инструментов
Агент может зарегистрировать несколько инструментов, и модель автоматически решит, когда какой инструмент использовать:
Пример
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage
@tool
def get_stock_price(symbol: str) -> str:
"""Запросить текущую цену акции.
Аргументы:
symbol: символ акции, например, AAPL, GOOGL, TSLA
"""
# Имитация данных об акциях
prices = {"AAPL": 185.50, "GOOGL": 142.30, "TSLA": 245.80}
price = prices.get(symbol.upper())
if price is None:
return f"Символ акции {symbol} не найден"
return f"{symbol.upper()} текущая цена: ${price}"
@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
"""Конвертация валют.
Аргументы:
amount: сумма денег
from_currency: код исходной валюты, например, USD, CNY
to_currency: код целевой валюты, например, CNY, USD
"""
# Имитация обменных курсов
rates = {
("USD", "CNY"): 7.25,
("CNY", "USD"): 0.138,
}
rate = rates.get((from_currency.upper(), to_currency.upper()))
if rate is None:
return f"Конвертация из {from_currency} в {to_currency} не поддерживается"
result = round(amount * rate, 2)
return f"{amount} {from_currency} = {result} {to_currency}"
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[get_stock_price, convert_currency],
system_prompt="Вы финансовый помощник, который помогает пользователям проверять цены акций и конвертировать валюты.",
)
# Агент автоматически решит, какой инструмент вызвать
result = agent.invoke({
"messages": [HumanMessage(content="Какова цена акций Apple? Сколько это в RMB?")]
})
for msg in result["messages"]:
if msg.type == "tool":
print(f"[Вызов инструмента {msg.name}] {msg.content}")
elif msg.type == "ai" and msg.content:
print(f"\nИтоговый ответ: {msg.content}")
Вывод:
[调用工具 get_stock_price] AAPL 当前价格:$185.5 [调用工具 convert_currency] 185.5 USD = 1344.88 CNY Final answer: 苹果(AAPL)当前股价为 $185.50,按当前汇率换算约为 1344.88 人民币。
Необязательные параметры и значения по умолчанию для инструментов
Установка значений по умолчанию для параметров инструмента делает инструменты более гибкими в использовании:
Пример
@tool
def recommend_tutorial(
language: str,
level: str = "Beginner",
count: int = 3,
) -> str:
"""Рекомендовать курсы из EXAMPLE Tutorial на основе языка программирования и сложности.
Аргументы:
language: язык программирования, например, Python, Java, HTML
level: уровень сложности, варианты "Beginner", "Intermediate", "Advanced". По умолчанию "Beginner"
count: количество курсов для рекомендации, по умолчанию 3
"""
tutorials = {
"python": ["Основы Python3", "Объектно-ориентированный Python", "Веб-краулер на Python", "Анализ данных на Python"],
"java": ["Основы Java", "Объектно-ориентированное программирование на Java", "Коллекции Java", "Многопоточность в Java"],
}
all_tutorials = tutorials.get(language.lower(), [f"Учебник по основам {language}"])
selected = all_tutorials[:count]
return f"Рекомендуемые курсы {language} {level}: {'、'.join(selected)}"
# При вызове нужно передавать только обязательные параметры
print(recommend_tutorial.invoke({"language": "Python"}))
# Вывод: Рекомендуемые курсы Python для начинающих: Основы Python3, Объектно-ориентированное программирование на Python, Веб-краулер на Python
# Вы также можете переопределить параметры по умолчанию
print(recommend_tutorial.invoke({
"language": "Java",
"level": "Средний",
"count": 2,
}))
# Вывод: Рекомендуемые курсы Java среднего уровня: Java Basics, Java Object-Oriented
Значения по умолчанию позволяют Агенту не указывать все параметры каждый раз при вызове инструментов. Но обратите внимание: если у параметра нет значения по умолчанию и Агент не передаёт его, вызов завершится ошибкой. Не задавайте значения по умолчанию для ключевых параметров.
args_schema инструмента — пользовательская проверка параметров
Для сложных требований к валидации параметров можно использовать модель Pydantic в качестве args_schema:
Пример
from langchain.tools import tool
# Определение модели параметров (обеспечивает более тонкий контроль параметров)
class CourseSearchInput(BaseModel):
"""Параметры поиска курса"""
keyword: str = Field(
description="Ключевое слово для поиска, поддерживает нечёткое сопоставление",
min_length=1, # Минимум 1 символ
max_length=50, # Не более 50 символов
)
category: str = Field(
default="all",
description="Категория курса: all (all), frontend (frontend), backend (backend), data (наука о данных)",
pattern=r"^(all|frontend|backend|data)$", # Ограничить допустимые значения
)
page: int = Field(
default=1,
description="Номер страницы, начиная с 1",
ge=1, # Больше или равно 1
le=100, # Меньше или равно 100
)
@tool(args_schema=CourseSearchInput)
def search_course(keyword: str, category: str = "all", page: int = 1) -> str:
"""Поиск курсов на учебном сайте EXAMPLE"""
return f"Поиск '{keyword}' (категория: {category}, страница {page}): всего найдено 15 результатов"
# Корректный вызов
print(search_course.invoke({"keyword": "Python", "category": "backend", "page": 1}))
# Некорректный вызов (category не входит в список допустимых значений)
try:
search_course.invoke({"keyword": "Python", "category": "invalid"})
except Exception as e:
print(f"Ошибка проверки параметров: {e}")
Результаты выполнения:
搜索 'Python' (分类: backend, 第 1 页):共找到 15 条结果 参数校验失败: ...
Сравнение методов определения инструментов
| Метод | Объём кода | Применимые сценарии | Пример |
|---|---|---|---|
| декоратор @tool | Минимальный | От простых до умеренно сложных инструментов | Большинство сценариев |
| @tool + args_schema | Средняя | Инструменты, требующие детальной валидации параметров | Обёртывание API, операции с базами данных |
| Класс Pydantic как инструмент | Подробнее | Инструменты со сложной бизнес-логикой | Инструменты, содержащие внутреннее состояние |
| Формат словаря | Наименее предпочтительно (но не рекомендуется) | Описание удалённых/встроенных инструментов | Инструменты MCP, серверные инструменты |