ai-functions 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,62 @@
1
+ import { JSONSchema7 } from 'json-schema-to-ts'
2
+
3
+ // This is a helper function to generate a JSON schema for string properties.
4
+ // It checks if a string includes '|' which would indicate an enum,
5
+ // or if it starts with 'number: ' or 'boolean: ' which would indicate
6
+ // a number or boolean type respectively, otherwise it defaults to string.
7
+ export const parseStringDescription = (description: string): JSONSchema7 => {
8
+ // Check if the description indicates an enum for string type
9
+ if (description.includes('|')) {
10
+ return { type: 'string', enum: description.split('|').map((v) => v.trim()) }
11
+ } else {
12
+ // Check for 'number: ' prefix
13
+ if (description.startsWith('number: ')) {
14
+ return {
15
+ type: 'number',
16
+ description: description.replace('number: ', ''),
17
+ }
18
+ }
19
+ // Check for 'boolean: ' prefix
20
+ else if (description.startsWith('boolean: ')) {
21
+ return {
22
+ type: 'boolean',
23
+ description: description.replace('boolean: ', ''),
24
+ }
25
+ }
26
+ // Default to string type
27
+ else {
28
+ return { type: 'string', description }
29
+ }
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Given a property description object, generate a JSON schema.
35
+ *
36
+ * @param propDescriptions - A record object with keys as property names
37
+ * and values as descriptions or nested property description objects.
38
+ * @returns A JSON schema object based on the provided descriptions.
39
+ */
40
+ export const generateSchema = (propDescriptions: Record<string, string | Record<string, any>>): JSONSchema7 => {
41
+ // If the propDescriptions is for an object structure
42
+ if (typeof propDescriptions !== 'object' || propDescriptions === null || Array.isArray(propDescriptions)) {
43
+ throw new Error('The propDescriptions parameter should be an object.')
44
+ }
45
+
46
+ const properties: Record<string, JSONSchema7> = {}
47
+ const required: string[] = Object.keys(propDescriptions)
48
+
49
+ for (const [key, description] of Object.entries(propDescriptions)) {
50
+ if (typeof description === 'string') {
51
+ // Handle the string description
52
+ properties[key] = parseStringDescription(description)
53
+ } else if (typeof description === 'object' && !Array.isArray(description)) {
54
+ // Recursive call for nested objects
55
+ properties[key] = generateSchema(description)
56
+ } else {
57
+ throw new Error(`Invalid description for key "${key}".`)
58
+ }
59
+ }
60
+
61
+ return { type: 'object', properties, required }
62
+ }
package/utils/state.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { createMachine, createActor } from 'xstate'
2
+
3
+ // Stateless machine definition
4
+ // machine.transition(...) is a pure function used by the interpreter.
5
+ const toggleMachine = createMachine({
6
+ id: 'Switch',
7
+ initial: 'inactive',
8
+ states: {
9
+ inactive: { on: { Toggle: 'active' } },
10
+ active: { on: { Toggle: 'inactive' } },
11
+ },
12
+ })
13
+
14
+ // Machine instance with internal state
15
+ const toggleService = createActor(toggleMachine).start()
16
+ toggleService.subscribe((state) => console.log(state.value))
17
+ // => 'inactive'
18
+
19
+ toggleService.send({ type: 'Toggle' })
20
+ // => 'active'
21
+
22
+ toggleService.send({ type: 'Toggle' })
23
+ // => 'inactive'
package/example.js DELETED
@@ -1,38 +0,0 @@
1
- import { AI } from './proxy.js'
2
-
3
- const { ai, list, gpt } = AI()
4
-
5
- for await (const item of list`synonyms for "fun"`) {
6
- console.log(item)
7
- }
8
-
9
-
10
- for await (const item of list`fun things to do in Miami`) {
11
- console.log(item)
12
- }
13
-
14
- const listBlogPosts = (count, topic) => list`${count} blog post titles about ${topic}`
15
- const writeBlogPost = title => gpt`write a blog post in markdown starting with "# ${title}"`
16
-
17
- async function* writeBlog(count, topic) {
18
- for await (const title of listBlogPosts(count, topic)) {
19
- const contentPromise = writeBlogPost(title).then(content => {
20
- console.log({ title, content })
21
- return { title, content }
22
- })
23
- yield { title, contentPromise }
24
- }
25
- }
26
-
27
- for await (const post of writeBlog(3, 'future of car sales')) {
28
- console.log({ post })
29
- }
30
-
31
- const product = await ai.categorizeProduct({
32
- productType: 'App | API | Marketplace | Platform | Packaged Service | Professional Service | Website',
33
- customer: 'ideal customer profile in 3-5 words',
34
- solution: 'describe the offer in 4-10 words',
35
- description: 'website meta description',
36
- })({ domain: 'OpenSaaS.org' })
37
-
38
- console.log({ product })
package/fetcher.js DELETED
@@ -1,48 +0,0 @@
1
- const headers = {
2
- 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
3
- 'Content-Type': 'application/json',
4
- }
5
- const openaiFetch = obj => fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers, body: JSON.stringify(obj) }).then(res => res.json())
6
-
7
- const openaiStream = obj => fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers, body: JSON.stringify(obj) })
8
-
9
-
10
- const response = await openaiStream({
11
- model: 'gpt-3.5-turbo',
12
- messages: [{ role: 'user', content: 'Tell me a funny joke about OpenAI' }],
13
- stream: true
14
- })
15
-
16
- const decoder = new TextDecoder('utf-8')
17
- let completion = ''
18
-
19
- try {
20
- for await (const chunk of response.body) {
21
- let done = false
22
- const currentChunk = decoder.decode(chunk)
23
- const lines = currentChunk.split('\n').filter(Boolean)
24
- // console.log(lines)
25
- for (const line of lines) {
26
- if (line.includes('[DONE]')) {
27
- done = true
28
- break
29
- }
30
- try {
31
- const data = JSON.parse(line.replace('data: ', ''))
32
- if (data.choices[0].delta.content) {
33
- const deltaContent = data.choices[0].delta.content
34
- completion += deltaContent
35
- console.log(deltaContent)
36
- }
37
- // console.log(chunks)
38
- } catch (error) {
39
- console.log(error.message, line)
40
- }
41
- }
42
- if (done) break
43
- }
44
- } catch (err) {
45
- console.error(err.stack);
46
- }
47
- console.log({ completion })
48
-
@@ -1,12 +0,0 @@
1
- export const generateBlogPost = {
2
- name: 'generateBlogPost',
3
- description: 'Generate high-converting marketing content for a landing page',
4
- parameters: {
5
- type: 'object',
6
- properties: {
7
- title: { type: 'string', description: 'The title of the Blog Post' },
8
- tags: { type: 'array', items: { type: 'string' } },
9
- markdown: { type: 'string', description: 'The content of the Blog Post in Markdown format' },
10
- }
11
- }
12
- }
@@ -1,11 +0,0 @@
1
- export const generateLandingPage = {
2
- name: 'generateLandingPage',
3
- description: 'Generate high-converting marketing content for a landing page',
4
- parameters: {
5
- type: 'object',
6
- properties: {
7
- title: { type: 'string', description: 'The title of the landing page' },
8
- description: { type: 'string', description: 'The description of the landing page' },
9
- }
10
- }
11
- }
@@ -1,11 +0,0 @@
1
- export const generateLandingPage = {
2
- name: 'generateLandingPage',
3
- description: 'Generate high-converting marketing content for a landing page',
4
- parameters: {
5
- type: 'object',
6
- properties: {
7
- titles: { type: 'array', items: { type: 'string' }, description: 'The title of the landing page' },
8
- description: { type: 'string', description: 'The description of the landing page' },
9
- }
10
- }
11
- }
package/index.js DELETED
@@ -1,3 +0,0 @@
1
- export * from './proxy.js'
2
- export * from './schema.js'
3
- export * from './withAI.js'
package/index.test.js DELETED
@@ -1,64 +0,0 @@
1
- import { describe, test, it, expect } from 'vitest'
2
-
3
- import { AI } from './index.js'
4
- const { ai, list, gpt } = AI()
5
-
6
- test('Math.sqrt()', () => {
7
- expect(Math.sqrt(4)).toBe(2)
8
- expect(Math.sqrt(144)).toBe(12)
9
- expect(Math.sqrt(2)).toBe(Math.SQRT2)
10
- })
11
-
12
- test('getJsonSchema', () => {
13
-
14
- const jsonSchema = schema({
15
- name: 'The name of the person',
16
- age: 'The age of the person'
17
- })
18
-
19
- expect(jsonSchema).toEqual({
20
- type: 'object',
21
- properties: {
22
- name: { type: 'string', description: 'The name of the person' },
23
- age: { type: 'string', description: 'The age of the person' } },
24
- required: ['name', 'age']
25
- })
26
-
27
- })
28
-
29
- test('list', () => {
30
-
31
- })
32
-
33
- test('ai', () => {
34
- expect(ai.writeLandingPage({
35
- brand: 'Auto.dev',
36
- audience: 'developers',
37
- offers: 'Automotive Data APIs',
38
- })).toEqual({
39
- functionName: 'writeLandingPage',
40
- args: {
41
- brand: 'Auto.dev',
42
- audience: 'developers',
43
- offers: 'Automotive Data APIs',
44
- }
45
- })
46
-
47
- // AI('writeLandingPage', ({ title, description, heroTitle, heroDescription, featuresTitle, featuresDescription }) =>
48
- })
49
-
50
- test('A thing', () => {
51
- it('should work', () => {
52
- expect(3).toBe(3)
53
- })
54
-
55
- it('should be ok', () => {
56
- expect(2).toBe(2)
57
- })
58
-
59
- describe('a nested thing', () => {
60
- it('should work', () => {
61
- expect(3).toBe(3)
62
- })
63
- })
64
- })
package/proxy.js DELETED
@@ -1,121 +0,0 @@
1
- // import { OpenAI } from 'openai'
2
- // import camelcaseKeys from 'camelcase-keys'
3
- import { dump } from 'js-yaml'
4
- import { schema } from './schema.js'
5
-
6
- export const AI = opts => {
7
- const { system, model = 'gpt-3.5-turbo', apiKey, OPENAI_API_KEY, ...rest } = opts || {}
8
-
9
- // const openai = new OpenAI({ apiKey: apiKey ?? OPENAI_API_KEY, ...rest })
10
- const headers = {
11
- 'Authorization': `Bearer ${apiKey ?? OPENAI_API_KEY ?? globalThis.process?.env?.OPENAI_API_KEY}`,
12
- 'Content-Type': 'application/json',
13
- }
14
- const openaiFetch = obj => fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers, body: JSON.stringify(obj) }).then(res => res.json())
15
-
16
- const gpt = async (strings, ...values) => {
17
- const user = values.map((value, i) => strings[i] + value).join('') + strings[strings.length - 1]
18
- const prompt = {
19
- model,
20
- messages: [
21
- { role: 'user', content: user },
22
- ],
23
- }
24
- if (system) prompt.messages.unshift({ role: 'system', content: system })
25
- const completion = await openaiFetch(prompt)
26
- return completion.choices?.[0].message.content
27
- }
28
-
29
- const ai = new Proxy({}, {
30
- get: (target, functionName, receiver) => {
31
- return (returnSchema, options) => async args => {
32
- console.log(schema(returnSchema))
33
- const { system, description, model = 'gpt-3.5-turbo', meta = false, ...rest } = options || {}
34
- const prompt = {
35
- model,
36
- messages: [
37
- { role: 'user', content: `Call ${functionName} given the context:\n${dump(args)}` }, // \nThere is no additional information, so make assumptions/guesses as necessary` },
38
- ],
39
- functions: [{
40
- name: functionName,
41
- description,
42
- parameters: schema(returnSchema),
43
- }],
44
- ...rest,
45
- }
46
- if (system) prompt.messages.unshift({ role: 'system', content: system })
47
- const completion = await openaiFetch(prompt)
48
- let data, error
49
- const { message } = completion.choices?.[0]
50
- prompt.messages.push(message)
51
- const { content, function_call } = message
52
- if (function_call) {
53
- try {
54
- data = JSON.parse(function_call.arguments)
55
- } catch (err) {
56
- error = err.message
57
- }
58
- }
59
- const gpt4 = model.includes('gpt-4')
60
- const cost = Math.round((gpt4
61
- ? completion.usage.prompt_tokens * 0.003 + completion.usage.completion_tokens * 0.006
62
- : completion.usage.prompt_tokens * 0.00015 + completion.usage.completion_tokens * 0.0002) * 100000) / 100000
63
- // completion.usage = camelcaseKeys(completion.usage)
64
- console.log({ data, content, error, cost })
65
- return meta ? { prompt, content, data, error, cost, ...completion } : data ?? content
66
- }
67
- }
68
- })
69
-
70
- async function* list(strings, ...values) {
71
- const listPrompt = values.map((value, i) => strings[i] + value).join('') + strings[strings.length - 1]
72
- const prompt = {
73
- model,
74
- messages: [{ role: 'user', content: 'List ' + listPrompt }],
75
- stream: true,
76
- }
77
- if (system) prompt.messages.unshift({ role: 'system', content: system })
78
- const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers, body: JSON.stringify(prompt) })
79
- const decoder = new TextDecoder('utf-8')
80
-
81
- let content = ''
82
- let seperator = undefined
83
- let numberedList = undefined
84
-
85
- for await (const chunk of response.body) {
86
- const lines = decoder.decode(chunk).split('\n').filter(Boolean)
87
- for (const line of lines) {
88
- try {
89
- if (line.includes('[DONE]')) break
90
- const part = JSON.parse(line.replace('data: ', ''))
91
- const { delta, finish_reason } = part.choices[0]
92
- content += delta?.content || ''
93
- if (seperator === undefined && content.length > 4) {
94
- numberedList = content.match(/(\d+\.\s)/g)
95
- seperator = numberedList ? '\n' : ', '
96
- }
97
-
98
- const numberedRegex = /\d+\.\s(?:")?([^"]+)(?:")?/
99
-
100
- if (content.includes(seperator)) {
101
- // get the string before the newline, and modify `content` to be the string after the newline
102
- // then yield the string before the newline
103
- const items = content.split(seperator)
104
- while (items.length > 1) {
105
- const item = items.shift()
106
- yield numberedList ? item.match(numberedRegex)?.[1] : item
107
- }
108
- content = items[0]
109
- }
110
-
111
- if (finish_reason) yield numberedList ? content.match(numberedRegex)?.[1] : content
112
- } catch (error) {
113
- console.log(error.message, line)
114
- }
115
- }
116
- }
117
-
118
- }
119
-
120
- return { ai, list, gpt }
121
- }
package/schema.js DELETED
@@ -1,50 +0,0 @@
1
- export const schema = propDescriptions => {
2
- // assume an object like this: { name: 'The name of the person', age: 'The age of the person' }
3
- // return an object like this: { type: 'object', properties: { name: { type: 'string', description: 'The name of the person' }, age: { type: 'number', description: 'The age of the person' } } required: ['name', 'age'] }
4
- if (Array.isArray(propDescriptions)) {
5
- const [ itemValue ] = propDescriptions
6
- const itemType = typeof itemValue
7
- if (itemType == 'string') {
8
- return { type: 'array', description: itemValue, items: { type: 'string' }}
9
- } else if (itemType == 'object') {
10
- const { _description, itemSchema } = itemValue
11
- return { type: 'array', description: _description, items: schema(itemSchema)}
12
- }
13
- } else {
14
- const properties = Object.entries(propDescriptions).reduce((acc, [key, value]) => {
15
- const type = typeof value
16
- if (Array.isArray(value)) {
17
- const [ itemValue ] = value
18
- const itemType = typeof itemValue
19
- if (itemType == 'string') {
20
- if (itemValue.includes('|')) {
21
- acc[key] = { type: 'string', enum: itemValue.split('|').map(value => value.trim()) }
22
- } else {
23
- acc[key] = { type: 'array', description: itemValue, items: { type: 'string' }}
24
- }
25
- } else if (itemType == 'object') {
26
- // const { _description, itemSchema } = itemValue
27
- const description = itemValue._description ? `${itemValue._description}` : undefined
28
- if (description) delete itemValue._description
29
- acc[key] = { type: 'array', description, items: schema(itemValue)}
30
- }
31
- } else {
32
- if (type == 'string') {
33
- if (value.includes('|')) {
34
- acc[key] = { type: 'string', enum: value.split('|').map(value => value.trim()) }
35
- } else {
36
- acc[key] = { type, description: value }
37
- }
38
- } else if (type == 'object') {
39
- if (value._description) value._description = undefined
40
- acc[key] = schema(value)
41
- } else {
42
- acc[key] = { type, description: value }
43
- }
44
- }
45
- return acc
46
- }, {})
47
- const required = Object.keys(properties)
48
- return { type: 'object', properties, required }
49
- }
50
- }
package/withAI.js DELETED
@@ -1,8 +0,0 @@
1
- import { AI } from './proxy.js'
2
-
3
- export const withAI = options => (req, env) => {
4
- const { ai, gpt, list } = AI({ apiKey: env.OPENAI_API_KEY, ...options })
5
- env.ai = ai
6
- env.gpt = gpt
7
- env.list = list
8
- }
File without changes