Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
import 'dotenv/config'
import { runAgent } from './src/agent'
import { z } from 'zod'
import { tools } from './src/tools'
// import { runLLM } from './src/llm'
// import { getMessages, addMessages } from './src/memory'

const userMessage = process.argv[2]

if (!userMessage) {
console.error('Please provide a message')
process.exit(1)
}

// const response = await runLLM({ userMessage })

//DIRECT CALL TO LLM CODE HERE
// await addMessages([{ role: 'user', content: userMessage }]) // add most recent usr message.
// const messages = await getMessages()

//DON"T UNCOMMENT USE NEXT RESPONSE.
// const response = await runLLM({
// messages: [...messages, { role: 'user', content: userMessage }],
// })

// const response = await runLLM({
// messages,
// })

// await addMessages([{ role: 'assistant', content: response }])

// AGENT CODE HERE

const weatherTool = {
name: 'get_weather_tool',
description: 'use this to get the weather',
parameters: z.object({
reasoning: z.string().describe('why did you pick this tool?'),
}),
}

// const response = await runAgent({ userMessage, tools: [weatherTool] })

const response = await runAgent({ userMessage, tools })
41 changes: 34 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions src/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// import { AIMessage } from '../types'
import { runLLM } from './llm'
import { getMessages, addMessages, saveToolResponse } from './memory'
import { runTool } from './toolRunner'
import { logMessage, showLoader } from './ui'

export const runAgent = async ({
userMessage,
tools,
}: {
userMessage: string
tools: any[]
}) => {
await addMessages([{ role: 'user', content: userMessage }])
const loader = showLoader('🤔')

while (true) {
const messages = await getMessages()

const response = await runLLM({
messages,
tools,
})
await addMessages([response])

if (response.content) {
logMessage(response)
loader.stop()
return getMessages()
}

if (response.tool_calls) {
const toolCall = response.tool_calls[0]
loader.update(`executing: ${toolCall.function.name}`)

const toolResponse = await runTool(toolCall, userMessage)
await saveToolResponse(toolCall.id, toolResponse)

loader.update(`executed: ${toolCall.function.name}`)
}
}
}
32 changes: 32 additions & 0 deletions src/llm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { openai } from './ai'
import type { AIMessage } from '../types'
import { zodFunction } from 'openai/helpers/zod'
import { systemPrompt } from './systemPrompt'

// type UserMessage = {
// userMessage: string
// }

type Messages = {
messages: AIMessage[]
}

export const runLLM = async ({
messages,
tools,
}: Messages & { tools: any[] }) => {
const formattedTools = tools.map(zodFunction)

const response = await openai.chat.completions.create({
model: 'gpt-5-nano',
messages: [{ role: 'system', content: systemPrompt }, ...messages],
tools: formattedTools,
tool_choice: 'auto',
parallel_tool_calls: false,
// temperature: 0.1,
})

// return response?.choices[0]?.message.content
//agent changes below, since no content is returned
return response?.choices[0]?.message
}
58 changes: 58 additions & 0 deletions src/memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { JSONFilePreset } from 'lowdb/node'
import type { AIMessage } from '../types'
import { v4 as uuidv4 } from 'uuid'

export type MessageWithMetaData = AIMessage & {
id: string
createdAt: string
}

// add metadata
const addMetadata = (message: AIMessage): MessageWithMetaData => {
return {
...message,
id: uuidv4(),
createdAt: new Date().toISOString(),
}
}

// remove metadata
const removeMetadata = (message: MessageWithMetaData): AIMessage => {
const { id, createdAt, ...messageWithoutMetadata } = message
return messageWithoutMetadata
}

type Data = {
messages: MessageWithMetaData[]
}

const defaultData: Data = { messages: [] }

const getDb = async () => {
const db = await JSONFilePreset('db.json', defaultData)
return db
}

export const addMessages = async (messages: AIMessage[]) => {
const db = await getDb()
db.data.messages.push(...messages.map(addMetadata))
await db.write()
}

export const getMessages = async () => {
const db = await getDb()
return db.data.messages.map(removeMetadata)
}

export const saveToolResponse = async (
toolCallId: string,
toolResponse: string
) => {
return addMessages([
{
role: 'tool',
content: toolResponse,
tool_call_id: toolCallId,
},
])
}
9 changes: 9 additions & 0 deletions src/systemPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const systemPrompt = `
You are a helpful AI assistant called Trolly Dada. Follow these instructions.

- Do not use celebrity names in image generation prompts, instead replace them with a generic character traits.

<context>
todays date: ${new Date().toLocaleDateString()}
</context>
`
30 changes: 30 additions & 0 deletions src/toolRunner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type OpenAI from 'openai'
import type Open from 'openai'
import { dadJoke } from './tools/dadJoke'
import { reddit } from './tools/reddit'
import { generateImage } from './tools/generateImage'

const getWeather = () => 'The weather is 30 degree celcius. Super duper hot.'

export const runTool = async (
toolCall: OpenAI.Chat.Completions.ChatCompletionMessageToolCall,
userMessage: string
) => {
const input = {
userMessage,
toolArgs: JSON.parse(toolCall.function.arguments),
}

switch (toolCall.function.name) {
case 'get_weather_tool':
return getWeather(input)
case 'dad_joke':
return dadJoke(input)
case 'generate_image':
return generateImage(input)
case 'reddit':
return reddit(input)
default:
throw new Error(`Unknown tool ${toolCall.function.name}`)
}
}
22 changes: 22 additions & 0 deletions src/tools/dadJoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { z } from 'zod'
import type { ToolFn } from '../../types'
import fetch from 'node-fetch'

export const dadJokeToolDefinition = {
name: 'dad_joke',
description: 'Go call an API and get me dad jokes',
parameters: z.object({}),
}

type Args = z.infer<typeof dadJokeToolDefinition.parameters>

export const dadJoke: ToolFn<Args, string> = async ({ toolArgs }) => {
const response = await fetch('https://icanhazdadjoke.com/', {
headers: {
Accept: 'application/json',
},
})

const data = await response.json()
return data.joke
}
36 changes: 36 additions & 0 deletions src/tools/generateImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { z } from 'zod'
import type { ToolFn } from '../../types'
import { openai } from '../ai'

export const generateImageToolDefinition = {
name: 'generate_image',
parameters: z
.object({
prompt: z
.string()
.describe(
'The prompt used to generate the image with a diffusion model generator like Dall-E'
),
})
.describe('generate an image and return the URL of the image'),
}

type Args = z.infer<typeof generateImageToolDefinition.parameters>

export const generateImage: ToolFn<Args, string> = async ({
toolArgs,
userMessage,
}) => {
const response = await openai.images.generate({
model: 'dall-e-3',
prompt: toolArgs.prompt,
n: 1,
size: '1024x1024',
})

const imageUrl = response.data[0].url
if (!imageUrl) {
throw new Error('Failed to generate Image URl')
}
return imageUrl
}
9 changes: 9 additions & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { generateImageToolDefinition } from './generateImage'
import { redditToolDefinition } from './reddit'
import { dadJokeToolDefinition } from './dadJoke'

export const tools = [
generateImageToolDefinition,
redditToolDefinition,
dadJokeToolDefinition,
]
38 changes: 38 additions & 0 deletions src/tools/reddit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { z } from 'zod'
import type { ToolFn } from '../../types'
import fetch from 'node-fetch'

export const redditToolDefinition = {
name: 'reddit',
parameters: z.object({}).describe(
`Use this tool to get the latest posts from Reddit. It will return a JSON object
with the title, link, subreddit, author, and upvotes of each post.`
),
}

type Args = z.infer<typeof redditToolDefinition.parameters>

export const reddit: ToolFn<Args, string> = async ({
toolArgs,
userMessage,
}) => {
const response = await fetch('https://www.reddit.com/r/aww/.json')

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}

const { data } = await response.json()

console.log('=====data---', data)

const relevantInfo = data.children.map((child: any) => ({
title: child.data.title,
link: child.data.url,
subreddit: child.data.subreddit_name_prefixed,
author: child.data.author,
upvotes: child.data.ups,
}))

return JSON.stringify(relevantInfo, null, 2)
}