| name | add-knowledge-connector |
| description | Guide for adding a new Knowledge Connector to a Cognigy Extension. Knowledge Connectors enable Cognigy AI users to fetch data from external knowledge bases (like Confluence, SharePoint, wikis) and create Knowledge chunks for storage in vector databases to be used by AI Agents. This skill covers file structure, implementation patterns, and best practices. |
Adding a New Knowledge Connector to an Extension
Knowledge Connectors integrate external knowledge sources with Cognigy.AI's Knowledge AI system. They fetch content from third-party systems, process it into chunks, and make it searchable by AI Agents.
Overview
What is a Knowledge Connector?
- A component that fetches data from external knowledge bases
- Processes content into searchable Knowledge Chunks
- Enables AI Agents to access contextual information
- Integrates with Knowledge Stores in Cognigy.AI
Package Information:
Examples:
Required Inputs
Before creating a Knowledge Connector, gather:
- Connector Name (camelCase): e.g.,
confluenceConnector, sharePointConnector
- Display Label: User-friendly name shown in UI, e.g., "Confluence", "SharePoint Documents"
- Summary: Brief description of what the connector does
- Source System: The external system to connect to (Confluence, SharePoint, etc.)
- Authentication Method: How to authenticate (API key, OAuth, username/password)
- Content Retrieval Logic: How to fetch content from the source
- Chunking Strategy: How to split content into searchable chunks
File Structure
A typical Knowledge Connector extension includes:
extensions/{extension-name}/
├── src/
│ ├── connections/
│ │ └── {source}Connection.ts # Authentication credentials
│ ├── knowledge-connectors/
│ │ ├── {connector}Connector.ts # Main connector definition
│ │ └── helper/ # Optional utilities
│ │ ├── utils.ts # API calls, data fetching
│ │ ├── parser.ts # Content parsing logic
│ │ └── chunker.ts # Text chunking utilities
│ ├── nodes/ # Optional Flow Nodes
│ │ └── *.ts
│ └── module.ts # Extension module definition
├── package.json
├── tsconfig.json
└── README.md
Implementation Steps
Step 1: Create Connection Schema
File: src/connections/{source}Connection.ts
Define authentication credentials for the external system.
Simple Example (API Token):
import type { IConnectionSchema } from "@cognigy/extension-tools";
export const diffbotConnection: IConnectionSchema = {
type: "diffbot",
label: "Diffbot Connection",
fields: [{ fieldName: "accessToken" }],
};
Complex Example (Username/Password):
import type { IConnectionSchema } from "@cognigy/extension-tools";
export const confluenceConnection: IConnectionSchema = {
type: "confluence",
label: "Confluence Connection",
fields: [
{ fieldName: "domain" },
{ fieldName: "email" },
{ fieldName: "key" },
],
};
Common Connection Field Names:
accessToken - API token
apiKey - API key
username / password - Basic auth
email / key - Email + API token
domain / baseUrl - Service URL
organizationId - Organization identifier
Step 2: Create Knowledge Connector
File: src/knowledge-connectors/{connector}Connector.ts
This is the main connector logic.
Basic Structure:
import { createKnowledgeConnector } from "@cognigy/extension-tools";
import type { IKnowledge } from "@cognigy/extension-tools";
export const {connector}Connector = createKnowledgeConnector({
type: "{connector}Connector",
label: "{Display Label}",
summary: "Description of what this connector does",
fields: [
] as const,
function: async ({ config, api }) => {
},
});
Important Notes:
- Always add
as const after the fields array for proper TypeScript type inference
- The
config parameter is automatically typed based on your fields
- Import
IKnowledge type namespace for strongly-typed chunk parameters
Key Components:
| Property | Type | Description |
|---|
type | string | Unique identifier (camelCase with "Connector" suffix) |
label | string | Display name in UI |
summary | string | Brief description shown to users |
fields | array | Configuration fields for the connector |
function | async function | Main execution logic |
Step 3: Define Configuration Fields
Add fields to configure the connector. Users fill these when setting up the connector in Cognigy.AI and the fields
are vendor specific based on the data needed to connect and fetch content.
Field Type Reference:
| Type | Description | Use Case |
|---|
connection | Connection selector | Authentication credentials |
text | Single-line text | URLs, identifiers |
textArray | Multiple text values | List of URLs, keywords |
toggle | Boolean switch | Enable/disable features |
select | Dropdown menu | Choose from options |
number | Numeric input | Limits, counts, timeouts |
chipInput | Tag input | Source tags, categories |
Step 4: Implement Connector Function
The function is where you fetch content and create Knowledge Chunks.
Make a plan outlining steps to implement, taking into consideration the third-party library and API you will be working with. Common steps include:
- Validate configuration inputs
- Authenticate with the external system
- Fetch content based on configuration
- Process and chunk content
- Create Knowledge Source(s) and add chunks using the
api object methods.
Function Signature:
function: async ({ config, sources, api }) => {
const { connection, url, tags } = config;
}
function: async ({ config: { connection, url, tags }, sources, api }) => {
}
function: async ({ config, sources: currentSources, api }) => {
}
Available Parameters:
config: Object containing all field values (typed based on your fields with as const)
sources: Array of KnowledgeSource objects created in previous runs by this connector (useful for incremental updates)
- Commonly renamed to
currentSources for clarity in sync implementations
api: Knowledge Connector API object with methods:
createKnowledgeSource(params): Create a new knowledge source
upsertKnowledgeSource(params): Create or update a knowledge source (v0.17.0+)
createKnowledgeChunk(params): Add a chunk to a source
deleteKnowledgeSource(params): Delete a source (for cleanup)
Example Connector
Use when content is ready to use without complex processing.
function: async ({ config, sources, api }) => {
const { connection, sourceTags } = config;
const authToken = await authenticate(connection);
const chunks = await fetchData(connection)
const knowledgeSource = await api.createKnowledgeSource({
name: "Example Source",
description: "Description of the knowledge source",
tags: sourceTags,
chunkCount: chunks.length,
});
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId: knowledgeSource.knowledgeSourceId,
text: chunk.text,
data: chunk.data,
});
}
}
Step 5: Handle Errors
Implement proper error handling and cleanup.
Best Practices:
- Always wrap connector logic in try-catch
- Delete Knowledge Sources on failure to avoid orphaned sources
- Provide descriptive error messages
- Validate input before processing
function: async ({ config: { connection, url, sourceTags }, sources, api }) => {
const { apiKey } = connection as { apiKey: string };
if (!url) {
throw new Error("URL is required");
}
const knowledgeSource = await api.createKnowledgeSource({
name: "Example Source",
description: "Example description",
tags: sourceTags,
chunkCount: 1,
});
try {
const response = await fetch(url, {
headers: { "Authorization": `Bearer ${apiKey}` }
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const content = await response.text();
if (!content || content.length === 0) {
throw new Error("No content retrieved from URL");
}
const chunks = processContent(content);
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId: knowledgeSource.knowledgeSourceId,
text: chunk.text,
data: chunk.data,
});
}
} catch (error) {
await api.deleteKnowledgeSource({
knowledgeSourceId: knowledgeSource.knowledgeSourceId,
});
throw new Error(
`Failed to fetch content from ${url}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
Error Handling Patterns:
| Scenario | Action | Example |
|---|
| Invalid input | Throw error immediately | if (!url) throw new Error(...) |
| API failure | Delete source, re-throw | await api.deleteKnowledgeSource(...) |
| Empty results | Delete source, throw error | Check items.length === 0 |
| Partial failure | Continue or fail based on severity | Log warnings, skip invalid items |
Step 6: Register Connector in Module
File: src/module.ts
import { createExtension } from "@cognigy/extension-tools";
import { confluenceConnection } from "./connections/confluenceConnection";
import { confluenceConnector } from "./knowledge-connectors/confluenceConnector";
import { searchNode } from "./nodes/search";
export default createExtension({
nodes: [searchNode],
connections: [confluenceConnection],
knowledge: [confluenceConnector],
options: {
label: "Confluence",
},
});
Step 7: Create Helper Utilities
File: src/knowledge-connectors/helper/utils.ts
Create reusable helper functions, especially for content hash calculation:
Option 1: Chunk objects (when chunks have metadata)
import { createHash } from "node:crypto";
import type { IKnowledge } from "@cognigy/extension-tools";
export type ChunkContent = Pick<
IKnowledge.CreateKnowledgeChunkParams,
"text" | "data"
>;
export const calculateContentHash = (chunks: ChunkContent[]): string => {
const hash = createHash("sha256");
chunks.forEach((c) => {
hash.update(c.text);
});
return hash.digest("hex");
};
Option 2: Simplified string array
import { createHash } from "node:crypto";
export const calculateContentHash = (content: string[]): string => {
const hash = createHash("sha256");
content.forEach((c) => {
hash.update(c);
});
return hash.digest("hex");
};
Additional Helper Examples:
export const fetchDataFromSource = async (url: string, authToken: string) => {
const response = await fetch(url, {
headers: { Authorization: `Bearer ${authToken}` },
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
};
Key Points:
- Use
node:crypto for SHA-256 hashing (built-in, no dependencies)
- Choose hash function signature based on your chunk structure
- Prefer avoiding intermediate array creation when hashing large content for smaller memory footprint
- Hash all chunk text content for accurate change detection
- Keep utility functions pure and testable
Step 8: Update Package Dependencies
File: package.json
Add any required dependencies:
{
"dependencies": {
"@cognigy/extension-tools": "^0.17.0",
"@langchain/textsplitters": "^0.0.3",
"axios": "^1.6.0",
"jsdom": "^24.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.0.0"
}
}
Version Notes:
- Use
@cognigy/extension-tools version ^0.17.0 or later for full Knowledge Connector support
- v0.17.0+ includes
upsertKnowledgeSource for incremental updates and sources parameter
- Check NPM for the latest version
- Older versions (< 0.16.0) may not support all Knowledge Connector features
Step 9: Create README Documentation
File: README.md
Document the connector for users:
# {Extension Name}
Integrates Cognigy.AI with {Source System} ({URL})
## Connection
This extension requires a connection with the following fields:
- **{field1}**: Description of field1
- **{field2}**: Description of field2
**How to get credentials:**
1. Step-by-step instructions
2. Screenshots if helpful
3. Link to official documentation
## Knowledge Connectors
### {Connector Name}
Extracts content from {source} and creates Knowledge Chunks for use with Knowledge AI.
**Configuration:**
- **Connection**: Select your {source} connection
- **{Field 1}**: Description and example
- **{Field 2}**: Description and example
- **Source Tags**: Tags to categorize the knowledge source (e.g., "confluence", "documentation")
**How it works:**
1. Connects to {source} using provided credentials
2. Fetches content from specified {location/URL/space}
3. Processes content into searchable chunks
4. Creates Knowledge Source(s) with chunks
5. Makes content available to AI Agents via Knowledge AI
**Example Use Case:**
When building a support agent, use this connector to index your company's documentation. The AI Agent can then:
- Search for relevant information from {source}
- Provide accurate answers based on your documentation
- Cite specific sources when responding to users
## Nodes (if applicable)
### {Node Name}
Description of what the node does...
API Reference
Knowledge Connector API
The Knowledge Connector API is provided through the api parameter in the connector function.
Type Imports:
import type { IKnowledge } from "@cognigy/extension-tools";
type ChunkParams = IKnowledge.CreateKnowledgeChunkParams;
type SourceParams = IKnowledge.CreateKnowledgeSourceParams;
createKnowledgeSource(params)
Creates a new Knowledge Source. Returns an object with knowledgeSourceId.
const { knowledgeSourceId } = await api.createKnowledgeSource({
name: string,
description?: string,
tags?: string[],
chunkCount: number,
contentHashOrTimestamp?: string,
externalIdentifier?: string,
});
upsertKnowledgeSource(params) - New in v0.17.0
Creates a new Knowledge Source or updates an existing one based on externalIdentifier (or name if not provided). Returns { knowledgeSourceId } if created/updated, or null if source already exists and is up-to-date (based on contentHashOrTimestamp).
const result = await api.upsertKnowledgeSource({
name: string,
description?: string,
tags?: string[],
chunkCount: number,
contentHashOrTimestamp?: string,
externalIdentifier?: string,
});
Important: Always Handle Null Return Value
When upsertKnowledgeSource returns null, it means the source is already up-to-date and no chunks should be created. Always check for null before calling createKnowledgeChunk:
const result = await api.upsertKnowledgeSource({ });
if (result === null) {
continue;
}
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId: result.knowledgeSourceId,
text: chunk.text,
});
}
Use Case for upsertKnowledgeSource:
- Incremental updates: Only re-index if content changed
- Check
sources parameter for existing sources
- Use
contentHashOrTimestamp to detect changes
- Returns
null if source is up-to-date (skip re-indexing)
function: async ({ config, sources, api }) => {
const latestHash = await getContentHash(config.url);
const existingSource = sources.find(s => s.name === "My Source");
if (existingSource?.contentHashOrTimestamp === latestHash) {
return;
}
const result = await api.upsertKnowledgeSource({
name: "My Source",
contentHashOrTimestamp: latestHash,
chunkCount: chunks.length,
});
if (result === null) {
return;
}
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId: result.knowledgeSourceId,
text: chunk.text,
});
}
}
createKnowledgeChunk(params)
Adds a chunk to a Knowledge Source. Must be called after creating the source.
await api.createKnowledgeChunk({
knowledgeSourceId: string,
text: string,
data?: Record<string, string | number | boolean>
});
deleteKnowledgeSource(params)
Deletes a Knowledge Source and all its chunks. Used primarily for error handling/cleanup.
await api.deleteKnowledgeSource({
knowledgeSourceId: string,
});
Type-Safe Usage:
import type { IKnowledge } from "@cognigy/extension-tools";
type ChunkParams = IKnowledge.CreateKnowledgeChunkParams;
type SourceParams = IKnowledge.CreateKnowledgeSourceParams;
type KnowledgeSource = IKnowledge.KnowledgeSource;
type KnowledgeApi = IKnowledge.KnowledgeApi;
type ChunkContent = Pick<ChunkParams, "text" | "data">;
function processContent(content: string): ChunkContent[] {
return [{
text: content,
data: { processed: true }
}];
}
function findExistingSource(
sources: KnowledgeSource[],
name: string
): KnowledgeSource | undefined {
return sources.find(s => s.name === name || s.externalIdentifier === name);
}
Best Practices
1. Chunk Size and Overlap
Optimal chunk size: 1000-2000 characters
- Too small: Loss of context
- Too large: Irrelevant information interferes with retrieval
Chunk overlap: 100-200 characters
- Helps preserve context across chunk boundaries
- Ensures important information isn't split
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 2000,
chunkOverlap: 200,
});
2. Metadata in Chunks
Add metadata to chunks for better context:
await api.createKnowledgeChunk({
knowledgeSourceId,
text: chunkText,
data: {
url: sourceUrl,
author: pageAuthor,
lastModified: timestamp,
type: "documentation",
section: "API Reference",
},
});
3. Error Handling
Always clean up on failure:
try {
} catch (error) {
await api.deleteKnowledgeSource({ knowledgeSourceId });
throw error;
}
4. Incremental Updates with Syncing support (v0.17.0+)
Use upsertKnowledgeSource with content hashing and automatic cleanup for full sync behavior:
import { createHash } from "node:crypto";
import type { IKnowledge } from "@cognigy/extension-tools";
function calculateContentHash(chunks: Array<{ text: string }>): string {
const hash = createHash("sha256");
for (const chunk of chunks) {
hash.update(chunk.text);
}
return hash.digest("hex");
}
function: async ({ config, sources: currentSources, api }) => {
const items = await fetchItemsFromExternalSystem(config);
const updatedSources = new Set<string>();
for (const item of items) {
const chunks = await processItemIntoChunks(item);
const contentHash = calculateContentHash(chunks);
const result = await api.upsertKnowledgeSource({
name: item.title,
description: item.description,
chunkCount: chunks.length,
contentHashOrTimestamp: contentHash,
externalIdentifier: item.id,
});
updatedSources.add(item.id);
if (result === null) {
continue;
}
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId: result.knowledgeSourceId,
...chunk,
});
}
}
for (const source of currentSources) {
if (updatedSources.has(source.externalIdentifier)) {
continue;
}
await api.deleteKnowledgeSource({
knowledgeSourceId: source.knowledgeSourceId,
});
}
}
Key Patterns:
- Content hash: Calculate SHA-256 from chunk text content for accurate change detection
- Track updates: Use
Set<string> to track processed external identifiers
- Always track: Add to
updatedSources before checking if upsert returned null
- Cleanup: Delete sources whose
externalIdentifier is not in updatedSources
- Efficiency: Skip chunk creation when
upsertKnowledgeSource returns null
Benefits:
- Accurate change detection via content hashing
- Automatic removal of deleted/removed items from source system
- Minimal re-indexing (only changed content is updated)
- Full synchronization between external system and Knowledge AI
Testing Your Connector
Manual Testing Steps
- Install the Extension in Cognigy.AI
- Create a Connection with valid credentials
- Create a Knowledge Store
- Add your Knowledge Connector to the store
- Configure the connector with test data
- Run the connector and verify:
- Knowledge Sources are created
- Chunks contain correct content
- Tags are applied correctly
- Metadata is accurate
- Test in an AI Agent:
- Create a Flow with Knowledge AI
- Query the knowledge store
- Verify relevant chunks are retrieved
Complete Examples
Example 1: Simple Web Page Connector
import { createKnowledgeConnector } from "@cognigy/extension-tools";
import { JSDOM } from "jsdom";
async function splitTextIntoChunks(text: string, maxSize: number = 2000): Promise<string[]> {
const chunks: string[] = [];
for (let i = 0; i < text.length; i += maxSize) {
chunks.push(text.slice(i, i + maxSize));
}
return chunks;
}
export const webpageConnector = createKnowledgeConnector({
type: "webpageConnector",
label: "Web Page",
summary: "Extract content from web pages",
fields: [
{
key: "urls",
label: "URLs",
type: "textArray",
description: "List of web page URLs to extract content from",
params: { required: true },
},
{
key: "sourceTags",
label: "Source Tags",
type: "chipInput",
defaultValue: ["webpage"],
description: "Tags for filtering and categorization",
},
] as const,
function: async ({ config: { urls, sourceTags }, sources, api }) => {
for (const url of urls) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
}
const html = await response.text();
const dom = new JSDOM(html);
const document = dom.window.document;
const title = document.querySelector("title")?.textContent || url;
const text = document.body.textContent || "";
const chunks = await splitTextIntoChunks(text, 2000);
const { knowledgeSourceId } = await api.createKnowledgeSource({
name: title,
description: `Content from ${url}`,
tags: sourceTags,
chunkCount: chunks.length,
});
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId,
text: chunk,
data: { url },
});
}
}
},
});
Example 2: API-Based Connector with Pagination
import { createKnowledgeConnector } from "@cognigy/extension-tools";
import type { IKnowledge } from "@cognigy/extension-tools";
interface ApiResponse {
items: Array<{
id: string;
title: string;
description: string;
content: string;
category: string;
url: string;
}>;
hasMore: boolean;
}
export const apiConnector = createKnowledgeConnector({
type: "apiConnector",
label: "API Documentation",
summary: "Extract documentation from API",
fields: [
{
key: "connection",
label: "API Connection",
type: "connection",
params: {
connectionType: "api",
required: true,
},
},
{
key: "category",
label: "Category",
type: "text",
description: "Documentation category to fetch",
params: { required: true },
},
{
key: "maxPages",
label: "Maximum Pages",
type: "number",
defaultValue: 100,
description: "Maximum number of API pages to fetch",
},
{
key: "sourceTags",
label: "Source Tags",
type: "chipInput",
defaultValue: ["api-docs"],
},
] as const,
function: async ({
config: { connection, category, maxPages, sourceTags },
sources,
api
}) => {
const { apiKey } = connection as { apiKey: string };
let page = 1;
let hasMore = true;
const allItems: ApiResponse["items"] = [];
while (hasMore && page <= maxPages) {
const response = await fetch(
`https://api.example.com/docs?category=${category}&page=${page}`,
{
headers: { "Authorization": `Bearer ${apiKey}` },
}
);
if (!response.ok) {
throw new Error(`API request failed: ${response.statusText}`);
}
const data: ApiResponse = await response.json();
allItems.push(...data.items);
hasMore = data.hasMore;
page++;
await new Promise(resolve => setTimeout(resolve, 250));
}
if (allItems.length === 0) {
throw new Error(`No documentation found for category: ${category}`);
}
const { knowledgeSourceId } = await api.createKnowledgeSource({
name: `API Docs - ${category}`,
description: `Documentation for ${category}`,
tags: sourceTags,
chunkCount: allItems.length,
});
for (const item of allItems) {
const text = `${item.title}\n\n${item.description}\n\n${item.content}`;
await api.createKnowledgeChunk({
knowledgeSourceId,
text,
data: {
id: item.id,
category: item.category,
url: item.url,
title: item.title,
},
});
}
},
});
Example 3: Sample connector with full syncing support (v0.17.0+)
Based on Confluence connector implementation - demonstrates production-grade patterns:
import { createHash } from "node:crypto";
import { createKnowledgeConnector } from "@cognigy/extension-tools";
import type { IKnowledge } from "@cognigy/extension-tools";
function calculateContentHash(chunks: Array<{ text: string }>): string {
const hash = createHash("sha256");
for (const chunk of chunks) {
hash.update(chunk.text);
}
return hash.digest("hex");
}
export const productionConnector = createKnowledgeConnector({
type: "productionConnector",
label: "Production Connector",
summary: "Full sync with content hashing and automatic cleanup",
fields: [
{
key: "connection",
label: "API Connection",
type: "connection",
params: { connectionType: "api", required: true },
},
{
key: "baseUrl",
label: "Base URL",
type: "text",
description: "Base URL of the content source",
params: { required: true },
},
{
key: "sourceTags",
label: "Source Tags",
type: "chipInput",
defaultValue: ["incremental"],
},
] as const,
function: async ({ config, sources: currentSources, api }) => {
const { connection, baseUrl, sourceTags } = config;
const { apiKey } = connection as { apiKey: string };
const items = await fetchAllItems(baseUrl, apiKey);
const updatedSources = new Set<string>();
for (const item of items) {
const chunks = await fetchAndProcessChunks(baseUrl, apiKey, item.id);
const contentHash = calculateContentHash(chunks);
const result = await api.upsertKnowledgeSource({
name: item.title,
description: `Data from ${item.title}`,
tags: sourceTags,
chunkCount: chunks.length,
externalIdentifier: item.id,
contentHashOrTimestamp: contentHash,
});
updatedSources.add(item.id);
if (result === null) {
continue;
}
for (const chunk of chunks) {
await api.createKnowledgeChunk({
knowledgeSourceId: result.knowledgeSourceId,
...chunk,
});
}
}
for (const source of currentSources) {
if (updatedSources.has(source.externalIdentifier)) {
continue;
}
await api.deleteKnowledgeSource({
knowledgeSourceId: source.knowledgeSourceId,
});
}
},
});
async function fetchAllItems(baseUrl: string, apiKey: string) {
const response = await fetch(`${baseUrl}/api/items`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
return await response.json();
}
async function fetchAndProcessChunks(baseUrl: string, apiKey: string, itemId: string) {
const response = await fetch(`${baseUrl}/api/items/${itemId}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const content = await response.json();
return [
{
text: content.body,
data: { url: content.url, author: content.author },
},
];
}
Production Patterns from Real Implementations:
- ✅ Parameter naming:
sources: currentSources for clarity (all production connectors)
- ✅ Content hashing: SHA-256 hash from all chunk text
- ✅ Tracking:
Set<string> for processed external identifiers
- ✅ Always track: Add to set before checking upsert result
- ✅ Cleanup: Delete sources not in updated set
- ✅ Efficiency: Skip chunk creation when content unchanged
- ✅ External IDs: Use stable, unique identifiers from source system
- Confluence: Uses page ID
- Diffbot: Uses composite ID format
${index}.${diffbotUri}@${url}
Existing Connectors:
Checklist
Additional Resources
- Extension Tools Package
- Cognigy Documentation
- Example Code