Skip to content

Commit

Permalink
Implement listContent
Browse files Browse the repository at this point in the history
  • Loading branch information
jmoseley committed Jun 20, 2024
1 parent 0280a1e commit 41a3505
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 43 deletions.
14 changes: 9 additions & 5 deletions client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class CortexClient {
async chat(opts: ClientCreateChatOptsSync): Promise<Chat>;
async chat(opts: ClientCreateChatOptsStreaming): Promise<StreamingChatResult>;
async chat(opts: ClientCreateChatOptsSync | ClientCreateChatOptsStreaming): Promise<Chat | StreamingChatResult> {
if(opts.stream === true) {
if (opts.stream === true) {
return Chat.create({
client: this.apiClient,
cortex: opts.cortex,
Expand All @@ -72,15 +72,15 @@ export class CortexClient {
}
}

async getChat(id: string){
async getChat(id: string) {
return Chat.get(this.apiClient, id);
}

async generateContent(opts: ClientCreateContentOptsSync): Promise<Content>
async generateContent(opts: ClientCreateContentOptsStreaming): Promise<StreamingContentResult>
async generateContent(opts: ClientCreateContentOptsSync | ClientCreateContentOptsStreaming) {
// note: this if statement is annoying but is necessary to appropriately narrow the return type
if(opts.stream === true) {
if (opts.stream === true) {
return Content.create({
client: this.apiClient,
cortex: opts.cortex,
Expand All @@ -105,7 +105,11 @@ export class CortexClient {
return Content.get(this.apiClient, id, version);
}

async listChats(){}
async listContent(paginationOptions?: { pageSize?: number; cursor?: string }) {
return Content.list(this.apiClient, paginationOptions);
}

async listChats() { }

async getCortex(name: string): Promise<Cortex> {
return Cortex.get(this.apiClient, name)
Expand All @@ -131,7 +135,7 @@ export class CortexClient {
return Catalog.configure(this.apiClient, name, opts);
}

async listCatalogs(){
async listCatalogs() {
return Catalog.list(this.apiClient);
}

Expand Down
74 changes: 57 additions & 17 deletions content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ const client = new CortexClient({
});


test('e2e catalog, cortex, and sync content generation workflow', {timeout: 120000}, async () => {
test('e2e catalog, cortex, and sync content generation workflow', { timeout: 120000 }, async () => {

client.configureOrg({
companyName: "Cortex Click",
companyInfo: "Cortex Click provides an AI platform for go-to-market. Cortex click allows you to index your enterprise knowledge base, and create agents called Cortexes that automate sales and marketing processes like SEO, content writing, RFP generation, customer support, sales document genearation such as security questionairres and more.",
personality: [ "friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
personality: ["friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
rules: ["never say anything disparaging about AI or LLMs", "do not offer discounts"],
})

const catalogName = `catalog-${Math.floor(Math.random() * 10000)}`

const config: CatalogConfig = {
description: "this catalog contains documentation from the cortex click marketing website",
instructions: [ "user this data set to answer user questions about the cortex click platform" ]
instructions: ["user this data set to answer user questions about the cortex click platform"]
};

// create
Expand All @@ -50,16 +50,16 @@ test('e2e catalog, cortex, and sync content generation workflow', {timeout: 1200
await catalog.upsertDocuments(documents);

const cortex = await client.configureCortex(`cortex-${Math.floor(Math.random() * 10000)}`, {
catalogs: [ catalog.name ],
catalogs: [catalog.name],
friendlyName: "Cortex AI",
instructions: [ "answer questions about the cortex click AI GTM platform"],
instructions: ["answer questions about the cortex click AI GTM platform"],
public: true,
});

// create content
const title = "Overview of the Cortex Click AI GTM Platform";
const prompt = "Write a blog post about the Cortex Click AI GTM Platform. Elaborate on scenarios, customers, and appropriate verticals. Make sure to mention the impact that AI can have on sales and marketing teams."
const content = await cortex.generateContent({title, prompt});
const content = await cortex.generateContent({ title, prompt });
const originalContent = content.content;
const originalTitle = content.title;
expect(content.content.length).toBeGreaterThan(1);
Expand All @@ -75,7 +75,7 @@ test('e2e catalog, cortex, and sync content generation workflow', {timeout: 1200
expect(getContent.commands.length).toBe(1);

// edit content
const editedContent = await content.edit({title: "foo", content: "bar"});
const editedContent = await content.edit({ title: "foo", content: "bar" });
expect(editedContent.content).toBe("bar");
expect(editedContent.title).toBe("foo");
expect(editedContent.version).toBe(1);
Expand Down Expand Up @@ -107,20 +107,20 @@ test('e2e catalog, cortex, and sync content generation workflow', {timeout: 1200
await catalog.delete();
});

test('test streaming content', {timeout: 120000}, async () => {
test('test streaming content', { timeout: 120000 }, async () => {

client.configureOrg({
companyName: "Cortex Click",
companyInfo: "Cortex Click provides an AI platform for go-to-market. Cortex click allows you to index your enterprise knowledge base, and create agents called Cortexes that automate sales and marketing processes like SEO, content writing, RFP generation, customer support, sales document genearation such as security questionairres and more.",
personality: [ "friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
personality: ["friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
rules: ["never say anything disparaging about AI or LLMs", "do not offer discounts"],
})

const catalogName = `catalog-${Math.floor(Math.random() * 10000)}`

const config: CatalogConfig = {
description: "this catalog contains documentation from the cortex click marketing website",
instructions: [ "user this data set to answer user questions about the cortex click platform" ]
instructions: ["user this data set to answer user questions about the cortex click platform"]
};

// create
Expand All @@ -147,9 +147,9 @@ test('test streaming content', {timeout: 120000}, async () => {
await catalog.upsertDocuments(documents);

const cortex = await client.configureCortex(`cortex-${Math.floor(Math.random() * 10000)}`, {
catalogs: [ catalog.name ],
catalogs: [catalog.name],
friendlyName: "Cortex AI",
instructions: [ "answer questions about the cortex click AI GTM platform"],
instructions: ["answer questions about the cortex click AI GTM platform"],
public: true,
});

Expand All @@ -159,7 +159,7 @@ test('test streaming content', {timeout: 120000}, async () => {
const statusStream = new Readable({
read() { }
});
const { content, contentStream } = await client.generateContent({ cortex, prompt, title, stream: true, statusStream});
const { content, contentStream } = await client.generateContent({ cortex, prompt, title, stream: true, statusStream });
let fullContent = ""
contentStream.on('data', (data) => {
fullContent += data.toString();
Expand All @@ -172,7 +172,7 @@ test('test streaming content', {timeout: 120000}, async () => {
statusStream.on('data', (data) => {
const message = JSON.parse(data);
expect(message.messageType).toBe("status");
switch(message.step) {
switch (message.step) {
case "plan":
sawPlan = true;
break;
Expand Down Expand Up @@ -200,7 +200,7 @@ test('test streaming content', {timeout: 120000}, async () => {

const refinedContentPromise = await contentResult.refine({ prompt: refinePrompt, stream: true });
let fullRefinedContent = "";
refinedContentPromise.contentStream.on('data', (data)=> {
refinedContentPromise.contentStream.on('data', (data) => {
fullRefinedContent += data.toString();
});

Expand All @@ -218,13 +218,13 @@ test('e2e content without any catalogs', { timeout: 120000 }, async () => {
client.configureOrg({
companyName: "Cortex Click",
companyInfo: "Cortex Click provides an AI platform for go-to-market. Cortex click allows you to index your enterprise knowledge base, and create agents called Cortexes that automate sales and marketing processes like SEO, content writing, RFP generation, customer support, sales document genearation such as security questionairres and more.",
personality: [ "friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
personality: ["friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
rules: ["never say anything disparaging about AI or LLMs", "do not offer discounts"],
})

const cortex = await client.configureCortex(`cortex-${Math.floor(Math.random() * 10000)}`, {
friendlyName: "Cortex AI",
instructions: [ "answer questions about the cortex click AI GTM platform"],
instructions: ["answer questions about the cortex click AI GTM platform"],
public: true,
});

Expand All @@ -238,3 +238,43 @@ test('e2e content without any catalogs', { timeout: 120000 }, async () => {
expect(content.version).toBe(0);
expect(content.commands.length).toBe(1);
});

test('list content', { timeout: 120000 }, async () => {
client.configureOrg({
companyName: "Cortex Click",
companyInfo: "Cortex Click provides an AI platform for go-to-market. Cortex click allows you to index your enterprise knowledge base, and create agents called Cortexes that automate sales and marketing processes like SEO, content writing, RFP generation, customer support, sales document genearation such as security questionairres and more.",
personality: ["friendly and helpful", "expert sales and marketing professional", "experienced software developer"],
rules: ["never say anything disparaging about AI or LLMs", "do not offer discounts"],
})

const cortex = await client.configureCortex(`cortex-list-content-test`, {
friendlyName: "Cortex AI",
instructions: ["answer questions about the cortex click AI GTM platform"],
public: true,
});

// create content
const title = "Overview of the Cortex Click AI GTM Platform";
const prompt = "Write a blog post about the Cortex Click AI GTM Platform. Elaborate on scenarios, customers, and appropriate verticals. Make sure to mention the impact that AI can have on sales and marketing teams."
const content = await cortex.generateContent({ title, prompt });

let contentList = (await client.listContent({ pageSize: 200 }));
// find the thing we just created
while (contentList.contents.filter(c => c.id === content.id).length === 0 && contentList.contents.length > 0) {
contentList = await contentList.nextPage();
}
expect(contentList.contents.filter(c => c.id === content.id)).toHaveLength(1) // filter to just the stuff we created in this test
expect(contentList.contents.find(c => c.id === content.id)?.latestVersion).toBe(0);

// check that it returns multiple items
await client.generateContent({ cortex, title: "foo", prompt: "bar" });
const contentList3 = (await client.listContent());
expect(contentList3.contents.length).toBeGreaterThan(1);

// check that we can get the next page
const contentList4 = (await client.listContent({ pageSize: 1 }));
expect(contentList4.contents.length).toBe(1);
const contentList5 = await contentList4.nextPage();
expect(contentList5.contents.length).toBe(1);
expect(contentList5.contents[0].id).not.toBe(contentList4.contents[0].id);
})
63 changes: 49 additions & 14 deletions content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ export type EditContentOpts = {
content?: string;
}

export type ContentListResult = {
title: string;
latestVersion: number;
id: string;
Content(): Promise<Content>;
}

export type ContentListRequestResult = { nextPage: () => Promise<ContentListRequestResult>, contents: ContentListResult[] };

export class Content {
get id() {
Expand Down Expand Up @@ -86,7 +94,7 @@ export class Content {
static async create(opts: CreateContentOptsStreaming): Promise<StreamingContentResult>;
static async create(opts: CreateContentOptsSync | CreateContentOptsStreaming): Promise<Content | StreamingContentResult> {
// note: this if statement is annoying but is necessary to appropriately narrow the return type
if(isCreateContentOptsSync(opts)) {
if (isCreateContentOptsSync(opts)) {
return this.createContentSync(opts);
} else {
return this.createContentStreaming(opts);
Expand All @@ -95,16 +103,16 @@ export class Content {
}

private static async createContentSync(opts: CreateContentOptsSync): Promise<Content> {
const { client, cortex, title, prompt} = opts;
const res = await client.POST(`/content`, { cortex: cortex.name, title, prompt });
const body = await res.json();
return new Content(client, body.id, body.title, body.content, body.commands, body.version);
const { client, cortex, title, prompt } = opts;
const res = await client.POST(`/content`, { cortex: cortex.name, title, prompt });
const body = await res.json();

return new Content(client, body.id, body.title, body.content, body.commands, body.version);
}

private static async createContentStreaming(opts: CreateContentOptsStreaming): Promise<StreamingContentResult> {
const { client, cortex, title, prompt, stream} = opts;
const res = await client.POST(`/content`, { cortex: cortex.name, title, prompt, stream});
const { client, cortex, title, prompt, stream } = opts;
const res = await client.POST(`/content`, { cortex: cortex.name, title, prompt, stream });
const reader = res.body!.getReader();
const decoder = new TextDecoder('utf-8');

Expand All @@ -115,17 +123,17 @@ export class Content {
const readableStream = new Readable({
read() { }
});

const contentPromise = processStream(reader, decoder, readableStream, opts.statusStream).then(content => {
return new Content(client, id, title, content, commands, version, cortex.name);
});

return { contentStream: readableStream, content: contentPromise};
return { contentStream: readableStream, content: contentPromise };
}

static async get(client: CortexApiClient, id: string, version?: number): Promise<Content> {
let res: Response;
if(version !== undefined) {
if (version !== undefined) {
res = await client.GET(`/content/${id}/version/${version}`);
} else {
res = await client.GET(`/content/${id}`);
Expand Down Expand Up @@ -159,7 +167,7 @@ export class Content {
async refine(opts: RefineContentOptsSync): Promise<Content>;
async refine(opts: RefineContentOptsStreaming): Promise<StreamingContentResult>;
async refine(opts: RefineContentOptsSync | RefineContentOptsStreaming): Promise<Content | StreamingContentResult> {
if(isRefineContentOptsSync(opts)) {
if (isRefineContentOptsSync(opts)) {
return this.refineContentSync(opts);
} else {
return this.refineContentStreaming(opts);
Expand Down Expand Up @@ -193,13 +201,13 @@ export class Content {
const readableStream = new Readable({
read() { }
});

const contentPromise = processStream(reader, decoder, readableStream, opts.statusStream).then(content => {
this._content = content;
return this;
});

return { contentStream: readableStream, content: contentPromise};
return { contentStream: readableStream, content: contentPromise };
}

async revert(version: number) {
Expand All @@ -219,6 +227,33 @@ export class Content {

return this;
}

static async list(client: CortexApiClient, paginationOpts?: { cursor?: string, pageSize?: number }): Promise<ContentListRequestResult> {
const contents: ContentListResult[] = [];

const query = new URLSearchParams();
if (paginationOpts?.cursor) {
query.set("cursor", paginationOpts.cursor);
}
query.set("pageSize", (paginationOpts?.pageSize || 50).toString());
const res = await client.GET(`/content?${query.toString()}`);
if (res.status !== 200) {
throw new Error(`Failed to list content: ${res.statusText}`);
}
const body = await res.json();
for (let content of body.contents) {
contents.push({
title: content.title,
latestVersion: content.latestVersion,
id: content.contentId,
Content: () => { return Content.get(client, content.contentId) }
})
}

const cursor = body.cursor;
const pageSize = paginationOpts?.pageSize;
return { contents, nextPage: async () => { return Content.list(client, { cursor, pageSize }) } };
}
}

function isCreateContentOptsSync(opts: CreateContentOptsSync | CreateContentOptsStreaming): opts is CreateContentOptsSync {
Expand Down
Loading

0 comments on commit 41a3505

Please sign in to comment.