Разработка AI‑приложений с Effect. agentic ai.. agentic ai. Antropic.. agentic ai. Antropic. effect.. agentic ai. Antropic. effect. llm.. agentic ai. Antropic. effect. llm. openai.. agentic ai. Antropic. effect. llm. openai. TypeScript.. agentic ai. Antropic. effect. llm. openai. TypeScript. искусственный интеллект.. agentic ai. Antropic. effect. llm. openai. TypeScript. искусственный интеллект. Программирование.. agentic ai. Antropic. effect. llm. openai. TypeScript. искусственный интеллект. Программирование. Промышленное программирование.. agentic ai. Antropic. effect. llm. openai. TypeScript. искусственный интеллект. Программирование. Промышленное программирование. Функциональное программирование.

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

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


Автор: kondaurovDev

Источник

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