PDF 和 docx 输入需分别安装 [pdf]、[docx] 额外依赖,graph-serve 需 [web];生成新图必须配置可用的 OpenAI 兼容端点,仅浏览样例图不需要;graph-serve 默认绑定 127.0.0.1:8008 且无身份认证。
ai-knowledge-graph
robert-mcdermottApache-2.0活跃维护用大模型从文档里自动抽取实体和关系,生成可交互的知识图谱,省去手工梳理和建模的麻烦。
AI Powered Knowledge Graph Generator
git clone https://github.com/robert-mcdermott/ai-knowledge-graph.git
cd ai-knowledge-graph
uv sync --extra webREADME 未说明显存要求;需 Python 3.11+ 和一个 OpenAI 兼容的 LLM 端点(本地 Ollama 即可),浏览仓库自带样例图无需模型。
用 LLM 从文本抽取主谓宾三元组并生成可交互知识图谱,支持问答与本地 Web 界面
用大模型从文档里自动抽取实体和关系,生成可交互的知识图谱,省去手工梳理和建模的麻烦。
核心能力
- 用 LLM 抽取带实体类型的主谓宾三元组并保留来源句
- 按句边界分块并重叠、并行抽取长文档
- 合并大小写、停用词和复数变体做实体标准化
- 保守可追溯的关系推断,每条推断边标注方法
- 导出 JSON、CSV、GraphML 和 Neo4j Cypher 脚本
容易踩的坑
- v0.8.0最新正式版2026-09-12 · 2026-09-12 20:39 UTC
Release notes
Two new optional ways to use the tool, a set of live examples you can open right now, and a round of quality fixes. The command-line workflow from 0.7 is unchanged: text in, self-contained HTML out.
Live examples: https://robert-mcdermott.github.io/ai-knowledge-graph/
Upgrading from 0.7
uv syncorpip install -e .as before. Add the[web]extra for the web interface:uv sync --extra weborpip install -e ".[web]".- No config changes are required; every new key has a default. New optional sections:
[query]forgraph-chat, andtheme,edge_labels,title_case,collapse_parallel_edgesunder[visualization]. - Runs now also write
<name>.meta.jsonnext to the JSON (community names). Old JSON files still work with--from-json. - If you use the package as a library, pipeline output now goes through
logginginstead ofprint; callknowledge_graph.logging_utils.configure_logging()to see it.
What's new
Ask your graph:
graph-chatgraph-chat knowledge_graph.json "How did the steam engine change cities?" graph-chat knowledge_graph.json # interactive, remembers the last few exchangesAnswers come only from the facts in the graph. Each reply cites the relationships it used, extracted ones with their source sentence, inferred ones with their method, and it says so when the graph has no answer.
--jsonfor scripting.A local web interface:
graph-servegraph-serve --config config.toml --graphs data/samples --openBrowse a library of graphs, explore each one in the same page as the static output, and ask questions in an Ask panel whose cited facts jump to the node. A New graph form pastes text or uploads files, shows the pipeline's progress, and opens the result; it writes the same files as the CLI. Localhost only, no accounts.
Sample corpus
Five texts and their verified graphs ship in
data/samples/: the Industrial Revolutions, Marie Curie, the Apollo program, coffee from farm to cup, and La Alhambra in Spanish. They render instantly with--from-json, populategraph-servewithout a model, back the GitHub Pages examples, and act as regression fixtures.Explorer
- Parallel edges collapse into one edge with a "+N" badge; every predicate stays in the tooltip and the details panel.
- Entity names display title-cased ("Steam Engine"); ids, search and exports are unchanged.
- Community names are stored with the graph, so they show without an LLM.
--library-pathpublishes many pages that share one copy of the graph library (used for the docs site).
Pipeline quality
- Community detection is stable across runs (it depended on set ordering before).
- Case variants left by LLM entity resolution are merged; inferred relationships are mapped onto existing entities and never introduce unknown ones.
- The taxonomy rule understands Romance and German phrases and predicates and skips people, places, organizations and dates.
- Console output:
--verboseand--quiet; default output unchanged.
Docs
README rewritten around a four-step quick start, with current help output for all three commands.
Resolved issues
#6 (chat with the knowledge graph). The web interface requested in #22 is implemented as the optional
graph-servecommand.Notes
All 170 tests run without an LLM; CI covers Python 3.11 to 3.13. The sample graphs were generated with
deepseek-v4.1-flash; any OpenAI-compatible endpoint works for your own documents. - v0.7.02026-09-12 · 2026-09-12 07:08 UTC
查看更新说明
The biggest release since the project started: a hardened extraction pipeline, inference that adds knowledge instead of noise, and a completely new interactive graph explorer. The demo has been regenerated: https://robert-mcdermott.github.io/ai-knowledge-graph/
Upgrading from 0.6.x
- Install with
pip install -e .(oruv sync). The package is nowknowledge_graph; thegenerate-graphcommand is unchanged. - Python 3.11 or newer is required.
- Check your
config.tomlagainst the README:max_tokensnow defaults to 32768 (reasoning models need it),temperatureto 0.2, and rule-based transitive inference is off by default. New optional[extraction]section and[visualization]keys. - Old
.jsonoutputs still load with--from-json; new ones add entity types, source sentences and inference methods.
What's new
Extraction you can trust
- The LLM client detects truncated or empty answers, a common failure with reasoning models that spend their whole token budget thinking, and stops with an explanation instead of silently producing a near-empty graph. Retries with back-off, automatic
max_completion_tokensfallback for newer OpenAI models,env:VARAPI keys,reasoning_effort,json_modeandextra_bodypass-through. - Sentence-aware chunking, parallel chunk extraction, and an on-disk response cache: re-running the same document with the same settings makes no API calls.
- Every extracted relationship now carries entity types (person, organization, place, event, technology, product, work, date, concept) and the sentence it came from.
Inference that adds knowledge, not noise
- In testing, the old transitive and lexical rules generated about 70 % of all edges and hid the real ones. They are now opt-in and constrained.
- New LLM passes connect every isolated part of the graph to the main body and add well-known relationships between the most central entities ("internet enabled e-commerce"), plus a deterministic taxonomy rule ("quantum computing" is a "computing"). Inferred edges are capped relative to extracted ones and every one records its method.
- Communities are named by the LLM ("First Industrial Revolution Textiles", "Blockchain and Decentralized Finance").
A new explorer page
- One self-contained HTML file that works offline (PyVis is gone; vis-network is embedded).
- Click a node to highlight its neighbourhood and see every relationship with its source sentence; search with autocomplete; a shortest-path finder between any two entities; a communities panel with entity-type filters and a minimum-connections slider; edge labels for all edges, none, or only around the selection; stats, physics controls, PNG/JSON/CSV export, keyboard shortcuts, shareable
#node=links, light and dark themes, responsive layout. - Fixes the
network is not definederror, invalid vis.js options and dropped parallel edges of the old page; 20 community colours instead of 8.
More inputs and outputs
.txt,.md,.rst,.pdf(pip install "ai-knowledge-graph[pdf]") and.docx([docx]) inputs, multiple files or whole directories, andextraction.languagefor non-English documents.--export csv,graphml,cypher: GraphML for Gephi, yEd and Cytoscape, and an idempotent Neo4jMERGEscript.--from-jsonre-renders any saved graph without LLM calls;--continue-on-errorand--no-cachefor control over runs.
Under the hood
src/package layout, dependencies reduced tonetworkx,jinja2andrequests, 124 tests that need no LLM, ruff, and CI on Python 3.11–3.13.- README rewritten with a full configuration reference, provider examples and troubleshooting.
Resolved issues
#7 (non-English output), #9 (encoding errors and PDFs), #15 (gpt-5
max_completion_tokens), #17 (Gemini configuration), #18 (Neo4j export). #11 (readability) is addressed by the new explorer.Thanks
To everyone who filed issues and opened pull requests since 0.6; mo
- Install with
- 0.6.3v0.6.32025-12-28 · 2025-12-28 00:22 UTC
查看更新说明
What's Changed
- Introduce PromptFactory and modular prompts package by @Deepak-png981 in https://github.com/robert-mcdermott/ai-knowledge-graph/pull/19
New Contributors
- @Deepak-png981 made their first contribution in https://github.com/robert-mcdermott/ai-knowledge-graph/pull/19
Full Changelog: https://github.com/robert-mcdermott/ai-knowledge-graph/compare/0.6.2...0.6.3
- 0.6.2v0.6.22025-12-28 · 2025-12-28 00:02 UTC
查看更新说明
What's Changed
- added filter for inferred vs non-inferred predicates by @esteinholtz-cloudera in https://github.com/robert-mcdermott/ai-knowledge-graph/pull/13
- added utility tool to convert from JSON to HTML by @esteinholtz-cloudera in https://github.com/robert-mcdermott/ai-knowledge-graph/pull/14
New Contributors
- @esteinholtz-cloudera made their first contribution in https://github.com/robert-mcdermott/ai-knowledge-graph/pull/13
Full Changelog: https://github.com/robert-mcdermott/ai-knowledge-graph/compare/0.6.1...0.6.2
- 0.6.1v0.6.12025-05-12 · 2025-05-12 22:20 UTC
查看更新说明
- Fixed bug with encoding on Windows systems.
- See https://github.com/robert-mcdermott/ai-knowledge-graph/pull/5
What's Changed
- Fixed Issue #4 (charmap) issue on Windows systems by @robert-mcdermott in https://github.com/robert-mcdermott/ai-knowledge-graph/pull/5
Full Changelog: https://github.com/robert-mcdermott/ai-knowledge-graph/compare/0.6.0...0.6.1
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。

AI Powered Knowledge Graph Generator
This system takes an unstructured text document, and uses an LLM of your choice to extract knowledge in the form of Subject-Predicate-Object (SPO) triplets, and visualizes the relationships as an interactive knowledge graph. Optional companion commands let you ask the
graph questions (graph-chat) and run everything from a local web interface (graph-serve).
Live examples (no install needed): https://robert-mcdermott.github.io/ai-knowledge-graph/
| The Industrial Revolutions | Marie Curie | The Apollo Program |
| Coffee, from farm to cup | La Alhambra (Spanish) |
Features
- Any text input:
.txt,.md,.rst,.pdfand.docxfiles or whole directories, in any language (extraction.language); large documents are split on sentence boundaries with overlap and extracted in parallel - Typed knowledge extraction: the LLM returns Subject-Predicate-Object triples with entity types (person, organization, place, event, technology, product, work, date, concept) and every extracted relationship keeps the sentence it came from
- Entity standardization: case, stop-word and plural variants are merged, with an optional LLM pass for the rest
- Conservative, traceable inference: LLM passes bridge isolated parts of the graph and add well-known relationships between central entities; a deterministic taxonomy rule links specific terms to general ones; every inferred edge carries its method and is capped relative to the extracted edges
- Exports: JSON, CSV, GraphML for Gephi/yEd/Cytoscape and a Cypher script for Neo4j
- Chat with the graph (optional
graph-chatcommand): grounded, cited answers from the generated graph - Local web interface (optional
graph-servecommand): ingest, browse, explore and ask questions in the browser - Interactive explorer: a single self-contained HTML file with search, click-to-highlight, a relationships panel with sources, named communities, entity-type filters, a shortest-path finder, exports and light/dark themes
- Robust LLM client: truncation detection for reasoning models, retries with back-off, automatic
max_completion_tokensfallback, environment-variable API keys and an on-disk response cache - Works with any OpenAI-compatible endpoint: Ollama, LM Studio, vLLM, OpenAI, Gemini, OpenRouter, LiteLLM (which fronts AWS Bedrock, Azure OpenAI, Anthropic and many others)
Requirements
- Python 3.11+
- Core dependencies:
networkx,jinja2,requests(installed automatically) - Optional extras:
[web]forgraph-serve(FastAPI, uvicorn),[pdf]and[docx]for those input formats,[all]for everything - An OpenAI-compatible LLM endpoint for generating new graphs (a local Ollama works well); the sample graphs in this repository can be explored without one
Quick Start
1. Install
git clone https://github.com/robert-mcdermott/ai-knowledge-graph.git
cd ai-knowledge-graph
uv sync --extra web # or: pip install -e ".[web]"
uv sync creates the environment and the generate-graph, graph-chat and graph-serve commands; prefix
commands with uv run (as below) or activate the environment. With pip, drop the uv run prefix.
2. Explore the sample graphs (no LLM needed)
uv run graph-serve --config config.toml --graphs data/samples --open
This opens a library with the five sample graphs from data/samples/; click one to explore it. Browsing works
without a reachable model; the Ask panel and the New graph form use the model configured in
config.toml. To render a sample to a static page instead:
uv run generate-graph --from-json data/samples/marie-curie.json --output marie-curie.html
3. Generate a graph from your own text
Edit config.toml (model name, endpoint, API key; see Configuration), then:
uv run generate-graph --input your_text_file.txt --output knowledge_graph.html
That writes knowledge_graph.html (a self-contained interactive page), knowledge_graph.json (the triples)
and knowledge_graph.meta.json (community names). Keep personal settings in a file git ignores, such as
config-working.toml, and pass it with --config. From a checkout without installing, python generate-graph.py
works the same way.
4. Ask the graph questions
uv run graph-chat knowledge_graph.json "How did the steam engine change cities?"
Development
uv sync --extra dev --extra web # or: pip install -e ".[dev,web]"
uv run pytest -q # 170 tests, no LLM needed
uv run ruff check .
python scripts/build_docs.py # rebuild the GitHub Pages site from data/samples
Configuration
The system is configured with a TOML file (config.toml by default, or --config other.toml).
Every key except model and base_url is optional; the values shown are the defaults.
[llm]
model = "gemma3" # any model name your endpoint accepts
api_key = "sk-1234" # or "env:OPENAI_API_KEY" to read it from the environment
base_url = "http://localhost:11434/v1/chat/completions" # any OpenAI-compatible chat completions URL
max_tokens = 32768 # reasoning models need a large budget, see note below
temperature = 0.2 # omit for models that only accept the default (e.g. gpt-5)
#reasoning_effort = "low" # passed through to servers/models that support it
#timeout = 300 # seconds per request
#max_retries = 3 # retries on 429/5xx/connection errors
#token_param = "auto" # auto-switches to max_completion_tokens for newer OpenAI models
#json_mode = false # request response_format = json_object
#concurrency = 4 # chunks extracted in parallel
#cache_dir = ".kg-cache" # cache LLM replies; re-running the same input is free ("" disables, or --no-cache)
#[llm.extra_body] # arbitrary extra request fields, e.g. Ollama's think switch (keep last in [llm])
#think = false
#[llm.extra_headers] # extra HTTP headers, e.g. OpenRouter attribution
[extraction]
language = "auto" # e.g. "Chinese" to get entity names and predicates in Chinese
[chunking]
chunk_size = 500 # target words per chunk; chunks end on sentence boundaries
overlap = 50 # words of trailing sentences repeated in the next chunk
[standardization]
enabled = true # merge case / stop-word / plural variants of the same entity
use_llm_for_entities = true # extra LLM pass that groups remaining variants (one call)
#merge_word_subsets = false # aggressive merging by shared words ("steam engine factories" -> "steam engine")
[inference]
enabled = true # master switch
use_llm_for_inference = true # master switch for the LLM methods below
#llm_bridge = true # link every isolated component to the main graph
#max_bridge_components = 20 # ...for up to this many components, batched
#bridge_groups_per_call = 5
#llm_hub = true # well-known relationships among the most central entities
#hub_entities = 25
#hub_max_new = 25
#taxonomy = true # rule: "quantum computing" is a "computing" (deterministic)
apply_transitive = false # rule: A->B->C => A->C for transitive predicate families only
#transitive_predicate_groups = ["is a", "part of", "located in", "led to"]
#transitive_max_hub_degree = 10 # never chain through nodes with more connections than this
#transitive_max_per_subject = 5
#lexical = false # rule: "related to" edges for names sharing a word (noisy)
#lexical_min_word_length = 5
#max_inferred_ratio = 0.5 # cap inferred edges at this fraction of extracted edges
[query] # used by the optional graph-chat command
hops = 2 # neighbourhood radius around the entities a question mentions
max_triples = 150 # facts sent to the model per question
max_seed_entities = 8
history_turns = 3 # previous Q&A pairs kept for follow-up questions
[visualization]
edge_smooth = false # or "dynamic", "continuous", "curvedCW", ... (true = "continuous")
name_communities = true # ask the LLM for a short name per community (one call)
show_inferred = true # whether inferred (dashed) relationships are visible when the page opens
theme = "light" # initial theme: "light" or "dark"
edge_labels = "all" # initial edge label mode: "all", "selection" or "none"
title_case = true # show "Steam Engine" for the entity "steam engine" (ids are unchanged)
collapse_parallel_edges = true # draw one edge per node pair with a "+N" badge; all predicates stay in the tooltip
Rule-based inference (apply_transitive, lexical) is off by default because in testing it generated
roughly 70 % of all edges and hid the relationships actually found in the text. Local overrides such as
config-*.toml and config.local.toml are ignored by git.
Provider examples
# OpenAI
[llm]
model = "gpt-4.1-mini"
api_key = "env:OPENAI_API_KEY"
base_url = "https://api.openai.com/v1/chat/completions"
# Google Gemini (OpenAI-compatible endpoint)
[llm]
model = "gemini-2.5-flash"
api_key = "env:GEMINI_API_KEY"
base_url = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions"
# LM Studio / vLLM / LiteLLM proxy (any OpenAI-compatible server)
[llm]
model = "your-model-name"
api_key = "not-needed"
base_url = "http://localhost:1234/v1/chat/completions"
A note on reasoning models
Models such as DeepSeek, Qwen3, gpt-5 and the o-series "think" before they answer, and that
hidden reasoning is charged against max_tokens. With a small budget the model can spend
everything on reasoning and return no answer at all. The generator detects this
(finish_reason = "length") and aborts with an explanation instead of silently producing an
empty graph. Set max_tokens to 16k–32k for such models, lower reasoning_effort, use
smaller chunks, or pass --continue-on-error to skip failed chunks and accept an
incomplete graph.
Command Line Options
--input PATH [PATH ...]: Input file(s) or directories. Plain text (.txt,.md,.rst),.pdf(install the[pdf]extra) and.docx([docx]extra). With several inputs every triple is tagged with itsdocument--output FILE: Output HTML file for visualization (default: knowledge_graph.html)--config FILE: Path to config file (default: config.toml)--debug/--verbose: Show debug output, including raw LLM responses and standardization merges--quiet: Only warnings and errors on the console (the final summary is still printed)--no-standardize: Disable entity standardization--no-inference: Disable relationship inference--continue-on-error: Skip chunks whose LLM call fails or is truncated instead of aborting--from-json FILE: Re-render the visualization from a previously saved.jsontriples file (no LLM calls)--no-cache: Bypass the LLM response cache for this run--library-path DIR: Reference the vis-network library fromDIR(copied there if missing) instead of embedding it, so many pages can share one copy, for example on GitHub Pages--export FORMATS: Extra outputs next to the HTML, comma-separated:json(always),csv,graphml(Gephi, yEd, Cytoscape),cypher(Neo4jMERGEscript)--test: Generate sample visualization using test data
Usage messages (--help)
usage: generate-graph [-h] [--test] [--config CONFIG] [--output OUTPUT]
[--input PATH [PATH ...]] [--from-json FILE] [--debug]
[--quiet] [--no-standardize] [--no-inference]
[--continue-on-error] [--no-cache] [--export FORMATS]
[--library-path DIR]
Knowledge Graph Generator and Visualizer
options:
-h, --help show this help message and exit
--test Generate a test visualization with sample data
--config CONFIG Path to configuration file
--output OUTPUT Output HTML file path
--input PATH [PATH ...]
Input file(s) or directories: .txt/.md/.rst, .pdf
(needs [pdf] extra), .docx (needs [docx] extra).
Required unless --test or --from-json is used
--from-json FILE Render a visualization from a previously saved triples
JSON file (no LLM calls)
--debug, --verbose Enable debug output (raw LLM responses and extracted
JSON)
--quiet Only show warnings and errors
--no-standardize Disable entity standardization
--no-inference Disable relationship inference
--continue-on-error Skip chunks whose LLM call fails or is truncated
instead of aborting
--no-cache Do not read or write the LLM response cache
(llm.cache_dir)
--export FORMATS Comma-separated extra outputs written next to the
HTML: json (always), csv, graphml, cypher
--library-path DIR Reference the vis-network library from DIR (copied
there if missing) instead of embedding it; useful when
publishing many pages, e.g. on GitHub Pages
usage: graph-chat [-h] [--config CONFIG] [--json] [--no-facts] graph [question]
positional arguments:
graph Triples JSON written by generate-graph (e.g. knowledge_graph.json)
question Question to answer; omit for an interactive session
options:
--config CONFIG Path to configuration file (uses its [llm] section)
--json Print the result as JSON (single-question mode)
--no-facts Do not list the cited facts after the answer
usage: graph-serve [-h] [--config CONFIG] [--graphs DIR] [--host HOST] [--port PORT] [--open]
options:
--config CONFIG Path to configuration file (uses [llm], [query], [visualization])
--graphs DIR Directory containing the .json graphs written by generate-graph
--host HOST Bind address (default 127.0.0.1; the server has no authentication)
--port PORT Port (default 8008)
--open Open the library in your browser
Example Run
Command:
generate-graph --input data/industrial-revolution.txt --output industrial-revolution-kg.html
Console Output (gemma4 via Ollama, about 20 seconds; --quiet reduces this to the summary):
Using input text from file: data/industrial-revolution.txt
==================================================
PHASE 1: INITIAL TRIPLE EXTRACTION
==================================================
Processing text in 3 chunks (size: 500 words, overlap: 50 words)
Extracting with 3 parallel requests
Processing chunk 1/3 (490 words)
Processing chunk 2/3 (495 words)
Processing chunk 3/3 (76 words)
Chunk 1: 58 triples
Chunk 2: 79 triples
Chunk 3: 15 triples
Extracted a total of 152 triples from all chunks
==================================================
PHASE 2: ENTITY STANDARDIZATION
==================================================
Starting with 152 triples and 154 unique entities
Standardizing entity names across all triples...
Applied LLM-based entity standardization for 12 entity groups
Removed 7 self-referencing triples
Standardized 154 entities into 152 standard forms
After standardization: 145 triples and 133 unique entities
==================================================
PHASE 3: RELATIONSHIP INFERENCE
==================================================
Starting with 145 triples
Top 5 relationship types before inference:
- enabled: 11 occurrences
- used in: 8 occurrences
- altered: 8 occurrences
- invented: 7 occurrences
- involved: 7 occurrences
Inferring additional relationships between entities...
Identified 11 disconnected components in the graph
LLM bridging proposed 11 relationships for 10 isolated components
LLM hub enrichment proposed 25 relationships among the 25 most central entities
Within-community LLM inference proposed 10 relationships
Taxonomy rule proposed 3 relationships
Transitive inference proposed 2 relationships (skipped 3 paths through hub nodes)
Accepted 48 inferred relationships (llm_bridge: 11, llm_hub: 23, llm_within: 10, taxonomy: 2, transitive: 2); dropped 3 already-connected pairs
Top 5 relationship types after inference:
- enabled: 18 occurrences
- is a: 15 occurrences
- used in: 8 occurrences
- altered: 8 occurrences
- invented: 7 occurrences
Added 48 inferred relationships
Final knowledge graph: 189 triples
Saved raw knowledge graph data to industrial-revolution-kg.json
Processing 189 triples for visualization
Found 133 unique nodes
Found 48 inferred relationships
Detected 13 communities using Louvain method
Named 13 communities
Knowledge graph visualization saved to industrial-revolution-kg.html
Saved community names to industrial-revolution-kg.meta.json
Knowledge Graph Statistics:
Nodes: 133
Edges: 189
Communities: 13
To view the visualization, open the following file in your browser:
file:///path/to/industrial-revolution-kg.html
Chat with your graph (optional)
generate-graph is unchanged: text in, static HTML (and JSON) out. The optional graph-chat command
reads that JSON and answers questions from it, using the same [llm] settings:
uv run graph-chat knowledge_graph.json "How did the steam engine change cities?"
uv run graph-chat knowledge_graph.json # interactive session; Ctrl-D or an empty line quits
uv run graph-chat data/samples/apollo-program.json "Who flew on Apollo 13?"
For each question it finds the entities the question mentions, retrieves the surrounding subgraph (and the
shortest paths between the mentioned entities), and asks the model to answer only from those facts. The
reply lists the facts it relied on, each marked extracted (with the source sentence) or inferred
(with the method), so answers stay traceable to the document. If the graph does not contain an answer it
says so. Tuning lives under [query] in the config (hops, max_triples, max_seed_entities,
history_turns); --json prints the result as JSON for scripting.
Local web interface (optional)
graph-serve is a small local server over the same code: it lists the graphs in a directory, opens each one in
the explorer, and adds an Ask panel that answers questions from the graph with cited facts (click a fact to
jump to it in the graph). It needs the [web] extra (uv sync --extra web or pip install -e ".[web]").
Start it with the sample graphs ready to use:
uv run graph-serve --config config.toml --graphs data/samples --open
or point it at the directory where you write your own graphs (any --config file works, e.g. your local
config-working.toml):
uv run graph-serve --config config-working.toml --graphs ./out --open
The library page also has a New graph form: paste text or upload files (.txt, .md, .rst, .pdf,
.docx, several at once), watch the phases run, and land in the explorer when it finishes. It runs the same
pipeline as generate-graph and writes the same .json and .html into the graphs directory, so the result is
usable from the command line and graph-chat too. One generation runs at a time.
It binds to 127.0.0.1:8008 by default, has no accounts or authentication, and only reads and writes the graphs
directory, so it is meant for your own machine. Static HTML output is unchanged: the chat panel only exists in
served pages. --host 0.0.0.0 exposes it on your network if you put your own access control in front.
Sample corpus
data/samples/ contains five short texts and the graphs generated from them (.json triples plus a
.meta.json sidecar with the LLM-generated community names), so you can try everything without an LLM or
an API key. They are also the live examples on GitHub Pages.
| Sample | Domain | What it exercises | Live |
|---|---|---|---|
industrial-revolution.txt | technology history | the original demo text: technologies, people, eras | open |
marie-curie.txt | biography | people, places, dates, organizations, works | open |
apollo-program.txt | program history | events, missions, organizations, many people | open |
coffee-supply-chain.txt | process description | concepts, technologies, products, places | open |
la-alhambra.txt | Spanish text | extraction.language = "Spanish": entity names and predicates in Spanish | open |
uv run generate-graph --from-json data/samples/marie-curie.json --output marie-curie.html # renders instantly
uv run graph-serve --config config.toml --graphs data/samples --open # browse all five
uv run graph-chat data/samples/apollo-program.json "Who flew on Apollo 13?" # needs your LLM
The graphs were generated with deepseek-v4.1-flash (reasoning_effort = "low", temperature = 0.2,
use_llm_for_entities = false because that model reasons without end over long entity lists) and the default
inference settings. They double as regression fixtures: tests/test_samples.py checks that they stay
well-formed, typed, sourced and connected. Regenerate them when the extraction prompt changes, then rebuild the
GitHub Pages site with python scripts/build_docs.py (it writes docs/index.html, docs/samples/*.html and a
shared docs/vendor/ copy of vis-network, using --library-path style pages so each is about 100 KB).
Output files
Next to the HTML page the generator writes a JSON file with the same base name containing every triple, and a
.meta.json sidecar with the community names so that --from-json and graph-serve show them without an LLM:
{
"subject": "james watt",
"predicate": "refined",
"object": "steam engine",
"subject_type": "person",
"object_type": "technology",
"source": "A key catalyst of the First Industrial Revolution was the refinement of the steam engine by Scottish engineer James Watt, ...",
"chunk": 1
}
With --export graphml,cypher,csv the same graph is also written as GraphML (typed nodes with community
and degree, edges with provenance), an idempotent Neo4j Cypher script (:Entity plus a label per entity
type, one relationship type per predicate; run it with cypher-shell -f graph.cypher), and a flat CSV.
Inferred triples carry "inferred": true and a "method" (llm_bridge, llm_hub, llm_within,
taxonomy, transitive or lexical); transitive triples also record the intermediate node in "via".
The JSON can be re-rendered at any time with generate-graph --from-json graph.json --output graph.html
(or python json_to_html.py graph.json graph.html), and the page itself can export the visible triples as
JSON or CSV.
How It Works
generate-graph loads and validates the configuration (defaults applied, env: API keys resolved), reads the
input files (text encodings detected; PDF and DOCX through optional readers), and runs the pipeline below.
--from-json skips straight to the visualization step, and --test renders a small built-in graph.
- Chunking: The document is split into overlapping chunks on sentence boundaries to fit within the LLM's context window
- First Pass - SPO Extraction:
- Chunks are processed by the LLM in parallel (
llm.concurrency) to extract typed Subject-Predicate-Object triplets, each tagged with the sentence it came from - Results are collected across all chunks (and documents) to form the initial knowledge graph
- Chunks are processed by the LLM in parallel (
- Second Pass - Entity Standardization:
- Variants that differ only by case, stop-words or plural form are merged ("The Steam Engines" / "steam engine")
- Optional LLM-assisted entity alignment (
standardization.use_llm_for_entities, one call): the LLM reviews the most frequent entities and groups the ones that refer to the same concept (e.g., "AI", "artificial intelligence", "AI system") - Predicate tense variants are normalized ("involve" / "involves"), and self-referencing triples are dropped
- Third Pass - Relationship Inference (
inference.enabled):- LLM bridging links every isolated component of the graph to the main component, in batched calls
- LLM hub enrichment adds well-known relationships between the most central entities (e.g. "internet enabled e-commerce")
- LLM within-component inference connects lexically related but unconnected pairs
- A deterministic taxonomy rule links specific terms to their general term ("quantum computing" is a "computing")
- Optional rules (off by default): transitive chains through transitive predicate families, and lexical similarity
- Candidates are accepted in that priority order; a node pair is never connected twice, and the total is capped at
max_inferred_ratio× the number of extracted triples. Every inferred triple records its method.
- Visualization: centrality metrics and Louvain communities are computed with NetworkX, the LLM names the communities (one call), and the interactive page is rendered from the project's own template with the vis-network library embedded
- Output: the HTML page, the JSON triples, the
.meta.jsoncommunity names, and any--exportformats
The second and third passes and community naming are optional and can be disabled in the configuration to minimize LLM usage. LLM replies are cached on disk, so re-running the same document with the same settings makes no API calls.
Visualization Features
The generated HTML is a single self-contained file (vis-network is embedded) that works offline; --library-path
switches to one shared library copy when you publish many pages.
- Explore by clicking: click a node to highlight its neighbourhood and open a details panel listing every incoming and outgoing relationship, tagged extracted or with its inference method; click a row to jump to that node. Double-click to zoom in.
- Entity types: the extraction pass labels every entity (person, organization, place, event, technology, product, work, date, concept); node shapes reflect the type and the Communities panel can filter by it.
- Provenance: each extracted relationship carries the sentence it came from, shown in tooltips and in the details panel, so inferred edges are easy to tell apart from what the text actually says.
- Named communities: after community detection the LLM gives each community a short name (one call;
disable with
visualization.name_communities = false). - Path finder: from any selected node, type another node's name to highlight the shortest chain of relationships between them, optionally through extracted relationships only.
- Search with autocomplete (
/), and shareable links: the selected node is kept in the URL (#node=...). - Communities panel: colour-coded Louvain communities with their top entities, toggle any of them on/off, a minimum-connections slider, and a switch for inferred relationships.
- Parallel edges collapsed: when two entities share several relationships one edge is drawn with a "+N" badge; the tooltip and the details panel still list every predicate (
visualization.collapse_parallel_edges). - Display names: lower-case entity names are shown title-cased ("Steam Engine"); the underlying names, search and exports are unchanged (
visualization.title_case). - Edge labels: shown for all edges by default (
visualization.edge_labels); cycle to none or selection only, which keeps dense graphs readable. Around a selected node only its own edges are labelled. - Node size reflects importance (degree, betweenness and eigenvector centrality).
- Extracted vs inferred: solid lines are extracted from the text, dashed lines are inferred; tooltips show the inference method and, for transitive edges, the intermediate node.
- Export the current view as PNG, or the visible triples as JSON or CSV.
- Physics controls, a layout progress bar, automatic physics freeze for graphs over 300 nodes, light and
dark themes (
visualization.theme), keyboard shortcuts (?for the list), responsive layout.
Project Layout
.
├── config.toml # Main configuration file for the system
├── generate-graph.py # Run from a checkout without installing
├── json_to_html.py # Re-render a saved .json graph (same as --from-json)
├── pyproject.toml # Project metadata, dependencies, tool config
├── requirements.txt # Pinned dependencies for 'pip' users
├── uv.lock # Lock file for 'uv' users
├── IMPROVEMENT_PLAN.md # Roadmap / task list
├── .github/workflows/ci.yml # Lint + tests on Python 3.11-3.13
├── data/ # Sample input text and screenshot
│ └── samples/ # Sample corpus: texts + generated graphs (no LLM needed to explore)
├── docs/ # GitHub Pages site: landing page + sample graphs (built by scripts/build_docs.py)
├── scripts/build_docs.py # Rebuilds docs/ from data/samples
├── tests/ # pytest suite (runs without an LLM)
└── src/knowledge_graph/ # Core package (installed as `knowledge_graph`)
├── __init__.py # Package initialization and version
├── config.py # Configuration loading, defaults and validation
├── entity_standardization.py # Entity standardization and relationship inference
├── exports.py # CSV, GraphML and Cypher exports
├── query.py # Optional graph-chat command (question answering over the JSON)
├── server.py # Optional graph-serve command (local web interface)
├── llm.py # LLM client (retries, truncation detection, cache) and JSON extraction
├── main.py # CLI, input handling and pipeline orchestration
├── text_utils.py # Sentence splitting, chunking and provenance lookup
├── visualization.py # Graph metrics, communities and page rendering
├── prompts/ # LLM prompts (extraction, entity resolution, inference, community naming)
└── templates/
├── graph.html.j2 # The interactive explorer page (Jinja2)
├── library.html.j2 # Graph library + new-graph form for graph-serve
├── job.html.j2 # Generation progress page for graph-serve
├── landing.html.j2 # GitHub Pages landing page (scripts/build_docs.py)
└── vendor/ # Embedded vis-network library
Troubleshooting
- "Response ... was cut off (finish_reason='length')": the model spent the token budget on hidden reasoning. Raise
max_tokensto 32k, setreasoning_effort = "low", use smaller chunks, or switch to a non-reasoning model.--continue-on-errorskips the failed chunk instead of aborting. - gpt-5 / o-series reject
max_tokensortemperature: the client switches tomax_completion_tokensautomatically; remove thetemperatureline for models that only accept the default. - "Reading PDFs needs the optional 'pypdf' package":
pip install "ai-knowledge-graph[pdf]"(or[docx],[all]). Scanned PDFs have no text layer and need OCR first. Text encodings are detected automatically. - Identical re-runs still call the API: the cache key includes the model and every request parameter, so any config change is a miss. Delete
.kg-cache/to start fresh. - Graph looks fragmented: check that
inference.enabledanduse_llm_for_inferenceare on; small models sometimes name the same concept differently across chunks, and a lowertemperaturehelps.