Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Build research knowledge graphs for literature synthesis and RAG systems
.claude/skills/brycewang-stanford-knowledge-graph-construction/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 87% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 48% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 98% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 133% | 0% |
Knowledge graphs (KGs) organize information as networks of entities and relationships, making them powerful tools for research synthesis, literature exploration, and AI-augmented retrieval. In academic contexts, knowledge graphs can represent relationships between papers, authors, methods, datasets, findings, and concepts -- enabling queries like "Which methods have been applied to dataset X?" or "What are the common limitations reported across studies of Y?"
This guide covers building knowledge graphs for research applications: defining schemas (ontologies), extracting entities and relations from text, storing and querying graph data, and integrating knowledge graphs with Retrieval Augmented Generation (RAG) systems for AI-powered research assistants.
Whether you are building a personal research knowledge base, constructing a domain-specific literature graph, or developing a RAG system for an academic chatbot, these patterns provide a solid foundation.
| Component | Definition | Research Example | |-----------|-----------|-----------------| | Entity (Node) | A distinct concept or object | Paper, Author, Method, Dataset | | Relation (Edge) | A typed connection between entities | "cites", "uses_method", "evaluates_on" | | Property | An attribute of an entity or relation | Paper.year, Author.affiliation | | Ontology/Schema | Formal definition of entity and relation types | Research ontology defining valid types |
yaml# research_ontology.yaml entities: Paper: properties: [title, year, doi, abstract, venue] Author: properties: [name, affiliation, orcid] Method: properties: [name, description, category] Dataset: properties: [name, domain, size, url] Finding: properties: [description, metric, value, significance] Concept: properties: [name, definition, domain] relations: CITES: from: Paper to: Paper AUTHORED_BY: from: Paper to: Author USES_METHOD: from: Paper to: Method EVALUATES_ON: from: Paper to: Dataset REPORTS_FINDING: from: Paper to: Finding RELATED_TO: from: Concept to: Concept INTRODUCES: from: Paper to: Method
Using a large language model to extract structured knowledge from paper abstracts:
pythonimport json from openai import OpenAI client = OpenAI() EXTRACTION_PROMPT = """Extract entities and relationships from this research paper abstract. Return JSON with: - entities: list of {type, name, properties} - relations: list of {source, relation, target} Entity types: Paper, Method, Dataset, Finding, Concept Relation types: USES_METHOD, EVALUATES_ON, REPORTS_FINDING, RELATED_TO, INTRODUCES Abstract: {abstract} Respond ONLY with valid JSON.""" def extract_from_abstract(abstract, paper_title): response = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "You are a research knowledge extraction system."}, {"role": "user", "content": EXTRACTION_PROMPT.format(abstract=abstract)} ], response_format={"type": "json_object"}, temperature=0 ) result = json.loads(response.choices[0].message.content) # Add the paper itself as an entity result['entities'].insert(0, { 'type': 'Paper', 'name': paper_title, 'properties': {'abstract': abstract[:200]} }) return result
pythonimport spacy from spacy.tokens import Span nlp = spacy.load("en_core_web_trf") # Register custom entity types @spacy.Language.component("research_entities") def research_entity_component(doc): # Pattern-based recognition for methods method_patterns = [ "random forest", "gradient boosting", "neural network", "transformer", "attention mechanism", "BERT", "GPT", "convolutional", "recurrent", "GAN" ] new_ents = list(doc.ents) for token in doc: for pattern in method_patterns: if pattern.lower() in doc[token.i:token.i+3].text.lower(): span = doc.char_span(token.idx, token.idx + len(pattern), label="METHOD") if span and span not in new_ents: new_ents.append(span) doc.ents = spacy.util.filter_spans(new_ents) return doc nlp.add_pipe("research_entities", after="ner")
pythonfrom neo4j import GraphDatabase class ResearchGraph: def __init__(self, uri, user, password): self.driver = GraphDatabase.driver(uri, auth=(user, password)) def add_paper(self, paper): with self.driver.session() as session: session.run(""" MERGE (p:Paper {doi: $doi}) SET p.title = $title, p.year = $year, p.abstract = $abstract """, **paper) def add_citation(self, citing_doi, cited_doi): with self.driver.session() as session: session.run(""" MATCH (a:Paper {doi: $citing}) MATCH (b:Paper {doi: $cited}) MERGE (a)-[:CITES]->(b) """, citing=citing_doi, cited=cited_doi) def add_method_usage(self, paper_doi, method_name): with self.driver.session() as session: session.run(""" MATCH (p:Paper {doi: $doi}) MERGE (m:Method {name: $method}) MERGE (p)-[:USES_METHOD]->(m) """, doi=paper_doi, method=method_name) def find_papers_using_method(self, method_name): with self.driver.session() as session: result = session.run(""" MATCH (p:Paper)-[:USES_METHOD]->(m:Method {name: $method}) RETURN p.title AS title, p.year AS year, p.doi AS doi ORDER BY p.year DESC """, method=method_name) return [dict(record) for record in result] def find_common_methods(self, doi1, doi2): with self.driver.session() as session: result = session.run(""" MATCH (p1:Paper {doi: $doi1})-[:USES_METHOD]->(m:Method) <-[:USES_METHOD]-(p2:Paper {doi: $doi2}) RETURN m.name AS method """, doi1=doi1, doi2=doi2) return [record['method'] for record in result]
pythonimport networkx as nx import json def build_research_graph(extracted_data_list): """Build a NetworkX graph from extracted paper data.""" G = nx.MultiDiGraph() for data in extracted_data_list: for entity in data['entities']: G.add_node( entity['name'], type=entity['type'], **entity.get('properties', {}) ) for rel in data['relations']: G.add_edge( rel['source'], rel['target'], relation=rel['relation'] ) return G # Query the graph def get_method_landscape(G): """Find which methods are most used across papers.""" methods = [n for n, d in G.nodes(data=True) if d.get('type') == 'Method'] method_usage = {} for method in methods: papers = [n for n in G.predecessors(method) if G.nodes[n].get('type') == 'Paper'] method_usage[method] = len(papers) return sorted(method_usage.items(), key=lambda x: x[1], reverse=True)
Combining knowledge graphs with retrieval augmented generation creates powerful research assistants:
pythondef kg_rag_query(question, graph, embedding_model, llm): """Answer a research question using KG-enhanced RAG.""" # Step 1: Extract entities from the question question_entities = extract_entities(question) # Step 2: Retrieve relevant subgraph subgraph_nodes = set() for entity in question_entities: if entity in graph: # Get 2-hop neighborhood neighbors = nx.ego_graph(graph, entity, radius=2) subgraph_nodes.update(neighbors.nodes()) # Step 3: Format context from subgraph context_parts = [] for node in subgraph_nodes: node_data = graph.nodes[node] edges = list(graph.edges(node, data=True)) context_parts.append( f"{node} ({node_data.get('type', 'Unknown')}): " f"{', '.join(f'{e[2].get(\"relation\", \"related_to\")} {e[1]}' for e in edges[:5])}" ) context = '\n'.join(context_parts[:20]) # Step 4: Generate answer with LLM prompt = f"""Based on the following knowledge graph context, answer the question. Context: {context} Question: {question} Provide a detailed answer citing specific papers, methods, and findings from the context.""" return llm.generate(prompt)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 19,597 | 45,705 | +133% | 1 | 1 | 0% | 3,505 | 6,050 | +73% | 0 | 0 | — |
case-02 | fail→fail | 17,787 | 23,754 | +34% | 1 | 1 | 0% | 3,809 | 6,792 | +78% | 0 | 0 | — |
case-03 | fail→fail | 31,579 | 27,492 | -13% | 1 | 1 | 0% | 4,972 | 8,078 | +62% | 0 | 0 | — |
case-04 | pass→pass | 16,940 | 17,850 | +5% | 1 | 1 | 0% | 3,325 | 6,168 | +86% | 0 | 0 | — |
case-05 | pass→pass | 10,642 | 11,915 | +12% | 1 | 1 | 0% | 2,038 | 4,870 | +139% | 0 | 0 | — |
case-06 | pass→pass | 11,843 | 14,319 | +21% | 1 | 1 | 0% | 2,329 | 5,375 | +131% | 0 | 0 | — |
case-07 | fail→pass | 25,257 | 25,650 | +2% | 1 | 1 | 0% | 4,089 | 7,661 | +87% | 0 | 0 | — |
case-08 | fail→pass | 21,556 | 18,776 | -13% | 1 | 1 | 0% | 4,115 | 6,091 | +48% | 0 | 0 | — |
case-09 | fail→fail | 12,508 | 11,434 | -9% | 1 | 1 | 0% | 2,126 | 4,769 | +124% | 0 | 0 | — |
case-10 | pass→pass | 18,312 | 18,465 | +1% | 1 | 1 | 0% | 3,510 | 5,545 | +58% | 0 | 0 | — |
case-11 | fail→pass | 12,715 | 10,541 | -17% | 1 | 1 | 0% | 2,320 | 4,588 | +98% | 0 | 0 | — |
case-12 | fail→pass | 22,800 | 19,523 | -14% | 1 | 1 | 0% | 3,762 | 6,159 | +64% | 0 | 0 | — |
case-13 | fail→fail | 18,772 | 21,377 | +14% | 1 | 1 | 0% | 3,097 | 6,436 | +108% | 0 | 0 | — |
case-14 | pass→pass | 10,855 | 13,616 | +25% | 1 | 1 | 0% | 2,134 | 4,710 | +121% | 0 | 0 | — |
case-15 | fail→fail | 20,146 | 18,987 | -6% | 1 | 1 | 0% | 3,022 | 6,088 | +101% | 0 | 0 | — |
case-16 | pass→pass | 15,910 | 13,888 | -13% | 1 | 1 | 0% | 2,522 | 4,908 | +95% | 0 | 0 | — |
case-17 | pass→pass | 18,871 | 26,546 | +41% | 1 | 1 | 0% | 2,945 | 7,443 | +153% | 0 | 0 | — |
case-18 | pass→fail | 21,741 | 24,223 | +11% | 1 | 1 | 0% | 3,416 | 7,017 | +105% | 0 | 0 | — |
case-19 | fail→pass | 17,509 | 29,628 | +69% | 1 | 1 | 0% | 2,939 | 6,840 | +133% | 0 | 0 | — |
case-20 | pass→pass | 20,893 | 20,808 | -0% | 1 | 1 | 0% | 2,918 | 5,671 | +94% | 0 | 0 | — |
case-21 | pass→pass | 16,798 | 25,296 | +51% | 1 | 1 | 0% | 2,821 | 6,471 | +129% | 0 | 0 | — |
case-22 | fail→pass | 10,378 | 2,792 | -73% | 1 | 1 | 0% | 1,592 | 3,179 | +100% | 0 | 0 | — |
case-23 | pass→pass | 10,701 | 2,290 | -79% | 1 | 1 | 0% | 1,513 | 2,969 | +96% | 0 | 0 | — |
case-24 | pass→pass | 18,944 | 21,220 | +12% | 1 | 1 | 0% | 2,824 | 5,950 | +111% | 0 | 0 | — |
case-25 | fail→pass | 20,456 | 19,529 | -5% | 1 | 1 | 0% | 2,928 | 6,227 | +113% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +24 percentage points is the difference between those two pass rates over the 25 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.