研究者向けのサーベイ論文ファインダー+新規性チェッカー。 キーワードが論文中で「目的(objective)」か「制約(constraint)」かを本文まで読んで判定する。 ("Undermind の素朴版" / 自前 ResearchRabbit)
research_assist/
├── paper_novelty_checker.py # リファレンス実装(単体で動くCLI。scaffoldの移植元)
├── backend/ # Python + FastAPI + LangGraph
│ ├── app/
│ │ ├── main.py # FastAPI エントリ(OpenAPI を吐く)
│ │ ├── config.py # pydantic-settings + TOML ローダ(設定の単一ソース)
│ │ ├── schemas.py # Paper / Judgment / RunRequest …(フロントとの契約)
│ │ ├── deps.py # 認証など共通依存(スケルトン)
│ │ ├── llm/ # provider非依存LLM層
│ │ │ ├── registry.py # providers.toml からプロバイダ解決(旧 _LLM_REGISTRY)
│ │ │ └── client.py # complete() → 使用トークンも返す(課金用)
│ │ ├── pipeline/ # LangGraph
│ │ │ ├── state.py # GraphState(state→state の純ステップ)
│ │ │ ├── nodes.py # search/snowball/embed_rank/judge/deepen
│ │ │ └── graph.py # 配線 + 「弱い判定は深掘り」条件サイクル
│ │ ├── services/ # 実ロジック(cache/semantic_scholar/pdf/judge)✅移植済
│ │ ├── routers/ # runs.py(SSEストリーム) / config.py(UI用メタ)
│ │ └── static/index.html # 組み込みの軽量UI(バニラJS, ビルド不要)← 当面はこれで操縦
│ ├── config/
│ │ ├── providers.toml # LLMレジストリ+料金 ← ハードコード排除
│ │ └── settings.toml # 既定値・許容範囲・パイプライン設定
│ └── prompts/
│ └── objective_vs_constraint.ja.jinja2 # 判定プロンプト(仮置き。UIから編集可)
└── frontend/ # (将来用)Next.js。当面は backend 組み込みUIで十分
└── package.json # `npm run gen:api` で OpenAPI → TS型生成
-
最大限カスタマイズ可能 / ハードコード排除 LLMレジストリ・料金・既定パラメータ・許容範囲・プロンプトはすべて
config/*.tomlとprompts/*.jinja2に外出し。コードには「賢いデフォルト」だけ残し、上書きを設定で受ける。 UI はGET /configでカスタマイズ可能項目を取得して描画する。 -
インタラクティブ=非同期+ストリーミング クロール+判定は数分かかるので、
POST /runsで即 run_id を返し、GET /runs/{id}/stream(SSE)で LangGraph のastream_eventsを流す。 論文が1件判定されるたびにUIへ届く。LangGraph の条件サイクル(弱い判定→深掘り→再判定)が このアプリでLangGraphを使う正当な理由。 -
自分+友人用 / 課金なし APIキーは環境変数でサーバ側が持つ(こちらが発行したキーを使う)。決済・台帳・認証は無し。 まずは関連研究を探すツールとして動くことを優先。 (将来 公開するなら、
providers.tomlに料金欄を残してあるので、後からメータリング層を 足せる設計にはしてある。)
- Anthropic は純正
anthropicSDK(prompt caching / adaptive thinking / 正確な usage のため)。 - OpenAI / Gemini / xAI / DeepSeek は
openaiSDK + base_url(いずれも OpenAI互換エンドポイント)。 - Anthropic にも OpenAI互換シムは存在するが機能不足(キャッシュ・思考制御・usage精度を失う)なので
本番では使わない。
client.pyの分岐は意図的。
cd backend
cp .env.example .env # → .env に DEEPSEEK_API_KEY=... を1行書く(git管理外)
uv run uvicorn app.main:app --reload
# ブラウザで http://localhost:8000 を開く(/docs に Swagger, / が操縦UI).env(git管理外)に LLMキー(いずれか1つ)、任意で SEMANTIC_SCHOLAR_API_KEY。
UIから クエリ / キーワード / research question / 各件数 / プロバイダ / 判定プロンプト を
すべて変更して実行でき、結果は判定されるたびにストリーム表示される。
- ✅ 設定の外出し(providers/settings TOML, jinja2 プロンプト), pydantic スキーマ
- ✅ provider非依存LLM層, LangGraph 配線(条件付き深掘りサイクル含む)
- ✅
services/実ロジック移植(検索/snowball/埋め込み/PDF/判定) - ✅ FastAPI + SSE + 組み込みUI(クエリ・件数・プロンプトをUIから変更可能)
- ✅ DeepSeek で end-to-end 動作確認済み
- ⬜ run_id の永続化(今はプロセス内メモリ), LangGraph checkpointer
- ⬜ 深掘りノードの中身(grobid全文 / 近傍snowball / 再プロンプト)
- ⬜ (必要になれば)Next.js フロント, 公開時の課金・認証