- BrainTools - https://www.braintools.ru -

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

Интеграция с крупными языковыми моделями (LLMs) стала неотъемлемой частью разработки современных приложений. Независимо от того, создаёте ли вы контент, анализируете данные или разрабатываете интерфейсы для общения с пользователем, добавление возможностей, основанных на AI, имеет потенциал как расширить функциональность вашего продукта, так и улучшить пользовательский опыт [1].

Однако успешная интеграция возможностей, основанных на LLM, в приложение может оказаться довольно сложной. Разработчикам приходится ориентироваться в многообразии потенциальных сбоев: ошибки [2] сети, сбои поставщика, ограничения по количеству запросов и многое другое – всё это необходимо обрабатывать, обеспечивая стабильность и отзывчивость приложения для конечного пользователя. Кроме того, различия между API поставщиков языковых моделей могут вынуждать разработчиков писать хрупкий «клейкий код», который в дальнейшем может стать значительным источником технического долга.

Сегодня мы рассмотрим интеграционные пакеты [3] AI от Effect – набор библиотек, спроектированных для упрощения работы с LLM, обеспечения гибкости и независимости от конкретного провайдера.

Почему Effect для AI?

Пакеты AI от Effect предоставляют простые, композиционные строительные блоки для моделирования взаимодействия с LLM в безопасном, декларативном и модульном стиле. С их помощью можно:

🔌 Писать бизнес-логику, независимую от провайдера (provider agnostic)

Опишите взаимодействие с LLM один раз, а затем просто подключите необходимого провайдера. Это позволяет переключаться между любыми поддерживаемыми провайдерами, не затрагивая бизнес-логику.

🧪 Тестировать взаимодействие с LLM

Проводите тестирование, предоставляя моки реализации сервисов, чтобы убедиться, что логика [4], зависящая от 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 [5].

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 [6]:

Пример в котором пишется стриминговый ответ на консоль
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 [7]».

Интеграционные пакеты Effect AI находятся на стадии экспериментов/альфа, но мы настоятельно рекомендуем вам опробовать их и предоставить обратную связь, которая поможет нам улучшить и расширить их возможности.

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


Автор: kondaurovDev

Источник [9]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/13812

URLs in this post:

[1] опыт: http://www.braintools.ru/article/6952

[2] ошибки: http://www.braintools.ru/article/4192

[3] интеграционные пакеты: https://Effect%E2%80%99s%20AI%20integration%20packages

[4] логика: http://www.braintools.ru/article/7640

[5] AiPlan: https://effect.website/docs/ai/planning-llm-interactions/

[6] Stream: https://effect.website/docs/stream/introduction/

[7] Getting Started: https://effect.website/docs/ai/getting-started/

[8] полной документацией: https://effect.website/docs/ai/introduction/

[9] Источник: https://habr.com/ru/articles/896686/?utm_source=habrahabr&utm_medium=rss&utm_campaign=896686

www.BrainTools.ru

Рейтинг@Mail.ru
Rambler's Top100