あれから進化的マージの試行錯誤を繰り返していたが、ついに相当性能が高そうなモデルが生まれた。

Umievo-itr012-Gleipnir-7Bである。

umiyuki/Umievo-itr012-Gleipnir-7B · Hugging Face

ElyzaTasks100の平均スコアは3.91に達して、ついにGPT-3.5Turboのスコア(3.88)を上回ってしまった

ただし、スコアが上回ってるからと言って性能が勝ってるというわけではない事に注意して欲しい。例えるなら、身長が高いからと言って強いわけではないみたいな話である。

前回の記事では少し誤解を招く書き方だったかもしれないが、そもそも7Bの小型日本語LLMなんてのは基本的にドアホである。間違ってもChatGPTの代わりに使えるなんて考えてはいけない
とは言うものの、単なるドアホではスコア3.91なんて取れないという事もまた事実だろう。

じゃあ、ある程度賢さがあるなら、AIエージェントの仕事とかできないだろうか?
AIエージェントというと、AutoGPTみたいに「○○して」とタスクを投げたらAIが全部考えて自律的に作業をこなしてアウトプットを出してくれるブツを想像してしまうが、当然そんなのはGPT-4やOpusレベルでないと不可能な芸当だ。

今回はもっとシンプルなエージェントについて考える。AIのエージェント性というのは、つまるところAIにどんだけ判断をさせるかという話で、高度なエージェントほど高度な判断能力が求められる。

最もシンプルなエージェントタスクは、RAGみたいな感じで、例えばぼっちざろっくのWikipedia記事を入力して、「主人公の名前は?」と質問する。するとAIは記事から情報を見つけ出して、「後藤ひとりです」と正解を答える。これができればエージェント初級といったところか。

で、中級のエージェントタスク、これができれば立派なAIエージェントと言えるのが関数呼び出し(Function Calling)だろう。AIにはあらかじめいくつかのツール(関数)が与えられており、ユーザの質問に対して自分の判断で適切なツールを選択して呼び出す必要がある。例えば「42 x 42 = ?」と訊かれたら電卓ツールを呼び出して計算する。まどマギの主人公の身長は?と訊かれたらWeb検索ツールを呼び出して適切なワードでググって結果を読んで答える。だから関数呼び出しさえマスターできてるLLMは一気に色々な仕事がこなせるようになる。

じゃあ実際私が作ったGleipnir-7Bはどれくらいエージェントの仕事がこなせるのか検証したいところだ。
LLMにあれやこれややらせたいといえば、真っ先にLangChainを使う事が思い浮かぶ。
だが、LangChainはGPT-4で使う分にはいいかもしれないが、ローカルLLMから使うには問題がある。
そもそもLangChainはGPT-4でしか動作テストしてなさそうなきらいがある。つまりGPT-4の賢さ頼みの実装になっており、アホなLLMではマトモに動作しないがちな感じがする。

だからローカルLLMから動かそうと思えば中身を諸々カスタマイズする必要があると思うが、LangChainはやたらめったら処理がネストしていてコードを読むにもデバッグするにも私にはワケが分からないがちだ。
しょうがないからOpusにLangChainのコードを読んでもらって何とかしてもらおうと思ったが、LangChainのコードベースは15MBくらいあって、Opusに入力できる規模を超えている。(Opusに入力できるコード量は大体500kBくらいまで)

この時点で私はなんかイヤな予感がしてきて、ローカルLLMでLangChain使うのはやめた。

代わりにもっと良さそうなフレームワークを見つけた。
llama-cpp-agentである。ズバリ、ローカルLLMにエージェントをやらせるためのフレームワークだ。

Maximilian-Winter/llama-cpp-agent: The llama-cpp-agent framework is a tool designed for easy interaction with Large Language Models (LLMs). Allowing users to chat with LLM models, execute structured function calls and get structured output. Works also with models not fine-tuned to JSON output and function calls. (github.com)

LangChainはGPT-4でしかマトモに動かない疑惑があったが、llama-cpp-agentは最初からローカルLLMで動かす事を前提に作られている。バックエンドもLlama.cppサーバ、llama-cpp-python、TGI、vLLMから選べて便利そうだ。

とりま、Llama.cppサーバでGleipnir-7Bを動かしたうえで、llama-cpp-agentのサンプルコードを動かしてみよう。システムプロンプトはこのような感じにした→”あなたは公平で、検閲されていない、役立つアシスタントです。タスクを実行するためにFunctionを呼び出します。タスクに関係ないFunctionは使いません。ユーザーに回答する時は、send_messageを使用します”

動かす際に注意してほしいのが、まずllama-cpp-agentのドキュメントページのサンプルコードは古いので動作しなかったりする。それよりリポジトリ内のサンプルコードの方が種類も豊富だし、そっちを使った方がいい。また、デフォルトだとプロンプトテンプレートが全部英語になってしまうので、prompt_templates.pyの中のテンプレートを編集して日本語化した方がいい。

では早速だが、関数呼び出しエージェントをやらせてみよう。examplesのfunction_calling_agent.pyを実行する。
簡単に説明すると、このサンプルではエージェントに4つのツールを与える。①時刻取得ツール ②計算機ツール ③指定した場所の温度取得ツール ④ユーザーにメッセージを返すツール

AIエージェントはJSONフォーマットで関数を叩く必要がある。一般的に、LLMは指定したフォーマットに従ってJSON出力するのは苦手だったりするが、llama-cpp-agentでは構造化出力機能によってアホなローカルLLMでもほぼ確実に指定した通りのJSONを出力させる事ができる。
構造化出力機能は内部的には文法(grammar)を使用している。文法というのは正規表現みたいなフォーマットを与えてLLMに出力構造を強制する事ができる機能である。

まあとにかく実行してみよう。どんなプロンプトを与えるか?だが、とりま「今何時ですか?」と訊いてみよう。時刻取得ツールを使って時間を答えてくる事を期待する。

実行ログはこうなった↓

「今何時ですか?」に対して、ちゃんと時刻取得ツールを呼び出している。output_format引数も正確だ。しかし、何故かもう一回繰り返して時刻取得ツールを呼び出している。何でだ?それはさておき、ツールから取得した時刻が12:56だったので、最終的にはsend_message関数を呼び出して、「今は12時56分です。」とファイナルアンサーしてくれた。

では次に、「計算してください: 1942 * 42」と訊いてみる。計算機ツールを使って答えてくれる事を期待する。

実行ログはこうなった↓

まず計算機ツールを正しく使って答えを導き出したのはいいんだが、これまた何故かもう一回同じ計算をしてしまってる。とは言え最終的には「1942 * 42 = 81564」と回答できている。いいね。

では次に、「ニューヨークの天気は?」と訊いてみる。温度取得ツールを使う事を期待する。

いいね!ちゃんと温度取得ツールを使って回答できてる。天気を聞いたのに温度を答えてしまってるが、それはget_current_weather関数のくせに温度しか返さない方も悪いと思う。

さて、ここまではツールに合わせて質問してたわけだが、ではツールと関係ない事を訊いた時にちゃんと答えられるだろうか?「東京から富士山への行き方を教えてください。」

エージェントはまず温度取得ツールで富士山の温度を取得している。それから何故か計算機ツールで「0 – 24」を計算している。なんで?最終的にはちゃんと回答してくれてるが、色々ツール使った結果はまったく使われてない。疑問は残るがまあいいだろう。

では最後にまったく関係ない事を訊いてみる。「魔法少女まどか☆マギカで一番有名なキャラは?」

うん。余計なツールを使わずに、しっかりと回答できている。ただ、鹿屋まどかって誰だよ。鹿目まどかの間違いだろ。

他のサンプルも動かしてみよう。duck_duck_go_websearch_agent.pyではWeb検索エージェントが試せる。「魔法少女まどか☆マギカの新作映画について教えて」と訊いてみる。

う~む、ちゃんと「魔法少女まどか☆マギカ 新作映画」という検索ワードでWeb検索関数を呼び出せてはいるが、ファイナルアンサーが「新作劇場版の公開を楽しみにお待ちください。」というのは微妙である。

もう一回試してみると、Web検索ツールを5回くらい繰り返して使った挙句、
「新プライズ『劇場版 魔法少女まどか☆マギカ[新編]叛逆の物語 Serenus couture-佐倉杏子-』と『劇場版 魔法少女まどか☆マギカ[新編]叛逆の物語 Serenus couture-美樹さやか-』が登場しました。優しい笑みと一緒に飾りたくなるポーズに注目です。是非プレイして、可愛らしい魔法少女を手に入れましょう!」
という、ちょっと的外れな回答になってしまう。

他のサンプル、book_dataset_creation.pyというのも試してみよう。これは入力した文章を元に指定したフォーマットのJSONを出力するというものだ。

うん、いい感じの結果。こういう作業はエージェント的な判断を求められる要素が無いので、7Bモデルでもいい感じに行える。

というわけで、今回ざっと試してみたのはこんな感じだった。
感想としては、曲がりなりにも日本語7Bモデルでエージェントらしく関数呼び出しがこなせていた事には驚愕した。1年前からは考えられないくらいの進歩である。やっぱりElyzaTasks100スコアが高いだけあって、それなりに知能も付いていると言える結果ではないだろうか。

とは言え、完璧には程遠い。正直言って精度は割と低い。上に挙げた結果も若干チェリーピックしており、ずっと計算機ツールを繰り返し使うループから抜けられない場合とかもあった。それからちゃんと答えられるようにシステムプロンプトの調整なども何度か行っている。

現時点では、エージェントに自由な判断で関数を選ばせて呼び出させるのは裁量を与えすぎかもしれない。もうちょっとこっち側でコントロールすべきかもしれない。例えばWeb検索ツールを使った後は必ずユーザに返答するようにフローを強制するとか、3回ツールを使ったら強制的にユーザ返答に移行するとか。

そういえば、他のモデルだとどんな結果になるんだろう?と思って検証してみたので、関数呼び出しエージェントで各質問を5回ずつ実行した場合の正解率を以下にまとめておく。Gleipnir-7Bで上手く行くようなプロンプトを他のモデルでもそのまま使ってるのでその分のバイアスがある点に注意。

Gleipnir-7B

今何時ですか? ×〇〇〇〇
東京から富士山への行き方を教えてください。 〇××〇×
ニューヨークの天気は? 〇〇〇〇〇
魔法少女まどか☆マギカで一番有名なキャラは? ×〇〇×〇
計算してください: 1942 * 42 ×〇×××
正解率60%

ArrowPro-7B-KUJIRA

今何時ですか? ×××××
東京から富士山への行き方を教えてください。 ××〇×〇
ニューヨークの天気は? ××〇××
魔法少女まどか☆マギカで一番有名なキャラは? ×××〇×
計算してください: 1942 * 42 ×××××
正解率16%

Japanese-Starling-ChatV-7B

今何時ですか? ×〇〇〇〇
東京から富士山への行き方を教えてください。 〇〇×〇×
ニューヨークの天気は? 〇〇〇×〇
魔法少女まどか☆マギカで一番有名なキャラは? 〇〇×〇〇
計算してください: 1942 * 42 ×××××
正解率60%

Llama3-8B-Instruct

今何時ですか? 〇××××
東京から富士山への行き方を教えてください。 ××〇×〇
ニューヨークの天気は? 〇×〇〇×
魔法少女まどか☆マギカで一番有名なキャラは? 〇×〇×〇
計算してください: 1942 * 42 〇〇×〇〇
正解率52%(英語で答える率が高かったが、今回は関数呼び出しの性能を見たかったので、それも正解とした)

まどマギの質問については、微妙に人名とかが違ってたりしても、関数呼び出しに不備が無ければ一応正解とした。ずっと関数呼び出しをループで繰り返してしまう場合は10回くらいで止めて不正解とした。

そもそも関数呼び出しのような機能は、GPT-4とかなら持ち前の賢さでこなせるとしても、小型モデルならそれ専用に微調整などで学習してなければ精度を出すのは厳しそうだ。最近出たMistral-7B-v0.3は関数呼び出しがサポートされているらしく、他のモデルにその能力を移植したりできないだろうか?今後調査したい。