LLM の構築#
from openai import OpenAI
from pydantic import Field # Pydanticモデルでフィールドのメタデータを定義するためのFieldをインポート
from llama_index.core.llms import (
CustomLLM,
CompletionResponse,
LLMMetadata,
)
from llama_index.core.embeddings import BaseEmbedding
from llama_index.core.llms.callbacks import llm_completion_callback
from typing import List, Any, Generator
# OurLLMクラスを定義し、CustomLLM基底クラスを継承
class OurLLM(CustomLLM):
api_key: str = Field(default=api_key)
base_url: str = Field(default=base_url)
model_name: str = Field(default=chat_model)
client: OpenAI = Field(default=None, exclude=True) # clientフィールドを明示的に宣言
def __init__(self, api_key: str, base_url: str, model_name: str = chat_model, **data: Any):
super().__init__(**data)
self.api_key = api_key
self.base_url = base_url
self.model_name = model_name
self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) # 渡されたapi_keyとbase_urlを使用してclientインスタンスを初期化
@property
def metadata(self) -> LLMMetadata:
"""LLMメタデータを取得します。"""
return LLMMetadata(
model_name=self.model_name,
)
@llm_completion_callback()
def complete(self, prompt: str, **kwargs: Any) -> CompletionResponse:
response = self.client.chat.completions.create(model=self.model_name, messages=[{"role": "user", "content": prompt}])
if hasattr(response, 'choices') and len(response.choices) > 0:
response_text = response.choices[0].message.content
return CompletionResponse(text=response_text)
else:
raise Exception(f"予期しないレスポンス形式: {response}")
@llm_completion_callback()
def stream_complete(
self, prompt: str, **kwargs: Any
) -> Generator[CompletionResponse, None, None]:
response = self.client.chat.completions.create(
model=self.model_name,
messages=[{"role": "user", "content": prompt}],
stream=True
)
try:
for chunk in response:
chunk_message = chunk.choices[0].delta
if not chunk_message.content:
continue
content = chunk_message.content
yield CompletionResponse(text=content, delta=content)
except Exception as e:
raise Exception(f"予期しないレスポンス形式: {e}")
llm = OurLLM(api_key=api_key, base_url=base_url, model_name=chat_model)
コード解析:
全体のコードは OurLLM クラスを定義し、CustomLLM クラスを継承しています。このクラスはカスタム LLM と対話するために使用されます。
- モジュールのインポート:
- OpenAI:OpenAI の API と対話するために使用されます。
- Field:pydantic から、データモデルフィールドのメタデータを定義するために使用されます。
- CustomLLM、CompletionResponse、LLMMetadata:llama_index.core.llms から、カスタム言語モデルおよびその応答とメタデータを定義するために使用されます。
- BaseEmbedding:埋め込みモデルに使用されます。
- llm_completion_callback:デコレーターで、LLM の完了コールバックを処理します。
- List, Any, Generator:typing から、型注釈に使用されます。
- OurLLM クラス:
- フィールド:
- api_key、base_url、model_name:Field を使用してデフォルト値を定義します。
- client:client という名前のフィールドを定義し、その型は OpenAI であり、client は OpenAI クラスのインスタンスです。client フィールドのデフォルト値は None であり、このモデルオブジェクトが JSON や他の形式に変換されるとき、client フィールドは結果に表示されません。これは、機密情報を保護したり、データ量を減らしたりするのに非常に便利です。 - コンストラクタ
- init: api_key、base_url、model_name を初期化し、OpenAI クライアントインスタンスを作成します。
- metadata: モデルのメタデータを返し、モデル名を含み、返り値の型は LLMMetadata です。metadata メソッドは @property デコレーターで修飾されており、llm.metadata を通じて metadata メソッドの返り値にアクセスできます。
-complete : llm_completion_callback は通常、言語モデル(LLM)がテキスト生成タスクを完了したときに呼び出されるコールバック関数です。これは、監視とログ記録、リソース管理、後処理などの目的で使用できます。self.client.chat.completions.create メソッドを使用して API を呼び出します。response に choices 属性があり、choices リストの長さが 0 より大きいかを確認します。条件が満たされると、choices から最初の message の content を抽出し、それを CompletionResponse のテキストとして返します。
- stream_complete:for ループを使用して、レスポンス内の各チャンクを反復処理します。chunk.choices [0].delta で現在のチャンクのメッセージ内容を抽出します。chunk_message.content が空であれば、そのチャンクをスキップします。yield キーワードを使用して、現在のチャンクのテキストと増分内容を含む CompletionResponse オブジェクトを返します。
定義した LLM クラスが成功したかどうかの検証#
response = llm.stream_complete("あなたは誰ですか?")
for chunk in response:
print(chunk, end="", flush=True)
結果:
私は ChatGLM という名前の人工知能アシスタントで、2024 年に清華大学 KEG 研究室と智譜 AI 社によって共同訓練された言語モデルに基づいて開発されました。私の任務は、ユーザーの質問や要求に対して適切な回答とサポートを提供することです。
カスタムエージェント内の特定の処理フロー#
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
from llama_index.core.agent import ReActAgent
from llama_index.core.tools import FunctionTool
def multiply(a: float, b: float) -> float:
"""2つの数を掛け算し、積を返します"""
return a * b
def add(a: float, b: float) -> float:
"""2つの数を足し算し、合計を返します"""
return a + b
def main():
multiply_tool = FunctionTool.from_defaults(fn=multiply)
add_tool = FunctionTool.from_defaults(fn=add)
# ReActAgentインスタンスを作成
agent = ReActAgent.from_tools([multiply_tool, add_tool], llm=llm, verbose=True)
response = agent.chat("20 + (2 * 4) はいくつですか?ツールを使って各ステップを計算してください")
print(response)
if __name__ == "__main__":
main()
コード解析:
- add、multiply 関数を定義
- FunctionTool.from_defaults メソッドを使用して multiply と add 関数をツールとしてラップします。その後、これらのツールと LLM を渡して ReActAgent インスタンスを作成します。verbose=True は、実行中に詳細情報を出力することを意味します。
- agent.chat (...) メソッドはエージェントとの対話に使用され、ここでは数学の問題が渡され、エージェントにツールを使って段階的に計算するように求めています。
ReActAgentは推論(Reasoning)と行動(Acting)を組み合わせて動的な LLM エージェントのフレームワークを作成します。この方法により、LLM モデルは複雑な環境で推論ステップと行動ステップを交互に行うことで、より効率的にタスクを実行できます。ReActAgent は推論と行動を閉ループに形成し、エージェントは与えられたタスクを自分で完了できます。
典型的な ReActAgent は以下のループに従います:
初期推論:エージェントは最初に推論ステップを行い、タスクを理解し、関連情報を収集し、次の行動を決定します。 行動:エージェントは推論に基づいて行動を取ります。例えば、API を照会したり、データを取得したり、コマンドを実行したりします。 観察:エージェントは行動の結果を観察し、新しい情報を収集します。 最適化推論:新しい情報を利用して、エージェントは再度推論を行い、理解、計画、または仮説を更新します。 繰り返し:エージェントはこのループを繰り返し、推論と行動の間を交互に行い、満足のいく結論に達するか、タスクを完了するまで続けます。
コードを実行した結果:
ステップ 8b8dc223-de0e-41ac-836e-87917ab466a3 を実行中。ステップ入力: 20 + (2 * 4) はいくつですか?ツールを使って各ステップを計算してください
考え:ユーザーは加算と乗算を含む計算を求めています。私は答えを得るためにツールを使用する必要があります。
行動: multiply
行動入力: {'a': 2, 'b': 4}
観察: 8
ステップ 13110be6-d7f8-4cad-85ed-60148842b327 を実行中。ステップ入力: None
考え:ユーザーは 20 + (2 * 4) を計算したいと思っています。私はすでに乗算部分を計算しました。今、私は乗算の結果に 20 を加える必要があります。
行動: add
行動入力: {'a': 20, 'b': 8}
観察: 28
ステップ efe67457-e8f7-4f3d-9a27-c48d3c883f18 を実行中。ステップ入力: None
考え:もうこれ以上ツールを使わずに答えることができます。私はユーザーの言葉を使って答えます。
答え: 28
28
しかし、何度も再生成すると、出てくる答えには安定性がないことがわかります。これについては強化が必要かもしれません。
天気を問い合わせるメソッドの追加#
私たちが大モデルに天気の質問をすると、ツールがない場合、大モデルはこのように答えます。彼は天気の状況を知らず、どこで天気を確認できるかを教えてくれます。今、私たちのエージェントに天気を問い合わせるメソッドを追加し、テスト用に偽データを返します。
def get_weather(city: str) -> int:
"""
指定された都市の天気の気温を取得します。
引数:
city (str): 都市の名前または略称。
戻り値:
int: 都市の気温。'NY'(ニューヨーク)には20を、'BJ'(北京)には30を、未知の都市には-1を返します。
"""
# 入力された都市名を大文字に変換して、大文字小文字を区別しない比較を処理します
city = city.upper()
# 都市がニューヨーク('NY')かどうかを確認
if city == "NY":
return 20 # ニューヨークのために20°Cを返します
# 都市が北京('BJ')かどうかを確認
elif city == "BJ":
return 30 # 北京のために30°Cを返します
# 都市が'NY'でも'BJ'でもない場合、未知の都市を示すために-1を返します
else:
return -1
weather_tool = FunctionTool.from_defaults(fn=get_weather)
agent = ReActAgent.from_tools([multiply_tool, add_tool, weather_tool], llm=llm, verbose=True)
response = agent.chat("ニューヨークの天気はどうですか?")
ステップ 12923071-c8af-4154-9922-242a1fe96dd0 を実行中。ステップ入力:ニューヨークの天気はどうですか?
考え:ユーザーはニューヨークの天気を尋ねています。私は答えるためにツールを使用する必要があります。
行動: get_weather
行動入力: {'city': 'NY'}
観察: 20
ステップ 7a5ab665-2f32-498e-aaa0-4caa5a7d6913 を実行中。ステップ入力: None
考え:私はニューヨークの天気情報を取得しました。今、ユーザーに答える必要があります。
答え:ニューヨークの現在の天気は 20 度です。
モデルの推論能力が非常に強力で、ニューヨークを NY に変換しました。ReActAgent はビジネスを自動的にコードに変換することを可能にし、API モデルがあれば呼び出すことができ、多くのビジネスシーンに適用できます。LlamaIndex は、いくつかのオープンソースのツール実装を提供しており、公式ウェブサイトで確認できます。
エージェントはビジネス機能を実現できますが、1 つのエージェントがすべての機能を完了することはできません。これはソフトウェアのデカップリング設計原則に合致しており、異なるエージェントが異なるタスクを完了し、それぞれの役割を果たし、エージェント間で相互作用や通信が可能であり、マイクロサービスに似ています。