Quick Start — Your First Knowledge Graph
Get from zero to a working knowledge graph in under 15 minutes.
This guide walks you through installing scinr, configuring your environment, preparing documents, running the full ingestion pipeline (Stages 0–4, plus an auto-detected tabular path), and verifying the extracted knowledge graph in Neo4j.
Prerequisites
Before you begin, make sure you have:
- Python 3.11+ installed on your system.
- Neo4j 5.0+ running and accessible via the Bolt protocol. If you do not have Neo4j installed, start one locally with Docker:
- LLM credentials for at least one provider (AWS Bedrock, OpenAI, or Ollama).
Step 1: Install scinr
Install scinr with pip. Choose the extras that match your LLM provider:
# AWS Bedrock (recommended — includes langchain-aws and boto3)
pip install "scinr[bedrock]"
# Or with OpenAI (includes langchain-openai)
pip install "scinr[openai]"
# Or with Ollama (includes langchain-ollama)
pip install "scinr[ollama]"
# Or with all extras at once
pip install "scinr[bedrock,openai,ollama,mongodb]"
Tip: To process any PDF you need a Mistral AI API key — PDF conversion is done entirely through the Mistral OCR API. There is no fallback path for PDFs without a key.
Step 2: Set up your environment
scinr reads Neo4j, storage, and OCR settings from environment variables. The LLM is configured in code — you pass a ready-built LangChain model as llm= to configure() (see Step 4). The recommended approach for everything else is a .env file in your working directory.
Create .env from the template
If you cloned the scinr repository, copy the example template:
Fill in the required values
At a minimum, you need to set the Neo4j credentials. Here is a minimal .env for a first run:
# ─── Neo4j ──────────────────────────────────────────────────────────────────
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password
NEO4J_DATABASE=neo4j
# ─── PDF conversion ────────────────────────────────────────────────────────
MISTRAL_API_KEY=your_mistral_key
Required vs. optional fields
| Variable | Required? | What to set |
|---|---|---|
NEO4J_URI |
No | Neo4j Bolt URI. Default: bolt://localhost:7687. |
NEO4J_USER |
Yes | Neo4j username (usually neo4j). |
NEO4J_PASSWORD |
Yes | Neo4j password. |
NEO4J_DATABASE |
Yes | Neo4j database name (usually neo4j). |
MISTRAL_API_KEY |
Yes, to process any PDF | Mistral OCR API key. PDF conversion has no non-Mistral path. |
STORAGE_BACKEND |
No | none (default, in-memory) or mongodb. |
LLM: there is no
MODEL_IDenvironment variable to set. Build a LangChain chat model in your script and pass it asllm=toconfigure().Note:
python-dotenvis included as a core dependency. When you callconfigure(), it automatically loads variables from a.envfile in your current working directory. You do not need to importdotenvmanually.
Step 3: Prepare your documents
Create a directory and place your source documents in it:
Supported file formats
| Format | Extensions | Notes |
|---|---|---|
.pdf |
Converted via the Mistral OCR API — requires MISTRAL_API_KEY |
|
| Word | .docx |
Full text + structure extraction |
| Excel | .xlsx, .xls |
Auto-routed to tabular pipeline |
| CSV | .csv |
Auto-routed to tabular pipeline |
Roadmap:
.pptx,.html,.json,.xml, and.txtconverters exist in the codebase but are not yet officially supported or tested. Stick to the formats above for production use.Warning: Tabular files (
.csv,.xlsx,.xls) are automatically routed to the tabular pipeline and bypass the standard extraction flow (Stages 0–4). This is intentional — spreadsheets have a fundamentally different structure than narrative documents.
Step 4: Run the pipeline
Create a Python script to configure scinr and run the full pipeline:
# quickstart.py
import asyncio
from langchain_aws import ChatBedrockConverse # or any LangChain chat model
from scinr.newton import configure, run_pipeline
async def main():
# The model is passed via llm= (no MODEL_ID env var). Neo4j and storage
# settings still come from .env (loaded automatically by configure()).
configure(
llm=ChatBedrockConverse(model="us.anthropic.claude-sonnet-4-6", region_name="us-east-1"),
neo4j_uri="bolt://localhost:7687",
neo4j_user="neo4j",
neo4j_password="your_password",
neo4j_database="neo4j",
)
# Run the default pipeline (Stages 0–4; tabular files are auto-detected)
result = await run_pipeline(input_raw="./raw_docs")
# Check overall result
print(f"Pipeline success: {result.success}")
print(f"Total duration: {result.total_duration_seconds:.1f}s")
print(f"Stages executed: {result.stages_executed}")
# Inspect individual stages
for stage_name in result.stages_executed:
stage = getattr(result, stage_name, None)
if stage is not None:
status = "OK" if stage.success else "FAIL"
print(
f" [{status}] {stage_name}: "
f"{stage.total_processed} processed, "
f"{stage.total_failed} failed "
f"({stage.duration_seconds:.1f}s)"
)
# Show per-document details
for doc in stage.documents:
if doc.errors:
for err in doc.errors:
print(f" - {doc.document_name}: {err}")
asyncio.run(main())
Run the script:
What happens behind the scenes
The pipeline runs these stages in order:
| Stage | Name | What it does |
|---|---|---|
| 0 | Preprocess | Converts raw files to an intermediate JSON/Markdown format |
| 1 | Extraction | Uses the LLM to parse document structure and extract hierarchical sections |
| 2 | Ingestion | Writes document and structure nodes into Neo4j |
| 3 | Annotation | An LLM agent assigns an extraction model to each structural node |
| 4 | Entity Extraction | Extracts typed Pydantic entities from annotated nodes and writes them as graph subgraphs |
| 5 | Tabular | If .csv, .xlsx, or .xls files are detected in input_raw, they are processed through a separate tabular pipeline with LLM-powered normalization |
Expected output
A successful run produces output similar to:
Pipeline success: True
Total duration: 45.2s
Stages executed: ['preprocess', 'extraction', 'ingestion', 'annotation', 'entity_extraction']
[OK] preprocess: 3 processed, 0 failed (2.1s)
[OK] extraction: 3 processed, 0 failed (18.5s)
[OK] ingestion: 3 processed, 0 failed (4.3s)
[OK] annotation: 47 processed, 0 failed (12.8s)
[OK] entity_extraction: 47 processed, 2 failed (8.5s)
Tip: If you see failures in entity extraction, this is often expected — not every structural node contains extractable domain entities. The pipeline continues gracefully. Check the per-document errors for details.
Step 5: Verify in Neo4j
After the pipeline completes, your data is available in Neo4j. Use the Neo4j Browser (http://localhost:7474) or any Cypher client to inspect the results.
Schema note:
HAS_STRUCTUREconnects a:Documentonly to its root:StructureNode; deeper nesting isHAS_CHILD. Extracted entities hang off(:StructureNode)-[:HAS_EXTRACTION]->(:ExtractionResult)-[:HAS_<field>]->(:ModelInstance)-[:REFERENCES]->(:LabeledEntity). See Neo4j Graph Model for the full reference.
List all ingested documents
MATCH (d:Document)
WHERE d.latest = true
RETURN d.name, d.path, d.version, d.load_date
ORDER BY d.load_date DESC;
View document structure
MATCH (d:Document)-[:HAS_STRUCTURE]->(:StructureNode)-[:HAS_CHILD*0..]->(s:StructureNode)
RETURN d.name AS document, s.title AS section, s.role
LIMIT 20;
View the full hierarchy
MATCH (d:Document)-[:HAS_STRUCTURE]->(:StructureNode)-[:HAS_CHILD*0..]->(parent:StructureNode)-[:HAS_CHILD]->(child:StructureNode)
RETURN d.name AS document, parent.title AS parent, child.title AS child, child.role AS child_role
LIMIT 30;
View extracted entity types
View labeled entities (globally deduplicated)
MATCH (:ModelInstance)-[:REFERENCES]->(e:LabeledEntity)
RETURN e.label AS entity_type, e.value, count(*) AS references
ORDER BY references DESC
LIMIT 20;
View entity relationships
MATCH (a:ModelInstance)-[r]->(b:ModelInstance)
RETURN type(r) AS relationship, count(r) AS count
ORDER BY count DESC;
Explore a specific document's extraction results
MATCH (d:Document {name: 'YourDocumentName'})-[:HAS_STRUCTURE]->(:StructureNode)-[:HAS_CHILD*0..]->(s:StructureNode)
MATCH (s)-[:HAS_EXTRACTION]->(:ExtractionResult)-[r]->(m:ModelInstance)
RETURN s.title AS section,
m.model_class AS model_type,
count(m) AS entities
ORDER BY entities DESC
LIMIT 20;
Visualize the graph
In Neo4j Browser, run this query to get a visual overview:
MATCH path = (d:Document {name: 'YourDocumentName'})-[:HAS_STRUCTURE]->(:StructureNode)-[:HAS_CHILD*0..2]->(:StructureNode)
RETURN path
LIMIT 50;
Step 6: Next steps
Now that you have a working pipeline, explore the rest of the documentation:
- Configuration — Full configuration reference: all environment variables,
configure()parameters, prompt families, concurrency tuning, and advanced settings. - Custom Models — Define your own Pydantic extraction schemas for domain-specific entities.
- Architecture — Detailed walkthrough of each pipeline stage, data flow between stages, and system design decisions.
- Tabular Pipeline — Working with CSV, XLSX, and spreadsheet data.
- Neo4j Graph Storage — Understanding the graph model and node/relationship types.
Common variations
Using OpenAI instead of Bedrock
from langchain_openai import ChatOpenAI
from scinr.newton import configure, run_pipeline
configure(
llm=ChatOpenAI(model="gpt-4o"),
neo4j_user="neo4j",
neo4j_password="your_password",
)
Set OPENAI_API_KEY in your .env file.
Using Ollama (local models)
from langchain_ollama import ChatOllama
from scinr.newton import configure, run_pipeline
configure(
llm=ChatOllama(model="llama3"),
neo4j_user="neo4j",
neo4j_password="your_password",
)
Make sure Ollama is running locally (ollama serve) and the model is pulled (ollama pull llama3).
Running only specific stages
You can skip stages by providing input from a later point in the pipeline:
# Run only annotation and entity extraction on an already-ingested document
result = await run_pipeline(
stages=["annotation", "entity_extraction"],
document_names=["MyDocument"],
)
Processing a single document
# Only process files matching a specific name pattern
result = await run_pipeline(
input_raw="./raw_docs",
document_names=["ClinicalTrialReport"],
)
Enabling MongoDB storage
Persist raw files and converted pages to MongoDB:
configure(
llm=ChatOllama(model="llama3"),
neo4j_user="neo4j",
neo4j_password="your_password",
neo4j_database="neo4j",
storage_backend="mongodb",
mongodb_uri="mongodb://localhost:27017",
mongodb_database="scinr",
)
Troubleshooting
An LLM stage fails with "no LLM configured"
The extraction, annotation, and entity-extraction stages (and tabular normalization) need a model. Pass an llm= argument to configure() with a LangChain BaseChatModel instance (e.g. ChatBedrockConverse, ChatOpenAI, ChatOllama). There is no environment-variable shortcut for the model. configure() itself does not require it — only these stages do.
"Neo4j username is not configured" / "Neo4j password is not configured"
configure() raises these separately. Set NEO4J_USER and NEO4J_PASSWORD in your .env file, or pass them as arguments to configure(). NEO4J_DATABASE is also required.
Neo4j connection refused
Make sure your Neo4j instance is running and accessible:
# Test Bolt connectivity
python -c "
from neo4j import GraphDatabase
driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'your_password'))
driver.verify_connectivity()
driver.close()
print('Neo4j connection OK')
"
"No documents discovered for this run"
Check that:
- The
input_rawdirectory exists and contains supported file types. - File extensions are recognized (
.pdf,.docx,.xlsx,.xls,.csv). - The path is correct — relative paths are resolved from your current working directory.
ImportError: langchain-aws is not installed
If you build a ChatBedrockConverse model but have not installed the Bedrock extra:
PDFs fail to process
PDF conversion goes entirely through the Mistral OCR API. Set MISTRAL_API_KEY in your .env (get a key at console.mistral.ai). Without it, no PDF can be processed — there is no local/text-extraction fallback.
LLM calls are slow or rate-limited
Adjust concurrency in your .env or via configure():
configure(
llm=my_llm,
neo4j_user="neo4j",
neo4j_password="your_password",
llm_concurrency=2, # Reduce concurrent LLM calls
neo4j_concurrency=5, # Reduce concurrent Neo4j writes
)
Annotation stage returns no model matches
This is expected for documents that do not contain content matching any registered extraction model. The pipeline falls back to generic triple extraction for unmatched nodes. To get specific entity extraction, define custom models matching your domain — see Custom Models.