///
/**
* Knowledge store examples: assets, stores, items, item collections, and search.
*
* Covers, end to end:
* - creating a knowledge store with each ingestionConfig variant
* (none / description enrichment / json_schema enrichment)
* - knowledge store CRUD (create, retrieve, update, list, delete)
* - adding assets as items and polling until ready
* - listing / filtering items by status
* - item collections (create, add items, list, update, remove, delete)
* - search: basic, groupBy=item, assetType filter, itemId filter,
* modality options, includeMetadata, and pagination
*
* Run:
* npx ts-node examples/knowledgeStores.ts
*/
import { TwelveLabs, TwelvelabsApi } from "twelvelabs-js";
import {
cleanupKnowledgeStore,
createAssetFromFile,
makeClient,
waitForAssetReady,
waitForItemReady,
VIDEO_PATH,
IMAGE_PATH,
} from "./_ksHelpers";
function printSearchResponse(resp: any, label: string) {
console.log(
`\n[${label}] ${resp.data.length} result(s); next_page_token=${
resp.nextPageToken ? "set" : "none"
}`
);
for (const hit of resp.data) {
if (hit.assetType === "video") {
console.log(` video rank=${hit.rank} item=${hit.itemId} matches=${hit.matches.length}`);
for (const m of hit.matches.slice(0, 3)) {
console.log(` ${m.startSec}-${m.endSec}s modalities=${JSON.stringify(m.modalities)}`);
}
} else {
console.log(` image rank=${hit.rank} item=${hit.itemId}`);
}
if (hit.metadata) console.log(` metadata=${JSON.stringify(hit.metadata)}`);
}
}
async function demoIngestionConfigVariants(client: TwelveLabs) {
console.log("\n=== Ingestion config variants ===");
const ksPlain = await client.knowledgeStores.create({
name: `ks-plain-${Date.now()}`,
description: "No enrichment config",
metadata: { team: "qa", purpose: "sdk-test" },
});
console.log(` plain: id=${ksPlain.id} metadata=${JSON.stringify(ksPlain.metadata)}`);
const ksDesc = await client.knowledgeStores.create({
name: `ks-desc-${Date.now()}`,
ingestionConfig: {
enrichmentConfig: {
type: "description",
description: "Extract the main subject, setting, and mood of each shot.",
},
},
});
console.log(` description-enrichment: id=${ksDesc.id}`);
const ksSchema = await client.knowledgeStores.create({
name: `ks-schema-${Date.now()}`,
ingestionConfig: {
enrichmentConfig: {
type: "json_schema",
jsonSchema: {
type: "object",
properties: {
subject: { type: "string", description: "The primary subject visible in the shot." },
setting: { type: "string", description: "Where the shot takes place." },
},
},
},
},
});
console.log(` json-schema-enrichment: id=${ksSchema.id}`);
for (const ks of [ksPlain, ksDesc, ksSchema]) {
await cleanupKnowledgeStore(client, ks.id!);
}
}
async function demoCrud(client: TwelveLabs) {
console.log("\n=== Knowledge store CRUD ===");
const ks = await client.knowledgeStores.create({ name: `ks-crud-${Date.now()}` });
console.log(` created: id=${ks.id} name=${ks.name}`);
const retrieved = await client.knowledgeStores.retrieve(ks.id!);
console.log(` retrieved: id=${retrieved.id} itemCount=${retrieved.itemCount}`);
const updated = await client.knowledgeStores.update(ks.id!, {
name: `ks-crud-updated-${Date.now()}`,
description: "Updated description",
metadata: { stage: "updated" },
});
console.log(
` updated: name=${updated.name} description=${updated.description} metadata=${JSON.stringify(
updated.metadata
)}`
);
// list() returns an auto-paginating Page (consistent with assets.list() /
// indexes.list()); `.data` is the current page, and it is also async-iterable
// for all pages.
console.log(" listing knowledge stores (first page):");
const listed = await client.knowledgeStores.list({ pageLimit: 5 });
for (const item of listed.data ?? []) {
console.log(` id=${item.id} name=${item.name} itemCount=${item.itemCount}`);
}
await cleanupKnowledgeStore(client, ks.id!);
}
async function demoItems(client: TwelveLabs, ksId: string): Promise> {
console.log("\n=== Items ===");
const items: Record = {};
// Add a video and an image to the knowledge store. Upload each file as an
// asset, wait for it to be ready, then create a knowledge store item from it.
const videoAssetId = await createAssetFromFile(client, VIDEO_PATH);
await waitForAssetReady(client, videoAssetId);
const videoItem = await client.knowledgeStoreItems.create(ksId, {
assetId: videoAssetId,
assetType: "video",
});
items.video = videoItem.id!;
console.log(` created video item: id=${videoItem.id}`);
const imageAssetId = await createAssetFromFile(client, IMAGE_PATH);
await waitForAssetReady(client, imageAssetId);
const imageItem = await client.knowledgeStoreItems.create(ksId, {
assetId: imageAssetId,
assetType: "image",
});
items.image = imageItem.id!;
console.log(` created image item: id=${imageItem.id}`);
// Wait for both items to finish processing.
for (const [kind, itemId] of Object.entries(items)) {
console.log(` waiting for ${kind} item ${itemId} to be ready ...`);
await waitForItemReady(client, ksId, itemId);
}
console.log(" list all items:");
const listed = await client.knowledgeStoreItems.list(ksId, {
page: 1,
pageLimit: 50,
sortBy: "created_at",
sortOption: "desc",
});
for (const it of listed.data ?? []) {
console.log(` id=${it.id} type=${it.assetType} status=${it.status}`);
}
console.log(" filter items by status=ready:");
const ready = await client.knowledgeStoreItems.list(ksId, {
status: ["ready"],
});
console.log(` ${(ready.data ?? []).length} ready item(s)`);
const single = await client.knowledgeStoreItems.retrieve(ksId, items.video);
console.log(
` retrieved item ${single.id}: type=${single.assetType} systemMetadata=${
single.systemMetadata !== undefined
}`
);
return items;
}
async function demoItemCollections(
client: TwelveLabs,
ksId: string,
items: Record
) {
console.log("\n=== Item collections ===");
const collection = await client.knowledgeStoreItemCollections.create(
ksId,
{
name: `collection-${Date.now()}`,
description: "A subset of items",
metadata: { group: "highlights" },
}
);
console.log(` created collection: id=${collection.id} name=${collection.name}`);
const allItemIds = Object.values(items);
await client.knowledgeStoreItemCollections.addItems(
ksId,
collection.id!,
{ itemIds: allItemIds }
);
console.log(` added ${allItemIds.length} item(s) to collection`);
const members =
await client.knowledgeStoreItemCollections.listItems(
ksId,
collection.id!
);
console.log(` collection now has ${(members.data ?? []).length} member item(s)`);
const updated = await client.knowledgeStoreItemCollections.update(
ksId,
collection.id!,
{ description: "Updated collection description" }
);
console.log(` updated collection description=${updated.description}`);
if (allItemIds.length > 0) {
await client.knowledgeStoreItemCollections.removeItems(
ksId,
collection.id!,
{ itemIds: [allItemIds[0]] }
);
console.log(` removed 1 item from collection`);
}
console.log(" listing collections:");
const cols = await client.knowledgeStoreItemCollections.list(ksId);
for (const c of cols.data ?? []) {
console.log(` id=${c.id} name=${c.name}`);
}
await client.knowledgeStoreItemCollections.delete(
ksId,
collection.id!
);
console.log(` deleted collection ${collection.id}`);
}
async function demoSearch(client: TwelveLabs, ksId: string, items: Record) {
console.log("\n=== Search ===");
// `searchOptions` selects which video modalities to match on.
const visualOnly: TwelvelabsApi.SearchKnowledgeStoreOptions = {
video: { modalities: ["visual"] },
};
let resp = await client.knowledgeStores.search(ksId, {
query: { text: "a person or animal moving" },
searchOptions: visualOnly,
});
printSearchResponse(resp, "basic (visual)");
resp = await client.knowledgeStores.search(ksId, {
query: { text: "a person or animal moving" },
searchOptions: visualOnly,
groupBy: "item",
});
printSearchResponse(resp, "groupBy=item");
resp = await client.knowledgeStores.search(ksId, {
query: { text: "dialogue or narration" },
filter: { assetType: { eq: "video" } },
searchOptions: {
video: {
modalities: ["visual", "audio"],
},
},
includeMetadata: true,
});
printSearchResponse(resp, "video-only + all modalities + metadata");
if (items.video) {
resp = await client.knowledgeStores.search(ksId, {
query: { text: "anything" },
filter: { itemId: { in: [items.video] } },
searchOptions: visualOnly,
});
printSearchResponse(resp, `itemId in [${items.video}]`);
}
// Paginate with a small page size, then fetch the next page with the token.
resp = await client.knowledgeStores.search(ksId, {
query: { text: "a person or animal moving" },
searchOptions: visualOnly,
pageSize: 1,
});
printSearchResponse(resp, "pageSize=1");
if (resp.nextPageToken) {
const page2 = await client.knowledgeStores.search(ksId, {
query: { text: "a person or animal moving" },
searchOptions: visualOnly,
pageSize: 1,
pageToken: resp.nextPageToken,
});
printSearchResponse(page2, "page 2 (via nextPageToken)");
}
}
async function main() {
const client = makeClient();
await demoIngestionConfigVariants(client);
await demoCrud(client);
// Full flow: create a store, add a video and an image item, then exercise
// items, collections, and search against it.
const ks = await client.knowledgeStores.create({ name: `ks-search-${Date.now()}` });
console.log(`\nCreated knowledge store: id=${ks.id} name=${ks.name}`);
try {
const items = await demoItems(client, ks.id!);
await demoItemCollections(client, ks.id!, items);
await demoSearch(client, ks.id!, items);
} finally {
await cleanupKnowledgeStore(client, ks.id!);
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});