Интеграция с крупными языковыми моделями (LLMs) стала неотъемлемой частью разработки современных приложений. Независимо от того, создаёте ли вы контент, анализируете данные или разрабатываете интерфейсы для общения с пользователем, добавление возможностей, основанных на AI, имеет потенциал как расширить функциональность вашего продукта, так и улучшить пользовательский опыт.
Однако успешная интеграция возможностей, основанных на LLM, в приложение может оказаться довольно сложной. Разработчикам приходится ориентироваться в многообразии потенциальных сбоев: ошибки сети, сбои поставщика, ограничения по количеству запросов и многое другое – всё это необходимо обрабатывать, обеспечивая стабильность и отзывчивость приложения для конечного пользователя. Кроме того, различия между API поставщиков языковых моделей могут вынуждать разработчиков писать хрупкий «клейкий код», который в дальнейшем может стать значительным источником технического долга.
Сегодня мы рассмотрим интеграционные пакеты AI от Effect – набор библиотек, спроектированных для упрощения работы с LLM, обеспечения гибкости и независимости от конкретного провайдера.
Почему Effect для AI?
Пакеты AI от Effect предоставляют простые, композиционные строительные блоки для моделирования взаимодействия с LLM в безопасном, декларативном и модульном стиле. С их помощью можно:
🔌 Писать бизнес-логику, независимую от провайдера (provider agnostic)
Опишите взаимодействие с LLM один раз, а затем просто подключите необходимого провайдера. Это позволяет переключаться между любыми поддерживаемыми провайдерами, не затрагивая бизнес-логику.
🧪 Тестировать взаимодействие с LLM
Проводите тестирование, предоставляя моки реализации сервисов, чтобы убедиться, что логика, зависящая от AI, выполняется так, как ожидается.
🧵 Использовать структурированную конкурентность (structured concurrency)
Запускайте параллельные вызовы LLM, отменяйте устаревшие запросы, реализуйте стриминг частичных результатов или организуйте «гонки» между несколькими провайдерами – всё это безопасно управляется моделью структурированной конкурентности от Effect.
🔍 Получать расширенную наблюдаемость (observability)
Инструментируйте взаимодействие с LLM с помощью встроенного трейсинга, логирования и метрик, чтобы выявлять узкие места в производительности или сбои в продакшне.
Понимание экосистемы пакетов
Экосистема AI от Effect состоит из нескольких узкоспециализированных пакетов, каждый из которых выполняет свою задачу:
@effect/ai:базовый пакет, который определяет независимые от провайдера сервисы и абстракции для взаимодействия с LLM.@effect/ai-openai:конкретные реализации AI-сервисов на базе API OpenAI.@effect/ai-anthropic:конкретные реализации AI-сервисов на базе API Anthropic.
Такая архитектура позволяет описывать взаимодействия с LLM с помощью сервисов, не привязанных к конкретному провайдеру, и затем подключать конкретную реализацию при запуске программы.
Ключевые концепции
Provider-Agnostic программирование
Основополагающая философия интеграций AI от Effect заключается в программировании, независимом от провайдера.
Вместо того чтобы захардкодить вызовы API конкретного провайдера LLM, вы описываете взаимодействие с помощью универсальных сервисов базового пакета @effect/ai.
Пример эффекта который генерирует шутку (читайте комментарии)
import { Completions } from "@effect/ai" import { Effect } from "effect" // Define a provider-agnostic AI interaction const generateDadJoke = Effect.gen(function*() { // Get the Completions service from the Effect environment const completions = yield* Completions.Completions // Use the service to generate text const response = yield* completions.create("Generate a dad joke") // Return the response return response })
Это разделение ответственности лежит в основе подхода Effect к взаимодействию с LLM.
Абстракция AiModel
Чтобы преодолеть разрыв между бизнес-логикой, независимой от провайдера, и конкретными провайдерами LLM, Effect вводит абстракцию AiModel.
AiModel представляет собой конкретную LLM от определённого провайдера, которая может удовлетворять требованиям сервиса, таким как Completions или Embeddings.
Пример создание AiModel "openai gpt-4o" (читайте комментарии)
import { OpenAiCompletions } from "@effect/ai-openai" import { Effect } from "effect" import { Completions } from "@effect/ai" // Define a provider-agnostic AI interaction const generateDadJoke = Effect.gen(function*() { // Get the Completions service from the Effect environment const completions = yield* Completions.Completions // Use the service to generate text const response = yield* completions.create("Generate a dad joke") // Return the response return response }) // Create an AiModel for OpenAI's GPT-4o const Gpt4o = OpenAiCompletions.model("gpt-4o") // Use the model to provide the Completions service to our program const main = Effect.gen(function*() { // Build the AiModel into a Provider const gpt4o = yield* Gpt4o // Provide the implementation to our generateDadJoke program const response = yield* gpt4o.provide(generateDadJoke) console.log(response.text) })
Преимущества данного подхода:
Переиспользуемость: можно использовать одну и ту же модель для нескольких операций
Гибкость: легко переключаться между провайдерами или моделями по мере необходимости
Абстрагирование: выделяйте логику AI в сервисы, скрывающие детали реализации.
End-to-End пример
Рассмотрим полный пример настройки взаимодействия с LLM с использованием Effect
Код полного примера
import { OpenAiClient, OpenAiCompletions } from "@effect/ai-openai" import { Completions } from "@effect/ai" import { NodeHttpClient } from "@effect/platform-node" import { Config, Effect, Layer } from "effect" // 1. Define our provider-agnostic AI interaction const generateDadJoke = Effect.gen(function*() { const completions = yield* Completions.Completions const response = yield* completions.create("Generate a dad joke") return response }) // 2. Create an AiModel for a specific provider and model const Gpt4o = OpenAiCompletions.model("gpt-4o") // 3. Create a program that uses the model const main = Effect.gen(function*() { const gpt4o = yield* Gpt4o const response = yield* gpt4o.provide(generateDadJoke) console.log(response.text) }) // 4. Create a Layer that provides the OpenAI client const OpenAi = OpenAiClient.layerConfig({ apiKey: Config.redacted("OPENAI_API_KEY") }) // 5. Provide an HTTP client implementation const OpenAiWithHttp = Layer.provide(OpenAi, NodeHttpClient.layerUndici) // 6. Run the program with the provided dependencies main.pipe( Effect.provide(OpenAiWithHttp), Effect.runPromise )
В приведённом примере продемонстрированы основные шаги:
Определяется взаимодействие с AI, независимое от провайдера.
Создается
AiModelдля конкретного провайдера и модели.Разрабатывается программа, использующая данную модель.
Создается слой (Layer), предоставляющий клиент OpenAI.
Предоставляется HTTP-клиент.
Программа запускается с нужными зависимостями.
Расширенные возможности
Обработка ошибок
Одна из сильных сторон Effect – его надёжная обработка ошибок, что особенно ценно при взаимодействии с LLM, где возможные сценарии сбоев могут быть сложными и разнообразными. С помощью Effect ошибки типизированы и могут обрабатываться явно.
Например, если программу генерации шутки необходимо переписать так, чтобы она могла завершаться с ошибками RateLimitError или InvalidInputError, можно прописать соответствующую логику обработки ошибок.
Пример со стратегией восстановления с конкретных ошибок
import { AiResponse, AiRole } from "@effect/ai" import { Effect } from "effect" import { Completions } from "@effect/ai" import { Data } from "effect" class RateLimitError extends Data.TaggedError("RateLimitError") {} class InvalidInputError extends Data.TaggedError("InvalidInputError") {} declare const generateDadJoke: Effect.Effect< AiResponse.AiResponse, RateLimitError | InvalidInputError, Completions.Completions > const withErrorHandling = generateDadJoke.pipe( Effect.catchTags({ RateLimitError: (error) => Effect.logError("Rate limited, retrying in a moment").pipe( Effect.delay("1 seconds"), Effect.andThen(generateDadJoke) ), InvalidInputError: (error) => Effect.succeed(AiResponse.AiResponse.fromText({ role: AiRole.model, content: "I couldn't generate a joke right now." })) }) )
Планы структурированного выполнения
Для более сложных сценариев, где требуется высокая надёжность при использовании нескольких провайдеров, Effect предлагает мощную абстракцию AiPlan.
AiPlan позволяет создавать структурированные планы выполнения для взаимодействия с LLM с встроенной логикой повторных попыток, стратегиями запасного варианта (fall-back) и обработкой ошибок.
Пример в котором будет использован Anthropic если 3 раза получили сетевую ошибку от OpenAi (читайте комментарии)
import { AiPlan } from "@effect/ai" import { OpenAiCompletions } from "@effect/ai-openai" import { AnthropicCompletions } from "@effect/ai-anthropic" import { Data, Effect, Schedule } from "effect" import { Completions } from "@effect/ai" const generateDadJoke = Effect.gen(function*() { const completions = yield* Completions.Completions const response = yield* completions.create("Generate a dad joke") return response }) // Define domain-specific error types class NetworkError extends Data.TaggedError("NetworkError") {} class ProviderOutage extends Data.TaggedError("ProviderOutage") {} // Build a resilient plan that: // - Attempts to use OpenAI's `"gpt-4o"` model up to 3 times // - Waits with an exponential backoff between attempts // - Only re-attempts the call to OpenAI if the error is a `NetworkError` // - Falls back to using Anthropic otherwise const DadJokePlan = AiPlan.fromModel(OpenAiCompletions.model("gpt-4o"), { attempts: 3, schedule: Schedule.exponential("100 millis"), while: (error: NetworkError | ProviderOutage) => error._tag === "NetworkError" }).pipe( AiPlan.withFallback({ model: AnthropicCompletions.model("claude-3-7-sonnet-latest"), }) ) // Use the plan just like an AiModel const main = Effect.gen(function*() { const plan = yield* DadJokePlan const response = yield* plan.provide(generateDadJoke) })
С помощью AiPlan можно:
Создавать сложные политики повторных попыток с настраиваемыми стратегиями экспоненциальной задержки.
Определять цепочки запасных вариантов между несколькими провайдерами.
Указывать, какие типы ошибок должны инициировать повторные попытки, а какие – запасной вариант.
Это особенно ценно для продакшн-систем, где критична надёжность, так как позволяет использовать нескольких провайдеров LLM в качестве резервных, сохраняя бизнес-логику независимой от конкретного провайдера.
Управление конкурентностью (concurrency control)
Модель структурированной конкурентности Effect облегчает управление параллельными запросами к LLM:
Пример в котором выполняются не больше двух параллельных запросов к LLM
import { Effect } from "effect" import { Completions } from "@effect/ai" const generateDadJoke = Effect.gen(function*() { const completions = yield* Completions.Completions const response = yield* completions.create("Generate a dad joke") return response }) // Generate multiple jokes concurrently const concurrentDadJokes = Effect.all([ generateDadJoke, generateDadJoke, generateDadJoke ], { concurrency: 2 }) // Limit to 2 concurrent requests
Стриминг ответов
Интеграции AI от Effect поддерживают стриминг ответов с использованием типа Stream:
Пример в котором пишется стриминговый ответ на консоль
import { Completions } from "@effect/ai" import { Effect, Stream } from "effect" const streamingJoke = Effect.gen(function*() { const completions = yield* Completions.Completions // Create a streaming response const stream = completions.stream("Tell me a long dad joke") // Process each chunk as it arrives return yield* stream.pipe( Stream.runForEach(chunk => Effect.sync(() => { process.stdout.write(chunk.text) }) ) ) })
Заключение
Неважно, создаёте ли вы интел��ектуального агента, интерактивный чат или систему, использующую LLM для фоновых задач – пакеты AI от Effect предоставляют все необходимые инструменты и даже больше. Наш подход, независимый от провайдера, гарантирует, что ваш код останется адаптируемым по мере развития AI-среды.
Готовы попробовать Effect для вашего следующего AI‑приложения? Обратитесь к руководству «Getting Started».
Интеграционные пакеты Effect AI находятся на стадии экспериментов/альфа, но мы настоятельно рекомендуем вам опробовать их и предоставить обратную связь, которая поможет нам улучшить и расширить их возможности.
Мы с нетерпением ждём увидеть ваши проекты! Ознакомьтесь с полной документацией для более глубокого погружения и присоединяйтесь к нашему сообществу, чтобы делиться опытом и получать помощь.
