‘LLMがデータアナリストを置き換えることはできるのか? LLMを活用したアナリストの構築’

『データアナリストを置き換えるにはLLMが活躍できるのか? LLMを活用したアナリストの育成』

パート1:ツールでChatGPTを強化する

Image by DALL-E 3

過去の1年間で、少なくとも一度はChatGPTがあなたの役割を置き換えることができるのか(いや、いつの時点で置き換えるのか)、私たちそれぞれが思ったことがあると思います。私も例外ではありません。

私たちは、生成型AIの最近のブレークスルーが私たちの個人的な生活や仕事に大きな影響を与えるという見解にはほぼ同意しています。しかし、私たちの役割が時間とともにどのように変化するのかについてはまだ明確な見通しがありません。

異なる可能性の未来シナリオやその確率について考えることに多くの時間を割くことは魅力的かもしれませんが、私はまったく異なるアプローチを提案します。まず、自分自身でプロトタイプを構築してみることです。まず、それはかなり挑戦的で楽しいでしょう。次に、私たちの仕事をより構造的な方法で見るのに役立つでしょう。さらに、最先端のアプローチの1つであるLLMエージェントの実践を試す機会を私たちに与えてくれるでしょう。

この記事では、簡単な始め方としてLLMsがツールを活用し、単純なタスクを実行する方法を学びます。しかし、次の記事では、LLMエージェントの異なるアプローチやベストプラクティスにより深く入り込んでいきます。

では、旅を始めましょう。

データ分析とは何か

LLMsに移る前に、データ分析とは何か、私たちアナリストがどのようなタスクを行っているのかを定義しましょう。

私のモットーは、分析チームの目標は利用可能な時間内でデータに基づいて製品チームが正しい意思決定をするのを助けることです。これは良いミッションですが、LLMを活用したアナリストの範囲を定義するためには、分析作業をさらに分解する必要があります。

私はGartnerが提案するフレームワークが好きです。これは、データと分析の4つの異なるテクニックを特定しています:

  • 記述統計は「何が起こったのか?」という質問に答えます。たとえば、12月の収益はいくらでしたか?このアプローチには、レポート作業やBIツールの使用などが含まれます。
  • 診断統計は「なぜ何かが起こったのか?」といった質問を投げかけます。たとえば、なぜ収益が前の年に比べて10%減少したのでしょうか?このテクニックでは、データのドリルダウンやスライス&ダイスがさらに必要とされます。
  • 予測統計は「何が起こるのか?」という質問に答えることができます。このアプローチの二つの基本は予測(通常の事例の将来を予測すること)とシミュレーション(さまざまな可能な結果のモデリング)です。
  • 指示統計は最終的な意思決定に影響を与えます。一般的な質問は「何に焦点を当てるべきか?」や「どのようにしてボリュームを10%増やすことができるか?」です。

通常、企業はこれらのステージを順番に進めていきます。あなたの企業が記述統計をまだ習得していない場合(データウェアハウスやBIツール、メトリクスの定義がない場合)、予測や異なるシナリオ分析を始めることはほとんど不可能です。したがって、このフレームワークは企業のデータの成熟度を示すこともできます。

同様に、アナリストが初級から上級に成長すると、彼女はおそらくすべてのステージを経験することになります。まずは明確に定義されたレポート作業から始まり、徐々に曖昧な戦略的な質問に進んでいきます。したがって、このフレームワークは個々のレベルでも関連しています。

LLMを活用したアナリストに戻ると、私たちは記述統計とレポート作業に焦点を当てるべきです。基礎から始める方が良いでしょう。そのため、私たちはデータに関する基本的な質問を理解するためにLLMを学ぶことに重点を置きます。

最初のプロトタイプのために焦点を定義しました。では、技術的な質問に移り、LLMエージェントとツールのコンセプトについて話し合いましょう。

LLMエージェントとツール

以前にLLMを使用していたとき(たとえば、トピックモデリングを行うために)、私たちは自分自身でコード内の具体的な手順を記述しました。たとえば、以下のチェーンを見てみましょう。まず、モデルに顧客のレビューの感情を判断させます。次に、感情に応じて、テキストに言及されている利点または欠点をレビューから抽出します。

Illustration by author

この例では、私たちはLLMの振る舞いを明確に定義し、LLMはこのタスクを非常にうまく解決しました。しかし、もし私たちがより高度で曖昧なもの、例えばLLMを活用したアナリストを構築する場合、このアプローチはうまくいきません。

もし少なくとも1日アナリストとして働いたことがある、またはアナリストと一緒に働いた経験があるならば、アナリストは非常にさまざまな質問や依頼を受けることを知っているでしょう。基本的な質問(例えば、「昨日のサイト上の顧客数はいくつでしたか?」や「明日の取締役会のためにグラフを作ってもらえますか?」など)から非常に高度な質問まで(例えば、「主な顧客の痛みのポイントは何ですか?」や「次にどの市場を開拓すべきですか?」など)さまざまな質問が寄せられます。当然のことながら、すべてのシナリオを説明することは実現不可能です。

しかし、私たちには役立つアプローチがあります。それはエージェントです。エージェントの核となるアイデアは、LLMを推論エンジンとして使用し、次に何をするか、いつ顧客に最終的な回答を返すべきかを選択することです。これは私たちの振る舞いに非常に似ています。タスクを受け取り、必要なツールを定義し、それらを使用して準備ができたら最終的な回答を返します。

エージェントに関連する重要な概念(先ほどすでに言及しましたが)がツールです。ツールは、LLMが不足している情報を取得するために呼び出すことができる関数です(例えば、SQLを実行したり、電卓を使用したり、検索エンジンを呼び出したりすることができます)。ツールは重要です、なぜならそれらによってLLMを次のレベルに引き上げ、世界と対話することができるからです。この記事では、主にツールとしてのOpenAI関数に焦点を当てます。

OpenAIは、関数を扱うために調整されたモデルを持っているため、以下のような機能が可能です:

  • 説明を含んだ関数のリストをモデルに渡すことができます。
  • クエリに関連する場合、モデルは関数呼び出し(関数名と入力パラメータ)を返します。

詳細な情報や関数をサポートしている最新のモデルのリストは、ドキュメントで確認できます。

LLMと関数を使用する主なユースケースは2つあります:

  • タグ付けと抽出 – これらの場合、関数はモデルの出力形式を保証するために使用されます。通常の出力ではなく、構造化された関数呼び出しを取得します。
  • ツールとルーティング – これはより興味深いユースケースで、エージェントを作成することができます。

まずは抽出のより簡単なユースケースから始めて、OpenAI関数の使用方法を学びましょう。

ユースケース#1:タグ付けと抽出

タグ付けと抽出の違いが気になるかもしれません。これらの用語は非常に似ています。唯一の違いは、モデルがテキストに示された情報を抽出するか、新しい情報(つまり、言語または感情を定義する)を提供するテキストにラベルを付けるかです。

Illustration by author

私たちは記述的な分析と報告タスクに焦点を当てることに決めたので、このアプローチを使用して入力データのリクエストを構造化し、次の構成要素を取得します:メトリック、ディメンション、フィルター、期間、および出力の目的。

Illustration by author

これは抽出の例になります。テキストに存在する情報だけが必要です。

OpenAI補完APIの基本的な例

まず、関数を定義する必要があります。OpenAIでは、関数の説明をJSON形式で指定する必要があります。このJSONはLLMに渡されるため、関数自体の内容、使用方法をすべて伝える必要があります。

以下は関数のJSONの例です。次の情報を指定しています:

  • 関数自体のnamedescription
  • 各引数のtypedescription
  • 関数の必要な入力パラメータのリスト
extraction_functions = [    {        "name": "extract_information",        "description": "情報を抽出します",        "parameters": {            "type": "object",            "properties": {                "metric": {                    "type": "string",                    "description": "計算する必要がある主要なメトリック、例えば 'ユーザー数' または 'セッション数'",                },                "filters": {                    "type": "string",                    "description": "計算に適用するフィルター(ここには日付のフィルターは含まれません)",                },                "dimensions": {                    "type": "string",                    "description": "メトリックを分割するためのパラメータ",                },                "period_start": {                    "type": "string",                    "description": "レポートの期間の開始日",                },                "period_end": {                    "type": "string",                    "description": "レポートの期間の終了日",                },                "output_type": {                    "type": "string",                    "description": "希望の出力",                    "enum": ["number", "visualisation"]                }            },            "required": ["metric"],        },    }]

このユースケースでは、関数自体を実装する必要はありません。関数呼び出しとして構造化されたLLMレスポンスのみを取得します。

今回は、標準のOpenAI Chat Completion APIを使用して関数を呼び出すことができます。API呼び出しには以下のものを渡しました:

  • モデル – 関数を使用できる最新のChatGPT 3.5 Turboを使用しました。
  • メッセージのリスト – コンテキストの設定とユーザーのリクエストのためのシステムメッセージ1つ。
  • 事前に定義した関数のリスト。
import openaimessages = [    {        "role": "system",        "content": "提供されたリクエストから関連情報を抽出します。"    },    {        "role": "user",        "content": "iOSユーザー数は時間とともにどのように変化しましたか?"    }]response = openai.ChatCompletion.create(    model = "gpt-3.5-turbo-1106",     messages = messages,    functions = extraction_functions)print(response)

結果として、次のJSONが返されました。

{  "id": "chatcmpl-8TqGWvGAXZ7L43gYjPyxsWdOTD2n2",  "object": "chat.completion",  "created": 1702123112,  "model": "gpt-3.5-turbo-1106",  "choices": [    {      "index": 0,      "message": {        "role": "assistant",        "content": null,        "function_call": {          "name": "extract_information",          "arguments": "{\"metric\":\"ユーザー数\",\"filters\":\"platform='iOS'\",\"dimensions\":\"date\",\"period_start\":\"2021-01-01\",\"period_end\":\"2021-12-31\",\"output_type\":\"可視化\"}"        }      },      "finish_reason": "function_call"    }  ],  "usage": {    "prompt_tokens": 159,    "completion_tokens": 53,    "total_tokens": 212  },  "system_fingerprint": "fp_eeff13170a"}

関数と関数呼び出しはトークンの制限に含まれ、請求されますことを忘れないでください。

モデルは通常の応答の代わりに関数呼び出しを返しました: content が空で、finish_reasonfunction_call に等しいことがわかります。応答には関数呼び出しの入力パラメータも含まれています:

  • metric = "ユーザー数"
  • filters = "platform = 'iOS'"
  • dimensions = "date"
  • period_start = "2021-01-01"
  • period_start = "2021-12-31"
  • output_type = "可視化"

モデルはかなりうまく機能しました。唯一の問題は、期間をどこからか推測してしまったことです。システムメッセージにより明確なガイダンスを追加することで修正できます。たとえば、 "提供されたリクエストから関連情報を抽出します。初期リクエストに表示された情報のみを抽出してください。何かが抜けている場合は部分的な情報を返してください。"

デフォルトでは、モデルは独自に関数を使用するかどうかを決定します(function_call = 'auto')。常に特定の関数呼び出しを返すように要求するか、関数を使用しないようにすることもできます。

# extract_information関数を常に呼び出すresponse = openai.ChatCompletion.create(    model = "gpt-3.5-turbo-1106",    messages = messages,    functions = extraction_functions,    function_call = {"name": "extract_information"})# 関数呼び出しを行わないresponse = openai.ChatCompletion.create(    model = "gpt-3.5-turbo-1106",    messages = messages,    functions = extraction_functions,    function_call = "none")

LLM関数を使用した最初の動作するプログラムができました。素晴らしいですね。ただし、関数をJSONで記述するのはあまり便利ではありません。もっと簡単に行う方法について話し合いましょう。

Pydanticを使用して関数を定義する

関数をより便利に定義するために、Pydanticを活用することができます。Pydanticは、データの検証のための最も人気のあるPythonライブラリです。

私たちはすでにPydanticを使用して、LangChain Output Parserを定義しました。

まず、BaseModelクラスを継承するクラスを作成し、すべてのフィールド(関数の引数)を定義する必要があります。

from pydantic import BaseModel, Fieldfrom typing import Optionalclass RequestStructure(BaseModel):  """情報を抽出する"""  metric: str = Field(description = "計算する必要のある主なメトリクス、例えば、'ユーザーの数'や'セッションの数'")  filters: Optional[str] = Field(description = "計算に適用するフィルタ(ここで日付のフィルタは含まれません)")  dimensions: Optional[str] = Field(description = "メトリクスを分割するためのパラメータ")  period_start: Optional[str] = Field(description = "レポートの期間の開始日")  period_end: Optional[str] = Field(description = "レポートの期間の終了日")  output_type: Optional[str] = Field(description = "希望する出力", enum = ["number", "visualisation"])

次に、LangChainを使用して、PydanticクラスをOpenAI関数に変換することができます。

from langchain.utils.openai_functions import convert_pydantic_to_openai_functionextract_info_function = convert_pydantic_to_openai_function(RequestStructure,     name = 'extract_information')

LangChainは提供されたクラスを検証します。たとえば、LLMがこのツールを使用するために関数の説明が指定されていることを保証します。

その結果、同じJSONをLLMに渡すために得ることができますが、これをPydanticクラスとして表現します。

{'name': 'extract_information', 'description': '情報を抽出する', 'parameters': {'title': 'RequestStructure',  'description': '情報を抽出する',  'type': 'object',  'properties': {'metric': {'title': 'メトリクス',    'description': "計算する必要のある主なメトリクス、例えば、'ユーザーの数'や'セッションの数'",    'type': 'string'},   'filters': {'title': 'フィルタ',    'description': '計算に適用するフィルタ(ここで日付のフィルタは含まれません)',    'type': 'string'},   'dimensions': {'title': '次元',    'description': 'メトリクスを分割するためのパラメータ',    'type': 'string'},   'period_start': {'title': '期間の開始',    'description': 'レポートの期間の開始日',    'type': 'string'},   'period_end': {'title': '期間の終了',    'description': 'レポートの期間の終了日',    'type': 'string'},   'output_type': {'title': '出力の種類',    'description': '希望する出力',    'enum': ['number', 'visualisation'],    'type': 'string'}},  'required': ['metric']}}

それでは、OpenAIへの呼び出しで使用できるようにしましょう。APIの呼び出しをよりモジュラーにするために、OpenAI APIからLangChainに切り替えましょう。

LangChainチェーンの定義

リクエストから必要な情報を抽出するために、チェーンを定義しましょう。LLM向けに最も人気のあるフレームワークであるLangChainを使用します。以前の記事のうちの1つで基本的な内容を学ぶことをお勧めします。one of my previous articles

私たちのチェーンはシンプルです。Open AIモデルとプロンプトで、request(ユーザーのメッセージ)という変数を1つ持っています。

また、bind関数を使用してモデルにfunctions引数を渡しました。 bind関数は、入力の一部ではない定数引数(例えばfunctiontemperature)を指定することができます。

from langchain.prompts import ChatPromptTemplatefrom langchain.chat_models import ChatOpenAImodel = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\  .bind(functions = [extract_info_function])prompt = ChatPromptTemplate.from_messages([    ("system", "初期リクエストに表示されている情報のみを抽出してください。\            他の情報は追加しないでください。\            不足している場合、部分的な情報を返してください。"),    ("human", "{request}")])extraction_chain = prompt | model

いま、私たちの関数を試してみる時間です。 request を渡して invoke メソッドを使用する必要があります。

extraction_chain.invoke({'request': "2023年4月に各国からiOSでサイトを訪れた顧客の数は何人でしたか?」})

出力では、コンテンツはなく、関数呼び出しのみの AIMessage を取得しました。

AIMessage(  content='',   additional_kwargs={    'function_call': {       'name': 'extract_information',        'arguments': '''{         "metric":"顧客数", "filters":"device = 'iOS'",         "dimensions":"country", "period_start":"2023-04-01",         "period_end":"2023-04-30", "output_type":"number"}        '''}  })

したがって、LangChain を使用して構造化された出力を取得するために OpenAI の関数を使用する方法を学びました。 では、興味深いユースケースであるツールとルーティングに進んでみましょう。

ユースケース2:ツールとルーティング

ツールを使用してモデルに外部機能を付加しましょう。このアプローチのモデルは、推論エンジンであり、使用するツールとそのタイミング(ルーティングと呼ばれる)を決定できます。

LangChain にはエージェントが世界とやり取りするために使用できる ツール の概念があります。ツールは関数、LangChain チェイン、さらには他のエージェントになることができます。

我々は簡単にツールを OpenAI の関数に変換し、引き続き LLMs に functions 引数を渡すことができます。

カスタムツールの定義

LLM ベースのアナリストに2つのメトリックの差を計算する方法を教えましょう。LLMs は数学のミスをすることがあるので、モデルに自己判断するよりも計算機を使うように依頼したいと思います。

ツールを定義するには、関数を作成し、@tool デコレータを使用する必要があります。

from langchain.agents import tool@tooldef percentage_difference(metric1: float, metric2: float) -> float:    """メトリックのパーセンテージ差を計算します"""    return (metric2 - metric1)/metric1*100

この関数には、LMM に渡される namedescription のパラメータがあります。

print(percentage_difference.name)# percentage_difference.nameprint(percentage_difference.args)# {'metric1': {'title': 'Metric1', 'type': 'number'},# 'metric2': {'title': 'Metric2', 'type': 'number'}}print(percentage_difference.description)# 'percentage_difference(metric1: float, metric2: float) -> float - メトリック間のパーセンテージ差を計算する'

これらのパラメータは、OpenAI 関数の仕様を作成するために使用されます。ツールを OpenAI の関数に変換しましょう。

from langchain.tools.render import format_tool_to_openai_functionprint(format_tool_to_openai_function(percentage_difference))

結果として、次の JSON を得ました。構造はアウトラインされていますが、フィールドの説明が抜けています。

{'name': 'percentage_difference', 'description': 'percentage_difference(metric1: float, metric2: float) -> float - メトリック間のパーセンテージ差を計算する', 'parameters': {'title': 'percentage_differenceSchemaSchema',  'type': 'object',  'properties': {'metric1': {'title': 'Metric1', 'type': 'number'},   'metric2': {'title': 'Metric2', 'type': 'number'}},  'required': ['metric1', 'metric2']}}

引数のスキーマを指定するために Pydantic を使用することができます。

class Metrics(BaseModel):    metric1: float = Field(description="差分を計算するベースメトリックの値")    metric2: float = Field(description="基準値と比較する新しいメトリックの値")@tool(args_schema=Metrics)def percentage_difference(metric1: float, metric2: float) -> float:    """メトリックのパーセンテージ差を計算します"""    return (metric2 - metric1)/metric1*100

今度は、新しいバージョンを OpenAI の関数仕様に変換すると、引数の説明が含まれます。モデルとすべての必要なコンテキストを共有できるので、ずっと良いです。

{'name': 'percentage_difference', 'description': 'percentage_difference(metric1: float, metric2: float) -> float - メトリック間のパーセンテージの差を計算します', 'parameters': {'title': 'メトリック',  'type': 'object',  'properties': {'metric1': {'title': 'メトリック1',    'description': '差を計算するための基本のメトリック値',    'type': 'number'},   'metric2': {'title': 'メトリック2',    'description': 'ベースラインと比較する新しいメトリック値',    'type': 'number'}},  'required': ['metric1', 'metric2']}}

それでは、LLMが使用できるツールを定義しました。実際に試してみましょう。

実践でツールを使用する

チェーンを定義し、ツールを関数に渡してみましょう。その後、ユーザーのリクエストでテストすることができます。

model = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\  .bind(functions = [format_tool_to_openai_function(percentage_difference)])prompt = ChatPromptTemplate.from_messages([    ("system", "あなたは製品アナリストであり、製品チームのサポートに協力したいと考えています。あなたは非常に厳格で正確です。事実のみを使用し、情報を作り出しません。"),    ("user", "{request}")])analyst_chain = prompt | modelanalyst_chain.invoke({'request': "4月には100人のユーザーがいて、5月にはわずか95人になりました。パーセンテージの差は何ですか?"})

正しい引数を持つ関数呼び出しを取得できましたので、正常に動作しています。

AIMessage(content='', additional_kwargs={    'function_call': {      'name': 'percentage_difference',       'arguments': '{"metric1":100,"metric2":95}'}  })

出力の取り扱いがより便利になるようにOpenAIFunctionsAgentOutputParserを使用することができます。チェーンに追加してみましょう。

from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParseranalyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()result = analyst_chain.invoke({'request': "4月には100人のユーザーがいて、5月には110人のユーザーがいました。ユーザー数はどのように変化しましたか?"})

今、より構造化された形で出力を得ることができ、result.tool_inputからツールの引数を簡単に取得できます。

AgentActionMessageLog(   tool='percentage_difference',    tool_input={'metric1': 100, 'metric2': 110},    log="\nInvoking: `percentage_difference` with `{'metric1': 100, 'metric2': 110}`\n\n\n",    message_log=[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}})])

したがって、LLMが要求したように関数を実行することができます。

observation = percentage_difference(result.tool_input)print(observation)# 10

モデルから最終的な回答を得たい場合、関数の実行結果を戻す必要があります。これを行うために、モデルの観測に渡すメッセージリストを定義する必要があります。

from langchain.prompts import MessagesPlaceholdermodel = ChatOpenAI(temperature=0.1, model = 'gpt-3.5-turbo-1106')\  .bind(functions = [format_tool_to_openai_function(percentage_difference)])prompt = ChatPromptTemplate.from_messages([    ("system", "あなたは製品アナリストであり、製品チームのサポートに協力したいと考えています。あなたは非常に厳格で正確です。事実のみを使用し、情報を作り出しません。"),    ("user", "{request}"),    MessagesPlaceholder(variable_name="observations")])analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()result1 = analyst_chain.invoke({    'request': "4月には100人のユーザーがいて、5月には110人のユーザーがいました。ユーザー数はどのように変化しましたか?",    "observations": []})observation = percentage_difference(result1.tool_input)print(observation)# 10

その後、観測をobservations変数に追加する必要があります。結果をモデルに渡すために、format_to_openai_functions関数を使用して結果を期待される形式にフォーマットすることができます。

from langchain.agents.format_scratchpad import format_to_openai_functionsformat_to_openai_functions([(result1, observation), ])

結果として、LLMが理解できるようなメッセージを得ました。

[AIMessage(content='', additional_kwargs={'function_call': {'name': 'percentage_difference',                                            'arguments': '{"metric1":100,"metric2":110}'}}), FunctionMessage(content='10.0', name='percentage_difference')]

さらに一度チェーンを呼び出し、関数の実行結果を観察結果として渡します。

result2 = analyst_chain.invoke({    'request': "4月には100人のユーザーがいましたが、5月には110人いました。ユーザーの数はどのように変化しましたか?",    "observations": format_to_openai_functions([(result1, observation)])})

今度は、モデルから最終結果を得ました。それは合理的です。

AgentFinish(  return_values={'output': 'ユーザー数は10%増加しました。'},   log='ユーザー数は10%増加しました。')

もしも私たちが通常のOpenAI Chat Completion APIを使っているなら、role = tool を持つ別のメッセージを追加すれば良いです。詳しい例はこちらにあります。

デバッグをオンにすると、OpenAI API に渡された正確なプロンプトが表示されます。

System: プロダクトのアナリストで、プロダクトチームの役に立ちたいです。私は要点に厳しく、正確です。情報を作り出すのではなく、事実のみを使用します。Human: 4月には100人のユーザーがいましたが、5月には110人いました。ユーザーの数はどのように変化しましたか?AI: {'name': 'percentage_difference', 'arguments': '{"metric1":100,"metric2":110}'}Function: 10.0

LangChain のデバッグをオンにするには、以下のコードを実行し、チェーンを呼び出して内部で何が起こっているかを確認します。

import langchainlangchain.debug = True

1つのツールで作業を試みましたが、ツールキットを拡張してLLMがどのように処理するかを見てみましょう。

ルーティング:複数のツールを使用する

アナリストのツールキットにさらにいくつかのツールを追加しましょう:

  • 月別のアクティブユーザーを取得する
  • Wikipediaの使用。

まず、月と都市ごとにフィルターされたオーディエンスを計算するためのダミー関数を定義しましょう。また、関数の入力引数を指定するためにPydanticを再度使用します。

import datetimeimport randomclass Filters(BaseModel):    month: str = Field(description="顧客の活動の月(%Y-%m-%dの形式)")    city: Optional[str] = Field(description="顧客の居住都市(デフォルトではフィルターなし)",                     enum = ["ロンドン", "ベルリン", "アムステルダム", "パリ"])@tool(args_schema=Filters)def get_monthly_active_users(month: str, city: str = None) -> int:    """指定された月のアクティブな顧客数を返します。"""    dt = datetime.datetime.strptime(month, '%Y-%m-%d')    total = dt.year + 10*dt.month    if city is None:        return total    else:        return int(total*random.random())

次に、モデルがWikipediaをクエリするためにwikipedia Pythonパッケージを使用しましょう。

import wikipediaclass Wikipedia(BaseModel):    term: str = Field(description="検索する用語")@tool(args_schema=Wikipedia)def get_summary(term: str) -> str:    """Wikipediaによって提供される指定された用語に関する基本的な知識を返します。"""    return wikipedia.summary(term)

現在、モデルが知っているすべての関数を持つ辞書を定義しましょう。後でルーティングに使用します。

toolkit = {    'percentage_difference': percentage_difference,    'get_monthly_active_users': get_monthly_active_users,    'get_summary': get_summary}analyst_functions = [format_tool_to_openai_function(f)   for f in toolkit.values()]

以前のセットアップにいくつかの変更を加えました:

  • システムプロンプトを少し編集して、モデルが必要な場合に基本的な知識をWikipediaで参照するようにしました。
  • タスクの推論を必要とする処理に対しては、GPT 4にモデルを変更しました。

<!–from langchain.prompts import MessagesPlaceholder
model = ChatOpenAI(temperature=0.1, model='gpt-4-1106-preview').bind(functions=analyst_functions)
prompt = ChatPromptTemplate.from_messages([
("system", "あなたは製品アナリストで、製品チームをサポートする意欲を持っています。あなたは非常に厳密で正確な点で厳格です。\n初期リクエストで提供された情報のみを使用します。\n例えば、首都の名前を決定する必要がある場合は、Wikipediaを使用することができます。"),
("user", "{request}"),
MessagesPlaceholder(variable_name="observations")
])
analyst_chain = prompt | model | OpenAIFunctionsAgentOutputParser()

全ての関数を使用してチェインを呼び出すことができます。まずは非常にシンプルなクエリから始めましょう。

result1 = analyst_chain.invoke({
    'request': "2023年4月のベルリンのユーザー数はいくつでしたか?",
    "observations": []
})
print(result1)

結果として、入力パラメータ「{‘month’: ‘2023–04–01’, ‘city’: ‘Berlin’}」を使用したget_monthly_active_usersの関数呼び出しが得られました。これは正しいようです。モデルは適切なツールを見つけてタスクを解決できました。

次に少し複雑なタスクを試してみましょう。

result1 = analyst_chain.invoke({
    'request': "ドイツの首都のユーザー数は2023年4月から5月にかけてどのように変化しましたか?",
    "observations": []
})

しばらく考えてみて、モデルがどのように推論するかを考えましょう。明らかに、モデルが直ちに答えるための十分な情報はありませんので、いくつかの関数呼び出しを行う必要があります:

  • ドイツの首都を調べるためにWikipediaを呼び出す
  • get_monthly_active_users関数を2回呼び出して4月と5月のMAUを取得する
  • percentage_differenceを呼び出して指標の差分を計算する

かなり複雑なようです。ChatGPTがこの質問に対処できるかどうか試してみましょう。

最初の呼び出しでは、LLMは次のパラメータを持つWikipediaの関数呼び出しを返しました: {'term': 'capital of Germany'}。これまで計画通りです。

次のステップを確認するために観察データを提供してみましょう。

observation1 = toolkit[result1.tool](result1.tool_input)
print(observation1)# ドイツの首都はベルリンです。ドイツの大統領の公式居住地はシュロス・ベルヴューです。 # ブンデスラート(「連邦評議会」)は、ドイツの連邦州(ブンデスランド)を代表するもので、 # かつてのプロイセン上院(国家院)に拠点を置いています。 # 大部分の省庁はベルリンに拠点を置いていますが、一部の省庁や小規模の部署はボンにあり、 # かつての西ドイツの首都です。# ベルリンは正式にはドイツ連邦共和国の首都ですが、連邦官僚機構に雇用されている # 約18,000人中8,000人がベルリンから約600km(370マイル)離れたボンで # 仕事をしています。 # 出典:https://en.wikipedia.org/wiki/Capital_of_Germany result2 = analyst_chain.invoke({
    'request': "ドイツの首都のユーザー数は2023年4月から5月にかけてどのように変化しましたか?",
    "observations": format_to_openai_functions([(result1, observation1)])
})

モデルは引数{'month': '2023–04–01', 'city': 'Berlin'}get_monthly_active_usersを実行したいと要求しています。それを実行し、再び情報をモデルに返しましょう。

observation2 = toolkit[result2.tool](result2.tool_input)
print(observation2)# 168result3 = analyst_chain.invoke({
    'request': "ドイツの首都のユーザー数は2023年4月から5月にかけてどのように変化しましたか?",
    "observations": format_to_openai_functions([(result1, observation1), (result2, observation2)])
})

次に、モデルは引数{'month': '2023–05–01', 'city': 'Berlin'}get_monthly_active_usersを再度呼び出すよう要求しています。これまでのところ、モデルは素晴らしい仕事をしています。その論理に従ってみましょう。

observation3 = toolkit[result3.tool](result3.tool_input)print(observation3)# 1046result4 = analyst_chain.invoke({    'request': "2023年4月から5月にかけて、ドイツの首都であるベルリンのユーザー数はどのように変化しましたか?",    "observations": format_to_openai_functions(      [(result1, observation1), (result2, observation2),       (result3, observation3)])})

次の結果は、percentage_differenceに対する関数呼び出しで、以下の引数を含みます {'metric1': 168, 'metric2': 1046}。観察を計算し、チェーンをもう一度呼び出しましょう。おそらく、これが最後のステップとなるでしょう。

observation4 = toolkit[result4.tool](result4.tool_input)print(observation4)# 523.27result5 = analyst_chain.invoke({    'request': "2023年4月から5月にかけて、ドイツの首都であるベルリンのユーザー数はどのように変化しましたか?",    "observations": format_to_openai_functions(      [(result1, observation1), (result2, observation2),       (result3, observation3), (result4, observation4)])})

最終的に、モデルから次のような応答が得られました:ドイツの首都であるベルリンのユーザー数は、2023年4月から5月にかけて約523.27%増加しました。

以下に、この質問のためのLLM呼び出しの完全なスキームを示します。

Illustration by author

上記の例では、連続的な呼び出しを手動で1つずつトリガーしましたが、これは簡単に自動化できます。

素晴らしい結果であり、LLMが推論を行い、複数のツールを活用できることが分かりました。この結果を実現するために、モデルは5つのステップを踏みましたが、最初に概要を示した計画に従った非常に論理的なパスでした。ただし、LLMを本番で使用する場合は、モデルが誤りを com導入し、評価および品質保証プロセスを導入することを念頭に置いてください。

完全なコードはGitHubで見つけることができます。

まとめ

この記事では、OpenAIの機能を使用してLLMに外部ツールを組み込む方法を学びました。抽出による構造化された出力の取得と、外部情報を使用して質問に対するルーティングの2つのユースケースを検討しました。最終的な結果は非常に素晴らしく、LLMが3つの異なるツールを使用してかなり複雑な質問に答えることができました。

LLMがデータアナリストに代わることができるかどうかという初期の質問に戻りましょう。現在のプロトタイプは基本的で、初級のアナリストの能力からは程遠いですが、これは始まりに過ぎません。引き続きお楽しみください!次回は、データベースにアクセスして基本的な質問に答えることができるエージェントを作成してみます。

参考文献

この記事はDeepLearning.AIの 「Functions, Tools and Agents with LangChain」 コースに触発を受けています。

</

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more