ai-functions 0.2.10 → 0.2.13
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.
- package/db/mongo.ts +3 -3
- package/dist/cjs/db/cache.d.ts +1 -0
- package/dist/cjs/db/cache.js +12 -0
- package/dist/cjs/db/mongo.d.ts +31 -0
- package/dist/cjs/db/mongo.js +47 -0
- package/dist/cjs/examples/data.d.ts +1105 -0
- package/dist/cjs/examples/data.js +1108 -0
- package/dist/cjs/functions/ai.d.ts +18 -0
- package/dist/cjs/functions/ai.js +99 -0
- package/dist/cjs/functions/ai.test.d.ts +1 -0
- package/dist/cjs/functions/ai.test.js +40 -0
- package/dist/cjs/functions/gpt.d.ts +4 -0
- package/dist/cjs/functions/gpt.js +24 -0
- package/dist/cjs/functions/list.d.ts +7 -0
- package/dist/cjs/functions/list.js +134 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/queue/kafka.d.ts +0 -0
- package/dist/cjs/queue/kafka.js +1 -0
- package/dist/cjs/queue/memory.d.ts +0 -0
- package/dist/cjs/queue/memory.js +1 -0
- package/dist/cjs/queue/mongo.d.ts +30 -0
- package/dist/cjs/queue/mongo.js +69 -0
- package/dist/cjs/streams/kafka.d.ts +0 -0
- package/dist/cjs/streams/kafka.js +1 -0
- package/dist/cjs/streams/memory.d.ts +0 -0
- package/dist/cjs/streams/memory.js +1 -0
- package/dist/cjs/streams/mongo.d.ts +0 -0
- package/dist/cjs/streams/mongo.js +1 -0
- package/dist/cjs/streams/types.d.ts +0 -0
- package/dist/cjs/streams/types.js +1 -0
- package/dist/cjs/types.d.ts +11 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils/completion.d.ts +9 -0
- package/dist/cjs/utils/completion.js +45 -0
- package/dist/cjs/utils/schema.d.ts +10 -0
- package/dist/cjs/utils/schema.js +64 -0
- package/dist/cjs/utils/schema.test.d.ts +1 -0
- package/dist/cjs/utils/schema.test.js +62 -0
- package/dist/cjs/utils/state.d.ts +1 -0
- package/dist/cjs/utils/state.js +21 -0
- package/dist/mjs/db/cache.d.ts +1 -0
- package/dist/mjs/db/cache.js +5 -0
- package/dist/mjs/db/mongo.d.ts +31 -0
- package/dist/mjs/db/mongo.js +48 -0
- package/dist/mjs/examples/data.d.ts +1105 -0
- package/dist/mjs/examples/data.js +1105 -0
- package/dist/mjs/functions/ai.d.ts +18 -0
- package/dist/mjs/functions/ai.js +79 -0
- package/dist/mjs/functions/ai.test.d.ts +1 -0
- package/dist/mjs/functions/ai.test.js +29 -0
- package/dist/mjs/functions/gpt.d.ts +4 -0
- package/dist/mjs/functions/gpt.js +10 -0
- package/dist/mjs/functions/list.d.ts +7 -0
- package/dist/mjs/functions/list.js +72 -0
- package/dist/mjs/index.d.ts +3 -0
- package/dist/mjs/index.js +3 -0
- package/dist/mjs/package.json +3 -0
- package/dist/mjs/queue/kafka.d.ts +0 -0
- package/dist/mjs/queue/kafka.js +1 -0
- package/dist/mjs/queue/memory.d.ts +0 -0
- package/dist/mjs/queue/memory.js +1 -0
- package/dist/mjs/queue/mongo.d.ts +30 -0
- package/dist/mjs/queue/mongo.js +42 -0
- package/dist/mjs/streams/kafka.d.ts +0 -0
- package/dist/mjs/streams/kafka.js +1 -0
- package/dist/mjs/streams/memory.d.ts +0 -0
- package/dist/mjs/streams/memory.js +1 -0
- package/dist/mjs/streams/mongo.d.ts +0 -0
- package/dist/mjs/streams/mongo.js +1 -0
- package/dist/mjs/streams/types.d.ts +0 -0
- package/dist/mjs/streams/types.js +1 -0
- package/dist/mjs/types.d.ts +11 -0
- package/dist/mjs/types.js +1 -0
- package/dist/mjs/utils/completion.d.ts +9 -0
- package/dist/mjs/utils/completion.js +20 -0
- package/dist/mjs/utils/schema.d.ts +10 -0
- package/dist/mjs/utils/schema.js +59 -0
- package/dist/mjs/utils/schema.test.d.ts +1 -0
- package/dist/mjs/utils/schema.test.js +60 -0
- package/dist/mjs/utils/state.d.ts +1 -0
- package/dist/mjs/utils/state.js +19 -0
- package/fixup +11 -0
- package/functions/ai.ts +1 -1
- package/functions/list.ts +1 -1
- package/package.json +10 -3
- package/tsconfig-backup.json +105 -0
- package/tsconfig-base.json +26 -0
- package/tsconfig-cjs.json +8 -0
- package/tsconfig.json +5 -102
- package/types.ts +1 -1
- package/utils/completion.ts +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ClientOptions, OpenAI } from 'openai';
|
|
2
|
+
import { ChatCompletionCreateParamsBase } from 'openai/resources/chat/completions';
|
|
3
|
+
export type AIConfig = ClientOptions & {
|
|
4
|
+
openai?: OpenAI;
|
|
5
|
+
system?: string;
|
|
6
|
+
model?: ChatCompletionCreateParamsBase['model'];
|
|
7
|
+
};
|
|
8
|
+
export type FunctionCallOptions = Omit<ChatCompletionCreateParamsBase, 'messages' | 'stream'> & {
|
|
9
|
+
system?: string;
|
|
10
|
+
meta?: boolean;
|
|
11
|
+
description?: string;
|
|
12
|
+
};
|
|
13
|
+
type AIFunctions<T = Record<string, string>> = Record<string, (returnSchema: T, options?: Partial<FunctionCallOptions>) => (args: string | object, callOptions?: Partial<FunctionCallOptions>) => Promise<T>>;
|
|
14
|
+
export declare const AI: (config?: AIConfig) => {
|
|
15
|
+
ai: AIFunctions<Record<string, string>>;
|
|
16
|
+
openai: OpenAI;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { OpenAI, } from 'openai';
|
|
2
|
+
// import { AIDB, AIDBConfig } from '../db/mongo'
|
|
3
|
+
import { dump } from 'js-yaml';
|
|
4
|
+
import { generateSchema } from '../utils/schema';
|
|
5
|
+
export const AI = (config = {}) => {
|
|
6
|
+
const { model = 'gpt-4-1106-preview', system, ...rest } = config;
|
|
7
|
+
const openai = config.openai ?? new OpenAI(rest);
|
|
8
|
+
// const { client, db, cache, events, queue } = config.db ? AIDB(config.db) : {}
|
|
9
|
+
// const prompt = {
|
|
10
|
+
// model,
|
|
11
|
+
// messages: [{ role: 'user', content: user }],
|
|
12
|
+
// }
|
|
13
|
+
// if (system) prompt.messages.unshift({ role: 'system', content: system })
|
|
14
|
+
// const messages = system ? [{ role: 'system', content: system }] : []
|
|
15
|
+
// const completion = openai.chat.completions.create({
|
|
16
|
+
// model,
|
|
17
|
+
// messages: [{ role: 'user', content: 'hello' }],
|
|
18
|
+
// })
|
|
19
|
+
const ai = new Proxy({}, {
|
|
20
|
+
get: (target, functionName, receiver) => {
|
|
21
|
+
target[functionName] = (returnSchema, options) => async (args, callOptions) => {
|
|
22
|
+
console.log(generateSchema(returnSchema));
|
|
23
|
+
const { system, description, model = 'gpt-3.5-turbo', meta = false, ...rest } = { ...options, ...callOptions };
|
|
24
|
+
const prompt = {
|
|
25
|
+
model,
|
|
26
|
+
messages: [
|
|
27
|
+
{
|
|
28
|
+
role: 'user',
|
|
29
|
+
content: `Call ${functionName} given the context:\n${dump(args)}`,
|
|
30
|
+
}, // \nThere is no additional information, so make assumptions/guesses as necessary` },
|
|
31
|
+
],
|
|
32
|
+
tools: [
|
|
33
|
+
{
|
|
34
|
+
type: 'function',
|
|
35
|
+
function: {
|
|
36
|
+
name: functionName,
|
|
37
|
+
description,
|
|
38
|
+
parameters: generateSchema(returnSchema),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
tool_choice: {
|
|
43
|
+
type: 'function',
|
|
44
|
+
function: { name: functionName }
|
|
45
|
+
},
|
|
46
|
+
...rest,
|
|
47
|
+
};
|
|
48
|
+
if (system)
|
|
49
|
+
prompt.messages.unshift({ role: 'system', content: system });
|
|
50
|
+
const completion = await openai.chat.completions.create(prompt);
|
|
51
|
+
const schema = generateSchema(returnSchema);
|
|
52
|
+
let data;
|
|
53
|
+
let error;
|
|
54
|
+
const { message } = completion.choices?.[0];
|
|
55
|
+
console.log({ message });
|
|
56
|
+
prompt.messages.push(message);
|
|
57
|
+
const { content, tool_calls } = message;
|
|
58
|
+
if (tool_calls) {
|
|
59
|
+
try {
|
|
60
|
+
data = JSON.parse(tool_calls[0].function.arguments);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
error = err.message;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const gpt4 = model.includes('gpt-4');
|
|
67
|
+
const cost = completion.usage ?
|
|
68
|
+
Math.round((gpt4
|
|
69
|
+
? completion.usage.prompt_tokens * 0.003 + completion.usage.completion_tokens * 0.006
|
|
70
|
+
: completion.usage.prompt_tokens * 0.00015 + completion.usage.completion_tokens * 0.0002) * 100000) / 100000 : undefined;
|
|
71
|
+
// completion.usage = camelcaseKeys(completion.usage)
|
|
72
|
+
console.log({ data, content, error, cost, usage: completion.usage });
|
|
73
|
+
return meta ? { prompt, content, data, error, cost, ...completion } : data ?? content;
|
|
74
|
+
};
|
|
75
|
+
return target[functionName];
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return { ai, openai }; //, client, db, cache, events, queue }
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { AI } from './ai';
|
|
3
|
+
describe('AI Functions', async () => {
|
|
4
|
+
const { ai } = AI();
|
|
5
|
+
it('should be initialized', () => {
|
|
6
|
+
expect(ai).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
const categorizeWord = ai.categorizeWord({
|
|
9
|
+
type: 'Noun | Verb | Adjective | Adverb | Pronoun | Preposition | Conjunction | Interjection | Other',
|
|
10
|
+
example: 'use the word in a sentence',
|
|
11
|
+
// partOfSpeech: 'Part of speech'
|
|
12
|
+
}, { seed: 1, model: 'gpt-3.5-turbo' });
|
|
13
|
+
it('should be a function', () => {
|
|
14
|
+
expect(typeof categorizeWord).toBe('function');
|
|
15
|
+
});
|
|
16
|
+
it('Destroy should be a verb', async () => {
|
|
17
|
+
expect(await categorizeWord('destroy')).toMatchObject({ type: 'Verb', example: 'I will destroy the old building.' });
|
|
18
|
+
});
|
|
19
|
+
it('Dog should be a Noun', async () => {
|
|
20
|
+
const dog = await categorizeWord({ word: 'dog' });
|
|
21
|
+
expect(dog).toMatchObject({ type: 'Noun', example: 'I have a dog.' });
|
|
22
|
+
});
|
|
23
|
+
it('Large should be an Adjective', async () => {
|
|
24
|
+
expect(await categorizeWord({ word: 'large' })).toMatchObject({ type: 'Adjective', example: 'She has a large collection of books.' });
|
|
25
|
+
});
|
|
26
|
+
it('To should be an Preposition', async () => {
|
|
27
|
+
expect(await categorizeWord('to')).toMatchObject({ type: 'Preposition', example: "I'm going to the park." });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { chatCompletion } from '../utils/completion';
|
|
2
|
+
export const GPT = (config) => {
|
|
3
|
+
const gpt = async (strings, ...values) => {
|
|
4
|
+
const user = values.map((value, i) => strings[i] + value).join('') + strings[strings.length - 1];
|
|
5
|
+
const completion = await chatCompletion({ user, ...config });
|
|
6
|
+
const content = completion.choices?.[0].message.content;
|
|
7
|
+
return content;
|
|
8
|
+
};
|
|
9
|
+
return { gpt };
|
|
10
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { GPTConfig } from '../types';
|
|
2
|
+
export declare const List: (config: GPTConfig) => {
|
|
3
|
+
list: (strings: string[], ...values: string[]) => Promise<string[]>;
|
|
4
|
+
};
|
|
5
|
+
export declare const StreamingList: (config: GPTConfig) => {
|
|
6
|
+
list: (strings: string[], ...values: string[]) => AsyncGenerator<string | undefined, void, unknown>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { OpenAI } from 'openai';
|
|
2
|
+
import { chatCompletion } from '../utils/completion';
|
|
3
|
+
export const List = (config) => {
|
|
4
|
+
const list = async (strings, ...values) => {
|
|
5
|
+
const user = values.map((value, i) => strings[i] + value).join('') + strings[strings.length - 1];
|
|
6
|
+
const completion = await chatCompletion({ user, ...config });
|
|
7
|
+
const content = completion.choices?.[0].message.content;
|
|
8
|
+
return content ? parseList(content) : [];
|
|
9
|
+
};
|
|
10
|
+
return { list };
|
|
11
|
+
};
|
|
12
|
+
function parseList(listStr) {
|
|
13
|
+
// Define a regex pattern to match lines with '1. value', '1) value', '- value', or ' - value'
|
|
14
|
+
const listItemRegex = /^\s*\d+\.\s*(.+)|^\s*\d+\)\s*(.+)|^\s*-\s*(.+)$/gm;
|
|
15
|
+
let match;
|
|
16
|
+
const result = [];
|
|
17
|
+
// Loop over the list string, finding matches with the regex pattern
|
|
18
|
+
while ((match = listItemRegex.exec(listStr)) !== null) {
|
|
19
|
+
// The actual value is in one of the capturing groups (1, 2 or 3)
|
|
20
|
+
const value = match[1] || match[2] || match[3];
|
|
21
|
+
if (value) {
|
|
22
|
+
result.push(value.trim());
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
export const StreamingList = (config) => {
|
|
28
|
+
async function* list(strings, ...values) {
|
|
29
|
+
const user = values.map((value, i) => strings[i] + value).join('') + strings[strings.length - 1];
|
|
30
|
+
const { system, model, db, queue, ...rest } = config;
|
|
31
|
+
const openai = config.openai ?? new OpenAI(rest);
|
|
32
|
+
const prompt = {
|
|
33
|
+
model,
|
|
34
|
+
messages: [{ role: 'user', content: user }],
|
|
35
|
+
stream: true,
|
|
36
|
+
};
|
|
37
|
+
if (system)
|
|
38
|
+
prompt.messages.unshift({ role: 'system', content: system });
|
|
39
|
+
const completion = await openai.chat.completions.create(prompt);
|
|
40
|
+
const stream = await openai.chat.completions.create(prompt);
|
|
41
|
+
let content = '';
|
|
42
|
+
let seperator;
|
|
43
|
+
let numberedList;
|
|
44
|
+
for await (const part of stream) {
|
|
45
|
+
const { delta, finish_reason } = part.choices[0];
|
|
46
|
+
content += delta?.content || '';
|
|
47
|
+
if (seperator === undefined && content.length > 4) {
|
|
48
|
+
numberedList = content.match(/(\d+\.\s)/g);
|
|
49
|
+
seperator = numberedList ? '\n' : ', ';
|
|
50
|
+
}
|
|
51
|
+
const numberedRegex = /\d+\.\s(?:")?([^"]+)(?:")?/;
|
|
52
|
+
if (seperator && content.includes(seperator)) {
|
|
53
|
+
// get the string before the newline, and modify `content` to be the string after the newline
|
|
54
|
+
// then yield the string before the newline
|
|
55
|
+
const items = content.split(seperator);
|
|
56
|
+
while (items.length > 1) {
|
|
57
|
+
const item = items.shift();
|
|
58
|
+
yield numberedList ? item?.match(numberedRegex)?.[1] : item;
|
|
59
|
+
}
|
|
60
|
+
content = items[0];
|
|
61
|
+
}
|
|
62
|
+
if (finish_reason) {
|
|
63
|
+
// TODO: Figure out DB saving strategy for streaming
|
|
64
|
+
// if (db) {
|
|
65
|
+
// db.log(prompt, completion as ChatCompletion)
|
|
66
|
+
// }
|
|
67
|
+
yield numberedList ? content.match(numberedRegex)?.[1] : content;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { list };
|
|
72
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AIDB } from '../db/mongo';
|
|
2
|
+
import { CompletionInput } from '../utils/completion';
|
|
3
|
+
import { BSON, ObjectId } from 'mongodb';
|
|
4
|
+
export type QueueConfig = AIDB & {
|
|
5
|
+
batchSize?: number;
|
|
6
|
+
concurrency?: number;
|
|
7
|
+
lockExpiration?: number;
|
|
8
|
+
};
|
|
9
|
+
export type QueueInputMerge = {
|
|
10
|
+
into: string;
|
|
11
|
+
on?: BSON.Document;
|
|
12
|
+
let?: BSON.Document;
|
|
13
|
+
contentAs?: string;
|
|
14
|
+
itemsAs?: string;
|
|
15
|
+
functionDataAs?: string;
|
|
16
|
+
forEachItem?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type QueueInput = (CompletionInput | ({
|
|
19
|
+
list: string;
|
|
20
|
+
} & Partial<CompletionInput>)) & {
|
|
21
|
+
_id?: ObjectId;
|
|
22
|
+
metadata?: object;
|
|
23
|
+
merge?: string | QueueInputMerge;
|
|
24
|
+
target?: string;
|
|
25
|
+
};
|
|
26
|
+
export type QueueDocument = QueueInput & {
|
|
27
|
+
lockedAt: Date;
|
|
28
|
+
lockedBy: string;
|
|
29
|
+
};
|
|
30
|
+
export declare const startQueue: (config: QueueConfig) => Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import PQueue from 'p-queue';
|
|
2
|
+
import { chatCompletion } from '../utils/completion';
|
|
3
|
+
let localQueue;
|
|
4
|
+
export const startQueue = async (config) => {
|
|
5
|
+
const { concurrency = 10, batchSize = 100, lockExpiration = 3600, ...db } = config;
|
|
6
|
+
const instance = Math.random().toString(36).substring(2, 6);
|
|
7
|
+
const queue = new PQueue({ concurrency });
|
|
8
|
+
const clearExpiredLocks = () => db.queue.updateMany({ lockedAt: { $lt: new Date(Date.now() - lockExpiration * 1000) } }, { $unset: { lockedAt: '', lockedBy: '' } });
|
|
9
|
+
const lockBatch = () => db.queue.aggregate([
|
|
10
|
+
{ $match: { lockedAt: { $exists: false } } },
|
|
11
|
+
{ $limit: batchSize },
|
|
12
|
+
{ $set: { lockedAt: new Date(), lockedBy: instance } },
|
|
13
|
+
{ $merge: { into: 'queue', on: '_id', whenMatched: 'replace' } }, // TODO: Change the `into` to the name of the `queue` collection
|
|
14
|
+
]).toArray();
|
|
15
|
+
db.queue.watch([
|
|
16
|
+
{ $match: { lockedBy: instance } },
|
|
17
|
+
]).on('change', async (change) => {
|
|
18
|
+
const { _id, metadata, merge, target, ...input } = change.fullDocument; // TODO: Move input to child object not catchall spread
|
|
19
|
+
const completion = await queue.add(() => chatCompletion(input));
|
|
20
|
+
if (merge) {
|
|
21
|
+
const coll = typeof merge === 'string' ? merge : merge.into;
|
|
22
|
+
const match = typeof merge === 'string' ? undefined : merge.on;
|
|
23
|
+
// TODO: Add support to make completion optional, and specify field names for content, items, and functionData
|
|
24
|
+
const mergeResult = match
|
|
25
|
+
? await db.db.collection(coll).updateOne(match, { $set: { completion, ...metadata ?? {} } })
|
|
26
|
+
: await db.db.collection(coll).insertOne({ completion, ...metadata ?? {} });
|
|
27
|
+
if (mergeResult.acknowledged && (mergeResult.modifiedCount ||
|
|
28
|
+
mergeResult.insertedId)) {
|
|
29
|
+
// TODO: We may want to store the merge result in the Events collection to link the queue input to the merge result
|
|
30
|
+
await db.queue.deleteOne({ _id });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
queue.on('idle', async () => {
|
|
35
|
+
await clearExpiredLocks();
|
|
36
|
+
await lockBatch();
|
|
37
|
+
});
|
|
38
|
+
queue.start();
|
|
39
|
+
// TODO: Add Find() and Watch() for Actors collection
|
|
40
|
+
// TODO: For each actor, create a state machine and start it
|
|
41
|
+
// TODO: Figure out how to create new queued jobs from the results of the state machine
|
|
42
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { OpenAI, ClientOptions } from 'openai';
|
|
2
|
+
import type { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
3
|
+
import type { AIDB } from './db/mongo';
|
|
4
|
+
import type PQueue from 'p-queue';
|
|
5
|
+
export type GPTConfig = {
|
|
6
|
+
openai?: OpenAI;
|
|
7
|
+
system?: string;
|
|
8
|
+
model?: ChatCompletionCreateParamsNonStreaming['model'] | 'gpt-4-1106-preview' | 'gpt-3.5-turbo-1106';
|
|
9
|
+
db?: AIDB;
|
|
10
|
+
queue?: PQueue;
|
|
11
|
+
} & ClientOptions;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OpenAI } from 'openai';
|
|
2
|
+
import type { ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
3
|
+
import { GPTConfig } from '../types';
|
|
4
|
+
export type CompletionInput = GPTConfig & ((({
|
|
5
|
+
user: string;
|
|
6
|
+
} | {
|
|
7
|
+
list: string;
|
|
8
|
+
}) & Partial<ChatCompletionCreateParamsNonStreaming>) | ChatCompletionCreateParamsNonStreaming);
|
|
9
|
+
export declare const chatCompletion: (config: CompletionInput) => Promise<OpenAI.Chat.Completions.ChatCompletion>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OpenAI } from 'openai';
|
|
2
|
+
// TODO: add support for list input and parsing
|
|
3
|
+
// TODO: add support for caching w/ seed generation for unit tests
|
|
4
|
+
export const chatCompletion = async (config) => {
|
|
5
|
+
const { user, system, model, db, queue, ...rest } = config;
|
|
6
|
+
const openai = config.openai ?? new OpenAI(rest);
|
|
7
|
+
const prompt = {
|
|
8
|
+
model,
|
|
9
|
+
messages: [{ role: 'user', content: user }],
|
|
10
|
+
};
|
|
11
|
+
if (system)
|
|
12
|
+
prompt.messages.unshift({ role: 'system', content: system });
|
|
13
|
+
const completion = queue
|
|
14
|
+
? await queue.add(() => openai.chat.completions.create(prompt))
|
|
15
|
+
: await openai.chat.completions.create(prompt);
|
|
16
|
+
if (db) {
|
|
17
|
+
db.log(prompt, completion);
|
|
18
|
+
}
|
|
19
|
+
return completion;
|
|
20
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { JSONSchema } from 'json-schema-to-ts';
|
|
2
|
+
export declare const parseStringDescription: (description: string) => JSONSchema;
|
|
3
|
+
/**
|
|
4
|
+
* Given a property description object, generate a JSON schema.
|
|
5
|
+
*
|
|
6
|
+
* @param propDescriptions - A record object with keys as property names
|
|
7
|
+
* and values as descriptions or nested property description objects.
|
|
8
|
+
* @returns A JSON schema object based on the provided descriptions.
|
|
9
|
+
*/
|
|
10
|
+
export declare const generateSchema: (propDescriptions: Record<string, string | Record<string, any>>) => JSONSchema;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// This is a helper function to generate a JSON schema for string properties.
|
|
2
|
+
// It checks if a string includes '|' which would indicate an enum,
|
|
3
|
+
// or if it starts with 'number: ' or 'boolean: ' which would indicate
|
|
4
|
+
// a number or boolean type respectively, otherwise it defaults to string.
|
|
5
|
+
export const parseStringDescription = (description) => {
|
|
6
|
+
// Check if the description indicates an enum for string type
|
|
7
|
+
if (description.includes('|')) {
|
|
8
|
+
return { type: 'string', enum: description.split('|').map((v) => v.trim()) };
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
// Check for 'number: ' prefix
|
|
12
|
+
if (description.startsWith('number: ')) {
|
|
13
|
+
return {
|
|
14
|
+
type: 'number',
|
|
15
|
+
description: description.replace('number: ', ''),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Check for 'boolean: ' prefix
|
|
19
|
+
else if (description.startsWith('boolean: ')) {
|
|
20
|
+
return {
|
|
21
|
+
type: 'boolean',
|
|
22
|
+
description: description.replace('boolean: ', ''),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Default to string type
|
|
26
|
+
else {
|
|
27
|
+
return { type: 'string', description };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Given a property description object, generate a JSON schema.
|
|
33
|
+
*
|
|
34
|
+
* @param propDescriptions - A record object with keys as property names
|
|
35
|
+
* and values as descriptions or nested property description objects.
|
|
36
|
+
* @returns A JSON schema object based on the provided descriptions.
|
|
37
|
+
*/
|
|
38
|
+
export const generateSchema = (propDescriptions) => {
|
|
39
|
+
// If the propDescriptions is for an object structure
|
|
40
|
+
if (typeof propDescriptions !== 'object' || propDescriptions === null || Array.isArray(propDescriptions)) {
|
|
41
|
+
throw new Error('The propDescriptions parameter should be an object.');
|
|
42
|
+
}
|
|
43
|
+
const properties = {};
|
|
44
|
+
const required = Object.keys(propDescriptions);
|
|
45
|
+
for (const [key, description] of Object.entries(propDescriptions)) {
|
|
46
|
+
if (typeof description === 'string') {
|
|
47
|
+
// Handle the string description
|
|
48
|
+
properties[key] = parseStringDescription(description);
|
|
49
|
+
}
|
|
50
|
+
else if (typeof description === 'object' && !Array.isArray(description)) {
|
|
51
|
+
// Recursive call for nested objects
|
|
52
|
+
properties[key] = generateSchema(description);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
throw new Error(`Invalid description for key "${key}".`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { type: 'object', properties, required };
|
|
59
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateSchema, parseStringDescription } from './schema';
|
|
3
|
+
// Tests for parseStringDescription function
|
|
4
|
+
describe('parseStringDescription', () => {
|
|
5
|
+
it('should create a string schema for plain strings', () => {
|
|
6
|
+
const result = parseStringDescription('A plain string description');
|
|
7
|
+
expect(result).toEqual({
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'A plain string description',
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
it('should create a string schema with enum for piped strings', () => {
|
|
13
|
+
const result = parseStringDescription('option1 | option2 | option3');
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
type: 'string',
|
|
16
|
+
enum: ['option1', 'option2', 'option3'],
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
it('should create a number schema when prefixed with "number: "', () => {
|
|
20
|
+
const result = parseStringDescription('number: A number description');
|
|
21
|
+
expect(result).toEqual({
|
|
22
|
+
type: 'number',
|
|
23
|
+
description: 'A number description',
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it('should create a boolean schema when prefixed with "boolean: "', () => {
|
|
27
|
+
const result = parseStringDescription('boolean: A boolean description');
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
description: 'A boolean description',
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
// Tests for generateSchema function
|
|
35
|
+
describe('generateSchema', () => {
|
|
36
|
+
it('should create a complex object schema', () => {
|
|
37
|
+
const result = generateSchema({
|
|
38
|
+
name: 'The name of the person',
|
|
39
|
+
age: 'number: The age of the person',
|
|
40
|
+
isActive: 'boolean: Whether the person is active or not',
|
|
41
|
+
});
|
|
42
|
+
const expected = {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
name: { type: 'string', description: 'The name of the person' },
|
|
46
|
+
age: { type: 'number', description: 'The age of the person' },
|
|
47
|
+
isActive: {
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
description: 'Whether the person is active or not',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
required: ['name', 'age', 'isActive'],
|
|
53
|
+
};
|
|
54
|
+
expect(result).toEqual(expected);
|
|
55
|
+
});
|
|
56
|
+
it('should throw an error for invalid propDescriptions', () => {
|
|
57
|
+
const callWithInvalidArg = () => generateSchema('invalid argument');
|
|
58
|
+
expect(callWithInvalidArg).toThrow('The propDescriptions parameter should be an object.');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createMachine, createActor } from 'xstate';
|
|
2
|
+
// Stateless machine definition
|
|
3
|
+
// machine.transition(...) is a pure function used by the interpreter.
|
|
4
|
+
const toggleMachine = createMachine({
|
|
5
|
+
id: 'Switch',
|
|
6
|
+
initial: 'inactive',
|
|
7
|
+
states: {
|
|
8
|
+
inactive: { on: { Toggle: 'active' } },
|
|
9
|
+
active: { on: { Toggle: 'inactive' } },
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
// Machine instance with internal state
|
|
13
|
+
const toggleService = createActor(toggleMachine).start();
|
|
14
|
+
toggleService.subscribe((state) => console.log(state.value));
|
|
15
|
+
// => 'inactive'
|
|
16
|
+
toggleService.send({ type: 'Toggle' });
|
|
17
|
+
// => 'active'
|
|
18
|
+
toggleService.send({ type: 'Toggle' });
|
|
19
|
+
// => 'inactive'
|
package/fixup
ADDED
package/functions/ai.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type FunctionCallOptions = Omit<ChatCompletionCreateParamsBase, 'messages
|
|
|
20
20
|
|
|
21
21
|
type AIFunctions<T = Record<string,string>> = Record<string, (
|
|
22
22
|
returnSchema: T,
|
|
23
|
-
options?: FunctionCallOptions
|
|
23
|
+
options?: Partial<FunctionCallOptions>
|
|
24
24
|
) => (
|
|
25
25
|
args: string | object,
|
|
26
26
|
callOptions?: Partial<FunctionCallOptions>
|
package/functions/list.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OpenAI } from 'openai'
|
|
2
|
-
import { ChatCompletionCreateParamsStreaming } from 'openai/resources
|
|
2
|
+
import { ChatCompletionCreateParamsStreaming } from 'openai/resources'
|
|
3
3
|
import { GPTConfig } from '../types'
|
|
4
4
|
import { chatCompletion } from '../utils/completion'
|
|
5
5
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-functions",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "Library for Developing and Managing AI Functions (including OpenAI GPT4 / GPT3.5)",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
5
|
+
"main": "dist/cjs/index.js",
|
|
6
|
+
"module": "dist/mjs/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/mjs/index.js",
|
|
10
|
+
"require": "./dist/cjs/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"scripts": {
|
|
8
14
|
"build": "tsc",
|
|
15
|
+
"builds": "rm -fr dist/* && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./fixup",
|
|
9
16
|
"format": "prettier --write .",
|
|
10
17
|
"test": "dotenv -- vitest --globals"
|
|
11
18
|
},
|