Daimon の現行実装は、Go API を中心にしたSNS/検索アプリです。Python は本体APIではなく、embedding と POV抽出だけを担当する ML microservice として分離されています。
この文書は、いま実際に動いている構成を正本として説明します。スタックは Go 中心で、Python は ML microservice(ml-service/)だけです。シードもスキーマ・ブートストラップも Go(api/cmd/seed、起動時の CREATE TABLE IF NOT EXISTS)に統合済みで、別途のマイグレーション手順や Python バックエンドはありません。
React / Vite frontend
|
| REST
v
Go API
| SQL | HTTP
v v
PostgreSQL Python ML service
System of Record embedding / POV extraction
|
| IDs, relations, metadata
v
Qdrant
System of Search
Redis
optional read-model cache
ローカルの compose.yml では次のポートで起動します。
| Component | Port | Role |
|---|---|---|
| frontend | 5173 |
React UI |
| api | 8000 |
Go HTTP API |
| ml | 8001 |
Python ML service |
| PostgreSQL | 5432 |
正本DB |
| Qdrant | 6333 |
vector index |
| Redis | 6379 |
optional cache |
| Path | 責務 |
|---|---|
frontend/ |
React + Vite + TypeScript。タイムライン、検索、投稿、POVページ、プロフィール。 |
api/ |
Go API。認証、投稿、検索、ランキング、フォロー、保存、POVコメント。cmd/server・cmd/seed・cmd/batch。 |
ml-service/ |
唯一の Python。/embed・/embed_batch・/povs のみ。 |
docs/ |
共有ドキュメント。 |
消えてはいけないデータは PostgreSQL に置きます。
- users
- sessions
- posts
- povs
- likes
- comments
- pov_likes
- pov_comments
- follows
- bookmarks
Qdrant や Redis は再生成できる派生データです。Qdrant への書き込みが失敗しても、投稿そのものは PostgreSQL に残ります。
Qdrant の posts collection は 384次元 cosine vector を保存します。payload は検索補助用です。
collection: posts
vector size: 384
distance: Cosine
payload:
post_id
user_id
tags
created_at
Qdrant は候補生成に使います。最終表示に必要な本文、ユーザー名、POV、like/comment/save数は PostgreSQL から bulk load します。
ml-service/app.py は次の3エンドポイントだけを持ちます。
| Endpoint | Input | Output |
|---|---|---|
GET /health |
none | health |
POST /embed |
{"text": "..."} |
{"vector": [...]} |
POST /povs |
{"text": "..."} |
{"povs": [...]} |
embedding model は paraphrase-multilingual-MiniLM-L12-v2 です。日本語を扱える 384次元モデルなので、Qdrant の vector size は変えずに済みます。
長文は先頭だけを見るのではなく、約1200文字単位に分割して embedding し、平均ベクトルにします。これで長い投稿の複数論点が1つの投稿vectorにある程度反映されます。
Redis は未設定でも動作します。設定されている場合は、次のような派生データを置きます。
feed:{userId}: ユーザー別に事前計算したホームフィードID列suggest:popular: よく使われるPOVsuggest:related:{pov}: 意味的に近いPOV候補
Redisが落ちても正本は失われません。APIはライブ計算に戻すか、空に近いレスポンスへ安全に劣化します。
アカウント、表示名、メール、password hash、avatar、bioを保持します。簡単なプロフィール登録はここで完結します。
ログインセッションです。現在はcookie/sessionベースの素朴な認証です。
投稿本文と投稿者を保持します。投稿本文は意味ベクトル化され、Qdrantにも同じIDで登録されます。
投稿に付く POV です。現在は post_id + pov の組で重複を防いでいます。
現状の限界は、POVがまだタグに近いことです。将来的には post_pov_assertions に移し、次の情報を持たせます。
post_id
pov_id or pov_text
grade: A/B/C or lean
comment
spoiler
confidence
created_by
created_at
投稿ではなくPOVそのものに対するコメントです。
普通のコメントは「この投稿について話す」ためのものです。POVコメントは「この観点で見るとどうかを話す」ためのものです。この違いがDaimonの中核です。
「このPOVに立つ」「この観点を追う」に近い軽い反応です。現状は like として実装されていますが、将来的にはPOV followやstanceに寄せていきます。
人へのfollowです。Daimonでは人followを完全には消しません。ただし、ネットワークの背骨は人ではなくPOVに寄せます。人はコンテンツと信頼の手がかり、POVは探索の地図です。
投稿の保存・クリップです。保存はlikeより強い嗜好シグナルとして扱い、タイムラインの user sense centroid に強めに混ぜます。
SQLはGoコード内に直書きせず、api/internal/db/queries/server.sql に名前付きで外出ししています。
-- name: feed.load_posts
SELECT id, user_id, COALESCE(username,''), text, created_at
FROM posts
WHERE id = ANY($1)Go側は db.SQL("feed.load_posts") のように参照します。実装は標準の embed と軽いパーサです。
今の規模ではこの方式で十分です。クエリ数が増え、型安全性やコード生成のメリットが上回った段階で sqlc の導入を検討します。
- frontend が
POST /postsを呼ぶ。 - Go API が本文と POV をvalidateする。
- Go API が ML service
POST /embedに本文を渡す。 - PostgreSQL に
postsとpovsを保存する。 - embeddingが取れていれば、Qdrant
postscollection に upsert する。 - Qdrant が失敗しても投稿保存は成功扱いにできる。検索インデックスは後で再構築できるため。
- frontend が本文を
POST /posts/generate-povsに送る。 - Go API が ML service
POST /povsを呼ぶ。 - ML service は spaCy の noun chunks と日本語名詞列、fallback regex で短い候補を返す。
- frontend は候補として表示し、ユーザーが採用する。
POVは自動抽出だけで決めません。最終的に人間が選ぶことが、Daimonのランキングにとって重要です。
- frontend が
POST /posts/timelineを呼ぶ。 - Go API が query text か user centroid を検索vectorにする。
- Qdrantから候補を100-200件取る。
- PostgreSQLから本文、POV、like/comment/save数をbulk loadする。
- ユーザー自身の投稿vectorと保存投稿vectorから sense centroid を作る。
RankBySenseDistanceで並べ替える。- MMRで似すぎた候補を間引く。
現在のランキングは次の思想です。
near = similarity(user_centroid, post_vector)
far = 1 - near
common = shares_pov(user, post)
bridge = far * common
score =
alpha * near
+ (1 - alpha) * bridge
+ common_pov_bonus
+ optional popularity / recency
保存はlikeより強いシグナルとして扱います。bookmark は「あとで読みたい」だけでなく、「この投稿は自分の感性モデルに残す価値がある」というML改善の材料です。
検索は2系統を混ぜます。
- text query: ML serviceでquery embeddingを作り、Qdrantで意味検索する。
- POV query: PostgreSQLの
povsを直接引き、存在するPOVを拾う。
これにより、意味的に近い文章だけでなく、既に存在するPOVを直接suggestできます。
POVページは、単なるタグ一覧ではなく「その観点で議論する部屋」です。
現状:
- 関連投稿を検索する。
- POVコメントを一覧する。
- POVに対する軽い反応を保存する。
次に強化するもの:
- 最近のPOVコメント
- 人気のPOVコメント
- そのPOVに強いユーザー
- 似たPOV、隣接POV、反対/緊張関係にあるPOV
- 同じ観点で違う感じ方をしている人/投稿
api/cmd/batch は重い読み取りモデルを事前計算します。
長文投稿を分割し、各チャンクから追加POVを抽出します。深い投稿は複数の観点を含むため、投稿1件を複数のPOVノードへ分解するための処理です。
人気POVと、意味的に近いPOVをRedisにcacheします。
suggest:popular
suggest:related:{pov}
ユーザーごとにホームフィード候補を事前計算し、Redisに feed:{userId} として保存します。
現在の povs はタグです。次は、投稿とPOVの関係を主張として保存します。
post_pov_assertions
id
post_id
pov_id
lean or grade
comment
spoiler
confidence
created_by
created_at
これにより、「この投稿にはこのPOVがある」ではなく、「この投稿をこのPOVで見るとこう感じる」を保存できます。
POV自体にも説明を持たせます。
pov_definitions
id
title
category
description
examples
synonyms
parent_pov
merged_into
open vocabulary は維持します。ただし、よく使われるPOVには説明と同義語を与え、検索・統合・安全性を改善します。
探索ビューは、PostgreSQLの正本テーブルから直接巨大グラフを毎回作るのではなく、POV中心の小さなread modelとして作ります。
内部データはグラフで構いません。ただし、UIは巨大なforce-directed graphではなく、sense-distance が読めるlocal mapにします。
最初のノード:
- POV
- Post
- User
最初のエッジ:
- post has POV
- user asserted POV
- user commented on POV
- user saved post
- POV similar to POV
- same-axis disagreement
MVPでは全体グラフを出しません。1つのPOVを中心に1-2 hopだけを返すAPIにします。
APIの返却イメージ:
{
"center": { "type": "pov", "id": "tempo", "label": "テンポがよい" },
"nodes": [],
"edges": [],
"zones": {
"near": [],
"bridge": [],
"far": []
}
}zones はUIがsense mapとして描画するための補助です。正確な物理シミュレーションより、「近い」「少し遠い」「遠いが同じ軸」が読めることを優先します。
- タイムラインはページ全体を再描画しない。
- PostCardは小さなcomponentに分割し、memo化しやすくする。
- POV suggestion はdebounceする。
- 探索ビューは初期表示を軽くし、ノード上限を決める。
- 100ノードを超える可視化はDOMだけで抱えず、canvas/WebGLか仮想化を検討する。
- N+1 queryを避け、ID列からbulk loadする。
- Qdrantは候補生成、PostgreSQLは正本と最終表示の材料に分ける。
- Redisは高速化のために使い、正しさのためには使わない。
- ML service失敗時は空候補やfallbackに劣化し、投稿/閲覧の基本動線を止めない。
- model変更時は、既存投稿を全件re-embedする。
- Qdrantのvector dimensionを変える変更はmigration扱いにする。
- 検索品質はsemantic similarityだけで判断しない。POV一致、保存、recency、同軸異見を合わせて見る。
- 全ユーザーを点数化するランキング。
- グローバルな中心性スコアの露出。
- 巨大な3Dグラフ可視化。
- force-directed graphをそのままメインUIにすること。
- 統計ダッシュボード化したPOVページ。
- MLがPOVを完全に決定する設計。