OSS版 DeepResearchを使って、ドメイン検索エージェントを作る

こんにちは！

ノベルワークス所属のザワッチです！

先日、Google GeminiのLangGraphサンプルをXで見かけて、「これ、ドメイン調査に特化させたら面白そう」って思い立って改造したら、思いのほかおもしろいものができてしまったので共有します。

# はじめに：なぜドメイン調査ツールを作ろうと思ったか
新しいWebサービスを作るとき、まず最初にやることってドメイン取得ですよね。

でも、いちいちお名前.comとかムームードメインとか開いて、ドメイン検索して、WHOIS調べて、SSL証明書の状態確認して...って、めんどくさくないですか？

そこで「全部AIでぜんぶやっちゃえばいいやん」と思いつきました。

# 元になったプロジェクト
ベースにしたレポジトリは、汎用的なDeepResearchのような動きをする調査エージェントになります。

Web検索をしてレポートに近い出力をしてくれるReactフロントエンドとLangGraphバックエンドエージェントを使ったフルスタックアプリケーションサンプルになります。

https://github.com/google-gemini/gemini-fullstack-langgraph-quickstart

:::note
実をいうと、このレポジトリの構造がめちゃくちゃ綺麗で改造しやすいです。
:::

以下、実装機能をまとめます。

# 主な機能
ドメインさえ抽出できているという前提で、AIかまさずに実現している機能と、AI使って実現している機能があります。

あと、検索履歴をduckdb-wasmを使って実装しています。（触ってみたかっただけ）

1. ドメイン可用性チェック
    - check_domain_availability() - ソケット解決でリアルタイム確認
2. WHOIS情報の詳細取得
    - Google Search API経由で実際のWHOIS情報を検索
    - 登録日、有効期限、所有者履歴まで取得
3. SSL証明書の完全分析
    - check_ssl_certificate() - 証明書の有効性確認
    - SSL Labs評価結果も自動取得
4. セキュリティ評判調査
    - マルウェア、フィッシング、ブラックリスト全チェック
    - 過去のインシデント履歴も調査
5. トラフィック・SEO分析
    - ドメインオーソリティ、被リンク状況
    - 推定トラフィック量も取得
6. ローカル検索履歴（DuckDB WASM）
    - SQLでの履歴検索
    - ページリロードしても消えない永続化(OPFS使用)
7. 次にとるべき行動がわかりやすいようなフォローアップ質問の生成
    - AIの応答に基づく次のアクション提案
    - ワンクリックでさらなる深堀り調査可能

## 技術的なポイント

### LangGraphでの状態管理
バックエンドでは backend/src/agent/graph.py で LangGraph を使って、「検索→分析→反省→再検索→統合」という複雑なプロセスをグラフ定義で管理しています。

下が公式が出してる概要図です：

![agent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/6b0db708-113a-4e7a-9bdd-92bad0577bf6.png)


生成にはGemini モデル活用して、
・検索クエリの自動生成
・検索結果に対しての反省（知識の抜け・偏りの検出）
・最終回答文の組み立てと引用挿入

を行います。

最初の検索結果が不十分であれば、自動で「反省」「更なる検索」「再度反省」を最大ループ数まで繰り返します。

今回は、ドメイン検索に特化するということで、ドメイン分析専用のノードを追加しました：



```python
def domain_analysis(state: OverallState, config: RunnableConfig) -> OverallState:
      # 技術的チェック（DNS、SSL、HTTP）
      domain_info = analyze_domain_info(domain)

      # 実際のWHOIS、評判、トラフィックデータを取得
      response = genai_client.models.generate_content(
          model=configurable.query_generator_model,
          contents=formatted_prompt,
          config={"tools": [{"google_search": {}}], "temperature": 0}
      )
```

### 具体的な検索クエリの生成
ドメインの詳細調査をするにあたって、以下の項目をWebで検索させています。

実際のデータを取得するように指示をかなり強化していて、以下はその例です：

1. **WHOIS情報の詳細検索**:
     - "{domain} WHOIS" で検索し、登録者情報、登録日、有効期限を確認
     - "{domain} domain registration date" で登録履歴を調査

2. **セキュリティ・評判の詳細調査**:
     - "{domain} malware check" でマルウェア検出状況を確認
     - "{domain} blacklist status" でブラックリスト登録状況を確認


書ききれないほど、いろいろやっていますが、処理フローを図で起こしました。

```mermaid
graph TD
      %% スタイル定義
      classDef userAction fill:#e1f5fe,stroke:#01579b,stroke-width:2px
      classDef langraphNode fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
      classDef toolCall fill:#fff3e0,stroke:#e65100,stroke-width:2px
      classDef apiCall fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px
      classDef frontend fill:#fce4ec,stroke:#880e4f,stroke-width:2px

      %% ユーザーアクション
      A[ユーザー: zawatti.orgを分析して]:::userAction

      %% LangGraphノード処理
      A --> B[extract_domains ノード]:::langraphNode
      B --> B1[extract_domain_from_query 正規表現でドメイン抽出]:::toolCall
      B1 --> B2[LLMでドメイン抽出 DomainAnalysisRequest]:::toolCall
      B2 --> B3[domains_to_analyze: zawatti.org is_domain_query: true]

      %% ルーティング
      B3 --> C{check_is_domain_query}
      C -->|true| D[domain_analysis ノード]:::langraphNode

      %% ドメイン分析処理
      D --> D1[analyze_domain_info]:::toolCall
      D1 --> D1a[check_domain_availability DNS解決チェック]:::toolCall
      D1 --> D1b[get_dns_records DNSレコード取得]:::toolCall
      D1 --> D1c[check_http_status HTTP/HTTPS応答確認]:::toolCall
      D1 --> D1d[check_ssl_certificate SSL証明書確認]:::toolCall

      D --> D2[analyze_security_via_search]:::toolCall
      D2 --> D2a[現在はプレースホルダーis_blacklisted: false reputation_score: 75.0]

      D --> D3[analyze_domain_value_via_search]:::toolCall
      D3 --> D3a[現在はプレースホルダー traffic/SEO情報: None]

      %% Google Search API呼び出し
      D --> E[Google Search API 呼び出し]:::apiCall
      E --> E1[domain_analysis_instructions + 日本語指示]
      E1 --> E2[genai_client.models.generate_content\ntools:google_search]:::apiCall
      E2 --> E3[WHOIS情報検索 SSL Labs評価検索 トラフィック統計検索]

      %% スコア計算と結果生成
      D --> F[calculate_overall_score]:::toolCall
      F --> F1[可用性]

      F --> G[DomainAnalysisResult 生成]:::toolCall
      G --> G1[overall_score]
      %% 最終レポート生成
      G1 --> H[finalize_domain_answer ノード]:::langraphNode
      H --> H1[日本語レポート生成]

      %% フロントエンドへの返却
      H1 --> I[StateGraph 終了]:::langraphNode
      I --> J[WebSocket SSEで配信]:::apiCall
      J --> K[フロントエンド受信]:::frontend
      K --> K1[ChatMessagesView でレンダリング]:::frontend
      K1 --> K2[Markdownパース ドメイン分析表示]:::frontend

      K2 --> L[FollowUpSuggestions 表示]:::frontend
```

### Duckdb-wasmとOPFS
duckdbを導入したのはいいものの、永続化するにはIndexedDB使わないといけないなど、あんまりスマートじゃないなと思って色々調べていると、Origin Private File System（OPFS）という存在に気づき、使用することにしました。

https://developer.mozilla.org/ja/docs/Web/API/File_System_API/Origin_private_file_system

ブラウザ内部の仮想ファイルシステム領域を各オリジンに割り当てられており、実際の OS 上のファイルツリーとは異なり、ブラウザが内部的に管理するデータベースやストレージ領域 上に保存されます。

ということで、ユーザがそのファイルを閲覧できない点がかなりセキュアであり、ほんでもって、バイナリの読み書きという低レベルアクセスがduckdb-wasmととても相性がいいとのこと。

先駆者たちが記事を残されていたので、とても参考になりました！

https://zenn.dev/akabana/articles/24ba54f63ae9b1

https://zenn.dev/voluntas/scraps/08e6065facbefd

完璧じゃないけど、動けばよし！の精神ではありますが。

# 実装してみて使ってみた
## ホーム画面
ほぼほぼもとのUIと一緒です。
海外のSaaSになんかありそうな感じにチューニングしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/8c79e4ad-2db3-4e10-94a3-dddcde9a28bb.png)

## 履歴表示画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478853/f4128bc4-50e4-4b15-9c84-0442b7ea0160.png)

## 分析画面
動画です。

https://x.com/zawattizawawa/status/1934265833376932141

# 使ってみてわかったこと
1. langgraphでエージェンティックな深堀り機能は作れる 
ちゃんとプロンプト書けば、WHOIS情報もSSL評価も検索して取ってきてくれる

2. どこまで深ぼって調べるのかその線引きが難しい
エージェント特有の問題ですが、コストの兼ね合いもあり、どこまで自律的に検索させていくかをちゃんと考える必要がある。

3. 検索エージェント系はこのご時世、もうユーザ向けじゃなくてMCPサーバとして実装するべきか？
簡単に作れちゃうというのがわかった以上、MCPサーバとしてAIによって呼び出されるのに特化してしまったほうがいい。こうなったら、フロントエンドを実装する必要がなくなってしまう。

ちょうど書いてるときにでてきたやつ↓：

https://t.co/N86WSvBJuo

# おわりに
Google GeminiとLangGraphを組み合わせることで、個人的には使えるドメイン調査ツールを作ることができました。

正直、最初は「ドメインが取れるかどうかわかればいいや」くらいに思ってたんですが、作り込んでいくうちにWHOIS情報からSSL評価、トラフィック推定まで取れるようになって、自分でも驚いてます。

次は、ドメインの自動取得まで実装してみたい。。