【失敗】Open WebUIへDeep Research機能をつくってみたら、いかに調整が難しいか実感した話

こんにちは!
九保すこひです(フリーランスのITコンサルタント、エンジニア)

さてさて、Open WebUIをつかうと、ローカルAIをつかったChatGPTみたいなページがつかえるようになりますが、あるとき面白そうな情報を聞きました。

それは・・・・・・

独自プラグインがつくれる

というものです。

そう!

つまりWordPressみたいに「こうだったらいいのにな…🤔」を自分で開発できるわけです。

だったらつくらなきゃダメでしょ!ってことで何か面白そうなテーマはないかと考えてたら思いつきました。

Deep Research機能

です。

Deep Research機能とは(いろんな定義があると思いますが)AIが

  • 検索
  • 読解
  • 推論

を繰り返し実行してより良い回答をする。

というものです。

つまり、我々がGoogle検索しながらゴールに近づいていくみたいなのをAIがやってくれるんですね。

そこで!

今回はOpen WebUIPipe Functionとして「Deep Research」機能を独自に作ってみたいと思います!

今回は次のような人に向けて記事を書いています。
ぜひ最後まで読んでくださいね!

  • 「ローカルAIをカスタマイズしてより便利に使いたい」
  • 「ローカルLLMを単なるチャットツールで終わらせたくない」
  • 「自分のローカル環境で“思考するAI”を使いたい」
  • 「ただ答えるAIより、“思考するAI”を動かしてみたい」
  • 「RAGの限界を超えたローカルAIを試したい」
  • 「Open WebUIの独自プラグインに興味がある」
  • 「ローカルLLMなのに“Webブラウザ的”な探索を再現したい」
  • 「AIと好みの検索プロセスを融合させたい」

「えっ、今回の正月って
2日間ぐらいじゃなかった!?
はやっ!」

前提として

ライトな実装です

タイトルでは「Deep Research機能」とドヤっていますが、実際のところはライト、もしくはスーパーライトといった内容です(つまり、Deep Researchらしきもの)

というのも、実行環境がGPU 4GBとミドルスペックではありますが、ローカルAIを動かすにはギリギリといったところで、あまり複雑なプロンプトが実行できない(時間がかかりすぎる)からです。

一応、検索結果をAIで考察して次の検索につなげるというDeep Researchの特徴はもっていますが、Brave Search APIで取得できる情報が少ない(逆に多くてもAIが処理できない)ので精度は高くありません。

Deep Researchを実装する戦略

今回は以下の手順でローカル環境にDeep Researchを実装しています。

  1. 質問をする
  2. AIが質問内容からジャンルを検出
  3. ジャンルに紐づく(事前にリストアップした)サイトのみで検索
  4. 検索結果をAIで考察
  5. 情報が足りなければ、次の検索ワードでもう一回検索(4に戻る)
  6. 情報が十分なら、AIが過去の全データをつかって総括する
  7. 回答が表示される

実装するために必要なもの

今回つかったのは、以下3つです。

  • Open WebUI v0.6.43:ChatGPTみたいなチャットページ
  • Ollama 0.13.5:ローカルで動く生成AI(モデルは「gemma3n:e4b」)
  • Brave Search API:APIから検索結果を取得

そして、Open WebUIにはFunctionという形で独自の改造ができるようになっています。

Brave APIのキーを取得する

今回、内部的な検索はBrave APIを使います。
そのため以下の手順でAPIキーを取得しておいてください。

※なお、Brave Search APIは毎月2,000回までは無料で使えます。

まず、Brave Search APIトップページにアクセスして、ページ右上にある「Sign up」ボタンをクリックします。

ユーザー登録画面が表示されるので、必要な項目を入力して送信します。

すると、入力したメールアドレスに認証リンクが送信されてきますので「Verify Email」ボタンをクリック。

登録したら、ログインページに移動してログインしてください。

※なお、最初のログインの場合は再度メールアドレスにログインコードが送信されてきますので、入力する必要があります。

ログインしたら、まずはプランを設定します。「Available plans」をクリック。

今回はテストなので、フリープランをクリックします。

利用規約のポップアップがでるので、同意してクリック。

プラン自体は無料ですが、クレジットカードは登録する必要があるので、必要項目を入力して登録してください。

次にページ左側メニューの「API keys」をクリック。

+ Add API Key」リンクをクリック。

適当な名前を入力して「Add」ボタンをクリック。

すると、APIキーが作成されるので、コピーして使えるようにしておきましょう(あとで設定するときにつかいます)

Pipe Functionをつくる(Python)

ではメインのブロックです。
まずはコードから。

deep_research.py

"""
title: Deep Research
author: Sukohi
author_url: https://blog.capilano-fw.com/
funding_url: https://github.com/open-webui
version: 1.0.0
"""

import requests
import json
import re
from typing import Optional
from pydantic import BaseModel, Field
from fastapi import Request
from open_webui.utils.chat import generate_chat_completion

class User(BaseModel):
id: Optional[str] = "system_user"
email: Optional[str] = "admin@example.com"
name: Optional[str] = "System"
role: Optional[str] = "admin"

class Pipe:
class Valves(BaseModel):
BRAVE_API_KEY: str = Field("", description="BraveのAPIキー")
MAX_ITERATIONS: int = Field(3, description="検索の最大ループ回数")

def __init__(self):
self.type = "pipe"
self.name = "Deep Research"
self.valves = self.Valves()
self.knowledge_items = []
self.source_urls = []
self.request = None
self.user = None
self.last_request_id = None
self.search_filter_key = ""
self.SEARCH_FILTERES = {
"Engineer": {
"description": "技術系の最新情報や専門知識に特化した検索",
"domains": [
"blog.capilano-fw.com", # <- 私のブログです(^^)
"github.com",
"stackexchange.com",
"stackoverflow.com",
"arxiv.org"
],
},
"Academic": {
"description": "学術論文や研究成果に特化した検索",
"domains": ["nature.com", "sciencedirect.com", "jstor.org", "researchgate.net", "ieeexplore.ieee.org"],
},
"Business": {
"description": "ビジネス、経済、マーケットに関する情報に特化した検索",
"domains": ["bloomberg.com", "forbes.com", "wsj.com", "ft.com", "economist.com"],
},
"Health": {
"description": "医療、健康、ウェルネスに関する情報に特化した検索",
"domains": ["mayoclinic.org", "webmd.com", "nih.gov", "who.int", "healthline.com"],
},
"Science": {
"description": "自然科学、環境、宇宙に関する情報に特化した検索",
"domains": ["nasa.gov", "sciencemag.org", "phys.org", "eurekalert.org", "nationalgeographic.com"],
},
"INTERESTING": {
"description": "興味深い話題やエンターテインメントに特化した検索",
"domains": ["omocoro.jp", "hatelabo.jp", "tabelog.com", "youtube.com"],
},
}

async def pipe(self, body: dict, __user__: dict, __request__: Request) -> str:

current_id = getattr(__request__.state, "request_id", None) or str(id(__request__))
if self.last_request_id == current_id:
print(f"DEBUG: 二重実行を検知してブロックしました (ID: {current_id})")
return ""
self.last_request_id = current_id
self.knowledge_items = []
self.source_urls = []
self.request = __request__
self.user = User(**__user__)

pinned_models = __user__.get("settings", {}).get("ui", {}).get("pinnedModels", [])
if not pinned_models:
return "Error: Pinned Modelが登録されていません。リサーチに使用するモデルをお気に入り(ピン留め)に追加してください。"
self.model = pinned_models[0]

api_key = self.valves.BRAVE_API_KEY
if not api_key:
return "Error: BraveのAPIキーが設定されていません。Valvesから設定してください。"

original_prompt = (body.get("messages") or [{}])[-1].get("content") or ""
if not original_prompt: return "エラー: 質問が空です。"

self.search_filter_key = await self._get_search_filter_key(original_prompt)
current_queries = [original_prompt]
max_iterations = int(self.valves.MAX_ITERATIONS)

for i in range(max_iterations):
search_results = await self._search_web(current_queries, api_key)

for item in search_results:
ref = f"- {item['title']}: {item['url']}"
if ref not in self.source_urls:
self.source_urls.append(ref)

search_descriptions = [f"{item['title']}: {item['description']}" for item in search_results]
analysis = await self._analyze(original_prompt, search_descriptions)

status = analysis.get("status", "UNKNOWN").upper()
knowledge = analysis.get("knowledge", "")
next_queries = analysis.get("next_queries", [])
print(f"{i+1}回目の分析: {status}")

if knowledge:
self.knowledge_items.append(str(knowledge))

if status != "CONTINUE":
print(f"調査完了: {status}")
break

current_queries = next_queries[:3]
if not current_queries:
break

last_system_prompt = (
"あなたはエビデンス重視の調査員です。提供された調査結果とソース情報を統合し、"
"客観的な根拠に基づいた回答を作成してください。回答の最後には必ず出典(References)を添えてください。"
"忖度は一切不要。不明な点は「不明」と断定してください。"
)
last_user_prompt = (
f"【調査済みの事実】\n{chr(10).join(self.knowledge_items)}\n\n"
f"【参照ソース】\n{chr(10).join(self.source_urls)}\n\n"
f"【ユーザーの質問】\n{original_prompt}"
)
return await self._ask_llm(last_system_prompt, last_user_prompt, mode="string")

async def _search_web(self, queries: list, api_key: str) -> list:
results = []
search_filter_domains = self.SEARCH_FILTERES.get(self.search_filter_key, {}).get("domains", [])
for q in queries[:3]:
try:
query = q
if search_filter_domains:
domain_filter = " OR ".join([f"site:{domain}" for domain in search_filter_domains])
query = f"{q} ({domain_filter})"
web_search_response = requests.get(
"https://api.search.brave.com/res/v1/web/search",
params={"q": query, "count": 3},
headers={"X-Subscription-Token": api_key, "Accept": "application/json"},
timeout=5
).json()
items = web_search_response.get("web", {}).get("results", [])
for item in items:
title = item.get("title", "")
url = item.get("url", "")
description = item.get("description", "")
results.append({
"title": title,
"url": url,
"description": re.sub(r'<[^>]+>', '', f"{title}: {description}")
})
except:
continue
return results

async def _analyze(self, goal, descriptions: list) -> dict:
system_prompt = (
"あなたは高度な分析能力を持つリサーチ・ディレクターです。調査の進捗を管理し、次に必要なアクションを決定します。\n\n"
"### 任務\n"
"提供された情報を分析し、調査目標が達成されたか判断してください。未達成の場合は、不足を補うための具体的な検索クエリを生成してください。\n\n"
"### 判断基準\n"
"- COMPLETE: 目標に対して客観的・確実な証拠が揃い、100%の確信がある場合のみ。\n"
"- CONTINUE: 少しでも不明点、推測、情報の鮮度不足がある場合。迷う場合はこちらを選択。\n\n"
"### 出力ルール\n"
"1. 出力は必ず以下のJSON形式1つのみとすること。\n"
"2. JSON以外の説明文や装飾(```json 等)は一切含めないこと。\n"
"3. thoughtには、現在の到達度と不足している情報の分析を日本語で記述すること。\n\n"
"{\n"
' "thought": "現在の情報の網羅性と、なぜCONTINUE/COMPLETEと判断したかの理由",\n'
' "knowledge": "判明した具体的な事実(箇条書き形式が望ましい)",\n'
' "status": "CONTINUE" または "COMPLETE",\n'
' "next_queries": ["クエリ1", "クエリ2"]\n'
"}\n"
)
user_prompt = f"目標: {goal}\n情報: {descriptions}"

answer = await self._ask_llm(system_prompt, user_prompt, mode="json")

if "status" not in answer: answer["status"] = "COMPLETE"
if "knowledge" not in answer: answer["knowledge"] = ""
if "next_queries" not in answer: answer["next_queries"] = []

return answer

async def _ask_llm(self, system_prompt: str, user_prompt: str, mode: str = "string") -> str | dict:
try:
res = await generate_chat_completion(self.request, {
"model": self.model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
"stream": False
}, user=self.user)
content = (res.get("choices") or [{}])[0].get("message", {}).get("content", "")
if(mode == "json"):
match = re.search(r'(\{.*\}|\[.*\])', content, re.DOTALL)
if match:
try:
return json.loads(match.group(1).replace("'", '"'))
except json.JSONDecodeError:
return {}
return {}
else:
return content
except Exception as e:
print(f"LLMへの問い合わせ中にエラーが発生しました: {e}")
if mode == "json":
return {}
return ""

async def _get_search_filter_key(self, prompt: str) -> str:
search_filter_keys = self.SEARCH_FILTERES.keys()
system_prompt = (
"以下のカテゴリから、与えられた質問に最も関連するものを1つだけ選びんでください。\n\n"
"【リスト】\n"
+ "\n".join([f"- {key}: {self.SEARCH_FILTERES[key]['description']}" for key in search_filter_keys])
+ "\n\n"
"出力ルール"
"- 次のいずれか 1つの単語だけ を、余計な文字を一切付けず に出力してください。\n"
"- 有効な出力は、ちょうど次の 6 通りのいずれかです。\n"
+ "\n".join([f" - {key}" for key in search_filter_keys])
)
user_prompt = f"質問: {prompt}"
answer = await self._ask_llm(system_prompt, user_prompt, mode="string")
answer = answer.strip()
if answer in search_filter_keys:
return answer
return "All"

では、Open WebUIを起動したら以下の手順で登録していきましょう。

ページ左下にあるログインユーザーをクリックした中にある「Admin Panel」をクリック。

さらにページ上部の「Functions」タグをクリック。

ページ右上から登録ページへ移動。

名前と説明文を入力し、先ほどのコードへ入れ替えします。

右下にある「Save」ボタンをクリック。

これで、Functionとして登録されたので、有効にしておいてください。

ついでにAPIキーも登録しておきましょう。
Deep Researchの歯車マークをクリックしてください。

Valvesのポップアップが表示されるので、中身を登録します。

この状態でチャットページへ移動したら、以下のように「Deep Research」という(擬似的な)モデルがつかえるようになっています。

テストしてみる

では、実際にDeep Researchに質問してみましょう!

質問1:最後に1つ残った唐揚げの食べ方

居酒屋で最後の一つ残った唐揚げを、卑しいと思われずに食べるための「自然なムーブ」を教えて

まずはDeep Researchの回答です(時間かかりました)

## 居酒屋で最後の一つ残った唐揚げを、卑しいと思われずに食べるための「自然なムーブ」に関する調査結果

提供された情報に基づき、居酒屋で最後の一つ残った唐揚げを、卑しいと思われずに食べるための「自然なムーブ」について、以下の情報をまとめます。

**1. 心理学的アプローチの活用:**

* **同情を誘う:** 状況を説明し、少し困っている様子を見せることで、周囲の同情を誘い、唐揚げを譲ってもらう可能性を高める。
* **相手に自由を与える:** 「〇〇さんも、もしよかったらどうぞ」と提案し、相手に選択肢を与えることで、相手がより気軽に唐揚げを譲るように促す。
* **メニュー表の視線誘導:** 視線を唐揚げに誘導し、その唐揚げに注目を集めることで、周囲の人が唐揚げを譲るきっかけを作る。
* **ラスト1個という状況の強調:** 「最後の一つだけ残ってるんだね」と状況を軽く指摘することで、唐揚げを譲る理由を明確にする。

**2. 唐揚げに関する動画からの示唆:**

* **人気動画、高品質な唐揚げの紹介:** 人気動画や高品質な唐揚げの紹介動画は、唐揚げに対するポジティブなイメージを醸成し、唐揚げを求める自然な欲求を正当化する。
* **元居酒屋店長による作り方動画:** 元居酒屋店長による唐揚げの作り方動画は、唐揚げに対する専門的な知識と愛情を示唆し、唐揚げを食べる行為に対する肯定的な印象を与える。
* **居酒屋で唐揚げをゲットするための心理学的アプローチに関する動画:** 居酒屋で唐揚げをゲットするための心理学的アプローチに関する動画は、状況に応じた適切な行動を促し、卑しいと思われずに唐揚げを食べるための戦略を提供する。
* **唐揚げをラスト1個でゲットするための心理学的戦略:** 唐揚げをラスト1個でゲットするための心理学的戦略は、状況を理解し、適切な行動をとることで、周囲の人が唐揚げを譲る可能性を高める。

**3. その他:**

* **「世界一旨い唐揚げ」動画:** この動画は、唐揚げに対するポジティブな感情を喚起し、唐揚げを求める自然な欲求を正当化する。
* **「唐揚げおデブ」動画:** この動画は、唐揚げに対する親近感とユーモアを醸成し、唐揚げを食べる行為に対する抵抗感を軽減する。
* **鶏の軟骨を使った唐揚げレシピ動画:** この動画は、唐揚げに対する興味と好奇心を刺激し、唐揚げを食べる行為に対する肯定的な印象を与える。

**結論:**

居酒屋で最後の一つ残った唐揚げを卑しいと思われずに食べるためには、心理学的アプローチを駆使し、状況を理解した上で、自然な行動をとることが重要です。具体的には、同情を誘う、相手に自由を与える、メニュー表の視線誘導、ラスト1個という状況の強調などのテクニックを活用し、唐揚げに対するポジティブなイメージを醸成することが有効です。

**References:**

* https://www.youtube.com/watch?v=xGKn7TD9jaM
* https://omocoro.jp/kiji/204818/
* https://omocoro.jp/kiji/93581/
* https://www.youtube.com/watch?v=Hg27MS47cfI
* https://www.youtube.com/watch?v=-Ouoararf6U
* https://www.youtube.com/watch?v=89Mr4ZBk6R0
* https://www.youtube.com/watch?v=mpkNybp4Eus

うん、まぁ、悪くはないといった印象ですね。
では、gemma3n:e4bで通常の質問をしてみましょう。

どうなったでしょうか・・・・・・

居酒屋で最後の一つ残った唐揚げを、卑しいと思われずに食べるための「自然なムーヴ」ですね!これはなかなか心理的な駆け引きが必要ですが、いくつか提案させていただきます。

**1. 謙虚な姿勢と感謝の気持ちを伝える**

* **「すみません、せっかくですが、一つだけ頂いてもよろしいでしょうか?」** と、丁寧に尋ねるのが基本です。
* **「他の方に譲る気持ちもありますが、どうしても食べたくなって…」** と、正直に伝えつつも、相手への配慮を示すと良いでしょう。
* もし、周りに「まだ食べたい人いませんか?」と声をかける人がいれば、**「私はもう一つだけ頂いても大丈夫です」** と、率先して申し出るのも効果的です。

**2. 状況を面白おかしく表現する**

* **「これは、私が最後の幸運を掴む瞬間です!」** と、少しユーモアを交えて言うのも、場を和ませるテクニックです。
* **「この唐揚げ、きっと私に特別なパワーをくれるはずです!」** と、冗談っぽく言うのも、相手の緊張をほぐすのに役立ちます。
* **「もしかして、この唐揚げは私のために残されていたのかもしれませんね…」** と、少し大げさに言うのも、面白いかもしれません。

**3. 周囲の状況を利用する**

* もし、周りの人が「もうお腹いっぱい」と言っている人がいれば、**「他に食べたい人いないみたいですね」** と、さりげなく付け加えることで、罪悪感を軽減できます。
* 店員さんが「最後の一品ですね」と声をかけてくれたら、**「ありがとうございます。ぜひ頂きたいです」** と、感謝の気持ちを伝えるのが良いでしょう。

**4. 自虐ネタを織り交ぜる**

* **「実は、唐揚げが大好きすぎて、我慢できなかったんです…」** と、自虐的に言うのも、相手に許しを得やすいかもしれません。
* **「ダイエット中なんですけど、誘惑に負けてしまいました…」** と、正直に告白するのも、共感を呼ぶ可能性があります。

**重要なポイント**

* **笑顔を忘れずに!** 笑顔は、どんな状況でも相手の心を和ませる魔法です。
* **相手の反応をよく観察する。** 相手が嫌な顔をしていたり、不快に感じているようであれば、無理に食べるのはやめましょう。
* **感謝の気持ちを伝える。** どんな状況でも、相手への感謝の気持ちを忘れずに伝えましょう。

これらのテクニックを参考に、状況に合わせて自然なムーヴを繰り出してみてください。ただし、一番大切なのは、相手への配慮を忘れず、感謝の気持ちを伝えることだと思います。

**免責事項:**

これらの提案は、あくまでもユーモアを交えたものです。必ずしもすべての状況で効果があるとは限りません。相手の文化や価値観を尊重し、状況に応じて適切な対応を心がけてください。

うーん、正直こっちの方がいいのでは…😱

では、もう1つ真面目な質問もしてみましょう。

質問2:gemini vs ChatGPT

今後、Google geminiとChatGPTの競争はどちらが勝者になる?

現状では、Googlegeminigmailやカレンダーとの統合や、TPUを使った(nvidia依存から脱却した)AIトレーニングができるということで優位だと言われています。

まずはDeep Researchの方です。
どうなったでしょうか・・・・・・

Google GeminiとChatGPTの競争の勝者について、現時点(2024年5月15日)で明確な結論を出すことは困難です。しかし、提供された情報に基づくと、両者の将来性、強み、弱みを考慮すると、以下の点が考えられます。

**Geminiの優位性:**

* **マルチモーダル対応:** テキスト、画像、音声、動画、コードなど、多様なデータ形式を同時に処理できる点は、Geminiの大きな強みです。これは、日常的なワークフローにおける活用範囲を広げる可能性を秘めています。
* **最新情報へのアクセス:** Google検索との連携により、最新の情報に基づいた回答や情報収集が可能です。ChatGPTが内部データに依存するのに対し、Geminiはリアルタイムな情報を取り込めるため、情報の信頼性や精度が高いと言えます。
* **高度な機能:** Gemini 2.5 Proは、100万トークン規模の長文処理能力、Deep Think、Deep Researchなどの機能を有しており、複雑なタスクの実行に適しています。
* **エコシステムとの統合:** Google検索、NotebookLMなど、複数のサービスとの統合が進んでおり、日常的なワークフローへの組み込みが容易です。
* **性能の向上:** Gemini 3 Proは、数学、コード、マルチモーダルにおいてGPT-5.2を上回る結果が多く、性能が向上しています。
* **市場シェアの拡大:** OpenAIはGeminiの台頭を受けて、エコシステム構築に注力しており、Geminiの市場シェアは拡大傾向にあります。

**ChatGPTの優位性:**

* **文章作成能力:** 無機質な事実の羅列よりも、読ませる記事を書くのに適しているという評価があります。
* **会話アーカイブ:** 会話のアーカイブが可能であり、後で表示、復元、削除できるという利点があります。
* **幅広い用途:** 文章作成、壁打ち、コード生成など、幅広い用途に対応できます。

**競争の行方:**

現時点では、GeminiがChatGPTに匹敵する、あるいは一部の面で上回る可能性を秘めています。特に、マルチモーダル対応や最新情報へのアクセスといった点で、今後のビジネス利用において大きなアドバンテージとなるでしょう。

ただし、ChatGPTは文章作成能力や会話アーカイブといった点で、依然として独自の強みを持っています。また、OpenAIはGeminiの台頭を受けて、エコシステム構築に注力しており、競争は激化する可能性があります。

**結論:**

現時点では、GeminiがChatGPTの競争において優位に立つ可能性が高いと考えられます。しかし、両者の競争は活発であり、今後の技術革新や市場動向によって、勝者が変わる可能性もあります。

**References:**

(参照元は省略しています)

はい!モデル自体はgemma3n:e4bなので古い情報も入っていますが、マルチモーダルがあるので有利というのは正しい見方ではないでしょうか。

では、通常のgemma3n:e4bの結果も見てみましょう。

Google GeminiとChatGPTの競争は、非常に興味深いテーマですね。どちらが「勝者」になるかは、現時点では断言できませんが、それぞれの強みと弱み、そして今後の動向を考慮すると、いくつかの可能性が考えられます。

**現時点での状況**

* **Gemini:** Googleが開発した最新のマルチモーダルAIモデル。テキスト、画像、音声、動画など、多様な情報を理解し、生成する能力に優れています。特に、Googleの持つ検索エンジンや他のサービスとの連携が強みです。
* **ChatGPT:** OpenAIが開発した大規模言語モデル。自然な文章生成能力が高く、幅広い用途で利用されています。特に、APIの提供やサードパーティの開発エコシステムが充実している点が強みです。

**今後の展望と勝敗の可能性**

どちらが勝つかは、以下の要素によって大きく左右されると考えられます。

1. **技術的な進歩:**
* **Gemini:** Googleは、Geminiの性能向上に積極的に投資しており、今後も更なる進化が期待されます。特に、より複雑なタスクや専門分野への対応が重要になるでしょう。
* **ChatGPT:** OpenAIも、GPT-4oなど、高性能なモデルを継続的にリリースしています。また、新しいアーキテクチャや学習方法の開発にも取り組んでおり、技術的な競争は激化するでしょう。
2. **ビジネス戦略:**
* **Gemini:** Googleは、Geminiを様々なサービスに統合し、より多くのユーザーに利用してもらう戦略をとるでしょう。Google WorkspaceやAndroidなど、既存のサービスとの連携が鍵となります。
* **ChatGPT:** OpenAIは、ChatGPT Plusの有料プランやAPIの提供を通じて、収益を拡大する戦略をとっています。また、企業向けのソリューション開発にも注力するでしょう。
3. **市場のニーズ:**
* **Gemini:** Googleの検索エンジンとの連携により、情報検索や知識獲得の分野で大きなアドバンテージを持つ可能性があります。
* **ChatGPT:** 幅広い用途に対応できる汎用性の高さが、様々な業界やユーザーに受け入れられる要因となります。

**勝敗のシナリオ**

* **Googleが勝つシナリオ:** GoogleがGeminiを様々なサービスに統合し、ユーザーに使いやすい形で提供することで、市場シェアを拡大する。特に、検索エンジンとの連携が成功すれば、圧倒的な優位性を確立する。
* **ChatGPTが勝つシナリオ:** OpenAIがAPIの提供やサードパーティの開発エコシステムをさらに発展させ、様々な業界でChatGPTの活用が広がる。特に、企業向けのソリューション開発が成功すれば、ビジネス市場で大きなシェアを獲得する。
* **両者が共存するシナリオ:** どちらか一方に決定的な差が生まれることなく、それぞれの強みを活かして共存する。Geminiは情報検索や知識獲得の分野で、ChatGPTは文章生成や創造的なタスクの分野でそれぞれ強みを発揮する。

**個人的な見解**

現時点では、どちらが勝つかは予想が難しいですが、**Googleが若干有利**だと考えています。Googleは、AI技術の開発に巨額の投資をしており、検索エンジンや他のサービスとの連携という点で、ChatGPTよりも優位性があると考えられます。

ただし、OpenAIも技術的な進歩やビジネス戦略によって、十分に巻き返す可能性があります。今後の動向から目が離せません。

**補足:**

* AI技術は、急速に進化しており、今後数年で大きく変化する可能性があります。
* 「勝者」の定義も、技術的な性能だけでなく、市場シェア、収益性、社会への貢献など、様々な要素によって判断される可能性があります。

この情報が、あなたの疑問を解決する一助となれば幸いです。

現状は「Googleが99%有利」という有識者がいるぐらいの状況なので、情報がちょっと古いかもしれませんね。リアルタイムで検索できないので、これはしかたないです。

そして、内容としても悪いものではなかったですね。

では、最後にリアルタイム性を重視した質問をしてみます。

質問3:ガソリン価格について

ガソリン価格ってどうなっていく?

まずはDeep Researchです。

ガソリン価格の動向は、複数の要因が複雑に絡み合っているため、一概に予測することは困難です。しかし、提供された情報に基づくと、以下の点が重要となります。

**現状と背景:**

* **原油価格の低下:** 原油市場には過剰供給があり、原油価格は4年ぶりの低水準に低下しています。これは、エネルギー転換によるガソリン需要の減少も背景にあります。
* **ベネズエラ情勢の影響:** ベネズエラへの石油制裁や、ベネズエラの政治情勢が原油供給に影響を与える可能性が指摘されています。また、ナイジェリアでの攻撃も原油市場に影響を与えています。
* **政府の物価対策:** 日本政府は物価高対策としてエネルギー価格の低下を目指しています。
* **過去の傾向:** トランプ政権時代には、石油会社への政府の働きかけがあり、ガソリン価格が目標価格($2以下)を下回る状況も発生しました。

**今後の予測:**

* **原油価格の安定または上昇の可能性:** 10月以降、原油価格は大幅に上昇しており、ベネズエラの情勢やナイジェリアでの攻撃が影響している可能性があります。ただし、原油市場はベネズエラの情勢を吸収できる見通しであり、過剰供給は変わらないとされています。
* **ガソリン価格の動向:** 原油価格の動向に大きく左右されます。原油価格が安定または上昇すれば、ガソリン価格も上昇する可能性があります。一方で、ガソリン需要の減少や政府の物価対策によって、ガソリン価格の上昇は抑制される可能性があります。
* **エネルギー転換の影響:** エネルギー転換の進展は、ガソリン需要の減少を加速させ、ガソリン価格に長期的な影響を与える可能性があります。

**結論:**

ガソリン価格は、原油価格、ベネズエラの情勢、ナイジェリアでの攻撃、エネルギー転換の進展、政府の政策など、複数の要因によって変動する可能性があります。現時点では、原油価格の安定または上昇と、ガソリン価格の抑制という相反する要因が混在しているため、今後のガソリン価格の動向を予測することは困難です。

**References**

(参照元は省略しています)

ベネズエラやナイジェリアの直近のニュースを踏まえて回答していますね。

では、gemma3n:e4bの回答です。

ガソリン価格の今後の動向は、様々な要因が複雑に絡み合っているため、予測は非常に難しい状況です。しかし、現時点での主な要因と、それに基づいたいくつかのシナリオを以下にまとめます。

**現在の主な要因**

* **原油価格:** ガソリン価格の最も大きな影響を与えるのは、国際的な原油価格です。原油価格は、OPEC(石油輸出国機構)の政策、地政学的なリスク、世界経済の動向などによって変動します。
* **為替レート:** 日本は原油を輸入しているため、円安になると原油の輸入コストが増加し、ガソリン価格も上昇する傾向があります。
* **関税・税金:** ガソリンに課される関税や税金も価格に影響します。
* **需給バランス:** 国内のガソリン需要と供給のバランスも価格に影響します。例えば、レギュラーガソリンの需要が供給を上回ると、価格は上昇する可能性があります。
* **円安傾向:** 最近、円安が進行しており、今後も円安が続けば、ガソリン価格の上昇圧力となる可能性があります。

**いくつかのシナリオ**

1. **原油価格が安定、または若干下落する場合:**
* ガソリン価格も比較的安定するか、緩やかに下落する可能性があります。
* ただし、円安の影響が残るため、大幅な下落は期待しにくいかもしれません。
2. **原油価格が上昇する場合:**
* ガソリン価格も上昇する可能性が高くなります。
* 原油価格の上昇幅によっては、過去の高値を超えることも考えられます。
3. **地政学的なリスクが高まる場合:**
* 原油供給が滞る可能性があり、ガソリン価格が急騰するリスクがあります。
* このような状況は、世界経済にも大きな影響を与える可能性があります。

**今後の見通し**

複数のエネルギー関連機関や専門家は、原油価格が引き続き不安定な状況が続くと予測しています。そのため、ガソリン価格も上下動が激しくなる可能性があります。

**参考情報**

* **読売新聞:** [https://www.yomiuri.co.jp/economy/20240515-OK003588/](https://www.yomiuri.co.jp/economy/20240515-OK003588/)
* **日本経済新聞:** [https://www.nikkei.com/article/DGXZQOGM1526000--C24AQA400000/](https://www.nikkei.com/article/DGXZQOGM1526000--C24AQA400000/)

**注意点**

* 上記はあくまで現時点での予測であり、今後の状況によって大きく変動する可能性があります。
* ガソリン価格は、地域やガソリンスタンドによっても異なります。

最新の情報は、上記のようなニュースサイトや、石油関連機関のウェブサイトなどで確認するようにしてください。

想定したとおり、最新情報はないので当たり障りのない回答になっています(これも仕方ないところですね)

結論:OllamaでDeep Researchをつかうべき?

高性能なGPUを実行できる環境でないと、おすすめできないというのが結論です。

というのも、結局Brave Search APIから取得できる情報が一部分しかなく、そのままAIに投げてもきちんとした回答にならないからです。

じゃあ、JINA Reader APIとかでページ全体のテキストをとってくれば…と思いやってみましたが、それだと今度はGPU4GB)ではオーバースペックすぎて、いつまでたっても回答がでてくることはありませんでした。

そのため、超ハイスペックなパソコンなら使う選択肢もあるかもですが

  • AIはローカルじゃなきゃダメ!
  • 特定のサイトだけターゲットにして回答してほしい!

といった制限がない限り、ChatGPTなどのクラウド型を使うのが正解ですね。そもそも検索してる時点でローカル完結ではなくなってますし。

なので、今後マシンスペックが急激に向上するか、超軽量なのに頭がいいローカルAIがでてくるかがあれば状況は変わるかもしれません。

ちなみに:開発について

実際のPythonコードはOpen WebUIで動くので、いちいちウェブページのエラーをチェックしたりすると効率がとんでもなく悪いです。

※そもそもドキュメントだけでなくソースコードも見ましたが、あまりカッチリつくっている印象ではなかったです。Anyとか普通にありますし、その影響で時間を溶かしました(笑)

そのため、私は以下のようなテスト環境を別の場所につくって開発を進めました。

こちらも参考になれば嬉しいです😊

test_run.py

import sys
from unittest.mock import MagicMock, patch
import asyncio
import requests

# インポートを偽装
mock_chat_mod = MagicMock()
sys.modules["open_webui"] = MagicMock()
sys.modules["open_webui.utils"] = MagicMock()
sys.modules["open_webui.utils.chat"] = mock_chat_mod

from deep_research import Pipe

# generate_chat_completionのモック実装
async def mocked_generate_chat_completion(request, body, user = None) -> dict:
url = "http://localhost:11434/api/chat"

ollama_body = {
"model": body.get("model", "gemma3n:e4b"),
"messages": body.get("messages"),
"stream": False,
}

if "json" in str(body).lower():
ollama_body["format"] = "json"

try:
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(
None, lambda: requests.post(url, json=ollama_body, timeout=60)
)
response.raise_for_status()
res_json = response.json()

return {
"choices": [
{
"message": {
"content": res_json.get("message", {}).get("content", "")
}
}
]
}
except Exception as e:
print(f"DEBUG: Ollama Connection Error: {e}")
return {"choices": [{"message": {"content": "Error connecting to Ollama" }}]}
mock_chat_mod.generate_chat_completion.side_effect = mocked_generate_chat_completion

async def test():
pipe = Pipe()
pipe.valves.BRAVE_API_KEY = "test_api_key"
pipe.valves.MAX_ITERATIONS = 3

# 本体が self.request を参照するためセット
mock_request = MagicMock()
mock_request.state = MagicMock()
mock_request.state.request_id = "test-unique-id-12345"

# モックでBrave Search APIのレスポンスをシミュレート
with patch("requests.get") as mock_get:

# 1回目
res1 = MagicMock()
res1.status_code = 200
res1.json.return_value = {
"web": {"results": [{"title": "概要2026", "url": "https://a.com", "description": "量子ビットが安定化。"}]}
}
# 2回目
res2 = MagicMock()
res2.status_code = 200
res2.json.return_value = {
"web": {"results": [{"title": "冷却技術の革新", "url": "https://b.com", "description": "新冷却方式でコスト減。"}]}
}
# 3回目
res3 = MagicMock()
res3.status_code = 200
res3.json.return_value = {
"web": {"results": [{"title": "量子アルゴリズムの進展", "url": "https://c.com", "description": "新しいアルゴリズムが提案された。"}]}
}

mock_get.side_effect = [res1, res2, res3]
body = {
"model": "gemma3n:e4b",
"messages": [{"role": "user", "content": "最新の量子コンピュータの動向が知りたい"}]
}
user_data = {"id": "12345", "email": "test@example.com", "name": "Test User", "role": "tester", "settings": {"ui": {"pinnedModels": ["gemma3n:e4b"]}}}

print("--- 実行開始 ---")
result = await pipe.pipe(body, __user__=user_data, __request__=mock_request)
print("\n--- 最終回答 ---")
print(result)

if __name__ == "__main__":
asyncio.run(test())

あと、以下のコマンドをつかうとリアルタイムでログを出してくれるので、めちゃくちゃ便利です。

docker logs -f open-webui-open-webui-1(←名前は、環境によってちがいます!)

企業様へのご提案

今回のようなローカルAIも含めまして、PoC的な内容もお引き受けしておりますので、もしなにか業務効率化などのご相談がありましたら、気軽にご相談ください。

お待ちしております😊

おわりに

ということで、今回はOpen WebUIに独自プラグインとしてPipe Functionをつくってみました。

結果としては「ローカルLLM」の性能が向上しない限り、スピードが出せず実用的ではないとの結論になりましたが、Open WebUIのプラグイン自体は自由度が高いので今後も何か思いついたら作ってみたいと思います。

ちなみに、記事の途中でも少し触れましたが、これまでOpen WebUIは仕様がかわっているので、AIに質問してみても過去バージョンの書き方ばかりで開発に時間がかかりました。

※ドキュメントから正しいコードをもってきて上げると、比較的ちゃんとコーディングしてくれるようになります。

そう考えると、今まで生成AIが提案してきた内容をいくらやってもうまくいかない場合は、バージョン違いによる「間違ってない間違い」みたいなのも多いかもしれませんね。

今後はそこも開発者のスキルに必要ですね。
まだまだ、やることはいっぱいあります。

ではでは〜!

「知り合いの経営者たちは、EVトゥクトゥクに
興味津々でした!(乗せました)」

このエントリーをはてなブックマークに追加       follow us in feedly  
お問い合わせ、お待ちしております。
開発のご依頼はこちら: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ