TalentX Tech Blog

Tech Blog

Bedrockを用いてテキストの文脈に応じて処理を切り分ける方法

はじめに

はじめまして!MyReferでバックエンドエンジニアをしている桃谷です。 MyReferチームでは、ユーザーが入力したテキストの文脈に応じて処理を切り分けたいといった課題があり、それをAmazonBedrockを用いて解決したのでその内容を紹介したいと思います。

Bedrockによるテキストの判定

課題の概要

まずは課題の全体像から見ていきたいと思います。 ユーザーがテキストフィールドに入力した内容に応じて、特定の文脈にマッチしたら処理Aを、それ以外の場合は処理Bを実行したいといった内容になります。
これは一般的なキーワードマッチではなく、テキストの文脈から判断して処理を切り分けるということです。例としては、「テキストに契約に前向きな文脈が含まれていたら処理Aを、それ以外は処理Bを実行する」といった感じになります。

処理の全体像

まずは処理の全体像を見ていきたいと思います。 元々の実装であれば、ユーザーがテキストフィールドに入力した内容が登録されると必ず処理Aが後続で行われておりました。

今回はユーザーがテキストフィールドの内容を登録すると、そのテキストをBedrockに読み込ませ、特定の文脈にマッチすれば処理Aを、マッチしない場合は処理Bを実行するといった内容になります。

生成AIが登場する前であれば同様の課題を解決する方法としては、特定のワードが含まれていないかをチェックする(例:「契約」というワードがテキストに含まれていないかチェック)といった方法が一般的に取られていたと思います。しかし、生成AIの登場によりテキストの文脈によって判断ができるようになり、より精度高く、シンプルに実装できるようになったのではないかと思います!

実装内容と得た知見

実装内容

MyReferチームではGo言語を使用しているため、Goとaws-sdk-go-v2を用いた簡易的な実装例を紹介いたします。

import (
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
    "github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
)

...

region := "Bedrockを使用するリージョン"
modelID := "使用するBedorockのモデルID"
sdkConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(region))
if err != nil {
    return false, fmt.Errorf("LoadDefaultConfig failed: %w", err)
}
bedrockRuntimeClient := bedrockruntime.NewFromConfig(sdkConfig)

converseInput := &bedrockruntime.ConverseInput{
    ModelId: aws.String(modelID),
}

// プロンプトの指示内容(例)
question := `
テキストの内容をチェックし、契約に前向きな内容が含まれるか否かをチェックしてください。 
含まれる場合はtrue、含まれない場合はfalseとして以下のJSON形式で出力してください。
{
"is_contract": true or false
}
また出力はJSONのみとしてください。
`

converseInput.Messages = []types.Message{
    {
        Role: types.ConversationRoleUser,
        Content: []types.ContentBlock{
            &types.ContentBlockMemberText{
                Value: question,
            },
            &types.ContentBlockMemberText{
                // チェックするテキスト
                Value: text,
            },
        },
    },
}

output, err := bedrockRuntimeClient.Converse(context.Background(), converseInput)
if err != nil {
    return false, fmt.Errorf("error response: %w", err)
}

response, ok := output.Output.(*types.ConverseOutputMemberMessage)
if !ok {
    return false, fmt.Errorf("error response: %w", err)
}

responseContentBlock := response.Value.Content[0]
text, ok := responseContentBlock.(*types.ContentBlockMemberText)
if !ok {
    return false, fmt.Errorf("error response: %w", err)
}

// Bedrockからの結果を受け取るための構造体
type checkResult struct {
    IsFlagA bool `json:"is_contract"`
}
result := checkResult{}
err = json.Unmarshal([]byte(text.Value), &result)
...

上記の実装例では、契約に前向きな内容がテキストに含まれていないかをBedrockにチェックさせる例になります。 今回はこちらの実装の詳細には触れませんが、以下で実装時の注意点をまとめます。

実装時の注意点

私が感じた実装時に注意するポイントは2点あります。

  • 精度を高めるためにはプロンプトが重要
    • これは生成AIを使用する際に一般的によく言われていることですが、一発で精度の高い指示を出すのは難しいと感じました。そのため、運用していく中でLLMが吐き出す結果を監視し、その結果に応じてプロンプトを修正してベストな指示に近づけていくことが大切だと思いました。
  • プロンプトに結果をどのようなフォーマットで出力するのかを明確に指示しておく必要がある
    • この指示がなければLLMは一貫性のないフォーマットで結果を出力するので、実装の中で使えません。そのため上記の実装のとおり明示的に以下のような指示を含める必要があります。
含まれる場合はtrue、含まれない場合はfalseとして以下のJSON形式で出力してください。
{
"is_contract": true or false
}
また出力はJSONのみとしてください。

このように指示を出すことで、一貫性のあるフォーマットで結果が返されるので、Goの構造体にUnmarshalすることができます。

まとめ

今回はAmazonBedrockを使用してテキストの文脈に応じて処理を切り分ける実装をする中で得た知見を共有させていただきました! まとめると以下です。

  • テキストの内容に応じて処理を切り分けたい場合、以前はワードマッチによる判定が主に採用されていたと思うが、生成AIの登場により精度高くシンプルな実装で同様の課題を解決できるようになった。
  • 精度を上げるためには、Bedrockに与えるプロンプトが重要になるが、運用の中で修正を加えて育てていくのが良さそう。
  • 結果の出力フォーマットは詳細に指示する必要がある。

最後に

最後までお読みいただきありがとうございました! TalentXでは一緒に働く仲間を募集しております。ご興味ある方は下記リンクの求人をご覧ください! talentx.brandmedia.i-myrefer.jp

カジュアル面談も行っていますのでぜひご応募ください。 i-myrefer.jp