Skip to content

Commit

Permalink
feat(blog): "Replacing complex UX patterns in Next.js using OpenAI o1" (
Browse files Browse the repository at this point in the history
#958)

* feat(blog): "Putting a Next.js contacts importer on autopilot with OpenAI o1 and Inngest"

* fix(blog): update asset

* fix(blog): avoid absolute links to inngest.com

* fix(blog): review changes
  • Loading branch information
charlypoly authored Oct 17, 2024
1 parent be3cf39 commit 30a23bc
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 0 deletions.
264 changes: 264 additions & 0 deletions pages/blog/_posts/nextjs-openai-o1-preview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
---
heading: "Replacing complex UX patterns in Next.js using OpenAI o1"
subtitle: A reimagined contacts importer leveraging the power of reasoning models with Inngest
showSubtitle: true
image: /assets/blog/nextjs-openai-o1-preview/featured-image.png
date: 2024-10-17
author: Charly Poly
disableCTA: false
---

Anyone who has already built a data-import user experience knows it requires some complex column-matching UI and many branches on the backend to safely parse and save the user-provided data:
![Contacts import UI are often complex for the end-user to use and the developers to develop and maintain.](/assets/blog/nextjs-openai-o1-preview/contact-importer-UI.png)

_Such UI is a slow and tedious experience for the end-user and a challenge to maintain for developers._

In this article, we will review a demo CRM Next.js putting a contacts import feature on autopilot, remarkably demonstrating the power of agentic workflows.

## What is an AI Agentic workflow?

An Agentic workflow is a new pattern consisting of relying on model reasoning to achieve a goal instead of providing a series of static prompts.
In short, the model is given a high-level goal to achieve and figures out the necessary steps to reach that goal.

Many [papers](https://arxiv.org/abs/2210.03629) and [articles](https://www.techzine.eu/blogs/applications/118176/the-ai-shift-from-prompt-engineering-to-flow-engineering/) have been published earlier this year, showcasing the [outstanding performance of agentic workflows](https://www.deeplearning.ai/the-batch/how-agents-can-improve-llm-performance/) compared to standard prompting techniques.
It turns out that agentic workflows, by combining reflection (reasoning), planning, and tools, lead to more precise answers, [solving important integration issues](https://www.bbc.com/travel/article/20240222-air-canada-chatbot-misinformation-what-travellers-should-know) of AI in user-facing use cases.

We are now witnessing the rise of new tools that help with agentic workflow developments with LangGraph and, recently, [OpenAI's swarm](https://github.com/openai/swarm), along with their new reasoning model: [`o1`](https://openai.com/o1/).
Adding Agentic workflows to an application comes with many challenges:

- **Duration volatility**: Models are responsible for reflecting and planning all the steps to reach a given goal, resulting in significant variance in the workflow duration.
- **Reliability**: Agentic workflows rely on more tools than regular prompt-designed workflows. Adding more tools calling external APIs based on LLM-generated parameters raises the risk of failure. Recovering from failure enables the model to self-correct during the workflow execution.
- **Unpredictability**: Pushing an agentic workflow to production requires setting up some safeguards on cost, duration, and output validation.

Let's now look at our Next.js CRM demo, featuring an autopilot contacts import feature.
We'll see that agentic workflows mostly reuse existing patterns found in Next.js and can already be used today to bring brand-new capabilities to Next.js applications.

## Designing an agentic workflow with OpenAI o1

We will design our CRM's autopilot contact importer feature by leveraging the new OpenAI `o1` reasoning model to plan the best steps to import a given file by:

- Ensuring that the fields are properly set, without typos
- Ranking the provided contact as good software Sales lead dynamically based on the provided columns
- Matching provided column by our CRM ones: Name, Email, Role, Company

Open AI `o1` will take this high-level goal to generate a set of steps leveraging our existing handwritten actions (`convert`, `enrich`, `save`) and generating custom OpenAI calls (if necessary) while managing cost.

Here's a visualization of our autopilot import contacts feature in our Next.js application:
![The image shows a workflow where a user uploads a contact list to a Next.js app, triggering Inngest via Vercel AI SDK to process and enrich the data using OpenAI before saving it to a database. The system manages contact parsing, enrichment, dynamic OpenAI calls, and final database storage, completing the workflow with custom steps.](/assets/blog/nextjs-openai-o1-preview/workflow-overview.jpg)

## Creating our contacts importer UI with Vercel v0

Our import contacts UI has been, of course, generated with [Vercel v0](https://v0.dev/):
![Our contact importer UI features a file selector, upload CTA and a list of contacts.](/assets/blog/nextjs-openai-o1-preview/demo-UI.png)

Once a file is selected, we call a [Server Action](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms) with the `FormData,` which triggers a `”contacts.uploaded”` event to Inngest.

```typescript app/actions/index.ts
"use server";

import { inngest } from "@/lib/inngest/client";

export async function uploadFile(formData: FormData) {
const file = formData.get("file") as File;
const fileText = await file.text();

await inngest.send({
name: "contacts.uploaded",
data: {
contactFileContent: fileText,
},
});
}
```

The front end does not expect the import to be instantaneous, and also, because the call to OpenAI's o1-preview may take multiple minutes, the actual processing and generation of the contact import workflow are performed in the background by an Inngest Function: `generateImportWorkflow()`. Let's look at its implementation.

## Asking OpenAI o1 to generate an import workflow

Our first Inngest Function, `generateImportWorkflow(),` leverages Vercel Edge Function's streaming capabilities to call OpenAI's `o1-preview` model and generate a tailored import workflow for our contact list without duration limitation.

Let's take a closer look at our `o1-preview` prompt:

```typescript lib/inngest/functions/generateImportWorkflow.ts
const prompt = (contactsFileContent: string) => `
Considering the following available actions:
${actions
.filter(({ kind }) => kind !== "openaiCall")
.map(({ kind, description }) => `- ${description}, name: ${kind}\n`)}
and, given the below CSV file (between \`\`\`), recommend the best steps by balancing existing actions and using custom openai calls (do not use deprecated models) to successfully:
- parse the contact information and remove any typos
- enrich the contacts data
- label them a decider and rank them as a good Sales target to sale software to based on the provided property
- rework the CSV file to match the provided column to the following: Name, Position, Company, Email and the ranking and decider columns
- save the contacts to the database
Return a JSON array made of steps to execute. When a step is a model call, provide the "model" for model name and "prompt" for the prompt with "{data}" as a placeholder for the provided data; When the step is an action, provide the action name.
\`\`\`
${contactsFileContent}
\`\`\`
`;

```

The above prompts demonstrate the power of reasoning models like `o1-preview`. From high-level goals, the model takes a few minutes to find the best chain of predefined actions and additional model calls to perform to successfully parse, transform, rank, and save the contacts.

We are using the stellar [Vercel AI SDK](https://sdk.vercel.ai/docs/reference/ai-sdk-core/generate-text) to call `o1-preview`. Here is an example of the actions generated by the model:

```json
[
{
"action": "convert"
},
{
"model": "gpt-3.5-turbo",
"prompt": "Parse the following contact information from JSON, correct any typos, and ensure all fields are properly formatted."
},
{
"action": "enrich"
},
{
"model": "gpt-3.5-turbo",
"prompt": "Label each contact as a decider and rank them as a good sales target for software based on their company, role, and industry."
},
{
"model": "gpt-3.5-turbo",
"prompt": "Reformat the following JSON data to match the columns: Name, Position, Company, Email."
},
{
"action": "save"
}
]
```

The actions generated will vary depending on the provided CSV file.
The code then transforms these actions into a format using [Inngest's Workflow Kit](/docs/reference/workflow-kit/workflow-instance) which can easily handle o1's multi-step plans and execute them. The transformed plan is then send to the importContacts Inngest Function to execute the plan.

<Callout>
**Note: `o1-preview` vs. `o1-mini` accuracy**
`o1-mini` is a preferred choice when developing, offering faster iterations and lower costs.
While developing this demo, we realized that `o1-mini` struggled to output a deterministic and simple JSON structure for model call actions. Switching back to `o1-preview` solved this problem.
</Callout>

Our `o1-preview` call and the processing of its results is wrapped as an Inngest Function, enabling it to run without duration limitation in the background and matching `o1-preview`'s [API Rate limits](https://x.com/OpenAIDevs/status/1841176573527077235) by leveraging [throttling](/docs/guides/throttling):

```typescript lib/inngest/functions/generateImportWorkflow.ts
// imports ...

export default inngest.createFunction(
{
id: "generate-import-workflow",
throttle: {
limit: 5000,
period: "1m",
},
},
{ event: "contacts.uploaded" },
async ({ event, step }) => {
const generatedStepsResult = await step.run(
"openai-o1-generate-steps",
async () => {
// ...
}
)
)
```
*Access the full source code [here](https://github.com/inngest/vercel-ai-open-o1-crm-agent/blob/main/lib/inngest/functions/generateImportWorkflow.ts).*
## Executing the dynamic workflow with Inngest's Workflow Kit
Our `importContact()` Inngest Function leverages the [Inngest Workflow Kit](/blog/introducing-workflow-kit) to execute a dynamic list of steps passed in the `contacts.process` event's body.
Our Inngest workflow is composed of 4 predefined actions:
- `openaiCall`: Make a dynamic call to OpenAI API
- `convert`: convert CSV data to JSON items
- `enrich`: Enrich contact information
- `save`: Save contact information to the database
Our `o1-preview` model leverages the `openaiCall` action to dynamically add actions, helping to format and structure the provided contact data. It takes two parameters: `prompt`, and `model` which the model configures according to the task to achieve.
Each workflow's action provides a `handler()` function that benefits from Inngest's features, such as [automatic retries](/blog/durable-functions-a-visual-typescript-primer) and throttling. These features improve the reliability of our agentic workflow and help manage its unpredictability.
An Inngest Workflow is then passed to our `importContacts()` Inngest Function for execution:
```typescript lib/inngest/functions/importContacts.ts
import { Engine, EngineAction } from "@inngest/workflow-kit";
import { inngest } from "../client";

export const actions: EngineAction[] = [
// other actions ...
{
kind: "save",
name: "Save contacts",
description: "save contact information to the database",
handler: async ({ state, step }) => {
await step.run("save-contacts-to-database", async () => {
const contacts = JSON.parse(state.get("contacts"));
await sql.query(
`INSERT INTO contacts (Name,Position,Company,Email,Decider,Ranking) VALUES ${contacts
.map((contact: any) => {
return `('${contact.Name}', '${contact.Position}', '${contact.Company}', '${contact.Email}', ${contact.Decider}, ${contact.Ranking})`;
})
.join()}`
);
});
},
}
];

const workflowEngine = new Engine({
actions,
loader: (event) => {
return event.data.workflowInstance;
},
});

export default inngest.createFunction(
{ id: "import-contacts" },
{ event: "contact.process" },
async ({ event, step }) => {
// When `run` is called, the loader function is called with access to the event
await workflowEngine.run({ event, step });
}
);
```
*Access the full source code [here](https://github.com/inngest/vercel-ai-open-o1-crm-agent/blob/main/lib/inngest/functions/importContacts.ts).*
## How our Agent performs
Our demo CRM application now features a smooth AI-powered contact import onboarding experience powered by a semi-autonomous agentic workflow as a complete part of the Next.js application ✨
Uploading the below CSV file sample generates a custom import workflow and runs smoothly in the background:
```csv
First Name,Last Name,Email,Seniority,Role,Company,Industry
Amanda,Brown,amanda.brown@example.com,VP,Data Scbentist,AutoCorp,Real Estate
Sarah,Johnson,sarah.johnson@example.com,Junior,CEO,BioGen,Education
Michael,Jackson,michael.jackson@example.com,Junior,CqO,AutoCorp,Healthcare
```
*Notice the typos in position, roles, and columns that do not match our contact properties.*
Once completed, the clean, structured, and enriched contact records are saved to the database:
![Contacts are inserted in the Vercel Postgres table](/assets/blog/nextjs-openai-o1-preview/vercel-postgres-contacts.png)
_Vercel Postgres database with the contact information loaded from the agentic workflow. **Our Agent event fixed the typos in contact properties like "Scbentist"**_
When deployed on Vercel, the workflow leverages Edge Function's streaming capabilities to reliably call `o1-preview` without duration considerations.
### Making our agent even smarter
This contacts import on autopilot is a first draft that can be used as a boilerplate to iterate and improve `o1`'s results.
A non-exhaustive list of interesting additional capabilities could be:
- Leveraging the OpenAI Batching API to reduce cost on large CSV files
- Have the model learn from previous runs to fine-tune its lead ranking mechanism
- Iterate and improve `o1`'s prompt by following [OpenAI's best practices](https://platform.openai.com/docs/guides/reasoning/advice-on-prompting)
## Try the demo now
The combination of Vercel AI SDK and Inngest makes it super easy to add Agentic workflows to provide more accurate (e.g., support chat) or achieve more complex tasks (e.g., data analysis, agent-based features).
**Try this demo now** by running it locally or deploying it on Vercel: [https://github.com/inngest/vercel-ai-open-o1-crm-agent](https://github.com/inngest/vercel-ai-o1-preview-crm-agent).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 30a23bc

Please sign in to comment.