Skip to content

Commit 46dd050

Browse files
authored
[INTEG-3262] feat: create ai content type parser agent (#10255)
* feat: helper function to fetch content types * wip: content parser agent * chore:cleanup * feat: content type service * ct picker modal for testing * fix: allow for multi select in content type picker modal * calls app action * app action fetches a single content type * app action fetches multiple content types * app action calls agent!!! * fix: openai api key parameter bug * chore: cleanup config page * chore: cleanup variable names * cleanup:logs n comments * fic: agent loader * chore: cleanup styles * fix: creating a `getAppActionId()` util to clean up code
1 parent cf9ad3e commit 46dd050

File tree

15 files changed

+602
-208
lines changed

15 files changed

+602
-208
lines changed

apps/google-docs/contentful-app-manifest.json

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
"parameters": {
1414
"installation": [
1515
{
16-
"id": "apiKey",
16+
"id": "openAiApiKey",
1717
"type": "Secret",
18-
"name": "API Key",
18+
"name": "OpenAI API Key",
1919
"description": "API Key for the app to use the OpenAI API."
2020
}
2121
]
@@ -27,7 +27,9 @@
2727
"description": "Function to create content blocks from App Action.",
2828
"path": "functions/createEntriesFromDocument.js",
2929
"entryFile": "functions/createEntriesFromDocument.ts",
30-
"allowNetworks": [],
30+
"allowNetworks": [
31+
"https://api.openai.com"
32+
],
3133
"accepts": [
3234
"appaction.call"
3335
]
@@ -85,27 +87,5 @@
8587
]
8688
}
8789
],
88-
"actions": [
89-
{
90-
"id": "createEntriesFromDocumentAction",
91-
"name": "Create entries from an uploaded document",
92-
"type": "function-invocation",
93-
"functionId": "createEntriesFromDocumentFunction",
94-
"category": "Custom",
95-
"parameters": [
96-
{
97-
"id": "contentTypeId",
98-
"name": "Content Type ID",
99-
"type": "Symbol",
100-
"required": true
101-
},
102-
{
103-
"id": "prompt",
104-
"name": "Prompt from the user",
105-
"type": "Symbol",
106-
"required": true
107-
}
108-
]
109-
}
110-
]
90+
"actions": []
11191
}

apps/google-docs/functions/agents/contentTypeParser.agent.ts

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { createOpenAI } from '@ai-sdk/openai';
2+
import { FinalContentTypesResultSummary, FinalContentTypesAnalysisSchema } from './schema';
3+
import { generateObject } from 'ai';
4+
import { ContentTypeProps } from 'contentful-management';
5+
6+
export interface ContentTypeParserConfig {
7+
contentTypes: ContentTypeProps[];
8+
openAiApiKey: string;
9+
}
10+
11+
/**
12+
* AI Agent that parses an array of Contentful content types and generates structured summaries
13+
*
14+
* @param contentTypes - Array of Contentful content type objects
15+
* @param config - Optional configuration for the AI model
16+
* @returns Promise resolving to structured parse result with summaries
17+
*
18+
*/
19+
export async function analyzeContentTypes({
20+
contentTypes,
21+
openAiApiKey,
22+
}: ContentTypeParserConfig): Promise<FinalContentTypesResultSummary> {
23+
// TODO: Double check these values and make sure they are compatible because not every user will have a key
24+
// to access all models
25+
const modelVersion = 'gpt-4o';
26+
const temperature = 0.3;
27+
28+
const openaiClient = createOpenAI({
29+
apiKey: openAiApiKey,
30+
});
31+
32+
const prompt = buildAnalysisPrompt(contentTypes);
33+
34+
const result = await generateObject({
35+
model: openaiClient(modelVersion),
36+
schema: FinalContentTypesAnalysisSchema,
37+
temperature,
38+
system: buildSystemPrompt(),
39+
prompt,
40+
});
41+
42+
const finalAnalysis = result.object as FinalContentTypesResultSummary;
43+
return finalAnalysis;
44+
}
45+
46+
/**
47+
* Builds the system prompt for the AI
48+
*/
49+
function buildSystemPrompt(): string {
50+
return `You are an expert Contentful content modeling analyst. Your role is to analyze Contentful content type definitions and provide clear, actionable summaries.
51+
52+
Your analysis should:
53+
1. Identify the purpose and intended use of each content type
54+
2. Explain what each field represents and how it should be used
55+
3. Identify relationships between content types
56+
4. Assess the overall model complexity
57+
5. Provide practical recommendations for content editors and developers
58+
59+
Focus on clarity and actionability. Your summaries will be used by:
60+
- Content editors who need to understand how to use content types
61+
- Content strategists planning content architecture`;
62+
}
63+
64+
/**
65+
* Builds the analysis prompt from content type data
66+
*/
67+
function buildAnalysisPrompt(contentTypes: ContentTypeProps[]): string {
68+
const contentTypeList = contentTypes.map((ct) => ct.name).join(', ');
69+
const totalFields = contentTypes.reduce((sum, ct) => sum + (ct.fields?.length || 0), 0);
70+
71+
return `Analyze the following Contentful content type definitions and provide concise summaries.
72+
73+
CONTENT TYPES TO ANALYZE: ${contentTypeList}
74+
TOTAL CONTENT TYPES: ${contentTypes.length}
75+
TOTAL FIELDS ACROSS ALL TYPES: ${totalFields}
76+
77+
CONTENT TYPE DEFINITIONS:
78+
${JSON.stringify(contentTypes, null, 2)}
79+
80+
For each content type, provide:
81+
1. Clear description of what this content type represents
82+
2. The intended purpose and use cases
83+
3. Total field count
84+
4. Names of 3-5 most important/key fields
85+
5. 2-3 practical usage recommendations
86+
87+
Also provide:
88+
- Overall summary of the content model (2-3 sentences)
89+
- Complexity assessment (simple/moderate/complex as a string)
90+
91+
Keep responses concise and actionable.`;
92+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { z } from 'zod';
2+
3+
// Schema Definitions for the Content Type Parser Agent
4+
5+
// Each invidual CT analysis by the AI Agent output schema
6+
export const ContentTypeAnalysisSchema = z.object({
7+
id: z.string(),
8+
name: z.string(),
9+
description: z.string(),
10+
purpose: z.string(),
11+
fieldCount: z.number(),
12+
keyFields: z.array(z.string()),
13+
recommendations: z.array(z.string()),
14+
});
15+
16+
// The entire set of CT analyses by the AI Agent output schema
17+
export const FinalContentTypesAnalysisSchema = z.object({
18+
contentTypes: z.array(ContentTypeAnalysisSchema),
19+
summary: z.string(),
20+
complexity: z.string(),
21+
});
22+
23+
export type ContentTypeSummary = z.infer<typeof ContentTypeAnalysisSchema>;
24+
export type FinalContentTypesResultSummary = z.infer<typeof FinalContentTypesAnalysisSchema>;

apps/google-docs/functions/createEntriesFromDocument.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@ import type {
44
FunctionTypeEnum,
55
AppActionRequest,
66
} from '@contentful/node-apps-toolkit';
7-
// INTEG-3262 and INTEG-3263: Likely imports to be used are commented out for now
8-
// import { ContentTypeProps, EntryProps, ContentFields } from 'contentful-management';
9-
// import { KeyValueMap } from 'contentful-management';
10-
import { parseContentType } from './agents/contentTypeParser.agent';
7+
import { analyzeContentTypes } from './agents/contentTypeParserAgent/contentTypeParser.agent';
118
import { createDocument } from './agents/documentParser.agent';
12-
// import { createEntries, createAssets } from './service/entryService';
9+
import { fetchContentTypes } from './service/contentTypeService';
10+
import { initContentfulManagementClient } from './service/initCMAClient';
1311

1412
export type AppActionParameters = {
15-
contentTypeId: string;
13+
contentTypeIds: string[];
1614
prompt: string;
1715
};
1816
interface AppInstallationParameters {
@@ -30,28 +28,28 @@ export const handler: FunctionEventHandler<
3028
event: AppActionRequest<'Custom', AppActionParameters>,
3129
context: FunctionEventContext
3230
) => {
33-
const { contentTypeId, prompt } = event.body;
31+
const { contentTypeIds } = event.body;
3432
const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters;
3533

3634
// INTEG-3262 and INTEG-3263: Take in Content Type, Prompt, and Upload File from user
3735

38-
// INTEG-3262: Implement the content type parser agent
39-
const aiContentTypeResponse = parseContentType({
40-
openaiApiKey: openAiApiKey,
41-
modelVersion: 'gpt-4o',
42-
jsonData: contentType,
43-
});
36+
const cma = initContentfulManagementClient(context);
37+
38+
const contentTypes = await fetchContentTypes(cma, new Set<string>(contentTypeIds));
39+
const contentTypeParserAgentResult = await analyzeContentTypes({ contentTypes, openAiApiKey });
40+
41+
console.log('contentTypeParserAgentResult', contentTypeParserAgentResult);
4442

4543
// INTEG-3261: Pass the ai content type response to the observer for analysis
4644
// createContentTypeObservationsFromLLMResponse()
4745

4846
// INTEG-3263: Implement the document parser agent
49-
const aiDocumentResponse = createDocument({
50-
openaiApiKey: openAiApiKey,
51-
modelVersion: 'gpt-4o',
52-
jsonData: aiContentTypeResponse,
53-
document: document,
54-
});
47+
// const aiDocumentResponse = createDocument({
48+
// openaiApiKey: openAiApiKey,
49+
// modelVersion: 'gpt-4o',
50+
// jsonData: aiContentTypeResponse,
51+
// document: document,
52+
// });
5553

5654
// INTEG-3261: Pass the ai document response to the observer for analysis
5755
// createDocumentObservationsFromLLMResponse()
@@ -62,5 +60,5 @@ export const handler: FunctionEventHandler<
6260
// INTEG-3265: Create the assets in Contentful using the asset service
6361
// await createAssets()
6462

65-
return { success: true, response: {} };
63+
return { success: true, response: contentTypeParserAgentResult };
6664
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { PlainClientAPI } from 'contentful-management';
2+
3+
/*
4+
* Fetches the user selected content types that the user wants the AI to create entries for
5+
* @param cma Content Management API client
6+
* @param contentTypeIds Array of content type IDs
7+
* @returns array of Content type json objects
8+
*/
9+
export const fetchContentTypes = async (
10+
cma: PlainClientAPI,
11+
contentTypeIds: Set<string>
12+
): Promise<any> => {
13+
try {
14+
const response = await cma.contentType.getMany({});
15+
const selectedContentTypes = response.items.filter((item) => contentTypeIds.has(item.sys.id));
16+
return selectedContentTypes;
17+
} catch (error) {
18+
throw new Error(`Failed to fetch content types ${contentTypeIds}: ${error}`);
19+
}
20+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { FunctionEventContext } from '@contentful/node-apps-toolkit';
2+
import { PlainClientAPI, createClient } from 'contentful-management';
3+
4+
export function initContentfulManagementClient(context: FunctionEventContext): PlainClientAPI {
5+
if (!context.cmaClientOptions) {
6+
throw new Error(
7+
'Contentful Management API client options are only provided for certain function types. To learn more about using the CMA within functions, see https://www.contentful.com/developers/docs/extensibility/app-framework/functions/#using-the-cma.'
8+
);
9+
}
10+
return createClient(context.cmaClientOptions, {
11+
type: 'plain',
12+
defaults: {
13+
spaceId: context.spaceId,
14+
environmentId: context.environmentId,
15+
},
16+
});
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "CommonJS",
5+
"lib": ["ES2020"],
6+
"strict": true,
7+
8+
},
9+
"include": ["./**/*.ts"],
10+
"exclude": ["node_modules", "build"]
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineConfig } from 'vitest/config';
2+
import { loadEnv } from 'vite';
3+
import path from 'path';
4+
5+
export default defineConfig(({ mode }) => ({
6+
test: {
7+
globals: true,
8+
environment: 'node',
9+
// Load .env from parent directory (google-docs root)
10+
env: loadEnv(mode, path.resolve(__dirname, '..'), ''),
11+
// Don't use the React setup file for functions tests
12+
setupFiles: undefined,
13+
// Only include tests in the functions directory
14+
include: ['**/*.test.ts', '**/*.spec.ts'],
15+
},
16+
}));
17+

0 commit comments

Comments
 (0)