Расширенное использование чат-модели LangChain
В этом разделе представлены две продвинутые возможности Chat Model: bind_tools() (привязка инструментов) и with_structured_output() (структурированный вывод). Они являются основой агентов и извлечения структурированных данных.
bind_tools() — сообщить модели, какие инструменты доступны
Обычные модели могут только генерировать текст. Но после вызоваbind_tools()модель может вернуть в своём ответеtool_call, сообщая программе «мне нужно вызвать этот инструмент».
Пример
load_dotenv()
from langchain.chat_models import init_chat_model
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# Описать инструмент с помощью словаря (формат вызова функций OpenAI)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Запросить погоду указанного города",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Название города, например, Ханчжоу, Пекин"
}
},
"required": ["city"]
}
}
}
]
# bind_tools() привязывает инструмент к модели
# Теперь модель "знает", что инструмент get_weather доступен
model_with_tools = model.bind_tools(tools)
# Задайте вопрос, требующий использования инструмента
response = model_with_tools.invoke("Какая сегодня погода в Ханчжоу?")
# Проверьте, запросила ли модель вызов инструмента
if response.tool_calls:
print("Модель запросила вызов следующих инструментов:")
for tc in response.tool_calls:
print(f" Имя инструмента: {tc['name']}")
print(f" Аргументы: {tc['args']}")
print(f" ID вызова: {tc['id']}")
else:
print(f"Прямой ответ модели: {response.content}")
Результат выполнения:
模型请求调用以下工具:
工具名: get_weather
Arguments: {'city': '杭州'}
Call ID: call_abc123def456
Обратите внимание, что модель фактически не выполняет функцию get_weather. bind_tools() лишь сообщает модели "у вас есть доступный инструмент", а модель возвращает запрос на вызов инструмента. Фактическое выполнение выполняет Агент или код, который вы напишете сами.
Описание инструментов с помощью моделей Pydantic
Для сложных инструментов использование моделей Pydantic для определения структур параметров понятнее, чем ручное написание словарей:
Пример
from langchain.chat_models import init_chat_model
# Определите структуру параметров инструмента с помощью Pydantic
class WeatherInput(BaseModel):
"""Запросить погоду указанного города"""
city: str = Field(description="Название города, например, Ханчжоу, Пекин")
unit: str = Field(
default="celsius",
description="Единица измерения температуры: celsius или fahrenheit"
)
class CalculatorInput(BaseModel):
"""Выполнить математическое вычисление"""
expression: str = Field(
description="Математическое выражение для вычисления, например, '(3 + 5) * 2'"
)
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# Передайте модели Pydantic, LangChain автоматически преобразует их в описания инструментов
model_with_tools = model.bind_tools([WeatherInput, CalculatorInput])
# Проверка сложного сценария
response = model_with_tools.invoke(
"Какая сегодня температура в Пекине? Кстати, помоги мне вычислить 123 * 456"
)
print(f"Модель запросила {len(response.tool_calls)} вызовов инструментов:")
for tc in response.tool_calls:
print(f" {tc['name']}({tc['args']})")
Результат выполнения:
模型请求了 2 个工具调用:
get_weather({'city': '北京', 'unit': 'celsius'})
calculate({'expression': '123 * 456'})
Использование Pydantic для определения параметров инструментов — рекомендуемая практика. Это обеспечивает типобезопасность, автоматическую валидацию, а LangChain автоматически генерирует описания инструментов из имён классов и описаний Field.
with_structured_output() — Заставьте модель возвращать структурированные данные
with_structured_output()Это более прямой подход, чем tool_calling. Он заставляет модель возвращать данные в соответствии с указанным вами форматом (Schema), вместо возврата tool_call.
Пример
from langchain.chat_models import init_chat_model
# Определите ожидаемую структуру выходных данных
class PersonInfo(BaseModel):
"""Информация о человеке, извлечённая из текста"""
name: str = Field(description="Имя человека")
age: int = Field(description="Возраст")
occupation: str = Field(description="Профессия")
skills: list[str] = Field(description="Список навыков")
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# with_structured_output() заставляет модель возвращать данные в формате PersonInfo
structured_model = model.with_structured_output(PersonInfo)
# Передайте неструктурированный текст, получите структурированные данные
text = "Чжан Сань в этом году 28 лет, full-stack-инженер, владеющий Python, React и Docker"
result = structured_model.invoke(text)
print(f"Имя: {result.name}")
print(f"Возраст: {result.age}")
print(f"Род занятий: {result.occupation}")
print(f"Навыки: {', '.join(result.skills)}")
print(f"Тип: {type(result)}")
Результат выполнения:
姓名: 张三 年龄: 28 职业: 全栈工程师 技能: Python, React, Docker Type: <class '__main__.PersonInfo'>
Возвращаемое значение является непосредственноэкземпляром модели Pydantic, и вы можете напрямую обращаться к таким свойствам, как .name и .age.
with_structured_output() против bind_tools()
Эти два метода выглядят похоже, но служат разным целям:
| Критерий сравнения | with_structured_output() | bind_tools() |
|---|---|---|
| Назначение | Извлечение структурированных данных из текста | Сообщите модели список доступных инструментов |
| Формат возврата | Напрямую возвращает объект Pydantic | Возвращает AIMessage, содержащий tool_calls |
| Применимые сценарии | Извлечение информации, парсинг данных | Вызов инструментов агентом, сценарии, требующие внешнего выполнения |
| Поддержка моделей | Требуется, чтобы модель поддерживала нативный структурированный вывод | Все модели, поддерживающие вызов функций |
Вложенный структурированный вывод
with_structured_output() поддерживает сложные вложенные структуры:
Пример
from langchain.chat_models import init_chat_model
class Ingredient(BaseModel):
"""Ingredient information"""
name: str = Field(description="Название ингредиента")
amount: str = Field(description="Количество, например, '200g', '2 pieces'")
class CookingStep(BaseModel):
"""Шаги приготовления"""
step_number: int = Field(description="Номер шага")
description: str = Field(description="Описание шага")
duration_minutes: int = Field(description="Время, необходимое для этого шага (minutes)")
class Recipe(BaseModel):
"""Рецепт"""
dish_name: str = Field(description="Название блюда")
difficulty: str = Field(description="Сложность: лёгкая, средняя, сложная")
ingredients: list[Ingredient] = Field(description="Список ингредиентов")
steps: list[CookingStep] = Field(description="Этапы приготовления")
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
structured_model = model.with_structured_output(Recipe)
# Введите описание рецепта
recipe_text = """
Сегодня я научу вас готовить классическую яичницу с помидорами. Это блюдо очень простое.
Вам нужно подготовить: 2 помидора, 3 яйца, немного нарезанного зелёного лука, немного соли, немного сахара.
Шаги:
1. Сначала нарежьте помидоры на кусочки и взбейте яйца, около 5 минут
2. Разогрейте сковороду, добавьте масло, обжарьте яйца и выньте их, около 3 минут
3. Снова добавьте масло, обжарьте помидоры, пока они не дадут сок, добавьте соль и сахар, около 5 минут
4. Добавьте яичницу, обжарьте, равномерно перемешивая, посыпьте нарезанным зелёным луком, около 2 минут
"""
result = structured_model.invoke(recipe_text)
print(f"Название блюда: {result.dish_name}")
print(f"Сложность: {result.difficulty}")
print(f"Ингредиенты ({len(result.ingredients)} видов):")
for ing in result.ingredients:
print(f" - {ing.name}: {ing.amount}")
print(f"Шаги ({len(result.steps)} шагов):")
for step in result.steps:
print(f" {step.step_number}. {step.description} ({step.duration_minutes} минут)")
Результат выполнения:
菜名: 番茄炒蛋 Difficulty: 简单 食材 (5 种): - 番茄: 2个 - 鸡蛋: 3个 - 葱花: 少许 - 盐: 适量 - 糖: 少许 步骤 (4 步): 1. 番茄切块,鸡蛋打散 (5分钟) 2. 热锅放油,鸡蛋炒熟盛出 (3分钟) 3. 炒番茄至出汁,加盐和糖 (5分钟) 4. 倒入鸡蛋翻炒均匀,撒上葱花 (2分钟)
Режим JSON Schema
Помимо моделей Pydantic, вы также можете напрямую передать JSON Schema:
Пример
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
# Передать JSON Schema напрямую
json_schema = {
"title": "SentimentAnalysis",
"description": "Результат анализа тональности",
"type": "object",
"properties": {
"sentiment": {
"type": "string",
"enum": ["positive", "negative", "neutral"],
"description": "Тональная направленность"
},
"confidence": {
"type": "number",
"description": "Уверенность, 0~1"
},
"keywords": {
"type": "array",
"items": {"type": "string"},
"description": "Ключевые слова тональности"
}
},
"required": ["sentiment", "confidence"]
}
structured_model = model.with_structured_output(json_schema)
result = structured_model.invoke("Пример туториала EXAMPLE действительно отличный, очень рекомендую его всем начинающим программистам!")
print(f"Тональность: {result['sentiment']}")
print(f"Уверенность: {result['confidence']}")
print(f"Ключевые слова: {result['keywords']}")
Результат выполнения:
Sentiment: positive Confidence: 0.95 Keywords: ['太棒了', '强烈推荐']
bind_tools и with_structured_output в ConfigurableModel
Настраиваемые модели также поддерживают эти два метода с точно таким же использованием:
Пример
from langchain.chat_models import init_chat_model
# Создание настраиваемой модели
configurable_model = init_chat_model(
"deepseek-v4-flash",
configurable_fields=("model", "model_provider"),
temperature=0,
)
# Цепочный вызов: сначала привязать инструменты, затем invoke
configurable_with_tools = configurable_model.bind_tools([...])
# Разные модели можно использовать во время выполнения для исполнения
result = configurable_with_tools.invoke(
"Запросить погоду",
config={"configurable": {"model": "claude-sonnet-4-5"}}
)
Другие расширенияПри связывании bind_tools или with_structured_output с ConfigurableModel фактическая операция откладывается — она действительно привязывается только при создании экземпляра модели, поэтому это не влияет на возможность динамически переключать модели во время выполнения.