〜無料枠で動くAI調査レポート自動生成システムの開発ログ〜
この記事はさくらのAI Engine 使い倒したで賞への応募記事です。
1. はじめに:作ったもの
「テーマを入力するだけで、数分後にMarkdownレポートが完成する」 Webアプリを作りました。
できること:
- 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)が主軸となっています。
🔍 詳細
- 自律型エージェントの台頭
- 従来のルールベースから、目標指向型の自律エージェントへシフト
- 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の無料枠、使い倒しましょう!
