6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

〜無料枠で動くAI調査レポート自動生成システムの開発ログ〜

この記事はさくらのAI Engine 使い倒したで賞への応募記事です。


1. はじめに:作ったもの

「テーマを入力するだけで、数分後にMarkdownレポートが完成する」 Webアプリを作りました。

demo.gif

できること:

  • Tavilyで最新情報を検索
  • AIが各サイトを要約
  • 統合レポートをMarkdownで生成
  • 自作MCPサーバー経由でファイル保存
  • 7つのモデルを切り替えて比較実行

使用技術: Gradio / LangGraph / MCP / Tavily / さくらのAI Engine


2. なぜこのアプリを作ったか

技術調査に毎回2時間かかっていました。PerplexityやChatGPTは便利ですが、自分のフォーマットにカスタムできないし、複数モデルの比較が面倒でした。

そこで「さくらのAI Engine」に注目。月3,000リクエストの無料枠があれば、コストを気にせずエージェントを動かせると思いました。

ちょうどnomurasanさんの入門記事で「使えるようになった」ので、次のステップとして**「使い倒す」=実用的なアプリを作る**ことを目指しました。


3. システム構成図


4. 核心実装①:自作MCPサーバー「report-saver」

MCP(Model Context Protocol)は「AIが外部ツールを使うためのUSBポート」のようなものです。

既存のfilesystemサーバーでは「保存」だけで不十分だったので、独自の保存ロジックをMCPサーバーとして切り出しました。

from mcp.server import MCPServer
import os

mcp = MCPServer("report-saver")

@mcp.tool()
def save_report(filename: str, content: str) -> str:
    os.makedirs("reports", exist_ok=True)
    filepath = f"reports/{filename}"
    with open(filepath, "w", encoding="utf-8") as f:
        f.write(content)
    return f"レポートを保存しました: {filepath}"

@mcp.tool()
def list_reports() -> str:
    files = os.listdir("reports")
    return "保存済みレポート:\n" + "\n".join(f"- {f}" for f in sorted(files))

if __name__ == "__main__":
    mcp.run()

ポイント: mcp 2.0.0ではFastMCPが使えず、MCPServerクラスに変更されていました。公式GitHubのREADMEを確認して書き換えました。


5. 核心実装②:自作MCPサーバー「search-history」

同じテーマを2回目以降検索する場合、Tavilyの消費をゼロにするため、SQLiteで検索履歴を管理します。

import sqlite3
from mcp.server import MCPServer

mcp = MCPServer("search-history")

@mcp.tool()
def save_search_history(theme: str, results_json: str) -> str:
    conn = sqlite3.connect("search_history.db")
    conn.execute("INSERT INTO search_history (theme, results_json) VALUES (?, ?)",
                 (theme, results_json))
    conn.commit()
    conn.close()
    return "検索履歴を保存しました"

@mcp.tool()
def get_search_history(theme: str) -> str:
    conn = sqlite3.connect("search_history.db")
    row = conn.execute(
        "SELECT results_json FROM search_history WHERE theme = ? ORDER BY created_at DESC LIMIT 1",
        (theme,)
    ).fetchone()
    conn.close()
    return row[0] if row else ""

実際に動作確認したところ、2回目の実行では「履歴再利用」が働き、Tavily APIを呼ばずに済みました。


6. 核心実装③:LangGraphのStateGraph設計

単一関数ではなく、4つのノードに責務を分離してLangGraphで管理しています。

class ReportState(TypedDict):
    theme: str
    model: str
    search_results: list
    summaries: list
    report: str
    filename: str
    ai_calls: int
    status: str

workflow = StateGraph(ReportState)
workflow.add_node("search", search_node)
workflow.add_node("summarize", summarize_node)
workflow.add_node("integrate", integrate_node)
workflow.add_node("save", save_node)

workflow.set_entry_point("search")
workflow.add_edge("search", "summarize")
workflow.add_edge("summarize", "integrate")
workflow.add_edge("integrate", "save")
workflow.add_edge("save", END)

各ノードの役割:

ノード 役割 AI呼び出し
search Tavily検索 or 履歴再利用 0回
summarize 各サイトを300字以内で要約 3回
integrate 要約を統合してレポート生成 1回
save MCPサーバー経由でファイル保存 0回

1回のレポート生成 = 4回のAI呼び出しです。


7. モデル比較検証(本記事の目玉)

さくらのAI Engineで使えるモデルを網羅し、同じテーマで出力を比較しました。

AVAILABLE_MODELS = [
    "gpt-oss-120b",
    "llm-jp-3.1-8x13b-instruct4",
    "preview/Kimi-K2.6",
    "preview/Qwen3.6-35B-A3B",
    "preview/gemma-4-31B-it",
    "preview/Kimi-K2.7-Code",
    "preview/Qwen3-VL-30B-A3B-Instruct",
]

7.1. gpt-oss-120b vs llm-jp-3.1-8x13b-instruct4

項目 gpt-oss-120b llm-jp-3.1-8x13b-instruct4
応答速度
日本語品質
特徴 thinkingブロックあり 日本語ドキュメントに強い
max_tokens制限 緩い 4096トークン(厳しい)

注意: gpt-oss-120bは自分のことを「ChatGPT」と名乗ることがあります(OpenAIのオープンウェイトモデルの特性)。

7.2. preview/Kimi-K2.6 vs preview/Qwen3.6-35B-A3B¥

同じテーマ「2026年AIエージェント最新動向」で比較した結果:

項目 Kimi-K2.6 Qwen3.6-35B-A3B
文字数・詳細度 簡潔(約1,200字) 詳細(約2,000字)
構造化 標準的な段落 絵文字マーカー・階層化・箇条書き
分析の深さ 要約レベル 各情報源を深掘り・今後の展望まで
レスポンス形式 contentフィールド model_extra['reasoning']から抽出
日本語 自然 ややカタカナ語混じり(proactive等)

Kimi-K2.6の出力例(一部):

2026年のAIエージェントは、単なる自動化ツールから「自律的な意思決定エージェント」へ進化しています。特に注目すべきは、マルチエージェント間の協調と、人間との自然な対話インターフェースの進化です。

Qwen3.6-35B-A3Bの出力例(一部):

📌 概要
2026年のAIエージェント市場は、大規模言語モデル(LLM)の進化と、エージェント間の協調(Multi-Agent Orchestration)が主軸となっています。
🔍 詳細

  1. 自律型エージェントの台頭
  • 従来のルールベースから、目標指向型の自律エージェントへシフト
  • proactive(先制的)な情報収集と提案が可能に

7.3. モデル選定の指針

用途 おすすめモデル
サクッと要約したい preview/Kimi-K2.6
深く分析・構造化したい preview/Qwen3.6-35B-A3B
日本語の正確性重視 llm-jp-3.1-8x13b-instruct4
複雑な推論・長文生成 gpt-oss-120b

8. ハマったポイントと解決策

8.1. gpt-oss-120bのcontentがNoneになる

nomurasanさんの記事でも触れられていた「thinkingブロック」問題。contentがNoneで.strip()が失敗します。

解決: reasoning_content → model_extra の順にフォールバック。

def get_content_from_response(resp):
    message = resp.choices[0].message
    if message.content:
        return message.content.strip()
    if hasattr(message, "reasoning_content") and message.reasoning_content:
        return message.reasoning_content.strip()
    if hasattr(message, "model_extra") and message.model_extra:
        reasoning = message.model_extra.get("reasoning")
        if reasoning:
            return extract_from_reasoning(reasoning)
    return "(AIからの応答が空でした)"

8.2. preview/Kimi-K2.6 / Qwen3.6-35B-A3Bの空レスポンス

デバッグしたところ、contentはNone、かつfinish_reasonがlengthでした。

原因: max_tokensが短すぎて、思考過程(model_extra['reasoning'])でトークンを使い切り、実際の回答が生成されなかった。

解決: モデルごとにmax_tokens上限を定義。

MODEL_LIMITS = {
    "gpt-oss-120b": {"summarize": 400, "integrate": 3000},
    "llm-jp-3.1-8x13b-instruct4": {"summarize": 400, "integrate": 1500},
    "preview/Kimi-K2.6": {"summarize": 800, "integrate": 3000},
    "preview/Qwen3.6-35B-A3B": {"summarize": 800, "integrate": 3000},
}

8.3. llm-jp-3.1-8x13b-instruct4のmax_tokens制限

Error: 'max_tokens' is too large: 3000. 
This model's maximum context length is 4096 tokens 
and your request has 1172 input tokens (3000 > 4096 - 1172).

解決: 統合レポートのmax_tokensを1500に制限。

8.4. MCP 2.0.0のAPI変更

from mcp.server.fastmcp import FastMCP → ModuleNotFoundError

解決: from mcp.server import MCPServer に変更。


9. リクエスト消費の記録と学び

本記事の開発を開始した時点で、222リクエストを消費済みでした。

フェーズ 消費リクエスト 累計
開発開始時 222(既存) 222
Gradio Quickstart 2 224
Tavily連携テスト 4 228
MCPサーバー構築 4 232
LangGraph化 4 236
モデル比較(2モデル×2回) 16 252

気づき:

  • 「リクエスト単位」の無料枠は、トークン数を気にしなくていい(長文プロンプトOK)
  • モデル比較を毎日3回実行すれば、残り2,778リクエストは約2週間で到達可能
  • Tavilyの無料枠(1,000回/月)とさくらのAI Engine(3,000回/月)の比率は約1:3が最適

10. まとめと今後の展望

作れたもの

  • 自作MCPサーバー×2(report-saver / search-history)
  • LangGraph StateGraphによる4ステップエージェント
  • 7モデル対応+比較機能
  • Gradio UIによるエンドユーザー向けWebアプリ

今後の展望

  • 従量課金に移行して「毎日自動ニュース収集」を運用
  • Slack Bot化、メール定期配信化
  • MCPサーバーを増やして「翻訳」「要約スタイル変更」等の機能を追加

ソースコード

GitHub: https://github.com/kenichikawaguchi/sakura-ai-agent

git clone https://github.com/kenichikawaguchi/sakura-ai-agent.git
cd sakura-ai-agent
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# .envにAPIキーを設定
python main.py

「Hello World」の次は「実用的なアプリ」です。さくらのAI Engineの無料枠、使い倒しましょう!

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?