ChatGPT に Google の検索結果を使って回答してもらおう

FacebooktwitterredditpinterestlinkedinmailFacebooktwitterredditpinterestlinkedinmail

OpenAI の API を使って、ChatGPT の中身(のようなもの)を小学生でも扱えるように解説する試みです。

ChatGPT の弱点としてテック系メディアもビジネス系メディアも、流行り物系メディアもこぞって挙げるのが「答えが正確でない、信用できない」ということ。お前らの書く記事はそんなに信用できるのかと。

そこで、同じくそれほど信用できるかと言われると微妙な気もしますが、少なくとも新しい情報が反映される仕組みになっている Google の検索結果を基に GPT-3.5-turbo に回答を作ってもらいます。

今回も直接 API は叩かずに LangChain を経由します。むしろこういうことをするためのライブラリですから、今回からが本領発揮だと思います。似た記事は一杯溢れていますが、こんな記事はなんぼあってもいいですからね(参照先はリンクします)。

更新版の記事

この記事で使われているLangchainのバージョンは古く、満足な品質の出力が得られないかもしれません。手っ取り早く動くコードが見たい方は更新版の記事を確認してください。

ChatGPT に Google検索とSystem Roleを食わせてちゃんと日本語で回答させる

前回:

ChatGPT の API が公開されたので遊んでみよう!

LangChainのアップデート

py -m pip install -U langchain

昨日の今日でバージョンが上がっていたので更新。結構頻繁なアップデートがされているみたいなので、こまめにチェックするといいと思います(公式ドキュメント見に行くとバージョン変わっているので分かると思います)。

Google Search API (プログラマブル検索エンジン)の利用

パッケージのインストール

py -m pip install google-api-python-client

インストールしないと動きません。outh とかはなくても Search API は大丈夫みたいです。

API キーの発行

上記ページに行き、「使って見る」をクリック。画面の指示に従って、新しい検索エンジンを作成。分かりやすい名前をつけて、「ウェブ全体の検索」を指定。古い資料だと適当なドメインを設定して後から削除となっていますが、現在は必要ないようです。

なぞのHTMLコードが出て来たら閉じて、「検索エンジン ID 」をコピーして控えておきます。

画面をスクロールして、

プログラマティックなアクセスから、Custom Search JSON API の「開始する」ボタンをクリック。

キーの取得をクリックして API キーを控えます。

動作テスト

import os, key
os.environ["GOOGLE_CSE_ID"] = key.GOOGLE_CSE_ID
os.environ["GOOGLE_API_KEY"] = key.GOOGLE_API_KEY

from langchain.utilities import GoogleSearchAPIWrapper

search = GoogleSearchAPIWrapper()
print(search.run("what is chusho kigyo shindanshi?"))

応答を返してくれたら成功です。

筆者は blog へのコピペのために、key.py ファイルを作って、そこに検索エンジン ID(GOOGLE_CSE_ID)とAPIキー(GOOGLE_API_KEY や OpenAI KEY)を格納していますが、公開予定がない方は直接記述または環境変数に設定でもいいと思います。

というかVS Codeを使って環境変数に設定が正道だと思いますが、そうすると今度はソースコード内で API キーの存在を明示できないのでこんな感じにしています。

タイムアウトしてしまったら……

本来なら、この手順だけで Search API を使えるらしいのですが、筆者の環境ではタイムアウトが連発(API キーエラーなどではなく)。ターミナルの再起動や VS Code の再起動では直りませんでした。

    解決までの手順としては

    • とりあえず待ってみる→5分程度有効になるまで時間がかかるらしいので。解決せず。どっちにしろタイムアウトは何かおかしいような
    • $300の体験用クレジットを申し込んでみる→やっぱり解決せず。
    • OSごと再起動→動作開始。ただし時間が経ったことによる解決の可能性が一応残っている。

    とはいえ、 単に API キーが有効になっていない状態であれば Timeout ではなく API キーエラーを返すと思うので(開通後にわざと間違えたところ、ちゃんとキーエラーが返ってきた)、再起動してみるのが妥当だと思います。

    レイテンシを確認すると、1000ms 以内には応答しているものの、Timeout になっているので実装側の問題かもしれません。

    再起動すると直るので、多分オマカンだと思われます。ノートPCでスリープに入るのが怪しいかな? というのが現状です。

    前回の GPT-3.5-turbo への問い合わせに Google 検索を組み合わせてみる

    参考:

    OpenAI()となっているところを OpenAIChat() とすれば、大体ChatGPT相当になると覚えておけば大丈夫だと思います。

    # Google のプログラマブル検索エンジンの結果を GPT-3.5turbo に食わせて結果を出力するテスト
    
    # Google Search API 用の agent
    from langchain.agents import load_tools
    from langchain.agents import initialize_agent
    
    # GPT-3.5 prompt周りのimport
    from langchain.llms import OpenAIChat
    from langchain import PromptTemplate, LLMChain
    
    # API キー類を別ファイルに
    import key
    
    # 環境変数にAPIキーを設定
    import os
    os.environ["OPENAI_API_KEY"] = key.OPEN_API_KEY
    os.environ["GOOGLE_CSE_ID"] = key.GOOGLE_CSE_ID
    os.environ["GOOGLE_API_KEY"] = key.GOOGLE_API_KEY
    
    # 事前に振る舞いを指示
    prefix_messages = [
        {"role": "system", "content": "あなたは検索結果を元に一歩一歩考えて、親切に問題を解決するアシスタントです"}
    ]
    
    # prefixを含めてLLMの準備
    llm = OpenAIChat(
        temperature=0, 
        prefix_messages=prefix_messages
    )
    
    # Google Search API Wrapper
    # 名前ベースで tool agent を作成
    tools = load_tools(["google-search"], llm=llm)
    # agent を作成
    agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)
    
    agent.run("中小企業診断士について詳しく教えて")

    結果:

    途中経過ではなんやら動いてるっぽいですが、

    in get_action_and_input
    raise ValueError(f"Could not parse LLM output: `{llm_output}`")

    ValueError として {llm_output} がないよ! と怒られます。

    類似事例を検索してみると

    どうも GPT-3.5 はユーザーの指示に「強く従う」らしく、明確に出力先を指定してあげないとダメらしいです。

    加えて、出力が英語になってしまっていそうだったので修正します。

    Agent を作って出力してもらう

    参考:

    最初からこちらの記事を見なさいよという話もありますが、上手く行かないログも大事なので……(タイムアウトとか)。

    修正したコード

    # Google のプログラマブル検索エンジンの結果を GPT-3.5turbo に食わせて結果を出力するテスト
    
    # Google Search API 用の agent
    from langchain.agents import load_tools
    # AgentExecutor と ZeroShotAgent を読み込み
    from langchain.agents import AgentExecutor, ZeroShotAgent
    
    # GPT-3.5 prompt周りのimport
    from langchain.llms import OpenAIChat
    from langchain import PromptTemplate, LLMChain
    
    # API キー類を別ファイルに
    import key
    
    # 環境変数にAPIキーを設定
    import os
    os.environ["OPENAI_API_KEY"] = key.OPEN_API_KEY
    os.environ["GOOGLE_CSE_ID"] = key.GOOGLE_CSE_ID
    os.environ["GOOGLE_API_KEY"] = key.GOOGLE_API_KEY
    
    # prefix と suffix に変更
    prefix = "次の質問に、ツールを使って親切に答えてください。次のツールにアクセスできます:"
    suffix = """最終的な答えを出すときには、一歩一歩考えて回答してください。
    
    Question: {input}
    {agent_scratchpad}"""
    
    # Google Search API Wrapper
    # 名前ベースで tool agent を作成, llm は未定義なので デフォルト引数で OpenAIChat を作成
    tools = load_tools(["google-search"], llm=OpenAIChat())
    
    # ZeroshotAgent で prompt を作成
    prompt = ZeroShotAgent.create_prompt(
        tools,
        prefix=prefix,
        suffix=suffix,
        input_variables=["input", "agent_scratchpad"]
    )
    
    # 用意したprompt を使って、LLMChainを作成
    llm_chain = LLMChain(llm=OpenAIChat(temperature=0), prompt=prompt)
    
    
    # tool と LLMChain を与えて agent を作成
    agent = ZeroShotAgent(llm_chain=llm_chain, tools=tools)
    
    agent_excutor = AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
    
    agent_excutor.run("中小企業診断士について詳しく教えて")

    prefix と suffix に、 ChatGPT っぽく振る舞うように「親切に」「一歩一歩」などの文言を含めています。

    また、コメントにも記載した通り、変数・オブジェクトの生成順序がこれまでと変わっています。特に、 load_toolsでは、llm に作成済みのOpenAIChat オブジェクトを入れられないため、新規に全てデフォルト引数で作成を行っています。

    実行結果

    Thought:中小企業診断士は、中小企業の経営課題に対応するための診断・助言を行う専門家であり、国家資格であることがわかった。
    Final Answer: 中小企業診断士は、中小企業の経営課題に対応するための診断・助言を行う専門家であり、国家資格である。

    正しく答えが出ていますが、前回と比較すると非常に簡潔な回答になってしまいました。これは、OpenAIChatのコンストラクタに入れていた prefix_messages の system の content を省略したため? と考えられます。

    prompt には似たような文言を入れたので似たような動作をしてくれるかなと思ったのですが、そうは行かなかったようです。

    prefix_messages を代入してみる

    ZeroShotAgent では省略した OpenAI コンストラクタに prefix_messages を入れる方法ですが、これは

    ValueError: Could not parseLLM output: `{llm_output}`
    

    が出力されてしまい、上手くいきません。

    や、issue によると LLM(GPT-3.5-turbo)が要求したフォーマットで答えを返してくれないことによる、正規表現のパースエラーらしいです。作成されるprompt を見ると特別おかしいところはないみたいなので、LLMChain の中に入れたときに prefix_messages を設定するとおかしくなるようです(OpenAI APIの messages リストに直接値を入れる方法も調べてみましたが、現状はなさそうです)。

    suffix に入れて見る

    suffix = """
    role: system, あなたは一歩一歩考える親切なアシスタントです。
    最終的な答えを出すときには、一歩一歩考えて回答してください。
    
    Question: {input}
    {agent_scratchpad}"""
    

    prefix_messages っぽいものをsuffix の中に直接入れて見ます。

    結果:

    Thought:Based on the Google Search results, a "中小企業診断士" is a professional who provides diagnosis and advice on management issues for small and medium-sized enterprises. It is a national qualification in Japan and is regulated by the Ministry of Economy, Trade and Industry. It seems to be a highly regarded qualification for business professionals in Japan.
    Final Answer: A "中小企業診断士" is a professional who provides diagnosis and advice on management issues for small and medium-sized enterprises in Japan. It is a national qualification regulated by the Ministry of Economy, Trade and Industry.

    意図は伝わったのか、少し丁寧になりましたが、英語での回答になってしまいました。Google の検索結果(verboseによる出力)は日本語なのですが、内部的には英訳されているのでしょうか(それだと不正確な回答が多いように感じるのも仕方がないですね)。

    指示が日本語になってしまう

    色々suffix を試すのですが、Google Seach が Google検索となって「ツールが見つからない…」と延々とループしてtoken を食い潰したりするので、 LangChain 経由でGoogle 検索と ChatGPT を思う様に組み合わせるのは現状難しそうです。

    もっと色々試してみたい気もしますが、他の機能(embeddingsなど)も試してみたいので、一旦ここで切り上げです。

    終わりに

    もともとが英語のツールということもあり、なかなか難易度が高いですね。

    またLangChainはとても便利で高度なライブラリですが、その分内部が隠蔽されているので、途中経過が翻訳されたりされなかったりといった制御がなかなか大変そうです。上手く制御できる方法があるといいのですが……。

    FacebooktwitterredditpinterestlinkedinmailFacebooktwitterredditpinterestlinkedinmail

    コメントを残す

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

    このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

    最新の記事