Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Инструменты и первый агент
tools_and_agents

Инструменты и первый агент

Создание инструментов через декоратор @tool, конфигурация агента с create_agent, структурированный вывод, контекст выполнения.

Инструменты и первый агент

Инструменты позволяют языковой модели взаимодействовать с внешним миром. Без них модель может только генерировать текст.

#Что модель на самом деле видит

Инструмент — это не «функция, которую вызывает модель». Модель не выполняет код: она получает описание инструмента (имя, текст описания, JSON-схему аргументов) и возвращает намерение — структуру tool_call с именем и аргументами. Выполняет функцию фреймворк, а результат возвращается модели как ToolMessage.

Из этого следует главное практическое правило: имя, описание и имена параметров — часть промпта. Модель выбирает инструмент, читая их, и больше ей опереться не на что. Плохой docstring это не «некрасиво», а прямая причина неправильных вызовов.

#Создание инструментов

Самый простой способ создать инструмент — декоратор @tool из langchain.tools. Декоратор берёт имя функции как имя инструмента, docstring — как описание, а аннотации типов — как схему аргументов:

from langchain.tools import tool @tool def get_weather(city: str) -> str: """Получить прогноз погоды для указанного города.""" return f"В городе {city} сегодня солнечно, +25°C"

Декоратор не обязателен: любую Python-функцию с аннотациями и docstring можно передать в create_agent напрямую — LangChain обернёт её сам. Декоратор нужен, когда требуется переопределить имя, описание или схему аргументов:

def search_database(query: str, limit: int = 10) -> str: """Поиск в базе данных по запросу. Args: query: поисковые термины limit: максимальное количество результатов """ return f"Найдено {limit} результатов для: {query}" agent = create_agent( model="openai:gpt-4o-mini", tools=[search_database], )

Секция Args в docstring попадает в описания отдельных параметров схемы. Для параметров с неочевидным форматом (даты, идентификаторы, единицы измерения) это решает большинство проблем: вместо дата: str модель видит «дата в формате ГГГГ-ММ-ДД».

#Правила, которые экономят часы отладки

Один инструмент, одно действие. Универсальный manage_data(action, payload) заставляет модель угадывать допустимые комбинации; три отдельных инструмента с говорящими именами она вызывает точнее.

Всегда указывайте типы. Без аннотации параметр попадает в схему как строка произвольного вида, и модель начинает передавать "десять" вместо 10.

Используйте Literal для перечислений: status: Literal["new", "paid", "shipped"] превращается в enum схемы, и модель физически не сможет придумать четвёртый статус.

Возвращайте то, что полезно модели, а не то, что удобно программе. Дамп на 50 КБ съест контекст и ухудшит следующие решения; лучше вернуть краткую сводку и идентификаторы для уточняющих запросов.

#Возвращаемые значения

Инструмент может вернуть строку, объект или мультимодальное содержимое. Строка отправляется модели как есть. Словарь сериализуется, поэтому структурированный ответ читается моделью лучше плоского текста, если полей много:

@tool def get_weather_data(city: str) -> dict: """Получить структурированные данные о погоде в городе.""" return {"city": city, "temperature_c": 22, "conditions": "sunny"}

Мультимодальный результат возвращается списком блоков — так инструмент может отдать модели изображение вместе с подписью:

@tool def capture_screenshot() -> list[dict]: """Сделать снимок текущей страницы.""" return [ {"type": "text", "text": "Снимок текущей страницы:"}, {"type": "image", "url": "https://example.com/page.png"}, ]

#Доступ к контексту выполнения: ToolRuntime

Инструменту часто нужны данные, которых нет в аргументах: идентификатор пользователя, состояние диалога, долговременная память. Для этого объявляется параметр типа ToolRuntime — он внедряется фреймворком автоматически и не показывается модели, поэтому она не может его подделать:

from langchain.tools import tool, ToolRuntime @tool def get_user_preference(pref_name: str, runtime: ToolRuntime) -> str: """Получить значение пользовательской настройки.""" preferences = runtime.state.get("user_preferences", {}) return preferences.get(pref_name, "не задано")

Через runtime доступны: state для текущего состояния диалога, context для неизменяемых данных запуска (идентификатор пользователя, роль, ключи), store для долговременного хранилища между потоками, stream_writer для отправки промежуточных событий, tool_call_id с идентификатором текущего вызова, execution_info с идентификаторами потока и запуска.

Это ключевой приём безопасности: идентификатор пользователя нельзя принимать аргументом инструмента, иначе модель сможет подставить чужой. Он должен приходить из runtime.context, куда его положил ваш код.

#Изменение состояния из инструмента

Обычно инструмент возвращает данные. Но если он должен изменить состояние агента, возвращают Command. Вместе с обновлением состояния нужно вернуть ToolMessage с tool_call_id — иначе история сообщений останется незакрытой, и провайдер отклонит следующий вызов:

from langchain.messages import ToolMessage from langchain.tools import ToolRuntime, tool from langgraph.types import Command @tool def set_user_name(new_name: str, runtime: ToolRuntime) -> Command: """Записать имя пользователя в состояние диалога.""" return Command( update={ "user_name": new_name, "messages": [ ToolMessage( content=f"Имя пользователя установлено: {new_name}.", tool_call_id=runtime.tool_call_id, ) ], } )

#Первый агент с create_agent

Агент создаётся фабрикой create_agent. Минимум — модель и инструменты; системный промпт задаёт роль и правила:

from langchain.agents import create_agent from langchain.tools import tool @tool def calculate(expression: str) -> str: """Вычислить арифметическое выражение, например '15 * 37'.""" return str(eval(expression)) @tool def get_current_time() -> str: """Получить текущее время сервера.""" from datetime import datetime return datetime.now().strftime("%Y-%m-%d %H:%M:%S") agent = create_agent( model="openai:gpt-4o-mini", tools=[calculate, get_current_time], system_prompt="Ты помощник. Используй инструменты для выполнения задач.", ) result = agent.invoke( {"messages": [{"role": "user", "content": "Сколько будет 15 * 37?"}]} ) print(result["messages"][-1].text)

Агент разбирает запрос, выбирает инструмент, вызывает его с аргументами и формирует ответ по результату. В result["messages"] останется весь след: запрос, вызов инструмента, ToolMessage и финальный ответ — по нему удобно отлаживать поведение.

eval в примере годится только для учебного кода. В реальном инструменте вычисление выражения делают через безопасный парсер: модель может передать в аргумент произвольную строку, а значит, это недоверенный ввод.

#Структурированный вывод

Когда ответ нужен программе, а не человеку, задают response_format с Pydantic-схемой. Агент вернёт валидированный объект в result["structured_response"]:

from pydantic import BaseModel, Field class Answer(BaseModel): city: str = Field(description="Город, о котором спрашивали") temperature_c: int = Field(description="Температура в градусах Цельсия") summary: str = Field(description="Краткое описание погоды одной фразой") agent = create_agent( model="openai:gpt-4o-mini", tools=[get_weather], response_format=Answer, ) result = agent.invoke({"messages": [{"role": "user", "content": "Погода в Казани?"}]}) answer: Answer = result["structured_response"] print(answer.temperature_c)

Описания в Field работают так же, как docstring инструмента: это подсказка модели, чем заполнять поле. Без них модель угадывает по имени поля.

#Данные сессии: context и context_schema

Постоянные для запуска данные (кто пользователь, какая у него роль, на каком языке отвечать) передают не в сообщениях, а через контекст. Схема объявляется отдельно, значения передаются при вызове:

from dataclasses import dataclass @dataclass class Context: user_id: str locale: str agent = create_agent( model="openai:gpt-4o-mini", tools=[get_user_preference], context_schema=Context, ) result = agent.invoke( {"messages": [{"role": "user", "content": "Какая у меня тема оформления?"}]}, context=Context(user_id="u-42", locale="ru"), )

Разница между контекстом и состоянием: контекст неизменен в пределах запуска и задаётся снаружи, состояние меняется узлами по ходу выполнения.

#Память между вызовами

Сам по себе агент не помнит прошлые запросы: каждый invoke начинается с переданных сообщений. Память включается чекпоинтером — тогда история привязывается к thread_id:

from langgraph.checkpoint.memory import InMemorySaver agent = create_agent( model="openai:gpt-4o-mini", tools=[get_weather], checkpointer=InMemorySaver(), ) config = {"configurable": {"thread_id": "user-42-chat-1"}} agent.invoke({"messages": [{"role": "user", "content": "Меня зовут Алексей"}]}, config=config) result = agent.invoke({"messages": [{"role": "user", "content": "Как меня зовут?"}]}, config=config)

Во втором вызове передаётся только новое сообщение — предыдущие подгружаются из контрольной точки. thread_id — это идентификатор диалога: один пользователь может иметь несколько потоков, и они не пересекаются.

#Имя агента

Параметр name задаёт идентификатор агента. В одиночном агенте он почти не важен, но в мультиагентной системе именно по нему субагента отличают в трассировке и адресуют при передаче управления — задавайте его сразу, чтобы потом не переписывать.

#Типичные ошибки

Инструмент без docstring: модель видит только имя и вызывает его наугад либо игнорирует.

Идентификатор пользователя как аргумент инструмента: модель может подставить чужой. Берите его из runtime.context.

Возврат исключения наружу: непойманное исключение в инструменте роняет весь запуск. Ошибку лучше вернуть текстом, чтобы модель могла исправиться, или подключить мидлварь обработки ошибок.

Слишком много инструментов: при нескольких десятках модель начинает путаться. Тогда нужен отбор инструментов (LLMToolSelectorMiddleware) или разделение на субагентов.

Устранение неисправностей

Далее: Мидлварь: как менять поведение агента