Home » Преобразование AWS Lambda в функцию Azure с использованием LLM в Go

Преобразование AWS Lambda в функцию Azure с использованием LLM в Go

Преобразование AWS Lambda в функцию Azure с использованием LLM в Go

Цель этой публикации — показать некоторые стратегии, которые можно использовать для преобразования функций AWS Lambda в функции Azure с использованием LLM.

Отказ от ответственности: Эта статья представляет собой экспериментальную реализацию применения LLM для преобразования функций AWS Lambda в функции Azure. Он не предназначен в качестве четкого руководства по процессу и не гарантирует успешного преобразования. Результат зависит от конкретного кода, который вы собираетесь преобразовать, и используемого вами LLM.

Что потребуется для преобразования лямбда-функции в функцию Azure?

Есть несколько вещей, которые нам необходимо учитывать при использовании лямбда-функций:

1. В отличие от функций Azure, функции AWS Lambda не используют привязки.

2. Входные объекты в точке входа принимаются обработчиком записи как объект формата json.

3. Поскольку привязок вывода не существует, все выходные данные в Lambdas обрабатываются с помощью AWS SDK.

Итак, что нам нужно для преобразования? Разобьем процесс на следующие этапы:

1. Преобразование точки входа Lambda в формат функции Azure.

2. Преобразование любых библиотек с помощью AWS SDK для использования интерфейса, который может подключаться либо к AWS, либо к Azure. Почему? Что ж, если мы выполняем миграцию, мы можем продолжать использовать сервисы AWS до тех пор, пока данные или очереди не будут полностью перенесены.

3. Наконец, необходимо создать файлы конфигурации функции Azure, чтобы иметь возможность запускать функцию, например host.json, local.settings.json и function.json. Этим файлам требуется некоторая информация, которой в настоящее время нет в лямбде, поскольку она определена в конфигурации. например, если привязки ввода предназначены для ввода http или очереди, поэтому информация должна быть предоставлена.

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

Как выглядит лямбда?

Базовая лямбда-функция в Go выглядит так:

package main

import (
    "context"
    "fmt"

    "github.com/aws/aws-lambda-go/lambda"
)

type MyEvent struct {
    Name string `json:"name"`
}

type MyResponse struct {
    Message string `json:"message"`
}

func HandleRequest(ctx context.Context, event *MyEvent) (*MyResponse, error) {
    if event == nil {
        return nil, fmt.Errorf("received nil event")
    }
    message := fmt.Sprintf("Hello %s!", event.Name)
    return &MyResponse{Message: message}, nil
}

func main() {
    lambda.Start(HandleRequest)
}

Этот пример представляет собой простую лямбда-функцию hello world. Он включает в себя некоторые интересные функции, такие как:

Пакет github.com/aws/aws-lambda-go/lambda”.

Основная функция, запускающая лямбду вызовом лямбда.Start(HandleRequest)”.

Обработчик, на который ссылается основная функция с этой сигнатурой: HandleRequest(ctx context.Context, событие *MyEvent) (*MyResponse, ошибка)”.

Интересной особенностью Lambda Functions является то, что обработчик может иметь разные подписи. Например, все эти подписи действительны:

func ()
func () error
func (TIn) error
func () (TOut, error)
func (context.Context) error
func (context.Context, TIn) error
func (context.Context) (TOut, error)
func (context.Context, TIn) (TOut, error)

Где TIn и TOut представляют типы, совместимые со стандартной библиотекой кодирования/json.

Так, нам нужно преобразовать эту точку входа в формат функции Azure.

Какой должен быть результат?

У нас есть Go Lambdas, и мы хотим преобразовать их в функции Go Azure. На сегодняшний день в функциях Azure нет обработчика Go для конкретного языка, поэтому вместо этого нам нужно использовать собственный обработчик для выполнения преобразования.

Пользовательские обработчики — это облегченные веб-серверы, которые получают события от узла функций. Единственное, что нам нужно, чтобы это заработало, — это реализовать HTTP-сервер на Go.

Лично, Мне нравится Gin Web Framework, поэтому давайте приведем пример его использования. Это примерно тот код, который мы хотели бы получить после преобразования:

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

type MyEvent struct {
    Name string `json:"name"`
}

type MyResponse struct {
    Message string `json:"message"`
}

func HandleRequest(ctx *gin.Context) {
    if ctx.Request.Body == nil {
        errorMsg := "received nil event"
        ctx.JSON(http.StatusBadRequest, gin.H{"error": errorMsg})
        return
    }

    var event MyEvent
    err := ctx.ShouldBindJSON(&event)
    if err != nil {
        errorMsg := fmt.Sprintf("error on reading request body: %vn", err.Error())
        ctx.JSON(http.StatusBadRequest, gin.H{"error": errorMsg})
        return
    }

    message := fmt.Sprintf("Hello %s!", event.Name)
    ctx.JSON(http.StatusOK, &MyResponse{Message: message})
}

func main() {
    r := gin.Default()
    r.Handle(http.MethodPost, "/HandleRequest", HandleRequest)

    r.Run()
}
​

Мы сохраняем исходные структуры запроса и ответа такими же, и у нас все еще есть обработчик функции, но с немного другой сигнатурой. func HandleRequest(ctx *gin.Context)” чтобы иметь возможность использовать вместо этого контекст джина. Мы сохраняем нулевую проверку оригинала, но нам нужно самостоятельно проанализировать объект. Наконец, основной функцией теперь является инициализация http-сервера gin вместо лямбды.

Мне это кажется довольно хорошим. 🙂

Read more:  WhatsApp разрабатывает функцию блокировки чата

Итак, как нам научить LLM выполнять это преобразование?

Попытки

Вы, наверное, уже слышали о оперативном проектировании. Оперативное проектирование — это процесс написания наилучших возможных инструкций для LLM, чтобы мы могли получить искомый результат. Он включает в себя несколько методов, и некоторые из них нам понадобятся, чтобы выполнить преобразование. Подробнее об этих методах вы можете узнать по этим ссылкам:

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

Подсказка цепочки мыслей (CoT)

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

Так, хотя это хороший подход, мы можем использовать CoT для подсказок, но нам нужно обогатить процесс дополнительными стратегиями.

Несколько снимков с использованием примера селектора

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

Снова, нам нужно было что-то лучшее.

Тонкая настройка

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

Это хороший вариант, но я хотел найти лучший способ сделать это без необходимости переподготовки LLM. Итак, что сработало?

Решение

Чтобы иметь возможность выполнить преобразование, я начал искать примеры успешной работы с LLM и кодом. Большая часть документации, которую вы найдете, посвящена LLM и тексту, и мало информации о коде, но мне удалось найти эту статью о Документы по генерации кода. В статье мы можем увидеть список статей о том, как генерировать код, и они оценивались с использованием разных наборов данных. Я начал проверять набор данных HumanEval и обнаружил несколько интересных методов, один из них под названием Parsel заключался в использовании псевдокода для описания того, что должна делать программа, и после того, как я попросил LLM преобразовать его в реальный код, это показалось интересным. но то, что привлекло мое внимание, было Поиск в дереве языковых агентов при использовании GPT-4 он набрал лучший результат в наборе данных и с процентом прохождения 94,4% звучал действительно многообещающе.

Так, что такое «Поиск в дереве языкового агента»? В статье Поиск в дереве языковых агентоввы можете найти хорошее объяснение этому, но мы можем резюмировать его как объединение двух методов быстрой инженерии, Древа мыслей и ReAct, чтобы получить наилучшие результаты.

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

Дерево мыслей

Реакт с другой стороны, попытки “выполнять рассуждения и действия, специфичные для задачи, в чередующемся порядке». Идея состоит в том, чтобы генерировать логические следы на основе выполнения модели и таким образом учиться на ее ошибках.

Объединив обе стратегии оперативного проектирования, мы получаем что-то вроде этого:

Read more:  Meta тестирует функцию трендовых тем в Threads
lats.drawio.png

Поиск в дереве языковых агентов

Наличие дерева мыслей в сочетании с ReAct позволяет нам перебирать проблему, но при этом давать LLM обратную связь о том, что она сделала неправильно, а затем действовать снова, используя саморефлексию в качестве входных данных, чтобы найти лучшее решение.

Итак, я создал реализацию конвертера лямбда-azure на основе кода реализации lats. Вы можете найти код в Репозиторий LanguageAgentTreeSearch.

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

Простая подсказка

Давайте предоставим нашему приложению исходный пример написанного нами лямбда-кода.

Я буду использовать для этого проекта модель GPT-4, поскольку она дала наилучшие результаты в статье, но вы можете попробовать тот же процесс, используя доступные вам LLM.

Нам нужно начать с первой попытки решения проблемы, используя простую подсказку, которую я использовал:

You are an AI that only responds with Go code, NOT ENGLISH. 
You will be given a lambda function code. Rewrite the code without using lambda code and using a GinGonic server instead.​

И вот результат:

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

type MyEvent struct {
    Name string `json:"name"`
}

type MyResponse struct {
    Message string `json:"message"`
}

func HandleRequest(c *gin.Context) {
    var event MyEvent
    if err := c.ShouldBindJSON(&event); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    message := fmt.Sprintf("Hello %s!", event.Name)
    c.JSON(http.StatusOK, MyResponse{Message: message})
}

func main() {
    router := gin.Default()
    router.POST("/handle-request", HandleRequest)
    router.Run() // By default, it listens on :8080
}

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

You are a Go programming assistant, an AI coding assistant that can write unique, diverse,
and intuitive unit tests for functions. You will be given a Go AWS Lambda function, that 
is being converted to a GinGonic http server. Your job is to generate a comprehensive set 
of tests to ensure its functionality remains consistent. The tests should cover all major 
functionality of the function, including error handling, input validation, and expected output.

Here is the Go code for the AWS Lambda function:
{{lambdaCode}}

Here is the Go code for the GinGonic http server:
{{azureCode}}

Вот сгенерированные тесты:

package main

import (
    "bytes"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
)

func setupRouter() *gin.Engine {
    gin.SetMode(gin.TestMode)
    r := gin.Default()
    r.POST("/event", HandleRequest)
    return r
}

func TestHandleRequest_Successful(t *testing.T) {
    router := setupRouter()

    w := httptest.NewRecorder()
    body := bytes.NewBufferString(`{"name":"John Doe"}`)
    req, _ := http.NewRequest("POST", "/event", body)
    req.Header.Set("Content-Type", "application/json")

    router.ServeHTTP(w, req)

    assert.Equal(t, http.StatusOK, w.Code)
    assert.JSONEq(t, `{"message":"Hello John Doe!"}`, w.Body.String())
}

func TestHandleRequest_BadRequest(t *testing.T) {
    router := setupRouter()

    w := httptest.NewRecorder()
    body := bytes.NewBufferString(`{"nom":"John Doe"}`) // Incorrect JSON field name
    req, _ := http.NewRequest("POST", "/event", body)
    req.Header.Set("Content-Type", "application/json")

    router.ServeHTTP(w, req)

    assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestHandleRequest_EmptyBody(t *testing.T) {
    router := setupRouter()

    w := httptest.NewRecorder()
    body := bytes.NewBufferString(`{}`) // Empty JSON body
    req, _ := http.NewRequest("POST", "/event", body)
    req.Header.Set("Content-Type", "application/json")

    router.ServeHTTP(w, req)

    assert.Equal(t, http.StatusOK, w.Code)
    assert.JSONEq(t, `{"message":"Hello !"}`, w.Body.String()) // Expect default behavior for empty name
}

Действительно мило! LLM тестирует успешный сценарий, неверный запрос и пустой ввод. Это поможет проверить, все ли работает правильно.

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

package main

import (
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
)

func TestHandleRequest(t *testing.T) {
    type args struct {
        request *http.Request
    }
    tests := []struct {
        name         string
        request      func() *http.Request
        expectedCode int
        expectedBody string
    }{
        {
            name: "success",
            request: func() *http.Request {
                req := `{"name":"Ana"}`
                return httptest.NewRequest(http.MethodPost, "/handle", strings.NewReader(req))
            },
            expectedCode: http.StatusOK,
            expectedBody: `{"message":"Hello Ana!"}`,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            router := gin.Default()
            router.POST("/handle", HandleRequest)

            w := httptest.NewRecorder()
            httpHandler := router
            httpHandler.ServeHTTP(w, tt.request())

            assert.Equal(t, tt.expectedCode, w.Code)
            assert.Equal(t, tt.expectedBody, w.Body.String())
        })
    }
}

Используя Go и некоторые методы для выполнения команд, я скомпилировал код и запустил тесты программно. Кроме того, я разработал систему оценок, в которой успешная сборка дает 2 балла, а каждый успешный тест дает 1 балл. Я делю все на максимальное количество доступных баллов и на основе на этот счет я могу определить, правильный ли код.

Вот результаты:

IsPassing: false
Feedback: |
    TestsPassed: ... 
    TestsFailed: ... 
    ErrorTrace:
        C:/Users/XXXXX/AppData/Local/Temp/go-lats/lats_test.go:44
        Error:          Not equal:
                        expected: 400
                        actual  : 200
        Test:           TestHandleRequest_BadRequest
Score: 0.75

Тест, который я ему дал, пройден, но тест, который он придумал, не удался, потому что функция не обрабатывает случай, когда входные данные неверны.

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

Итак, основываясь на этой обратной связи, давайте попросим LLM провести самоанализ:

You are a Go programming assistant. You will be given a function implementation and a 
series of unit tests. Your goal is to write a few sentences to explain why your 
implementation is wrong as indicated by the tests. You will need this as a hint when you 
try again later. Only provide the few sentence description in your answer, not the 
implementation.

{{examples}}

[function impl]:
{{azure_code}}

[unit test results]:
{{feedback}}

[self-reflection]:"

Вот ответ от LLM:

The function implementation seems to assume that if the JSON binding fails due to a bad 
request (such as wrong field names), the error should be reported with a status code of 
`http.StatusBadRequest`. However, the failing unit test `TestHandleRequest_BadRequest` 
suggests that this behavior is not occurring as expected. The test sends a JSON body with 
an incorrect field name (`"nom"` instead of `"name"`) and expects a 
`http.StatusBadRequest` status code due to binding failure, but the test reports receiving 
a `http.StatusOK` status code instead. This indicates that the `ShouldBindJSON` method 
might not be detecting the discrepancy between the expected and received JSON fields or 
the error handling logic after the `ShouldBindJSON` call is not functioning correctly.

Хорошо, теперь у нас есть все необходимое, чтобы попробовать еще раз, поэтому давайте попросим LLM еще раз сгенерировать код и тесты:

[system message]:
You are an AI Go assistant. You will be given your past function implementation, a 
series of unit tests, and a hint to change the implementation appropriately. Write your 
full implementation, Rewrite the code without using lambda code and using a GinGonic 
server instead. Do not by any chance use lambda code.
Use a Go code block to write your response. For example:
```go
func main() {
    fmt.Println("Hello, World!")
}

{{examples}}

[user message]:
{{lambda_code}}

[assistant message]:
{{azure_code}}

[user message]:
[unit test results from previous impl]:
{{feedback}}

[assistant message]:
[reflection on previous impl]:
{{self-reflection}}

[user message]:
Try to convert this code again:
{{lambda_code}}

И это полученный новый код:

package main

import (
    "fmt"
    "net/http"

    "github.com/gin-gonic/gin"
)

type MyEvent struct {
    Name string `json:"name"`
}

type MyResponse struct {
    Message string `json:"message"`
}

// HandleRequest processes the event and returns a response
func HandleRequest(c *gin.Context) {
    var event MyEvent

    // Bind the JSON to the struct; if nil or incorrect, return an error.
    if err := c.ShouldBindJSON(&event); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Check if the event is nil or the name is an empty string
    if event.Name == "" {
        // Respond with an error message if Name is empty
        c.JSON(http.StatusBadRequest, gin.H{"error": "name field is required"})
        return
    }

    // Constructing the message
    message := fmt.Sprintf("Hello %s!", event.Name)
    response := MyResponse{Message: message}

    // Sending back the response as JSON
    c.JSON(http.StatusOK, response)
}

func main() {
    // Set up Gin router
    r := gin.Default()

    // Define POST route
    r.POST("/event", HandleRequest)

    // Run the Gin server on a specified port
    r.Run(":8080")
}

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

Этот метод еще необходимо протестировать на реальном пользовательском коде, а подсказки также можно немного доработать, но это хорошее начало.

Теперь, после завершения преобразования точки входа, нам нужно подумать о следующих шагах. Для шага 2 мы должны иметь возможность использовать аналогичный подход, используя разные подсказки, вместо преобразования кода в веб-сервер Gin нам нужно будет задать его. выполнить преобразование, в котором используются реализации интерфейса вместо реализаций AWS, и на шаге 3 мы могли бы попытаться получить информацию из документации или предоставить ее самостоятельно и с помощью этой информации попросить создать необходимые файлы json для запустите функцию Azure.

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

2024-02-12 18:40:32


1708285219
#Преобразование #AWS #Lambda #функцию #Azure #использованием #LLM

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.