The Core Problem
We keep feeding books, papers, and manuals into LLMs as if they were search engines. The result is well-known: plausible answers, but inconsistent in domains where causality and contradiction matter. The error isn’t the model. It’s how we give it knowledge.
An LLM — including state-of-the-art models like Qwen3.6-27B — reads sequences. It doesn’t understand causality, dependency, or contextual contradiction. If page 12 of a pharmacology manual states “Drug A is safe for hypertension” and page 84 clarifies “except in grade 3 renal failure,” the model averages both statements. In domains like clinical practice, software engineering, or financial regulation, averaging is not an option.
What we’ve tested — and works in production environments with audit requirements — is inverting the approach: instead of feeding raw text to the model, we first provide it with structured grounding.
And we build that structured grounding on Apache Jena, not a proprietary graph database. The reason: RDF/SPARQL standards guarantee traceability, interoperability across systems, and auditability without vendor lock-in.
Why Qwen3.6 + Apache Jena: The Structured Tandem
Before detailing the flow, it’s worth justifying the choice of both components:
ComponentRoleKey AdvantageQwen3.6-27BStructured extractionThinking mode for causal relationships, guaranteed JSON via response_formatApache JenaGraph storage and queryW3C standards (RDF, SPARQL, OWL), native auditability, rule-based inference
Why Apache Jena instead of Neo4j:
- Legal traceability: Each RDF triple can carry metadata (provenance, source document, page, extraction date) as part of the model.
- Native inference: You can define OWL or Jena Rules to derive implicit relationships without modifying extracted data.
- SPARQL: Standard query language, not vendor-dependent. Any audit system can query the graph.
- Flexible persistence: TDB2 (native store) integrates with Fuseki to serve the graph as a service.
In internal tests, Jena with TDB2 handles graphs of up to 100 million triples with sub-second SPARQL queries — more than sufficient for clinical or regulatory domains.
From Word Cloud to RDF Graph: A Mechanical Flow on Standards
Classic RAG retrieves chunks by semantic similarity (cosine, BM25, etc.) and injects them into the prompt. This maintains the original problem: contradictions get averaged. We don’t retrieve chunks. We retrieve RDF triples from a Jena model.
1. Lightweight Ontology in RDFS/OWL (No need to model everything from day one)
We define classes and properties in Turtle (TTL). Example:
turtle
@prefix : <http://example.com/pharmacology#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
# Classes
: Disease a owl:Class .
:Drug a owl:Class .
:Contraindication a owl:Class .
:MechanismOfAction a owl:Class .
# Properties
:treats a owl:ObjectProperty ;
rdfs:domain :Drug ;
rdfs:range :Disease .
:contraindicates a owl:ObjectProperty ;
rdfs:domain :Drug ;
rdfs:range :Contraindication .
:requires a owl:ObjectProperty .
Complex OWL isn’t needed from day one. RDFS is sufficient to start. Jena allows adding inference rules later.
2. Extraction and Mapping with Qwen3.6 (Forcing JSON → RDF)
Qwen3.6 extracts triples as JSON. We then convert them to RDF using Jena’s API.
Extraction with Qwen3.6:
python
from openai import OpenAI
import json
from rdflib import Graph, URIRef, Literal
from rdflib.namespace import RDF, RDFS
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
response = client.chat.completions.create(
model="qwen3.6-27b",
messages=[
{
"role": "system",
"content": """You are a medical knowledge extractor. Extract triples (subject, predicate, object).
Use the namespace <http://example.com/pharmacology#>.
Allowed predicates: treats, contraindicates, causes, requires.
Return ONLY JSON: {"triples": [{"s": str, "p": str, "o": str}]}"""
},
{
"role": "user",
"content": f"Text: {chunk_text}"
}
],
response_format={"type": "json_object"}
)
triples_data = json.loads(response.choices[0].message.content)
Mapping to RDF using rdflib (Apache Jena compatible):
python
from rdflib import Graph, URIRef, Literal
g = Graph()
namespace = "http://example.com/pharmacology#"
for triple in triples_data["triples"]:
s = URIRef(namespace + triple["s"].replace(" ", "_"))
p = URIRef(namespace + triple["p"])
o = URIRef(namespace + triple["o"].replace(" ", "_"))
g.add((s, p, o))
# Optional: add provenance metadata
from rdflib.namespace import DCTERMS
g.add((s, DCTERMS.source, Literal(source_document)))
g.add((s, DCTERMS.created, Literal(extraction_date)))
Critical advantage: The resulting Jena graph is standard RDF. You can load it into Apache Jena Fuseki and expose it via a SPARQL endpoint.
3. RDF Graph in Apache Jena TDB2 (Nodes = URIs, Edges = Properties)
We load the RDF triples into Jena TDB2 (persistent native store):
bash
# Create a TDB2 dataset from the RDF file
tdbloader --loc=/data/pharmacology/ dataset.nt
Or using rdflib (which can serialize to NTriples and then import to Jena):
python
g.serialize(destination="pharmacology.nt", format="nt")
Concrete example of what Jena stores:
turtle
@prefix : <http://example.com/pharmacology#> .
:NSAID :treats :Arthrosis .
:NSAID :contraindicates :AcuteRenalFailure .
:AcuteRenalFailure :requires :CreatinineDosage .
No LLM needs to “remember” that relationship. It’s in the Jena graph as traceable triples.
What the graph reveals: Relationships that were pages apart in the text become directly connected in the RDF model.
4. Automatic Q&A Generation with SPARQL (Traversing Standard Paths)
The critical step: traverse the graph using SPARQL 1.1 and formulate questions using templates over the results.
Example SPARQL query:
sparql
PREFIX : <http://example.com/pharmacology#>
SELECT ?drug WHERE {
?drug :treats :DiabeticNephropathy .
?drug :contraindicates :RenalArteryStenosis .
}
Automatic question generation from the query:
- Detect variables and patterns: ?drug :treats :X → “drugs that treat X”
- ?drug :contraindicates :Y → “but are contraindicated in Y”
- Compose template: “What {type} {positive_action} but {negative_action}?”
Result: “What drugs treat diabetic nephropathy but are contraindicated in renal artery stenosis?”
Answer: Execute the SPARQL query against Jena (via Fuseki endpoint or direct API) and get the list of drug URIs. The answer is not guessed. It’s topologically queried.
5. Fine-tuning or Structured RAG on the Generated Dataset
With questions generated from SPARQL and answers obtained from the graph, we build a clean, traceable dataset.
Option A: Fine-tuning Qwen3.6 with curated data from the Jena graph
Use Llama-Factory or Qwen’s native training. Alpaca format:
json
{
"instruction": "You are a pharmacology expert. Answer based on the RDF knowledge graph.",
"input": "What drugs treat diabetic nephropathy but are contraindicated in renal artery stenosis?",
"output": "Enalapril, lisinopril, and ramipril (ACE inhibitors) treat diabetic nephropathy but are contraindicated in bilateral renal artery stenosis."
}
Option B: Structured RAG with real-time SPARQL queries
No fine-tuning needed. For each query:
- Parse the question into a SPARQL query (you can use Qwen3.6 for NL→SPARQL)
- Execute SPARQL against Apache Jena Fuseki
- Provide the results as structured context (not text chunks) to Qwen3.6 for final answer formulation.
Example prompt in this mode:
text
Results of the SPARQL query to the Jena graph:
- Enalapril (source: ESC 2024 guideline, page 15)
- Lisinopril (source: ESC 2024 guideline, page 15)
- Ramipril (source: ESC 2024 guideline, page 112)
Original question: What drugs treat diabetic nephropathy but are contraindicated in renal artery stenosis?
Answer in natural language, citing the sources.
Traceability and Governance: The Standard Changes Everything
With Apache Jena, traceability isn’t an add-on. It’s part of the model.
Each RDF triple can carry explicit provenance using standard vocabularies like PROV-O or DCTERMS:
turtle
:triple_123 :extracted_from "ESC_2024_guideline.pdf" ;
:page "15" ;
:extracted_by "Qwen3.6-27B" ;
:extraction_date "2026-04-15" .
This changes the conversation with audit and compliance teams:
- You can run SPARQL to list all statements extracted from a specific document.
- You can delete a source document, and Jena lets you remove all its associated triples (named graphs).
- There’s no black box. The LLM only answers from what’s in the graph.
Correction at source: You detect an error (e.g., “NSAID should be CONTRAINDICATES, not TREATS”). Modify the triple in Jena with a SPARQL DELETE/INSERT update. Regenerate the QA dataset. Retrain the fine-tuned model. The system is corrected in minutes, not weeks.
Real Example: Clinical Guidelines with Jena + Qwen3.6
Domain: Cardiovascular pharmacology. RDFS ontology: Disease, Drug, Contraindication, MechanismOfAction. Extractor model: Qwen3.6-27B with thinking mode enabled. Store: Apache Jena TDB2 + Fuseki (SPARQL endpoint).
Processing: 200 pages of ESC guidelines. ~4,500 RDF triples extracted in ~50 minutes (A10 GPU).
Concrete example that appeared in the graph:
Document 1 (page 15): :ACEI :treats :DiabeticNephropathy .
Document 2 (page 112): :ACEI :contraindicates :RenalArteryStenosis .
Automatically generated SPARQL query:
sparql
PREFIX : <http://example.com/pharmacology#>
SELECT ?drug WHERE {
?drug :treats :DiabeticNephropathy .
?drug :contraindicates :RenalArteryStenosis .
}
Graph answer: :Enalapril, :Lisinopril, :Ramipril.
Generated question: “Which ACE inhibitors are recommended for diabetic nephropathy but should be avoided in bilateral renal artery stenosis?”
Final fine-tuning: Qwen3.6-27B fine-tuned with 10k (question → answer) pairs generated from SPARQL query patterns. Accuracy on similar questions: 95.2% traceable to specific triples in the Jena graph.
Apache Jena vs. Neo4j for This Use Case
When comparing Apache Jena (based on RDF standards) with Neo4j (based on Cypher) for building structured grounding on knowledge graphs, the differences are significant:
Regarding standards: Apache Jena relies on W3C standards (RDF, SPARQL, OWL), while Neo4j uses a proprietary language (Cypher), although its core is open source.
Native traceability: Jena offers traceability out of the box through named graphs and provenance metadata. In Neo4j, traceability is limited and requires ad-hoc modeling.
Inference capabilities: Jena natively supports inference with Jena Rules, OWL, and RDFS. Neo4j requires the Graph Data Science plugin (paid) for equivalent functionality.
Query language: Jena uses SPARQL 1.1 (an ISO standard), while Neo4j uses Cypher, which is not an open standard.
External auditability: Any SPARQL-compatible tool can audit a Jena graph. For Neo4j, auditing is limited to Neo4j’s own ecosystem tools or third-party adapters.
Interoperability: Jena natively exports to standard formats such as RDF/XML, Turtle, N-Triples, and JSON-LD. Neo4j exports to CSV or JSON, but without native RDF support.
Data model: Jena uses the triple model (subject-predicate-object). Neo4j uses a labeled property graph, which is conceptually different.
Learning curve: Jena has a medium curve (SPARQL differs from SQL). Neo4j is more accessible for simple operations.
Final decision: For environments with audit requirements, long-term interoperability, and regulatory compliance, Apache Jena is superior. Neo4j is more comfortable for teams with little experience in semantic standards, but Jena ensures knowledge isn’t locked into a vendor.
Where Does Structure Outweigh Volume? (Updated)
Domains validated with Jena + Qwen3.6 in production:
- Clinical / Pharmacology: Cross-indications, drug-drug interactions.
- Financial regulation: Clause dependencies (e.g., Solvency II, MiFID).
- Requirements engineering: Traceability between functional requirements and tests.
- Maintenance manuals (aerospace): Causal chains “if A then B except when C”.
- Regulatory domains: Cross-referencing between versions of standards (ISO, IEC).
Real limits we still observe:
- Extraction with Qwen3.6 isn’t perfect: There’s about 8-10% noise (incorrectly extracted triples). But it can be filtered with SPARQL consistency queries (e.g., “list drugs that both treat and contraindicate the same condition” → likely an error).
- The bottleneck isn’t the LLM, it’s the graph: If the ontology is poorly designed (e.g., not distinguishing absolute vs. relative contraindication), generated questions will be ambiguous.
- SPARQL isn’t natural for clinical teams: That’s why you need the LLM layer to translate natural language to queries.
Conclusion (Operational, Standards-Based)
The future won’t be decided by who has the largest model, but by who builds better knowledge maps. And those maps must be traceable, interoperable, and auditable.
With Apache Jena + Qwen3.6 you build:
- Lightweight RDFS ontology (in Turtle, versionable in Git)
- Extraction with Qwen3.6 → JSON → mapping to RDF
- RDF graph in Jena TDB2 with explicit provenance
- QA generation by SPARQL (traversing the graph, not guessing)
- Fine-tuning or structured RAG on triples, not raw text
LLMs stop being “statistical guessers” and become response engines that rely on an auditable RDF substrate. Every answer has its path of triples in the graph. Every triple has its source document.
“We don’t sell ‘AI that knows more.’ We sell systems that explain why they know what they know.”
In which domain do you need traceability with standards, not just a graph database? That’s the signal to build on Jena.



