waktu membaca : 14 minutes

Anda sedang membaca sebuah artikel tentang integrasi pgvector dengan LangChain4j. Di bagian bawah halaman, JBake Anda disarankan « Artikel terkait ». Anda mengklik. Ini adalah artikel tentang…​ konfigurasi. dari Kitty Terminal. Yang satu-satunya titik persamaan di antara keduanya? Mereka telah diterbitkan kurang dari Selisih sebelas hari. Ini bukan rekomendasi. Ini adalah kalender yang disamarkan sebagai editorial.

Panggung: Selasa, 12 Mei, 17:30

Saya membaca kembali salah satu artikel saya — itu tentang Knowledge Graph sebagai alat untuk memahami codebase Konten tersebut padat: node, edge, komunitas, PlantUML, onboarding. Sebuah artikel teknik membaca 15 menit yang menggerakkan`graphify-gradle`, plantuml-gradle, dan konsep topologi grafik.

Di bagian bawah halaman, artikel terkait menyarankan kepada saya:

  1. Sebuah artikel tentang Firebase Contact Form (0113)

  2. Sebuah artikel tentang mekanisme Eager/Lazy (0108)

  3. Sebuah artikel tentang migrasi skrip Gradle ke plugin (0102)

  4. Sebuah artikel tentang akumulasi langganan Ollama Pro (0120)

Tiga dari empat artikel ini tidak memiliki tidak ada hubungan dengan Knowledge Graph. Mereka ada karena mereka adalah yang terakhir dipublikasikan — kedekatan temporal, tidak lingkungan semantik

Saya melihat template`post.thyme`:

<!-- 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`adalah sebuah daftar kronologis.`postStat.index lt 4`mengambil empat pertama yang bukan post saat ini. Itu saja. Tidak ada logika de kemiripan. Tidak ada konsep konten. Hanya sebuah lingkaran`for`bersamaran sebagai rekomendasi.

Ini bukan bug dari JBake. Ini adalah perilaku default dari semua generator situs statis: daftar postingan datar dan diurutkan berdasarkan tanggal. Template melakukan apa yang dapat dilakukan dengan apa yang dimilikinya.

Tapi itu bukan alasan untuk menerimanya.

Diagnosa: Tiga Jenis Hubungan yang tidak terlihat oleh template

Korpus artikel saya — 23 posting dalam 4 bulan — memiliki struktur kaya yang template kronologis sepenuhnya mengalahkan :

  1. Referensi eksplisit : saya menggunakan`xref:`secara masif dalam artikel-artikel saya.

Artikel 0122 mereferensikan 0106 (knowledge graph) dan 0116 (kompartimentasi) epistemik). Ini adalah hard linking redaksi — saya sengaja memutuskan untuk menghubungkan konsep-konsep ini.

  1. Tag berbagi : setiap artikel memiliki beberapa`:jbake-tags:`. Artikel tentang

Graphify + PlantUML (0105) memiliki`gradle, graphify, plantuml, knowledge-graph`. Artikel tentang Knowledge Graph (0106) memiliki knowledge-graph, graphify, plantuml. Tiga tag bersama dari tujuh — 43% kesamaan.

  1. Co-occurring named entities : « pgvector », « RAG », « embedding

muncul bersama dalam empat artikel yang berbeda. « LangChain4j », Ollama », « plugin Gradle » dalam enam lainnya. Bukan tag yang dideklarasikan — ini adalah pola muncul dari korpus yang hanya sebuah NLP dapat mendeteksi.

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center

title Tiga lapisan hubungan antara artikel
package "Lapisan 1 — Referensi eksplisit" #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 "Lapisan 2 — Tags dibagikan" #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 "Lapisan 3 — Ontologi emergens" #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

Hari ini, situs saya tidak menggunakan satu pun dari ketiga lapisan ini. Ia menggunakan lapisan nol: urutan pemasukan dalam daftar Java.

Solusi : Sebuah Pipeline Gradle, Bukan Panggilan LLM Panas

Accuan pertama akan memanggil LLM saat proses bake: « Untuk artikel ini, menemukan tiga artikel yang paling serupa dalam korpus. Jangan lakukan itu.

Satu panggilan LLM untuk setiap`./gradlew bake`Memakan waktu, uang, dan Memperkenalkan non-determinisme dalam build Anda. Respons dari LLM bisa beralih antara dua build tanpa mengubah konten. CI Anda menjadi tidak dapat direproduksi, tes Anda menjadi tidak stabil.

Solusinya deterministik: sebuah pipeline Gradle yang pra-kalkulasi grafik. kesamaan dan menyimpannya di`graph.json`. template membaca hasil — dia tidak pernah memicu sebuah perhitungan.

Arsitektur : Bakery Mengimpor Graphify, Bukan Engine

Ini adalah titik arsitektur yang paling penting. Godaan akan menjadi mengkabel kolaborasi di`engine/build.gradle.kts`mesin mengaplikasikan graphify untuk pemindaian, bakery untuk memanggang, dan engine menjembatani antara kedua

Ini adalah anti-pola. Engine adalah terminal konsumen — menerapkan plugin, dia tidak mengimplementasikan logika bisnis. Aturan adalah : Beban bukti berada pada plugin bersifat proprietary.

Arsitektur yang tepat — Bakery mengimpor Graphify
@startuml
skinparam backgroundColor #FEFEFE
skinparam packageBackgroundColor #FFF3CD

title Bakery mengimpor Graphify — Engine tidak tahu apa-apa
package "N3 — MESIN" #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 — BAKERY" #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 — membuat grafik" #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 : "plugins { bakery }"
BAK -down-> GRF : "plugins { graphify } ← N2→N0 OK"
@enduml

Kontrak DAG dipatuhi: bakery (N2) mengimpor graphify (N0), N2 > N0, tidak ada pelanggaran. Mesin (N3) mengimpor bakery (N2), N3 > N2, OK.

Engine tidak memiliki satu pun baris kode yang mereferensikan`graph.json`, relatedPosts, atau apa pun konsep kesamaan. Dia menerapkan bakery, titik. La kolaborasi adalah internal bakery

Pipeline: Scan → Grafik → Template

Ini adalah pipeline lengkap dalam tiga langkah:

  1. Pemindaian (menggrafikan) :`graphify-plugin`memindai workspace. Di dalamnya

bentuk saat ini, ia sudah mendeteksi`xref:`di antara`.adoc`dan menunjukkan di`graph.json`seperti edges jenis`reference`.
Kita melengkapinya untuk konten redaksi : Uraikan metadata JBake (:jbake-tags:, :jbake-description:) setiap`.adoc`blog * Menghitung ko-occurence tag → edges`tag_cooccurrence`dengan berat * Mengekstrak entitas bernama dari deskripsi melalui TF-IDF → tepi`entity_overlap` Menyuntikkan sebuah bagian`blog_articles`dalam`graph.json`yang ada

// 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)
    )
}
  1. Bake (bakery) : pada saat`./gradlew bake`, `BakeryPlugin`tempat tidur

`graph.json`dan menyelesaikan artikel terkait untuk setiap post

// 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 }
        }
}
  1. Template (post.thyme) : model JBake sekarang menerima sebuah peta

terstruktur alih-alih sebuah daftar kronologis yang datar

<!-- 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>

Template-nya sama — dia tidak tahu dari mana data berasal. Hanya kontrak antara bakery dan JBake yang berubah:`published_posts`(daftar kronologis) menjadi`relatedPosts`(peta yang berbobot berdasarkan graf).

Lencana « alasan » (contoh:`xref 0122`, tag:gradle, cluster:pgvector-rag) jelaskan kepada pembaca mengapa artikel ini relevan. Bukan hanya transparansi — ini adalah pedagogi tentang topologi dari konten pribadi Anda ====

Fallback Kronologis

Si `graph.json`tidak hadir (build lokal tanpa pemindaian sebelumnya, pertama penerapan, CI yang belum diintegrasikan pemindaian graphify), templat harus menurunkan dengan anggun:

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)
}

Perilaku default sama dengan perilaku saat ini. Le mesin rekomendasi adalah sebuah perbaikan progresif — bukan sebuah perubahan yang merusak.

Ontologi yang Emergent: Ketika Artikel Berkelompok Tanpa Saling Mengenal

Lapisan yang paling menarik adalah yang ketiga: ontologi emergente. Artikel yang tidak dikutip, yang tidak memiliki tag yang sama, tetapi yang berbicara tentang hal yang sama tanpa mengetahuinya.

Mari kita ambil contoh konkret. Tiga artikel dari korpus saya:

  1. 0119 — Perbandingan DGX Spark vs Cloud Langganan LLM

  2. 0121 — Plugin Gradle Mengendalikan Dua Instansi Ollama Pro

  3. 0122 — Rasio Efisiensi 27x 45x Armada Ahli AI

Ketiga artikel ini tidak memiliki aucun xref di antara mereka. Tag mereka tidak mereka hanya tumpang tindih hingga 20% (« ollama », « llm ») umum. Namun NLP ringan pada deskripsi menunjukkan klaster yang jelas: « biaya », « langganan », awaan », « GPU », « kunci API », « Ollama Pro », « efektivitas », « rasio

Mereka membentuk kluster ontologis : ekonomi LLM self-hosted vs cloud. Klaster ini tidak dideklarasikan di mana pun. Ia muncul dari korpus.

@startuml
skinparam backgroundColor #FEFEFE
skinparam packageBackgroundColor #E8F8F5

title Cluster Ontologique Émergent — "Ekonomi LLM Self-Hosted"

package "Cluster terdeteksi : ekonomi-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 : "ollama", "llm".
    Cluster découvert par NLP :
    co-occurrences "biaya", "GPU",
    "langganan", "Ollama Pro".
end note
@enduml

Ini kluster ontologi menjadi sebuah edge komposit dalam`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 sengaja ringan — TF-IDF + cosine similarity pada deskripsi. Tidak perlu BERT, tidak perlu model bahasa. Corpus terdiri dari 23 artikel, matriks kemiripan muat dalam satu file JSON berukuran 50 KB.

Kekuatan NLP tidak berasal dari sofistikasi algoritme tapi dari ukuran korpus dan dari kualitas deskripsi. Se `:jbake-description:`ditulis dengan baik dari 150 karakter mengandung lebih dari sinyal untuk TF-IDF bahwa sebuah artikel penuh berjumlah 3000 kata.

Kontrak DAG: Siapa yang penting bagi siapa

Ini adalah titik di mana disiplin arsitektur membayar. DAG N0→N3 ditentukan dalam`engine/build.gradle.kts`memberi sebuah aturan sederhana: Tidak ada proyek yang merupakan proyek tingkat lebih tinggi.

Plugin Pengguna Plugin yang diimpor Tingkat konsumen Tingkat yang diimpor Sah?

bakery-gradle

graphify-gradle

N2

N0

✅ N2 > N0 — OK

mesin

bakery-gradle

N3

N2

✅ N3 > N2 — OK

mesin

graphify-gradle

N3

N0

✅ Teknik OK, tetapi konseptual salah — kolaborasi internal pada bakery

Engine menerapkan bakery. Bakery menerapkan graphify. Itu saja. Jika suatu hari saya ingin menambahkan vector store codebase (N1) untuk penelitian semantik cross-corpus, bakery juga mengimpornya. Engine tidak berubah.

Polanya adalah : plugin N2 adalah hub dari dependensi pribadinya Engine N3 bukan sebuah hub — ini adalah sebuah terminal yang menerapkan hub.

Apa yang Kita Dapatkan (Dan Apa yang Kita Tidak Dapatkan)

Keuntungan utama adalah editorial, bukan teknis:

sebelum Setelah

Artikel terkait = 4 posting terakhir

Artikel terkait = 4 teratas berdasarkan bobot dalam Knowledge Graph

Membaca dari RAG → rekomendasi Kitty Terminal

Pembaca membaca RAG → rekomendasi pgvector, chunking, vector store

Tidak ada transparansi tentang mengapa

Badge`xref 0122`, tag:gradle, `cluster:rag`terlihat

JBake standar, nol usaha, nol nilai

Pipeline Gradle deterministik, dapat direproduksi, dapat diuji

Tidak ada kurva belajar untuk pembaca

Pembaca menemukan topologi dari konten saya

Apa yang tidak kita dapatkan:

  • Ini bukan mesin rekomendasi « sebenarnya » (tidak ada kolaboratif

pemfilteran, tidak ada pengujian A/B, tidak ada loop umpan balik)

  • Kualitas tergantung pada kekayaan metadata JBake — jika Anda

deskripsi kosong, TF-IDF tidak melihat apa-apa

  • NLP offline — artikel baru hanya kluster di

scan berikutnya (ini bagus: build tetap deterministik)

Perspektif: Blog sebagai Knowledge Graph Publik

Fitur ini membuka perspektif yang lebih luas: dan jika`cheroliv.com` menjadi dirinya sendiri sebuah knowledge graph navigable ?

Artikel adalah node. Tag adalah komunitas. xref adalah tepi. Pembaca tidak lagi membaca artikel yang terisolasi — ia menavigasi di dalam graf pengetahuan yang artikel saat ini adalah titik masuk.

Bayangkan sebuah halaman utama yang menampilkan bukan daftar kronologis dari postingan terbaru, tetapi sebuah peta corpus : kluster ontologis, artikel pivot (yang memiliki banyak sisi), jalur bacaan disarankan (« Jika Anda menyukai artikel tentang Graphify, baca selanjutnya itu tentang Knowledge Graph sebagai alat pemahaman

Ini bukan lagi sebuah blog. Ini sebuah atlas semantik.

Dan ini bukan science fiction. Yang`graph.json`sudah ada. Dia hanya memerlukan antarmuka navigasi.

Kesimpulan: sistem file tahu yang template abaikan

Apa yang saya rancang pada malam Selasa ini adalah penggantian sebuah heuristik malas (urutan kronologis) oleh representasi akurat dari struktur korpus saya (Knowledge Graph).

template`post.thyme`tidak berubah. Yang berubah adalah apa yang kita Memberinya makan. Sebelumnya: sebuah daftar Java yang diurutkan berdasarkan`date`.Setelah : graf berbobot yang berasal dari tiga lapisan analisis — xrefs, co-occurrences tag, dan ontologi yang muncul melalui NLP.

Lingkaran sudah selesai. graphify memindai workspace. Bakery membaca le Graf memberi makan template. Pembaca melihat rekomendasi. Dan engine — maestro — bahkan tidak tahu bahwa semua ini ada.

Ini adalah arsitektur yang baik. Setiap plugin melakukan satu hal. Dan terminal konsumen tidak perlu mengetahui caranya.

Artikel terkait