目次
はじめに
こんにちは、omkです。
先日、今話題になっていますChatGPT/LangChainによるチャットシステム構築[実践]入門という技術書を会社で購入してもらいました!!
早速LangChainを用いた生成AI活用アプリをバリバリ開発中です。
丁度いい感じに動くものが作れたのでまとめていきます。
LangChainとは
LangChainはLLMを利用したアプリケーション開発のためのフレームワークです。
ChatGPTなどの様々な言語モデルに対してのワークフローを抽象化したりプロンプトエンジニアリングを容易にしてくれます。
やりたいこと
情報技術に関する質問のテキストから技術の名称やサービス名を抽出してカテゴライズするための機能を作成しようと考えていました。
いわば「stackoverflow」のようなサイトで自動でタグ付けしてくれるようなイメージですね。
これを作る前にpkeとGiNZAで日本語文章からキーフレーズを抽出するプログラムを作っていたのですが、
どうもITに関連した語彙に主軸をおいて抽出するように重み付けを行うというのが難しく断念していました。
LLMなら重み付けも自然言語で簡単に指定できるので作成してみました。
完成物
構成
北米リージョンでコンテナで動作するLambdaを作成してECRからデプロイします。
入出力サンプル
◯入力
{
"question": "アジャイルでLLMを活用したアプリケーションを開発する際のAIとの対話部分に利用するフレームワークはどのような基準で選定すればよいでしょうか?チームメンバーのスキルレベルは同一です。"
}
◯出力
{
"categories": [
"アジャイル",
"LLM",
"AI",
"フレームワーク"
]
}
いい感じに質問内容に直結した技術ワードが抽出されてJSONで返ってきます。
プログラム
全体像は以下です。
from langchain.prompts import (
ChatPromptTemplate,
PromptTemplate
)
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
from langchain.chains import LLMChain
from langchain.llms import Bedrock
import boto3
def chat(question):
# 出力フォーマットの指定
category_format = [
ResponseSchema(name="categories",type="list[str]", description="情報技術の名称や開発手法やサービス名のリスト")
]
output_parser = StructuredOutputParser.from_response_schemas(category_format)
format_instructions = output_parser.get_format_instructions()
# 入力プロンプト
template = """
次の文章から文章の主題となる情報技術の名前や開発手法、サービス名を抽出してください。最終的には文章中の重要度に応じて5個以内で表示してください。特に重要と判断されるもの以外は表示しないでください。
文章は以下です。
------------------------------------------------------
{question_body}
------------------------------------------------------
以下に該当する情報技術やサービス名は重要度が高いと判断してください。以下の重要度は上から順に高いものとします。
------------------------------------------------------
話題の中心にある、質問文に含まれている
エラー本文に含まれている
困っている問題に直接関係している
------------------------------------------------------
出力は以下の文章に従って重要度の高い順に出力してください。
------------------------------------------------------
{format_instructions}
------------------------------------------------------
"""
chat_prompt = PromptTemplate(
template = template,
input_variables=["question_body"],
partial_variables={"format_instructions": format_instructions}
)
# Bedrock
llm = Bedrock(
region_name = "us-east-1",
model_id="anthropic.claude-v2"
)
chain = LLMChain(prompt=chat_prompt,llm=llm,output_parser=output_parser)
result = chain.run(question_body=question)
return result
def lambda_handler(event, context):
print(event)
categories = chat(event['question'])
print(categories)
return categories
すごくシンプルで簡単なプログラムですがこれでちゃんと動くので非常に作るのが楽でした。
ポイントを細かく見ていきます。
# 出力フォーマットの指定
category_format = [
ResponseSchema(name="categories",type="list[str]", description="情報技術の名称や開発手法やサービス名のリスト")
]
output_parser = StructuredOutputParser.from_response_schemas(category_format)
format_instructions = output_parser.get_format_instructions()
出力フォーマットの指定をしている箇所です。
OutputParserには「StructuredOutputParser」を指定しています。
Pydantic (JSON) parserで自作クラスにパースするやり方では単一の値(ここではcategories)を受け取る際に上手くJSONにパースされなかったので「StructuredOutputParser」を利用しています。
template = """
次の文章から文章の主題となる情報技術の名前や開発手法、サービス名を抽出してください。最終的には文章中の重要度に応じて5個以内で表示してください。特に重要と判断されるもの以外は表示しないでください。
文章は以下です。
------------------------------------------------------
{question_body}
------------------------------------------------------
以下に該当する情報技術やサービス名は重要度が高いと判断してください。以下の重要度は上から順に高いものとします。
------------------------------------------------------
話題の中心にある、質問文に含まれている
エラー本文に含まれている
困っている問題に直接関係している
------------------------------------------------------
出力は以下の文章に従って重要度の高い順に出力してください。
------------------------------------------------------
{format_instructions}
------------------------------------------------------
"""
プロンプトエンジニアリングの賜物です。
条件はちょっと曖昧さが残る表現になってしまっていますが、実際にClaudeと相談しながら決めました。
もう少し相談しながらブラッシュアップしていく必要がありそうですね。
# Bedrock
llm = Bedrock(
region_name = "us-east-1",
model_id="anthropic.claude-v2"
)
chain = LLMChain(prompt=chat_prompt,llm=llm,output_parser=output_parser)
result = chain.run(question_body=question)
ここでBedrockにプロンプトを投げ込んでいます。
profileは指定しなければ自動でIAMロールの権限が利用されます。
boto3のバージョンが古いとBedrockの権限エラーが出てしまうので若干苦労しました。
最終的にrequirement.txtはこんな感じになっています。
boto3==1.28.70
langchain==0.0.320
langchain-experimental==0.0.32
動作確認
最後に何個か試してみます。
シンプルな質問
◯入力
{
"question": "AWSの東京リージョンでホストしているWEBサイトにアメリカから接続する場合のネットワークレイテンシーはどのように下げられますか?"
}
◯出力
{
"categories": [
"AWS",
"東京リージョン",
"WEBサイト",
"ネットワークレイテンシー",
"アメリカ"
]
}
ちょっと惜しいですね。
AWSの次にネットワークレイテンシーが来てほしいですしアメリカは(なんなら東京リージョンも)要らないですね。
エラー文付き
◯入力
{
"question": "プログラム中のAmazon Bedrockに問い合わせを行う箇所で次のエラーが出力されました。「Could not load credentials to authenticate with AWS client. Please check that credentials in the specified profile name are valid.」どのような原因が考えられますか?"
}
◯出力
{
"categories": [
"Amazon Bedrock",
"AWS client",
"credentials",
"profile name"
]
}
まぁいい感じですね。credentialsとprofile nameは要る要らないの瀬戸際くらい。
情報多め
◯入力
{
"question": "AWS IoT CoreをMQTTのメッセージブローカーとしてRaspberry piのメッセージを収集サーバに集積させています。IoT Coreに届いたメッセージはIoTルールとIoTアクションを用いてLambdaで加工していますが、Lambdaの起動が行われません。原因はどこにあるでしょうか?"
}
◯出力
{
"categories": [
"AWS IoT Core",
"MQTT",
"Lambda",
"IoT ルール",
"IoT アクション"
]
}
色々とダミーを入れてみましたが本題に近い部分で抽出してくれました。
おわりに
最終的にはもう少しプロンプトを調整してクリティカルなカテゴリーを抽出するようにしたいのでロジックは考える必要がありそうですが、
まずは非常に簡単に動くものが出来たので感動しました。
さらに色々なものと組み合わせてもっと大きなシステムを作ってみたいですね。
以上、最後までお付き合いありがとうございました。
アーキテクト課のomkです。
AWSについて雑多に取り組んだ内容を発信しています!!