Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DX-1580: Add agent examples #53

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"singleQuote": true,
"printWidth": 80,
"editor.formatOnSave": true,
"proseWrap": "always",
"tabWidth": 4,
"requireConfig": false,
"useTabs": false,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"semi": true
}
Binary file modified bun.lockb
Binary file not shown.
41 changes: 41 additions & 0 deletions examples/agents-instagram-post-generator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
13 changes: 13 additions & 0 deletions examples/agents-instagram-post-generator/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"singleQuote": true,
"printWidth": 80,
"editor.formatOnSave": true,
"proseWrap": "always",
"tabWidth": 4,
"requireConfig": false,
"useTabs": false,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"semi": true
}
36 changes: 36 additions & 0 deletions examples/agents-instagram-post-generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Post } from '@/app/page';
import { Redis } from '@upstash/redis';
import { NextRequest, NextResponse } from 'next/server';

const redis = Redis.fromEnv();



export async function POST(request: NextRequest) {
try {
const { callKey } = await request.json();

if (!callKey) {
return NextResponse.json(
{ error: 'No callKey provided' },
{ status: 400 }
);
}

const posts = await redis.lrange<Post>(`${callKey}-posts`, 0, -1);

if (!posts || posts.length === 0) {
return NextResponse.json(null);
}


console.log({
callKey,
posts
})

return NextResponse.json({
posts
});
} catch (error) {
console.error('Error checking workflow:', error);
return NextResponse.json(
{ error: 'Failed to check workflow status' },
{ status: 500 }
);
}
}
Binary file not shown.
191 changes: 191 additions & 0 deletions examples/agents-instagram-post-generator/app/generate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { serve } from "@upstash/workflow/nextjs";
import { SerpAPIClient } from '@agentic/serpapi';
import { FirecrawlClient } from '@agentic/firecrawl';
import { tool } from 'ai';
import { z } from "zod";
import { Redis } from "@upstash/redis"
import { ImagesResponse } from "../types";




const redis = Redis.fromEnv();
const serpapi = new SerpAPIClient();
const firecrawl = new FirecrawlClient();


type Payload = {
productWebsite: string;
productDetails: string;
}

export const { POST } = serve<Payload>(async (context) => {
const { productDetails, productWebsite } = context.requestPayload
const model = context.agents.openai('gpt-4-turbo');

const productAnalysisAgent = context.agents.agent({
model,
name: 'productAnalysisAgent',
maxSteps: 3,
background: `You are a Lead Market Analyst at a premier digital marketing firm.
You specialize in dissecting online business landscapes and providing
in-depth insights to guide marketing strategies.`,
tools: {
searchWeb: tool({

description: 'Search the web using SerpAPI',
parameters: z.object({
query: z.string().describe('The search query')
}),
execute: async ({ query }) => {
const results = await serpapi.search(query);
const organicResults = results.organic_results || [];
return {
content: organicResults
.slice(0, 3)
.map(result => `Title: ${result.title}\nKey Points: ${result.snippet}\nSource: ${result.link}`)
.join('\n\n')
};
}
}),
scrapeWebsite: tool({
description: 'Scrape content from a webpage',
parameters: z.object({
url: z.string().describe('The URL to scrape')
}),
execute: async ({ url }) => {
const result = await firecrawl.scrapeUrl({ url });
return { content: result.data };
}
})
}
});

// Strategy Planner Agent
const strategyPlannerAgent = context.agents.agent({
model,
name: 'strategyPlannerAgent',
maxSteps: 3,
background: `You are the Chief Marketing Strategist at a leading digital marketing agency,
known for crafting bespoke strategies that drive success.`,
tools: {
searchInstagram: tool({
description: 'Search Instagram trends',
parameters: z.object({
query: z.string().describe('The search query')
}),
execute: async ({ query }) => {
const results = await serpapi.search(`site:instagram.com ${query}`);
return { content: results.organic_results?.slice(0, 3) || [] };
}
})
}
});

const creativeAgent = context.agents.agent({
model,
name: 'creativeAgent',
maxSteps: 3,
background: `You are a Creative Content Creator who excels in crafting narratives
that resonate with social media audiences.`,
tools: {}
});

const photographerAgent = context.agents.agent({
model,
name: 'photographerAgent',
maxSteps: 2,
background: `You are a Senior Photographer specialized in creating compelling
visual narratives for social media campaigns.`,
tools: {}
});

const productAnalysis = await context.agents.task({
agent: productAnalysisAgent,
prompt: `Analyze the product website: ${productWebsite}.
Additional details: ${productDetails}
Focus on identifying unique features, benefits, and overall narrative.`
}).run();

const marketAnalysis = await context.agents.task({
agent: productAnalysisAgent,
prompt: `Analyze competitors of ${productWebsite}.
Identify top 3 competitors and their strategies.
Consider: ${productAnalysis.text}`
}).run();

const campaignStrategy = await context.agents.task({
agent: strategyPlannerAgent,
prompt: `Develop a marketing campaign strategy based on:
Product Analysis: ${productAnalysis.text}
Market Analysis: ${marketAnalysis.text}
Create a strategy that will resonate with the target audience.`
}).run();

const instagramCaptions = await context.agents.task({
agent: creativeAgent,
prompt: `Create exactly 3 engaging Instagram post captions based on:
Campaign Strategy: ${campaignStrategy.text}
Make them punchy, captivating, and aligned with the strategy. Dont use emojis or special characters.
Return exactly 3 distinct copies, no more and no less.`
}).run();

const photoDescription = await context.agents.task({
agent: photographerAgent,
prompt: `Create exactly 3 detailed photo concepts for Instagram posts using:
Captions: ${instagramCaptions.text}
Product: ${productWebsite}
Details: ${productDetails}
Format each description like: "high tech airplane in beautiful blue sky at sunset, 4k, professional wide shot"
Return exactly 3 concepts, no more and no less.`
}).run();

// 2. Limit the array to 3 items when splitting
const photoPrompts = photoDescription.text.split('\n\n').slice(0, 3);

const instagramPostResults = await Promise.all(
photoPrompts.slice(0, 3).map(async (prompt, index) =>
await context.call<ImagesResponse>(
`generate-image-${index + 1}`,
{
url: "https://api.openai.com/v1/images/generations",
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
},
body: {
model: "dall-e-3",
prompt: `${prompt}. Make it look professional and Instagram-worthy.`,
n: 1,
size: "1024x1024",
quality: "hd",
style: "natural"
}
}
)
)
);

await Promise.all(
instagramPostResults.map((async (post, index) => {
await context.run(`persist post to redis ${index}`, async () => {
const callKey = context.headers.get('callKey');

const { url, revised_prompt } = post.body.data[0]
const result = {
imageUrl: url,
prompt: revised_prompt,
caption: instagramCaptions.text.split('\n\n')[index]
}

if (callKey) {
await redis.lpush(
`${callKey}-posts`,
JSON.stringify(result)
);
}
})
}))
)
})
21 changes: 21 additions & 0 deletions examples/agents-instagram-post-generator/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--background: #ffffff;
--foreground: #171717;
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
Loading