Gluegent Blog

Gluegent Blog

LangChainを用いてブログ記事作成をしてみた

  • 技術

お久しぶりです。GluegentSL新卒2年目エンジニアの塩山です。
今回は今書いているブログ記事作成もAIによって自動化できないかなと思い、ChatGPT APIやLangChainを用いた簡単なプログラムを作ってみたので、そちらの紹介をしようと思います。また、今回使ったLangChainについても詳しく説明していきます。

LangChainを用いてブログ記事作成をしてみた

LangChainとは

LangChainとは大規模言語モデル(LLM)を活用したアプリケーション開発を効率化するためのライブラリです。「Chain(鎖)」と名前に含まれているだけあり、複数のタスクやステップを組み合わせて「チェーン」を作成することができます。これにより、例えば情報抽出、質問応答、テキスト生成などの複数のタスクを連携させて一連の処理を行うことが容易になります。

pythonでは以下のコードをターミナルで打つとインストールして使用できます。

(ターミナル)
$ pip install langchain

LangChainの主な機能

  1. Model I/O(モデルの入出力)Model I/Oは、言語モデルとの入出力のやり取りを簡便にするための機能です。この機能により、ユーザーは大規模言語モデル(LLM)へのプロンプトの送信や、モデルからの応答の取得をシンプルなAPIを通じて行うことができます。これにより、LLMを使ったアプリケーション開発が容易になります。

  2. Retrieval(情報検索)
    Retrievalは、外部データソースから必要な情報を検索して取得する機能です。LangChainは、データベース、ドキュメントストア、APIなどと連携して、ユーザーが必要とするデータを取得し、言語モデルに供給することができます。LangChainは、データベース、ドキュメントストア、APIなどと連携して、ユーザーが必要とするデータを取得し、言語モデルに供給することができます

  3. Chains(チェーン)
    Chainsは、複数のNLPタスクを連結して一連の処理を自動化する機能です。例えば、入力テキストの前処理、情報抽出、テキスト生成を順番に行うチェーンを構築できます。これにより、複雑なワークフローを簡単に実装することが可能になります。

  4. Agents(エージェント)
    Agentsは、特定のタスクを動的に選択して実行するためのインテリジェントなエージェントです。エージェントはユーザーの指示に応じて、適切なアクションを選択し、必要なタスクを実行します。例えば、質問応答、データ検索、テキスト生成など、複数のタスクをエージェントが自動的に判断して処理します。

  5. Memory(メモリ)
    Memoryは、対話の履歴や状態を保持する機能です。これにより、エージェントやモデルは会話のコンテキストを理解し、継続的な対話を行うことができます。メモリ機能は、長期的なユーザーエンゲージメントや複雑な対話システムにおいて特に重要です。

  6. Callbacks(コールバック)
    Callbacksは、特定のイベントが発生したときに実行される関数やフックの仕組みです。LangChainでは、チェーンの各ステップやエージェントのアクションに対してコールバックを設定することで、カスタムロジックを追加したり、デバッグ情報を収集したりできます。これにより、処理の流れを柔軟に制御することが可能になります。

LangChainを使う上での注意点

LangChainを使用する際の注意点として互換性のないアップデートが頻繁に行われる可能性があります。最新の情報を確認したり、参考にする情報やコードにバージョンを合わせる必要があります。
(※筆者のLangChainのversionは0.2.3です。)

(ターミナル)
#バージョンを合わせてインストール、またはダウングレードする
$ pip install langchain==0.2.3

#バージョンを確認する
$pip show langchain

ブログ記事作成

今回はLangChainを用いてGoogle検索で取得した情報を基に記事を生成するプログラムを作成していきたいと思います。
具体的には、下記のようなことができるプログラムです。

  1. 「app_blog.py」というファイルをターミナルで実行する
  2. ニュース記事のテーマについて入力する
(ターミナル)
$ python app_blog.py
記事のテーマを入力してください: 大谷選手について

  1. Google検索により情報を取得し、ChatGPTが記事を生成する
  2. 作成した記事をGoogleドキュメントに書き出す

OpenAI API

今回はchatgpt-4oのモデルを使用するので、OpenAI APIを取得する必要があります。
4oは1M トークン(日本語約250万文字程度)当たり入力5ドル、出力15ドルの料金がかかります。


OpenAI APIのサイトにアクセスし、APIキーを取得してください。
これを環境変数に「OPENAI_API_KEY」という変数名で設定してください。
(※langchainでは明示的に環境変数を設定する必要がなく、langchain_openaiの中にあるChatOpenAIというメソッドが環境変数を自動的に参照してくれます。)

Googleの検索エンジンAPI

今回はLLM(ChatGPT)に渡す作成したいテーマに関する情報をGoogleの検索エンジンAPIによって取得していきます。GCP(Google Cloud Platform)やProgrammable Search EngineからアクセスできるCustom Search JSON APIを用います。1 日あたり 100 件の検索クエリを無料で利用できます。
(※最新モデルのGPT-4oはGoogle検索できるようですが、今回LangChainを使う例としてGoogleの検索エンジンAPIを用いました。GPTが取得できない情報を得られるAPIに置き換えれば実用性は上がると思います。)

      1. APIの取得
        Programmable Search Engineのサイトにアクセスし、API keyと検索エンジンIDを取得する。(GCPからでも取得可)

      2. 取得したAPIを環境変数に設定
        API keyを「GOOGLE_API_KEY」、検索エンジンIDを「GOOGLE_CSE_ID」という環境変数に設定する。
        (※こちらもOpenAI API同様に環境変数を設定する必要がなく、langchain_google_communityの中にあるGoogleSearchAPIWrapperというメソッドが自動参照します。)

      3. 必要なライブラリのインストール

        以下のコードをターミナルで打つとインストールして使用できます。

(ターミナル)
$ pip install google-api-python-client


(※GoogleSearchAPIWrapperはlangchainの中にあるためインストール不要ですが、
GoogleSearchAPIWrapperが内部で使用しているgoogle-api-python-clientのインストールが必要です。)

Agentsで指定する必要がある変数

AgentsはLangChainの主な機能の章でも紹介した通り、ユーザーの要求を「どのようなアクションをどのような順序で解決するか」LLMによって自動で決定し、実行してくれる機能です。このAgentsをプログラムで用いるので、そこで指定する必要がある3つの変数について具体的なコードも交えて紹介していきます。

(python)
#agentを作成するにはmodel,tools,apgent_promptの3変数を指定する必要がある 
agent = create_openai_tools_agent(model, tools, agent_prompt)


1. Model

LLMのモデルを指定します。
モデルにはOpenAIのgpt-4oやGooglePaLMのpalm-2,  HuggingFaceのbertなどを指定できます。今回はOpenAIのgpt-4oを使用します。

(python)
 # # OpenAIのgpt-4oモデルを設定
 from langchain.llms import OpenAI 
 model = ChatOpenAI(temperature=0, model="gpt-4o", max_tokens=2000)

# Google PaLMのpalm-2モデルを設定
from langchain.llms import GooglePaLM 
model = GooglePaLM(api_key='your_google_palm_api_key', model='palm-2')

# Hugging FaceのBERTモデルを設定
from langchain.llms import HuggingFace
model = HuggingFace(model_name="bert-base-uncased")


2. Tools
エージェントが特定のタスクを実行するために使用するツールです。例えば、計算
やデータ検索などのタスクに対応するためのツールを設定します。今回はデータ検
索で必要なGoogleSearchAPIWrapperのみを使用します。

(python)
from langchain_google_community import GoogleSearchAPIWrapper
from langchain.tools import Calculator
google_search = GoogleSearchAPIWrapper(name="GoogleSearch", description="Uses Google Custom Search API to search for information on the web." )
calculator = Calculator()

tools = [
google_search,
calculator 
]


3. Prompt
エージェントがどのように動作するかを定義する指示やガイドラインです。エー
ジェントが特定のタスクを実行するためのプロンプトを作成します。エージェント
の役割やタスクの概要、使用するツールの説明などを含めます。今回はlangchainに用意されているhub.pullを用いて事前定義されたエージェント設定を取得してエージェントを作成します。今回は「hwchase17/openai-tools-agent」というものを使用していますが、具体的には以下のようなプロンプトが含まれています。

(text)
You are an AI assistant that helps users with various tasks such as answering questions, performing calculations, and retrieving information. Use the provided tools to complete these tasks efficiently. Always ensure that your responses are clear and helpful.



(python)
from langchain import hub
agent_prompt = hub.pull("hwchase17/openai-tools-agent")

構築したAgentsに投げるプロンプトの作成

プロンプトの作成にはLangChainの主な機能の章で紹介したModel I/Oで提供されているPromptsを用います。これはプロンプトを簡単に管理するための機能で、適切な入力をテンプレート化したり、状況に応じて入力を動的に選択したりできます。
(※先程のAgentsで指定する必要がある変数の節で紹介したPromptはエージェントの振る舞いを定義するプロンプトであり、今回作成するプロンプトと混同しないように注意が必要です。)

プロンプトをテンプレート化したいときはPromptTemplateを用います。その中で用意されているfomat関数を用いればプロンプトの一部を変数化することができます。

(python)
from langchain.prompts import PromptTemplate
prompt_template = PromptTemplate(
        input_variables=["theme"],
        template="""
        あなたはニュース記事を書くブロガーです。
        下記のテーマについて、Google検索で最新情報を取得し、取得した情報に基
        づいてニュース記事を書いてください。1000文字以上で、日本語で出力してください。
        記事の末尾に参考にしたURLを参照元としてタイトルとURLを出力してください。
        ###
        テーマ:{theme}
        """
    )
user_input = input("記事のテーマを入力してください: ")
prompt = prompt_template.format(theme=user_input)

こうすることで、元々のテンプレートとユーザが指定したテーマを組み合わせたプロンプトを作成することができます。

結果(完成したコードと作成されたブログ記事)

LangChainを用いてGoogle検索で取得した情報を基に記事を生成し、Googleドキュメントに出力するプログラムは以下のようになります。
(※あまり時間が取れなかったもので、まだまだブログとは言えないこと、コードの汚さやエラーハンドリングが適切でないことなどいろいろとご容赦ください。。)

コード

(python)
from langchain.agents import initialize_agent, Tool
from langchain_google_community import GoogleSearchAPIWrapper
from langchain.prompts import PromptTemplate
from langchain.agents import AgentExecutor,create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain import hub
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# 認証情報の設定
SCOPES = ['https://www.googleapis.com/auth/documents','https://www.googleapis.com/auth/drive']
SERVICE_ACCOUNT_FILE = '../credentials/credentials.json'

def main():
    model = ChatOpenAI(temperature=0, model="gpt-4o", max_tokens=2000)
    tools = define_tools()
    agent_prompt = hub.pull("hwchase17/openai-tools-agent")

    agent = create_openai_tools_agent(model, tools, agent_prompt)
    agent_executor = AgentExecutor(agent=agent, tools=tools)

    user_input_theme = input("記事のテーマを入力してください: ")
    prompt = create_prompt(user_input_theme)

    response = agent_executor.invoke({"input": "{}".format(prompt)})

    docs_service = create_docs_service()
    drive_service = create_drive_service()

      # 新しいドキュメントの作成
    document = docs_service.documents().create(body={
        'title': '{}'.format(user_input_theme)
    }).execute()

    document_id = document.get('documentId')
    print(f'Created document with ID: {document_id}')

    # ドキュメントにテキストを追加
    response_text = str(response['output'])
    update_document(docs_service, document_id, response_text)

    # 閲覧権限を追加するユーザーのメールアドレス
    user_email = 'o-shioyama@sios.com'
    add_permission(drive_service,document_id, user_email)

def create_prompt(user_input_theme):
    prompt = PromptTemplate(
        input_variables=["theme"],
        template="""
        あなたはニュース記事を書くブロガーです。
        下記のテーマについて、Google検索で最新情報を取得し、取得した情報に基
        づいてニュース記事を書いてください。2000文字以上で、日本語で出力してください。
        記事の末尾に参考にしたURLを参照元としてタイトルとURLを出力してください。
        ###
        テーマ:{theme}
        """
    )
    return prompt.format(theme=user_input_theme)


def define_tools():
    search = GoogleSearchAPIWrapper()
    return [
        Tool(
            name="Search",
            func=search.run,
            description="useful for when you need to answer questions about current events. You should ask targeted questions"
        ),
    ]

def create_docs_service():
    credentials = service_account.Credentials.from_service_account_file(
                SERVICE_ACCOUNT_FILE, scopes=SCOPES)

    # Google Docs API クライアントの作成
    service = build('docs', 'v1', credentials=credentials)
    return service

def create_drive_service():
    credentials = service_account.Credentials.from_service_account_file(
                SERVICE_ACCOUNT_FILE, scopes=SCOPES)

    # Google Drive API クライアントの作成
    service = build('drive', 'v3', credentials=credentials)
    return service

def chunk_text(text, chunk_size):
    return [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]

def update_document(service, document_id, text, chunk_size=1000):
    chunks = chunk_text(text, chunk_size)
    requests = []
    index = 1

    for chunk in chunks:
        requests.append({
            'insertText': {
                'location': {
                    'index': index,
                },
                'text': chunk
            }
        })
        index += len(chunk)  # 次の挿入位置を更新

    service.documents().batchUpdate(
        documentId=document_id, body={'requests': requests}).execute()
    
    print('Text added to the document.')



# Googleドキュメントの閲覧権限を追加
def add_permission(drive_service, file_id, email):
    try:
        permission = {
            'type': 'user',
            'role': 'reader',
            'emailAddress': email
        }
        drive_service.permissions().create(
            fileId=file_id,
            body=permission,
            fields='id',
        ).execute()
        print(f'Permission granted to {email}')
    except HttpError as error:
        print(f'An error occurred: {error}')
        return None



if __name__ == "__main__":
    main()

作成されたブログ記事

(ファイル実行)

(ターミナル)
$ python app_blog.py
記事のテーマを入力してください: 大谷選手について
Created document with ID: ${document-id}
Text added to the document.
Permission granted to ${email-address}


(ブログ記事)

まとめと今後の展望

今回はLangChainを用いてGoogle検索で取得した情報を基に記事を生成するプログラムの紹介とLangChainの使い方について説明しました。LangChainについて詳しく知ることができたでしょうか。
今後としては、Google検索だけでなくフリー画像を検索できるAPIを用いて取得し適切な位置に画像を挿入したり、Googleドキュメントの章構成やコードブロックなどをGoogle docs APIのupdateParagraphStyleやupdateTextStyleを用いて自動で生成したり、過去自分が書いたブログ記事を読み取ってテンプレートやフォーマット化することで章構成や言葉遣いなどを模倣させたりなど色々と実際に書いているブログに近づけていけたらなと思っています。何年後かには自分の代わりに生成したブログ記事AIが書いてくれたらなと思います(笑)

- LangChainロゴ © Sandizer, [Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/) ライセンスに基づいて使用。このロゴは原作から変更されています。