使用 Go 中的 LLM 将 AWS Lambda 转换为 Azure 函数

使用 Go 中的 LLM 将 AWS Lambda 转换为 Azure 函数

本文的目的是展示一些可用于使用 LLM 将 AWS Lambda 函数转换为 Azure 函数的策略。

免责声明: 本文是关于应用 LLM 将 AWS Lambda 函数转换为 Azure Functions 的实验性实现。 它并非旨在作为该过程的明确指南,也不保证成功转换。 结果取决于您打算转换的具体代码以及您正在使用的法学硕士。

将 Lambda 函数转换为 Azure 函数需要什么?

使用 Lambda 函数需要考虑以下几点:

1. 与 Azure Functions 不同,AWS Lambda 函数不使用绑定。

2. 入口点中的输入对象作为 json 格式对象在入口处理程序中接收。

3. 由于不存在输出绑定,因此 Lambda 中的所有输出均通过使用 AWS 开发工具包进行处理。

那么,我们需要做哪些转换呢? 让我们将过程分为以下步骤:

1. 将 Lambda 入口点转换为 Azure Function 格式。

2. 将使用 AWS 开发工具包的任何库转换为使用可以连接到 AWS 或 Azure 的接口。 为什么? 好吧,如果我们正在进行迁移,我们可能希望继续使用 AWS 服务,直到数据或队列完全迁移。

3. 最后,需要生成能够运行该函数的 Azure Function 配置文件,例如 host.json、local.settings.json 和 function.json。 这些文件需要一些 lambda 当前没有的信息,因为它是从配置中定义的。 就像输入绑定用于 http 输入或队列一样,因此必须提供信息。

在本文中,我们将使用即时工程技术回顾第一步过程。 让我们首先看一下 Lambda。

Lambda 是什么样子的?

Go 中的基本 Lambda 函数如下所示:

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 Lambda 函数。 它包括一些有趣的功能,例如:

一套 github.com/aws/aws-lambda-go/lambda”

通过调用启动 lambda 的 main 函数 lambda.Start(HandleRequest)”

主函数中引用的处理程序具有以下签名: HandleRequest(ctx context.Context, 事件 *MyEvent) (*MyResponse, 错误)”

Lambda 函数的一个有趣的事情是处理程序可以有不同的签名。 例如,所有这些签名都是有效的:

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表示与encoding/json标准库兼容的类型。

所以, 我们需要将此入口点转换为 Azure 函数格式。

结果应该是什么?

我们有 Go Lambda,我们希望将它们转换为 Go Azure Functions。 截至目前,Azure Functions 还没有针对 Go 的特定于语言的处理程序,因此我们需要使用自定义处理程序来进行转换。

自定义处理程序是从 Functions 主机接收事件的轻量级 Web 服务器。 要让它工作,我们唯一需要做的就是在 Go 中实现一个 HTTP 服务器。

亲自, 我喜欢 Gin Web 框架,所以让我们用它来做一个例子。 这或多或少是我们希望在转换后获得的代码:

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)” 能够使用 gin 上下文来代替。 我们保留了原始的 nil 验证,但我们需要自己解析对象。 最后,现在的主要功能是初始化 gin http 服务器而不是 lambda。

对我来说看起来不错。 🙂

那么现在我们如何教法学硕士进行这种转换呢?

尝试

您现在可能已经听说过“即时工程”,“即时工程”是向法学硕士编写尽可能最佳说明的过程,因此我们能够获得我们正在寻找的结果。 它包括多种技术,我们将需要其中一些技术才能进行转换。 您可以在以下链接中了解有关这些技术的更多信息:

有一些使用即时工程的技术对于这种情况效果不佳,因为我们产生了幻觉和错误的代码,但根据情况,它们可能是很好的替代方案。 让我们在这里列出它们。

思路链 (CoT) 提示

这项技术是通过为法学硕士提供所需遵循的过程的分步指南来指导法学硕士,这是帮助法学硕士解决数学方程和复杂问题的一个非常好的选择,但是,对于这种情况,CoT没有给我们预期的结果。 大多数时候它会返回代码无法转换。

所以, 虽然这是一个很好的方法,我们可以使用CoT作为提示,但我们需要用更多的策略来丰富这个过程。

使用示例选择器的几张照片

这是一项非常好的技术,它使用矢量数据库和嵌入来选择示例,以选择最相关的示例来进行转换。 我没有大量的例子,但我已经足够开始测试它了。 结果是出乎意料的,当我在提示中只添加一个示例时,我得到了一个相当好的转换,可能与包有一些问题,比如没有执行所需的导入,甚至有一次产生了一个不存在的天蓝色函数包的幻觉,但是与基本想法。 奇怪的是,当给出更多示例时,它没有改进模型,而是开始返回无法进行转换。

再次, 我们需要更好的东西。

微调

所以, 这是一个非常好的方法,不是立即进行工程,而是专门针对该任务重新训练模型。 问题是它需要大量输入/输出对的示例,而我没有这么多的示例,而且它可能非常昂贵。 因此,如果您正在尝试类似的操作,并且在使用所有提示工程选项进行测试后仍无法获得所需的结果,请尝试一下,但要注意随之而来的培训、部署和查询可能产生的成本它。

这是一个不错的选择,但我想找到最好的方法来做到这一点,而不必重新培训法学硕士。 那么,什么有效呢?

解决方案

为了能够进行转换,我寻找了法学硕士和代码的成功工作示例。 你找到的大多数文档都是关于法学硕士和文本的,关于代码的很少,但我找到了这篇关于 代码生成论文。 在文章中,我们可以看到有关如何生成代码的论文列表,并且正在使用不同的数据集对它们进行评估。 我开始检查 HumanEval 数据集,发现了一些有趣的技术,其中一种称为 Parsel,就是使用伪代码来描述程序需要做什么,在要求 LLM 将其转换为真实代码后,这听起来很有趣但引起我注意的是 语言代理树搜索 使用 GPT-4,它是数据集中得分最高的一个,通过率高达 94.4%,听起来确实很有希望。

所以, “语言代理树搜索”是关于什么的? 文章中 语言代理树搜索,你可以找到一个很好的解释,但我们可以将其总结为结合两种即时工程技术,即思想树和 ReAct,以获得最佳结果。

思想之树*一个概括了思维链提示并鼓励对思想进行探索的框架,作为使用语言模型解决一般问题的中间步骤*。 思想树背后的想法是通过部分解决方案树来搜索解决方案,并丢弃那些不显示任何未来的解决方案,如图所示。

思想之树

反应 另一方面,试图“以交错的方式进行推理跟踪和特定于任务的操作”。 这个想法是根据模型的执行生成推理轨迹,并从错误中学习。

结合这两种即时工程策略,我们得到如下结果:

lats.drawio.png

语言代理树搜索

将思想树与 ReAct 相结合,使我们能够迭代问题,但向法学硕士反馈其做错了什么,然后使用自我反思作为输入再次采取行动,以找到更好的解决方案。

因此,我根据 lats 实现的代码创建了 lambda-azure 转换器的实现。 您可以在以下位置找到代码 LanguageAgentTreeSearch 存储库。

让我们看看它的实际效果。

简单提示

让我们为我们的应用程序提供我们编写的初始示例 Lambda 代码。

我将在这个项目中使用 GPT-4 模型,这是论文中给出最佳结果的模型,但您可以使用可用的法学硕士尝试相同的过程。

我们需要使用一个简单的提示来第一次尝试解决问题,这是我一直在使用的提示:

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
}

这是一个很好的第一次尝试,我们还想检查它生成的代码是否正常工作,所以让我们要求法学硕士为我们生成一些测试。

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

我给他的测试通过了,但他提出的测试失败了,因为该函数没有处理输入错误时的情况。

由于我们无法在第一次尝试中获得解决方案,下一步是使用自我反思创建一棵子解决方案树,然后使用为我们提供最佳分数的解决方案。 但当然,如果这些尝试之一是解决方案,我们将停止迭代并返回它。

因此,根据这个反馈,让我们要求法学硕士进行自我反思:

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 Web 服务器,我们需要询问它进行使用接口实现而不是 AWS 实现的转换,对于步骤 3,我们可以尝试从文档中推断信息,或者自己将其提供给它,并使用该信息,要求它创建所需的 json 文件运行 Azure 函数。

希望您喜欢这种实施方法,很高兴听到您的意见。 直到下一篇!

1707794182
#使用 #中的 #LLM #将 #AWS #Lambda #转换为 #Azure #函数
2024-02-12 18:40:32

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​