Интеграция с крупными языковыми моделями (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 находятся на стадии экспериментов/альфа, но мы настоятельно рекомендуем вам опробовать их и предоставить обратную связь, которая поможет нам улучшить и расширить их возможности.
Мы с нетерпением ждём увидеть ваши проекты! Ознакомьтесь с полной документацией для более глубокого погружения и присоединяйтесь к нашему сообществу, чтобы делиться опытом и получать помощь.
Автор: kondaurovDev