Питоняшка
СтатистикаВаш telegram-гид в мире Python и IT🌐 — Вопросы и предложения принимаем здесь: 👉 pythonyashkapy@yandex.ru 👉 https://t.me/avtozavodetz
- Последний пост
- 4 авг.
- Последнее чтение
- ещё не заходили
- Постов за неделю
- 0
- Всего постов
- 41
- Тип
- открытый
- Язык
- русский
- В каталоге с
- 13 авг.
- 1/24сутки в ленте
- 31
- 1/48двое суток
- 35
- 1/72трое суток
- 38
Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.
Посты
Добрый день, друзья☀️🍀☕ В прошлый раз мы разобрались с навыками, а сегодня мы посмотрим на простейший пример MCP сервиса на Python. Большие языковые модели (LLM)😜 - это, по сути, гигантские массивы чисел, которые генерируют текст на основе входного запроса. Они не имеют доступа к часам, календарям, веб-браузерам, калькуляторам или интерпретаторам. Это ограничение - модель может ответить на вопрос о текущем времени только на основе своих обучающих данных, которые к моменту вашего запроса уже устарели. Именно здесь на помощь приходит Model Context Protocol (MCP) - открытый стандарт, представленный Anthropic в 2024 году. MCP обеспечивает интеграцию между LLM-приложениями и внешними источниками данных и инструментами/навыками. Его часто сравнивают с USB-стандартом для систем ИИ: USB создал универсальный интерфейс для любых устройств, MCP создаёт способ для ИИ-приложений подключаться к различным источникам данных. Архитектура MCP следует клиент-серверной модели и состоит из нескольких компонентов: 1. MCP-хост - это ИИ-приложение, которое хочет получить доступ к данным через MCP. 2. MCP-клиент - компонент внутри хоста, поддерживающий соединение с сервером. 3. MCP-сервер - программа, которая предоставляет определённые возможности через стандартизированный протокол. 4. Источники данных - локальные базы данных, файлы, сервисы и удалённые API. MCP построен вокруг 3 сущностей, предоставляемых сервером: 1. Resources - объекты данных, на которые можно ссылаться и которые можно извлекать (документы, изображения, схемы баз данных). 2. Prompts - шаблоны для эффективного взаимодействия с языковой моделью. 3. Tools/Skills - функции, которые могут быть выполнены LLM для совершения действий, таких как запрос к базе данных или вызов API. Именно инструменты/навыки (tools/skills) являются механизмом, позволяющим LLM взаимодействовать с внешним миром. Инструменты/навыки в MCP используются моделью - это означает, что языковая модель может самостоятельно обнаруживать и вызывать их в процессе беседы. Итак, перейдем к нашему примеру, который позволяет модели понимать текущие дату и время. Мы будем использовать FastMCP и Ollama. Начнем с MCP-сервера: https://inventwithpython.com/files/mcp_server.py MCP-сервер - это компонент, который предоставляет инструменты модели. Обратите внимание, что комментарии и докстринги лучше оставлять, т.к. они используются моделью. Также важно понимать, что вы не запускаете скрипт напрямую. Он запускается фреймворком FastMCP по запросу от клиента. Код клиента будет выглядеть следующим образом: https://inventwithpython.com/files/ollama_client.py Скрипт ollama_client.py - это программа, которую вы запускаете. Клиент подключается к MCP-серверу, получает список доступных инструментов и предоставляет их модели Ollama. В клиентском скрипте параметры сервера: StdioServerParameters указывает, как запустить MCP-сервер. В данном случае мы запускаем python mcp_server.py через стандартный ввод-вывод (stdio). Функция mcp_tools_to_ollama_format() преобразует инструменты из формата MCP в формат, который ожидает Ollama для вызова функции. Программа принимает пользовательский ввод, отправляет его модели Ollama вместе со списком доступных инструментов, обрабатывает вызовы инструментов и возвращает ответ. Когда модель решает вызвать инструмент, мы перехватываем этот вызов, отправляем запрос к MCP-серверу через session.call_tool(), получаем результат и передаём его обратно модели. В MCP докстринги функций - это не просто документация. Они являются частью промпта, который видит модель. Когда модель решает, какой инструмент вызвать, она анализирует: имя функции (должно быть понятным и описательным), докстринг (описывает, что делает функция и когда её следует использовать), тип возвращаемого значения (сообщает модели, что она получит). Поэтому докстринг должен быть максимально чётким и точным. Больше подробных деталей доступно в оригинальной статье 👉 https://inventwithpython.com/blog/basic-mcp-python-example.html #ai #llm #mcp
Доброе утро, друзья☀️☂️🍹 Давненько не было от нас новых публикаций🤷♂️ Надеемся возобновить практику написания интересных постов с информацией о Python и об IT в целом. Все чаще и чаще на собеседованиях встречаются вопросы касательно использования LLMs, рабочих процессов, инструментария и примеров того, как кандидат использует LLM. Один из вопросов, который можно встретить на подобных собеседованиях касается навыков (skills) и MCP (Model Context Protocol). Давайте в двух следующих постах попробуем разобраться что это такое, как оно работает, и рассмотрим наглядный пример. Итак, начнем с навыков (они же skills, они же скилы)🛠️ В контексте LLM агентов навыки - это программные модули, которые расширяют возможности LLM за пределы генерации текста. Если LLM - это "мозг", то навыки - это "руки и инструменты", позволяющие агенту взаимодействовать с внешним миром: искать информацию, запускать код, работать с файлами, отправлять письма, обращаться к базам данных и т.д. С точки зрения LLM, навык - это инструмент, доступный для выполнения задачи, когда собственных знаний модели недостаточно или когда требуется произвести реальное действие. Например: - поиск в интернете - калькулятор или математический движок - интерпретатор кода или работа с БД - отправка сообщений в телеграм или на почту - композитные навыки, которые включают комбинацию из нескольких действий Уже существует довольно много наработанных скилов, например: https://github.com/google/skills, https://github.com/MiniMax-AI/skills, https://github.com/mukul975/Anthropic-Cybersecurity-Skills Допустим, что мы хотим сами добавить какой-то навык. Что нам для этого необходимо сделать? Самый простой пример: Сначала мы создаем целевую функцию с конкретной реализацией. Главное, чтобы у функции были: имя и докстринг описание, которое объясняет что она делает и когда её применять, также желательно использовать аннотации типов или JSON-схему, чтобы LLM знала, какие аргументы и какого типа передавать. Pythondef calculator(expression: str) -> float: """Вычисляет математическое выражение. Используй, когда нужно точно посчитать арифметику.""" import numexpr return numexpr.evaluate(expression).item() Чтобы добавить наш навык в агент у нас есть несколько способов, которые имеют свои плюсы и минусы: 1. Мы можем сынтегрировать навык в код агента (например используя фреймворки типа LangChain, LlamaIndex и т.д., но тут много ограничений и специфики). 2. Можно вынести навык в веб-сервис. 3. Можем использовать MCP-сервер (в данном случае, агент через MCP получает навыки откуда угодно). Как скилы работают во время выполнения? Агент действует по циклу ReAct: 1. Получение задачи. Например: "Посчитай логарифм 100 и умножь на 5. Потом отправь результат на почту my@email.com" 2. LLM анализирует: "Мне нужно вычислить выражение. Мои знания неточны, лучше использовать калькулятор". LLM формирует Action - вызов навыка calculator с параметром "log(100)*5". 3. Далее выполняется действие (Action): среда агента перехватывает запрос, вызывает реальную функцию calculator("log(100)*5"), получает результат 23.03. 4. Результат добавляется в контекст диалога как сообщение от инструмента: "Observation: 23.03". 5. LLM видит результат, решает, что вычисление готово, и думает: "Теперь нужно отправить письмо". Поэтому далее вызывается следующий навык send_email(to="my@email.com", subject="Результат", body="Результат вычисления: 23.03"). Комбинация "LLM + skills + цикл принятия решений" и создает то, что называют агентом. В следующем посте мы разберем пример с MCP сервисом на Python. #ai #llm #skills
Добрый день, друзья☀️🌱☕ Кто тут создает web-приложения с помощью FastAPI?🤨 Мы идем к вам🫵 ...чтоб рассказать про правильную обработку ошибок в этом фреймворке. Ошибки и исключения неизбежны в любом приложении, включая приложения на FastAPI. Неправильная обработка ошибок может нарушить нормальный ход выполнения, раскрыть конфиденциальную информацию и ухудшить пользовательский опыт. Эффективная стратегия обработки ошибок важна для создания надежных и безопасных API. В приложениях FastAPI встречаются несколько ключевых типов ошибок: 1. Внутренняя ошибка сервера (500 Internal Server Error) - возникает при необработанных исключениях во время выполнения и без соответствующего обработчика сервер возвращает ошибку 500, что может раскрыть внутреннюю структуру приложения. 2. Ошибка "Метод не разрешен" (405 Method Not Allowed) - происходит при использовании неподходящего HTTP-метода для эндпоинта и FastAPI автоматически обрабатывает такие случаи через StarletteHTTPException. 3. Ошибка валидации запроса (422 Unprocessable Entity) - возникает при несоответствии входных данных схеме Pydantic, и FastAPI предоставляет детализированные сообщения об ошибках валидации. 4. HTTP-исключения - встроенные исключения, которые разработчики могут вызывать вручную для бизнес-логики с помощью класса HTTPException, указывая статус-код и детальное сообщение. 5. Пользовательские исключения - кастомные классы ошибок, наследуемые от базового Exception, предназначенные для специфических сценариев бизнес-логики. FastAPI предлагает несколько подходов к обработке исключений: - Try-except блоки позволяют перехватывать конкретные исключения в коде эндпоинтов и преобразовывать их в HTTP-ответы с подходящими статус-кодами. Такой подход может приводить к дублированию кода и пропускать некоторые исключения👎 - Пользовательские обработчики исключений через декоратор @app.exception_handler позволяют централизованно обрабатывать все исключения определенного типа в приложении👍 - Доступ к данным запроса в обработчиках реализуется через механизм зависимостей: функция-зависимость сохраняет валидированные данные в request.state.payload, что позволяет обработчикам исключений получать входные параметры для формирования информативных сообщений об ошибках👍 - Глобальный обработчик исключений регистрируется для базового класса Exception и перехватывает любые необработанные ошибки, предотвращая возврат сырых внутренних сообщений и обеспечивая отказоустойчивость приложения👍 Лучшие практики обработки ошибок: 1. Используйте HTTPException для ручного вызова ошибок с четкими статус-кодами и сообщениями - это стандартный механизм FastAPI, автоматически преобразующий исключение в корректный HTTP-ответ. 2. Применяйте JSONResponse в обработчиках исключений, а не вызывайте HTTPException внутри них, чтобы избежать вложенных исключений и обеспечить предсказуемое поведение. 3. Создавайте кастомные классы исключений для ошибок бизнес-логики вместо универсальных HTTP-исключений для упрощения логирования, тестирования и поддержки кода. 4. Добавьте глобальный обработчик исключений для перехвата всех неожиданных ошибок и возврата безопасных сообщений вместо внутренних трейсбеков. 5. Стандартизируйте формат ответов об ошибках во всем приложении, чтобы упростить парсинг на клиентской стороне. 6. Кастомизируйте ответы валидации, переопределив обработчик RequestValidationError, чтобы привести все сообщения об ошибках валидации к единому формату. 7. Логируйте все ошибки перед отправкой ответа клиенту и настройте алерты для критических инцидентов. 8. Никогда не раскрывайте внутренние сообщения Python в ответах пользователям - всегда преобразовывайте их в безопасные публичные сообщения без утечки конфиденциальных данных, API-ключей или персональной информации. Еще больше деталей - в оригинальной статье 👉 https://www.honeybadger.io/blog/fastapi-error-handling/ #itpython #fastapi #http
Доброе утро, друзья☔🌱☕️ 👨💻Представьте, что вы приходите на собеседование на позицию разработчика Python, и вам задают простую на первый взгляд, но нетривиальную на самом деле задачу, которая звучит так... В одной директории находятся три файла: a.py: A = 1; from b import * b.py: from a import *; A += 1 c.py: from a import A; print(A) Вопрос: что выведет команда python c.py? Чтоб ответить на этот вопрос необходимо понимать 2 используемых механизма: 1. Оператор from module import * не создаёт живых ссылок на имена из другого модуля - он копирует текущие привязки имён в пространство имён импортирующего модуля. 2. Python позволяет импортировать модули, загрузка которых ещё не завершена. При импорте модуля Python сначала создаёт его объект и добавляет в sys.modules, лишь затем выполняет код модуля. Если в процессе выполнения встречается from X import Z, интерпретатор проверяет наличие X в sys.modules - и если оно есть, немедленно копирует доступные на данный момент имена, не дожидаясь завершения загрузки модуля X. Это позволяет избежать явного обнаружения циклических зависимостей, но может приводить к неочевидному поведению. Давайте разберем по шагам: 1. Запуск c.py инициирует импорт a. 2. Создаётся объект модуля a, добавляется в sys.modules, начинается выполнение a.py. 3. В a.py устанавливается A = 1 (теперь a.A = 1), затем выполняется from b import *. 4. Создаётся объект модуля b, добавляется в sys.modules, начинается выполнение b.py. 5. В b.py выполняется from a import * - модуль a уже есть в sys.modules, поэтому копируется текущее значение a.A = 1, создавая b.A = 1. 6. Выполняется A += 1 в b.py, что изменяет только b.A (теперь b.A = 2). 7. Загрузка b.py завершается. Управление возвращается в a.py, где завершается from b import *. Эта операция копирует все имена из b, включая b.A = 2, перезаписывая исходное a.A = 1. Теперь a.A = 2. 8. Загрузка a.py завершается. В c.py завершается from a import A, копируя a.A = 2 в локальное пространство имён c. 9. c.py выводит значение 2. Таким образом, несмотря на то, что A += 1 выполняется в модуле b, результат всё равно равен 2, потому что последующий импорт from b import * в a.py перезаписывает значение A в модуле a. Но если мы запустим напрямую python a.py, то возникает ошибка ImportError: cannot import name 'B'. Причина - запускаемый файл выполняется как модуль __main__, а не как модуль a. Поэтому при импорте from b import B в a.py создаётся настоящий модуль a, который пытается импортировать B из b, но в тот момент b.B ещё не определено — возникает циклическая зависимость без возможности разрешения. Вот такая вот веселая задачка с собеседования🤪 Оригинальная статья 👉 https://utcc.utoronto.ca/~cks/space/blog/python/PythonCircularImportPuzzle #itpython #imports
Доброе майское утро, друзья☀️🌱☕️ Сегодня мы поговорим... *неожиданно*... об автомобильных автопилотах🤖🚗 В этом нам поможет следующая статья 👉 https://cardog.app/blog/autonomous-driving-stack-technical-guide Не пересказывая большую оригинальную статью, коротко опишем ее основной посыл. Представьте, что автомобиль с автопилотом — это человек за рулём, только вместо мозга у него компьютер, а вместо глаз и ушей — камеры, радары и лазерные датчики. Вся система устроена по принципу "увидел ➡️ понял ➡️ решил ➡️ сделал", и работает это за доли секунды. Органы чувств автомобиля — это сенсоры. Камеры распознают разметку, светофоры и пешеходов. Лазерный радар (LiDAR) нащупывает пространство вокруг машины, создавая 3D-карту окружения (даже в темноте). Обычный радар следит за скоростью других машин и работает в дождь или туман, когда камеры слепнут. Все эти данные объединяются в единый образ текущего состояния. За понимание дорожной ситуации сегодня отвечают нейросети, обученные на миллионах километров вождения. Современные системы (openpilot) используют единую нейросеть, которая за 25 миллисекунд анализирует 5 секунд видео и выдаёт готовый план движения: куда повернуть, с какой скоростью ехать, кого пропустить. За понимание своего местоположения автомобиль использует сразу несколько источников: комбинирует данные со спутников, встроенных гироскопов и визуальной одометрии — когда камера замечает, что мимо проплыли фонарные столбы, и рассчитывает пройденное расстояние. Математический алгоритм на фильтрах Калмана взвешивает все источники и даёт позицию с точностью до 10 сантиметров. Принятие решение возлагается также на нейросеть, которая обучена на поведении людей, и она сама генерирует плавную траекторию на 10 секунд вперёд — с учётом комфорта, правил и дистанции до впереди идущего авто. "Руки и ноги" автомобиля — это контуры управления. Поперечный контур (руление) работает как водитель, корректирующий курс: если машину сносит вправо — чуть поворачивает руль влево. Продольный контур (газ/тормоз) ведёт себя как участник движения: на свободной дороге разгоняется до заданной скорости, а при приближении к другой машине плавно снижает скорость, сохраняя безопасную дистанцию. Оба контура обновляются 100 раз в секунду — что быстрее возможностей человека. Безопасность должна быть встроена на каждом уровне. Перед отправкой команд на руль или тормоза система проверяет: не превышена ли скорость поворота, не нарушен ли лимит ускорения. Устройство-посредник следит, чтобы ни одна программа случайно не нажала на газ. Если система теряет связь с сенсорами или начинает работать с задержкой — автопилот мгновенно отключается и передаёт управление человеку. Важно понимать, что современные автопилоты — это умные помощники, а не замена водителю. Они отлично справляются с рутиной на шоссе или в пробке, но реальный мир полон неожиданностей: стройка с временной разметкой, нестандартный манёвр другого участника движения, погодные аномалии. На все такие ситуации систему обучить невозможно — их слишком много. Поэтому водитель должен оставаться внимательным, держать руки на руле и быть готовым вмешаться в любой момент. Технология уже делает дороги безопаснее, но окончательная ответственность за управление пока лежит на человеке. Автопилот — это не волшебство, а симфония инженерных решений🎼 Каждый модуль решает свою задачу, но только их слаженная работа 100 раз в секунду позволяет машине двигаться по сложному городу так, будто за рулём сидит внимательный и никогда не устающий водитель — при условии, что настоящий водитель рядом и готов взять управление в свои руки, если потребуется😉 #itcommon #autopilot
Доброе утро, друзья❄️🤔☀️ Сегодня у нас будет несколько необычная и специфичная тема, которая может пригодиться в некоторых случаях при обработке рекурсивных древовидных структур данных Речь пойдет о рекурсивном сопоставлении с шаблоном (pattern matching, появился в Python 3.10). Допустим мы имеем следующий набор, который моделирует булевы выражения: class Expr: pass @dataclass class And(Expr): exprs: list[Expr] @dataclass class Or(Expr): exprs: list[Expr] @dataclass class Not(Expr): expr: Expr @dataclass class Var(Expr): name: str Выражение Not(And([Var("A"), Var("B")])) представляет логическую формулу !(A ∧ B). Такая структура естественным образом образует дерево, где узлы — операторы (And, Or, Not), а листья — переменные (Var). Попробуем написать функцию, которая реализует следующее: evaluate(expression: Expr, assignments: dict) -> bool, и которая рекурсивно вычисляет значение выражения при заданных значениях переменных. Pattern matching позволяет элегантно обработать каждый тип узла: def evaluate(expression: Expr, assignments: dict[str, bool]) -> bool: match expression: case Var(name): return assignments[name] case And(exprs): return all(evaluate(sub, assignments) for sub in exprs) case Or(exprs): return any(evaluate(sub, assignments) for sub in exprs) case Not(expr): return not evaluate(expr, assignments) case _: raise RuntimeError(f"Unknown exp: {type(expression)}") Наша функция разбивает атрибуты прямо в шаблоне (case Var(name)), что упрощает доступ к данным. При этом рекурсия проявляется при обработке подвыражений: для каждого элемента списка exprs функция вызывает саму себя. В следующем примере реализуем форматирование сложных выражений с отступами для наглядного отображения иерархии: def pretty_print(expression: Expr, depth: int = 0, tc: str = "") -> None: indent = " " * 4 * depth match expression: case Var(name): print(f"{indent}Var({name!r}){tc}") case Not(Var(name)): print(f"{indent}Not(Var({name!r})){tc}") case Not(expr): print(f"{indent}Not(") pretty_print(expr, depth + 1) print(f"{indent}){tc}") case And(exprs) | Or(exprs): print(f"{indent}{type(expression).__name__}([") for sub in exprs: pretty_print(sub, depth + 1, ",") print(f"{indent}]){tc}") case _: raise RuntimeError(f"Unknown type: {type(expression)}") Рекурсивный pattern matching превращает обработку древовидных структур в декларативный процесс: вместо ручной проверки типов через isinstance() и извлечения атрибутов, мы описываем желаемую структуру прямо в шаблоне. Это повышает читаемость, снижает вероятность ошибок и делает рекурсивные алгоритмы (вычисление, сериализация, трансформация деревьев) лаконичными и выразительными. Подход особенно ценен при работе с AST, математическими выражениями, конфигурационными структурами и любыми данными с вложенной иерархией. Оригинальная статья 👉 https://mathspp.com/blog/recursive-structural-pattern-matching #itpython #patternmatching
Доброе утро, друзья❄️☃️😲 🤔Вам интересно, сколько времени занимают типовые операции в Python? И сколько памяти потребляется при использовании типовых структур и объектов? Нам — очень! В этом посте собран небольшой справочник по производительности и потреблению памяти в Python, основанный на реальных бенчмарках на современной системе (CPython 3.14.2, Mac Mini M4 Pro)👇 Время типичных операций: - Чтение атрибута (obj.x): ~14 нс - Обращение к ключу в словаре / вызов пустой функции: ~22 нс - Добавление элемента в список (list.append): ~29 нс - Форматирование через f-строку: ~65 нс - Выброс и перехват исключения: ~140 нс - orjson.dumps() для сложного объекта: ~310 нс (0.3 мкс) - json.loads() для простого объекта: ~714 нс (0.7 мкс) - sum() по 1000 целых: ~1.9 мкс - Проход по 1000 элементам списка: ~7.9 мкс - Открытие и закрытие файла: ~9.1 мкс - Запись 1 КБ файла: ~35 мкс - asyncio.run_until_complete() (пустой): ~28 мкс - import json: ~3 мс - import asyncio: ~18 мс - import fastapi: ~104 мс Память (размер объектов): - Число с плавающей точкой (float): 24 байта - Маленькое целое (кэшированное, от –5 до 256): 28 байт - Пустая строка (""): 41 байт - Пустой список ([]): 56 байт - Пустой словарь ({}): 64 байта - Пустое множество (set()): 216 байт - Экземпляр класса с __slots__ (5 атрибутов): 212 байт - Обычный экземпляр класса (5 атрибутов): 694 байта - Список из 1000 целых: ~36 КБ - Словарь из 1000 элементов: ~91 КБ - Пустой Python-процесс: ~16 МБ Функции и исключения: - Пустой вызов функции: ~22 нс - Вызов метода: ~23 нс - try/except без исключения: ~21 нс - try/except с выбросом исключения: ~140 нс Доступ к атрибутам класса: - Обычный класс (чтение атрибута): 14.1 нс - Класс со __slots__ (чтение атрибута): 14.1 нс - Обычный класс (запись атрибута): 15.7 нс - Класс со __slots__ (запись атрибута): 16.4 нс slots позволяют экономить примерно до 30% памяти на больших коллекциях с почти одинаковой скоростью доступа к атрибутам. А вот, сколько стоит по времени вернуть простой JSON ответ (без обработки и без базы данных) в популярных веб-фреймворках: - Django: 18.1 мкс - Flask: 16.5 мкс - FastAPI: 8.63 мкс - Litestar: 8.19 мкс - Starlette: 8.01 мкс Больше интересных измерений можно найти в этой статье 👉 https://mkennedy.codes/posts/python-numbers-every-programmer-should-know/ А мы лишь добавим ключевые выводы из данных цифр: - Объекты в Python имеют большие расходы по памяти (пустой список — 56 байт, пустой словарь — 64 байта). - Словари и множества — очень быстрые для поиска, списки — медленные - orjson и msgspec намного быстрее стандартного json. - FastAPI / Starlette быстрее Flask и Django на простых JSON-эндпоинтах. - __slots__ даёт ~30% экономии памяти для экземпляров классов без потери скорости доступа. - Асинхронность имеет ощутимые накладные расходы; её стоит использовать, когда нужна concurrency, а не просто для модности. #itpython #numbers #duration
Доброе утро, друзья☁️☂️☕️ Типизировать или не типизировать - вопрос сродни гамлетовскому "быть или не быть"?🤴 С тем же экзистенциальным влиянием на судьбу проекта. Поговорим сегодня о продвинутых возможностях типизации в Python 3.13+. Если бы Гамлет начинал работу над большим и амбициозным проектом, то наверняка он бы выбрал путь типизации. Помимо удобств в виде навигации по проекту в любимом IDE, автозаполнении и генерации кода, типизация в Python позволяет согласно статистике сократить количество дефектов на 15%. Итак, посмотрим на продвинутые возможности typing annotations, позволяющие решать проблемы, которых бы у вас не было, если бы вы не использовали typing annotations: 1. assert_never Эта утилита применяется для объявления ветки кода как недостижимой с точки зрения типизатора и опирается на тип Never, представляющий невозможное значение. При работе с объединениями (Union) это даёт возможность проверять исчерпываемость: если к union добавили новый вариант и забыли обработать его в match/if, статический анализ покажет неучтённый путь. 2. get_args Этот инструмент помогает избавиться от дублирования между Literal-типами и боевыми структурами данных. Вместо того, чтобы поддерживать список строк (или других констант) и отдельно Literal[...] с теми же значениями, можно извлекать значения из аннотации и использовать их в рантайме, превращая тип в единственный источник истины. 3. TypeGuard TypeGuard формализует условную типизацию и выносит проверки в отдельные предикаты, позволяя типизатору сужать типы по результатам этих функций. Он особенно полезен для структур, где ковариантность нарушается (например, списки), но при этом умеет сужать тип только в положительной ветке, не уточняя его при False. 4. TypeIs Эта утилита обеспечивает двунаправленное сужение типов, основанное на вычитании множеств в Union. Типизатор может вывести, что если значение не является подтипом из TypeIs, то в else оно относится к оставшейся части union, но для этого требуются отношения подтип–супертип между исходным и целевым типами. Рекомендуется использовать TypeIs по умолчанию, а TypeGuard оставлять для случаев структурной несовместимости из-за инвариантности контейнеров. 5. Overloading Перегрузка аннотаций позволяет описывать несколько сигнатур для одной функции, когда фактический возвращаемый тип однозначно зависит от аргументов. Это особенно полезно, когда разные ветки кода возвращают разные типы, но фактический тип зависит от используемого аргумента. Перегрузка является структурированной формой условной типизации, которая позволяет типизатору выполнять точное сужение на месте вызова. Это означает, что вызывающий код немедленно получает конкретный тип возвращаемого значения, избегая необходимости дополнительных проверок isinstance на результате функции. 6. Unpack Unpack позволяет разворачивать типизированные mapping-структуры в отдельные именованные аргументы функции. Это делает вызовы функций с большим числом связанных параметров безопаснее: статический анализ знает, какие ключи обязательны и какие типы значений ожидаются, и может заранее подсвечивать ошибки. 7. Concatenate Декораторы часто ломают типизацию, когда их аннотируют как Callable[..., R] и теряют исходную сигнатуру. Concatenate с ParamSpec позволяет формально описать изменение сигнатуры: например, Concatenate[logging.Logger, P] информирует типизатор, что обернутая функция ожидает Logger в качестве первого параметра, а затем параметры, захваченные P. Это позволяет типизатору понять, как декоратор изменяет сигнатуру, и обнаруживать недопустимые вызовы, сохраняя при этом целостность декоратора для различных сигнатур функций. Оригинальная статья 👉 https://martynassubonis.substack.com/p/advanced-overlooked-python-typing #itpython #typing
Доброе утро, друзья☔🌱☕️ Сегодня мы разберем, почему глубокое копирование такое медленное🐌, и как его можно избежать😉 copy.deepcopy() является мощным инструментом для создания полностью независимых копий объектов, но её использование часто приводит к серьёзным проблемам с производительностью. Функция рекурсивно обходит каждый вложенный элемент объекта. Этот подход удобен и универсален, но очень затратный по времени и памяти. Также deepcopy должен корректно обрабатывать случаи, когда объекты ссылаются друг на друга по кругу. Чтобы избежать бесконечной рекурсии, функция использует "мемо-словарь", который отслеживает уже скопированные объекты. Проверки и обновления этого словаря добавляют дополнительную нагрузку. Как же избежать использования deepcopy и ускорить код? 1. Использование поверхностного копирования copy.copy(), когда это возможно. Если структура данных состоит в основном из неизменяемых объектов, то глубокое копирование избыточно. Изменение неизменяемого объекта создаст новую ссылку, а не повлияет на оригинал. deepcopy() необходимо только когда есть вложенные изменяемые объекты, которые мы хотим дублировать независимо. 2. Создание собственной реализации глубокого копирования для классов. Можно определить специальный метод __deepcopy__(). Этот метод будет вызван функцией copy.deepcopy(), и он позволяет точно контролировать процесс копирования объекта. 3. Использование __slots__ для уменьшения накладных расходов. __slots__ в классе не только экономит память, но и может ускорить deepcopy, поскольку количество атрибутов объекта фиксировано и известно заранее. 4. Альтернативные сериализации для определенных задач. В некоторых задачах при работе с данными, которые нужно сохранить или передать, сериализация в формат JSON с последующей десериализацией может быть быстрее, чем deepcopy. 5. Пересмотр архитектуры и отказ от копирования. Можно ли реорганизовать код так, чтобы работать с данными, не создавая их полную копию? Использование представлений (views), генераторов или функционального подхода с неизменяемыми структурами данных может полностью устранить необходимость в копировании. Конечно, далеко не всегда удаётся отказаться от глубокого копирования. Но искать способы оптимизации и ускорения кода стоит всегда. Особенно в Python😏 Оригинальная статья 👉 https://www.codeflash.ai/blog-posts/why-pythons-deepcopy-can-be-so-slow-and-how-to-avoid-it #itpython #optimization #deepcopy
Доброе утро, друзья❄️☕️☀️ Давайте сегодня заглянем под капот и поговорим о памяти в Python и о том, как часто Python производит аллокации памяти, что влияет и на эффективность использования ресурсов и самой программы. Будем рассматривать данный вопрос на работе с целыми числами и проверим распространенное мнение, что интерпретатор Python чрезмерно часто выполняет аллокации памяти. Запустим следующий код: for i in range(0, 100_000): print(i + 1) Результат может немного удивить — интерпретатор выделил память 100 904 раза, что почти соответствует количеству итераций! Хорошо, тогда попробуем удалить функцию print: for i in range(0, 100_000): a = i + 1 Количество аллокаций резко сократилось до 905, что указывает на наличие внутренних оптимизаций в Python. Причиной такого поведения является несколько ключевых механизмов оптимизации: 1. Кэш малых целых чисел. Python заранее выделяет объекты для чисел в диапазоне от -5 до 1025 (ранее диапазон был от -5 до 257, но недавно расширен в Python 3.15). Эти объекты не требуют динамического выделения памяти, так как хранятся в статическом массиве _Py_static_objects. При попытке создать целое число в этом диапазоне интерпретатор просто возвращает ссылку на уже существующий объект. 2. Freelist (список свободных блоков). Когда объекты типа PyLongObject освобождаются, они не удаляются полностью, а добавляются в специальный список для повторного использования. В цикле из 100k итераций Python выполнил всего 102 новых аллокации и 99 193 раза переиспользовал существующие объекты. Это объясняет, почему при удалении оператора print количество аллокаций резко сократилось — без вывода на экран объекты могли эффективно переиспользоваться. 3. Специализированный pool-аллокатор памяти. Python использует собственный аллокатор вместо стандартного malloc. Память разбивается на фиксированные пулы для объектов разных размеров. Это снижает фрагментацию и ускоряет операции выделения/освобождения. Базовое хранилище выделяется крупными блоками (аренами) размером 1 МБ или 256 КБ с использованием mmap, при этом физическая память выделяется по требованию. Оператор print приводит к дополнительным аллокациям, так как внутренняя реализация преобразования чисел в строку требует выделения временных объектов PyLongObject для конвертации из внутреннего представления в десятичное представление. Python НЕ использует некоторые продвинутые техники, такие как tagged pointers (используемые в например V8, JSC, LuaJIT), которые позволили бы избежать аллокаций для большинства целых чисел. Вместо этого Python жертвует производительностью ради универсальности представления чисел. Таким образом, мы можем сказать, что аллокации памяти в Python действительно происходят очень часто. Однако благодаря умным оптимизациям реальная стоимость этих аллокаций значительно снижена. Pool-аллокаторы, freelist и кэширование малых целых чисел позволяют минимизировать влияние частых аллокаций на производительность. Ещё больше деталей и примеров — в оригинальной статье 👉 https://zackoverflow.dev/writing/how-often-does-python-allocate/ #itpython #memory #allocation
Доброе утро, друзья❄️🤔☀️ Периодически мы рассматриваем различные трюки и ходы, которые позволяют улучшить производительность Python программ. Сегодня как раз тот самый день😉 Итак, сегодня приведем 10 приёмов для ускорения🚀 Python-кода (какие-то приёмы известные, какие-то, наверняка, еще нет). 1. Использование множества (sets) для проверки вхождения элементов. Поиск элемента в списке происходит за линейное время O(n), тогда как множества реализованы через хэш-таблицы, обеспечивая среднем постоянное время O(1). Это особенно эффективно для больших коллекций, операций с пересечениями, объединениями и проверками дубликатов. 2. Изменение объектов in-place, вместо копирования. Копирование больших списков или словарей приводит к значительным затратам памяти и времени. По возможности изменяйте объекты на месте, используя встроенные методы, такие как sort(), append(), или update(). Работа с ссылками на объекты экономит ресурсы и ускоряет выполнение, что особенно важно в критичных по производительности местах. 3. Использование __slots__ для экономии памяти. По умолчанию атрибуты экземпляров классов хранятся в словаре __dict__, что даёт гибкость, но требует дополнительной памяти. __slots__ позволяет явно задавать фиксированный набор атрибутов, избавляясь от словаря. Это снижает потребление памяти и ускоряет доступ к атрибутам, особенно при создании большого количества экземпляров класса. 4. Использование модуля math для численных операций. Функции модуля math реализованы на С, что обеспечивает большую скорость и точность по сравнению с эквивалентом в чистом Python, например, math.sqrt() быстрее и надежнее, чем возведение в степень 0.5. Это существенно при повторяющихся вычислениях в циклах или больших объёмах данных. 5. Предварительное выделение памяти при заранее известном размере. Динамическое изменение размера списков требует многократного выделения и копирования памяти. Если заранее известен размер, создание заполненного нулями списка с фиксированным размером значительно ускоряет обработку и уменьшает фрагментацию памяти. Это важно для численных и масштабных вычислений. 6. Отказ от обработки исключений в горячих циклах. Исключения в Python дороги по времени из-за необходимости раскрутки стека и переключения контекста. Используйте условные проверки для предотвращения ошибок, оставляя обработку исключений для действительно редких ситуаций. Такой подход даёт заметное увеличение производительности в часто вызываемых циклах. 7. Использование локальных функций для повторяющейся логики. Локальные вложенные функции имеют более быстрый доступ к переменным за счёт локального разрешения имён. Кроме того, они делают код чище и модульнее, уменьшая накладные расходы во внутреннем коде, часто вызывающемся в циклах или при рекурсии. 8. Использование модуля itertools для комбинаторики. Для генерации перестановок, сочетаний и произведений лучше применять itertools, так как его функции реализованы на C и работают лениво, что экономит память и ускоряет вычисления по сравнению с циклическими реализациями. 9. Использование bisect для операций с отсортированными списками. bisect обеспечивает бинарный поиск и вставку за логарифмическое время O(log n), что значительно эффективнее, чем линейный поиск и вставка. Это полезно при работе с динамическими упорядоченными структурами данных. 10. Уклонение от повторных вызовов функций внутри циклов, если результат не меняется. Даже быстрые функции при многократных вызовах вызывают дополнительную нагрузку. Вычисляйте константные значения вне циклов и сохраняйте результат в локальной переменной, что уменьшит количество обращений и улучшит читаемость кода. Оптимизация Python-кода — это не всегда глобальная перестройка. Эффективные приёмы и правильное применение стандартных библиотек, могут существенно повысить производительность приложения. Главное, не забывать при этом о балансе между скоростью, поддерживаемостью и читаемостью кода и принимать решение исходя из ситуации. Оригинальная статья 👉 https://blog.jetbrains.com/pycharm/2025/11/10-smart-performance-hacks-for-faster-python-code/
Добрый день, друзья☀️🌱☕️ Телеграм сбоит, но не сдается👍 Наконец мы на финальном шаге этапа 2, и сегодня мы добавим возможность выполнять сгенерированный код нашим агентом. Сперва мы добавим дополнительные меры предосторожности. Даже если код проходит проверку, нам все равно нужна защита во время выполнения. Мы будем проявлять чрезмерную осторожность и создавать пользовательскую среду Python только с определенными встроенными функциями: # Пользовательский код выполняется ТОЛЬКО с этими функциями safe_builtins = { 'print': print, # Безопасно для вывода 'len': len, # Безопасно для измерения 'range': range, # Безопасно для итерации 'int': int, # Безопасное преобразование типов # ... другие безопасные функции # Заблокированые функции: # - open (нет доступа к файлам) # - __import__ (нет импортов) # - eval/exec (нет динамического выполнения) # - input (нет взаимодействия с пользователем) } Когда агент выполняет код, он запускает его в отдельном процессе: process = await asyncio.create_subprocess_exec( sys.executable, code_file, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=str(self.sandbox_dir) # Изолированная директория ) Это дает следующие возможности: - Изоляция памяти: код не может получить доступ к памяти родительского процесса - Защита от сбоев: если код падает, основная программа продолжает работать - Чистое завершение: можно завершить зависшие процессы - Захват вывода: весь вывод захватывается и контролируется Также мы установим строгие ограничения ресурсов на уровне ОС и для этого будем использовать модуль resource: # Ограничение времени ЦП (предотвращает бесконечные циклы) resource.setrlimit(resource.RLIMIT_CPU, (5, 5)) # Ограничение памяти (предотвращает "бомбы памяти") resource.setrlimit(resource.RLIMIT_AS, (100_000_000, 100_000_000)) # 100MB # Нет дампов ядра (предотвращает заполнение диска) resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) # Ограничение процессов (предотвращает fork-бомбы) resource.setrlimit(resource.RLIMIT_NPROC, (1, 1)) В качестве окончательной меры предосторожности все выполнение кода имеет таймаут: try: stdout, stderr = await asyncio.wait_for( process.communicate(), timeout=10 # Максимум 10 секунд ) except asyncio.TimeoutError: process.kill() # Принудительное завершение return {"error": "Выполнение превысило время ожидания"} Обычно создание изолированной среды требует еще несколько моментов. Но мы пока не будем их рассматривать, чтобы не затягивать серию😏 Итак, у нас есть готовый агент, который может создавать и безопасно выполнять код. На этом мы пока завершим серию по созданию собственного кодинг-агента. В дальнейшем, мы дополним серию этапом 3. Оригинальная статья, по мотивам которой была подготовлена данная серия находится по этой ссылке 👉 https://www.siddharthbharath.com/build-a-coding-agent-python-tutorial/ Надеемся эта серия была увлекательной и интересной😜 #ml #mycodingagent
Доброе утро, друзья❄️🌷☕️ Сегодня перейдем на второй шаг этапа 2 в процессе создания своего кодинг-агента. На втором шаге мы добавим валидатор для проверки того, насколько сгенерированный агентом код безопасен для выполнения. Добавим класс CodeValidator. Этот класс будет использовать абстрактное синтаксическое дерево (AST) для анализа кода перед его запуском. Напомним, что AST — это структура данных, представляющая синтаксис исходного кода в виде дерева. Используется для анализа и манипуляции кода без его выполнения. Но вернемся к классу CodeValidator. Его можно рассматривать как охранника, который осуществляет безопасность. class CodeValidator: def validate(self, code: str) -> Tuple[bool, List[str]]: tree = ast.parse(code) # Преобразуем код в AST дерево self._check_node(tree) # Обходим дерево в поисках опасных элементов return len(self.violations) == 0, self.violations Что будет блокировать наш валидатор: 1. Опасные импорты import os # ЗАБЛОКИРОВАНО - может удалять файлы import subprocess # ЗАБЛОКИРОВАНО - может запускать shell-команды import socket # ЗАБЛОКИРОВАНО - может устанавливать сетевые соединения 2. Операции с файлами open('file.txt', 'w') # ЗАБЛОКИРОВАНО - может перезаписывать файлы with open('/etc/passwd', 'r'): # ЗАБЛОКИРОВАНО - может читать конфиденциальные файлы 3. Опасные встроенные функции eval("malicious_code") # ЗАБЛОКИРОВАНО - выполнение произвольного кода exec("import os; os.system('rm -rf /')") # ЗАБЛОКИРОВАНО __import__('os') # ЗАБЛОКИРОВАНО - динамические импорты 4. Попытки доступа к системе sys.exit() # ЗАБЛОКИРОВАНО - может аварийно завершить программу os.environ['SECRET_KEY'] # ЗАБЛОКИРОВАНО - доступ к окружению Валидатор работает, пробегая AST дерево и проверяя каждый тип ноды: - ast.Import и ast.ImportFrom проверяем на опасные модули - ast.Call проверяем на опасные вызовы функций - ast.Attribute проверяем на опасный доступ к атрибутам Большинство кодинг-агентов на самом деле не блокируют все это. Обычно у них есть система разрешений, чтобы дать пользователям контроль. Но мы изначально будем соблюдать чрезмерную осторожность, и только по мере созревания агента, мы придем к тем самым системам разрешений. Реализовать _check_node() каждый может сам, как ему удобно👨🎓 В следующий раз мы расскажем о завершающем шаге этапа 2: выполнение сгенерированного нашим агентом кода. #ml #mycodingagent
Доброе утро, друзья❄️☀️☕️ Итак, у нас есть кодинг-агент, который может читать и писать код, но мы хотим, чтобы при применении вайб-кодинга он мог тестировать и выполнять код, т.к. ошибки не будут отлаживаться сами по себе. А что же такое вайб-кодинг?😉 Вайб-кодинг — это метод программирования, при котором человек формулирует задачи и желаемую функциональность на обычном человеческом языке, а ИИ на базе больших языковых моделей генерируют соответствующий программный код. При этом разработчик может минимально участвовать в написании кода, используя голосовые или текстовые команды для управления процессом. Этот подход был предложен в начале 2025 года и позволяет создавать программное обеспечение даже без глубоких знаний программирования, хотя для правок и тестирования всё равно потребуется понимание кода. Основная идея в том, что программирование становится процессом общения с ИИ, а не написания кода вручную. Различные исследования показывают разную статистику на тему: "увеличивает ли вайб-кодинг производительность программиста?". Многое зависит от контекста, сложности задачи, способности взаимодействовать с ИИ, используемых моделях и других аспектов. Вернемся к нашему проекту☝️ Все, что нам нужно сделать, это дать агенту новые инструменты для выполнения кода. Основная сложность заключается в обеспечении того, чтобы агент не запускал вредоносный код или случайно не удалил нашу операционную систему😵 Вот почему этап 2 в основном посвящен проверке кода и изоляции. Давайте посмотрим, как. Шаг 1: Рефакторинг кода Прежде чем что-либо делать, давайте отрефакторим наш существующий код для лучшей читаемости и модульности. Вот наша новая структура проекта: coding_agent/ ├── __init__.py # Инициализация пакета ├── config.py # Централизованная конфигурация ├── agent.py # Основной класс CodingAgent ├── tools/ │ ├── __init__.py │ ├── base.py # Интерфейс и реестр инструментов │ ├── file_ops.py # Инструменты операций с файлами │ └── code_exec.py # Инструменты выполнения кода ├── execution/ │ ├── __init__.py │ ├── validator.py # Валидатор на основе AST │ └── executor.py # Изолированный исполнитель └── cli.py # CLI-интерфейс Большая часть кода почти такая же: - config.py содержит параметры конфигурации нашей модели и системный промпт - cli.py — это основной интерфейс командной строки, который мы добавили в конце Этапа 1 - agent.py — это основной класс агента без настройки инструментов, которые переходят в папку tools - file_ops.py — содержит инструменты чтения, записи и поиска файлов Новый код, который нам предстоит добавить — это файл code_exec.py, который содержит метаданные для исполнителя и валидатора, а фактическая реализация этих инструментов находится в папке execution для удобства и лучшей поддержки проекта. Структуру мы обеспечили и в следующем посте мы поработаем над валидатором, как части безопасного выполнения кода. #ml #mycodingagent
Доброе утро, друзья❄️☕️🍰 В прошлый раз мы подготовили всё, чтобы начать использовать нашего готового агента. Давайте добавим функцию main в наш код, чтобы получить интерфейс командной строки и протестируем агента: async def main(): """Основной интерфейс командной строки""" print("Добро пожаловать в Baby Claude Code!!") print("Введите 'exit' или 'quit' для выхода, 'clear' для очистки истории, 'history' для просмотра последних сообщений") print("-" * 50) # Получить API-ключ api_key = os.getenv("BABY_CLAUDE_CODE_ANTHROPIC_API_KEY") if not api_key: api_key = input("Введите ваш Anthropic API-ключ: ").strip() # Инициализировать агента agent = CodingAgent(api_key) while True: try: user_input = input("\n Вы: ").strip() if user_input.lower() in ['exit', 'quit']: print("До свидания!") break elif user_input.lower() == 'clear': agent.messages = [] agent.save_history() print("История очищена!") continue elif user_input.lower() == 'history': print("\nПоследняя история разговора:") for msg in agent.messages[-10:]: role = msg.get("role", "unknown") content = msg.get("content", "") if len(content) > 100: content = content[:100] + "..." timestamp = msg.get("timestamp", "") print(f" [{role}] {content}") continue elif not user_input: continue print("\n Обработка агента...") response = await agent.process_message(user_input) except KeyboardInterrupt: print("\nДо свидания!") break except Exception as e: print(f"\n Ошибка: {e}") if __name__ == "__main__": asyncio.run(main()) Сначала мы передаем задачу методу react_loop, который загружает историю разговора и вызывает Claude. На основе нашего системного промпта и схемы инструментов Claude решает, нужно ли ему использовать инструмент для ответа на наш запрос. Если да, он отправляет запрос на использование инструмента, который мы выполняем. Мы добавляем результаты в нашу историю сообщений и отправляем обратно Claude, и повторяем цикл. Мы продолжаем делать это, пока не останется вызовов инструментов, в этом случае мы предполагаем, что Claude больше нечего делать, и возвращаем окончательный ответ. И у нас есть функционирующий кодинг-агент, который может объяснять кодовые базы, писать новый код и отслеживать разговор! В дальнейшем мы рассмотрим, что нужно сделать для безопасного выполнения кода🛡 #ml #mycodingagent
Доброе утро, друзья❄️☀️☕️ Вот мы и добрались до финального шага под номером 5 для первого этапа создания нашего кодинг-агента — реализации главного цикла ReAct: async def react_loop(self, user_input: str) -> str: # Добавить пользовательское сообщение в историю self.add_message("user", user_input) # Построить начальный список сообщений messages = self.build_messages_list(user_input=user_input) # Отслеживать последний текстовый ответ, чтобы избежать дублирования last_complete_response = None # Ограничение безопасности для предотвращения бесконечных циклов safety_limit = 20 iterations = 0 while iterations < safety_limit: iterations += 1 # Получить ответ от Claude content_blocks, error = await self._call_claude(messages) if error: error_msg = f"Ошибка: {error}" self.add_message("assistant", error_msg) return error_msg # Разобрать ответ на текстовые ответы и вызовы инструментов text_responses, tool_uses = self._parse_claude_response(content_blocks) # Сохранить последний полный текстовый ответ if text_responses: last_complete_response = "\n".join(text_responses) # Если инструменты не использовались, Claude завершил работу - вернуть окончательный ответ if not tool_uses: break # Выполнить инструменты и собрать результаты tool_results = await self._execute_tool_calls(tool_uses) # Построить сообщения для следующей итерации messages = self.build_messages_list( assistant_content=content_blocks, tool_results=tool_results ) # Подготовить окончательный ответ if not last_complete_response: final_response = "Агент не смог сгенерировать ответ." elif iterations >= safety_limit: final_response = f"{last_complete_response}\n(Note: Агент достиг предела обработки. Возможно, необходимо разбить задачу на более мелкие шаги.)" else: final_response = last_complete_response # Сохранить в историю и вернуть self.add_message("assistant", final_response) return final_response async def process_message(self, user_input: str) -> str: """Основная точка входа для обработки пользовательских сообщений""" try: # Использовать цикл ReAct для обработки сообщения response = await self.react_loop(user_input) return response except Exception as e: error_msg = f"Неожиданная ошибка при обработке сообщения: {str(e)}" self.add_message("assistant", error_msg) return error_msg Мы вызываем модель с запросом, и она отвечает. Если агенту нужно использовать инструмент, мы запускаем инструмент и добавляем результат в список сообщений. Затем цикл повторяется. Мы установили ограничение безопасности в 20 шагов, чтобы избежать бесконечных циклов (и чтобы не накапливалось слишком много вызовов API). Когда больше нет вызовов инструментов, мы предполагаем, что работа завершена, и выводим окончательный ответ. Нам также нужно реализовать парсинг ответа от модели: def _parse_claude_response(self, content_blocks: Any) -> Tuple[List[str], List[Any]]: text_responses = [] tool_uses = [] for block in content_blocks: if block.type == "text": text_responses.append(block.text) print(f" {block.text}") elif block.type == "tool_use": tool_uses.append(block) print(f" Вызов инструмента: {block.name}") return text_responses, tool_uses На этом мы завершаем Этап 1! Теперь мы имеем все кирпичики для нашего минимального агента и в следующий раз протестируем его в работе... #ml #mycodingagent
Доброе утро, друзья❄️☕️🍰 Мы продолжаем реализовывать нашего кодинг-агента, и сегодня построим управление контекстом и памятью. Шаг 4: Управление контекстом и памятью Сделаем грубую реализацию памяти, что будет достаточно для минимального жизнеспособного агента. Мы будем записывать наши сообщения и результаты в файл истории. Каждый раз, когда мы запускаем нашего агента, он читает этот файл и загружает полный разговор. Мы можем очистить файл и начать новый разговор. История будет использоваться нашим агентом в качестве контекста, чтобы помнить какие были запросы и какие были ответы, что помогает получать более точный результат. def save_history(self): """Сохранить историю разговора""" try: with open(self.history_file, 'w') as f: json.dump(self.messages, f, indent=2) except Exception as e: print(f"Предупреждение: Не удалось сохранить историю: {e}") def load_history(self): """Загрузить историю разговора""" try: if os.path.exists(self.history_file): with open(self.history_file, 'r') as f: self.messages = json.load(f) except Exception: self.messages = [] Далее определим функции для управления контекстом. Мы будем просто отслеживать историю разговора и строить список сообщений. def add_message(self, role: str, content: str): """Добавить сообщение в историю разговора""" self.messages.append({"role": role, "content": content}) self.save_history() def build_messages_list(self, user_input: Optional[str] = None, tool_results: Optional[List[Dict]] = None, assistant_content: Optional[Any] = None, max_history: int = 20) -> List[Dict]: """Построить чистый список сообщений для вызова API""" messages = [] # Добавить историю разговора (ограниченную недавними сообщениями для контекстного окна) start_idx = max(0, len(self.messages) - max_history) for msg in self.messages[start_idx:]: if isinstance(msg, dict) and "role" in msg and "content" in msg: # Очистить сообщение для совместимости с API clean_msg = {"role": msg["role"], "content": msg["content"]} messages.append(clean_msg) # Добавить новый пользовательский ввод, если предоставлен if user_input: messages.append({"role": "user", "content": user_input}) # Добавить контент ассистента, если предоставлен (для продолжения использования инструментов) if assistant_content: messages.append({"role": "assistant", "content": assistant_content}) # Добавить результаты инструментов как пользовательское сообщение, если предоставлены if tool_results: messages.append({ "role": "user", "content": [ { "type": "tool_result", "tool_use_id": tr["tool_use_id"], "content": tr["content"] } for tr in tool_results ] }) return messages Отлично! С памятью и контекстом разобрались. В следующем посте мы реализуем главный цикл ReAct! #ml #mycodingagent
Доброе утро, друзья❄️☀️☕️ Мы уже сделали 2 шага на пути к минимальному жизнеспособному кодинг-агенту. Сегодня шаг будет посвящен определению логики инструментов. Шаг 3: Определение логики инструментов Определим логику инструментов. Вот как это будет выглядеть для инструмента "read_file": async def _read_file(self, path: str) -> Dict[str, Any]: """Прочитать файл и вернуть его содержимое""" try: file_path = (self.working_directory / path).resolve() if not str(file_path).startswith(str(self.working_directory)): return {"error": "Доступ запрещен: путь вне рабочей директории"} with open(file_path, 'r', encoding='utf-8') as f: content = f.read() return {"success": True, "content": content, "path": str(file_path)} except Exception as e: return {"error": f"Не удалось прочитать файл: {str(e)}"} Аналогичным образом мы определяем остальные инструменты и добавляем их в схему инструментов. Мы реализовали чтение. Нам также может понадобится запись в файл, вывод списка файлов, поиск файлов и другой функционал. Реализацию записи, вывода списка и поиск мы оставляем вам для самостоятельной проработки🤓 Нам также будет необходима функция для вызова инструмента, которую мы будем выполнять, если наша модель ответит запросом на использование инструмента. Её реализация может выглядеть следующим образом: async def _execute_tool_calls(self, tool_uses: List[Any]) -> List[Dict]: tool_results = [] for tool_use in tool_uses: print(f" Выполнение: {tool_use.name}") try: if tool_use.name == "read_file": result = await self._read_file(tool_use.input.get("path", "")) elif tool_use.name == "write_file": result = await self._write_file(tool_use.input.get("path", ""), tool_use.input.get("content", "")) elif tool_use.name == "list_files": result = await self._list_files(tool_use.input.get("path", ".")) elif tool_use.name == "search_files": result = await self._search_files(tool_use.input.get("pattern", ""), tool_use.input.get("path", ".")) else: result = {"error": f"Неизвестный инструмент: {tool_use.name}"} except Exception as e: result = {"error": f"Выполнение инструмента не удалось: {str(e)}"} # Запись успеха/ошибки if "success" in result and result["success"]: print(f"Инструмент выполнен успешно") elif "error" in result: print(f"Ошибка: {result['error']}") # Сбор результатов для API tool_results.append({ "tool_use_id": tool_use.id, "content": json.dumps(result) }) return tool_results Теперь наш мозг связан с инструментами🧠🛠 На следующем шаге, который будет описан в следующем посте, мы построим управление контекстом и памятью... #ml #mycodingagent
Доброе утро, друзья❄️☃️☕️ В прошлый раз мы начали реализацию нашего кодинг-агента. На первом шаге мы заложили фундамент для мозга агента. Сегодня мы сделаем следующий шаг — дадим агенту инструкции. Шаг 2: Предоставление инструкций Когда мы инициализировали клиента, мы передали ему системный промпт (SYSTEM_PROMPT) и схему инструментов (TOOL_SCHEMA). Это инструкции, которые мы даем модели, чтобы она знала, как себя вести и какие инструменты у нее есть. Напомним, что системный промпт — это начальный набор инструкций, который задает поведение и цели агента, определяя его особенности и ограничения. Системный промпт может выглядеть следующим образом: SYSTEM_PROMPT = """Вы являетесь полезным кодинг-агентом, который помогает с программированием и операциями с файлами. При ответе на запросы: 1. Проанализируйте, что нужно пользователю 2. Используйте минимально необходимое количество инструментов для выполнения задачи 3. После использования инструментов предоставьте краткое резюме выполненных действий ВАЖНО: Как только вы завершите запрошенную задачу, ПРЕКРАТИТЕ и предоставьте окончательный ответ. Не продолжайте создавать дополнительные файлы или выполнять дополнительные действия, если это не указано явно. Примеры хорошего поведения: - Пользователь: "Создайте файл, который складывает числа" → Создайте ОДИН файл, затем резюмируйте - Пользователь: "Создайте файлы для сложения и вычитания" → Создайте ТОЛЬКО эти два файла, затем резюмируйте - Пользователь: "Создайте файлы операций с математикой" → Запросите уточнение по операциям или создайте разумный набор и остановитесь После получения результатов инструментов: - Если задача завершена, предоставьте окончательное резюме - Продолжайте использовать дополнительные инструменты, только если исходный запрос еще не выполнен - Не интерпретируйте успешное выполнение инструмента как запрос на выполнение дополнительных действий Будьте краткими и эффективными. Выполните запрошенную задачу и остановитесь.""" В зависимости от целей и предпочтений можно изменить системный промпт под себя. Современные модели имеют возможность использования инструментов, и нам просто нужно отправить схему заранее, чтобы, когда модель рассуждает, она могла посмотреть на список инструментов и решить, нужны ли они для выполнения задачи. TOOLS_SCHEMA = [ { "name": "read_file", "description": "Прочитать содержимое файла", "input_schema": { "type": "object", "properties": { "path": {"type": "string", "description": "Путь к файлу для чтения"} }, "required": ["path"] } }, { # Другие определения инструментов следуют аналогичному шаблону } ] На этом шаг 2 завершен, и мы готовы сделать следующий шаг — определить логику инструментов, что и сделаем в следующем посте... #ml #mycodingagent #claudesonnet
Доброе утро, друзья❄️🎄☕️ Продолжаем создавать нашего кодинг-агента🤖 В прошлый раз мы теоретически обговорили некоторые моменты нашего решения. Прежде, чем переходить к описанию этапов разработки и непосредственно к реализации, необходимо сделать важное замечание: для кодинг-агентов нужен изолированный режим выполнения (песочница). Наш агент будет писать и выполнять код на рабочей машине. Без надлежащей изоляции мы фактически дадим очень активному и неутомимому стажеру root-доступ к нашей системе. Песочница в данном случае — изолированная среда выполнения, которая ограничивает возможности программы, предотвращая доступ к системным ресурсам и защищая основную систему от потенциально вредоносных действий. Итак, наш план состоит из следующих этапов: Этап 1: Минимально жизнеспособный агент — реализация основного цикла ReAct, который выполняет базовые операции с файлами. К концу этого этапа у нас будет агент, который может читать файлы, понимать простые задачи и пошагово рассуждать над решениями. Этап 2: Фундамент безопасного выполнения кода — добавление возможности генерировать и безопасно выполнять код. Здесь мы реализуем валидацию на основе абстрактного синтаксического дерева (AST) и изоляцию процесса. Наш агент сможет писать код на Python, тестировать его и итерировать на основе результатов. Этап 3: Управление контекстом для больших кодовых баз — масштабирование за пределы учебных примеров до реальных проектов. Реализация поиска и интеллектуальное извлечение контекста, чтобы наш агент мог работать с кодовыми базами, содержащими сотни файлов. Этот этап мы реализуем, возможно, чуть позже. В течение следующих нескольких постов, мы будем реализовывать этап 1 — минимально жизнеспособный агент. Начнем! Создаём файл agent.py, в котором будет реализован наш агент. Пока вся реализация будет в одном файле. Затем мы сделаем рефакторинг и структурируем проект. Шаг 1: Закладка фундамента для мозга Все помещается в один большой класс CodingAgent. Мы инициализируем клиент Anthropic и устанавливаем рабочую директорию: class CodingAgent: def __init__(self, api_key: str, working_directory: str = ".", history_file: str = "agent_history.json"): self.client = anthropic.Anthropic(api_key=api_key) self.working_directory = Path(working_directory).resolve() self.history_file = history_file self.messages: List[Dict] = [] self.load_history() history_file — это наша примитивная память и система управления контекстом. Мы вернемся к этому позже. Мы будем использовать Sonnet 4 в качестве основной модели. Это надежная модель рассуждений и она хорошо справляется с программированием. async def _call_claude(self, messages: List[Dict]) -> Tuple[Any, Optional[str]]: try: response = self.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4000, system=SYSTEM_PROMPT, tools=TOOLS_SCHEMA, messages=messages, temperature=0.7 ) return response.content, None except anthropic.APIError as e: return None, f"API Error: {str(e)}" except Exception as e: return None, f"Unexpected error calling Claude API: {str(e)}" И это всё!🙂 Это шаблонный код для вызова модели Claude. Gemini, GPT, DeepSeek и другие отличаются, но пока мы используем модель рассуждений, все будет в порядке😉 В следующем посте мы предоставим агенту инструкции... #ml #mycodingagent #claudesonnet