# Knowledge Graph

⏱️ **Time to Complete:** 20 minutes\
🎯 **Level:** Intermediate\
💻 **Language:** TypeScript

## What You'll Build

A knowledge graph that automatically extracts:

* ✅ Entities (people, organizations, places, events, products)
* ✅ Relationships between entities
* ✅ Queryable structure from unstructured content
* ✅ Multi-hop reasoning across sources

[**📁 Knowledge graph example app**](https://github.com/graphlit/graphlit-samples/tree/main/nextjs/chat-graph)

***

## Why Knowledge Graphs Matter

**Traditional search**: "Find documents mentioning Sarah"\
**Knowledge graph**: "Find all interactions between Sarah Chen at Acme Corp and our Sales team, including meetings, emails, and Slack conversations"

**The difference:**

* ✅ Entity recognition (Sarah Chen = person, Acme Corp = organization)
* ✅ Relationship tracking (Sarah works at Acme, spoke with Sales)
* ✅ Semantic understanding (group related mentions across sources)
* ✅ Structured queries on unstructured data

**Real-world example**: [Zine](https://www.zine.ai) uses knowledge graphs to answer "Who from Acme Corp have we talked to?" across Slack, email, meetings, and CRM notes.

***

## Prerequisites

* Complete the [Quickstart tutorial](/getting-started/quickstart.md)
* Graphlit project with API credentials configured in `.env`
* `npm install graphlit-client dotenv`

{% hint style="info" %}
Need Python or .NET examples? Open **Ask Graphlit** in the Developer Portal (or visit [ask.graphlit.dev](https://ask.graphlit.dev)) for autogenerated samples tailored to your SDK.
{% endhint %}

***

## Step 1: Extract Entities from Content

Create a workflow, ingest content, and extract entities - all in one script:

```typescript
import { Graphlit } from 'graphlit-client';
import {
  SpecificationTypes,
  ModelServiceTypes,
  OpenAiModels,
  FilePreparationServiceTypes,
  EntityExtractionServiceTypes,
  ObservableTypes,
} from 'graphlit-client/dist/generated/graphql-types';

const graphlit = new Graphlit();

const SAMPLE_NOTE = `
Met with Sarah Chen, CTO at Acme Corp, at their San Francisco office.
She's evaluating our API for their 800-user deployment.

Key attendees:
- Sarah Chen (CTO, Acme Corp)
- Mike Rodriguez (VP Engineering, Acme Corp)
- Alex Kim (Solutions Engineer, our team)

They need CRM integration and mentioned Salesforce as their current tool.
Looking to deploy by Q4 2025. Budget approved for Enterprise tier.

Follow-up: Technical demo scheduled for next Tuesday in Acme's office.
`;

async function main() {
  console.log('⚙️ Creating extraction workflow...');

  // 1️⃣ Configure the LLM that performs entity extraction
  const extractionSpec = await graphlit.createSpecification({
    name: 'Knowledge Graph Extraction',
    type: SpecificationTypes.Extraction,
    serviceType: ModelServiceTypes.OpenAi,
    openAI: {
      model: OpenAiModels.Gpt5_400K,
      temperature: 0,  // Deterministic for consistent extraction
    },
  });

  // 2️⃣ Build a workflow that prepares documents and extracts entities
  const workflow = await graphlit.createWorkflow({
    name: 'Entity Extraction Workflow',
    preparation: {
      jobs: [
        {
          connector: {
            type: FilePreparationServiceTypes.Document,
          },
        },
      ],
    },
    extraction: {
      jobs: [
        {
          connector: {
            type: EntityExtractionServiceTypes.ModelText,
            modelText: {
              specification: { id: extractionSpec.createSpecification.id },
              tokenThreshold: 32,
            },
            extractedTypes: [
              ObservableTypes.Person,
              ObservableTypes.Organization,
              ObservableTypes.Place,
              ObservableTypes.Event,
              ObservableTypes.Product,
              ObservableTypes.Software,
              ObservableTypes.Repo,
            ],
          },
        },
      ],
    },
  });

  console.log('✅ Workflow created:', workflow.createWorkflow.id);

  // 3️⃣ Ingest content with the workflow (extraction happens automatically)
  console.log('📄 Ingesting content...');
  const content = await graphlit.ingestText(
    SAMPLE_NOTE,
    'Sales Call Notes',
    undefined,
    undefined,
    undefined,
    undefined,
    true, // Wait for processing to finish
    { id: workflow.createWorkflow.id },
  );

  console.log('✅ Content ready:', content.ingestText.id);

  // 4️⃣ List extracted entities
  console.log('\n🔍 Extracted entities:');
  const observables = await graphlit.queryObservables();
  const results = observables.observables?.results ?? [];

  const format = (type: ObservableTypes) =>
    results
      .filter((entry) => entry?.type === type)
      .map((entry) => entry?.observable.name)
      .filter(Boolean) as string[];

  console.log('👥 People:', format(ObservableTypes.Person));
  console.log('🏢 Organizations:', format(ObservableTypes.Organization));
  console.log('📍 Places:', format(ObservableTypes.Place));
  console.log('🛠️ Products/Software:', [
    ...format(ObservableTypes.Product),
    ...format(ObservableTypes.Software),
  ]);

  return content.ingestText.id;
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

**What happens:**

* Creates extraction specification (GPT-5, temperature 0 for deterministic results)
* Creates workflow with entity extraction
* Ingests sample text with the workflow
* Automatically extracts entities (people, organizations, places, etc.)
* Lists all extracted entities

Run: `npx tsx extract-entities.ts`

***

## Step 2: Explore Relationships in the Graph

Query content and visualize the entity relationships:

```typescript
import { Graphlit } from 'graphlit-client';

const graphlit = new Graphlit();

async function main() {
  // Query content by name (realistic production pattern)
  const contents = await graphlit.queryContents({ name: 'Sales Call Notes' });

  const contentId = contents.contents?.results?.[0]?.id;
  if (!contentId) {
    console.error('Content not found. Run Step 1 first.');
    process.exit(1);
  }

  console.log('🕸️ Exploring knowledge graph...\n');

  // Get the graph for this content
  const graph = await graphlit.queryContentsGraph({
    contents: [{ id: contentId }],
  });

  const nodes = (graph.contents?.graph?.nodes ?? []).filter(
    (node): node is NonNullable<typeof node> => Boolean(node),
  );
  const edges = (graph.contents?.graph?.edges ?? []).filter(
    (edge): edge is NonNullable<typeof edge> => Boolean(edge),
  );

  const nodeById = new Map(nodes.map((node) => [node.id, node]));

  console.log('🕸️ Relationships in this document:');
  edges.forEach((edge) => {
    const from = nodeById.get(edge.from);
    const to = nodeById.get(edge.to);
    if (!from || !to) return;
    const relation = edge.relation ? ` —${edge.relation}→ ` : ' → ';
    console.log(`${from.name}${relation}${to.name}`);
  });
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

This renders the knowledge graph for a document. Each node represents a person, organization, or event; edges capture relationships Graphlit inferred (e.g., "Sarah Chen → worksFor → Acme Corp").

***

## Step 3: Find Every Document Mentioning an Entity

```typescript
import { Graphlit } from 'graphlit-client';
import { ObservableTypes } from 'graphlit-client/dist/generated/graphql-types';

const graphlit = new Graphlit();

async function findContentForPerson(personName: string) {
  const observables = await graphlit.queryObservables();
  const matches = observables.observables?.results?.filter(
    (entry) =>
      entry?.type === ObservableTypes.Person &&
      entry.observable.name?.toLowerCase() === personName.toLowerCase(),
  );

  if (!matches?.length) {
    console.log(`No entities found for ${personName}`);
    return;
  }

  const { id } = matches[0]!.observable;
  const related = await graphlit.queryContents({
    observations: [
      {
        type: ObservableTypes.Person,
        observable: { id },
      },
    ],
  });

  const docs = related.contents?.results ?? [];
  console.log(`
📚 Documents mentioning ${personName}:`);
  docs.forEach((doc) => console.log(`- ${doc?.name} (${doc?.id})`));
}

findContentForPerson('Sarah Chen').catch((error) => {
  console.error(error);
  process.exit(1);
});
```

Filtering by `observations` lets you answer questions like "Show me every asset where Sarah Chen appears" or "Find all notes referencing Salesforce".

***

## Production Patterns

### Batch Ingestion + Polling

```typescript
// Query for your extraction workflow by name
const workflows = await graphlit.queryWorkflows({ name: 'Entity Extraction Workflow' });
const workflowId = workflows.workflows?.results?.[0]?.id;

// Ingest multiple files
const batch = await graphlit.ingestBatch(
  ['https://example.com/questionnaire.pdf', 'https://example.com/demo-notes.docx'],
  { id: workflowId },
);

// Poll until processing completes
await Promise.all(
  batch.ingestBatch.ids.map(async ({ id }) => {
    let done = false;
    while (!done) {
      const status = await graphlit.isContentDone(id);
      done = Boolean(status.isContentDone?.result);
      if (!done) await new Promise((r) => setTimeout(r, 1500));
    }
  }),
);
```

### Build Collections from Entities

```typescript
const observables = await graphlit.queryObservables();
const organizations = observables.observables?.results?.filter(
  (entry) => entry?.type === ObservableTypes.Organization,
);

if (organizations?.length) {
  const collection = await graphlit.createCollection({
    name: `${organizations[0]!.observable.name} Knowledge Base`,
  });

  const related = await graphlit.queryContents({
    observations: [
      {
        type: ObservableTypes.Organization,
        observable: { id: organizations[0]!.observable.id },
      },
    ],
  });

  const ids = (related.contents?.results ?? [])
    .filter(Boolean)
    .map((item) => ({ id: item!.id }));

  if (ids.length) {
    await graphlit.addContentsToCollections(ids, [{ id: collection.createCollection.id }]);
  }
}
```

### Keep Graph Conversations Focused

```typescript
// Query for the organization entity by name
const observables = await graphlit.queryObservables({
  filter: {
    types: [ObservableTypes.Organization],
    name: 'Acme Corp'
  }
});
const targetOrganizationId = observables.observables?.results?.[0]?.observable.id;

// Create conversation filtered to that organization
const conversation = await graphlit.createConversation({
  name: 'Acme Corp CRM Review',
  filter: {
    observations: targetOrganizationId
      ? [
          {
            type: ObservableTypes.Organization,
            observable: { id: targetOrganizationId },
          },
        ]
      : undefined,
  },
});

const response = await graphlit.promptConversation(
  'Summarize our interactions with Acme Corp this quarter.',
  conversation.createConversation.id,
);

console.log(response.promptConversation.message?.message);
```

***

## Real-World Examples

### 1. Customer 360

* Aggregate Slack, email, and meeting notes per account
* Surface key contacts + topics before every call
* Feed a sales agent that remembers context across teams

### 2. Incident Response Watchtower

* Track mentions of outages across Sentry, Slack, and PagerDuty
* Connect incidents to affected services and owners
* Push summaries to on-call staff in real time

### 3. Market Intelligence Radar

* Monitor competitor names across research docs and news
* Attach relationships between mentions, products, and regions
* Drive alerts when new entities (people or products) appear

***

## Next Steps

* [**Knowledge Graph Use Cases**](/api-guides/use-cases/knowledge-graph.md) – Deep-dive patterns for extraction, enrichment, and queries
* [**Context Engineering**](/tutorials/context-engineering.md) – Feed knowledge graph insights into retrieval

***

## Full Example: Production Agent

See the complete Next.js agent in [graphlit-samples](https://github.com/graphlit/graphlit-samples/tree/main/nextjs/chat-graph):

* Visual knowledge-graph explorer with streaming chat
* Entity filtering and relationship queries in the UI
* Production-ready environment configuration and error handling

***

**Build agents that understand your data model. Build with Graphlit.**


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.graphlit.dev/tutorials/knowledge-graph.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
