Zenn
🔬

Gemini 2.0 FlashとRubyでDeepResearchを作ってみた!

2025/02/04に公開
9

はじめに

LLMの躍進はすさまじく、年初からGemini 2.0、DeepSeek R1、Mistral Small 3、OpenAI o3-miniなど、新しくて強力なモデルが次々と登場しています。今年も非常にエキサイティングな年になりそうです。
そんな2025年ですが、単なるLLMだけでなく「AIエージェントの年になる!」とも言われています。先日も、GoogleによるGemini 1.5をベースにしたDeepResearchと同名の、OpenAIによるo3ベースのDeepResearchが発表され、大きな注目を集めました。

現在、個人的にAriaというAIエージェントフロントエンドを作っているので、今回はDeepResearch風のアプリケーションを自作して、その感触を掴んでみたいと思います。
ちなみに、今回作成した機能のデモはこちらです。
https://youtu.be/mvK74yHigEw

AIエージェントとは?

エージェントに関してまとめた論文によると、AIエージェントは「脳」「知覚」「行動」の要素から成り、言語モデル(LLM)を活用して複数回のイテレーションを通じ、目標達成のための計画を立て、実際に行動を実行する存在とされています。
個人的に重要だと感じるのは、**「目標を達成するための計画」**と 「実際に行動を実行する」 部分です。

DeepResearchも、**「検索結果をレポートにまとめる」**という戦略に基づき、どのように検索するかを計画し、実際に検索を実行してレポートを作成します。
エージェントと言えば、ClineAnthropic Computer Use、あるいはopen-interpreterといった汎用的なものが思い浮かびますが、こうした特化型のものもエージェントに含まれるのです。

基本的なアプローチ

さて、ではどうDeepResearchを作るかです。DeepResearchは簡単に言えば、ユーザの質問を元に複数のWebサイトを検索し、最終的にレポートにまとめるエージェントです。
今回は以下のような設計にしました

  1. ユーザーの問い合わせをもとに、以下のベースとなるプロンプトを使用して検索戦略を策定する
    以下は簡略化したプロンプトです。実際のものはこちらをご参照ください。
あなたはユーザーの代理としてウェブ検索を実行し、その結果をもとにレポートを作成するエージェントです。
複数のウェブ検索結果から包括的なレポートを作成するための具体的な手順を提案してください。ユーザーのリクエストは以下です。

"<%= user_request %>"

手順:
1. 質問を複数のサブテーマに分解する。
2. 各サブテーマがどのように連携して全体の回答を形成するか、論理的な流れで説明する。
3. 各サブテーマに対して、適切な検索キーワード候補を複数提示する。

アウトプットサンプル:
{
  "検索テーマ": [
    {
      "theme": "What are AI Agents?",
      "search-words": ["AI agent", "autonomous agent", "intelligent agent"]
    },
    {
      "theme": "What is MCP in the context of AI agents?",
      "search-words": ["MCP AI agent", "Master Control Program AI", "agent framework MCP"]
    }
  ],
  "レポート作成手順": [
    "1. Search the web using the keywords in English for each theme.",
    "2. From the search results for each theme, extract key information and summarize it concisely.",
    "3. Synthesize the summaries from each theme to create a coherent report.",
    "4. The report should first introduce AI Agents, then explain MCP, followed by an explanation of Functional Calling, and finally compare and contrast AI Agents/MCP with Functional Calling, highlighting their differences.",
    "5. If relevant information is missing after the initial search, reformulate the search keywords for that theme and try again."
  ],
}
  1. 上記のプロンプトを使ってLLMを実行し、検索キーワード一覧を基にテーマごとに検索を実施する。
  2. 検索結果と検索キーワード、そして生成されたレポート作成手順をプロンプトとして最終的なレポートを作成する。

このような手順で実装しています。実際に使ったプロンプトはもう少し詳細になっていますが、概要はこの通りです。ポイントとして、ユーザーの入力によって、検索テーマやその検索に用いるキーワード、さらにはレポート作成手順が毎回変動するため、例えば 「英語のサイトで検索して日本語でレポートを作成する」 といった柔軟な指示にも対応可能です。

恐らく、各社のDeepResearch製品では、生成したレポートをダブルチェックする仕組みや、ハルシネーションを局所化する対策、レポートの質を向上させる仕組みがさらに実装されているのではないかと思いますが、私が試した限りでは、このレベルでも割と適切なレポートが生成されたため、いったん良しとしています。

Gemini 2.0 FlashとRubyで実装!

今回はLLMとしてGemini 2.0 Flash、実装言語としてRubyを選びました。

Gemini 2.0 Flashは、コストが低くレスポンスも非常に良い割には賢く、1.5 Proを凌駕する性能を持っています。また、1Mトークンという非常に大きなコンテキストを扱えるため、特別な工夫をしなくても、比較的データ量の多い検索結果をシンプルに処理できると考え、採用しました。Rubyについては、特に理由があるわけではなく、純粋に趣味で使用しています!

あえて言えば、以前はLangChainとPythonでAITuberを作成しましたが、慣れ親しんだRubyに比べると書きにくかった上、既存のモデルをAPIやollama経由で呼び出す際には、特別なフレームワークがなくても十分に対応できると感じたからです。function_calling(tools)を備えた賢いモデルであれば、様々な処理が簡単に実装可能ですしね。

というわけで、Geminiの公式サイトを参考にしてRubyで実装を進めていきます。
https://ai.google.dev/gemini-api/docs

では、まずgem install geminiでライブラリを導入しようと試みるところですが、実はRubyにはGeminiの公式ライブラリが存在しませんw
PythonやNode.js、Goなどのライブラリはありますがね… これまではここで頭を抱えたくなるところでしたが、今は令和です。我々にはAIがあるのです。
最近、個人開発でClineを試してるので、以下のようなREST APIを叩くcurlコマンドの例を元にコーディングの指示を出すと、瞬く間にSDKが生成されました。

以下のcurlコマンドを元にGeminiクラスのinvokeメソッドを作ってください。YOUR_API_KEYは環境変数GCP_API_KEYから取得します.
curl "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=YOUR_API_KEY" \
-H 'Content-Type: application/json' \
-X POST \
-d '{
  "contents": [{
    "parts":[{"text": "Write a story about a magic backpack."}]
    }]
   }'

上記のような指示を元に、ある程度対話的に進めることでClineがうまくSDKを作成してくれました。最終的にはこんな感じになっています。微調整として自分で直接コーディングした部分もありましたが、最初の実装をサクッと作成してくれるので、自力で全てコーディングするよりずっと楽でしたね。モデルがさらに賢くなれば微調整も減り、レスポンスが向上すれば開発体験もさらに良くなるので、AIの進化に期待が高まる体験でした。ちなみに、今回はGemini SDKの開発以外にも全面的にClineを採用しました。

まず、基本となるDeepResearcherは、以下のようなinvokeメソッドを持っています。

class DeepResearcher
  def invoke(user_request)
    puts "==== Creating strategy"
    strategy = make_strategy(user_request)
    puts strategy
    puts "==== Searching contents"
    contents = search(strategy["search-themes"])
    puts "==== Generate report"
    report = generate_report(contents, strategy["report-creation-prompt"])
    report
  end

そして、make_strategysearchgenerate_reportを順次実行します。これらのメソッドは非常にシンプルで、前述の基本的なアプローチに従っています。
make_strategyは、ユーザーリクエストをもとにLLMを実行し、リクエストに応じた検索テーマとそのための複数の検索キーワードを生成するとともに、テーマを使ったレポート作成手順を生成します。
searchは、実際に検索を実行し、その結果をテーマごとにまとめたJSONを返します。この際、Web検索にはSeleniumを使用しており、Windows環境を想定して決め打ちでヘッドレスモードで実行させています.

selenium_options = Selenium::WebDriver::Edge::Options.new
selenium_options.add_argument('--log-level=3') # 0: INFO, 1: WARNING, 2: LOG_ERROR, 3: LOG_FATAL

headless = options[:headless]
if headless.nil? || headless == true
  selenium_options.add_argument('--headless')
end
driver = Selenium::WebDriver.for :edge, options: selenium_options
driver.get(url)

最初はopen-uriとNokogiriで試していましたが、JavaScriptの解釈が必要なページが多かったため、ヘッドレスブラウザを使用することにしました。ちなみに、find_elements(css: 'ol.react-results--main li article')のような要素の特定をClineに任せようと試みたものの、gemini-2.0-flash-thinkingでは十分に対応できず、最終的には自分でEdgeのDeveloper Consoleを確認しながら作成しました。この点については、今後LLMの精度向上に期待したいところです。

generate_reportは、searchの結果とmake_strategyで生成されたレポート作成プロンプトを使って最終的なレポートを作成します。
面白いのは、ユーザーリクエストで「それぞれの特徴を説明して比較表を作って」や「語尾ににゃんと付けて」といった指示を出すと、make_strategyで生成されるプロンプトが変化し、固定的な結果にならないところです。

まとめ

今回は、Gemini 2.0 FlashとRubyを用いてDeepResearchを作成し、エージェントの感触を掴んでみました。検索以外の機能は実装していないため、エージェントと呼ぶにはやや弱いかもしれませんが、LLMのプロンプトを動的に生成することで、かなり柔軟な振る舞いを実現できたと感じています。もちろん、商用エージェントでは、さらに精度や安全性を向上させるための多くの仕組みが導入されているでしょうが、大筋では同じ方向性だと思います。

ちなみに、デモ動画で表示されるレポートがあっさりしているのはUIの都合によるものです。実際に作成したDeepResearchは、Markdownや表を入れたりなどまとまったレポートになりますが、キャラクターの会話ウィンドウに表示する場合は内容を大幅に削る必要があったため、この点は今後の改善ポイントです。

今後は、Clineのように開発をサポートするエージェントや、PowerShellなどを介してPC操作を実行するエージェントなどを作成・統合も試していきたいです。

それでは、Happy Hacking!

9

Discussion

ログインするとコメントできます