How to Schedule AI Content Creation Using Convex
This article is part of our 4-part series that covers the steps to build a fully functional app using Convex and Expo.
- User Authentication with Clerk and Convex
- Real-Time User Profiles with Convex and Clerk
- Implementing Real-Time AI Chat with Convex
- Automating Content with Convex Cron and ChatGPT (You are here)
Building on our previous implementation of ChatGPT-powered conversations, we'll now explore how to automate content generation using Convex's scheduling capabilities.
Now, we’re taking it a step further by automatically generating new knowledge from ChatGPT every minute for testing purposes. In this post, we’ll dive into Convex’s Cron functionality and explore how to effectively integrate it into an Expo app.
Overview
What will be covered?
This post demonstrates how to use Convex's Cron functionality to automate backend tasks. We'll build a practical example that automatically generates and stores Q&A content about Convex using ChatGPT.
Even if you’re not familiar with Convex, you can gain insights into its features and capabilities simply by following along. This automated scenario can serve as a practical reference for developers seeking inspiration for their own app projects.
Convex simplifies the implementation of Cron jobs, making it easier to automate processes in your backend.
What you'll learn
By the end of this section, you’ll have learned:
- Automating Q&A Generation
Discover how to use ChatGPT to automatically generate questions about a specific topic (e.g., Convex) and retrieve concise, useful answers.
- Using Convex Cron
Learn how to automate tasks at regular intervals using Cron jobs and integrate them seamlessly with Convex.
- Understanding Convex Actions
Explore how Convex Actions can be used to handle external API calls and asynchronous operations in your backend logic.
- Implementing Internal Functions
Learn how to write secure, server-only logic with Convex’s Internal Functions to ensure data integrity and privacy.
- Expanding Development Ideas
Apply these concepts to develop automated information systems or periodic data updates for other use cases, fueling your creativity in app development.
What is Convex Cron?
Convex Cron is a powerful scheduling feature that enables you to automatically execute tasks at specific intervals in your Convex backend. It simplifies repetitive backend operations and ensures reliable workflow automation.
Key Features
-
Serverless Environment. Schedule tasks without managing servers, ensuring a stable and scalable backend.
-
Precision Timing. Define task intervals using standard cron expressions or simple time-based configurations.
-
Seamless Integration. Easily integrate Cron jobs with Convex databases and APIs.
Implementing a Cron Job that Runs Every Minute
Here’s a step-by-step guide to implementing a minute-by-minute Cron job using Convex.
Step 1: Write an Internal Mutation
Since Cron jobs can’t directly call standard mutations, we need to create an Internal Mutation to handle data insertion into the messages
table, then set up our scheduled task.
The saveMessage
function we implemented in the last article cannot be directly used in a cron job. For cron jobs, you need to implement and execute the function using Internal Functions, as explained in the Convex documentation: Internal Functions.
Here’s how you define an Internal Mutation to add data to the messages
table:
1export const insertAiMessage = internalMutation({
2 args: v.object({
3 message: v.string(),
4 reply: v.string(),
5 author: v.id('users'),
6 }),
7 handler: async (ctx, {message, reply, author}) => {
8 await ctx.db.insert('messages', {
9 author,
10 message,
11 reply,
12 });
13 },
14});
15
Step 2: Creating a Cron Job
Now, let’s set up a Cron job that runs every minute.
Create a convex/crons.ts
file with the following implementation:
Here’s how the Cron job implementation looks:
1import {cronJobs} from 'convex/server';
2import {internal} from './_generated/api';
3import {Id} from './_generated/dataModel';
4
5const crons = cronJobs();
6
7crons.interval(
8 'Add AI message every minute',
9 {minutes: 1},
10 internal.messages.insertAiMessage,
11 {
12 author: 'j97d6x6smxpb8p2rhk96g13mpd71znb1' as Id<'users'>,
13 message: 'Hello',
14 reply: 'Hello, how can I help you?',
15 },
16);
17
18export default crons;
19
Key Configuration
- 🤔Interval Setting:
{minutes: 1}
tells Convex to run this job every minute. You can adjust this interval based on your needs. - Function Reference:
internal.messages.insertAiMessage
specifies which Internal Mutation to call. - Author ID: The
author
field requires a valid user ID. Using a hardcoded ID like'j97d6x6smxpb8p2rhk96g13mpd71znb1'
is not recommended for production, but work for our testing purposes.
Once you save the Cron function and start the server (npx convex dev
), you should see the Cron job running as expected by monitoring the job execution in your app. You should see it populate the database with new data every minute.
Enhancing with ChatGPT API
Now that the Cron job is working, let’s integrate the ChatGPT API to generate random questions and answers automatically. Here's how to extend your existing insertAiMessage
function:
1export const insertAiMessage = internalMutation({
2 args: v.object({
3 message: v.string(),
4 reply: v.string(),
5 author: v.id('users'),
6 }),
7 handler: async (ctx, {message, reply, author}) => {
8 const apiKey = process.env.OPENAI_API_KEY;
9
10 if (!apiKey) {
11 throw new Error('OpenAI API key is not set in environment variables.');
12 }
13
14 const systemPrompt =
15 'You are an assistant that generates random questions about the Convex framework and answers them concisely. Do not use prefixes like "Question:" or "Answer:" in your response.';
16
17 const response = await fetch('https://api.openai.com/v1/chat/completions', {
18 method: 'POST',
19 headers: {
20 'Content-Type': 'application/json',
21 Authorization: `Bearer ${apiKey}`,
22 },
23 body: JSON.stringify({
24 model: 'gpt-4o-mini',
25 messages: [
26 {role: 'system', content: systemPrompt},
27 {
28 role: 'user',
29 content: 'Generate a random question about Convex and answer it.',
30 },
31 ],
32 }),
33 });
34
35 console.log('response', response);
36
37 ...
38 },
39});
40
Encountering Issues with the Fetch Function
After implementing the code, I eagerly waited for the updated AI-generated message. Unfortunately, nothing happened 🤨. Curious about what went wrong, I checked the Convex dashboard logs and found the following errors:
The logs revealed that the fetch
function cannot be used directly within queries or mutations. Instead, Convex recommends implementing an Action for such cases, which we'll cover in the next section.
Step 3: Implementing a Convex Action
To resolve this issue, I created an Action to handle the fetch
call. Follow these steps to do the same:
- Create the Action File
In your project, create a file named convex/actions.ts
to implement the Action function.
- Set Up Environment Variables
Ensure your OpenAI API key is securely stored as an environment variable in Convex. Use the following command:
1npx convex env set OPENAI_API_KEY your-open-ai-key
2
Refer to the Convex documentation for more details on managing environment variables.
Writing the Action
Here's how to implement the Action step by step:
-
Inject the OpenAI API Key
1const apiKey = process.env.OPENAI_API_KEY; 2 3if (!apiKey) { 4 throw new Error('OpenAI API key is not set in environment variables.'); 5} 6 7const systemPrompt = 8 'You are an assistant that generates random questions about the Convex framework and answers them concisely. Do not use prefixes like "Question:" or "Answer:" in your response.'; 9
-
Define the System Prompt
Create a prompt instructing ChatGPT to generate concise and useful responses:
1const systemPrompt = 2 'You are an assistant that generates random questions about the Convex framework and answers them concisely. Do not use prefixes like "Question:" or "Answer:" in your response.'; 3
-
Call the OpenAI API
Use the
fetch
function to interact with OpenAI’s API:1const response = await fetch('https://api.openai.com/v1/chat/completions', { 2 method: 'POST', 3 headers: { 4 'Content-Type': 'application/json', 5 Authorization: `Bearer ${apiKey}`, 6 }, 7 body: JSON.stringify({ 8 model: 'gpt-4o-mini', 9 messages: [ 10 { role: 'system', content: systemPrompt }, 11 { role: 'user', content: 'Generate a random question about Convex and answer it.' }, 12 ], 13 }), 14}); 15 16if (!response.ok) { 17 throw new Error(`HTTP error! status: ${response.status}`); 18} 19
-
Store the Result in the Database
Parse the response and pass the result to the
insertAiMessage
mutation:1const data = await response.json(); 2const assistantMessage = data.choices?.[0]?.message?.content; 3 4if (!assistantMessage) { 5 throw new Error('Failed to retrieve a valid response from OpenAI.'); 6} 7 8const [question, ...answerParts] = assistantMessage.split('\n'); 9 10await ctx.runMutation(internal.messages.insertAiMessage, { 11 author, 12 message: question, 13 reply: answerParts.join('\n').trim(), 14}); 15
Complete Action Implementation
Here's the full implementation for action.ts
:
1import {internalAction} from './_generated/server';
2import {internal} from './_generated/api';
3import {v} from 'convex/values';
4
5export const fetchAndInsertAiMessage = internalAction({
6 args: v.object({
7 author: v.id('users'),
8 }),
9 handler: async (ctx, {author}) => {
10 const apiKey = process.env.OPENAI_API_KEY;
11
12 if (!apiKey) {
13 throw new Error('OpenAI API key is not set in environment variables.');
14 }
15
16 const systemPrompt =
17 'You are an assistant that generates random questions about the Convex framework and answers them concisely. Do not use prefixes like "Question:" or "Answer:" in your response.';
18
19 const response = await fetch('https://api.openai.com/v1/chat/completions', {
20 method: 'POST',
21 headers: {
22 'Content-Type': 'application/json',
23 Authorization: `Bearer ${apiKey}`,
24 },
25 body: JSON.stringify({
26 model: 'gpt-4o-mini',
27 messages: [
28 {role: 'system', content: systemPrompt},
29 {
30 role: 'user',
31 content: 'Generate a random question about Convex and answer it.',
32 },
33 ],
34 }),
35 });
36
37 if (!response.ok) {
38 throw new Error(`HTTP error! status: ${response.status}`);
39 }
40
41 const data = await response.json();
42 const assistantMessage = data.choices?.[0]?.message?.content;
43
44 if (!assistantMessage) {
45 throw new Error('Failed to retrieve a valid response from OpenAI.');
46 }
47
48 const [question, ...answerParts] = assistantMessage.split('\n');
49
50 await ctx.runMutation(internal.messages.insertAiMessage, {
51 author,
52 message: question,
53 reply: answerParts.join('\n').trim(),
54 });
55
56 return {question, answer: answerParts};
57 },
58});
59
Step 4: Updating the Cron Job
Now let's modify our Cron job to use the Action we just created. Update your crons.ts
file:
1import {cronJobs} from 'convex/server';
2import {internal} from './_generated/api';
3import {Id} from './_generated/dataModel';
4
5const crons = cronJobs();
6
7crons.interval(
8 'Add AI message every minute',
9 {minutes: 1},
10+ internal.actions.fetchAndInsertAiMessage,
11 {author: 'j97d6x6smxpb8p2rhk96g13mpd71znb1' as Id<'users'>},
12);
13
14export default crons;
15
Final Output
After implementing the Action and updating the Cron job, your application will automatically generate and store Q&A content about Convex every minute:
This implementation demonstrates several key Convex features:
- Cron jobs for automated task scheduling
- Actions for external API integration
- Real-time database updates that automatically reflect in your UI
- Type-safe backend operations with TypeScript
It also opens up exciting possibilities for AI-driven applications. For example, apps that deliver personalized educational content to children or provide real-time information updates. Convex makes it incredibly simple to build these features by offering seamless database integration, scheduling, and a clean dashboard for monitoring.
Conclusion
This tutorial has walked you through building a practical automated system with Convex. Starting from basic database operations, we progressed to creating a fully automated content generation system that showcases the simplicity and power of Convex's backend features.
For the complete code used in this project, check out the GitHub Pull Request. Convex provides an excellent foundation for creating intelligent, scalable apps, and we’re excited to see what you’ll build with it!
Convex is the sync platform with everything you need to build your full-stack project. Cloud functions, a database, file storage, scheduling, search, and realtime updates fit together seamlessly.