Генеративные AI-приложения с использованием Amazon Bedrock начало для разработчиков на Go

Разработка приложений на языке Go с использованием Amazon Bedrock введение в генеративное искусство и искусственный интеллект

Эта статья является вводным руководством для разработчиков Go, которые хотят начать создавать приложения Generative AI с использованием Amazon Bedrock, который является полностью управляемым сервисом, предоставляющим доступ к базовым моделям от Amazon и поставщиков моделей сторонних разработчиков через API.

Мы будем использовать AWS Go SDK для Amazon Bedrock, и мы рассмотрим следующие темы по мере выполнения:

  • API Go Amazon Bedrock и как использовать их для задач, таких как генерация контента
  • Как создать простое чат-приложение и обработать потоковый вывод от базовых моделей Amazon Bedrock
  • Прохождение кода примеров

Примеры кода доступны в этом репозитории GitHub.

Перед началом работы

Вам потребуется установить последнюю версию Go, если ее у вас еще нет.

Убедитесь, что вы настроили и настроили Amazon Bedrock, включая запрос доступа к моделям Foundation.

При выполнении примеров мы будем использовать AWS Go SDK для вызова операций API Amazon Bedrock с нашей локальной машины. Для этого вам нужно:

  1. Предоставить программный доступ с использованием IAM-пользователя/роли.
  2. Предоставьте нижеприведенные разрешения IAM-идентифицируемому субъекту, которым вы пользуетесь:
{    "Version": "2012-10-17",    "Statement": [        {            "Effect": "Allow",            "Action": "bedrock:*",            "Resource": "*"        }    ]}

Примечание об аутентификации в AWS Go SDK

Если вы ранее использовали AWS Go SDK, вам это будет знакомо. Если нет, обратите внимание, что в примерах кода я использовал следующее для загрузки конфигурации и указания учетных данных для аутентификации:

cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))

При инициализации экземпляра aws.Config с помощью config.LoadDefaultConfig, AWS Go SDK использует свою цепочку учетных данных по умолчанию для поиска учетных данных AWS. Вы можете прочитать подробности здесь, но в моем случае у меня уже есть файл credentials в <USER_HOME>/.aws, который обнаруживается и используется SDK.

Типы клиентов Amazon Bedrock

Amazon Bedrock Go SDK поддерживает два типа клиентов:

  1. Первый, bedrock.Client, может использоваться для операций типа планировщика управления, таких как получение информации о базовых моделях Foundation или пользовательских моделях, создание задания на настройку модели для настройки базовой модели и т. д.
  2. bedrockruntime.Client в пакете bedrockruntime используется для выполнения вывода на моделях Foundation (самый интересный момент!).

Список базовых моделей Amazon Bedrock

Чтобы начать, давайте взглянем на простой пример клиента планировщика управления для перечисления базовых моделей в Amazon Bedrock (обработка ошибок, регистрация пропущены):

    region := os.Getenv("AWS_REGION")    if region == "" {        region = defaultRegion    }    cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(region))    bc := bedrock.NewFromConfig(cfg)    fms, err := bc.ListFoundationModels(context.Background(), &bedrock.ListFoundationModelsInput{        //По провайдеру: aws.String("Amazon"),        //По режиму вывода: types.ModelModalityText,    })    for _, fm := range fms.ModelSummaries {        info := fmt.Sprintf("Название: %s | Провайдер: %s | Идентификатор: %s", *fm.ModelName, *fm.ProviderName, *fm.ModelId)        fmt.Println(info)    }

Мы создаем экземпляр bedrock.Client и используем его для получения поддерживаемых моделей Foundation в Amazon Bedrock с помощью API ListFoundationModels.

Клонируем репозиторий GitHub и переходим в корректную директорию:

git clone https://github.com/build-on-aws/amazon-bedrock-go-sdk-examplescd amazon-bedrock-go-sdk-examplesgo mod tidy

Запустите этот пример:

go run bedrock-basic/main.go

Вы должны увидеть список поддерживаемых моделей Foundation.

Обратите внимание, что вы также можете фильтровать по провайдеру, режиму (ввод/вывод) и т. д., указав это в ListFoundationModelsInput.

Вызов модели для вывода результатов (с использованием API bedrockruntime)

Начнем с использования модели Anthropic Claude (v2). Вот пример простого сценария генерации контента с помощью следующего промпта:

<paragraph> "В 1758 году шведский ботаник и зоолог Карл Линней опубликовал в своей работе Systema Naturae двусоставные названия видов (бинарную номенклатуру). Canis - это латинское слово, означающее «собака», и в рамках этого рода он перечислил домашнюю собаку, волка и золотого шакала."</paragraph> Пожалуйста, переформулируйте вышеприведенный абзац так, чтобы его можно было понять для ученика 5 класса. Пожалуйста, выведите вашу переформулировку в тегах <rewrite></rewrite>.

Чтобы запустить программу:

go run claude-content-generation/main.go

Результат может отличаться в вашем случае, но должен быть примерно похож на этот:

<rewrite>Карл Линней был ученым из Швеции, который изучал растения и животных. В 1758 году он опубликовал книгу под названием Systema Naturae, в которой давал всем видам двусоставные названия. Например, он назвал собаку Canis familiaris. Canis - это латинское слово, которое означает "собака". Под названием Canis Линней перечислил домашнюю собаку, волка и золотого шакала. Таким образом, он использовал первое слово Canis для группировки близкородственных животных, таких как собаки, волки и шакалы. Такой способ названия видов двумя словами называется биномиальной номенклатурой и до сих пор используется учеными.</rewrite>

Вот фрагмент кода (за исключением обработки ошибок и т.д.).

    //...    brc := bedrockruntime.NewFromConfig(cfg)    payload := {        Prompt:            fmt.Sprintf(claudePromptFormat, prompt),        MaxTokensToSample: 2048,        Temperature:       0.5,        TopK:              250,        TopP:              1,    }    payloadBytes, err := json.Marshal(payload)    output, err := brc.InvokeModel(context.Background(), &bedrockruntime.InvokeModelInput{        Body:        payloadBytes,        ModelId:     aws.String(claudeV2ModelID),        ContentType: aws.String("application/json"),    })    var resp Response    err = json.Unmarshal(output.Body, &resp)    //.....

Мы получаем экземпляр bedrockruntime.Client и создаем данные, содержащие запрос, который необходимо отправить в Amazon Bedrock (включая промпт). Данные оформлены в формате JSON, и их детали подробно описаны здесь: Параметры вывода для моделей Foundation.

Затем мы включаем полезную нагрузку в вызов InvokeModel. Обратите внимание на ModelId в вызове, который можно получить из списка Идентификаторов базовых моделей. JSON ответ затем преобразуется в структуру Response.

Обратите внимание, что этот “рабочий процесс” (подготовка полезной нагрузки с помощью подсказки, маршалинг полезной нагрузки, вызов модели и размаршалинг) будет общим для наших примеров (и, вероятно, для ваших приложений) с небольшими изменениями в соответствии с моделью/кейсом использования.

Вы также можете попробовать сценарий извлечения информации с использованием этой подсказки:

<directory>Телефонный справочник: Джон Латраб, 800-232-1995, john909709@geemail.comДжози Лана, 800-759-2905, josie@josielananier.comКевен Стивенс, 800-980-7000, drkevin22@geemail.com Телефонный справочник будет обновляться менеджером по персоналу."<directory>Пожалуйста, выведите адреса электронной почты из справочника, по одному на строку, в том порядке, в котором они появляются в тексте. Если в тексте нет адресов электронной почты, выведите "N/A".

Чтобы запустить программу:

go run claude-information-extraction/main.go

Чат: канонический пример GenAI

Мы не можем обойтись без статьи о GenAI без приложения чата, верно?

Продолжая работать с моделью Клод, давайте рассмотрим пример разговора. В то время как вы можете обмениваться отдельными сообщениями, этот пример показывает, как обмениваться несколькими сообщениями (чат) и также сохранять историю разговора.

Поскольку это простая реализация, состояние сохраняется в памяти.

Чтобы запустить приложение:

go run claude-chat/main.go# Если вы хотите вести журнал обмениваемых сообщений с LLM, # запустите программу в режиме подробного выводаго run claude-chat/main.go --verbose

Вот результат разговора, который я провел. Обратите внимание, как последний ответ генерируется на основе предыдущих ответов благодаря сохранению истории чата:

Использование потокового API

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

API InvokeModelWithResponseStream позволяет нам использовать асинхронный подход – также называемый потоковым. Это полезно, если вы хотите отображать ответ пользователю или обрабатывать ответ по мере его поступления; это обеспечивает “отзывчивый” опыт приложения.

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

  <rewrite>Карл Линней был ученым из Швеции, который изучал растения и животных. В 1758 году он опубликовал книгу под названием Systema Naturae, в которой он дал всем видам двусловные имена. Например, он назвал собаку Canis familiaris. Canis - латинское слово для собаки. Под именем Canis Линней перечислил домашнюю собаку, волка и золотого шакала. Таким образом, он использовал первое слово Canis, чтобы объединить близкородственных животных, таких как собаки, волки и шакалы. Такой способ называть виды двусловными словами называется биномиальной номенклатурой и до сих пор используется учеными сегодня.</rewrite>

Запустите приложение:

go run streaming-claude-basic/main.go

Вы должны увидеть вывод, записываемый в консоль, по мере генерации частей Amazon Bedrock.

Давайте посмотрим на код.

Вот первая часть: бизнес, как обычно. Мы создаем заголовок с запросом (и параметрами) и вызываем API InvokeModelWithResponseStream, который возвращает экземпляр bedrockruntime.InvokeModelWithResponseStreamOutput.

//...    brc := bedrockruntime.NewFromConfig(cfg)    payload := Request{        Prompt:            fmt.Sprintf(claudePromptFormat, prompt),        MaxTokensToSample: 2048,        Temperature:       0.5,        TopK:              250,        TopP:              1,    }    payloadBytes, err := json.Marshal(payload)    output, err := brc.InvokeModelWithResponseStream(context.Background(), &bedrockruntime.InvokeModelWithResponseStreamInput{        Body:        payloadBytes,        ModelId:     aws.String(claudeV2ModelID),        ContentType: aws.String("application/json"),    })    //....

Следующая часть отличается по сравнению с синхронным подходом с использованием API InvokeModel. Поскольку экземпляр InvokeModelWithResponseStreamOutput не имеет полного ответа (еще), мы не можем (или не должны) просто возвращать его вызывающей стороне. Вместо этого мы выбираем обрабатывать этот вывод частями с помощью функции processStreamingOutput.

Переданная функция имеет тип type StreamingOutputHandler func(ctx context.Context, part []byte) error, который я определил для предоставления возможности вызывающему приложению указывать, как обрабатывать части вывода – в данном случае мы просто выводим их на консоль (стандартный вывод).

//...    _, err = processStreamingOutput(output, func(ctx context.Context, part []byte) error {        fmt.Print(string(part))        return nil    })    //...

Взгляните на то, что делает функция processStreamingOutput (некоторые части кода опущены для краткости). InvokeModelWithResponseStreamOutput дает нам доступ к каналу событий (типа types.ResponseStream), который содержит данные события. Это всего лишь форматированная строка JSON с частично сгенерированным ответом от LLM; мы преобразуем ее в структуру Response.

Мы вызываем функцию handler (она выводит частичный ответ на консоль) и при этом продолжаем собирать полный ответ, добавляя его части. Затем полный ответ наконец возвращается из функции.

func processStreamingOutput(output *bedrockruntime.InvokeModelWithResponseStreamOutput, handler StreamingOutputHandler) (Response, error) {    var combinedResult string    resp := Response{}    for event := range output.GetStream().Events() {        switch v := event.(type) {        case *types.ResponseStreamMemberChunk:            var resp Response            err := json.NewDecoder(bytes.NewReader(v.Value.Bytes)).Decode(&resp)            if err != nil {                return resp, err            }            handler(context.Background(), []byte(resp.Completion))            combinedResult += resp.Completion            //....    }    resp.Completion = combinedResult    return resp, nil}

Отзывчивое приложение чата благодаря стриминговому API

Теперь, когда вы понимаете, как и почему обрабатывать стриминговые ответы, наше простое приложение чата является идеальным кандидатом для использования этого!

Я не буду проходить код еще раз. Я обновил приложение чата, чтобы использовать API InvokeModelWithResponseStream и обрабатывать ответы согласно предыдущему примеру.

Чтобы запустить новую версию приложения:

go run claude-chat-streaming/main.go

До сих пор мы использовали модель Anthropic Claude v2. Вы также можете попробовать пример модели Cohere для генерации текста. Чтобы запустить: go run cohere-text-generation/main.go

Генерация изображений

Генерация изображений является еще одним основным применением Генеративного Искусственного Интеллекта! В этом примере используется модель Stable Diffusion XL в Amazon Bedrock для генерации изображения на основе заданного стимула и других параметров.

Чтобы попробовать это:

go run stablediffusion-image-gen/main.go "<ваш стимул>"# например, go run stablediffusion-image-gen/main.go "Плантация чая в Шри-Ланке"go run stablediffusion-image-gen/main.go "Ракета, запускающаяся из леса с цветущим садом под голубым небом, мастерский, Джибли"

Вы должны увидеть созданный JPG-файл вывода.

Вот краткое описание кода (без обработки ошибок и т.д.).

Выходная нагрузка от результата вызова InvokeModel преобразуется в структуру Response, которая затем разбирается, чтобы извлечь кодированное в формате base64 изображение ([]byte) и декодировать его с помощью encoding/base64 и записать финальный []byte в выходной файл (формат output-.jpg).

    //...    brc := bedrockruntime.NewFromConfig(cfg)    prompt := os.Args[1]    payload := Request{        TextPrompts: []TextPrompt{{Text: prompt}},        CfgScale:    10,        Seed:        0,        Steps:       50,    }    payloadBytes, err := json.Marshal(payload)    output, err := brc.InvokeModel(context.Background(), &bedrockruntime.InvokeModelInput{        Body:        payloadBytes,        ModelId:     aws.String(stableDiffusionXLModelID),        ContentType: aws.String("application/json"),    })    var resp Response    err = json.Unmarshal(output.Body, &resp)    decoded, err := resp.Artifacts[0].DecodeImage()    outputFile := fmt.Sprintf("output-%d.jpg", time.Now().Unix())    err = os.WriteFile(outputFile, decoded, 0644)    //...

Обратите внимание на параметры модели (CfgScale, Seed и Steps); их значения зависят от вашего случая использования. Например, CfgScale определяет, насколько промпт будет влиять на финальное изображение: используйте меньшее значение, чтобы увеличить случайность при генерации. Подробнее обратитесь к документации Amazon Bedrock Inference Parameters.

Создание эмбеддингов из текста

Текстовые эмбеддинги представляют собой значимые векторные представления структурированного текста, такого как документы, абзацы и предложения. Amazon Bedrock в настоящее время поддерживает модель Titan Embeddings G1 - Text для текстовых эмбеддингов. Она поддерживает поиск текста, семантическую схожесть и кластеризацию. Максимальный размер входного текста составляет 8K токенов, а максимальная длина выходного вектора составляет 1536.

Для запуска примера:

go run titan-text-embedding/main.go "<ваш ввод>"# например, go run titan-text-embedding/main.go "кошка"go run titan-text-embedding/main.go "собака"go run titan-text-embedding/main.go "тираннозавр"

Вероятно, это наименее захватывающий результат, который вы увидите! Правда в том, что по этому срезу float64 сложно что-либо понять.

Он становится более актуальным, когда используется в сочетании с другими компонентами, такими как векторная база данных (для хранения этих эмбеддингов) и такими применениями, как семантический поиск (для использования этих эмбеддингов). Эти темы будут освещены в будущих блог-постах. Пока просто примите к сведению, что “это работает”.

Заключительные мысли

Я надеюсь, что это окажется полезным для разработчиков Go в качестве отправной точки по использованию моделей Foundation на Amazon Bedrock для реализации приложений GenAI.

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