From 79c5f2a56985a9505bf0029862ed555f4d5d797a Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Fri, 1 Aug 2025 17:00:25 -0400 Subject: [PATCH 1/2] feat: update OpenAI model and enhance TipTap JSON schema for policy updates - Changed the OpenAI model from 'o4-mini' to 'gpt-4o-mini' for generating security policies. - Introduced a new TipTap JSON schema to validate the structure of the generated JSON content for policies. - Updated the schema used in the generateObject function to utilize the new TipTap JSON schema for improved data handling. --- .../jobs/tasks/onboarding/update-policies.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/app/src/jobs/tasks/onboarding/update-policies.ts b/apps/app/src/jobs/tasks/onboarding/update-policies.ts index 24ae96a50..e35f91142 100644 --- a/apps/app/src/jobs/tasks/onboarding/update-policies.ts +++ b/apps/app/src/jobs/tasks/onboarding/update-policies.ts @@ -52,7 +52,7 @@ export const updatePolicies = schemaTask({ try { const { text } = await generateText({ - model: openai('o4-mini'), + model: openai('gpt-4o-mini'), system: 'You are an expert at writing security policies in TipTap JSON.', prompt: `Update the following policy to be strictly aligned with SOC 2 standards and controls. Only include JSON content as your output. @@ -64,13 +64,31 @@ export const updatePolicies = schemaTask({ return; } + // Define TipTap JSON schema + const tipTapNodeSchema: z.ZodType = z.lazy(() => + z.object({ + type: z.string(), + attrs: z.record(z.any()).optional(), + content: z.array(tipTapNodeSchema).optional(), + text: z.string().optional(), + marks: z + .array( + z.object({ + type: z.string(), + attrs: z.record(z.any()).optional(), + }), + ) + .optional(), + }), + ); + const { object } = await generateObject({ - model: openai('gpt-4.1-mini'), + model: openai('gpt-4o-mini'), mode: 'json', system: 'You are an expert at writing security policies in TipTap JSON.', prompt: `Convert the following text into TipTap JSON. Do not include any other text in your output: ${JSON.stringify(text)}`, schema: z.object({ - json: z.array(z.any()), + json: z.array(tipTapNodeSchema), }), }); From 1c799e2a687c759aa23a2e6d6eda13c5d6a8cbb3 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Fri, 1 Aug 2025 17:28:38 -0400 Subject: [PATCH 2/2] feat: streamline policy generation by directly producing TipTap JSON - Removed the intermediate text generation step and updated the policy generation process to directly create TipTap JSON. - Enhanced the prompt for generating SOC 2 compliant security policies, ensuring all formatting instructions are followed. - Adjusted the schema to validate the generated TipTap JSON structure more effectively. --- .../jobs/tasks/onboarding/update-policies.ts | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/apps/app/src/jobs/tasks/onboarding/update-policies.ts b/apps/app/src/jobs/tasks/onboarding/update-policies.ts index e35f91142..cd85ad69d 100644 --- a/apps/app/src/jobs/tasks/onboarding/update-policies.ts +++ b/apps/app/src/jobs/tasks/onboarding/update-policies.ts @@ -2,7 +2,7 @@ import { openai } from '@ai-sdk/openai'; import { db } from '@db'; import type { JSONContent } from '@tiptap/react'; import { logger, schemaTask } from '@trigger.dev/sdk/v3'; -import { generateObject, generateText, NoObjectGeneratedError } from 'ai'; +import { generateObject, NoObjectGeneratedError } from 'ai'; import { z } from 'zod'; import { generatePrompt } from '../../lib/prompts'; @@ -51,44 +51,32 @@ export const updatePolicies = schemaTask({ }); try { - const { text } = await generateText({ + // Generate TipTap JSON directly in one step to avoid malformed JSON issues + const { object } = await generateObject({ model: openai('gpt-4o-mini'), - system: 'You are an expert at writing security policies in TipTap JSON.', - prompt: `Update the following policy to be strictly aligned with SOC 2 standards and controls. Only include JSON content as your output. + mode: 'json', + system: `You are an expert at writing security policies. Generate content directly as TipTap JSON format. - ${prompt.replace(/\\n/g, '\n')}`, - }); +TipTap JSON structure: +- Root: {"type": "document", "content": [array of nodes]} +- Paragraphs: {"type": "paragraph", "content": [text nodes]} +- Headings: {"type": "heading", "attrs": {"level": 1-6}, "content": [text nodes]} +- Lists: {"type": "orderedList"/"bulletList", "content": [listItem nodes]} +- List items: {"type": "listItem", "content": [paragraph nodes]} +- Text: {"type": "text", "text": "content", "marks": [formatting]} +- Bold: {"type": "bold"} in marks array +- Italic: {"type": "italic"} in marks array - if (!text) { - logger.error(`Failed generating policy for ${policyId}`); - return; - } +IMPORTANT: Follow ALL formatting instructions in the prompt, implementing them as proper TipTap JSON structures.`, + prompt: `Generate a SOC 2 compliant security policy as a complete TipTap JSON document. - // Define TipTap JSON schema - const tipTapNodeSchema: z.ZodType = z.lazy(() => - z.object({ - type: z.string(), - attrs: z.record(z.any()).optional(), - content: z.array(tipTapNodeSchema).optional(), - text: z.string().optional(), - marks: z - .array( - z.object({ - type: z.string(), - attrs: z.record(z.any()).optional(), - }), - ) - .optional(), - }), - ); +INSTRUCTIONS TO IMPLEMENT IN TIPTAP JSON: +${prompt.replace(/\\n/g, '\n')} - const { object } = await generateObject({ - model: openai('gpt-4o-mini'), - mode: 'json', - system: 'You are an expert at writing security policies in TipTap JSON.', - prompt: `Convert the following text into TipTap JSON. Do not include any other text in your output: ${JSON.stringify(text)}`, +Return the complete TipTap document following ALL the above requirements using proper TipTap JSON structure.`, schema: z.object({ - json: z.array(tipTapNodeSchema), + type: z.literal('document'), + content: z.array(z.record(z.unknown())), }), }); @@ -98,7 +86,7 @@ export const updatePolicies = schemaTask({ id: policyId, }, data: { - content: object.json as JSONContent[], + content: object.content as JSONContent[], }, }); @@ -106,7 +94,7 @@ export const updatePolicies = schemaTask({ policyId, contextHub, policy, - updatedContent: text, + updatedContent: object, }; } catch (dbError) { logger.error(`Failed to update policy in database: ${dbError}`); @@ -128,7 +116,7 @@ export const updatePolicies = schemaTask({ throw aiError; } } catch (error) { - logger.error(`Unexpected error in populatePolicyWithAI: ${error}`); + logger.error(`Unexpected error in updatePolicies: ${error}`); throw error; } },