Набор для Python устроен симметрично: те же понятия, тот же жизненный цикл, те же границы безопасности. Отличия чисто языковые - синхронный и асинхронный клиенты, типизированные структуры данных и привычная итерация по потокам и страницам. Требуется современная версия языка, а семантика исполнения совпадает с другим набором: локальные файлы остаются на машине, рассуждение идёт через размещённые модели, а автоматический режим требует явного ограничения инструментов. Совпадение здесь не случайность реализации, а заявленное свойство: два набора описывают один и тот же сервис, и различаться они должны формой вызова, а не тем, что вообще разрешено делать.
Полезно один раз увидеть минимальную интеграцию. Ниже - создание агента с моделью, ключом из окружения и локальными параметрами через контекстный менеджер, отправка запроса и печать результата. Контекстный менеджер здесь не украшение: он гарантирует освобождение ресурсов при любом выходе из блока, включая исключение, а в долгоживущем сервисе именно забытые после ошибок агенты становятся источником непонятного расхода.
Набор принимает и обычные словари, но типизированные структуры лучше по двум причинам. Первая практическая: подсказки в редакторе и проверка типов ловят опечатку в имени параметра до запуска, а не в момент, когда сервис уже отработал час с неверной настройкой. Вторая стратегическая: когда схема меняется в новой версии, типизированный код падает на проверке, а словарь молча передаёт неизвестное поле дальше, и вы узнаёте о смене по изменившемуся поведению, а не по ошибке. Для интеграции, которая живёт год, это разница между управляемым обновлением и внезапной поломкой.
Отсюда набор правил для эксплуатации, одинаковый для обоих языков. Версию пакета закрепляют. После обновления прогоняют проверку типов и тесты. Ключ не хранят в исходниках и не собирают в словарь, который потом целиком попадёт в журнал при разборе ошибки. Это скучные пункты, но именно они отличают интеграцию, которую можно передать коллеге, от скрипта, который работает только у автора и только на его машине.
Основная мысль здесь шире, чем язык. Если у вас есть исполнители на двух языках, у них должен быть один контракт: одинаковый способ узнавать доступные модели, одинаковая схема запроса, одинаковые точки контроля разрешений, одинаковые таймауты и отмена, одинаковое хранение событий и одинаковые метрики расхода. Тогда наборы различаются средой выполнения, а не уровнем безопасности, и ответ на вопрос о том, что именно делала автоматика вчера, не зависит от того, на чём она написана.
Без такого контракта возникает знакомая ситуация: на одном языке исполнитель работает в песочнице и пишет метрики, а на другом - нет, потому что писал другой человек в другое время и торопился. Различие обнаруживается в момент инцидента, когда выясняется, что половина автоматизации не имела ограничений, о которых все думали, что они общие. Хуже того, расхождение почти невозможно заметить заранее: оба исполнителя работают, оба дают результат, и отличие видно только в том, чего они не делают.
Выбор между синхронным и асинхронным клиентом кажется вопросом вкуса, но у него есть цена. Синхронный проще и уместен в скриптах и разовых задачах, где никто не ждёт параллельности. Асинхронный нужен сервису, который держит несколько запусков одновременно: запуск - операция длиной в минуты, и поток, ожидающий поток событий, всё это время ничего не делает. Опасность возникает при смешении: одна синхронная блокирующая операция внутри асинхронного цикла останавливает весь цикл, а не только свою задачу. Признак этой ошибки узнаваем: пропускная способность падает до одной задачи за раз, при этом процессор простаивает, а в журналах нет ни ошибок, ни таймаутов. Поэтому выбор клиента делают под способ исполнения, а не под привычку.
Контракт полезно выразить не документом, а кодом - тонкой общей обёрткой в каждом языке. Она берёт на себя ровно шесть вещей: получение ключа из хранилища секретов, узнавание доступных моделей, сборку запроса по общей схеме, точки контроля разрешений, таймауты с отменой, запись событий и метрик расхода. Всё остальное остаётся набором. Граница здесь важна: обёртка не должна повторять программный интерфейс своими словами, иначе её придётся сопровождать при каждом обновлении. Её задача - закрепить политику в одном месте, чтобы новый исполнитель наследовал правила фактом использования, а не аккуратностью автора.
Инженерный вывод простой: язык - это деталь реализации, а контракт - предмет договорённости. Опишите его один раз в виде общей библиотеки-обёртки, и переход между языками перестанет быть источником различий в поведении и в безопасности.
Типичные провалы предсказуемы. Собрать конфигурацию словарём и потерять типизацию вместе с ранней проверкой ошибок. Не закрепить версию пакета и получить разное поведение на разных машинах. Оставить ключ в исходнике или в словаре, попадающем в журнал. Смешать блокирующие вызовы с асинхронным исполнением и потерять параллельность. И развести два языка с разными правилами безопасности вместо общего контракта.
import os
from cursor_sdk import Agent, LocalAgentOptions
with Agent.create(
model="composer-2.5",
api_key=os.environ["CURSOR_API_KEY"],
local=LocalAgentOptions(cwd=os.getcwd()),
) as agent:
result = agent.send("Explain the current test architecture")
print(result.text())
# контекстный менеджер освобождает ресурсы; типизированные параметры ловят смену схемы