Расширенное использование чат-модели LangChain

В этом разделе представлены две продвинутые возможности Chat Model: bind_tools() (привязка инструментов) и with_structured_output() (структурированный вывод). Они являются основой агентов и извлечения структурированных данных.


bind_tools() — сообщить модели, какие инструменты доступны

Обычные модели могут только генерировать текст. Но после вызоваbind_tools()модель может вернуть в своём ответеtool_call, сообщая программе «мне нужно вызвать этот инструмент».

Пример

from dotenv import load_dotenv
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 pydantic import BaseModel, Field
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 pydantic import BaseModel, Field
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 pydantic import BaseModel, Field
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:

Пример

from langchain.chat_models import init_chat_model

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 pydantic import BaseModel, Field
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 фактическая операция откладывается — она действительно привязывается только при создании экземпляра модели, поэтому это не влияет на возможность динамически переключать модели во время выполнения.

Другие расширения