AI

LangChainとOllamaでMAGI SYSTEMを作る

Takuya Kobayashi⛰
2024.06.22

LangChainとOllamaを使って、異なるペルソナを持つ3つのLLMの出力結果をまとめて結論を出すMAGI SYSTEMを作ってみました。

複数のLLMの出力をまとめて良質な出力を得る手法を「Mixture-of-Agents(MoA)」と呼ぶそうです。
https://arxiv.org/html/2406.04692v1

コード全文:

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_community.llms import Ollama
import readline

# ―――――――――――――――――――――――――――――――――――――――――――――
# プロンプト
# ―――――――――――――――――――――――――――――――――――――――――――――

prompt_tpl_melchior = """
議題:{agenda}

指示:論理的に議題を思考し、完結に三行で意見を書いてください

出力:"""

prompt_tpl_balthasar = """
議題:{agenda}

指示:感情的に議題を思考し、完結に三行で意見を書いてください

出力:"""

prompt_tpl_casper = """
議題:{agenda}

指示:ユニークな視点で議題を思考し、完結に三行で意見を書いてください。

出力:"""

prompt_tpl_magi = """
You have been provided with a set of responses from various open-source models to the latest user query. Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability.

Responses from models:

1.{melchior}

2.{balthasar}

3.{casper}

ここからは日本語で出力:"""

prompt_melchior = PromptTemplate(
    template=prompt_tpl_melchior,
    input_variables=["agenda"],
)
prompt_balthasar = PromptTemplate(
    template=prompt_tpl_balthasar,
    input_variables=["agenda"],
)
prompt_casper = PromptTemplate(
    template=prompt_tpl_casper,
    input_variables=["agenda"],
)
prompt_magi = PromptTemplate(
    template=prompt_tpl_magi,
    input_variables=["melchior", "balthasar", "casper"],
)

# ―――――――――――――――――――――――――――――――――――――――――――――
# チェーン
# ―――――――――――――――――――――――――――――――――――――――――――――

llm = Ollama(model="killerwhale")

chain_melchior = (
    {"agenda": RunnablePassthrough()}
    | prompt_melchior | llm | StrOutputParser()
)
chain_balthasar = (
    {"agenda": RunnablePassthrough()}
    | prompt_balthasar | llm | StrOutputParser()
)
chain_casper = (
    {"agenda": RunnablePassthrough()}
    | prompt_casper | llm | StrOutputParser()
)
chain_magisystem = (
    {"melchior": RunnablePassthrough(), "balthasar": RunnablePassthrough(), "casper": RunnablePassthrough()}
    | prompt_magi | llm | StrOutputParser()
)

# ―――――――――――――――――――――――――――――――――――――――――――――
# 実行
# ―――――――――――――――――――――――――――――――――――――――――――――

user_input = input("議題を入力してください>>> ")

# RunnableParallelでチェーンを並列実行
chain_parallel = RunnableParallel(
    melchior  = chain_melchior,
    balthasar = chain_balthasar,
    casper    = chain_casper,
)
chain_parallel_result = chain_parallel.invoke({"agenda": user_input})

# 複数の出力を総括する
result = chain_magisystem.invoke(
    {
        "melchior": chain_parallel_result["melchior"],
        "balthasar": chain_parallel_result["balthasar"],
        "casper": chain_parallel_result["casper"]
    }
)
print(result)

とてもスッキリ見やすいコードに仕上がりました。

出力:

議題「きのこの山とたけのこの里はどちらが美味しいか」をMAGI SYSTEMに提出しました。

出力は以下の通りです:

総合的に考えると、『きのこの山』と『たけのこの里』は、それぞれが独自の魅力を持ち、支持される理由が明確なスナック菓子です。『きのこの山』には複雑な味わいと種類豊富なきのこが特徴で、大人層を中心に愛されています。一方、『たけのこの里』にはもちもち感ある食感とコーンの甘みが魅力で、若者や乳製品好きから支持を集めています。
美味しさの基準は個人差があり、どちらが上とは一概に決められないことが重要です。それぞれのフレーバーが異なる市場で成功しており、多様化する消費者ニーズに応える形で共存しています。
拡張エピソードとしては、将来的には『きのこの山』と『たけのこの里』がコラボレーションを行い、新しい味覚体験を提供する商品開発が期待されます。このような取り組みは、単にお菓子の範競サを超え、消費者文化や社会現象にも影響を与える可能性があります。
心理学的視点から見た場合、選択するスナックが個人のアイデンティティや価値観、文化背景にどう影響するかという研究は、深く掘り下げるべき重要なテーマです。これらの探求を通じて、我々はより理解を深め、多様な消費者文化に対する認識を広げることができます。
以上から、『きのこの山』と『たけのこの里』は、それぞれ異なる魅力を持ち、どちらも必要不可欠な存在であることがわかります。個人の好みに応じて選ばれるべきであり、お菓子業界全体の発展や、文化的・社会的な側面からも大切にされる存在なのです。

MAGI SYSTEMによる出力

事実には基づいていませんが、文章自体には説得力があります。

ちなみに単体のモデルに「議題:きのこの山とたけのこの里はどちらが美味しいか」と投げると次のような出力になります。

掲示板に投稿された意見をいくつか紹介します。

  1. きのこの山派の意見
    ・定番のおいしさで安定感がある。具材のバランスが良く、甘みと酸味の絶妙なハームニーが楽しめる。
    ・きのこ類が好きな人にはたまらない、種類豊富で食感も様々なきのこが入っている。
    ・昔から親しまれており、馴染み深い味わいがある。
  2. たけのこの里派の意見
    ・クリーミーなチーズ味が好きで、新しい味わいを楽しめる。たけのこ型の形状も面白く、食感が異なる。
    ・たけのコ本来の風味と、柔らかい食感が特徴的。シリーズ化されている様々な味わいが魅力。
    ・期間限定商品や、新しい取り組みに積極的なイメージがある。
    どちらが美味しいかは、個人の好みによって大きく変わってくると思われます。きのこの山派の人はその安定感と具材のバランス
    、豊富なきのこ類を、たけのこの里派の人はクリーミーな味わいとたけのコの風味、そして新しい取り組みに魅力を感じるようで
    す。自分自身に合ったチョイスを見つけることが大切ですね。(以上、両派の意見を紹介)
    結論としては、「美味しさ」は完全に個人の好み次第なので、一概にどちらが上とは言えません。大切なのは自分の口に合ったも
    のを選び、それぞれの特徴や味わいを楽しむことです。気軽に両方を試してみるのもおすすめですよ。
ArrowPro-7B-KillerWhaleによる出力

モデル単体の出力よりも、MAGI SYSTEMの方が多角的に語りつつ良くまとめられている印象です。

コード解説:

まず、必要なライブラリをインポートします。
インストールされていない場合は、次のコマンドでインストールしてください。
pip install langchain-core langchain-community

from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_community.llms import Ollama
import readline

「import readline」は、input()で全角文字を取り扱う際の不具合を解消してくれます。

次に、プロンプトを定義するセクションです。
下記のように3つのペルソナのプロンプトを用意します。

prompt_tpl_melchior = """
議題:{agenda}

指示:論理的に議題を思考し、完結に三行で意見を書いてください

出力:"""
prompt_melchior = PromptTemplate(
    template=prompt_tpl_melchior,
    input_variables=["agenda"],
)

意見を総括するプロンプトを定義します。
テンプレ―トはこちらを参考にしました。

prompt_tpl_magi = """
You have been provided with a set of responses from various open-source models to the latest user query. Your task is to synthesize these responses into a single, high-quality response. It is crucial to critically evaluate the information provided in these responses, recognizing that some of it may be biased or incorrect. Your response should not simply replicate the given answers but should offer a refined, accurate, and comprehensive reply to the instruction. Ensure your response is well-structured, coherent, and adheres to the highest standards of accuracy and reliability.

Responses from models:

1.{melchior}

2.{balthasar}

3.{casper}

ここからは日本語で出力:"""
prompt_magi = PromptTemplate(
    template=prompt_tpl_magi,
    input_variables=["melchior", "balthasar", "casper"],
)

次にチェーンを作ります。

llm = Ollama(model="killerwhale")

chain_melchior = (
    {"agenda": RunnablePassthrough()}
    | prompt_melchior | llm | StrOutputParser()
)
chain_balthasar = (
    {"agenda": RunnablePassthrough()}
    | prompt_balthasar | llm | StrOutputParser()
)
chain_casper = (
    {"agenda": RunnablePassthrough()}
    | prompt_casper | llm | StrOutputParser()
)
chain_magisystem = (
    {"melchior": RunnablePassthrough(), "balthasar": RunnablePassthrough(), "casper": RunnablePassthrough()}
    | prompt_magi | llm | StrOutputParser()
)

Ollama(model=”killerwhale”)はollamaに登録されているLLMを指定します。

Huggingfaceなどに上がっているggufファイルをollamaで利用する方法はこちらを参考にしてください。

チェーンを実行します。
RunnableParallelで3つのチェーンを並列に実行することができます。

# RunnableParallelでチェーンを並列実行
chain_parallel = RunnableParallel(
    melchior  = chain_melchior,
    balthasar = chain_balthasar,
    casper    = chain_casper,
)
chain_parallel_result = chain_parallel.invoke({"agenda": user_input})

最後に意見を総括するチェーンを実行して出力を表示します。

# 複数の出力を総括する
result = chain_magisystem.invoke(
    {
        "melchior": chain_parallel_result["melchior"],
        "balthasar": chain_parallel_result["balthasar"],
        "casper": chain_parallel_result["casper"]
    }
)
print(result)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です