日本語版
最新ニュース
世界

Go で LLM を使用して AWS Lambda を Azure 関数に変換する

Go で LLM を使用して AWS Lambda を Azure 関数に変換する この投稿の目的は、LLM を使用して AWS Lambda 関数を Azure 関数に変換するために使用できるいくつかの戦略を示すことです。 免責事項: この記事は、LLM を適用して AWS Lambda 関数を Azure Functions に変換することに関する実験的な実装です。 これはプロセスの定義されたガイドとして意図されたものではなく、変換の成功を保証するものではありません。 結果は、変換する特定のコードと使用している LLM によって異なります。 Lambda 関数を Azure 関数に変換するには何が必要ですか? Lambda 関数に関して考慮する必要があることがいくつかあります。 1. Azure Functions…

Go で LLM を使用して AWS Lambda を Azure 関数に変換する

1708342475
2024-02-12 18:40:32

Go で LLM を使用して AWS Lambda を Azure 関数に変換する

この投稿の目的は、LLM を使用して AWS Lambda 関数を Azure 関数に変換するために使用できるいくつかの戦略を示すことです。

免責事項: この記事は、LLM を適用して AWS Lambda 関数を Azure Functions に変換することに関する実験的な実装です。 これはプロセスの定義されたガイドとして意図されたものではなく、変換の成功を保証するものではありません。 結果は、変換する特定のコードと使用している LLM によって異なります。

Lambda 関数を Azure 関数に変換するには何が必要ですか?

Lambda 関数に関して考慮する必要があることがいくつかあります。

1. Azure Functions とは異なり、AWS Lambda 関数はバインディングを使用しません。

2. エントリポイントの入力オブジェクトは、エントリ ハンドラで json 形式のオブジェクトとして受信されます。

3. 出力バインディングが存在しないため、Lambda のすべての出力は AWS SDK を使用して処理されます。

では、変換するには何が必要でしょうか? プロセスを次のステップに分けてみましょう。

1. Lambda エントリポイントを Azure Function 形式に変換します。

2. AWS SDK を使用するライブラリを、代わりに AWS または Azure に接続できるインターフェイスを使用するように変換します。 なぜ? そうですね、移行を行っている場合は、データまたはキューが完全に移行されるまで AWS のサービスを使い続けたいと思うかもしれません。

3. 最後に、関数を実行できるようにするための Azure Function 構成ファイル (host.json、local.settings.json、function.json など) を生成する必要があります。 これらのファイルには、設定で定義されているため、ラムダが現在持っていない情報が必要です。 入力バインディングが http 入力またはキュー用である場合など、情報を提供する必要があります。

この記事では、プロンプト エンジニアリング手法を使用して、最初のステップのプロセスを確認します。 まずは 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”

呼び出してラムダを開始する main 関数 lambda.Start(HandleRequest)”

このシグネチャを持つ main 関数で参照されるハンドラー: 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 は、エンコーディング/json 標準ライブラリと互換性のある型を表します。

それで、 このエントリポイントを Azure 関数形式に変換する必要があります。

結果はどうなるでしょうか?

Go Lambda を持っており、それを Go Azure Functions に変換したいと考えています。 現在のところ、Azure Functions には Go 用の言語固有のハンドラーがないため、代わりにカスタム ハンドラーを使用して変換を行う必要があります。

カスタム ハンドラーは、Functions ホストからイベントを受信する軽量の Web サーバーです。 これを機能させるために必要なのは、Go に HTTP サーバーを実装することだけです。

個人的には、 私は、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)” 代わりに gin コンテキストを使用できるようになります。 元の nil 検証を維持していますが、オブジェクトを自分で解析する必要があります。 最後に、メインの機能はラムダではなく gin http サーバーを初期化することです。

私にはかなり良いように見えます。 🙂

では、LLM にこの変換を行うようにどのように教えればよいでしょうか?

試み

おそらくプロンプト エンジニアリングについて聞いたことがあるでしょう。プロンプト エンジニアリングは、LLM に対して可能な限り最良の命令を記述するプロセスであり、これにより、探している結果を得ることができます。 これにはいくつかのテクニックが含まれており、変換を行うにはこれらのテクニックのいくつかが必要になります。 これらのテクニックについて詳しくは、次のリンクをご覧ください。

プロンプトエンジニアリングを使用したテクニックがいくつかありますが、幻覚や間違ったコードが発生したため、このケースではうまく機能しませんでしたが、ケースによっては良い代替手段となる可能性があります。 ここにそれらを列挙してみましょう。

思考連鎖 (CoT) プロンプト

この手法は、LLM が従う必要があるプロセスの段階的なガイドを提供することで LLM をガイドすることを目的としています。これは、LLM が数式や複雑な問題を解決するのに役立つ非常に優れたオプションですが、この場合、CoT期待した結果が得られませんでした。 ほとんどの場合、コードを変換できないことが返されました。

それで、 これは良いアプローチではありますが、プロンプトに CoT を使用することもできますが、より多くの戦略でプロセスを強化する必要があります。

サンプルセレクターを使用したいくつかのショット

これは、ベクトル データベースと埋め込みを使用してサンプルを選択し、変換を行うために最も関連性の高いサンプルを選択する非常に優れた手法です。 サンプルの数はそれほど多くありませんでしたが、テストを始めるには十分でした。 結果は予想外でした。プロンプトに 1 つの例を追加しただけで、かなり良好な変換が得られました。おそらく、必要なインポートが実行されないなどのパッケージに関するいくつかの問題があり、存在しない Azure 関数パッケージに関する幻覚さえ見せたこともありましたが、基本的なアイデアとともに。 奇妙なことに、より多くの例を与えると、モデルを改善する代わりに、変換を実行できなかったという結果が返され始めました。

また、 もっと良いものが必要でした。

微調整

それで、 これは非常に優れたアプローチであり、迅速なエンジニアリングではなく、タスク専用にモデルを再トレーニングします。 問題は、多くの入出力ペアの例が必要であること、そして私にはこれほどの数の例がなかったこと、またかなり高価になる可能性があることです。 したがって、同様のことを試みていて、すべてのプロンプト エンジニアリング オプションを使用してテストした後で希望する結果が得られなかった場合は、試してみてください。ただし、トレーニング、デプロイメント、およびクエリに伴うコストが発生する可能性があることに注意してください。それ。

これは良いオプションですが、LLM を再トレーニングすることなくこれを行う最良の方法を見つけたいと思いました。 それで、何がうまくいったのでしょうか?

ソリューション

変換を実行できるようにするために、LLM とコードを使用して成功した例を探しました。 見つかるドキュメントのほとんどは LLM とテキストに関するもので、コードに関するものはほとんどありませんが、次の記事を見つけることができました。 コード生成に関するペーパー。 この記事では、コードの生成方法に関する論文のリストが示されており、それらはさまざまなデータセットを使用して評価されています。 HumanEval データセットをチェックし始めました。そして、いくつかの興味深いテクニックを見つけました。そのうちの 1 つは Parsel と呼ばれるもので、疑似コードを使用してプログラムが何を行う必要があるかを記述し、LLM にそれを実際のコードに変換するよう依頼するというものでした。これは面白そうです。しかし、私の注意を引いたのは、 言語エージェントツリー検索 GPT-4 を使用すると、データセット内で最高のスコアを獲得し、94.4% の合格率で非常に有望に思えました。

それで、 「言語エージェント ツリー検索」とは何ですか? 記事の中で 言語エージェントツリー検索これについては素晴らしい説明が見つかりますが、最良の結果を得るために、Tree of thought と ReAct という 2 つのプロンプト エンジニアリング手法を組み合わせると要約できます。

思考の木*思考連鎖のプロンプトを一般化し、言語モデルを使用した一般的な問題解決の中間ステップとして機能する思考の探索を促進するフレームワーク*。 Tree of Thoughts の背後にある考え方は、画像に示されているように、将来が示されていないものを破棄する部分的な解決策のツリーを通じて解決策を検索することです。

思考の木

反応する 一方、「推論トレースとタスク固有のアクションを交互に実行します。」。 このアイデアは、モデルの実行に基づいて推論トレースを生成し、その方法でその間違いから学ぶことです。

両方のプロンプト エンジニアリング戦略を組み合わせると、次のような結果が得られます。

lats.drawio.png

言語エージェントツリー検索

ReAct と組み合わせた思考のツリーを使用すると、問題を反復処理しながら、何が間違っていたかについて LLM にフィードバックを提供し、その後、より良い解決策を見つけるためのインプットとして内省を使用して再度行動することができます。

そこで、lats 実装のコードに基づいて、lambda-azure コンバーターの実装を作成しました。 コードは次の場所にあります。 LanguageAgentTreeSearch リポジトリ。

実際に見てみましょう。

シンプルなプロンプト

最初に作成した Lambda コードの例をアプリに供給してみましょう。

このプロジェクトでは、論文内で最良の結果が得られた 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 では、コードを Jin Web サーバーに変換する代わりに、別のプロンプトを使用して同様のアプローチを使用できるようにする必要があります。 AWS の代わりにインターフェイス実装を使用する変換を実行します。ステップ 3 では、ドキュメントから情報を推測するか、自分で情報を与え、その情報を使用して、必要な json ファイルを作成するように要求します。 Azure 関数を実行します。

この実装アプローチを楽しんでいただければ幸いです。ぜひコメントをお待ちしています。 次まで!

#で #LLM #を使用して #AWS #Lambda #を #Azure #関数に変換する

執筆者について: nipponese

Nipponese News編集部は、国内外のニュースを日本語で分かりやすくお届けします。