Toilを無くして徒然なるままに日暮し硯に向かひたい

生成AIアプリケーション開発などを行うエンジニアのブログです。

学術的根拠から読み解くNotebookLMの音声活用法

この記事はQiita 3-shake Advent Calendar 2025 シリーズ1日目の記事です。

2025年11月22日(土)に、Google Developer Group - DevFest Tokyo 2025があり、その招待制懇親会でLTをさせていただく機会がありました。

「学術的根拠から読み解くNotebookLMの音声活用法」というタイトルで、NotebookLMの音声解説で学習する際のポイントを過去のマルチメディア学習の学術的根拠や実験を基にまとめました。

speakerdeck.com

1ページずつ解説をさせていただきたいと思います。

1枚目

1枚目は表紙です

2枚目

2枚目は自己紹介です。

3枚目

Notebookでの音声を作る操作方法です。

画面にドキュメントなどをアップロードし、音声解説ボタンを押すだけで簡単に作れます。

音声は、男女掛け合いのPodcast形式です。

4枚目

仕事や学業で、難解なドキュメントを読む場面は多々あると思いますが、NotebookLMの音声解説機能により、学習効率が高められるか期待が高まっています。

AIによって作られた音声がどれだけ学習効果があるか過去のマルチメディア学習の学術的根拠実験を基に解説していきます。

5枚目

学習効果を測定する実験も行いました。

とある専門的なドキュメントを音声化して実験に用いました。被験者は熟達者(エキスパート)と初学者のグループに分かれます。

熟達者、初学者でそれぞれ、音声の元となったドキュメントのみ読んで学習したグループ、音声のみ聴いて学習したグループ、両方を用いたグループに分かれ、学習後に4択の理解度チェックテストを受けてもらいました。

GoogleスライドをPDF化して文字が崩れているので、直せるなら直しておきます。

6枚目

ドキュメント・音声の両方を用いたグループが優位に思えましたが、結果はご覧の通り。

初学者は、実験を1回のみ行い、両方 > 音声のみ > ドキュメントのみ、という期待通りの結果でしたが、

熟達者は、実験を3回行い、両方グループが最高点を取るとは限りませんでした。

なぜ、熟達者は両方グループが優位とは限らなかったのでしょうか?

7枚目

初学者の説明です。初学者は音声学習を順書立てて勉強するのが有効です。

構造的ガイダンスを提供することを足場かけ理論といいます。

また、初学者の両方グループは音声を主、ドキュメントを従(文章を読むより、俯瞰的に見る)ことにより、認知負荷分散につながりました。

8枚目

一方の熟達者の説明です。

熟達者は、初学者に有効な順序立てた構造的ガイダンスが邪魔になることがあります。

熟達者の知識ネットワークに対して、手厚い構造的ガイダンスが知識をマッピングするのが非常に認知負荷が高いためです。

これを熟達化のリバーサル効果といいます。

また、熟達者の両方群は音声とドキュメント両方から情報を得ようと頑張り、認知負荷が高い状態でした。

9枚目

実験の制約により、不利な面もありました。

実験の時間の都合上、音声の一時停止、巻き戻しを禁止していました。音声を一時停止、巻き戻して、自分のペースで聴けるなら、熟達者の両方グループは、音声とドキュメント両方からしっかり情報を取れていた可能性があります。

学習者のペースを守らせることが効率を上げるのですが、例えば、音声や動画の学習をする際、数分ごとに区切って、学習者が「次へ」を押すことで次のパートが始まると学習がしやすいです。

このことを「セグメンテーション原理」と呼ぶのですが、実験の制約上、阻害されたことになります。

また、熟達者の実験の中で、音声のみグループの平均点が低いときがありました。

それは、グラフ・図を見ていないと難しい問題が多く、音声でグラフ・図など視覚的な情報伝達が難しいことを意味します。

また、各グループの点数のばらつきでは、ドキュメントのみグループが最も大きかったです。これは当然と言えば当然で、ドキュメント学習は各個人の学習能力に大きく左右されるためです。

一方、音声は画一的な指導が可能とも言えます。

10枚目

実験や学術的根拠から読み解く、音声学習のおすすめとしては、学習者の習熟度を考慮し、

初学者は音声とドキュメント両方を併用し、音声を主、ドキュメントを従とするのが良いでしょう。

一方、熟達者は各個人で使い分けをするのがよく、概要把握や復習、思い出すなどの目的では音声、詳細や図表の把握はドキュメントを使うのがよく、安易に両方同時に使うと認知負荷を増大させるリスクがあります。

11枚目

参考文献です。一部、有料のものもありますが、Web検索等で概要を知ることもできます。

12枚目

終わりましたが、他の勉強会での登壇情報です。

2025年11月27日(木)に、Jagu'e'r 月末 Tech Lunchの勉強会「月末 Tech Lunch Online#7 - Google Cloud を語る!-」に「MCP・A2A概要 〜Google Cloudで構築するなら〜」というタイトルで登壇した話は、ブログ記事にまとめていますので、よろしければご覧ください。

shu-kob.hateblo.jp

最後に

qiita.com

3-shake Advent Calendar 2025 シリーズ2の1日目はmasasuzuさんが書いてくれています。

シリーズ1の2日目はyteraokaさんの「VPC Lattice を理解したい」

シリーズ2の2日目はnwiizoさんの「生成AIエージェントによるブログレビュー環境の構築(上)」です。

今後もお楽しみに!

MCP・A2A概要 〜Google Cloudで構築するなら〜

この記事はQiita Jagu'e'r Advent Calendar 2025の1日目の記事です。

2025年11月27日(木)に、Jagu'e'r 月末 Tech Lunchの勉強会「月末 Tech Lunch Online#7 - Google Cloud を語る!-」に「MCP・A2A概要 〜Google Cloudで構築するなら〜」というタイトルで登壇させていただきましたので、その発表内容でのポイントを記事化したいと思います。

AIエージェントが流行っているけど、MCPやA2Aという概念は難しいやろうと思い、噛み砕いて説明したいというのが発表のモチベーションでした。

speakerdeck.com

なお、今回の資料は、NotebookLMで作成しました。ここまで作れるのはすごいです!

1ページずつ解説をさせていただきたいと思います。

1枚目

1枚目は表紙です。

2枚目

2枚目は、アジェンダで、全体の話の流れを書いています。

3枚目

3枚目は、LLMの制約について述べています。LLMは「Brain in a Jar」(瓶の中の脳)とも言われ、賢いけど、手足を持たなくて実行能力のないものの例えです。

例えば、学習時点までの知識しか知りません。これをナレッジカットオフといいます。

「今日の株価」「明日の天気」「最新のニュース」などは分かりません。

また、旅行のプランをLLMに尋ねても、航空券やホテルの予約はしてくれません。

APIなどを操作し、データベースのトランザクション操作をする実行能力はないのです。

4枚目

ここで、LLMの制約を解決する手段として、MCPの話が出てくるのに加え、さらなる機能拡張のためにA2Aの話が出てきます。

MCPはLLMに実行能力を与えます。

A2Aはエージェント同士が連携し、より複雑なことができるようになる仕組みです。

5枚目

MCP(Model Context Protocol)は外部ツールやデータへのアクセスを標準化するプロトコルです。

LLMという脳に手足を与えて、検索やAPI操作ができるようになり、APIを介してデータベースのトランザクション操作ができるようになるのです。

ここでポイントは、推論機能と実行機能を分離して疎結合に実装するということです。

6枚目

A2A(Agent-to-Agent)は、AIエージェント同士で、連携するためのプロトコルです。

能力を記述したAgent CardがAIエージェントの名刺となり、どのエージェントにどのタスクを任せるかの判断ができます。

また、通信プロトコルが定められているため、拡張性に優れています。

7枚目

MCPとA2Aのご紹介をしましたが、Google CloudでMCPやA2Aをどう構築していくかのポイントに移りましょう。

まず、認知(推論)機能と実行機能を分離すること

クラウドを利用する上で、サーバーレスファーストが大事であること(8枚目で詳説)

誰も信頼せずとも動くゼロトラストセキュリティであることです。

8枚目

Google CloudでのMCPサーバー構築は、Cloud Runを使うのが定石です。

サーバーレスでありコスト最適化できます。

また、高いスケーラビリティに対応していて、

コンテナベースで、デプロイが容易です。

9枚目

MCPサーバーをCloud Runで構築する際の注意点です。

ローカル開発で使うようなstdio(Standard I/O)はCloud Runでは使用できないため、Streamable HTTPかSSE over HTTPを使う必要があります。

最近では、新しいStreamable HTTPの方が推奨となっています。

10枚目

一方、A2A対応のエージェントの構築は、Vertex AI Agent Engineが最適です、

フルマネージドサービスで、A2Aのプロトコルに準拠しており、Agent Registoryによるガバナンスも効いています。

11枚目

A2Aエージェントを構築するためのポイントです。

スライドには文言が書いていませんが、ADK(Agent Development Kit)を用いた方法です。

AgentCardの定義、使用するLLMやツールの定義、タスク処理のロジックを実装し、これらをA2Agentで統合し、A2A準拠のエージェントを作成できます。

12枚目

MCPとA2Aを連携させた構築例です。

「Social Agent」というのは友人の好みを推論するエージェントです。

外部連携、つまり実行部分はMCPを用いて、推論と実行の分離を行います。

13枚目

簡単にAIエージェントが開発できるようになると、企業内でみんな好き勝手にエージェントを作り始めて、野良エージェントが増えそうですが、Gemini Enterpriseによる一元管理でガバナンスを効かせられます。

14枚目

MCPもA2Aもオープンプロトコルであるため、拡張性に優れています。

インターネットでもTCP/IPというオープンプロトコルのおかげで相互運用性があるように、AIエージェントもどんどん拡張していき、どんどん便利な世の中になるのかもしれません。

最後に

お読みいただきありがとうございました。

2日目のQiita Jagu'e'r Advent Calendar 2025は、pHaya72さん「テクサミの宣伝」です。

qiita.com

空きもまだありますので、Jagu'e'r 会員の方はぜひ書きましょう!

私もできれば、複数記事書きます!

Gemini CLI AI駆動開発体験ハンズオン

この記事は#17 Gemini CLI AI駆動開発体験ハンズオン【オンライン】 - connpassの資料です。

Gemini CLI AI駆動開発体験ハンズオン

🎯 本日のゴール

このハンズオンでは、Googleの強力なAIモデルであるGeminiをターミナルから対話的に利用できるGemini CLIを使い、以下の3つの体験を通じて、日々の開発タスクを劇的に効率化する「AI駆動開発」の第一歩を踏み出すことを目指します。

  1. 面倒なドキュメント作成の自動化
  2. 未知のアプリケーションの迅速な立ち上げ
  3. 対話によるスマートな機能追加

🧠 Gemini CLIとは?

Gemini CLIは、Googleが公開したオープンソースのAIエージェントです。ターミナル(コマンドライン)から自然言語で指示を出すだけで、まるで優秀なアシスタントがいるかのように、以下のようなタスクをこなします。

  • コードの生成・編集・解説
  • ファイル操作
  • 情報検索
  • ワークフローの自動化

それでは、早速AIとのペアプログラミングの世界を体験してみましょう!

1. 準備

a. Node.js (npm) のインストール

Gemini CLIのインストールに必要です。未インストールの方はVer.20以上をインストールしてください。

b. Gemini CLIのインストールと設定

ターミナルを開き、以下のコマンドを実行します。

# Gemini CLIをインストール
npm install -g @google/gemini-cli
# インストールされたことを確認
gemini --version

以下のようにバージョン情報が表示されればOKです。

0.3.2

c. 認証設定

Gemini-CLIのREADMEを参照

github.com

ターミナルで

gemini

と入力すると、対話モードとなります。

/quit

で退出できます。

2. ハンズオン1: ローカルコードを解析してREADME.mdを自動生成

まずは、既存のコードからプロジェクトの説明書であるREADME.mdを自動生成させてみましょう。

手順

1. 作業用ディレクトリの作成と移動

mkdir gemini-cli-handson && cd gemini-cli-handson

2. サンプルコードの作成

簡単なWebサーバーのPythonコードを作成します。main.pyというファイル名で以下の内容を保存してください。

touch main.py
import http.server
import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()
main.pyを動かしてください

などと入力することで起動させることができます。

3. ハンズオン1: GeminiにREADMEの作成を依頼!

カレントディレクトリの情報をコンテキスト (-c ) として渡し、READMEの作成を依頼し、> を使ってファイルに保存します。

💻 実行するコマンド:

gemini -p "このプロジェクトのREADME.mdを日本語で生成してください。プロジェクトの概要、使い方、実行方法を簡潔にまとめてください。" -c  > README.md

ls コマンドで README.md ファイルが作成されていることを確認してください。たったこれだけで、プロジェクトのドキュメントが完成しました!

4. ハンズオン2: 未知のアプリを動かしてみる

次に、GitHubから使い方があまり書かれていないプロジェクトをCloneしてきて、Geminiに起動方法を尋ねて動かしてみましょう。

手順 サンプルリポジトリのクローン まずは一つ上の階層に戻り、サンプルリポジトリをクローンします。

git clone https://github.com/shu-kob/rag-app-handson

READMEがあるとGeminiがその内容をヒントにしてしまうため、READMEがなくてもどれだけ自力でアプリの構造を理解できるか試すために README.mdを削除します。

cd rag-app-handson
rm frontend/README.md backend/README.md

Geminiに起動方法を質問してみます。 このディレクトリにはREADME.mdがありません。どうやって動かせばいいか、Geminiに聞いてみましょう。

💻 実行するコマンド:

gemini -p "このプロジェクトの実行方法を教えて。必要な手順をステップバイステップで説明して。" -c 

Geminiは ファイルを見て、以下のような実行手順を説明してくれます。

5. ハンズオン3: プロンプトを工夫して機能追加

最後に、対話を通じてアプリケーションに新しい機能を追加してみましょう。ハンズオン1で作成したPythonのWebサーバーコードを拡張します。

手順 作業ディレクトリへ移動

cd gemini-cli-handson

現在のコードを確認

cat main.py

で現在のコードを再確認します。これはシンプルなWebサーバー機能しかありません。

Geminiに機能追加を依頼! このWebサーバーに、「アクセスすると'Hello, Gemini!'と表示する」機能を追加してもらいましょう。コード全体を書き換えてもらうように依頼するのがポイントです。

💻 実行するコマンド:

gemini -p "現在のmain.pyを修正して、どのパスにアクセスしても 'Hello, Gemini!' というテキストを返すように変更してください。コード全体を提示してください。" -c main.py

生成されたコードでファイルを上書き Geminiが修正版のmain.pyコードを生成します。上書きの指示をしてください。

(生成されるコードの例)

import http.server
import socketserver

PORT = 8000

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write('Hello, Gemini!'.encode('utf-8'))

with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

動作確認 変更したWebサーバーを起動し、ブラウザやcurlコマンドで動作を確認します。

💻 実行するコマンド (ターミナル):

python3 main.py

もしくは、Gemini-CLIで「main.pyを起動してください」と指示します。

💻 別のターミナルを開いて実行、またはブラウザで http://localhost:8000 にアクセス:

curl http://localhost:8000

ターミナルに "Hello, Gemini!" と表示されれば、機能追加は成功です!

RAGアプリ開発ハンズオン(後編:フロントエンド編)

genai-users.connpass.com

上記ハンズオン勉強会の資料になります。

前回資料

shu-kob.hateblo.jp

前回の課題

  • retriever_service を定義しましたが、検索結果をcontextとして、LLMへの問い合わせを行なってください。

  • llm_serviceretriever_serviceを使うようにします。

@app.post('/api/llm')
def llm_service(question: Question):
    human_question = question.query
    model = VertexAI(model_name="gemini-2.0-flash-001", location="us-west1")
    template = """質問: {question}

    ステップバイステップで考えてください。"""

    prompt_template = PromptTemplate.from_template(template)

    chain = prompt_template | model # prompt_templateをmodelに引き渡す処理を"|"を用いて簡単に実現

    response = chain.invoke({"question": human_question}) # invokeは全ての処理が終わってから値を返す。他にはstreamなど
    print(response)
    resp = { 'answer': response }
    return resp

@app.post('/api/llm')
def llm_service(question: Question):
    human_question = question.query
    model = VertexAI(model_name="gemini-2.0-flash-001", location="us-west1")
    context_resp = retriever_service(question)
    context = context_resp['search_result']
    print(context)
    template = """質問: {question}

    以下の情報を参考にして、質問に答えてください。
    {context}
    """

    prompt_template = PromptTemplate.from_template(template)

    chain = prompt_template | model # prompt_templateをmodelに引き渡す処理を"|"を用いて簡単に実現

    response = chain.invoke({"question": human_question, "context": context}) # invokeは全ての処理が終わってから値を返す。他にはstreamなど
    print(response)
    resp = { 'answer': response }
    return resp

以下も行っておくと便利です。

  • .envを作成
DISCOVERY_ENGINE_ID=XXXXXXXXXXXXX
  • 以下の行を main.pyに追記
from dotenv import load_dotenv

load_dotenv()
  • engine_idの行を変更
@app.post('/api/retriever')
def retriever_service(question: Question):
    search_query = question.query
    project_id
    location: str = "global"
    engine_id: str = 'DISCOVERY_ENGINE_ID'

@app.post('/api/retriever')
def retriever_service(question: Question):
    search_query = question.query
    project_id
    location: str = "global"
    engine_id: str = os.environ['DISCOVERY_ENGINE_ID']
  • 動作確認
QUESTION='{"query":"情報セキュリティにおいて気をつけるべきことを教えてください"}'
curl -X POST -H "Content-Type: application/json" -d "$QUESTION" -s http://localhost:8000/api/llm | jq .

参考)ソースコード差分

retriever_serviceで得た検索結果をcontextに by shu-kob · Pull Request #4 · shu-kob/rag-app-handson · GitHub

フロントエンドの実装

フォルダ整理

これまでバックエンドを追加してきたのと同じリポジトリでフロントエンドも管理いたします。

そのためにこれまで追加してきたファイルをバックエンド用のフォルダに移動させます。

mkdir backend

# 下記以外にも必要なファイル、フォルダはbackendに移動してください。
# - __pycache__とfastapi-envは削除してください。
# - .gitがある場合は移動も削除もしないでください。
mv *.md *.py *.txt .env backend

アプリ作成

アプリの雛形を作成し、起動を確認します。

npx --yes create-react-router@latest --install --no-git-init frontend
cd frontend
npm run dev

ブラウザでhttp://localhost:5173/を開いてReact Routerの画面が表示されればOKです。

画面を変更してみる

見た目を定義しているコンポーネントはfrontend/app/welcome/welcome.tsxです。

Welcomeコンポーネントを以下のように変更します。

export function Welcome() {
  return (
    <main className="flex items-center justify-center pt-16 pb-4">
      <div className="flex-1 flex flex-col items-center gap-16 min-h-0">
        <div>
          <div>
            <label htmlFor="message">メッセージ</label>
          </div>
          <div>
            <textarea
              id="message"
              rows={4}
              cols={50}
              style={{
                padding: "0.5rem",
                border: "1px solid #ccc",
                outline: "none",
                boxShadow: "none",
              }}
            />
          </div>
          <div>
            <button
              type="button"
              style={{
                border: "1px solid #ccc",
                padding: "0.5rem 1rem",
              }}
            >
              送信
            </button>
          </div>
        </div>
      </div>
    </main>
  );
}

画面に入力欄とボタンが表示されればOKです。

入力をコントロールする

上記で入力欄に文字を入力することはできますが、その値はブラウザ側で管理されており、Reactアプリ側では取得できません。

そこでstateを用いてアプリ側で入力を制御します。

import { useState } from "react";

export function Welcome() {
  const [input, setInput] = useState("");

  const onSend = () => {
    console.log(input)
  }

  return (
    <main className="flex items-center justify-center pt-16 pb-4">
      <div className="flex-1 flex flex-col items-center gap-16 min-h-0">
        <div>
          <div>
            <label htmlFor="message">メッセージ</label>
          </div>
          <div>
            <textarea
              id="message"
              rows={4}
              cols={50}
              style={{
                padding: "0.5rem",
                border: "1px solid #ccc",
                outline: "none",
                boxShadow: "none",
              }}
              value={input}
              onChange={(e) => setInput(e.target.value)}
            />
          </div>
          <div>
            <button
              type="button"
              style={{
                border: "1px solid #ccc",
                padding: "0.5rem 1rem",
              }}
              onClick={onSend}
            >
              送信
            </button>
          </div>
        </div>
      </div>
    </main>
  );
}

テキストを入力して送信ボタンをクリックするとログにテキストの内容が表示されるようになります。

ログの確認はブラウザの開発者ツールで行います。

バックエンドとの接続

フロントエンドはバックエンドと異なるオリジンで動かしているため、CORSエラーにならないようバックエンドを修正します。

backend/main.pyに以下を追加してください。

# CORSミドルウェアの設定
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # すべてのオリジンを許可
    allow_credentials=True,
    allow_methods=["*"],  # すべてのメソッドを許可
    allow_headers=["*"],  # すべてのヘッダーを許可
    expose_headers=["*"]  # すべてのヘッダーを公開
)

変更後、バックエンドを起動します。

python -m venv fastapi-env
source fastapi-env/bin/activate

Windowsコマンドプロンプトの場合

fastapi-env/Scripts/activate
uvicorn main:app --reload

送信ボタンが押された際に入力されたテキストをバックエンドに送信し、生成AIの回答を取得できるようにします。

レスポンスの確認はブラウザの開発者ツールで行います。

  const onSend = () => {
    fetch("http://localhost:8000/api/llm", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query: input }),
    })
  }

演習

バックエンドのResponseを画面に表示させましょう

バックエンドからのresponseをフロントエンドに表示 by shu-kob · Pull Request #6 · shu-kob/rag-app-handson · GitHub

RAGアプリ開発ハンズオン(前編:バックエンド編)

genai-users.connpass.com

上記ハンズオン勉強会の資料になります。

ソースコード

github.com

FastAPIの準備

python -m venv fastapi-env
source fastapi-env/bin/activate
fastapi-env/Scripts/activate
pip install fastapi uvicorn
touch main.py
from fastapi import FastAPI

app = FastAPI()

@app.get('/')
def index():
  return 'hello'
  • 実行
uvicorn main:app --reload
  • 別ターミナルにて
curl -s http://localhost:8000/
  • POSTも追加
from pydantic import BaseModel
class User(BaseModel):
    name: str

@app.post('/api/hello')
def hello_service(user: User):
    resp = { 'message': 'Hello, {}!'.format(user.name) }
    return resp
USER='{"name":"平賀源内"}'
curl -X POST -H "Content-Type: application/json" -d "$USER" -s http://localhost:8000/api/hello | jq .

Google Cloudでサービスアカウントの準備

Geminiマルチモーダルプログラミングハンズオン - Toilを無くして徒然なるままに日暮し硯に向かひたいの記事を参考に、ロールへ

を追加し、環境変数の設定

Geminiを呼び出すコードを記載

  • main.pyの上に以下を追加
import vertexai
from vertexai.generative_models import GenerativeModel
  • main.pyの下に以下を追加
class Question(BaseModel):
    query: str

@app.post('/api/llm')
def llm_service(question: Question):
    prompt = question.query

    vertexai.init(location="us-west1") # vertexaiの初期化で、ロケーションを設定

    model = GenerativeModel("gemini-2.0-flash-001") # モデルを設定

    response = model.generate_content( # プロンプトをモデルに入れて出力(レスポンスを得る)
        prompt
    )

    print(response.text) # コンソールログにresponseのテキストを表示
    resp = { 'answer': response.text } # responseを形作る
    return resp

ライブラリのインストール

  • requirements.txtに以下を記載
google-cloud-aiplatform==1.83.0
vertexai==1.43.0
langchain_core==0.3.33
langchain_google_vertexai==2.0.12
google===3.0.0
google-cloud-discoveryengine==0.13.6
pip install -r requirements.txt

--break-system-packagesをつけよ、とエラーが出たら以下

pip install --user -r requirements.txt --break-system-packages

実行方法

uvicorn main:app --reload
  • 別ターミナルにて
QUESTION='{"query":"プロンプトエンジニアリングとは何ですか?"}'
curl -X POST -H "Content-Type: application/json" -d "$QUESTION" -s http://localhost:8000/api/llm | jq .

LangChainを用いる

import vertexai # 削除
from vertexai.generative_models import GenerativeModel # 削除

from langchain_google_vertexai import VertexAI # 追記
from langchain_core.prompts import PromptTemplate # 追記
@app.post('/api/llm')
def llm_service(question: Question):
    human_question = question.query
    model = VertexAI(model_name="gemini-2.0-flash-001", location="us-west1")
    template = """質問: {question}

    ステップバイステップで考えてください。"""

    prompt_template = PromptTemplate.from_template(template)

    chain = prompt_template | model # prompt_templateをmodelに引き渡す処理を"|"を用いて簡単に実現

    response = chain.invoke({"question": human_question}) # invokeは全ての処理が終わってから値を返す。他にはstreamなど
    print(response)
    resp = { 'answer': response }
    return resp

RAG構築

Google Cloud Vertex AI Agent Builderの使い方 - Toilを無くして徒然なるままに日暮し硯に向かひたいの記事を参考に、Google Cloud Storageにドキュメントを格納し、Agent Builderで検索アプリを作ります。

  • main.pyの上に追記
from google.api_core.client_options import ClientOptions
from google.cloud import discoveryengine_v1 as discoveryengine
import os
import google.auth

credentials, project_id = google.auth.default()
  • main.pyの下に追記

  • 'DISCOVERY_ENGINE_ID'を書き換えます

@app.post('/api/retriever')
def retriever_service(question: Question):
    search_query = question.query
    project_id
    location: str = "global"
    engine_id: str = 'DISCOVERY_ENGINE_ID' # AI Applicationsで作成したアプリケーションのIDに変更する
    def search(
        project_id: str,
        location: str,
        engine_id: str,
        search_query: str,
    ) -> discoveryengine.services.search_service.pagers.SearchPager:
        client_options = (
            ClientOptions(api_endpoint=f"{location}-discoveryengine.googleapis.com")
            if location != "global"
            else None
        )

        client = discoveryengine.SearchServiceClient(client_options=client_options)

        serving_config = f"projects/{project_id}/locations/{location}/collections/default_collection/engines/{engine_id}/servingConfigs/default_config"

        content_search_spec = discoveryengine.SearchRequest.ContentSearchSpec(
            snippet_spec=discoveryengine.SearchRequest.ContentSearchSpec.SnippetSpec(
                return_snippet=True
            ),
            summary_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec(
                summary_result_count=3,
                include_citations=True,
                ignore_adversarial_query=True,
                ignore_non_summary_seeking_query=True,
                model_prompt_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec.ModelPromptSpec(
                    preamble="文献の検索結果を要約してください"
                ),
                model_spec=discoveryengine.SearchRequest.ContentSearchSpec.SummarySpec.ModelSpec(
                    version="stable",
                ),
            ),
        )
        request = discoveryengine.SearchRequest(
            serving_config=serving_config,
            query=search_query,
            page_size=3,
            content_search_spec=content_search_spec,
            query_expansion_spec=discoveryengine.SearchRequest.QueryExpansionSpec(
                condition=discoveryengine.SearchRequest.QueryExpansionSpec.Condition.AUTO,
            ),
            spell_correction_spec=discoveryengine.SearchRequest.SpellCorrectionSpec(
                mode=discoveryengine.SearchRequest.SpellCorrectionSpec.Mode.AUTO
            ),
        )
        page_result = client.search(request)

        return page_result

    response = search(project_id, location, engine_id, search_query)
    resp = { 'search_result': response.summary.summary_text }
    print(resp)
    return resp
QUESTION='{"query":"情報セキュリティにおいて気をつけるべきことを教えてください"}'
curl -X POST -H "Content-Type: application/json" -d "$QUESTION" -s http://localhost:8000/api/retriever | jq .

課題

retriever_service を定義しましたが、検索結果をcontextとして、LLMへの問い合わせを行なってください。

次回、5月の回(日程未定)で解説します。

プロンプトエンジニアリング プログラミング ハンズオン

genai-users.connpass.com

この記事は上記勉強会の資料です。

shu-kob.hateblo.jp

↑上記記事を参考にサービスアカウントの設定をしてください。

Google Cloudの無料期間が終了していると、課金されますが、ハンズオンの内容だけだと数百円もいかないと考えています。

料金は確実には言えないので、Google Cloudはご自身の責任でご使用ください。

github.com

↑今回のサンプルコード

git clone https://github.com/shu-kob/prompt_engineering
cd prompt_engineering
pip install vertexai

LangChainを使わずVertex AIのライブラリを使用

シンプルなVertex AIでGeminiを実行

project_id = "PROJECT_ID" # 書き換える

実行

python3 generate_content.py
response = model.generate_content(
  "プロンプトエンジニアリングとは"
)

プロンプトを変更して実行してみましょう。

Zero Shot プロンプティング

project_id = "PROJECT_ID" # 書き換える

実行

python3 zero_shot_prompting.py
prompt = """
以下はニュース記事のタイトルです。「政治」「経済」「芸能」「スポーツ」「科学」「その他」のうち1つに分類してください。
回答だけ一言で出力してください
===========================
紅白出場歌手の選考基準 NHK公開
"""

プロンプトを変更して実行してみましょう。

Few Shot プロンプティング

project_id = "PROJECT_ID" # 書き換える

実行

python3 few_shot_prompting.py
prompt = """
以下はニュース記事のタイトルです。「政治」「経済」「芸能」「スポーツ」「科学」「その他」のうち1つに分類してください。
回答だけ一言で出力してください
===========================
「紅白出場歌手の選考基準 NHK公開」
===========================
以下は例です
「G20 バイデン氏不在で集合写真」:政治
「岡田将生&高畑充希結婚 SNS反応」:芸能
"""

プロンプトを変更して実行してみましょう。

LangChainを使用

langchain_google_vertexai を使用

pip install langchain_google_vertexai
python3 invoke.py
messages = [
  ("human", "ネコの鳴き真似をしてください。"),
]

プロンプトを変更して実行してみましょう。

PromptTemplateを使用

pip install langchain_core
pip install pydantic==2.9.0

実行

python3 prompt_template.py
template = """質問: {question}

ステップバイステップで考えてください。"""

question = """
10 + 2 * 3 - 4 * 2
"""

プロンプトテンプレートやQuestionを変更して実行してみましょう。

ChatPromptTemplateを使用

実行

python3 chat_prompt_template.py
prompt_template = ChatPromptTemplate.from_messages([
    ("system", "ステップバイステップで考えてください。"),
    ("human", "{question}"),
])

question = """
10 + 2 * 3 - 4 * 2
"""

システムプロンプトやQuestionを変更して実行してみましょう。

参考資料

python.langchain.com

python.langchain.com

参考文献

LangChainとLangGraphによるRAG・AIエージェント[実践]入門

Google Gemini 1.5/LlamaIndex/LangChain 人工知能プログラミング実践入門

LLMのモデル更新や廃止による影響を考える

この記事は、MLOps(LLMOps、生成AIOps) Advent Calendar 2024 4日目の記事です。

生成AIの普及により、アプリケーションに組み込んで実運用を始めた方も増えてきたと思います。

LLMOpsをする中で気をつけたいことを考えてみました。

モデルの更新

まず、思い浮かぶのがモデルの更新よる影響です。

モデルの更新によって性能が上がるなどのメリットを享受できる反面、

挙動変更によって、困ることもあります。

私の場合、システムの実運用では無いですが、LLM技術書のサンプルコードが動かなくなる事態がありました。

06_agent/agent_5.py で2回目の実行結果が正しく表示されません · Issue #3 · harukaxq/langchain-book · GitHub

gpt-3.5-turboをAgentとして使用したときの挙動が変わったという内容です。

アプリに組み込んでいたら、機能が使えなくなる可能性があり、 使えなくなった場合の代替案も用意しておく必要があると考えました。

また、LLMのリリース情報もウォッチしておく必要があるでしょう。

Geminiはリリースの最新情報を日本語で提供しています。

gemini.google.com

ChatGPTはリリースノートを英語のみですが提供しています。

ChatGPT — Release Notes | OpenAI Help Center

Anthropic製品(Claude)のリリースノートは日本語で提供されています。

docs.anthropic.com

モデルの廃止

モデルの廃止もウォッチする必要があるでしょう。

GPT-3.5 Turbo終了はニュースになりました。

xtech.nikkei.com

↑日経クロステックの有料会員記事ですが、会員でなくても1ページ目で内容は把握できます。

learn.microsoft.com

Azure OpenAIでは、GPTの各種マイナーバージョンが提供されていますが、適宜廃止になるので注意が必要です。

廃止になる場合、モデルのVersion UPが必要なので、早めに開発環境でVersion UPしたモデルの挙動確認をする必要があるでしょう。

Version UPしたモデルだと、LLMの利用料が高くなることも念頭に置いて、コスト試算しましょう。

まとめ

モデル更新や廃止を早く知るために、LLM公式サイトのリリースノートなどのウォッチをして、早めに対策をしましょう。