📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Pydantic AI Crash Course: Agentic Framework For Production

NeuralNine41:15

Transcription

Сегодня мы проведем экспресс-курс по Pyantic AI, еще одному из этих агентурных Python-фреймворков, похожих на Langchain и Llama Index. По обоим из них у меня есть туториалы на этом канале, и, как всегда с этими агентурными фреймворками, они немного отличаются. В основном они довольно похожи, но у них есть разные точки фокусировки. Итак, Langchain — это более универсальный сквозной фреймворк, который немного похож на Django, можно сказать. Многое включено, предвзято. Затем Llama Index больше ориентирован на поиск, а Pyantic, который мы сегодня рассмотрим, больше ориентирован на проверку безопасности типов, структурированный вывод, и в целом он более минималистичен и гибок, немного похож на Fast API. Сегодня мы рассмотрим довольно много. Так что, если вам понравилось это видео, дайте мне знать, нажав кнопку "лайк" и подписавшись. А теперь давайте приступим. [музыка] >> Хорошо. Итак, Pyantic AI — это то, о чем этот экспресс-курс. Это еще один из тех фреймворков для агентов GenAI, похожий на Langchain и Llama Index, как я уже упомянул во вступлении, и вы можете видеть, что они говорят здесь: "Это Pyantic-способ". Итак, для тех из вас, кто не знает, что такое Pyantic, это, по сути, Python-пакет, используемый для валидации во многих различных контекстах. Один из них — Fast API, но Pyantic также используется многими другими фреймворками для агентов. Он используется Langchain, и он также используется Llama Index. И это также один из их аргументов здесь. Когда вы спускаетесь к "Почему использовать Pyantic AI", там, по сути, говорится: "Почему использовать производное, когда можно обратиться прямо к источнику?". Но если говорить серьезно, то реальная причина использовать Pyantic AI в некоторой степени является вопросом выбора. Так что некоторым людям просто нравится стиль Fast API для создания этих агентурных приложений. Многие люди в сообществе считают, что Pyantic AI просто намного более, э, намного чище, чем Langchain, с точки зрения того, как написан код, и с точки зрения того, как вы пишете код, используя его. Э, но у него также есть другая направленность. Он очень ориентирован на готовность к производству, безопасность типов, валидацию данных и структурированный вывод. Так что это направленность здесь, подобно тому, как Llama Index ориентирован на поиск. Мы можем использовать все эти фреймворки для, по сути, всего, но у них есть определенные сильные стороны, и это направленность или ключевое преимущество Pyantic AI, я бы сказал. Итак, хватит теории, давайте приступим к настройке. Откройте терминал, перейдите в каталог, в котором вы хотите работать. В моем случае это каталог с туториалами, и здесь вы можете настроить виртуальное окружение. Вы также можете просто использовать свою системную установку, если хотите. Так что самый простой способ сделать это — просто сказать pip или pip 3 install пакеты, которые мы будем использовать сегодня. Я лично всегда предпочитаю использовать UV. Так что я инициализирую проект UV с помощью uv init. И я скажу uv add вместо pip install. Так что все, что я скажу здесь после uv add, вы можете просто сказать после pip install, и это будет иметь тот же эффект, только на вашей системной установке. Итак, нам понадобится здесь Pyantic-ai. Но нам также понадобится python-dotenv. И еще одна вещь, которая нам понадобится, это также Jupyter Lab. Это среда, в которой мы будем работать сегодня. Это необязательно. Вы также можете просто работать в обычных Python-файлах. Мне просто нравятся Jupyter-ноутбуки, когда мы что-то изучаем, потому что мы можем интерактивно запускать, э, ячейки отдельно. Нам не нужно запускать полные Python-скрипты. Итак, установите эти зависимости, а затем мы можем сделать jupyter lab, чтобы запустить Jupyter Lab. И если вы используете UV, как я, вам нужно сделать uv run jupyter lab. Так что это просто откроет среду разработки. Э, как всегда, когда я делаю туториалы в Jupyter Lab, я кратко объясняю, о чем эта среда. Так что здесь мы не работаем с Python-файлами. Мы работаем с интерактивными Python-ноутбуками. Э, звучит более наворочено, чем есть на самом деле. У нас просто есть отдельные ячейки кода, которые мы можем запускать. Так что я могу сказать A = 10, и я могу сказать print A ниже. И затем я также могу сделать несколько print A, и мне не нужно запускать весь код сверху вниз. Я могу запускать отдельные ячейки, и я могу запускать их в разном порядке. это просто облегчает интерактивную работу с вещами, э, когда вы исследуете пакет или набор данных или что бы вы ни делали. Теперь, прежде чем мы начнем кодирование, нужно настроить еще одну вещь. Вам нужен файл .env, содержащий ваш API-ключ провайдера, которого вы хотите использовать. Вам, очевидно, нужно использовать какую-то большую языковую модель, чтобы обеспечить ваш агент интеллектом. Так что вы хотите перейти в каталог, в котором вы работаете. И в моем случае сейчас я скопирую это из моей подготовленной программы. Так что у меня есть библиотека подготовки YouTube, у меня есть каталог Pyantic, и у меня есть файл .env здесь. Теперь я не буду показывать свой файл .env, потому что этот файл .env содержит API-ключ. Но что вы хотите сделать, это создать файл с именем .env. Просто точка, а затем env. И в этом файле вы хотите иметь, э, API-ключ. Я скажу test здесь, чтобы показать вам, как это выглядит. Это просто будет ключ OpenAI API_key, например, если это ключ OpenAI, иначе Anthropic или что бы вы ни использовали, а затем сам ключ. Так что вы просто вставляете ключ сюда, а затем сохраняете файл вот так. Это, по сути, все. Это файл .env, и он будет актуален, потому что он будет автоматически распознаваться, э, Pyantic, если вы попытаетесь использовать модель OpenAI, и он автоматически аутентифицирует вас с помощью API OpenAI. Так что давайте избавимся от всего этого и начнем с импорта из.env функции load.env. Вот почему мы установили python-dotenv, потому что этот вызов функции позволяет нам загружать API-ключи из файла .env. Альтернативно, конечно, вы также можете указать их с помощью оператора export в терминале, если хотите. Но это простой способ. Вы просто создаете файл, помещаете туда ключи. Вы также можете иметь там несколько ключей, и load.env затем загружает их в ваш скрипт или приложение. Так что это настройка. Теперь то, что я всегда люблю делать при исследовании этих фреймворков для агентов, это создавать очень простой пример. Так что просто отправляем сообщение агенту, и все. В Pyantic мы можем легко сделать это, сказав from pyantic_ai import agent, а затем мы скажем agent = agent. И здесь теперь мы укажем модель. Опять же, вы можете использовать несколько провайдеров. Это модель-агностик. Так что мы можем сказать, например, OpenAI: GPT40. Затем все, что нам нужно предоставить в дополнение к этому, это системный промпт. Так что давайте скажем system_prompt = "Ты полезный ассистент". И чтобы получить быстрый ответ, я скажу: "Ты всегда отвечай одним кратким предложением", чтобы нам не пришлось слишком долго ждать ответов. И теперь, как мы это вызываем, может быть немного иначе, чем в других фреймворках. Это также зависит от того, в каком контексте вы это запускаете. Если вы запускаете это в Python-файле, вы также можете использовать другую функцию. Поскольку мы используем это в Jupyter-ноутбуке, нам нужно вызвать функцию run вместо функции run_sync. Есть также функция agent.run_sync, как вы можете видеть здесь, но мы просто будем использовать run и будем await ее. Так что для тех из вас, кто знаком с асинхронным программированием, есть async await, await, по сути, заставляет нас ждать, чтобы получить этот асинхронный ответ. Так что я скажу response = await agent.run, а затем я могу предоставить сообщение, например, "Что такое Python?", а затем после этого я просто напечатаю response.output. Так что это отправит запрос и даст мне исключение, потому что я неправильно набрал prompt. Так что это будет system_prompt вот так. Затем "Что такое Python?". Python — это высокоуровневый интерпретируемый язык программирования, известный своей читаемостью и универсальностью. Круто. Так, давайте покажу, что произойдет, если вы не будете await. Что происходит, когда вы не await, это то, что вы можете сказать response = agent.run, а затем вы можете задать похожий вопрос, например, "Что такое Python?". даже если я не использовал заглавную букву сейчас, но затем я могу просто немедленно напечатать ответ, не дожидаясь, и то, что я получаю в результате, это корутинный объект. Так вот почему нам нужно ждать, потому что это просто обещание, можно сказать, что мы получим ответ в будущем. У нас его еще нет, и здесь мы ждем ответа, а затем мы выводим, э, мы выводим вывод в командную строку. Так что это не сильно отличается от того, как мы делаем это с другими фреймворками. Итак, давайте теперь добавим к этому также базовый пример структурированного вывода. Так что давайте скажем, что я хочу сделать, и для этого я буду использовать сам Pyantic, а не Pyantic AI, а основной пакет Pyantic. Я скажу from pydantic import BaseModel. Так что это просто, э, базовый класс, от которого мы наследуемся при создании модели Pyantic. И давайте скажем, что у меня есть person_output здесь, или давайте просто назовем это PersonBaseModel. И у него будет name, который является строкой. У него будет int, э, извините, age, который является int. И у него будет job, который тоже является строкой. Так что этот класс теперь определяет структуру вывода, который я ожидаю от модели. Так что я могу затем перейти, скопировать агента, фактически скопировать все здесь. И теперь мне просто нужно указать, что я ожидаю, что ответ модели будет этого типа. Так что я могу сказать: "Ты всегда" и, кажется, я что-то вставил здесь. Я могу сказать: "Ты всегда отвечаешь структурированным выводом для людей". Это не лучший промпт, который я когда-либо писал, но это примерно то, что он делает. А затем я могу сказать: "Майк — 20-летний сантехник", вот так. Единственное, чего здесь не хватает, это аргумент output_type. Так что я скажу здесь output_type = Person. И затем, как только у нас это будет, я могу просто отправить это, и результат будет name = Mike, age = 20, job = plumber. И на самом деле, я думаю, что тип этого ответа, output, также должен быть типа Person. Вот так. Теперь я сказал, что мы не можем использовать синхронный метод, потому что мы запускаем это в ноутбуке. Но что мы можем сделать, это можем потоково передавать вывод. Так что вместо того, чтобы просто получать ответ сразу, мы можем также генерировать его и видеть, как он генерируется шаг за шагом, по частям, пока он генерируется. Для этого я могу использовать run_stream. Так что я могу сказать async with agent.run_stream, и я могу ввести промпт, например, "Я не знаю, что такое Python", и, возможно, для этого я скопирую это, но я не буду говорить ему быть кратким, чтобы мы получили более длинный ответ. Так что я просто избавлюсь от всего этого. "Ты полезный ассистент" — этого достаточно. "Что такое Python?" — это вопрос. Затем я могу сказать as run, и я также могу сказать async for output in run_stream: print(output). Теперь это не то, что нам на самом деле нужно. Я просто хочу показать вам это, чтобы вы могли видеть, как текст постоянно добавляется. Затем мы напечатаем его правильно или потоково передадим его правильно. Но, по сути, вы сейчас увидите, как это становится все длиннее и длиннее. Так что он начинается с "Python — это высокоуровневый интерпретируемый язык программирования, известный своим", затем "ясным синтаксисом и читаемостью, который", а затем вы можете видеть, что он становится все длиннее и длиннее, но он постоянно повторяется. Это не чистый способ сделать это, но я хотел показать вам, как выглядит структура, потому что каждый объект run в этом потоке становится все длиннее и длиннее, содержит все целиком, так сказать. Так что единственное, что нам нужно изменить здесь, чтобы получить другой результат, это нам нужно установить здесь параметр delta = True. Так что я могу сказать, что это не вывод. Теперь это фрагмент. Затем я могу напечатать фрагмент. Я могу сказать end = "" (без переноса строки). И затем я могу сказать flush = True. И просто сделав это, мы увидим, что это теперь генерируется здесь, как будто мы разговаривали с ChatGPT. Так что это метод run_stream здесь. Теперь, если вы немного поиграетесь с этим, и на самом деле давайте используем исходный. Так что позвольте мне снова избавиться от всего этого здесь. Э, если мы немного поиграемся с этим, вы заметите, что у него нет никакого контекста. Так что прямо сейчас я спросил его, что такое Python, а затем я могу попробовать спросить что-то еще. response = await agent.run, и я могу спросить, если я не буду так сильно ошибаться, я могу спросить: "Он лучше других?". И, конечно, он не будет знать, о чем я говорю, потому что у него нет контекста. Так что вопрос, конечно, "Лучше ли Python других языков программирования?", что, конечно, тоже глупый вопрос. Но если я сделаю это, он скажет мне: "Это зависит от конкретных критериев и контекста, который вы сравниваете". Или, может быть, мне следует спросить что-то еще. Может быть, мне следует спросить: "И когда он был выпущен?". Потому что тогда он не сможет уклониться от ответа. Он говорит: "Извините, но мне нужен дополнительный контекст, чтобы точно ответить на ваш вопрос. Не могли бы вы уточнить, что вы имеете в виду?". Потому что он не знает, что мы говорили о Python. Если я хочу отслеживать сообщения, мне просто нужно добавить историю в качестве контекста. Так что мне нужно сказать agent.run, а затем здесь мне нужно сказать message_history = и затем объект response предыдущего промпта response. И теперь я могу либо выбрать new_messages, либо all_messages. Так что new_messages будет получать только самые последние. Так что он будет забывать со временем, но all_messages просто будет отслеживать все сообщения. И если я запущу это сейчас, а затем запущу это, он скажет мне: "Python был впервые выпущен 20 февраля 1991 года". И если я теперь перейду к этому ответу и скажу response.all_messages, вы увидите, что все эти сообщения являются частью этой, э, этой всей истории. Так что вы можете видеть здесь: "Ты полезный ассистент", и у него все еще есть неправильно набранный системный промпт здесь, но вы можете видеть, что это часть контекста. Затем вы также можете видеть здесь: "Что такое Python?". Вы также можете видеть: "Python — это высокоуровневый язык программирования". "Python был впервые выпущен" и так далее. Но если я скажу response.new_messages, только тогда вы увидите, что я получаю только "И когда он был выпущен?", а также ответ на это. Я не получаю полный контекст. Так что, если вы не передаете all_messages, он будет рассматривать только новые сообщения, предыдущие. Теперь еще одна интересная вещь, и позвольте мне снова избавиться от всего этого здесь. Еще одна интересная вещь заключается в том, что существует разница между системными промптами и инструкциями в Pyantic. И для этого я хочу скопировать пример, потому что он не введет ничего нового. Он просто использует то, что мы уже знаем. Но я хочу показать вам разницу здесь. Так что это тот же самый код, который мы написали до этого момента. Agent one = agent, agent two = agent. Единственное отличие — мы не используем system_prompt. Мы используем instructions. И прямо сейчас вы можете видеть, что первый агент получает инструкцию, которая похожа на системный промпт, который гласит: "Если спросят, какой твой любимый цвет, всегда отвечай ровно синим, иначе отвечай нормально". А затем я спрашиваю его: "Хорошо, сколько будет 2 плюс 2?". Он говорит: "Скорее всего, четыре". А затем я спрашиваю его позже во втором агенте здесь, и второй агент берет историю сообщений от первого агента. Так что это два отдельных агента, но второй агент ссылается на историю сообщений первого агента, и я спрашиваю его, какой мой любимый цвет. Теперь, если я запущу это, вы заметите что-то интересное, потому что он говорит "красный". Вы можете видеть, что агент 2 имеет системный промпт, который гласит: "Если меня спросят о любимом цвете, всегда говори красный". Этот говорит "синий". И поскольку я использовал инструкции, это не взято из первого агента. Хотя я передаю историю сообщений здесь, которая является историей, связанной с результатом, сгенерированным агентом один. Даже если это так, я не беру системный промпт отсюда. Теперь, если я изменю это, если я сделаю это system_prompt, мы увидим, что это приведет к другому поведению. Так что я могу заменить обе инструкции здесь на system_prompt. И тогда я получу синий ответ, потому что он все еще ссылается на системный промпт первого агента, потому что он был передан через этот объект result.all_messages. Так что, если я хочу убедиться, что этого не произойдет, я должен использовать инструкции, иначе я использую system_prompt. Это важное различие здесь. Итак, теперь давайте вернемся к более обычным вещам, таким как использование инструментов, например. Давайте вернемся к одному агенту. Давайте скажем, что системный промпт — это просто снова "Ты полезный ассистент", и мы спросим его что-то вроде: "Какой мой любимый цвет на этот раз?". Так что, если я спрошу его об этом, и я снова избавлюсь от этого, и скажу print(result.output). Если я спрошу его, правильный ответ должен быть: "Я не знаю. У меня нет доступа к этой информации". Однако я могу предоставить ему доступ к этой информации, связав инструмент или добавив инструмент, который содержит информацию о моем любимом цвете. Так что я могу сказать здесь, что есть функция, которую я хочу вызвать get_favorite_color, и давайте скажем, что она принимает name в качестве параметра. Так что есть параметр name, который является строкой, и эта функция также возвращает строку. Это важно, потому что типизация будет использоваться Pyantic AI, чтобы знать, что передать в эту функцию. Затем мне нужно добавить docstring здесь, чтобы убедиться, что мы знаем, о чем функция. Я скажу: "Функция, которая возвращает любимый цвет пользователя на основе его имени". И затем я могу просто сказать, например, э, если name ==, и затем я буду использовать свое имя Florian, вернуть blue, или давайте сделаем что-то более экзотическое, например, teal, затем я могу сказать elif name == Mike, тогда это будет orange, а затем, наконец, мы скажем "информация не найдена". Так что это простая функция. Теперь есть два способа зарегистрировать это как инструмент для агента. Один похож на то, что вы используете в Langchain и Llama Index. Вы просто передаете его как инструмент в списке, по сути. Так что я могу сказать здесь tools = и затем я могу сослаться на это. Так что я сделаю это, э, ниже. Так что я определю функцию. Затем я скажу tools = get_favorite_color. Давайте, может быть, попробуем это. get_favorite_color. Давайте сначала запустим это. Затем get_favorite_color. Передайте ее вот так. И тогда у него будет доступ, но мне нужно ввести свое имя. Меня зовут Флориан. Затем он вызывает инструмент, и он знает, что мой любимый цвет — teal. Однако это не единственный способ. Так что я могу снова удалить это. Он не знает, какой мой любимый цвет. И теперь, что я могу сделать, это я могу сделать и наоборот. Я могу определить агента. Я могу сделать промптинг ниже. А затем после того, как я определю агента, я могу сказать @agent.tool над этой функцией. Теперь вещь, которую вам нужно учитывать здесь, это tool и tool_plain. Tool используется, когда вы также передаете контекст в функцию, чего мы еще не делаем. Так что я назову это tool_plain. Agent.tool_plain в качестве декоратора над функцией делает ее инструментом агента. Так что просто сделав это, я могу затем запустить это, и у него также будет доступ к этой функции и он скажет мне, что мой любимый цвет — teal. Теперь мы переходим к чему-то немного более продвинутому и очень интересному, называемому внедрением зависимостей. Звучит более наворочено, чем есть на самом деле. Все, что это означает, это то, что мы можем передавать контекст или зависимости нашему агенту при выполнении запроса. И этот контекст затем также может использоваться в инструменте. Так что контекст, который мы передаем, или зависимости, которые мы передаем, могут быть либо простой информацией, такой как строка или целое число. Они могут быть чем-то более сложным, например, соединением с базой данных, или они могут быть даже классом данных, который содержит несколько из этих простых вещей, таких как несколько соединений с базами данных, целые числа, строки, все в одном классе данных. Теперь, чтобы показать вам, как это работает, я сначала использую простой пример из документации, который, по сути, является агентом рулетки. Все, что он делает, это угадывает число для пользователя, а затем есть также правильное число. Это будет наш контекст в данном случае. А затем я покажу вам более продвинутый пример с использованием классов данных, а также соединения с базой данных. Итак, давайте начнем с того, что скажем from pyantic_ai import RunContext. Это класс, который нам нужен для использования контекста в инструменте. И затем я создам здесь агента рулетки. И это будет просто агент Pyantic. Он будет использовать какую-то модель, как и раньше. Давайте возьмем OpenAI/GPT40. И теперь мы можем указать больше, чем просто системный промпт. Мы можем указать, например, тип зависимостей. Так что тип зависимостей в нашем случае — целое число. Это будет очень простая вещь. Как я сказал здесь, мы можем передать класс данных. Мы можем передать соединение с базой данных. В моем случае здесь я просто передаю простое целое число, и это будет правильное число для игры в рулетку. Теперь, в дополнение к этому, я также хочу иметь тип вывода, который будет булевым. Была ли догадка правильной или нет? И затем мы можем сказать system_prompt: "Ты ассистент рулетки. Ты можешь обрабатывать догадки чисел от пользователей", что-то вроде этого, и теперь мы можем перейти и сказать, что я хочу определить инструмент для угадывания числа, и я могу сказать roulette_agent.tool. Теперь я не использую tool_plain, я использую tool, потому что мы будем передавать контекст. Так что я могу сказать async def guess_number. Это, конечно, будет принимать догадку числа, но также и контекст. Так что мы скажем здесь ctx будет типа RunContext, который будет типа int. Так что зависимость будет типа int. И затем мы можем сказать guess_number, который также является целым числом, который будет возвращать строку. Мы не будем напрямую возвращать булево значение, потому что это задача агента — возвращать правильный тип данных. И затем нам также нужен docstring: "Угадай число рулетки для пользователя". И мы можем легко просто сказать return 1, если guess_number. И теперь вопрос в том, с чем мы сравниваем? Так что, чтобы получить зависимость, нам нужно сказать ctx.deps. И теперь, поскольку deps будет только целым числом, это нормально. Мы можем просто сделать это вот так. Это будет сравнение целых чисел. Иначе мы вернем "проиграл". Итак, как это работает? Теперь мы можем перейти и указать правильное число. Давайте сделаем это статически. Так что мы можем проверить, что происходит. 17 будет правильным числом сейчас. И что я могу сделать, это я могу сказать response = await roulette_agent.run. Затем я хочу поставить свои деньги на 18. И важная вещь сейчас для этого запроса — мне нужно передать контекст. Так что то, что я могу сделать здесь, это я могу сказать dependencies = correct_number, поскольку это целое число, это нормально, и я могу затем сказать print(response.output). И, конечно, я не должен говорить openai/, я должен говорить openai:gpt40. И то, что происходит сейчас, это я получаю false. Но если я изменю это на 17, я должен получить true. Вот так. Но теперь давайте посмотрим на более интересный пример с соединением базы данных, а также с классом данных. Для этого у меня в каталоге туториалов, где я сейчас работаю, есть папка data, и в этой папке data у меня есть база данных db. Это очень-очень простая база данных SQLite 3. Так что я могу открыть ее, и если я сделаю tables, вы увидите, что есть одна таблица person, и я могу сказать select * from person, и у меня, по сути, три записи здесь: Mike, Lisa, John, 20, 30 и 50 лет, сантехник, бухгалтер, программист, это, по сути, все. И я хочу предоставить моему агенту доступ к этой базе данных, и я также хочу, чтобы агент обращался ко мне по имени. Это две вещи, которые я хочу достичь с помощью моих зависимостей. Так что я начну с того, что скажу import sqlite3, а также from dataclasses import dataclass. Затем мы можем смоделировать наши зависимости как класс данных, используя декоратор @dataclass, и класс будет называться MyDependencies, и я хочу иметь здесь две вещи. Одна — это username: str, а другая — db_connection, которая будет sqlite3.Connection, что является классом, так что заглавная буква C, а затем подход, по сути, такой же, как и раньше. Мы создаем агента, агент — это экземпляр класса agent, мы передаем модель OpenAI, затем: GPT-40, и на этот раз для dependencies_type я фактически использую свой класс данных, так что MyDependencies. Теперь, поскольку я хочу, чтобы модель обращалась ко мне по имени, мне также нужно указать это в системном промпте. Я могу сказать system_prompt = "Ты полезный ассистент. Всегда начинай каждое сообщение с обращения к пользователю по имени". Теперь, конечно, username будет передаваться агенту как поле зависимости. Но вопрос в том, как агент на самом деле знает имя пользователя? Просто передать его недостаточно, потому что, как вы видели, нам нужно намеренно сделать что-то с полем с зависимостями, чтобы использовать информацию. Теперь это правда, если вы используете ее в инструменте, но есть также другой декоратор, который мы можем использовать, называемый system_prompt. Так что то, что я могу сделать, это я могу сказать @agent.system_prompt, а затем я могу сказать, что эта функция должна использоваться для улучшения системного промпта. Например, я могу сказать, что это называется @user_name. И он принимает контекст в качестве параметра здесь. RunContext, MyDependencies. И затем он возвращает строку. И здесь я просто говорю: "Верни имя пользователя" и затем context. На самом деле, мне нужно сделать это строкой f. f"{context.user_name}". Так что здесь улучшается системный промпт и добавляется информация из переданного контекста в системный промпт. Теперь давайте также добавим инструмент для соединения с базой данных. Я скажу agent.tool, и это будет list_all_employees или что-то в этом роде. context будет снова RunContext с моими зависимостями, который будет возвращать, скажем, список строк, и мы будем использовать docstring, который гласит: "Перечислить всех сотрудников из базы данных", а затем нам просто нужно сделать обычные вещи SQLite. Так что connection = context.deps, а затем db_connection, который просто передается здесь. Курсор будет connection.cursor. Затем мы скажем cursor.execute, и мы выполним select * from person. Или, может быть, нам следует сказать select name, age, job from person. И затем, чтобы получить результаты, мы можем просто сказать people = cursor.fetchall(). Затем мы можем сказать result = [] (пустой список). И затем для row in people мы можем сказать result.append, и, э, или на самом деле мы можем сказать, да, result.append, и затем мы можем передать кортеж. Так что, может быть, это должен быть список кортежей, которые являются строками, или на самом деле один из них будет целым числом. Так что давайте скажем, что списка кортежей достаточно для типизации, и давайте скажем, что это row[0], row[1], row[2], и, наконец, мы возвращаем result, это наш инструмент, и этому инструменту нужно соединение с базой данных. Так что то, что я могу сделать сейчас, это я могу сказать connection, или давайте назовем его dbcon, = sqlite3.connect, и мне просто нужно указать путь: data/database.db. И username будет равен Florian. Так что теперь я могу просто сказать response = await agent.run, и я могу спросить: "Какие сотрудники есть в базе данных?". И зависимость этого будет равна чему-то, чего у нас еще нет, а именно объекту dependencies. Так что это будет MyDependencies с username и db_connection. Так что мы передаем это здесь как именованный аргумент. Теперь одна вещь, которая важна, поскольку это будет асинхронно, э, запускаться асинхронно, нам нужно сказать здесь check_same_thread = False, иначе у нас возникнут проблемы. Но как только у нас это будет, мы просто скажем print(response.output). И это должно теперь использовать инструмент для получения информации из базы данных, и он говорит здесь: "Флориан, вот сотрудники в базе данных", а затем перечисляет их здесь, э, в виде строки. Так что вы можете видеть, с одной стороны, контекст может быть получен функцией, декорированной системным промптом. Это просто улучшает системный промпт информацией из контекста, или мы также можем просто получить к нему доступ в инструментах для использования соединений с базами данных для выполнения всех видов вещей и, таким образом, улучшить использование инструментов. Если я могу на секунду, я хотел бы прорекламировать себя в качестве спонсора собственного видео. Если вы зайдете на мой сайт neurallonline.com, вы найдете вкладку "Услуги" и вкладку "Репетиторство". Здесь вы можете нанять меня для всевозможных вещей, таких как наука о данных, машинное обучение, веб-разработка. Если вам нужна помощь с чем-то в проекте, здесь вы можете забронировать меня для индивидуального репетиторства. Если вы хотите, чтобы я лично научил вас чему-то, что вы не понимаете, если вам нравится мой стиль преподавания, на обеих страницах внизу вы можете связаться со мной по электронной почте, а также через LinkedIn. Просто хотел сообщить вам об этом. Следующее, что я хочу показать вам, это как использовать наборы инструментов. И, как уже следует из названия, это довольно просто. У нас есть несколько инструментов, и мы хотим объединить их в набор инструментов, чтобы мы могли передавать их как единое целое. И это называется набор инструментов. Теперь, поскольку это довольно просто, я не хочу набирать все с нуля. Здесь у меня есть три подготовленные функции, которые являются очень простыми Python-функциями. Нам просто нужно импортировать datetime как dt из основного Python. И это три функции, которые просто возвращают дату, день недели и время. Очень просто. И то, что я хочу сделать сейчас, это я хочу упаковать их как набор инструментов под названием "Инструменты даты и времени". Как мне это сделать? В Pyantic AI я говорю from pyantic_ai.toolsets import function_toolset. Так что это позволяет мне сказать, что эти три являются одним набором инструментов, который я собираюсь назвать datetime_tools. И я просто скажу function_toolset и передам три инструмента. Так что get_current_date_tool, get_current_time_tool и get_current_weekday_tool. Теперь, что я могу сделать, это я могу сказать agent = agent. Давайте снова возьмем GPT40. Так что OpenAI GPT40. И теперь я могу сказать вместо tools = я могу сказать toolsets = и я могу просто передать datetime_tools. И если я сделаю это, я могу задать вопрос: "Который час, который день?", и он будет знать правильный ответ. Так что response = await agent.run, и я могу спросить его: "Какая сегодня дата и время?", а затем, когда я посмотрю на response.output, я увижу "5 февраля", а время "16:42". Это правильно прямо сейчас. И это супер просто и прямолинейно. Теперь, если я не хочу добавлять это к самому агенту, так что я могу перезапустить это. Вы увидите, что он не знает текущего времени и даты. Я также могу добавить это к этому конкретному ответу, э, или к этому, э, конкретному запросу, я хотел сказать. Так что toolsets = datetime_tools. Это также возможно для одного запуска. И в этом случае он снова будет знать дату и время. Еще одна вещь, которую я хочу показать вам, это как вводить тайм-ауты и лимиты повторных попыток для ваших инструментов. Для этого у меня здесь есть небольшой фрагмент кода, подготовленный. Простой агент снова использует GPT40 и простой plain tool, который просто ждет 10 секунд, а затем возвращает синий как любимый цвет. Это, по сути, вся магия здесь. Теперь, если я просто запущу это, скажем, с 2 секундами здесь без проблем, это даст нам правильный результат. Любимый цвет — синий. И если я сделаю это на 10 секунд, и нет лимита тайм-аута, это также будет работать. Но я также могу ввести лимит тайм-аута. Я могу сказать здесь tool_timeout = 10, например. И тогда это будет, или на самом деле это не то, что я хотел сделать. Я хотел сделать это короче. Э, теперь, вероятно, это не позволит мне, э, выйти из этого. Но если я скажу tool_timeout = 5, а затем это займет 10 секунд, чтобы это произошло, он просто отменит. скажет, что это не работает, и это приведет к завершению вызова инструмента, и он либо скажет мне что-то, либо выдаст исключение. Одно из двух произойдет здесь. Вот так. Я получаю исключение, которое говорит мне: "Неожиданное поведение модели, превышен максимальный лимит повторных попыток 1". Теперь, если я хочу увеличить лимит повторных попыток, я могу сказать retries = 5 или что-то в этом роде. В этом случае он повторит тот же инструмент, если он истечет по времени, но затем он просто вернет мне ответ, что инструмент не ответил, или что он не может получить эту информацию прямо сейчас. Так что, как вы видите, у меня все еще проблемы с получением вашего любимого цвета из-за ошибки тайм-аута. Пожалуйста, попробуйте спросить позже или дайте мне знать, если я могу помочь вам чем-нибудь еще. Кодирование наших собственных инструментов — это не единственный способ использовать инструменты в Pyantic. Мы также можем использовать встроенные инструменты, предоставляемые самим пакетом. Я хочу показать вам два из них. Первый — это инструмент веб-поиска. Для этого мы можем сказать from pyantic_ai import web_search_tool. И затем, чтобы использовать это с OpenAI, нам нужно импортировать специальный класс модели. Нам нужно сказать pyantic_ai.models.openai. Оттуда мы хотим импортировать модель OpenAI_Responses. Это просто необходимо здесь. И затем я могу сказать OpenAI_Responses. И затем здесь мне просто нужно сказать GPT40. Мне больше не нужно указывать OpenAI. И затем я могу создать простой агент. Я могу сказать, что этот агент принимает модель как модель. И затем я использую именованный аргумент built_in_tools, чтобы предоставить список инструментов, таких как web_search_tool. Однако здесь нам нужно его инстанцировать. Так что это не функция. Это класс. Нам нужно создать экземпляр этого класса. И как только у нас это будет, мы можем просто спросить его снова. response = await agent.run, и мы можем спросить его: "Какое последнее видео от Neural 9?". Теперь я не думаю, что он получит правильный ответ, но он по крайней мере будет искать в Интернете, и мы увидим, что это так. Так что я напечатаю вывод здесь, который теперь должен найти по крайней мере одно из более недавних видео. Может быть, одно месячной давности, но он определенно будет искать в Интернете. он не будет обучен, э, на данных. Так что самое недавно загруженное видео называется "Crash Course по визуализации данных на Python". Теперь это не совсем так, но это довольно недавнее видео, как вы можете видеть, 13 января. Так что да, он действительно искал информацию в Интернете. Второй инструмент, который я хотел показать вам, это инструмент выполнения кода. Так что мы можем сказать code_execution_tool вместо web_search_tool. И это позволяет нам, или позволяет агенту выполнять Python-код для ответов на вопросы. Например, я мог бы спросить что-то вроде: "Каков факториал, э, скажем, 56?". И когда я запущу это, я надеюсь, получу правильный ответ. Я могу проверить ответ, используя модуль math в Python. Хорошо, кажется, это было, возможно, немного слишком. Давайте спросим 24. Это должно быть нормально. "Каков факториал 24?", и это должно дать мне конкретное число, и я могу посмотреть, является ли это число на самом деле тем, которое я получаю из Python, если я запущу это. Так что я могу сказать import math, и я могу сказать math.factorial(24). Так что вы можете видеть, я получаю ответ здесь, это конкретное число, и если я теперь запущу это, это, по сути, то же самое число. Да. И если я хочу убедиться, что фактически был использован инструмент, я могу сказать response.response.built_in_tool_calls. И это должно показать мне, что code_execution фактически использовался. И вы можете видеть, что код, который выполнялся здесь, это import math, factorial(24) = math.factorial(24). И затем он напечатал factorial(24). Так что это фактически использовало инструмент code_execution. Теперь осталось всего две вещи, которые я хочу показать вам сегодня в этом экспресс-курсе. Одна — это как использовать модели встраивания. Для этого мы скажем from pyantic_ai import Embedder. Так что это для использования моделей встраивания. Так что я могу сказать embed = Embedder, и [кашляет] я могу указать здесь имя модели. Так что OpenAI text-embedding-3-small. И затем то, что я могу сделать, это я могу сказать output, или давайте назовем это embedding, = embed.embed_query. И, например, "Я люблю Python" как утверждение, которое, конечно, приведет к корутинному объекту. Но если мы скажем await, это фактически даст нам результат, и это вектор в n-мерном пространстве, где n — размерность модели встраивания. Я думаю, здесь это, э, 1500 с чем-то. Но это затем, конечно, может быть использовано с FAISS с любым векторным хранилищем для выполнения RAG-приложения. Теперь, наконец, последнее, что я хочу показать вам здесь, это как сделать так, чтобы ваш агент получал доступ к инструментам с MCP-серверов, используя Pyantic AI. Для этого мы перейдем сюда в командную строку. У меня уже есть MCP-сервер здесь. Очень минималистичный. Он просто имеет простой инструмент для получения любимого цвета, возвращает teal. Так что мы сохраняем его очень минималистичным здесь. И теперь я создам MCP-агента, который будет использовать этот инструмент через MCP-сервер. Так что я скажу import asyncio. Я также скажу from pyantic import agent снова. И теперь из pyantic.mcp импортируем mcp. Теперь, где моя автодополнение? О, конечно, нет автодополнения, потому что это pyantic_ai, а не pyantic. Так что pyantic_ai.mcp, а затем mcp_server_streamable_http. Это просто тип транспорта, который мы используем на нашем сервере. И теперь я могу сказать server = mcp_streamable_http. Затем путь к серверу, который будет http://localhost:8000/mcp. И для агента это довольно просто. Мы просто инстанцируем агент, как и раньше. И я забыл еще одну вещь, чтобы это работало. Нам также нужно сказать from.env import load_env. И затем нам нужно вызвать load_env. Но затем мы можем просто сказать, что агент будет использовать OpenAI GPT40. И чтобы теперь получить инструменты с MCP-сервера, нам просто нужно сказать toolsets = server. Так что это автоматически извлекает инструменты, предоставляемые MCP-сервером. И затем я могу просто сказать async def main, а затем result = await agent.run. Затем "Какой мой любимый цвет?". Печатаем result.output. И чтобы запустить все это, конечно, asyncio.run(main()) внутри этого. Так что сначала uv_run mcp_server. Затем во втором терминале нам нужно перейти в каталог туториалов, пример MCP, а затем uv_run mcp_agent. И, как вы можете видеть, это отправило запрос сюда, и "Твой любимый цвет — teal". Так что это все на сегодня. Надеюсь, вам понравилось, и надеюсь, вы что-то узнали. Если так, дайте мне знать, нажав кнопку "лайк" и оставив комментарий в разделе комментариев ниже. Также, если вы заинтересованы, на моем сайте вы найдете вкладку "Услуги" и вкладку "Репетиторство". Там вы можете увидеть, что я могу предложить. Если вам нужна помощь с проектом, если вам нужна помощь от фрилансера, или если вам нужно, чтобы вам что-то объяснили один на один, вы можете связаться со мной там внизу через LinkedIn или по электронной почте. Кроме того, не забудьте подписаться на этот канал и нажать на колокольчик уведомлений, чтобы не пропустить ни одного будущего видео бесплатно. В остальном, большое спасибо за просмотр. Увидимся в следующем видео и пока.