Skip to main content

knowledge-graph-construction

Implements knowledge graph construction (entity extraction, relationship mapping, graph database storage) and Graph-RAG integration for enterprise AI agents with structured reasoning over connected data.

Aller à l'installation

Informations de source

Dépôt
paulpas/agent-skill-router
Dernière activité de la source
9 juin 2026 à 01:54
Langue détectée de SKILL.md
anglais
Étoiles
4
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
knowledge-graph-construction
description
Implements knowledge graph construction (entity extraction, relationship mapping, graph database storage) and Graph-RAG integration for enterprise AI agents with structured reasoning over connected data.
license
MIT
compatibility
opencode
archetypes
["tactical","strategic"]
anti_triggers
["simple vector search","basic RAG pipeline","unstructured text only"]
response_profile
{"verbosity":"medium","directive_strength":"high","abstraction_level":"operational"}
metadata
{"version":"1.0.0","domain":"agent","role":"implementation","scope":"infrastructure","output-format":"code","triggers":"knowledge graph, Neo4j, GraphRAG, entity extraction, relationship mapping, how do i build a knowledge graph for AI, multi-hop queries","related-skills":"rag-patterns,mcp-integration,evaluation-monitoring"}
# Knowledge Graph Construction for AI Agents This skill makes the model design, build, and deploy knowledge graph systems — constructing structured entity-relationship representations from unstructured documents, connecting them to enterprise datastores via MCP, integrating as retrieval backends for RAG pipelines (Graph-RAG), and configuring multi-tenant agent platforms like Google AgentSpace with role-based access control. ## TL;DR Checklist - [ ] Design a graph schema with explicit node types, relationship types, and property constraints before ingestion - [ ] Extract entities using LLM-based extraction with strict JSON schemas — never ad-hoc field names - [ ] Map relationships with directional edges (source → target) and typed predicates following ontology conventions - [ ] Load the graph into Neo4j or store in-memory with NetworkX for prototyping, enforcing uniqueness constraints - [ ] Build Graph-RAG queries that combine vector similarity on node embeddings with graph traversal for multi-hop reasoning - [ ] Implement enterprise safeguards: tenant isolation, audit logging, and role-based access control per agent - [ ] Validate graph quality post-ingestion: check entity consistency, relationship completeness, and query accuracy --- ## When to Use Use this skill when: - Building a knowledge representation that captures relationships between entities (people, organizations, concepts, events) for downstream reasoning - Answering multi-hop queries that require traversing entity connections (e.g., "Which vendors supply components used in Project X?") - Integrating an AI agent with structured enterprise data via Google AgentSpace or a custom knowledge graph platform - Implementing Graph-RAG — using a knowledge graph as the retrieval backend for RAG queries instead of pure vector search - Constructing entity resolution pipelines that merge duplicate entities across disparate data sources (CRM, ERP, wikis) - Designing ontology-driven agent systems where domain relationships must be explicitly modeled (fraud detection, supply chain analysis, biomedicine) --- ## When NOT to Use Avoid this skill for: - **Simple FAQ lookup** — A flat document store with vector search (`rag-patterns`) handles single-document Q&A cheaper and faster - **Unstructured text classification** — Classification tasks don't need graph relationships; use a fine-tuned model or prompt-based classifier instead - **Real-time event streaming** — Graph construction has ingestion latency; for sub-second event processing, use a message queue (`aws-sqs`) with simple state stores - **Pure vector similarity search** — If the only query pattern is "find documents similar to this," a vector database alone is simpler and sufficient --- ## Core Workflow 1. **Schema Design** — Define your graph ontology: node types (entities), relationship types (predicates), and property schemas. Start minimal — only model relationships that queries actually need. Use a domain ontology as a reference (e.g., DBpedia, schema.org) when one exists. **Checkpoint:** Every node type has at least one unique constraint property (e.g., `Person:email`, `Company:ticker`). Verify the schema supports all target queries before proceeding to ingestion. 2. **Document Ingestion and Parsing** — Load raw documents (PDFs, HTML, JSON, CSV) and preprocess them into clean text blocks suitable for extraction. Remove headers, footers, navigation elements, and boilerplate. Segment documents into logical units (sections, paragraphs, table rows) that map to graph entities. **Checkpoint:** Each ingestion unit is self-contained with a source identifier. Run a sampling pass to confirm no critical entity data was lost during preprocessing. 3. **Entity Extraction** — Use an LLM-based extraction pipeline to identify named entities and structured attributes from each document unit. Extract entities into typed nodes (Person, Organization, Product, Event, Location) with properties derived from the schema. Use few-shot prompting or JSON mode to enforce consistent output structure across all documents. **Checkpoint:** Extracted entities conform to the defined schema. Check for duplicate entities referencing the same real-world object — flag them for resolution in the next step. 4. **Relationship Mapping and Entity Resolution** — Map relationships between extracted entities using typed, directional edges (e.g., `(Person:Employee)-[:WORKS_AT]->(Company)`). Resolve entity duplicates by matching on canonical properties (email, tax ID, ticker symbol) or fuzzy matching on names with a confidence threshold. Merge resolved entities into single canonical nodes. **Checkpoint:** The graph passes quality gates: no orphaned relationship endpoints, duplicate entity rate below 5%, and all relationships have a source, type, target, and temporal validity window. 5. **Graph Storage and Indexing** — Load the constructed graph into the target graph database (Neo4j for production, NetworkX for prototyping). Create indexes on constraint properties and relationship endpoints to ensure query performance. Generate vector embeddings for node text attributes if Graph-RAG retrieval is needed. **Checkpoint:** Run a benchmark of representative queries against the stored graph — all queries must return within the SLA threshold. Verify index coverage for every indexed property used in WHERE clauses. 6. **Query Optimization and Graph-RAG Integration** — Implement Cypher query patterns optimized for the access patterns of your agents. Integrate the knowledge graph as a retrieval backend for RAG: combine vector similarity search on node embeddings with structured graph traversal to answer multi-hop questions. Build MCP tools that expose graph queries to agents. **Checkpoint:** The end-to-end Graph-RAG pipeline answers a test suite of 20+ multi-hop queries with >90% accuracy. Agent tool calls through MCP execute within acceptable latency bounds. --- ## Implementation Patterns / Reference Guide ### Pattern 1: Knowledge Graph Builder — Entity and Relationship Extraction Pipeline Constructs knowledge graphs from unstructured documents using LLM-based entity extraction, relationship mapping, and Neo4j loading. This is the core ingestion pipeline. ```python """ knowledge_graph_builder.py — Entity extraction and graph construction pipeline. Uses an LLM for structured entity/relation extraction with JSON mode, then loads results into Neo4j with constraint enforcement. """ import json import logging from dataclasses import dataclass, field from typing import Any, Optional from uuid import uuid4 from openai import OpenAI from neo4j import GraphDatabase, exceptions as neo4j_exceptions logger = logging.getLogger(__name__) @dataclass class ExtractedEntity: """A single entity extracted from a document.""" entity_id: str entity_type: str # Person, Organization, Product, Event, Location name: str properties: dict[str, Any] = field(default_factory=dict) @property def canonical_key(self) -> str: """Generate a stable key for entity resolution. Returns: A composite key that uniquely identifies this real-world entity. """ if "email" in self.properties: return f"{self.entity_type}:email:{self.properties['email']}" if "ticker" in self.properties: return f"{self.entity_type}:ticker:{self.properties['ticker']}" return f"{self.entity_type}:name:{self.name.lower().strip()}" @dataclass class ExtractedRelationship: """A directed relationship between two entities.""" source_id: str target_id: str relation_type: str # WORKS_AT, SUPPLIES, LOCATED_IN, etc. properties: dict[str, Any] = field(default_factory=dict) @property def canonical_edge(self) -> tuple[str, str, str]: """Unique edge key for deduplication.""" return (self.source_id, self.relation_type, self.target_id) class KnowledgeGraphBuilder: """Builds knowledge graphs from documents using LLM extraction + Neo4j loading. This class handles the full ingestion pipeline: 1. Prompt-based entity/relation extraction via LLM JSON mode 2. Entity resolution (canonical key matching) 3. Graph construction in Neo4j with constraint enforcement """ # Strict extraction prompt template — few-shot guidance ensures consistent schema EXTRACTION_PROMPT = """ Extract structured entities and relationships from the following text. Return ONLY a JSON object matching this exact schema: {{ "entities": [ {{ "entity_id": "e_001", "entity_type": "Person | Organization | Product | Event | Location", "name": "<exact name from text>", "properties": {{}} }} ], "relationships": [ {{ "source_entity_name": "<name of source entity exactly as extracted>", "target_entity_name": "<name of target entity exactly as extracted>", "relation_type": "WORKS_AT | EMPLOYER_OF | SUPPLIES | LOCATED_IN | OWNS | PART_OF", "properties": {{}} }} ] }} Rules: - Use ONLY the entity types listed above. - relation_type must be one of the five types listed. - Entity names must match the text exactly (case-sensitive). - Do NOT invent relationships — only extract those explicitly stated or strongly implied. - If no entities are found, return empty arrays. Text: {text} """ def __init__( self, openai_client: OpenAI, neo4j_uri: str = "bolt://localhost:7687", neo4j_user: str = "neo4j", neo4j_password: str = "password", database_name: str = "knowledge_graph", ) -> None: """Initialize the knowledge graph builder. Args: openai_client: OpenAI client instance for extraction calls. neo4j_uri: Neo4j connection URI. neo4j_user: Neo4j authentication username. neo4j_password: Neo4j authentication password. database_name: Target Neo4j database name. """ self.llm = openai_client self.neo4j_driver = GraphDatabase.driver( neo4j_uri, auth=(neo4j_user, neo4j_password) ) self.database_name = database_name def extract_from_text(self, text: str) -> tuple[list[ExtractedEntity], list[ExtractedRelationship]]: """Extract entities and relationships from raw text using LLM JSON mode. Args: text: The raw text to extract structured data from. Returns: Tuple of (entities, relationships) lists. Raises: ValueError: If the LLM response cannot be parsed as valid JSON. """ if not text or len(text.strip()) < 10: raise ValueError(f"Text too short for extraction: {len(text)} characters") response = self.llm.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": self.EXTRACTION_PROMPT.format(text=text[:8000])}], response_format={"type": "json_object"}, temperature=0.0, # Deterministic extraction max_tokens=2048, ) raw = response.choices[0].message.content try: parsed = json.loads(raw) except json.JSONDecodeError as exc: raise ValueError(f"LLM returned invalid JSON: {exc}") from exc # --- Parse entities --- entities: list[ExtractedEntity] = [] entity_name_to_id: dict[str, str] = {} for item in parsed.get("entities", []): eid = f"e_{uuid4().hex[:8]}" name = item.get("name", "").strip() if not name: continue # Skip empty names (Early Exit) entity_type = item.get("entity_type", "Unknown") if entity_type not in ("Person", "Organization", "Product", "Event", "Location"): logger.warning(f"Skipping unrecognized entity type: {entity_type}") continue entity = ExtractedEntity( entity_id=eid, entity_type=entity_type, name=name, properties=item.get("properties", {}), ) entities.append(entity) entity_name_to_id[name] = eid # --- Parse relationships --- relationships: list[ExtractedRelationship] = [] for rel in parsed.get("relationships", []): source_name = rel.get("source_entity_name", "").strip() target_name = rel.get("target_entity_name", "").strip() relation_type = rel.get("relation_type", "") source_id = entity_name_to_id.get(source_name) target_id = entity_name_to_id.get(target_name) if not source_id or not target_id: logger.warning( f"Skipping relationship with unresolved entities: " f"'{source_name}' → '{target_name}'" ) continue # Skip orphaned relationships (Fail Fast) relationship = ExtractedRelationship( source_id=source_id, target_id=target_id, relation_type=relation_type, properties=rel.get("properties", {}), ) relationships.append(relationship) return entities, relationships def resolve_entities( self, all_entities: list[ExtractedEntity], ) -> tuple[dict[str, str], list[ExtractedEntity]]: """Resolve duplicate entities by canonical key and merge properties. Args: all_entities: All extracted entities from one or more documents. Returns: Tuple of (canonical_map: original_id → resolved_id, merged_entities). """ canonical_map: dict[str, str] = {} merged: dict[str, ExtractedEntity] = {} # canonical_key → entity for entity in all_entities: key = entity.canonical_key if key not in merged: merged[key] = entity canonical_map[entity.entity_id] = entity.entity_id else: # Merge properties — later documents override earlier ones existing = merged[key] for prop_k, prop_v in entity.properties.items(): if prop_v and not existing.properties.get(prop_k): existing.properties[prop_k] = prop_v canonical_map[entity.entity_id] = existing.entity_id return canonical_map, list(merged.values()) def load_to_neo4j( self, entities: list[ExtractedEntity], relationships: list[ExtractedRelationship], source_doc_id: str, ) -> dict[str, int]: """Load resolved entities and relationships into Neo4j with constraint enforcement. Args: entities: Resolved (deduplicated) entity list. relationships: Relationship list with resolved IDs. source_doc_id: Document identifier for provenance tracking. Returns: Counts of created nodes, created relationships, and updated (merged) nodes. """ driver = self.neo4j_driver counts = {"nodes_created": 0, "relationships_created": 0, "nodes_updated": 0} # Step 1: Ensure constraints exist with driver.session(database=self.database_name) as session: for node_type in ("Person", "Organization"): constraint_name = f"unique_{node_type.lower()}_name" try: session.run(f""" CREATE CONSTRAINT {constraint_name} IF NOT EXISTS FOR (n:{node_type}) REQUIRE n.name IS UNIQUE """) except neo4j_exceptions.ClientError: pass # Constraint already exists # Step 2: Upsert entities using MERGE — prevents duplicates for entity in entities: props = dict(entity.properties) props["graph_id"] = entity.entity_id props["ingested_from"] = source_doc_id props["updated_at"] = None # Placeholder; set by application session.run(f""" MERGE (e:{entity.entity_type} {{name: $name}}) SET e += $props, e.graph_id = $graph_id RETURN count(e) AS count """, name=entity.name, props=props, graph_id=entity.entity_id) counts["nodes_updated"] += 1 # Step 3: Create relationships seen_edges: set[tuple[str, str, str]] = set() for rel in relationships: edge_key = (rel.source_id, rel.relation_type, rel.target_id) if edge_key in seen_edges: continue # Skip duplicates (Early Exit) seen_edges.add(edge_key) session.run(f""" MATCH (source {{graph_id: $source_id}}), (target {{graph_id: $target_id}}) WHERE source IS NOT NULL AND target IS NOT NULL MERGE (source)-[r:{rel.relation_type}]->(target) SET r += $props, r.source_doc = $doc_id """, source_id=rel.source_id, target_id=rel.target_id, props=rel.properties, doc_id=source_doc_id) counts["relationships_created"] += 1 return counts def close(self) -> None: """Close the Neo4j driver connection.""" self.neo4j_driver.close() # --- Usage example --- def build_graph_from_documents( texts: list[str], doc_ids: list[str], openai_api_key: str,
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub