レコメンデーション エンジンとしての Knowledge Graph:私が時系列の「関連記事」をどう廃止したか
公開日: 12 May 2026
あなたは pgvector と LangChain4j の統合に関する記事を読んでいます。ページの下部に、JBake 「関連記事」を提案されます。それをクリックします。それは…設定に関する記事です。 キティターミナルの。二つの間の唯一の共通点は?彼らは未満で公開されました 15日間隔。これは推奨ではありません。これは偽装されたカレンダーである 編集.
シーン : 火曜日 5月12日 17時30
私は自分の記事の一つを読み返しています — celui sur le Knowledge Graph comme outil de compréhension de codebase 内容は濃い:ノード、エッジ、コミュニティ、PlantUML、オンボーディング。記事 動員する15分間の読書テクニック`graphify-gradle`, plantuml-gradle, およびグラフトポロジーの概念。
ページ下部で、 「関連記事」 が提案しています:
-
Firebase Contact Form (0113)の記事
-
Eager/Lazy メカニズムに関する記事(0108)
-
グラドルスクリプト→プラグインへの移行(0102)についての記事
-
Ollama Pro(0120)のサブスクリプションの累積に関する記事
これらの4つの記事のうち、3つは Knowledge Graph と 全く 関係がない。 彼らはそこにいる理由は、彼らが最も新しく公開されたものだからだ — 時間的近接ではない 意味的近傍。
私はテンプレートを見ています`post.thyme`</think>
<!-- Articles connexes (liens internes SEO) -->
<th:block th:each="post,postStat : ${published_posts}">
<th:block th:if="${!post.uri.equals(content.uri) and postStat.index lt 4}">
...
</th:block>
</th:block>
`published_posts`時系列のリストです。`postStat.index lt 4`取る 現在の投稿ではない最初の4つです。それだけです。ロジックはありません。 類似性。内容の概念はない。ただのループ`for`として仮装されている 推奨。
|
JBakeのバグではありません。これはすべてのデフォルトの動作です。 静的サイトジェネレータ:投稿のリストはフラットで、日付順に並んでいる。 テンプレートは持っているものでできることをする。 |
しかし、それを受け入れる理由ではない。
診断:テンプレートが見逃す3つの関係のタイプ
私の記事コーパス — 4か月で23件の投稿 — は豊かな構造を持っている 時系列テンプレートは完全に上書きします:
-
明示的な参照 : 使っています`xref:`私の記事で大量に。
記事 0122 参照 0106 (ナレッジグラフ) および 0116 (区画) 認識論的)。 これらのリンクは編集による*hard linking*です — 私は 故意にこれらの概念を接続することを決めた。
-
共有タグ: 各記事には`:jbake-tags:`. 記事について
Graphify + PlantUML (0105) a`gradle, graphify, plantuml, knowledge-graph`. Knowledge Graph (0106)の記事は knowledge-graph, graphify, plantuml. 7つのうち3つのタグが共通 — 43%の重複率。
-
共起する固有名詞 : « pgvector », « RAG », « embedding
4つの異なる記事に共同で現れる。 « LangChain4j », Ollama », « plugin Gradle » は他の六つにあります。これらはタグではありません。 宣言された — これらは*patterns émergents*のコーパスで、ただNLPだけが 検出できる。
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
title 記事間の関係の3層
package "層 1 — 明示的な参照" #CCFFCC {
[Article A] as A1
[Article B] as B1
A1 -down-> B1 : xref:
note right of A1
Liens durs
Déterministes
Déjà dans graph.json
end note
}
package "レイヤー 2 — 共有タグ" #FFFFCC {
[Article A] as A2
[Article C] as C2
A2 ..> C2 : tag_cooccurrence
note right of A2
Poids = tags communs / tags totaux
Déterministes
À extraire via graphify
end note
}
package "層3 — エマージェントオントロジー" #FFCCCC {
[Corpus A] as A3
[Corpus B] as B3
A3 ..> B3 : entity_overlap
note right of A3
Clusters NLP
Co-occurrences d'entités nommées
TF-IDF / cosine similarity
end note
}
@enduml
今日、私のサイトはこれらの三つの層の*いずれも*使っていない。それは使う ゼロ層: Javaリストにおける挿入順序
解決策:Gradle パイプライン、ホットな LLM 呼び出しではない
最初の誘惑は、bakeの瞬間にLLMを呼び出すことでしょう:「Pour この記事は、コーパスの中で最も類似している3つの記事を見つける。 それをしないでください。
|
各回のLLM呼び出し`./gradlew bake`時間とお金がかかり、 ビルドに非決定性を導入します。LLMの応答は 内容が変わらないまま、二つのビルド間を切り替える。あなたのCIが変化する。 再現できないため、あなたのテストは不安定になります。 |
解決策は決定論的です:事前にグラフを計算するGradleパイプライン 類似性があり、それを保存します`graph.json`. テンプレートは結果を読み取ります — 彼は決して計算を開始しない。
アーキテクチャ : BakeryはGraphifyをインポートし、エンジンはインポートしない
これは建築における最も重要な点です。 誘惑は…することになるだろう コラボレーションをケーブルする中で`engine/build.gradle.kts`:engine適用する スキャン用にgraphify、ベイク用にbakery、そしてエンジンがその間をつなぐ 両方。
これはアンチパターンです。Engine は 消費者端末 — それは適用する プラグインに関するビジネスロジックは実装していません。ルールは: �証明の責任はプロプライエタリなプラグインにあります。
@startuml
skinparam backgroundColor #FEFEFE
skinparam packageBackgroundColor #FFF3CD
title Bakery が Graphify をインポートします — Engine は何も知りません
package "N3 — エンジン" #FFCCCC {
[engine/build.gradle.kts] as ENG
note right of ENG
plugins { bakery }
Zéro connaissance de graphify
4 tâches, ~150 lignes
end note
}
package "N2 — ベーカリー" #CCFFCC {
[bakery-plugin] as BAK
note right of BAK
plugins { graphify } ← import interne
Lit graph.json
Résout articles connexes
Expose le modèle à JBake
end note
}
package "N0 — GRAPHIFY" #CCE5FF {
[graphify-plugin] as GRF
note right of GRF
Scan workspace → graph.json
Enrichi avec :
- xref: edges
- co-occurrences de tags
- entités nommées (NLP)
end note
}
ENG -down-> BAK : "プラグイン { ベーカリー }"
BAK -down-> GRF : "プラグイン { グラフィファイ } ← N2→N0 オーケー"
@enduml
DAG契約は守られています:bakery (N2) が graphify (N0) をインポートしています、N2 > N0, 違反はありません。エンジン (N3) はベーカリー (N2) をインポートします。N3 > N2、OK。
Engineには参照するコードの行が*ひとつも*ありません`graph.json`, relatedPosts, または他の類似性の概念も。 彼はbakeryを適用する、点。 その コラボレーションはベーカリー内部です。
パイプライン: スキャン → グラフ → テンプレート
以下は3段階の完全なパイプラインです:
-
スキャン (グラフ化) :`graphify-plugin`ワークスペースをスキャンします。彼/彼女の
現在の形、すでにそれらを検出している`xref:`の間`.adoc`そして ~にさらす`graph.json`タイプのedgesのような`reference`.
私たちはそれを編集コンテンツのために豊かにします: * JBakeのメタデータを解析 (:jbake-tags:, :jbake-description:) それぞれの`.adoc`ブログの * タグの共起を計算 → edges`tag_cooccurrence`重さとともに * 名前付きエンティティをTF-IDFを使って説明から抽出 → エッジ`entity_overlap` セクションを注入する`blog_articles`の中`graph.json`存在する
// Extrait de l'enrichissement graphify pour le blog
fun enrichBlogSection(graphJson: File, blogDir: File): GraphJson {
val articles = blogDir.listFiles { f -> f.extension == "adoc" }
.map { parseJbakeMetadata(it) }
val nodes = articles.map { ArticleNode(it.slug, it.title, it.tags) }
val edges = mutableListOf<GraphEdge>()
// Couche 1 : xref (déjà fait par scanWorkspace)
// Couche 2 : co-occurrences de tags
for (a in articles) {
for (b in articles) {
if (a.slug == b.slug) continue
val common = a.tags.intersect(b.tags)
if (common.isNotEmpty()) {
edges.add(GraphEdge(
source = a.slug,
target = b.slug,
type = "tag_cooccurrence",
weight = common.size.toDouble() / (a.tags.size + b.tags.size)
))
}
}
}
return graphJson.copy(
blogArticles = BlogSection(nodes, edges)
)
}
-
Bake (bakery) : その瞬間の`./gradlew bake`, `BakeryPlugin`ベッド
`graph.json`そして関連記事を各投稿ごとに解決します。
// BakeryPlugin — résolution des articles connexes
fun resolveRelatedPosts(
currentSlug: String,
graph: GraphJson,
maxResults: Int = 4
): List<RelatedPost> {
val edges = graph.blogArticles.edges
.filter { it.source == currentSlug || it.target == currentSlug }
return edges
.sortedByDescending { it.weight }
.take(maxResults)
.map { edge ->
val relatedSlug = if (edge.source == currentSlug) edge.target else edge.source
graph.blogArticles.nodes.first { it.slug == relatedSlug }
}
}
-
Template (
post.thyme) : JBakeモデルは現在、Mapを受け取ります
平坦な時系列リストではなく、構造化されたもの。
<!-- Articles connexes basés sur le Knowledge Graph -->
<th:block th:if="${relatedPosts != null and !relatedPosts.empty}">
<section class="mt-5 pt-4 border-top">
<h2 class="h4 mb-3">Articles connexes</h2>
<th:block th:each="related : ${relatedPosts}">
<div class="mb-2">
<a th:href="${content.rootpath} + ${related.uri}"
th:text="${related.title}" class="fw-semibold"></a>
<br/>
<small class="text-muted">
<th:block th:each="reason,iterStat : ${related.reasons}">
<span class="badge bg-light text-dark"
th:text="${reason}"></span>
</th:block>
</small>
</div>
</th:block>
</section>
</th:block>
テンプレートは同じです — それはデータがどこから来たかを知りません。 ベーカリーとJBakeの間の契約だけが変更されました:published_posts(リスト 時系列) なる`relatedPosts`(グラフによる重み付けマップ)
|
「理由」バッジ(例: |
クロノロジカルフォールバック
Si `graph.json`存在しない(ローカルビルド、事前スキャンなし、最初) デプロイ, CIはまだスキャン グラフファイ), テンプレート 優雅に劣化しなければならない:
fun resolveRelatedPosts(currentSlug: String, graph: GraphJson?): List<RelatedPost> {
if (graph != null && graph.blogArticles != null) {
return resolveFromGraph(currentSlug, graph)
}
// Fallback chronologique — même comportement qu'aujourd'hui
logger.warn("[bakery] graph.json absent — fallback chronologique")
return resolveFromChronology(currentSlug)
}
デフォルトの動作は現在の動作と同じです。その レコメンデーションエンジンは、段階的な改善 — ではない ブレイキング・チェンジ。
エマージェントオントロジー : 記事が互いを知らずに集まるとき
最も興味深い層は第三層:新興オントロジーです。 引用されない記事で、同じタグを持たないものだが 同じことを話している人は、それに気づいていない。
具体的な例を挙げましょう。 私のコーパスから3つの記事:
-
0119 — ベンチマーク DGX Spark 対 クラウド サブスクリプション LLM
-
0121 — Gradle プラグイン Ollama Pro 2 インスタンスを操作
-
0122 — 効率比 27x 45x IAエキスパートフリート
これらの3つの記事には、aucun xref がありません。彼らのタグは 彼らは20%しかカバーしていません(「ollama」、「llm」が共通です)。しかし軽量な NLP です。 説明について明らかなクラスターが示される:« コスト », « サブスクリプション », クラウド », « GPU », « APIキー », « Ollama Pro », « 効率 », « 比率
彼らはオントロジー的クラスターを形成します:LLMセルフホストドの経済 vs cloud このクラスターはどこにも宣言されていません。コーパスから現れます。
@startuml
skinparam backgroundColor #FEFEFE
skinparam packageBackgroundColor #E8F8F5
title Cluster Ontologique Émergent — "LLMセルフホステッド経済"
package "クラスター検出 : 経済-LLM" #D5F5E3 {
[0119 — Benchmark DGX Spark\nvs Cloud Abonnement] as A
[0121 — Plugin Gradle Piloter\nDeux Instances Ollama Pro] as B
[0122 — Ratio Efficacité\n27x 45x Flotte Experts] as C
}
A .. B : entity_overlap (0.73)
B .. C : entity_overlap (0.81)
A .. C : entity_overlap (0.68)
note bottom of A
Aucun xref entre ces articles.
Tags communs : "オラマ", "llm".
Cluster découvert par NLP :
co-occurrences "コスト", "GPU",
"サブスクリプション", "オラマ プロ".
end note
@enduml
このオントロジークラスターは複合エッジになります。graph.json:
{
"source": "0119-benchmark-dgx-spark",
"target": "cluster:economie-llm",
"type": "entity_cluster",
"weight": 0.73,
"metadata": {
"clusterLabel": "Économie LLM Self-Hosted vs Cloud",
"commonEntities": ["coût", "GPU", "abonnement", "Ollama Pro", "ratio"],
"articlesInCluster": [
"0119-benchmark-dgx-spark",
"0121-ollama-pro-deux-instances",
"0122-ratio-efficacite-flotte-experts"
]
}
}
NLPは意図的に軽量です — TF-IDF + コサイン類似度(sur les) 説明。BERTは必要ありません、言語モデルは必要ありません。 コーパスは23件の記事で構成されており、類似度行列は1つに収まります 50 KoのJSONファイル。
|
NLPの力は、アルゴリズムの高度さから来なくて しかし、*コーパスのサイズ*と*説明の品質*によるものです。 一つ `:jbake-description:`よく書かれた150文字はもっと含んでいる TF-IDFに、3000語の記事全体であることを示す信号。 |
DAG契約:誰が誰に重要か
これは建築の規律が報われる点です。DAG N0→N3 で定義されている`engine/build.gradle.kts`簡単なルールを与える: どのプロジェクトも上位のプロジェクトとは関係ない。
| コンシューマープラグイン | インポートされたプラグイン | 消費者レベル | インポートされたレベル | 有効ですか? |
|---|---|---|---|---|
bakery-gradle |
graphify-gradle |
N2 |
N0 |
✅ N2 > N0 — OK |
エンジン |
bakery-gradle |
N3 |
N2 |
✅ N3 > N2 — OK |
エンジン |
graphify-gradle |
N3 |
N0 |
�✅ 技術 OK, しかし 概念的に間違っている — コラボレーションはbakery内部にある |
Engine 適用する bakery. Bakery 適用する graphify. それだけです。ある日 ベクトルストアコードベース (N1) を研究のために追加したいです。 クロス・コーパスの意味解析、ベーカリーもインポートします。Engineは変わりません。
|
パターンは:プラグイン N2 は自身の依存関係のハブです。 エンジンN3はハブではありません―それはハブを適用するターミナル。 |
得られることと得られないこと
主な利益は編集的で、技術的ではない:
| 前に | 後 |
|---|---|
関連記事 = 最新4件の投稿 |
関連記事 = Knowledge Graph での重み上位 4件 |
読み手はRAGを読む → Kitty Terminalの推奨 |
リーダーが RAG を読む → pgvector、チャンク化、ベクターストアの推奨 |
なぜについての透明性はゼロ |
バッジ`xref 0122`, |
JBake標準、ゼロ労力、ゼロ価値 |
決定的、再現可能、テスト可能なグレードルパイプライン |
読者に学習曲線はありません |
読者は私のコンテンツの*トポロジー*を発見する |
得られないもの:
-
これは « 本当の » レコメンデーション エンジンではない (コラボラティブ
filtering,A/B testingなし,feedback loopなし)
-
品質はJBakeのメタデータの豊富さに依存します — もしあなたの
説明は空です、TF-IDFは何も見ません
-
NLPはオフラインです — 新しい記事はただクラスター化されているだけです
次のスキャン (いいね:ビルドは依然として決定的です)
視点:パブリックナレッジグラフとしてのブログ
この機能はより広い視点を開く:もし~なら`cheroliv.com` 彼は自分自身が*ナビゲータブルな知識グラフ*になったのか?
記事はノードです。タグはコミュニティです。xrefは �辺。読者はもはや孤立した記事を読まず — 彼は~の中をナビゲートする 知識グラフ 現在の記事がエントリーポイントです。
時系列のリストを表示しないホームページを想像してください 最新の投稿だが、コーパスマップ:オントロジーのクラスター, ピボット記事(エッジが最も多いもの), 読み取りパス おすすめ「もしGraphifyの記事が気に入ったら、次に読んでください Knowledge Graphを理解するためのツールとしてのもの
これはもうブログではない。これは*セマンティックアトラス*です。
そしてこれはSFではありません。その`graph.json`既に存在しています。 彼に欠けているのはナビゲーションインターフェースだけだ。
結論:ファイルシステムはテンプレートが無視するものを知っている
私が火曜日の夜に考案したのは、ヒューリスティックの置き換えです。 怠惰な(年代順)による忠実な表現の 私のコーパスの構造 (Knowledge Graph)。
テンプレート`post.thyme`変わらない。変わるのは、私たちが 彼に食べ物を与える。 前 : 並び替えられた Java リスト`date`. 後: 3つの解析層から導出された重み付きグラフ — xrefs, 共起 タグ、およびNLPによる新興オントロジー
ループが閉じられました。 graphifyはワークスペースをスキャンします。 Bakeryは グラフとテンプレートを埋める。 読者はおすすめを見る そしてエンジン — 指揮者 — はそれについてさえ知らないと それらはすべて存在する。
これで正しいアーキテクチャです。各プラグインは一つのことだけを行います。 消費者端末はどのようにするかを知る必要はありません。
関連記事
31 May 2026
14 May 2026