LLM adapter DeepSeek Harness описывает работу по подключению API поставщика модели к контракту среды разработки. DeepSeek Harness — это агент с открытым исходным кодом. В его README сказано, что DeepSeek AI разрабатывает проект. В README он обозначен как предварительная версия для разработчиков и содержится предупреждение о том, что ожидаются изменения, нарушающие совместимость. Перед выпуском адаптера снова ознакомьтесь с актуальной поваренной книгой и кодом репозитория.
Это руководство по стороннему проекту. Оно не устанавливает интеграцию OdiRouter, партнёрство, поддерживаемый маршрут провайдера, цену или доступность модели. Информацию о продукте OdiRouter можно найти на сайте https://odirouter.ai/.
Сначала определите границу адаптера
В поваренной книге адаптер описывается как расширение LlmAdapter, реализующее stream(options: GenerateOptions) и выдающее значения StreamChunk. Регистрация сопоставляет маршруты провайдера с адаптером. options.provider выбирает этот адаптер, а options.model остаётся идентификатором модели провайдера. Таким образом, один адаптер может обслуживать изменяющийся каталог моделей без дополнительной регистрации в жизненном цикле.
Разделяйте зоны ответственности. Типы wire-формата описывают полезную нагрузку провайдера. Сериализация запроса сопоставляет GenerateOptions с этой полезной нагрузкой. Разбор транспорта считывает ответ. Преобразование чанков превращает события провайдера в значения StreamChunk. Класс адаптера координирует работу. Поваренная книга указывает на llm-deepseek и llm-pi-ai как на эталонные структуры, использующие разные подходы к транспорту.
Тестируйте саму регистрацию. В поваренной книге сказано, что дублирующий маршрут провайдера вызывает исключение, тогда как регистрация нескольких маршрутов выполняется успешно или завершается неудачей как единая операция. Проверьте настроенный список маршрутов и поведение очистки вместо того, чтобы полагаться на перезагрузку в среде разработки.
Храните учетные данные в конфигурации
В руководстве используется схема конфигурации с резервными значениями из окружения, включая process.env.MY_API_KEY, передаваемое через cordis.yml. В нем сказано не считывать специальные файлы ключей из кода адаптера.
Не помещайте ключ API в исходный код, тестовый фикстур, строку журнала или конфигурацию, видимую в браузере. Передайте ссылку на учетные данные через конфигурацию. Используйте среду или механизм управления секретами, соответствующие параметрам развертывания, и отредактируйте значения авторизации в диагностических данных. Для потокового перевода не требуется ключ-образец.
Строго соблюдайте условия контракта на потоковую передачу.
Адаптер должен выдавать начало блока, одну или несколько дельт и конец блока с собранным значением. Затем он может выдать токен usage и должен выдать finish с одним терминальным блоком finish.
Сначала должен быть выдан usage, затем finish. После finish ничего не должно следовать. Провайдер может отправить usage после данных о видимом завершении, поэтому дождитесь его маркера окончания, а затем выдайте терминальную последовательность в задокументированном порядке.
Присваивайте содержательному блоку index при его первом появлении. Используйте один и тот же индекс для каждой последующей дельты и для завершения блока. Это позволяет привязать чередующиеся фрагменты текста, рассуждений и вызовов инструментов к одному и тому же содержательному блоку.
Аргументы вызова инструмента остаются необработанными строками JSON на границе адаптера. Передавайте частичные аргументы через argumentsDelta. Если SDK провайдера возвращает объект, сериализуйте его в строковое представление JSON при завершении блока, чтобы итоговый блок вызова инструмента содержал требуемое строковое значение.
Обеспечьте четкие границы для ошибок и отмены
DeepSeek Harness описывает два пути обработки ошибок. stream() выбрасывает LlmError со стабильным кодом при сбоях транспорта или протокола. Сбой провайдера, поступивший в корректном потоке, завершается finish, причина которого — error или aborted. Выберите один вариант исхода для каждого класса сбоев и задокументируйте его для вызывающих сторон.
Передавайте options.signal в fetch или SDK провайдера. Когда вызывающая сторона отменяет запрос, активный запрос должен быть остановлен. Если поле GenerateOptions невозможно поддержать, например список остановки для провайдера без поддержки последовательностей остановки, выбросьте LlmError с UNSUPPORTED. Удаление поля незаметно изменяет запрос, не уведомляя вызывающую сторону.
Некоторым провайдерам в последующем запросе нужны идентификаторы ответов, подписи или другие нативные данные. В таком случае передавайте минимальную JSON-проекцию без потерь в виде finish.replayState и проверяйте её перед повторным построением истории. В руководстве указано, что среда выполнения передаёт состояние воспроизведения только тогда, когда исторический и целевой маршруты принадлежат одному и тому же экземпляру адаптера. Одного лишь имени провайдера или модели недостаточно, чтобы доказать безопасность воспроизведения.
Пусть адаптер сам управляет своими возможностями
В руководстве точные метаданные модели помещаются в resolveModel(). Он может возвращать идентификаторы провайдера и модели вместе с необязательной информацией context и reasoning. Объявляйте defaultEffort только в том случае, если для адаптера заданы параметры по умолчанию. Сохраните список параметров, определяемых адаптером, включая off, если это поддерживается поставщиком исходного кода, вместо того, чтобы придумывать общее написание для каждого канала связи.
Это сохраняет нейтральность контракта тестовой обвязки. Это также означает, что страница документации или метка в пользовательском интерфейсе не являются доказательством того, что поставщик поддерживает ту или иную функцию. Перед включением параметра, специфичного для конкретной модели, ознакомьтесь с текущей документацией поставщика и конфигурацией адаптера.
Тестируйте потоковую передачу и сценарии сбоев
Политика тестирования репозитория предусматривает уровни модульного тестирования, покрытия, e2e-тестов с реальным API, снимков и снимков в веб-браузере. Начните с целевых тестов адаптера, затем выполните команды репозитория, требуемые для изменённого пакета и процесса выпуска. Для теста с работающим провайдером требуются авторизованные учётные данные, и их нельзя раскрывать в выводе.
Используйте тесты, проверяющие следующее поведение:
Обычный текстовый ответ имеет совпадающие начало и конец, упорядоченные дельты,
usageпередfinishи не содержит последующих фрагментов.Вызов инструмента, разделённый между событиями, сохраняет один индекс блока и одно исходное JSON-значение аргументов.
Завершающее событие провайдера, содержащее только usage, всё равно создаёт корректную завершающую последовательность.
Прерывание со стороны вызывающей стороны достигает активного запроса через
options.signal.HTTP-ответ с кодом неуспеха, некорректно сформированное событие и поле запроса, которое провайдер не поддерживает, приводят к задокументированному результату ошибки.
Последующий запрос, зависящий от воспроизведения, принимает только проверенное состояние из пути владения того же адаптера.
Смоделированный транспорт полезен для некорректных кадров и проверки таймингов. Проверка с реальным провайдером может подтвердить работоспособность авторизованного маршрута, если это допускают политика проекта и учётные данные. Ни один из результатов не доказывает совместимость OdiRouter или доступность модели.
Проверьте перед выпуском
Перед выпуском сопоставьте выпускаемый код с текущим руководством, типами StreamChunk и политикой тестирования. Проверьте зарегистрированные маршруты, границу секретов, порядок потока, исходный JSON инструментов, отмену, классификацию ошибок, обработку воспроизведения и средство определения возможностей. DeepSeek Harness находится в предварительной версии для разработчиков, поэтому изменение вышестоящего контракта может потребовать повторной проверки даже после успешного прохождения локальных тестов.