Разработка AI‑приложений с Effect

от автора

Интеграция с крупными языковыми моделями (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 )

В приведённом примере продемонстрированы основные шаги:

  1. Определяется взаимодействие с AI, независимое от провайдера.

  2. Создается AiModel для конкретного провайдера и модели.

  3. Разрабатывается программа, использующая данную модель.

  4. Создается слой (Layer), предоставляющий клиент OpenAI.

  5. Предоставляется HTTP-клиент.

  6. Программа запускается с нужными зависимостями.

Расширенные возможности

Обработка ошибок

Одна из сильных сторон 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 находятся на стадии экспериментов/альфа, но мы настоятельно рекомендуем вам опробовать их и предоставить обратную связь, которая поможет нам улучшить и расширить их возможности.

Мы с нетерпением ждём увидеть ваши проекты! Ознакомьтесь с полной документацией для более глубокого погружения и присоединяйтесь к нашему сообществу, чтобы делиться опытом и получать помощь.



ссылка на оригинал статьи https://habr.com/ru/articles/896686/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *