@prefix : <https://loom.ogbuji.net/tech/building-and-working-knowledge-graphs-with-onya-and-local-llms/#> .
@prefix schema: <http://schema.org/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix prov: <http://www.w3.org/ns/prov#> .

#################################################################
# Lightweight ontology
#################################################################

:ontology a owl:Ontology ;
    schema:name "Onya knowledge graph article ontology"@en ;
    schema:description "A small ontology for describing serialization-format token economics discussed in the article on extracting knowledge graphs from books with the Onya model and local LLMs."@en ;
    schema:identifier <https://loom.ogbuji.net/tech/building-and-working-knowledge-graphs-with-onya-and-local-llms/> ;
    rdfs:label "Onya article ontology"@en .

:SerializationFormat a rdfs:Class ;
    rdfs:label "Serialization Format"@en ;
    rdfs:comment "A textual encoding used to represent a knowledge graph, such as JSON, TSV, or Onya Literate."@en ;
    rdfs:subClassOf schema:CreativeWork ;
    rdfs:isDefinedBy : .

:hasTokenCount a rdf:Property ;
    rdfs:label "has token count"@en ;
    rdfs:comment "The number of tokens a given serialization format required to encode the article's sample Things Fall Apart graph, per the Qwen 3.6 tokenizer."@en ;
    rdfs:domain :SerializationFormat ;
    rdfs:range xsd:integer ;
    rdfs:isDefinedBy : .

#################################################################
# Main article
#################################################################

:article a schema:TechArticle ;
    schema:name "Building and working knowledge graphs with Onya and local LLMs"@en ;
    schema:headline "Building and working knowledge graphs with Onya and local LLMs"@en ;
    schema:url <https://loom.ogbuji.net/tech/building-and-working-knowledge-graphs-with-onya-and-local-llms/> ;
    schema:datePublished "2026-07-30T15:59:56.000Z"^^xsd:dateTime ;
    schema:dateModified "2026-07-31T00:22:23.000Z"^^xsd:dateTime ;
    schema:inLanguage "en"@en ;
    schema:keywords "deep-tech"@en, "Artificial Intelligence"@en, "Literature"@en, "Metadata"@en, "Technology"@en, "Nigeria"@en, "Igbo"@en, "Knowledge Graphs"@en, "Python"@en, "Data Viz"@en ;
    schema:abstract "Knowledge graphs are powerful tools for GenAI context. Learn how to work with them using a small, private/local model, building knowledge structure from large documents, and treating the graph as a full-blown system of record rather than a snapshot."@en ;
    schema:articleBody """You may not need a frontier cloud model to turn a shelf of documents into a knowledge graph. Responding to Laurent Picard's Gemini-based walkthrough, the author extracts an entity/relationship graph from a full public-domain book using only a local model reachable through the plain OpenAI-style API, and the Onya graph format and library, which he develops at Oori Data. Unlike a networkx-and-matplotlib pipeline that computes, renders, and discards, Onya treats the graph as a durable, mergeable, queryable system of record: extraction results merge idempotently across document chunks, persist to a SQLite or PostgreSQL store, and analytics computed via a networkx projection are written back into the graph as first-class typed assertions."""@en ;
    schema:image :heroImage ;
    schema:author <https://www.linkedin.com/in/ucheogbuji/#this> ;
    schema:publisher :loomiverse ;
    schema:about <http://dbpedia.org/resource/Knowledge_graph>, <http://dbpedia.org/resource/Large_language_model> ;
    schema:mentions :onya, :omlx, :wordLoom, :things_fall_apart, :equiano_narrative, <http://dbpedia.org/resource/Olaudah_Equiano>, <http://dbpedia.org/resource/Chinua_Achebe>, :picardGeminiArticle ;
    schema:citation :mlxDayOneArticle, :mlxNotesRepo, :powerSteeringArticle ;
    schema:relatedLink <https://hackernoon.com/building-knowledge-graphs-with-gemini>,
        <https://github.com/jundot/omlx>,
        <https://github.com/uogbuji/mlx-notes>,
        <https://github.com/OoriData/Onya>,
        <https://huggingface.co/blog/ucheog/mlx-day-one>,
        <https://en.wikipedia.org/wiki/Things_Fall_Apart>,
        <https://schema.org/children>,
        <https://huggingface.co/blog/ucheog/llm-power-steering>,
        <https://github.com/OoriData/WordLoom/>,
        <https://toml.io/en/>,
        <https://mermaid.live/>,
        <https://frickers.co.uk/art/blog/2017/10/16/olaudah-equiano-i-salute-you/>,
        <https://www.gutenberg.org/cache/epub/15399/pg15399.txt> ;
    schema:hasPart :sectionChallenge, :sectionSetup, :sectionOnyaIntro, :sectionOutputFormat,
        :sectionSteering, :sectionChunking, :sectionCheckpoint, :sectionRoundTrip,
        :sectionSeeingIt, :sectionColdWater, :sectionSailAway,
        :faqSection, :glossarySection, :howtoSection, : ;
    prov:wasGeneratedBy <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/kg-generator#this> .

:sectionChallenge schema:isPartOf :article .
:sectionSetup schema:isPartOf :article .
:sectionOnyaIntro schema:isPartOf :article .
:sectionOutputFormat schema:isPartOf :article .
:sectionSteering schema:isPartOf :article .
:sectionChunking schema:isPartOf :article .
:sectionCheckpoint schema:isPartOf :article .
:sectionRoundTrip schema:isPartOf :article .
:sectionSeeingIt schema:isPartOf :article .
:sectionColdWater schema:isPartOf :article .
:sectionSailAway schema:isPartOf :article .
:faqSection schema:isPartOf :article .
:glossarySection schema:isPartOf :article .
:howtoSection schema:isPartOf :article .
: schema:isPartOf :article .

<https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/kg-generator#this> a schema:SoftwareApplication ;
    schema:name "kg-generator skill"@en ;
    schema:url <https://github.com/OpenLinkSoftware/ai-agent-skills/tree/main/kg-generator> ;
    schema:description "An AI-agent skill that generates standards-compliant RDF/JSON-LD knowledge graphs from web articles and documents."@en .

#################################################################
# Article sections
#################################################################

:sectionChallenge a schema:CreativeWork ;
    schema:name "The challenge"@en ;
    schema:about :onya ;
    schema:text "Given a long document from Project Gutenberg and only a local model: extract a knowledge graph of characters and relationships within an honest local context window (not an 800k-token single request), keep the graph as a queryable, persistent, growable artifact, and run real graph analytics while keeping those results in the graph too."@en ;
    schema:hasPart :picardDiagramImage .

:sectionSetup a schema:CreativeWork ;
    schema:name "Setup"@en ;
    schema:about :omlx, :qwen35BA3B, :qwen4B8bit ;
    schema:text "Point an OpenAI-compatible client at a local inference server such as oMLX. Relationship extraction wants more model heft than entity extraction; the author lands on Qwen 3.6 35B-A3B, a mixture-of-experts model with 35B total parameters but ~3B active per token, needing ~26GB RAM at 6-bit. A smaller 4B model still yields a decent entity graph but will not wire relationships together."@en ;
    schema:hasPart :codeSetupClient, :codeInstallOnya .

:sectionOnyaIntro a schema:CreativeWork ;
    schema:name "Onya in about ninety seconds"@en ;
    schema:about :onya, :onyaLiterateFormat ;
    schema:text """Onya's conceptual model: a node has an IRI identifier, a set of types, and a set of assertions. An assertion is either a property (label IRI to string value) or an edge (label IRI to target node); assertions can themselves carry assertions. Onya Literate, a Markdown dialect, is the serialization that makes this practical for LLM work: a docheader block sets the document IRI and bases, '# NodeID [Type]' blocks declare nodes, '* label: value' declares a property, and '* label -> Target' declares an edge."""@en ;
    schema:hasPart :codeThingsFallApartGraph, :tfaStylizedImage .

:sectionOutputFormat a schema:CreativeWork ;
    schema:name "The output format is the game entire"@en ;
    schema:about :onyaLiterateFormat, :prettyJsonFormat, :compactJsonFormat, :tsvFormat ;
    schema:text "Comparing token counts on the Things Fall Apart sample graph with the Qwen 3.6 tokenizer: pretty JSON costs 418 tokens, compact JSON 224, TSV 96, and Onya Literate 187 -- more than halving pretty JSON while remaining self-describing and diff-friendly, unlike TSV's disconnected integer-id tables that must be reassembled by hand."@en ;
    schema:hasPart :codeTokenizerMeasure .

:sectionSteering a schema:CreativeWork ;
    schema:name "Steering without a sampler"@en ;
    schema:about :onyaLiterateFormat, :threeSO ;
    schema:text "Onya Literate is not a JSON schema and the sampler is not constrained (3SO); instead the article uses validation by parse, with the parser's own errors fed back as repair feedback. Prompts live in a separate WordLoom TOML file so they can be reviewed, diffed, and swapped without touching Python."@en ;
    schema:hasPart :codePromptsToml, :codeExtractChunk, :steeringIllustrationImage .

:sectionChunking a schema:CreativeWork ;
    schema:name "Chunking, and why merge semantics is the real story"@en ;
    schema:about :onya, :aliasingProblem ;
    schema:text "A local model sees only ~32k tokens at a time, so documents are chunked. Onya's answer to cross-chunk entity identity is structural: an entity's IRI is minted from its name under a stable nodebase, so the same name in different chunks resolves to the same node, and merge() collapses duplicate occurrences under the spec's identity rules, making extraction an idempotent graph union."@en ;
    schema:hasPart :codeFetchEquianoText, :equianoPortraitImage .

:sectionCheckpoint a schema:CreativeWork ;
    schema:name "Checkpoint it: the graph is a record, not a printout"@en ;
    schema:about :onya ;
    schema:text "Onya has a pluggable persistence layer whose correctness criterion is that a round trip through a store must be indistinguishable from an in-memory graph union. SQLite ships dependency-free in the box; a PostgreSQL backend is available for scale, with the same merge semantics."@en ;
    schema:hasPart :codeStoreCheckpoint .

:sectionRoundTrip a schema:CreativeWork ;
    schema:name "The round trip: analytics as first-class data"@en ;
    schema:about :onya ;
    schema:text "Onya's onya.serial.nx module projects the graph into networkx, unlocking centrality measures, community detection, and layout algorithms. Results such as betweenness centrality and Louvain communities are written back into the graph as typed, merge-safe assertions via an explicit data contract (interp=ONYA_INTERP), rather than evaporating in a notebook."@en ;
    schema:hasPart :codeNetworkxAnalytics, :codeQuerySelector .

:sectionSeeingIt a schema:CreativeWork ;
    schema:name "Seeing it"@en ;
    schema:about :onya ;
    schema:text "Rendering is deliberately not Onya's job, but from the networkx projection a matplotlib figure is a short walk, sizing and coloring nodes from values stored in the graph itself. Onya's CLI can also export Mermaid or Graphviz DOT directly from a Literate file for a quick structural look."@en ;
    schema:hasPart :codeMatplotlibViz, :equianoKgVizImage .

:sectionColdWater a schema:CreativeWork ;
    schema:name "Cold Water"@en ;
    schema:about :onya, :qwen35BA3B, :qwen4B8bit ;
    schema:text "Honest caveats: the parse-and-repair loop is a statistical comfort, not a guarantee -- two of ten chunks skipped on one run. Extraction quality degrades ungracefully as model size shrinks; the 4B model gave clean entities but no edges, while the 35B-A3B model still occasionally mistyped a ship or place as a Person. The networkx projection is deliberately lossy in this first version."@en ;
    schema:hasPart :racehorseProgressImage .

:sectionSailAway a schema:CreativeWork ;
    schema:name "Sail away!"@en ;
    schema:about :onya ;
    schema:text "The pattern in a trice: prompt a local model to emit Onya Literate, validate by parsing and repair with the parser's own errors, chunk long documents and let IRI identity plus explicit merge() make extraction an idempotent union, checkpoint to a store, project to networkx for analysis, and write the results back as typed assertions so the analysis is part of the record."@en .

#################################################################
# Person and organization entities
#################################################################

<https://www.linkedin.com/in/ucheogbuji/#this> a schema:Person ;
    schema:name "Uche Ogbuji"@en ;
    schema:url <https://www.linkedin.com/in/ucheogbuji/> ;
    schema:identifier <https://www.linkedin.com/in/ucheogbuji/> ;
    schema:worksFor :oori ;
    schema:memberOf :loomiverse ;
    schema:image <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/size/w1200/2022/08/IMG_20200215_193319.jpg> ;
    owl:sameAs <https://x.com/uogbuji#this>, <https://linktr.ee/uche720#this> .

# Alternate-platform identities for Uche Ogbuji, linked above via owl:sameAs only.
# Deliberately untyped (no rdf:type / schema:name) so they are not rendered as
# separate People entities by RDF-to-HTML extraction -- they are aliases, not
# distinct entities. See howto/kg-alias-iri-untyped.ttl.

:oori a schema:Organization ;
    schema:name "Oori Data"@en ;
    schema:url <https://oori.dev/> ;
    schema:identifier <https://oori.dev/> ;
    schema:description "The company where the author develops the Onya knowledge graph model and format."@en ;
    owl:sameAs <https://www.linkedin.com/company/oori-data#this> .

# Alternate-platform identity for Oori Data, linked above via owl:sameAs only;
# deliberately untyped for the same reason as the person aliases above.

:loomiverse a schema:Organization ;
    schema:name "Loomiverse"@en ;
    schema:url <https://loom.ogbuji.net/> ;
    schema:identifier <https://loom.ogbuji.net/> ;
    schema:description "Uche Ogbuji's technology and literature blog, where this article was published."@en .

<https://www.linkedin.com/in/picardparis/#this> a schema:Person ;
    schema:name "Laurent Picard"@en ;
    schema:url <https://www.linkedin.com/in/picardparis/> ;
    schema:identifier <https://www.linkedin.com/in/picardparis/> ;
    schema:description "Author of the HackerNoon article 'Building Knowledge Graphs with Gemini', which prompted this response article."@en .

<http://dbpedia.org/resource/Chinua_Achebe> a schema:Person ;
    schema:name "Chinua Achebe"@en ;
    rdfs:seeAlso <https://en.wikipedia.org/wiki/Things_Fall_Apart> .

<http://dbpedia.org/resource/Olaudah_Equiano> a schema:Person ;
    schema:name "Olaudah Equiano"@en ;
    schema:description "Also known as Gustavus Vassa: a kidnapped Igbo child who bought his own freedom and became a founding voice of British abolitionism; subject of the memoir used as the article's full-scale extraction target."@en ;
    schema:image :equianoPortraitImage .

<http://dbpedia.org/resource/Gordon_Frickers> a schema:Person ;
    schema:name "Gordon Frickers"@en ;
    schema:description "Marine artist; painter of 'Racehorse progress', a detail from which illustrates the article's Equiano section."@en ;
    owl:sameAs <https://frickers.co.uk/art/#this> .

# Alternate-platform identity for Gordon Frickers, linked above via owl:sameAs
# only; deliberately untyped for the same reason as the aliases above.

#################################################################
# Software and format entities
#################################################################

<https://github.com/OoriData/Onya#this> a schema:SoftwareApplication ;
    schema:name "Onya"@en ;
    schema:url <https://github.com/OoriData/Onya> ;
    schema:identifier <https://github.com/OoriData/Onya> ;
    schema:description "A knowledge graph model and format developed at Oori Data, designed so the graph is the durable artifact: extraction merges idempotently, persists to a store, and analytics flow back into the graph as first-class typed assertions. From Igbo 'onya': web, snare, network."@en ;
    schema:softwareVersion "0.4.2"@en ;
    schema:creator <https://www.linkedin.com/in/ucheogbuji/#this> ;
    schema:isPartOf :oori .
:onya owl:sameAs <https://github.com/OoriData/Onya#this> .

<https://github.com/jundot/omlx#this> a schema:SoftwareApplication ;
    schema:name "oMLX"@en ;
    schema:url <https://github.com/jundot/omlx> ;
    schema:identifier <https://github.com/jundot/omlx> ;
    schema:description "A native inference server for Apple Silicon with continuous batching and tiered KV caching; the author's daily-driver local model server, exposed through an OpenAI-compatible API."@en .
:omlx owl:sameAs <https://github.com/jundot/omlx#this> .

<https://github.com/OoriData/WordLoom/#this> a schema:SoftwareApplication ;
    schema:name "WordLoom"@en ;
    schema:url <https://github.com/OoriData/WordLoom/> ;
    schema:identifier <https://github.com/OoriData/WordLoom/> ;
    schema:description "A TOML-based prompt file format that keeps LLM prompts out of Python code, so they can be reviewed, diffed, and git-committed independently."@en ;
    schema:isPartOf :oori .
:wordLoom owl:sameAs <https://github.com/OoriData/WordLoom/#this> .

:onyaLiterateFormat a :SerializationFormat, schema:DefinedTerm ;
    schema:name "Onya Literate"@en ;
    schema:description "A Markdown-dialect serialization of an Onya graph: self-describing, human-legible, and directly parseable, with no integer-id reassembly step."@en ;
    :hasTokenCount 187 ;
    rdfs:isDefinedBy : .

:prettyJsonFormat a :SerializationFormat ;
    schema:name "Pretty JSON"@en ;
    :hasTokenCount 418 ;
    rdfs:isDefinedBy : .

:compactJsonFormat a :SerializationFormat ;
    schema:name "Compact JSON"@en ;
    :hasTokenCount 224 ;
    rdfs:isDefinedBy : .

:tsvFormat a :SerializationFormat ;
    schema:name "TSV"@en ;
    schema:description "A tab-separated tabular serialization that is token-lean but shatters the graph into disconnected tables of integer ids requiring downstream reassembly."@en ;
    :hasTokenCount 96 ;
    rdfs:isDefinedBy : .

:qwen35BA3B a schema:SoftwareApplication ;
    schema:name "Qwen 3.6 35B-A3B (6-bit)"@en ;
    schema:description "A mixture-of-experts model with 35 billion total parameters but ~3 billion active per token; used for relationship extraction, requiring ~26GB RAM at 6-bit quantization."@en ;
    rdfs:seeAlso <http://dbpedia.org/resource/Qwen> .

:qwen4B8bit a schema:SoftwareApplication ;
    schema:name "Qwen 3.5 4B (8-bit)"@en ;
    schema:description "A smaller ~5GB local model that produces a decent entity graph but does not reliably wire relationships together."@en ;
    rdfs:seeAlso <http://dbpedia.org/resource/Qwen> .

:threeSO a schema:DefinedTerm ;
    schema:name "Schema-steered structured output (3SO)"@en ;
    schema:description "Constraining an LLM sampler so structured output is guaranteed rather than merely requested, discussed in the author's earlier 'power steering' article."@en ;
    schema:sameAs :powerSteeringArticle .

:aliasingProblem a schema:DefinedTerm ;
    schema:name "The aliasing problem"@en ;
    schema:description "The identity problem of one real-world entity appearing under multiple names across a document -- e.g. Olaudah Equiano also called Gustavus Vassa -- producing distinct nodes that must be reconciled with an alias_of-style edge."@en .

#################################################################
# Referenced creative works
#################################################################

:picardGeminiArticle a schema:Article ;
    schema:name "Building Knowledge Graphs with Gemini"@en ;
    schema:url <https://hackernoon.com/building-knowledge-graphs-with-gemini> ;
    schema:identifier <https://hackernoon.com/building-knowledge-graphs-with-gemini> ;
    schema:author <https://www.linkedin.com/in/picardparis/#this> ;
    schema:description "The article that prompted this response: extracts entity/relationship graphs from entire books using Gemini's large context window, and inspired the addition of onya.serial.nx to Onya 0.4.1."@en .

:mlxDayOneArticle a schema:Article ;
    schema:name "Apple MLX for AI/Large Language Models -- Day One"@en ;
    schema:url <https://huggingface.co/blog/ucheog/mlx-day-one> ;
    schema:identifier <https://huggingface.co/blog/ucheog/mlx-day-one> ;
    schema:author <https://www.linkedin.com/in/ucheogbuji/#this> .

:powerSteeringArticle a schema:Article ;
    schema:name "LLM power steering"@en ;
    schema:url <https://huggingface.co/blog/ucheog/llm-power-steering> ;
    schema:identifier <https://huggingface.co/blog/ucheog/llm-power-steering> ;
    schema:author <https://www.linkedin.com/in/ucheogbuji/#this> .

:mlxNotesRepo a schema:SoftwareSourceCode ;
    schema:name "mlx-notes"@en ;
    schema:codeRepository <https://github.com/uogbuji/mlx-notes> ;
    schema:identifier <https://github.com/uogbuji/mlx-notes> ;
    schema:author <https://www.linkedin.com/in/ucheogbuji/#this> .

:things_fall_apart owl:sameAs <http://dbpedia.org/resource/Things_Fall_Apart> .
<http://dbpedia.org/resource/Things_Fall_Apart> a schema:Book ;
    schema:name "Things Fall Apart"@en ;
    schema:author <http://dbpedia.org/resource/Chinua_Achebe> ;
    rdfs:seeAlso <https://en.wikipedia.org/wiki/Things_Fall_Apart> .

:equiano_narrative owl:sameAs <http://dbpedia.org/resource/The_Interesting_Narrative_of_the_Life_of_Olaudah_Equiano> .
<http://dbpedia.org/resource/The_Interesting_Narrative_of_the_Life_of_Olaudah_Equiano> a schema:Book ;
    schema:name "The Interesting Narrative of the Life of Olaudah Equiano"@en ;
    schema:author <http://dbpedia.org/resource/Olaudah_Equiano> ;
    schema:datePublished "1789"@en ;
    schema:description "The memoir used as the article's full-scale extraction target, sourced as plain text from Project Gutenberg; its dual naming (Equiano/Gustavus Vassa) is the article's running example of the aliasing problem."@en ;
    schema:isBasedOnUrl <https://www.gutenberg.org/cache/epub/15399/pg15399.txt> ;
    rdfs:seeAlso <http://dbpedia.org/resource/Project_Gutenberg> .

#################################################################
# Code examples
#################################################################

:codeSetupClient a schema:SoftwareSourceCode ;
    schema:name "Point an OpenAI client at a local server"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Configures an OpenAI Python client against a local oMLX server base URL, selecting the Qwen 3.6 35B-A3B model."@en ;
    schema:isPartOf :sectionSetup .

:codeInstallOnya a schema:SoftwareSourceCode ;
    schema:name "Install Onya"@en ;
    schema:programmingLanguage "Shell"@en ;
    schema:text "pip install 'onya[nx]>=0.4.2' matplotlib"@en ;
    schema:isPartOf :sectionSetup .

:codeThingsFallApartGraph a schema:SoftwareSourceCode ;
    schema:name "Onya Literate sample graph (Things Fall Apart)"@en ;
    schema:programmingLanguage "Onya Literate"@en ;
    schema:description "A complete example Onya Literate document encoding the opening character relationships of Things Fall Apart, with a docheader and five Person/Place node blocks."@en ;
    schema:isPartOf :sectionOnyaIntro ;
    schema:about <http://dbpedia.org/resource/Things_Fall_Apart> .

:codeTokenizerMeasure a schema:SoftwareSourceCode ;
    schema:name "Measure token counts across serialization formats"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Uses a Hugging Face AutoTokenizer for the Qwen 3.6 model to compare token counts of pretty JSON, compact JSON, TSV, and Onya Literate encodings of the same sample graph."@en ;
    schema:isPartOf :sectionOutputFormat .

:codePromptsToml a schema:SoftwareSourceCode ;
    schema:name "extract.loom.toml prompt file"@en ;
    schema:programmingLanguage "TOML"@en ;
    schema:description "A WordLoom prompt file defining kg-extract (instructs the model to emit Onya Literate from a document excerpt) and kg-repair (feeds a parser error back for correction)."@en ;
    schema:isPartOf :sectionSteering ;
    schema:isBasedOn <https://github.com/OoriData/WordLoom/#this> .

:codeExtractChunk a schema:SoftwareSourceCode ;
    schema:name "extract_chunk: parse-and-repair extraction loop"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Renders the kg-extract prompt, sends it to the local model, attempts to parse the response with Onya's LiterateParser, and on failure feeds the parser exception back through kg-repair for up to two retries."@en ;
    schema:isPartOf :sectionSteering .

:codeFetchEquianoText a schema:SoftwareSourceCode ;
    schema:name "Fetch and chunk Equiano's narrative"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Downloads the Project Gutenberg plain-text edition of Equiano's narrative, trims front matter, splits it into ~12k-token chunks, and unions each chunk's extracted subgraph into one accumulating Onya graph."@en ;
    schema:isPartOf :sectionChunking ;
    schema:about <http://dbpedia.org/resource/The_Interesting_Narrative_of_the_Life_of_Olaudah_Equiano> ;
    schema:isBasedOnUrl <https://www.gutenberg.org/cache/epub/15399/pg15399.txt> .

:codeStoreCheckpoint a schema:SoftwareSourceCode ;
    schema:name "Checkpoint the graph to SQLite"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Opens a SQLite-backed Onya store and puts the graph with merge=True, so re-running extraction unions into what is already stored rather than clobbering it."@en ;
    schema:isPartOf :sectionCheckpoint .

:codeNetworkxAnalytics a schema:SoftwareSourceCode ;
    schema:name "Project to networkx and compute analytics"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Projects the Onya graph into networkx, isolates the Person-typed giant connected component, computes betweenness centrality and Louvain community detection, and writes the results back into the graph as typed assertions."@en ;
    schema:isPartOf :sectionRoundTrip .

:codeQuerySelector a schema:SoftwareSourceCode ;
    schema:name "Query written-back analytics via Onya's selector"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Selects assertions by the betweenness label from the graph and sorts them via value_of to print the top five most central nodes."@en ;
    schema:isPartOf :sectionRoundTrip .

:codeMatplotlibViz a schema:SoftwareSourceCode ;
    schema:name "Render the giant component with matplotlib"@en ;
    schema:programmingLanguage "Python"@en ;
    schema:description "Sizes and colors nodes from the betweenness and community values stored in the graph, lays out the giant connected component with a networkx spring layout, and saves the figure as equiano_kg.png."@en ;
    schema:isPartOf :sectionSeeingIt ;
    schema:image :equianoKgVizImage .

#################################################################
# Images
#################################################################

:heroImage a schema:ImageObject ;
    schema:name "Building and working knowledge graphs with Onya and local LLMs"@en ;
    schema:caption "Tiny fragment of a knowledge graph from Chinua Achebe's novel, Things Fall Apart"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/size/w1200/2026/07/tfa_web-1.png> ;
    schema:thumbnailUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/size/w960/2026/07/tfa_web-1.png> ;
    schema:about <http://dbpedia.org/resource/Things_Fall_Apart> .

:picardDiagramImage a schema:ImageObject ;
    schema:caption "Knowledge Graph diagram from Laurent Picard's article"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/2026/07/image.png> ;
    schema:about :picardGeminiArticle ;
    schema:isPartOf :sectionChallenge .

:tfaStylizedImage a schema:ImageObject ;
    schema:caption "Stylized representation of the Things Fall Apart graph snippet"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/2026/07/tfa_web.png> ;
    schema:about <http://dbpedia.org/resource/Things_Fall_Apart> ;
    schema:isPartOf :sectionOnyaIntro .

:steeringIllustrationImage a schema:ImageObject ;
    schema:caption "Illustration from the schema-steered structured output article"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/2026/07/image-1.png> ;
    schema:about :threeSO ;
    schema:isPartOf :sectionSteering .

:equianoPortraitImage a schema:ImageObject ;
    schema:name "Olaudah Equiano, 1745-1797"@en ;
    schema:caption "Olaudah Equiano, 1745-1797"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/2026/07/RR7BJ25EX5E6HFVPZLKCUK7L6I.png> ;
    schema:about <http://dbpedia.org/resource/Olaudah_Equiano> ;
    schema:isPartOf :sectionChunking .

:equianoKgVizImage a schema:ImageObject ;
    schema:caption "Gustavus Vassa the central hub, Robert King's Montserrat circle branching off (Equiano, Nancy, Doran, Farmer), the John Annis and Phipps-expedition clusters in their own colors, and the separate Equiano node that betrays the aliasing"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/2026/07/equiano_kg.png> ;
    schema:about :aliasingProblem ;
    schema:isPartOf :sectionSeeingIt .

:racehorseProgressImage a schema:ImageObject ;
    schema:caption "Detail from 'Racehorse progress', a painting by Gordon Frickers, inspired by the life and journeys of Olaudah Equiano"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/2026/07/Racehorse-progress-detail-14.10.17-IMG_15831-1.jpg> ;
    schema:creator <http://dbpedia.org/resource/Gordon_Frickers> ;
    schema:about <http://dbpedia.org/resource/Olaudah_Equiano> ;
    schema:isPartOf :sectionColdWater .

<https://www.linkedin.com/in/ucheogbuji/#this> schema:image :authorPhotoImage .
:authorPhotoImage a schema:ImageObject ;
    schema:name "Uche Ogbuji"@en ;
    schema:caption "Uche Ogbuji"@en ;
    schema:contentUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/size/w1200/2022/08/IMG_20200215_193319.jpg> ;
    schema:thumbnailUrl <https://storage.ghost.io/c/82/c0/82c0f4a1-bd31-487c-9c3e-cb9f780d0874/content/images/size/w150/2022/08/IMG_20200215_193319.jpg> ;
    schema:about <https://www.linkedin.com/in/ucheogbuji/#this> .

#################################################################
# FAQ
#################################################################

:faqSection a schema:FAQPage ;
    schema:mainEntity :q1, :q2, :q3, :q4, :q5, :q6, :q7, :q8, :q9, :q10, :q11, :q12 .

:q1 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "What is Onya?"@en ;
    schema:acceptedAnswer :a1 .
:a1 a schema:Answer ;
    schema:text "Onya is a knowledge graph model and format developed by Uche Ogbuji at Oori Data, in which nodes have IRI identifiers and assertions (properties or edges), and the graph is treated as a durable, mergeable system of record rather than a disposable computation."@en .

:q2 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "What is Onya Literate?"@en ;
    schema:acceptedAnswer :a2 .
:a2 a schema:Answer ;
    schema:text "Onya Literate is a Markdown-dialect serialization of an Onya graph: a docheader sets the document IRI and bases, '# NodeID [Type]' blocks declare nodes, and '* label: value' / '* label -> Target' lines declare properties and edges -- self-describing, diff-friendly, and directly parseable."@en .

:q3 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "Why does the article avoid cloud LLMs for knowledge graph extraction?"@en ;
    schema:acceptedAnswer :a3 .
:a3 a schema:Answer ;
    schema:text "Running inference locally through an OpenAI-compatible API keeps potentially private information on the author's own machine, works with any local stack (llama.cpp, vLLM, LM Studio, Ollama, oMLX), and demonstrates that a shelf of documents can become a knowledge graph without a frontier cloud model."@en .

:q4 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "Which local model does the author use, and why?"@en ;
    schema:acceptedAnswer :a4 .
:a4 a schema:Answer ;
    schema:text "Qwen 3.6 35B-A3B at 6-bit: a mixture-of-experts model with 35 billion total parameters but only ~3 billion active per token, giving near-larger-model quality at small-model speed. Entity extraction works with a tiny 4B model, but relationship extraction needed the larger model's heft."@en .

:q5 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "How does Onya solve the cross-chunk entity identity problem?"@en ;
    schema:acceptedAnswer :a5 .
:a5 a schema:Answer ;
    schema:text "Entity identity is an IRI minted from the entity's name under a stable nodebase, so the same name appearing in different document chunks resolves to the same node by construction; an explicit merge() operation then collapses duplicate occurrences under the spec's identity rules."@en .

:q6 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "What is the 'aliasing problem' described in the article?"@en ;
    schema:acceptedAnswer :a6 .
:a6 a schema:Answer ;
    schema:text "It is the identity problem of one real person appearing under different names: Olaudah Equiano is also called Gustavus Vassa in his own memoir's title, so chunks drawn from different life periods mint two distinct nodes that must be reconciled with an alias_of-style edge."@en .

:q7 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "How does the parse-and-repair loop work?"@en ;
    schema:acceptedAnswer :a7 .
:a7 a schema:Answer ;
    schema:text "The model's Onya Literate output is parsed with Onya's LiterateParser; on failure, the parser's own error message (naming the offending token and suggesting a fix) is fed back to the model as a repair prompt, for up to two retries, before the chunk is skipped."@en .

:q8 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "How are graph analytics results written back into the graph?"@en ;
    schema:acceptedAnswer :a8 .
:a8 a schema:Answer ;
    schema:text "After projecting to networkx and computing metrics like betweenness centrality and Louvain communities, the results are written back as typed, merge-safe Onya assertions using an explicit data contract (interp=ONYA_INTERP('number')), so the analysis becomes part of the persisted record rather than a disposable figure."@en .

:q9 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "What persistence layer does Onya use?"@en ;
    schema:acceptedAnswer :a9 .
:a9 a schema:Answer ;
    schema:text "Onya ships with a dependency-free SQLite backend by default, and a PostgreSQL backend for when you outgrow the laptop, both sharing the same merge semantics via store.put(doc_iri, graph, merge=True)."@en .

:q10 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "What limitations does the 'Cold Water' section acknowledge?"@en ;
    schema:acceptedAnswer :a10 .
:a10 a schema:Answer ;
    schema:text "The parse-and-repair loop is a statistical comfort, not a hard guarantee (two of ten chunks skipped on one run); extraction quality degrades with model size; the small local models can mistype entities (a ship or place tagged as a Person); and the networkx projection is deliberately lossy in its first version."@en .

:q11 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "What prompted this article?"@en ;
    schema:acceptedAnswer :a11 .
:a11 a schema:Answer ;
    schema:text "Laurent Picard's HackerNoon article 'Building Knowledge Graphs with Gemini', which walked through extracting entity/relationship graphs from entire books using Gemini's large context window; the author sets out to walk similar ground with local, private models instead."@en .

:q12 a schema:Question ; schema:isPartOf :faqSection ;
    schema:name "Why is the graph format named 'Onya'?"@en ;
    schema:acceptedAnswer :a12 .
:a12 a schema:Answer ;
    schema:text "'Onya' comes from the Igbo word 'onya' (web, snare, and by extension network); the expanded phrase 'onya uche' means a web of knowledge, reflecting the graph's role as a durable knowledge structure."@en .

#################################################################
# Glossary
#################################################################

:glossarySection a schema:DefinedTermSet ;
    skos:prefLabel "Onya article glossary"@en ;
    schema:hasDefinedTerm :termOnya, :termOnyaLiterate, :termWordLoom, :termOmlx, :term3SO,
        :termMerge, :termNodebase, :termBetweenness, :termLouvain, :termMoE,
        :termAliasing, :termDocheader .

:termOnya a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "Onya"@en ;
    schema:description "A knowledge graph model and format, from Igbo 'onya' (web, snare, network), designed so the graph itself is a durable, mergeable system of record."@en ;
    owl:sameAs <https://github.com/OoriData/Onya#this> .

:termOnyaLiterate a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "Onya Literate"@en ;
    schema:description "The Markdown-dialect serialization of an Onya graph, using docheader, node, property, and edge conventions that a language model can readily emit."@en ;
    owl:sameAs :onyaLiterateFormat .

:termWordLoom a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "WordLoom"@en ;
    schema:description "A TOML-based prompt file format that keeps LLM prompts separate from application code for review, diffing, and version control."@en ;
    owl:sameAs <https://github.com/OoriData/WordLoom/#this> .

:termOmlx a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "oMLX"@en ;
    schema:description "A native local inference server for Apple Silicon offering continuous batching and tiered KV caching, exposed through an OpenAI-compatible API."@en ;
    owl:sameAs <https://github.com/jundot/omlx#this> .

:term3SO a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "Schema-steered structured output (3SO)"@en ;
    schema:description "Constraining a model's sampler so structured output is guaranteed by construction, contrasted in the article with the parse-and-repair approach used for Onya Literate."@en ;
    owl:sameAs :threeSO .

:termMerge a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "merge()"@en ;
    schema:description "An explicit, on-demand Onya operation that collapses duplicate node occurrences accumulated from unioning multiple extraction passes, under the format's identity rules."@en .

:termNodebase a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "nodebase"@en ;
    schema:description "The stable IRI base declared in an Onya docheader against which node identifiers are resolved, making the same name in different chunks resolve to the same node."@en .

:termBetweenness a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "betweenness centrality"@en ;
    schema:description "A networkx graph metric measuring how often a node lies on shortest paths between other nodes; used in the article to identify the memoir's central figure."@en ;
    rdfs:seeAlso <http://dbpedia.org/resource/Betweenness_centrality> .

:termLouvain a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "Louvain community detection"@en ;
    schema:description "A networkx community-detection algorithm used to cluster the extracted social graph into groups such as the Montserrat trading circle and the Phipps expedition officers."@en ;
    rdfs:seeAlso <http://dbpedia.org/resource/Louvain_method> .

:termMoE a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "mixture-of-experts (MoE) model"@en ;
    schema:description "A model architecture, such as Qwen 3.6 35B-A3B, with a large total parameter count but only a small fraction active per token, trading memory for near-larger-model quality at small-model inference speed."@en ;
    rdfs:seeAlso <http://dbpedia.org/resource/Mixture_of_experts> .

:termAliasing a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "the aliasing problem"@en ;
    schema:description "The recurring identity challenge of one entity appearing under multiple names in a document, illustrated by Olaudah Equiano also being named Gustavus Vassa." ;
    owl:sameAs :aliasingProblem .

:termDocheader a schema:DefinedTerm ; schema:inDefinedTermSet :glossarySection ;
    schema:name "docheader"@en ;
    schema:description "The opening '# @docheader' block of an Onya Literate document, declaring the document IRI, nodebase, and schema against which the rest of the file resolves."@en .

#################################################################
# HowTo
#################################################################

:howtoSection a schema:HowTo ;
    schema:name "Extract and maintain a knowledge graph from a book with a local LLM and Onya"@en ;
    schema:step :step1, :step2, :step3, :step4, :step5, :step6, :step7 .

:step1 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 1 ;
    schema:name "Install Onya and point a client at your local model"@en ;
    schema:text "Run pip install 'onya[nx]>=0.4.2' matplotlib, then configure an OpenAI-compatible client (e.g. against oMLX) with your chosen local model, such as Qwen 3.6 35B-A3B for relationship-capable extraction."@en .

:step2 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 2 ;
    schema:name "Author extraction and repair prompts in a WordLoom file"@en ;
    schema:text "Write a kg-extract prompt instructing the model to emit Onya Literate for a document excerpt, and a kg-repair prompt that feeds a parser error back for correction, both kept in a separate .loom.toml file."@en .

:step3 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 3 ;
    schema:name "Chunk the source document"@en ;
    schema:text "Fetch the full text (e.g. from Project Gutenberg), trim non-narrative front matter, and split it into chunks sized to your model's honest local context window, such as ~12k tokens."@en .

:step4 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 4 ;
    schema:name "Extract each chunk with parse-and-repair"@en ;
    schema:text "For each chunk, render the kg-extract prompt, send it to the model, and attempt to parse the response with Onya's LiterateParser, feeding parser errors back through kg-repair for up to two retries before skipping a stubborn chunk."@en .

:step5 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 5 ;
    schema:name "Union chunk graphs and checkpoint to a store"@en ;
    schema:text "Union each parsed chunk graph into one accumulating Onya graph, then persist it to a SQLite (or PostgreSQL) store with merge=True so repeated runs union into what is already stored."@en .

:step6 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 6 ;
    schema:name "Project to networkx and compute analytics"@en ;
    schema:text "Project the graph via onya.serial.nx, isolate the giant connected component of interest, run networkx algorithms such as betweenness centrality and Louvain community detection, and write the results back into the graph as typed assertions."@en .

:step7 a schema:HowToStep ; schema:isPartOf :howtoSection ; schema:position 7 ;
    schema:name "Visualize and query the results"@en ;
    schema:text "Render the projected component with matplotlib, sizing and coloring nodes from the written-back analytics, or export Mermaid/Graphviz DOT from a Literate file via the Onya CLI for a quick structural look; query the persisted analytics directly through Onya's graph selector."@en .
