Agency: The Go Way to AI. Часть 1

от автора

Рост генеративного ИИ, API OpenAI и локальные LLM, влияют на то, как мы разрабатываем приложения. У разработчиков на Python и JavaScript есть много инструментов, особо популярен LangChain. Однако, у гошников вариантов меньше. LangChainGo, порт оригинального LangChain, пытается маппить питонячие концепции на го, получается не слишком идеоматично. К тому же, есть ощущение, что LangChain сам по себе переусложнен.

Из-за потребности в простом, но мощном инструменте для Go, мы разработали Agency. Эта простая гошная либа с маленьким ядром, которую мы постарались тщательно спроектировать.

Пример — Продолжение текста

Начнем с самого простого примера — продолжить текст. Для простоты мы разберем его по частям, а затем объединим в единый, целостный кусок.

Для начала создадим «провайдер»:

provider := openai.New(     openai.Params{Key: "YOUR_OPENAI_API_KEY"}, )

Провайдер представляет собой набор операций, реализованных каким-либо внешним сервисом. В данном случае это OpenAI.

Обратите внимание, мы передаем структуру Params. В случае с локальной LLM (должна иметь совместимый API с OpenAI) мы можем передать openai.Params{BaseURL: "YOUR_SERVICE_URL_HERE"}.

Теперь, когда у нас есть провайдер, пора создать операцию:

operation := provider.     TextToText(openai.TextToTextParams{Model: "gpt-3.5-turbo"}).     SetPrompt("You are a helpful assistant that translates English to French")

Метод TextToText, который мы тут вызываем, является конструктором операций — функцией, которая принимает некоторые параметры и возвращает операцию, значение типа agency.Operation.

Этот конструктор операций имеет следующую сигнатуру:

func (p Provider) TextToText(params TextToTextParams) *agency.Operation

Эта структура TextToTextParams специфична для каждого конкретного конструктора операций. Она зависит от того, какой внешний сервис использует провайдер и какую функциональность (модальность) он реализует. Почти любой конструктор позволяет указать Model, но позже мы увидим и различия.

Приведу пример: провайдер Anthropic может иметь другие параметры, нежели OpenAI, а конструктор операций openai.SpeechToText будет иметь параметры отличные от openai.TextToText(из-за использования whisper вместо GPT).

Далее, видите строку SetPrompt("You are a helpful assistant that translates English to French")? Так мы конфигурируем операции. В данном случае мы настраиваем используемый промпт.

Окей, похоже, пришло время поговорить о том, чем являются операции на самом деле. Давайте заглянем в исходный код:

// Operation is basic building block. type Operation struct {     handler OperationHandler     config  *OperationConfig }  // OperationHandler is a function that implements the actual logic. // It could be thought of as an interface that providers must implement. type OperationHandler func(context.Context, Message, *OperationConfig) (Message, error)  // OperationConfig represents abstract operation configuration. // It contains fields for all possible modalities but nothing specific to concrete model implementations. type OperationConfig struct {     Prompt   string     Messages []Message } 

На момент написания статьи библиотека имеет версию v0.1.0 и находится в активной разработке, поэтому детали реализации могут измениться, но основная идея должна остаться — операция состоит из обработчика и конфига.

Теперь давайте посмотрим, что делает метод SetPrompt("..."):

func (p *Operation) SetPrompt(prompt string, args ...any) *Operation {     p.config.Prompt = fmt.Sprintf(prompt, args...)     return p }

Вот и все. Он просто сетит промпт в конфиге и реализует шаблонизацию через fmt.Sprintf.

Вы еще тут? Самое страшное позади!

На самом деле, наша операция готова к использованию! Но нам нужно что-то в качестве входных данных для нашей операции. Давайте-ка создадим сообщение:

input := agency.UserMessage("I love programming.")

Раз уж мы не боимся заглядывать в исходники, то вот вам реализация UserMessage

func UserMessage(content string, args ...any) Message {     s := fmt.Sprintf(content, args...)     return Message{Role: UserRole, Content: []byte(s)} }

Это просто маленький хелпер для Message. Message это абстрактное сообщение, представляющее любое возможное сообщение, с которым работают операции:

type Message struct {     Role    Role     Content []byte }

Мы уже видели это сообщение ранее, в определении операции:

func(context.Context, Message, *OperationConfig) (Message, error)

То есть операция — это функция, которая принимает сообщение в качестве входных данных и возвращает сообщение в качестве выходных данных.

Наконец, осталось выполнить нашу операцию.

output, err := operation.Execute(context.Background(), input) if err != nil {     panic(err) }

Соберем все вместе и получим вот такой код:

provider := openai.New(     openai.Params{Key: os.Getenv("OPENAI_API_KEY")}, )  operation := provider.     TextToText(openai.TextToTextParams{Model: "gpt-3.5-turbo"}).     SetPrompt("You are a helpful assistant that translates English to French")  input := agency.UserMessage("I love programming.") output, err := operation.Execute(context.Background(), input) if err != nil {     panic(err) }  fmt.Println(string(output.Content)) 

Не забудьте вставить свой ключ API OpenAI. Вот мой результат:

J'adore la programmation. 

Похоже, что это работает! Вы, конечно, можете переписать это более компактно, если хотите:

openai.New(openai.Params{Key: "YOUR_OPENAI_API_KEY"}).     TextToText(openai.TextToTextParams{Model: "gpt-3.5-turbo"}).     SetPrompt("You are a helpful assistant that translates English to French").     Execute(context.Background(), agency.UserMessage("I love programming.")) 

Вот и все! Большое спасибо за чтение. Если найдете какие-то ошибки, пожалуйста, оставьте комментарий. Ссылка на этот и многие другие рабочие примеры, а также ссылка на саму библиотеку будут ниже.


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

В следующей серии мы рассмотрим такие темы, как:

  • Как создать чат в 40 строк кода

  • Как комбинировать операции в цепочки для последовательного выполнения

  • Как создавать свои операции

  • Как использовать интерсепторы для наблюдения за выполнением операций

  • Как использовать шаблонизацию запросов

  • Как использовать различные модальности (речь, изображения и т.д.)

  • Как реализовать RAG (используя векторные базы данных)

  • И многое, многое другое!


Ссылки:


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


Комментарии

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

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