In government services, healthcare, finance and supply chains, a good answer is rarely one passage away. It depends on exact entities, numbers, dates and rules spread across documents and tables. Knowledge Augmented Generation (KAG), developed at Ant Group, attacks this with three ideas: a knowledge graph built against a schema that domain experts write, two-way links between that graph and the source text, and a solver that turns each question into a small program of retrieval, calculation and deduction steps. This article explains each part, works through an example, and weighs the cost.
Why KAG exists
Vector RAG, covered in embeddings, vector databases and RAG, ranks passages by embedding similarity. Similarity is a good proxy for topic, but not for the logical relevance a multi-step question needs. Ask "what is the combined inbound transfer amount of the companies that make components for Acme Motors' products?" and no single passage resembles the answer. The answer sits at the end of a chain: the company's products, their upstream components, the makers of those components, their transfers, and finally a sum. The KAG paper also points to what it calls knowledge logic, which embeddings handle poorly: numerical values, temporal relations and expert rules. "Transfers in the last 90 days above one million" is a filter, not a similarity.
GraphRAG, described in its own article, adds structure by letting an LLM extract entities and relationships from text. Because that extraction is open (OpenIE, with no fixed vocabulary of types or relations), the graph inherits the noise of free text: "Acme Motors", "ACME Motors Ltd" and "Acme" can become three nodes, and one chunk's "supplies" is another's "sells to". That is acceptable for summarising themes, which is what GraphRAG's community reports are for, but a weak base for exact multi-hop lookups and arithmetic.
KAG keeps the graph but constrains it. The paper by Liang et al. names five components:
- LLM-friendly knowledge representation. A layered model, called LLMFriSPG, that holds expert-defined knowledge, loosely extracted information and raw text in one graph.
- Mutual indexing. Every graph element points to the chunks it came from, and every chunk points to the elements extracted from it.
- Logical-form-guided hybrid reasoning. Questions become explicit plans whose steps mix graph lookup, text retrieval, calculation and LLM reasoning.
- Knowledge alignment with semantic reasoning. Extracted mentions are normalised and linked to concepts, so that variants of one entity collapse into one node.
- Model capability enhancement. Fine-tuning the models for the understanding, inference and generation tasks the framework relies on.
The implementation is open source under the Apache 2.0 licence and runs on OpenSPG, Ant Group's knowledge-graph engine (SPG stands for Semantic-enhanced Programmable Graph). The framework has two main parts, kg-builder and kg-solver; a third, kag-model, covers the models and was announced for gradual release.
Knowledge representation: three layers, one graph
KAG borrows the DIKW hierarchy (data, information, knowledge, wisdom) to organise what it stores:
- Data: raw chunks of the source documents, stored with their text and embeddings. In KAG schemas a chunk is itself a node type,
Chunk, so text and graph live in one store. - Information: entities and relations produced by schema-free extraction. Broad coverage, with some noise.
- Knowledge: entities, properties and relations that conform to the expert schema, plus concepts arranged in
isAhierarchies and rules that derive new facts.
The same entity type can be filled both ways: a Company may get its industry from a structured table and loosely typed facts from news articles. The solver trusts the layers differently, looking for exact answers in the knowledge layer first.
Mutual indexing makes the layers usable together. From a node, the solver can fetch and cite the passages that support it; from a chunk found by vector search, it can jump to the entities the chunk mentions and follow graph edges. Because chunks are first-class nodes, retrieval can also rank them by walking outwards from the entities a question mentions.
A schema in SPG
Schemas are written in OpenSPG's schema language and committed to the server before any data is loaded. The illustrative schema below follows the style of the supply-chain example that ships with KAG. It defines one concept type, two entity types, a relation with its own properties, and a derived property computed by a rule. Each name is followed by a display name in brackets; the bundled examples use Chinese there.
namespace SupplyChain
Industry(Industry): ConceptType
hypernymPredicate: isA
Product(Product): EntityType
properties:
belongToIndustry(Industry): Industry
hasSupplyChain(Upstream product): Product
constraint: MultiValue
Company(Company): EntityType
properties:
product(Products made): Product
constraint: MultiValue
totalTransInAmt(Total inbound transfers): Float
rule: [[
Define (s:Company)-[p:totalTransInAmt]->(o:Float) {
STRUCTURE {
(payer:Company)-[t:fundTrans]->(s)
}
CONSTRAINT {
total("sum of inbound transfers") = group(s).sum(t.transAmt)
o = total
}
}
]]
relations:
fundTrans(Funds transfer): Company
properties:
transDate(Transfer date): Text
transAmt(Amount): Integer
Three features matter. Concept types such as Industry form isA taxonomies that alignment uses to place mentions like "EV maker" under "Automotive". Typed properties and MultiValue constraints tell the extractor what shape each fact must have. And rules derive facts: totalTransInAmt is never extracted from text but computed from fundTrans edges, so a question about it becomes a lookup rather than LLM arithmetic over retrieved passages.
Building the graph with kg-builder
kg-builder is a chain of components, each named in the project's kag_config.yaml. A typical chain for unstructured documents looks like this:
| Stage | What it does | Components in the config |
|---|---|---|
| Scanner and reader | Finds input files and turns text, PDF, Word, Markdown or records into documents | file_scanner, txt_reader and other readers |
| Splitter | Cuts documents into chunks by length, pattern, outline or meaning | length_splitter with split_length and window_length |
| Extractor | Schema-constrained extraction fills the schema's types and properties; schema-free extraction runs entity recognition, standardisation and triple prompts | schema_constraint_extractor, schema_free_extractor |
| Vectorizer | Embeds chunks and node descriptions for vector and hybrid search | batch_vectorizer |
| Post-processor | Aligns extracted nodes with each other and with an injected domain graph | kag_post_processor |
| Writer | Writes nodes, edges and chunk links to the OpenSPG store | kg_writer |
Extraction is where cost and quality are decided. The schema-free path makes several LLM calls per chunk: named-entity recognition, standardisation of each entity's name and type, then extraction of triples between the entities. KAG's examples configure a cheaper model for this work (openie_llm) and a stronger one for answering (chat_llm), which is a sensible default. Structured sources do not need an LLM at all: the supply-chain example ships its companies, products and transfers as CSV files that map directly onto schema types.
Alignment is KAG's answer to extraction noise. Each extracted mention is matched against existing nodes by name and embedding similarity, and placed in concept hierarchies through relations such as isA. Version 0.6 added domain knowledge injection: you first load an expert-curated graph of terms, and the post-processor links new extractions to it, so the graph converges on the experts' vocabulary rather than on whichever wording appeared first.
Build cost grows with the corpus. Version 0.7 introduced a lightweight build mode that, according to the project, cuts the token cost of knowledge construction by 89%. Version 0.8 added index types alongside the graph (Outline, Summary, KnowledgeUnit, AtomicQuery, Chunk and Table), so you can build only the indexes your questions need.
The logical-form solver
kg-solver answers in three stages: plan, execute, generate. The planner prompts an LLM to break the question into atomic steps, each written as a logical form: a function call in a small language with four operators.
- Retrieval(s, p, o) looks up subject–predicate–object patterns. Variables bound in one step are reused in later ones, so a multi-hop question becomes a chain of one-hop lookups.
- Math(content, target) computes: sums, counts, comparisons and sorting.
- Deduce(op, content, target) reasons over earlier results, with
opset to judgement, entailment, extract, choice or multiChoice. - Output(…) names the variables that hold the answer.
For the question from the first section, a plan in KAG's Step and Action format looks like this:
Question: What is the combined inbound transfer amount of the companies
that make the components of Acme Motors' products?
Step1: Which products does Acme Motors make?
Action1: Retrieval(s=s1:Company[`Acme Motors`], p=p1:product, o=o1:Product)
Step2: Which upstream products do those products depend on?
Action2: Retrieval(s=o1, p=p2:hasSupplyChain, o=o2:Product)
Step3: Which companies make those upstream products?
Action3: Retrieval(s=s3:Company, p=p3:product, o=o2)
Step4: What are those companies' total inbound transfers?
Action4: Retrieval(s=s3, p=p4:totalTransInAmt, o=o4:Float)
Step5: What is the sum of those amounts?
Action5: Math(content=[`o4`], target=`Sum the inbound transfer amounts`)->math5
Step6: Output the result
Action6: Output(math5)
Each step is small enough to check: step 3 reuses o2 and binds s3, step 4 reads the rule-derived property, and step 5 goes to code.
The executor runs each step with the most reliable method available. Retrieval goes to a hybrid executor that queries three retrievers, giving priority to the most trusted: kg_cs, an exact one-hop lookup on the schema-constrained graph after linking the step's entity names to nodes; kg_fr, fuzzy one-hop matching on the open-information layer plus chunks ranked by personalised PageRank from the linked entities; and rc, vector search over raw chunks. A merger combines what they return. Math is executed as Python that the LLM writes for the step, so sums are computed rather than guessed, and Deduce is an LLM call over the step's inputs. A generator then writes the answer and attaches references to the chunks behind each fact, which mutual indexing makes cheap.
Static planning (kag_static_pipeline) writes the whole plan up front, which is fast and easy to inspect. Iterative planning (kag_iterative_pipeline) plans one step at a time and looks at each result before choosing the next, which suits questions whose shape depends on intermediate answers but costs more LLM calls. Both arrived with the solver refactor in version 0.7.
Hands-on: running KAG
KAG has two moving parts: the OpenSPG server, a Java service that the official Docker Compose file runs alongside MySQL, Neo4j and MinIO, and the Python KAG package. Start the server first:
curl -sSL https://raw.githubusercontent.com/OpenSPG/openspg/refs/heads/master/dev/release/docker-compose-west.yml \
-o docker-compose-west.yml
docker compose -f docker-compose-west.yml up -d
# Product UI: http://127.0.0.1:8887 (change the default login from the README)
Product mode works entirely in the browser. Developer mode exposes the pipelines as code and configuration, and every example project has the same layout: kag_config.yaml for models and pipelines, schema/ for the SPG schema, builder/ with the data and an indexer.py, and solver/ with a qa.py that loads the solver pipeline from the config and calls it.
git clone https://github.com/OpenSPG/KAG.git
cd KAG
python3 -m venv .venv && source .venv/bin/activate # Python 3.10 or later
pip install -e .
cd kag/examples/domain_kg
knext project restore --host_addr http://127.0.0.1:8887 --proj_path .
knext schema commit # upload schema/*.schema
cd builder && python injection.py && python indexer.py && cd ..
cd solver && python qa.py && cd .. # ask the example questions
The configuration is long but mostly wiring. You change the model endpoints (any OpenAI-compatible API) and the solver first:
openie_llm: &openie_llm # many calls during extraction: a small, fast model
type: maas
base_url: https://your-provider.example/v1
api_key: YOUR_KEY
model: your-extraction-model
chat_llm: &chat_llm # planning, deduction and answers
type: maas
base_url: https://your-provider.example/v1
api_key: YOUR_KEY
model: your-reasoning-model
vectorize_model: &vectorize_model
type: openai
base_url: https://your-provider.example/v1
api_key: YOUR_KEY
model: BAAI/bge-m3
vector_dimensions: 1024
# ... builder pipeline and executor definitions trimmed ...
kag_solver_pipeline:
type: kag_static_pipeline
planner:
type: lf_kag_static_planner
executors:
- *kag_hybrid_executor_conf # Retrieval: kg_cs, kg_fr, rc
- *py_code_based_math_executor_conf
- *kag_deduce_executor_conf
- *kag_output_executor_conf
generator:
type: llm_index_generator
Expect to adjust prompts too: the bundled examples default to Chinese, and the planner's few-shot examples work best when they resemble your questions.
Borrowing the pattern without the framework
If operating OpenSPG is too heavy, the central idea still transfers: the LLM writes a plan in a small, checkable language, deterministic code executes it against structured data, and text retrieval is the fallback where that data runs out. The toy executor below runs the plan from the previous section over seven triples.
# A toy logical-form executor over an in-memory triple store.
# It shows the idea only; KAG's solver is far richer.
TRIPLES = [
("Acme Motors", "product", "Electric van"),
("Electric van", "hasSupplyChain", "Battery pack"),
("Electric van", "hasSupplyChain", "Drive motor"),
("Volt Cells", "product", "Battery pack"),
("Ferro Drives", "product", "Drive motor"),
("Volt Cells", "totalTransInAmt", 4_200_000),
("Ferro Drives", "totalTransInAmt", 1_900_000),
]
def resolve(term, env):
"""'$x' -> values bound by an earlier step, '?x' -> unbound, else a constant."""
if term.startswith("$"):
return env[term[1:]]
return None if term.startswith("?") else [term]
def retrieval(s, p, o, env):
subjects, objects = resolve(s, env), resolve(o, env)
hits = [(ts, to) for ts, tp, to in TRIPLES
if tp == p
and (subjects is None or ts in subjects)
and (objects is None or to in objects)]
if not hits:
# KAG would fall back to fuzzy graph and text-chunk retrieval here
raise LookupError(f"no graph facts for {s} {p} {o}")
if s.startswith("?"):
env[s[1:]] = [ts for ts, _ in hits]
if o.startswith("?"):
env[o[1:]] = [to for _, to in hits]
plan = [
("Retrieval", "Acme Motors", "product", "?o1"),
("Retrieval", "$o1", "hasSupplyChain", "?o2"),
("Retrieval", "?s3", "product", "$o2"),
("Retrieval", "$s3", "totalTransInAmt", "?o4"),
("Math", "math5", sum, "$o4"),
("Output", "$math5"),
]
def run(plan):
env = {}
for op, *args in plan:
if op == "Retrieval":
retrieval(*args, env)
elif op == "Math":
name, fn, source = args
env[name] = fn(resolve(source, env))
elif op == "Output":
return resolve(args[0], env), env
answer, env = run(plan)
print(env["s3"], answer) # ['Volt Cells', 'Ferro Drives'] 6100000
In a real system the plan comes from an LLM prompted with the operator definitions and a few worked examples (KAG's own planner prompt is a good template), the triples live in PostgreSQL or a graph database, and the LookupError branch triggers chunk retrieval as described in the RAG article. Validating each plan before running it (known predicates, variables bound before use, no unknown operators) catches most planner mistakes cheaply. Log every plan, and keep questions with expected plans and answers as evals.
Results and costs
On multi-hop question answering, the KAG paper reports relative F1 improvements of 19.6% on 2WikiMultiHopQA and 33.5% on HotpotQA over the state-of-the-art methods it compared against. It also describes deployments in Ant Group's E-Government and E-Health question answering, with clear gains in professionalism over RAG. These are the authors' numbers on public datasets; your questions, schema and models will decide your results. The repository's open_benchmark directory, which compares several RAG methods on the same base, is a better starting point than headline figures.
The costs are concrete:
- Schema design. Someone who knows the domain must define and maintain types, relations and rules. It is why KAG works, and why it is slower to start than vector RAG.
- Build cost. Several LLM calls per chunk at indexing time; the lightweight mode and a cheap extraction model help.
- Operations. A Java server with MySQL, Neo4j and MinIO to run, back up and upgrade, besides the Python pipelines.
- Latency. Planning, several retrieval steps and deduction mean more LLM calls per question than single-shot RAG.
- Ecosystem. Documentation is partly Chinese-first, and the release cadence has slowed: as of November 2026 the latest release is 0.8.0, from June 2025. Check the project's activity before you commit to it.
| Vector RAG | GraphRAG | KAG | |
|---|---|---|---|
| Structure | Chunks and embeddings | LLM-extracted open graph, communities and community reports | Schema-constrained graph plus open information, mutually indexed with chunks |
| Building it | Embed every chunk; cheap | LLM extraction and summarisation over the whole corpus; expensive | Expert schema, LLM extraction and alignment; expensive up front |
| Strongest at | Direct lookups and paraphrased questions | Corpus-wide themes and summaries | Multi-hop, numeric, temporal and rule-based questions with citations |
| Weakest at | Multi-hop chains and aggregation | Exact facts and arithmetic over noisy entities | Questions outside the schema; time to a first working system |
| Best fit | Most document question answering | Exploring large narrative collections | Regulated domains with stable entities and rules |
When KAG is the right choice
Reach for KAG, or for its plan-then-execute pattern, when most of these hold:
- Questions chain facts or compute over them: totals, comparisons, date windows, eligibility checks.
- The domain has a stable vocabulary of entities, relations and rules that experts can write down.
- Answers must be exact and auditable, with a trace of which facts and passages produced them.
- Part of the data is already structured, in tables or databases that map directly onto schema types.
Prefer vector RAG with hybrid search and a reranker for ordinary document question answering, and GraphRAG when the questions are about themes across a large unstructured corpus. A pragmatic path: start with hybrid retrieval, collect the questions it fails on, and if those are mostly multi-hop or numeric questions about a definable set of entities, build a schema for that slice. KAG need not be the whole system: since version 0.7 it supports MCP, so an agent can call it as one tool among others (see AI agents, tool calling and MCP).
Key takeaways
- KAG targets professional domains where answers depend on exact facts, numbers, dates and rules, which vector similarity and open extraction handle poorly.
- It stores schema-constrained knowledge, open information and raw chunks in one graph, with two-way links between graph elements and their source chunks.
- Alignment and domain knowledge injection fold variant mentions into canonical nodes, reducing the noise of open extraction.
- The solver plans questions as logical forms (Retrieval, Math, Deduce, Output) and runs each step with the most reliable method: exact graph lookup first, then fuzzy graph and text, with calculations executed as code.
- The price is schema design, extraction cost and an extra server to operate. The plan-then-execute pattern can be borrowed without the framework.
Sources and further reading
- Liang et al., KAG: Boosting LLMs in Professional Domains via Knowledge Augmented Generation (2024).
- OpenSPG/KAG: the framework, example projects and release notes.
- KAG's logical-form planning prompt, with the operator definitions and few-shot plans.
- OpenSPG/openspg: the knowledge-graph engine and server that KAG runs on.
- Edge et al., From Local to Global: A Graph RAG Approach to Query-Focused Summarization (2024): the GraphRAG paper.