KAG: Knowledge Augmented Generation with Schemas and Logical Forms

An engineering guide to KAG, Ant Group's framework for question answering in professional domains: how it models knowledge, builds the graph, plans questions as logical forms, and when it is worth the extra machinery.

11 min read Updated KAG knowledge graphs OpenSPG logical forms RAG

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 isA hierarchies 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.

KAG's layered knowledge representation with mutual indexing Three layers in one graph. Top, knowledge: schema-constrained nodes Acme Motors (a Company), Electric van (a Product) and Automotive (an Industry concept), linked by product and industry edges. Middle, information: open-extraction nodes Acme Motors Ltd, battery packs and Volt Cells, linked by sold-to and makes edges; Acme Motors Ltd is aligned with the Acme Motors node above it. Bottom, data: source chunks 12, 37 and 58, each linked in both directions to the node extracted from it. Knowledge schema-constrained types, concepts, rules Information schema-free extraction open triples, noisier Data raw text chunks Acme Motors Company Electric van Product Automotive Industry concept product industry Acme Motors Ltd battery packs Volt Cells sold to makes aligned: same entity Chunk 12 supplier report Chunk 37 annual report Chunk 58 trade news mutual index (both ways) alignment
One graph, three layers. Schema-constrained knowledge sits on top, open extraction adds broader but noisier information, alignment links variant mentions to canonical nodes, and every extracted element keeps two-way links to the chunks it came from.

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:

StageWhat it doesComponents in the config
Scanner and readerFinds input files and turns text, PDF, Word, Markdown or records into documentsfile_scanner, txt_reader and other readers
SplitterCuts documents into chunks by length, pattern, outline or meaninglength_splitter with split_length and window_length
ExtractorSchema-constrained extraction fills the schema's types and properties; schema-free extraction runs entity recognition, standardisation and triple promptsschema_constraint_extractor, schema_free_extractor
VectorizerEmbeds chunks and node descriptions for vector and hybrid searchbatch_vectorizer
Post-processorAligns extracted nodes with each other and with an injected domain graphkag_post_processor
WriterWrites nodes, edges and chunk links to the OpenSPG storekg_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 op set 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.

How kg-solver answers a question The question goes to a planner that writes atomic steps as logical forms. Each step goes to the executor for its operator. Retrieval queries the schema-constrained graph exactly (kg_cs), then the open-information layer with fuzzy matching and personalised-PageRank chunk ranking (kg_fr), then raw chunks by vector search (rc). Math runs Python written by the LLM, Deduce is an LLM reasoning step, and Output selects the answer variables. All results go to a generator that writes the answer with references. In iterative mode, step results feed back into the planner. Question Planner atomic steps as logical forms Retrieval 1. kg_cs: exact graph lookup 2. kg_fr: fuzzy graph + PPR 3. rc: vector chunk search Math LLM-written Python Deduce LLM reasoning step Output selected variables Generator answer with references iterative mode: each result shapes the next step
kg-solver turns a question into logical-form steps and sends each to the executor for its operator. Retrieval queries the knowledge, information and chunk layers in order of trust; calculations run as code. In iterative mode, results feed back into planning.

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 RAGGraphRAGKAG
StructureChunks and embeddingsLLM-extracted open graph, communities and community reportsSchema-constrained graph plus open information, mutually indexed with chunks
Building itEmbed every chunk; cheapLLM extraction and summarisation over the whole corpus; expensiveExpert schema, LLM extraction and alignment; expensive up front
Strongest atDirect lookups and paraphrased questionsCorpus-wide themes and summariesMulti-hop, numeric, temporal and rule-based questions with citations
Weakest atMulti-hop chains and aggregationExact facts and arithmetic over noisy entitiesQuestions outside the schema; time to a first working system
Best fitMost document question answeringExploring large narrative collectionsRegulated 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

  1. Liang et al., KAG: Boosting LLMs in Professional Domains via Knowledge Augmented Generation (2024).
  2. OpenSPG/KAG: the framework, example projects and release notes.
  3. KAG's logical-form planning prompt, with the operator definitions and few-shot plans.
  4. OpenSPG/openspg: the knowledge-graph engine and server that KAG runs on.
  5. Edge et al., From Local to Global: A Graph RAG Approach to Query-Focused Summarization (2024): the GraphRAG paper.