@sprucelabs/sprucebot-llm 5.0.688 → 6.0.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.
- package/build/bots/SprucebotLlmBotImpl.d.ts +2 -2
- package/build/bots/SprucebotLlmBotImpl.js +6 -2
- package/build/bots/adapters/OpenAi.d.ts +2 -3
- package/build/bots/adapters/OpenAi.js +12 -17
- package/build/bots/adapters/OpenAiMessageBuilder.d.ts +17 -0
- package/build/bots/adapters/OpenAiMessageBuilder.js +115 -0
- package/build/bots/adapters/SpyOpenAiApi.d.ts +16 -9
- package/build/bots/adapters/SpyOpenAiApi.js +26 -17
- package/build/chat.js +2 -3
- package/build/esm/bots/SprucebotLlmBotImpl.d.ts +2 -2
- package/build/esm/bots/SprucebotLlmBotImpl.js +6 -2
- package/build/esm/bots/adapters/OpenAi.d.ts +2 -3
- package/build/esm/bots/adapters/OpenAi.js +12 -17
- package/build/esm/bots/adapters/OpenAiMessageBuilder.d.ts +17 -0
- package/build/esm/bots/adapters/OpenAiMessageBuilder.js +112 -0
- package/build/esm/bots/adapters/SpyOpenAiApi.d.ts +16 -9
- package/build/esm/bots/adapters/SpyOpenAiApi.js +23 -17
- package/build/esm/bots/templates.js +2 -2
- package/build/esm/chat.js +2 -3
- package/build/esm/examples/buildCallbackSkill.js +20 -1
- package/build/esm/examples/buildProfileSkill.js +1 -0
- package/build/esm/llm.types.d.ts +10 -2
- package/build/esm/parsingResponses/ResponseParser.d.ts +2 -1
- package/build/esm/parsingResponses/ResponseParser.js +23 -5
- package/build/esm/parsingResponses/renderPlaceholder.d.ts +1 -1
- package/build/esm/parsingResponses/renderPlaceholder.js +1 -1
- package/build/examples/buildCallbackSkill.js +20 -1
- package/build/examples/buildProfileSkill.js +1 -0
- package/build/llm.types.d.ts +10 -2
- package/build/parsingResponses/ResponseParser.d.ts +2 -1
- package/build/parsingResponses/ResponseParser.js +19 -2
- package/build/parsingResponses/renderPlaceholder.d.ts +1 -1
- package/build/parsingResponses/renderPlaceholder.js +2 -2
- package/package.json +3 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractEventEmitter } from '@sprucelabs/mercury-event-emitter';
|
|
2
2
|
import { Schema, SchemaValues } from '@sprucelabs/schema';
|
|
3
|
-
import { BotOptions, LlmAdapter, LlmEventContract, LlmMessage, SerializedBot, SprucebotLlmBot, SprucebotLLmSkill } from '../llm.types';
|
|
3
|
+
import { BotOptions, LlmAdapter, LlmEventContract, LlmMessage, MessageResponseCallback, SerializedBot, SprucebotLlmBot, SprucebotLLmSkill } from '../llm.types';
|
|
4
4
|
export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends AbstractEventEmitter<LlmEventContract> implements SprucebotLlmBot<StateSchema, State> {
|
|
5
5
|
static messageMemoryLimit: number;
|
|
6
6
|
protected adapter: LlmAdapter;
|
|
@@ -15,7 +15,7 @@ export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, St
|
|
|
15
15
|
markAsDone(): void;
|
|
16
16
|
getIsDone(): boolean;
|
|
17
17
|
serialize(): SerializedBot<StateSchema, State>;
|
|
18
|
-
sendMessage(message: string): Promise<string>;
|
|
18
|
+
sendMessage(message: string, cb?: MessageResponseCallback): Promise<string>;
|
|
19
19
|
private trackMessage;
|
|
20
20
|
updateState(newState: Partial<State>): Promise<void>;
|
|
21
21
|
setSkill(skill: SprucebotLLmSkill<any>): void;
|
|
@@ -43,7 +43,7 @@ class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
|
|
|
43
43
|
skill,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
async sendMessage(message) {
|
|
46
|
+
async sendMessage(message, cb) {
|
|
47
47
|
(0, schema_1.assertOptions)({ message }, ['message']);
|
|
48
48
|
this.trackMessage({
|
|
49
49
|
from: 'Me',
|
|
@@ -55,7 +55,7 @@ class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
|
|
|
55
55
|
promptTemplate: serializedSkill?.promptTemplate,
|
|
56
56
|
});
|
|
57
57
|
const parser = ResponseParser_1.default.getInstance();
|
|
58
|
-
const { isDone, message: parsedResponse, state, } = await parser.parse(response, serializedSkill?.callbacks);
|
|
58
|
+
const { isDone, message: parsedResponse, state, callbackResults, } = await parser.parse(response, serializedSkill?.callbacks);
|
|
59
59
|
this.isDone = isDone;
|
|
60
60
|
if (this.stateSchema && state) {
|
|
61
61
|
await this.updateState(state);
|
|
@@ -67,6 +67,10 @@ class SprucebotLlmBotImpl extends mercury_event_emitter_1.AbstractEventEmitter {
|
|
|
67
67
|
from: 'You',
|
|
68
68
|
message: parsedResponse,
|
|
69
69
|
});
|
|
70
|
+
cb?.(parsedResponse);
|
|
71
|
+
if (callbackResults) {
|
|
72
|
+
await this.sendMessage(`API Results: ${callbackResults}`, cb);
|
|
73
|
+
}
|
|
70
74
|
return parsedResponse;
|
|
71
75
|
}
|
|
72
76
|
trackMessage(m) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
2
|
import { LlmAdapter, SendMessageOptions, SprucebotLlmBot } from '../../llm.types';
|
|
3
3
|
export declare class OpenAiAdapter implements LlmAdapter {
|
|
4
|
-
static
|
|
5
|
-
static OpenAIApi: typeof OpenAIApi;
|
|
4
|
+
static OpenAI: typeof OpenAI;
|
|
6
5
|
private api;
|
|
7
6
|
constructor(apiKey: string);
|
|
8
7
|
sendMessage(bot: SprucebotLlmBot, options?: SendMessageOptions): Promise<string>;
|
|
@@ -5,30 +5,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.MESSAGE_RESPONSE_ERROR_MESSAGE = exports.OpenAiAdapter = void 0;
|
|
7
7
|
const schema_1 = require("@sprucelabs/schema");
|
|
8
|
-
const openai_1 = require("openai");
|
|
9
|
-
const
|
|
8
|
+
const openai_1 = __importDefault(require("openai"));
|
|
9
|
+
const OpenAiMessageBuilder_1 = __importDefault(require("./OpenAiMessageBuilder"));
|
|
10
10
|
class OpenAiAdapter {
|
|
11
11
|
constructor(apiKey) {
|
|
12
12
|
(0, schema_1.assertOptions)({ apiKey }, ['apiKey']);
|
|
13
|
-
|
|
14
|
-
this.api = new OpenAiAdapter.OpenAIApi(config);
|
|
13
|
+
this.api = new OpenAiAdapter.OpenAI({ apiKey });
|
|
15
14
|
}
|
|
16
15
|
async sendMessage(bot, options) {
|
|
17
|
-
const
|
|
18
|
-
|
|
16
|
+
const messageBuilder = OpenAiMessageBuilder_1.default.Builder(bot);
|
|
17
|
+
const messages = messageBuilder.buildMessages();
|
|
18
|
+
const response = await this.api.chat.completions.create({
|
|
19
|
+
messages,
|
|
20
|
+
model: options?.model ?? 'gpt-4o',
|
|
19
21
|
});
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
model: options?.model ?? 'text-davinci-003',
|
|
24
|
-
max_tokens: 250,
|
|
25
|
-
stop: ['__Me__:'],
|
|
26
|
-
});
|
|
27
|
-
return (response.data.choices[0]?.text?.trim() ??
|
|
28
|
-
exports.MESSAGE_RESPONSE_ERROR_MESSAGE);
|
|
22
|
+
const message = response.choices?.[0]?.message?.content?.trim() ??
|
|
23
|
+
exports.MESSAGE_RESPONSE_ERROR_MESSAGE;
|
|
24
|
+
return message;
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
exports.OpenAiAdapter = OpenAiAdapter;
|
|
32
|
-
OpenAiAdapter.
|
|
33
|
-
OpenAiAdapter.OpenAIApi = openai_1.OpenAIApi;
|
|
28
|
+
OpenAiAdapter.OpenAI = openai_1.default;
|
|
34
29
|
exports.MESSAGE_RESPONSE_ERROR_MESSAGE = "Oh no! Something went wrong and I can't talk right now!";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChatCompletionMessageParam } from 'openai/resources';
|
|
2
|
+
import { SprucebotLlmBot } from '../../llm.types';
|
|
3
|
+
export default class OpenAiMessageBuilder {
|
|
4
|
+
private bot;
|
|
5
|
+
protected constructor(bot: SprucebotLlmBot);
|
|
6
|
+
static Builder(bot: SprucebotLlmBot): OpenAiMessageBuilder;
|
|
7
|
+
buildMessages(): ChatCompletionMessageParam[];
|
|
8
|
+
private buildChatHistoryMessages;
|
|
9
|
+
private buildFirstMessage;
|
|
10
|
+
private buildSkillMessages;
|
|
11
|
+
private buildCallbacksMessage;
|
|
12
|
+
private buildPleaseKeepInMindMessage;
|
|
13
|
+
private buildStateMessage;
|
|
14
|
+
private buildYourJobMessage;
|
|
15
|
+
private buildWeAreDoneWhenMessage;
|
|
16
|
+
private buildStateSchemaMessage;
|
|
17
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const templates_1 = require("../templates");
|
|
4
|
+
class OpenAiMessageBuilder {
|
|
5
|
+
constructor(bot) {
|
|
6
|
+
this.bot = bot;
|
|
7
|
+
}
|
|
8
|
+
static Builder(bot) {
|
|
9
|
+
return new this(bot);
|
|
10
|
+
}
|
|
11
|
+
buildMessages() {
|
|
12
|
+
const values = this.bot.serialize();
|
|
13
|
+
const allMessages = [
|
|
14
|
+
this.buildFirstMessage(values.youAre),
|
|
15
|
+
...this.buildSkillMessages(values.skill),
|
|
16
|
+
...this.buildChatHistoryMessages(values.messages),
|
|
17
|
+
];
|
|
18
|
+
return allMessages;
|
|
19
|
+
}
|
|
20
|
+
buildChatHistoryMessages(messages) {
|
|
21
|
+
return messages.map((message) => ({
|
|
22
|
+
role: message.from === 'Me' ? 'user' : 'assistant',
|
|
23
|
+
content: message.message,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
buildFirstMessage(youAre) {
|
|
27
|
+
return {
|
|
28
|
+
role: 'system',
|
|
29
|
+
content: `You are ${youAre}.`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
buildSkillMessages(skill) {
|
|
33
|
+
if (!skill) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const messages = [];
|
|
37
|
+
messages.push(this.buildYourJobMessage(skill.yourJobIfYouChooseToAcceptItIs));
|
|
38
|
+
if (skill.stateSchema) {
|
|
39
|
+
messages.push(this.buildStateSchemaMessage(skill.stateSchema));
|
|
40
|
+
}
|
|
41
|
+
if (skill.state) {
|
|
42
|
+
messages.push(this.buildStateMessage(skill.state));
|
|
43
|
+
}
|
|
44
|
+
if (skill.weAreDoneWhen) {
|
|
45
|
+
messages.push(this.buildWeAreDoneWhenMessage(skill.weAreDoneWhen));
|
|
46
|
+
}
|
|
47
|
+
if (skill.pleaseKeepInMindThat) {
|
|
48
|
+
messages.push(this.buildPleaseKeepInMindMessage(skill.pleaseKeepInMindThat));
|
|
49
|
+
}
|
|
50
|
+
if (skill.callbacks) {
|
|
51
|
+
messages.push(this.buildCallbacksMessage(skill.callbacks));
|
|
52
|
+
}
|
|
53
|
+
return messages;
|
|
54
|
+
}
|
|
55
|
+
buildCallbacksMessage(callbacks) {
|
|
56
|
+
const keys = Object.keys(callbacks);
|
|
57
|
+
const descriptions = [];
|
|
58
|
+
for (const key of keys) {
|
|
59
|
+
const callback = callbacks[key];
|
|
60
|
+
let definition = `<Function name="${key}">
|
|
61
|
+
<Description>For use when ${callback.useThisWhenever}</Description>`;
|
|
62
|
+
if (callback.parameters) {
|
|
63
|
+
let params = '<Parameters>';
|
|
64
|
+
for (const param of callback.parameters) {
|
|
65
|
+
params += `
|
|
66
|
+
<Parameter${param.isRequired ? ' required="true"' : ''}>
|
|
67
|
+
<Name>${param.name}</Name>
|
|
68
|
+
<Type>${param.type}</Type>
|
|
69
|
+
${param.description ? `<Description>${param.description}</Description>` : ''}
|
|
70
|
+
</Parameter>`;
|
|
71
|
+
}
|
|
72
|
+
params += '</Parameters>';
|
|
73
|
+
definition += params;
|
|
74
|
+
}
|
|
75
|
+
definition += `</Function>`;
|
|
76
|
+
descriptions.push(definition);
|
|
77
|
+
}
|
|
78
|
+
const api = `<APIReference>\n\n${descriptions.join('\n\n')}</APIReference>`;
|
|
79
|
+
return {
|
|
80
|
+
role: 'system',
|
|
81
|
+
content: `You have an API available to you to lookup answers. When you need the response of the function call to proceed, you can call a function using a custom markup we created that looks like this: << FunctionName />>. The API will respond with the results and then you can continue the conversation with your new knowledge. If the api call has parameters, call it like this: << FunctionName >>parameters json encoded<</ FunctionName >>. Make sure to json encode the data and drop it between the function tags. I will respond with the api's response and you can use it going forward. The api is as follows (in xml format):\n\n${api}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
buildPleaseKeepInMindMessage(pleaseKeepInMindThat) {
|
|
85
|
+
return {
|
|
86
|
+
role: 'system',
|
|
87
|
+
content: `During this conversation, please keep the following in mind:\n\n${pleaseKeepInMindThat.map((m, idx) => `${idx + 1}. ${m}`).join('\n')}.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
buildStateMessage(state) {
|
|
91
|
+
return {
|
|
92
|
+
role: 'system',
|
|
93
|
+
content: `The current state of this conversation is:\n\n${JSON.stringify(state)}. As the state is being updated, send it back to me in json format (something in can JSON.parse()) at the end of each response (it's not meant for reading, but for parsing, so don't call it out, but send it as we progress), surrounded by a boundary, like this: ${templates_1.STATE_BOUNDARY} { "fieldName": "fieldValue" } ${templates_1.STATE_BOUNDARY}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
buildYourJobMessage(yourJob) {
|
|
97
|
+
return {
|
|
98
|
+
role: 'system',
|
|
99
|
+
content: `For this interaction, your job is ${yourJob}.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
buildWeAreDoneWhenMessage(weAreDoneWhen) {
|
|
103
|
+
return {
|
|
104
|
+
role: 'system',
|
|
105
|
+
content: `Our conversation is done when ${weAreDoneWhen}. Once you determine we are done, send me the following message so I know we're done: ${templates_1.DONE_TOKEN}`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
buildStateSchemaMessage(schema) {
|
|
109
|
+
return {
|
|
110
|
+
role: 'system',
|
|
111
|
+
content: `We will be tracking state for this conversation. The following schema is what we'll use to define the shape of the state:\n\n${JSON.stringify(schema)}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.default = OpenAiMessageBuilder;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export default class SpyOpenAiApi extends
|
|
4
|
-
static config?:
|
|
5
|
-
static
|
|
6
|
-
static lastModel?: string;
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { ChatCompletion, ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
3
|
+
export default class SpyOpenAiApi extends OpenAI {
|
|
4
|
+
static config?: OpenAiOptions;
|
|
5
|
+
static lastSentCompletion?: ChatCompletionCreateParamsNonStreaming;
|
|
7
6
|
static responseMessage: string | false;
|
|
8
|
-
constructor(config:
|
|
9
|
-
|
|
7
|
+
constructor(config: OpenAiOptions);
|
|
8
|
+
chat: {
|
|
9
|
+
completions: {
|
|
10
|
+
create: (options: ChatCompletionCreateParamsNonStreaming) => Promise<Response>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
private createCompletion;
|
|
14
|
+
}
|
|
15
|
+
type Response = ChatCompletion;
|
|
16
|
+
interface OpenAiOptions {
|
|
17
|
+
apiKey: string;
|
|
10
18
|
}
|
|
11
|
-
type Response = AxiosResponse<CreateCompletionResponse, any>;
|
|
12
19
|
export {};
|
|
@@ -1,32 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const openai_1 = require("openai");
|
|
4
|
-
class SpyOpenAiApi extends openai_1.
|
|
6
|
+
const openai_1 = __importDefault(require("openai"));
|
|
7
|
+
class SpyOpenAiApi extends openai_1.default {
|
|
5
8
|
constructor(config) {
|
|
6
9
|
super(config);
|
|
10
|
+
//@ts-ignore
|
|
11
|
+
this.chat = {
|
|
12
|
+
completions: {
|
|
13
|
+
create: this.createCompletion.bind(this),
|
|
14
|
+
},
|
|
15
|
+
};
|
|
7
16
|
SpyOpenAiApi.config = config;
|
|
8
17
|
}
|
|
9
|
-
async createCompletion(
|
|
10
|
-
SpyOpenAiApi.
|
|
11
|
-
SpyOpenAiApi.lastModel = createCompletionRequest.model;
|
|
18
|
+
async createCompletion(options) {
|
|
19
|
+
SpyOpenAiApi.lastSentCompletion = options;
|
|
12
20
|
const choices = [];
|
|
13
21
|
if (SpyOpenAiApi.responseMessage) {
|
|
14
22
|
choices.push({
|
|
15
|
-
|
|
23
|
+
finish_reason: 'stop',
|
|
24
|
+
index: 0,
|
|
25
|
+
logprobs: null,
|
|
26
|
+
message: {
|
|
27
|
+
content: SpyOpenAiApi.responseMessage,
|
|
28
|
+
role: 'assistant',
|
|
29
|
+
refusal: null,
|
|
30
|
+
},
|
|
16
31
|
});
|
|
17
32
|
}
|
|
18
33
|
return {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
id: 'cmpl-1',
|
|
25
|
-
model: 'text-davinci-003',
|
|
26
|
-
created: 0,
|
|
27
|
-
object: 'text_completion',
|
|
28
|
-
choices,
|
|
29
|
-
},
|
|
34
|
+
id: 'cmpl-1',
|
|
35
|
+
model: 'text-davinci-003',
|
|
36
|
+
created: 0,
|
|
37
|
+
choices,
|
|
38
|
+
object: 'chat.completion',
|
|
30
39
|
};
|
|
31
40
|
}
|
|
32
41
|
}
|
package/build/chat.js
CHANGED
|
@@ -61,13 +61,12 @@ void (async () => {
|
|
|
61
61
|
};
|
|
62
62
|
const bot = bots.Bot({
|
|
63
63
|
adapter,
|
|
64
|
-
skill: skills.
|
|
64
|
+
skill: skills.callbacks,
|
|
65
65
|
youAre: "a bot named Sprucebot that is in test mode. At the start of every conversation, you introduce yourself and announce that you are in test mode so I don't get confused! You are both hip and adorable. You say things like, 'Jeepers' and 'Golly' or even 'Jeezey peezy'!",
|
|
66
66
|
});
|
|
67
67
|
do {
|
|
68
68
|
const input = await rl.question('Message > ');
|
|
69
|
-
|
|
70
|
-
console.log('>', response);
|
|
69
|
+
await bot.sendMessage(input, (message) => console.log('>', message));
|
|
71
70
|
} while (!bot.getIsDone());
|
|
72
71
|
console.log('Signing off...');
|
|
73
72
|
rl.close();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractEventEmitter } from '@sprucelabs/mercury-event-emitter';
|
|
2
2
|
import { Schema, SchemaValues } from '@sprucelabs/schema';
|
|
3
|
-
import { BotOptions, LlmAdapter, LlmEventContract, LlmMessage, SerializedBot, SprucebotLlmBot, SprucebotLLmSkill } from '../llm.types';
|
|
3
|
+
import { BotOptions, LlmAdapter, LlmEventContract, LlmMessage, MessageResponseCallback, SerializedBot, SprucebotLlmBot, SprucebotLLmSkill } from '../llm.types';
|
|
4
4
|
export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends AbstractEventEmitter<LlmEventContract> implements SprucebotLlmBot<StateSchema, State> {
|
|
5
5
|
static messageMemoryLimit: number;
|
|
6
6
|
protected adapter: LlmAdapter;
|
|
@@ -15,7 +15,7 @@ export default class SprucebotLlmBotImpl<StateSchema extends Schema = Schema, St
|
|
|
15
15
|
markAsDone(): void;
|
|
16
16
|
getIsDone(): boolean;
|
|
17
17
|
serialize(): SerializedBot<StateSchema, State>;
|
|
18
|
-
sendMessage(message: string): Promise<string>;
|
|
18
|
+
sendMessage(message: string, cb?: MessageResponseCallback): Promise<string>;
|
|
19
19
|
private trackMessage;
|
|
20
20
|
updateState(newState: Partial<State>): Promise<void>;
|
|
21
21
|
setSkill(skill: SprucebotLLmSkill<any>): void;
|
|
@@ -45,7 +45,7 @@ class SprucebotLlmBotImpl extends AbstractEventEmitter {
|
|
|
45
45
|
skill,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
sendMessage(message) {
|
|
48
|
+
sendMessage(message, cb) {
|
|
49
49
|
return __awaiter(this, void 0, void 0, function* () {
|
|
50
50
|
var _a, _b;
|
|
51
51
|
assertOptions({ message }, ['message']);
|
|
@@ -59,7 +59,7 @@ class SprucebotLlmBotImpl extends AbstractEventEmitter {
|
|
|
59
59
|
promptTemplate: serializedSkill === null || serializedSkill === void 0 ? void 0 : serializedSkill.promptTemplate,
|
|
60
60
|
});
|
|
61
61
|
const parser = ResponseParser.getInstance();
|
|
62
|
-
const { isDone, message: parsedResponse, state, } = yield parser.parse(response, serializedSkill === null || serializedSkill === void 0 ? void 0 : serializedSkill.callbacks);
|
|
62
|
+
const { isDone, message: parsedResponse, state, callbackResults, } = yield parser.parse(response, serializedSkill === null || serializedSkill === void 0 ? void 0 : serializedSkill.callbacks);
|
|
63
63
|
this.isDone = isDone;
|
|
64
64
|
if (this.stateSchema && state) {
|
|
65
65
|
yield this.updateState(state);
|
|
@@ -71,6 +71,10 @@ class SprucebotLlmBotImpl extends AbstractEventEmitter {
|
|
|
71
71
|
from: 'You',
|
|
72
72
|
message: parsedResponse,
|
|
73
73
|
});
|
|
74
|
+
cb === null || cb === void 0 ? void 0 : cb(parsedResponse);
|
|
75
|
+
if (callbackResults) {
|
|
76
|
+
yield this.sendMessage(`API Results: ${callbackResults}`, cb);
|
|
77
|
+
}
|
|
74
78
|
return parsedResponse;
|
|
75
79
|
});
|
|
76
80
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
2
|
import { LlmAdapter, SendMessageOptions, SprucebotLlmBot } from '../../llm.types';
|
|
3
3
|
export declare class OpenAiAdapter implements LlmAdapter {
|
|
4
|
-
static
|
|
5
|
-
static OpenAIApi: typeof OpenAIApi;
|
|
4
|
+
static OpenAI: typeof OpenAI;
|
|
6
5
|
private api;
|
|
7
6
|
constructor(apiKey: string);
|
|
8
7
|
sendMessage(bot: SprucebotLlmBot, options?: SendMessageOptions): Promise<string>;
|
|
@@ -8,31 +8,26 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { assertOptions } from '@sprucelabs/schema';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
11
|
+
import OpenAI from 'openai';
|
|
12
|
+
import OpenAiMessageBuilder from './OpenAiMessageBuilder.js';
|
|
13
13
|
export class OpenAiAdapter {
|
|
14
14
|
constructor(apiKey) {
|
|
15
15
|
assertOptions({ apiKey }, ['apiKey']);
|
|
16
|
-
|
|
17
|
-
this.api = new OpenAiAdapter.OpenAIApi(config);
|
|
16
|
+
this.api = new OpenAiAdapter.OpenAI({ apiKey });
|
|
18
17
|
}
|
|
19
18
|
sendMessage(bot, options) {
|
|
20
19
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
-
var _a, _b, _c, _d;
|
|
22
|
-
const
|
|
23
|
-
|
|
20
|
+
var _a, _b, _c, _d, _e, _f;
|
|
21
|
+
const messageBuilder = OpenAiMessageBuilder.Builder(bot);
|
|
22
|
+
const messages = messageBuilder.buildMessages();
|
|
23
|
+
const response = yield this.api.chat.completions.create({
|
|
24
|
+
messages,
|
|
25
|
+
model: (_a = options === null || options === void 0 ? void 0 : options.model) !== null && _a !== void 0 ? _a : 'gpt-4o',
|
|
24
26
|
});
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
prompt,
|
|
28
|
-
model: (_a = options === null || options === void 0 ? void 0 : options.model) !== null && _a !== void 0 ? _a : 'text-davinci-003',
|
|
29
|
-
max_tokens: 250,
|
|
30
|
-
stop: ['__Me__:'],
|
|
31
|
-
});
|
|
32
|
-
return ((_d = (_c = (_b = response.data.choices[0]) === null || _b === void 0 ? void 0 : _b.text) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : MESSAGE_RESPONSE_ERROR_MESSAGE);
|
|
27
|
+
const message = (_f = (_e = (_d = (_c = (_b = response.choices) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.content) === null || _e === void 0 ? void 0 : _e.trim()) !== null && _f !== void 0 ? _f : MESSAGE_RESPONSE_ERROR_MESSAGE;
|
|
28
|
+
return message;
|
|
33
29
|
});
|
|
34
30
|
}
|
|
35
31
|
}
|
|
36
|
-
OpenAiAdapter.
|
|
37
|
-
OpenAiAdapter.OpenAIApi = OpenAIApi;
|
|
32
|
+
OpenAiAdapter.OpenAI = OpenAI;
|
|
38
33
|
export const MESSAGE_RESPONSE_ERROR_MESSAGE = "Oh no! Something went wrong and I can't talk right now!";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ChatCompletionMessageParam } from 'openai/resources';
|
|
2
|
+
import { SprucebotLlmBot } from '../../llm.types';
|
|
3
|
+
export default class OpenAiMessageBuilder {
|
|
4
|
+
private bot;
|
|
5
|
+
protected constructor(bot: SprucebotLlmBot);
|
|
6
|
+
static Builder(bot: SprucebotLlmBot): OpenAiMessageBuilder;
|
|
7
|
+
buildMessages(): ChatCompletionMessageParam[];
|
|
8
|
+
private buildChatHistoryMessages;
|
|
9
|
+
private buildFirstMessage;
|
|
10
|
+
private buildSkillMessages;
|
|
11
|
+
private buildCallbacksMessage;
|
|
12
|
+
private buildPleaseKeepInMindMessage;
|
|
13
|
+
private buildStateMessage;
|
|
14
|
+
private buildYourJobMessage;
|
|
15
|
+
private buildWeAreDoneWhenMessage;
|
|
16
|
+
private buildStateSchemaMessage;
|
|
17
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { DONE_TOKEN, STATE_BOUNDARY } from '../templates.js';
|
|
2
|
+
export default class OpenAiMessageBuilder {
|
|
3
|
+
constructor(bot) {
|
|
4
|
+
this.bot = bot;
|
|
5
|
+
}
|
|
6
|
+
static Builder(bot) {
|
|
7
|
+
return new this(bot);
|
|
8
|
+
}
|
|
9
|
+
buildMessages() {
|
|
10
|
+
const values = this.bot.serialize();
|
|
11
|
+
const allMessages = [
|
|
12
|
+
this.buildFirstMessage(values.youAre),
|
|
13
|
+
...this.buildSkillMessages(values.skill),
|
|
14
|
+
...this.buildChatHistoryMessages(values.messages),
|
|
15
|
+
];
|
|
16
|
+
return allMessages;
|
|
17
|
+
}
|
|
18
|
+
buildChatHistoryMessages(messages) {
|
|
19
|
+
return messages.map((message) => ({
|
|
20
|
+
role: message.from === 'Me' ? 'user' : 'assistant',
|
|
21
|
+
content: message.message,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
buildFirstMessage(youAre) {
|
|
25
|
+
return {
|
|
26
|
+
role: 'system',
|
|
27
|
+
content: `You are ${youAre}.`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
buildSkillMessages(skill) {
|
|
31
|
+
if (!skill) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const messages = [];
|
|
35
|
+
messages.push(this.buildYourJobMessage(skill.yourJobIfYouChooseToAcceptItIs));
|
|
36
|
+
if (skill.stateSchema) {
|
|
37
|
+
messages.push(this.buildStateSchemaMessage(skill.stateSchema));
|
|
38
|
+
}
|
|
39
|
+
if (skill.state) {
|
|
40
|
+
messages.push(this.buildStateMessage(skill.state));
|
|
41
|
+
}
|
|
42
|
+
if (skill.weAreDoneWhen) {
|
|
43
|
+
messages.push(this.buildWeAreDoneWhenMessage(skill.weAreDoneWhen));
|
|
44
|
+
}
|
|
45
|
+
if (skill.pleaseKeepInMindThat) {
|
|
46
|
+
messages.push(this.buildPleaseKeepInMindMessage(skill.pleaseKeepInMindThat));
|
|
47
|
+
}
|
|
48
|
+
if (skill.callbacks) {
|
|
49
|
+
messages.push(this.buildCallbacksMessage(skill.callbacks));
|
|
50
|
+
}
|
|
51
|
+
return messages;
|
|
52
|
+
}
|
|
53
|
+
buildCallbacksMessage(callbacks) {
|
|
54
|
+
const keys = Object.keys(callbacks);
|
|
55
|
+
const descriptions = [];
|
|
56
|
+
for (const key of keys) {
|
|
57
|
+
const callback = callbacks[key];
|
|
58
|
+
let definition = `<Function name="${key}">
|
|
59
|
+
<Description>For use when ${callback.useThisWhenever}</Description>`;
|
|
60
|
+
if (callback.parameters) {
|
|
61
|
+
let params = '<Parameters>';
|
|
62
|
+
for (const param of callback.parameters) {
|
|
63
|
+
params += `
|
|
64
|
+
<Parameter${param.isRequired ? ' required="true"' : ''}>
|
|
65
|
+
<Name>${param.name}</Name>
|
|
66
|
+
<Type>${param.type}</Type>
|
|
67
|
+
${param.description ? `<Description>${param.description}</Description>` : ''}
|
|
68
|
+
</Parameter>`;
|
|
69
|
+
}
|
|
70
|
+
params += '</Parameters>';
|
|
71
|
+
definition += params;
|
|
72
|
+
}
|
|
73
|
+
definition += `</Function>`;
|
|
74
|
+
descriptions.push(definition);
|
|
75
|
+
}
|
|
76
|
+
const api = `<APIReference>\n\n${descriptions.join('\n\n')}</APIReference>`;
|
|
77
|
+
return {
|
|
78
|
+
role: 'system',
|
|
79
|
+
content: `You have an API available to you to lookup answers. When you need the response of the function call to proceed, you can call a function using a custom markup we created that looks like this: << FunctionName />>. The API will respond with the results and then you can continue the conversation with your new knowledge. If the api call has parameters, call it like this: << FunctionName >>parameters json encoded<</ FunctionName >>. Make sure to json encode the data and drop it between the function tags. I will respond with the api's response and you can use it going forward. The api is as follows (in xml format):\n\n${api}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
buildPleaseKeepInMindMessage(pleaseKeepInMindThat) {
|
|
83
|
+
return {
|
|
84
|
+
role: 'system',
|
|
85
|
+
content: `During this conversation, please keep the following in mind:\n\n${pleaseKeepInMindThat.map((m, idx) => `${idx + 1}. ${m}`).join('\n')}.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
buildStateMessage(state) {
|
|
89
|
+
return {
|
|
90
|
+
role: 'system',
|
|
91
|
+
content: `The current state of this conversation is:\n\n${JSON.stringify(state)}. As the state is being updated, send it back to me in json format (something in can JSON.parse()) at the end of each response (it's not meant for reading, but for parsing, so don't call it out, but send it as we progress), surrounded by a boundary, like this: ${STATE_BOUNDARY} { "fieldName": "fieldValue" } ${STATE_BOUNDARY}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
buildYourJobMessage(yourJob) {
|
|
95
|
+
return {
|
|
96
|
+
role: 'system',
|
|
97
|
+
content: `For this interaction, your job is ${yourJob}.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
buildWeAreDoneWhenMessage(weAreDoneWhen) {
|
|
101
|
+
return {
|
|
102
|
+
role: 'system',
|
|
103
|
+
content: `Our conversation is done when ${weAreDoneWhen}. Once you determine we are done, send me the following message so I know we're done: ${DONE_TOKEN}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
buildStateSchemaMessage(schema) {
|
|
107
|
+
return {
|
|
108
|
+
role: 'system',
|
|
109
|
+
content: `We will be tracking state for this conversation. The following schema is what we'll use to define the shape of the state:\n\n${JSON.stringify(schema)}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export default class SpyOpenAiApi extends
|
|
4
|
-
static config?:
|
|
5
|
-
static
|
|
6
|
-
static lastModel?: string;
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { ChatCompletion, ChatCompletionCreateParamsNonStreaming } from 'openai/resources';
|
|
3
|
+
export default class SpyOpenAiApi extends OpenAI {
|
|
4
|
+
static config?: OpenAiOptions;
|
|
5
|
+
static lastSentCompletion?: ChatCompletionCreateParamsNonStreaming;
|
|
7
6
|
static responseMessage: string | false;
|
|
8
|
-
constructor(config:
|
|
9
|
-
|
|
7
|
+
constructor(config: OpenAiOptions);
|
|
8
|
+
chat: {
|
|
9
|
+
completions: {
|
|
10
|
+
create: (options: ChatCompletionCreateParamsNonStreaming) => Promise<Response>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
private createCompletion;
|
|
14
|
+
}
|
|
15
|
+
type Response = ChatCompletion;
|
|
16
|
+
interface OpenAiOptions {
|
|
17
|
+
apiKey: string;
|
|
10
18
|
}
|
|
11
|
-
type Response = AxiosResponse<CreateCompletionResponse, any>;
|
|
12
19
|
export {};
|
|
@@ -7,34 +7,40 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import
|
|
11
|
-
class SpyOpenAiApi extends
|
|
10
|
+
import OpenAI from 'openai';
|
|
11
|
+
class SpyOpenAiApi extends OpenAI {
|
|
12
12
|
constructor(config) {
|
|
13
13
|
super(config);
|
|
14
|
+
//@ts-ignore
|
|
15
|
+
this.chat = {
|
|
16
|
+
completions: {
|
|
17
|
+
create: this.createCompletion.bind(this),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
14
20
|
SpyOpenAiApi.config = config;
|
|
15
21
|
}
|
|
16
|
-
createCompletion(
|
|
22
|
+
createCompletion(options) {
|
|
17
23
|
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
-
SpyOpenAiApi.
|
|
19
|
-
SpyOpenAiApi.lastModel = createCompletionRequest.model;
|
|
24
|
+
SpyOpenAiApi.lastSentCompletion = options;
|
|
20
25
|
const choices = [];
|
|
21
26
|
if (SpyOpenAiApi.responseMessage) {
|
|
22
27
|
choices.push({
|
|
23
|
-
|
|
28
|
+
finish_reason: 'stop',
|
|
29
|
+
index: 0,
|
|
30
|
+
logprobs: null,
|
|
31
|
+
message: {
|
|
32
|
+
content: SpyOpenAiApi.responseMessage,
|
|
33
|
+
role: 'assistant',
|
|
34
|
+
refusal: null,
|
|
35
|
+
},
|
|
24
36
|
});
|
|
25
37
|
}
|
|
26
38
|
return {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
id: 'cmpl-1',
|
|
33
|
-
model: 'text-davinci-003',
|
|
34
|
-
created: 0,
|
|
35
|
-
object: 'text_completion',
|
|
36
|
-
choices,
|
|
37
|
-
},
|
|
39
|
+
id: 'cmpl-1',
|
|
40
|
+
model: 'text-davinci-003',
|
|
41
|
+
created: 0,
|
|
42
|
+
choices,
|
|
43
|
+
object: 'chat.completion',
|
|
38
44
|
};
|
|
39
45
|
});
|
|
40
46
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import renderLegacyPlaceholder from '../parsingResponses/renderPlaceholder.js';
|
|
2
2
|
export const STATE_BOUNDARY = '*****';
|
|
3
3
|
export const DONE_TOKEN = `DONE_DONE_DONE`;
|
|
4
4
|
export const CALLBACK_BOUNDARY = 'xxxxx';
|
|
@@ -37,7 +37,7 @@ When asking me about a "select" field, make sure I only pick a valid choice by s
|
|
|
37
37
|
|
|
38
38
|
Your primary objective for this conversation is <%= it.skill.yourJobIfYouChooseToAcceptItIs %>
|
|
39
39
|
<% if (it.skill.callbacks) { %>
|
|
40
|
-
While we are talking, there are going to be things I don't want you to answer, but instead to respond with a placeholder in the form of ${
|
|
40
|
+
While we are talking, there are going to be things I don't want you to answer, but instead to respond with a placeholder in the form of ${renderLegacyPlaceholder('example')}.
|
|
41
41
|
|
|
42
42
|
Here are the placeholders we will be using:
|
|
43
43
|
|
package/build/esm/chat.js
CHANGED
|
@@ -32,13 +32,12 @@ void (() => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
32
32
|
};
|
|
33
33
|
const bot = bots.Bot({
|
|
34
34
|
adapter,
|
|
35
|
-
skill: skills.
|
|
35
|
+
skill: skills.callbacks,
|
|
36
36
|
youAre: "a bot named Sprucebot that is in test mode. At the start of every conversation, you introduce yourself and announce that you are in test mode so I don't get confused! You are both hip and adorable. You say things like, 'Jeepers' and 'Golly' or even 'Jeezey peezy'!",
|
|
37
37
|
});
|
|
38
38
|
do {
|
|
39
39
|
const input = yield rl.question('Message > ');
|
|
40
|
-
|
|
41
|
-
console.log('>', response);
|
|
40
|
+
yield bot.sendMessage(input, (message) => console.log('>', message));
|
|
42
41
|
} while (!bot.getIsDone());
|
|
43
42
|
console.log('Signing off...');
|
|
44
43
|
rl.close();
|
|
@@ -10,7 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
export default function buildCallbackSkill(bots) {
|
|
11
11
|
return bots.Skill({
|
|
12
12
|
weAreDoneWhen: 'the appointment is booked!',
|
|
13
|
-
yourJobIfYouChooseToAcceptItIs: "to be be the best appointment taker on the planet. You have a many years of experience. You are going to ask me only 2 questions for this practice run. First, you'll ask me to pick an available time. Then, you'll ask me to pick my favorite color
|
|
13
|
+
yourJobIfYouChooseToAcceptItIs: "to be be the best appointment taker on the planet. You have a many years of experience. You are going to ask me only 2 questions for this practice run. First, you'll ask me to pick an available time. Then, you'll ask me to pick my favorite color (make sure to call the api to see what times and colors i can choose from). After all is said and done, make sure to actually book the appointment!:",
|
|
14
14
|
pleaseKeepInMindThat: [
|
|
15
15
|
"getting a service is really important, so if i don't like any of the time, as to check another day or if i want to see a different provider",
|
|
16
16
|
],
|
|
@@ -35,6 +35,25 @@ export default function buildCallbackSkill(bots) {
|
|
|
35
35
|
}),
|
|
36
36
|
useThisWhenever: 'your are showing what colors i can pick from.',
|
|
37
37
|
},
|
|
38
|
+
book: {
|
|
39
|
+
cb: (options) => __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
console.log('BOOKING OPTIONS', options);
|
|
41
|
+
return 'Appointment booked!';
|
|
42
|
+
}),
|
|
43
|
+
useThisWhenever: 'You are ready to book an appointment!',
|
|
44
|
+
parameters: [
|
|
45
|
+
{
|
|
46
|
+
name: 'time',
|
|
47
|
+
isRequired: true,
|
|
48
|
+
type: 'string',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'color',
|
|
52
|
+
isRequired: true,
|
|
53
|
+
type: 'string',
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
38
57
|
},
|
|
39
58
|
});
|
|
40
59
|
}
|
|
@@ -2,6 +2,7 @@ import { buildSchema } from '@sprucelabs/schema';
|
|
|
2
2
|
export default function buildProfileSkill(bots) {
|
|
3
3
|
return bots.Skill({
|
|
4
4
|
yourJobIfYouChooseToAcceptItIs: 'to collect some information from me! You are a receptionist with 20 years experience and are very focused on getting answers needed to complete my profile',
|
|
5
|
+
weAreDoneWhen: 'You have all the information to complete my profile',
|
|
5
6
|
stateSchema: buildSchema({
|
|
6
7
|
id: 'profile',
|
|
7
8
|
fields: {
|
package/build/esm/llm.types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface BotOptions<StateSchema extends Schema = Schema, State extends S
|
|
|
8
8
|
export interface SprucebotLlmBot<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends MercuryEventEmitter<LlmEventContract> {
|
|
9
9
|
markAsDone(): void;
|
|
10
10
|
getIsDone(): boolean;
|
|
11
|
-
sendMessage(message: string): Promise<string>;
|
|
11
|
+
sendMessage(message: string, responseCb?: MessageResponseCallback): Promise<string>;
|
|
12
12
|
serialize(): SerializedBot<StateSchema, State>;
|
|
13
13
|
updateState(state: Partial<State>): Promise<void>;
|
|
14
14
|
setSkill(skill: SprucebotLLmSkill<any>): void;
|
|
@@ -62,6 +62,14 @@ export interface SerializedSkill<StateSchema extends Schema = Schema, State exte
|
|
|
62
62
|
}
|
|
63
63
|
export type LlmCallbackMap = Record<string, LlmCallback>;
|
|
64
64
|
export interface LlmCallback {
|
|
65
|
-
cb: () => string | Promise<string>;
|
|
65
|
+
cb: (options?: Record<string, any>) => string | Promise<string>;
|
|
66
66
|
useThisWhenever: string;
|
|
67
|
+
parameters?: LlmCallbackParameter[];
|
|
67
68
|
}
|
|
69
|
+
export interface LlmCallbackParameter {
|
|
70
|
+
name: string;
|
|
71
|
+
type: ('string' | 'number' | 'boolean' | 'dateMs' | 'dateTimeMs') | (string & {});
|
|
72
|
+
isRequired?: boolean;
|
|
73
|
+
description?: string;
|
|
74
|
+
}
|
|
75
|
+
export type MessageResponseCallback = (message: string) => any;
|
|
@@ -4,7 +4,7 @@ export default class ResponseParser {
|
|
|
4
4
|
static setInstance(parser: ResponseParser): void;
|
|
5
5
|
static getInstance(): ResponseParser;
|
|
6
6
|
parse(response: string, callbacks?: LlmCallbackMap): Promise<ParsedResponse>;
|
|
7
|
-
private
|
|
7
|
+
private invokeCallbackAndDropInLegacyResults;
|
|
8
8
|
private doesIncludeDoneToken;
|
|
9
9
|
private parseState;
|
|
10
10
|
}
|
|
@@ -12,4 +12,5 @@ export interface ParsedResponse {
|
|
|
12
12
|
isDone: boolean;
|
|
13
13
|
state?: Record<string, any>;
|
|
14
14
|
message: string;
|
|
15
|
+
callbackResults?: string;
|
|
15
16
|
}
|
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { DONE_TOKEN, STATE_BOUNDARY } from '../bots/templates.js';
|
|
11
|
-
import
|
|
11
|
+
import renderLegacyPlaceholder from './renderPlaceholder.js';
|
|
12
12
|
class ResponseParser {
|
|
13
13
|
static setInstance(parser) {
|
|
14
14
|
this.instance = parser;
|
|
@@ -18,12 +18,29 @@ class ResponseParser {
|
|
|
18
18
|
}
|
|
19
19
|
parse(response, callbacks) {
|
|
20
20
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
var _a;
|
|
21
22
|
let message = response.replace(DONE_TOKEN, '').trim();
|
|
22
23
|
let state;
|
|
24
|
+
let callbackResults;
|
|
23
25
|
for (const key of Object.keys(callbacks || {})) {
|
|
24
|
-
const match = message.match(
|
|
26
|
+
const match = message.match(renderLegacyPlaceholder(key));
|
|
25
27
|
if (match) {
|
|
26
|
-
message = yield this.
|
|
28
|
+
message = yield this.invokeCallbackAndDropInLegacyResults(callbacks, key, message);
|
|
29
|
+
}
|
|
30
|
+
let simpleMatches = message.match(new RegExp(`<<\\s*${key}\\s*\/>>`, 'g'));
|
|
31
|
+
let data;
|
|
32
|
+
if (!simpleMatches) {
|
|
33
|
+
const matchWithJson = message
|
|
34
|
+
.matchAll(new RegExp(`<<\\s*${key}\\s*>>(.*?)<<\/\\s*${key}\\s*>>`, 'gs'))
|
|
35
|
+
.next().value;
|
|
36
|
+
simpleMatches = (matchWithJson === null || matchWithJson === void 0 ? void 0 : matchWithJson[0]) ? [matchWithJson === null || matchWithJson === void 0 ? void 0 : matchWithJson[0]] : null;
|
|
37
|
+
data = (matchWithJson === null || matchWithJson === void 0 ? void 0 : matchWithJson[1])
|
|
38
|
+
? JSON.parse(matchWithJson === null || matchWithJson === void 0 ? void 0 : matchWithJson[1])
|
|
39
|
+
: undefined;
|
|
40
|
+
}
|
|
41
|
+
if (simpleMatches) {
|
|
42
|
+
callbackResults = yield ((_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks[key]) === null || _a === void 0 ? void 0 : _a.cb(data));
|
|
43
|
+
message = message.replace(simpleMatches[0], '').trim();
|
|
27
44
|
}
|
|
28
45
|
}
|
|
29
46
|
const { match, fullMatch } = this.parseState(message);
|
|
@@ -35,14 +52,15 @@ class ResponseParser {
|
|
|
35
52
|
isDone: this.doesIncludeDoneToken(response),
|
|
36
53
|
state,
|
|
37
54
|
message,
|
|
55
|
+
callbackResults,
|
|
38
56
|
};
|
|
39
57
|
});
|
|
40
58
|
}
|
|
41
|
-
|
|
59
|
+
invokeCallbackAndDropInLegacyResults(callbacks, key, message) {
|
|
42
60
|
return __awaiter(this, void 0, void 0, function* () {
|
|
43
61
|
var _a;
|
|
44
62
|
const v = yield ((_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks[key]) === null || _a === void 0 ? void 0 : _a.cb());
|
|
45
|
-
message = message.replace(
|
|
63
|
+
message = message.replace(renderLegacyPlaceholder(key), v !== null && v !== void 0 ? v : '').trim();
|
|
46
64
|
return message;
|
|
47
65
|
});
|
|
48
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function
|
|
1
|
+
export default function renderLegacyPlaceholder(key: string): string;
|
|
@@ -4,7 +4,7 @@ exports.default = buildCallbackSkill;
|
|
|
4
4
|
function buildCallbackSkill(bots) {
|
|
5
5
|
return bots.Skill({
|
|
6
6
|
weAreDoneWhen: 'the appointment is booked!',
|
|
7
|
-
yourJobIfYouChooseToAcceptItIs: "to be be the best appointment taker on the planet. You have a many years of experience. You are going to ask me only 2 questions for this practice run. First, you'll ask me to pick an available time. Then, you'll ask me to pick my favorite color
|
|
7
|
+
yourJobIfYouChooseToAcceptItIs: "to be be the best appointment taker on the planet. You have a many years of experience. You are going to ask me only 2 questions for this practice run. First, you'll ask me to pick an available time. Then, you'll ask me to pick my favorite color (make sure to call the api to see what times and colors i can choose from). After all is said and done, make sure to actually book the appointment!:",
|
|
8
8
|
pleaseKeepInMindThat: [
|
|
9
9
|
"getting a service is really important, so if i don't like any of the time, as to check another day or if i want to see a different provider",
|
|
10
10
|
],
|
|
@@ -29,6 +29,25 @@ function buildCallbackSkill(bots) {
|
|
|
29
29
|
},
|
|
30
30
|
useThisWhenever: 'your are showing what colors i can pick from.',
|
|
31
31
|
},
|
|
32
|
+
book: {
|
|
33
|
+
cb: async (options) => {
|
|
34
|
+
console.log('BOOKING OPTIONS', options);
|
|
35
|
+
return 'Appointment booked!';
|
|
36
|
+
},
|
|
37
|
+
useThisWhenever: 'You are ready to book an appointment!',
|
|
38
|
+
parameters: [
|
|
39
|
+
{
|
|
40
|
+
name: 'time',
|
|
41
|
+
isRequired: true,
|
|
42
|
+
type: 'string',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'color',
|
|
46
|
+
isRequired: true,
|
|
47
|
+
type: 'string',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
32
51
|
},
|
|
33
52
|
});
|
|
34
53
|
}
|
|
@@ -5,6 +5,7 @@ const schema_1 = require("@sprucelabs/schema");
|
|
|
5
5
|
function buildProfileSkill(bots) {
|
|
6
6
|
return bots.Skill({
|
|
7
7
|
yourJobIfYouChooseToAcceptItIs: 'to collect some information from me! You are a receptionist with 20 years experience and are very focused on getting answers needed to complete my profile',
|
|
8
|
+
weAreDoneWhen: 'You have all the information to complete my profile',
|
|
8
9
|
stateSchema: (0, schema_1.buildSchema)({
|
|
9
10
|
id: 'profile',
|
|
10
11
|
fields: {
|
package/build/llm.types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface BotOptions<StateSchema extends Schema = Schema, State extends S
|
|
|
8
8
|
export interface SprucebotLlmBot<StateSchema extends Schema = Schema, State extends SchemaValues<StateSchema> = SchemaValues<StateSchema>> extends MercuryEventEmitter<LlmEventContract> {
|
|
9
9
|
markAsDone(): void;
|
|
10
10
|
getIsDone(): boolean;
|
|
11
|
-
sendMessage(message: string): Promise<string>;
|
|
11
|
+
sendMessage(message: string, responseCb?: MessageResponseCallback): Promise<string>;
|
|
12
12
|
serialize(): SerializedBot<StateSchema, State>;
|
|
13
13
|
updateState(state: Partial<State>): Promise<void>;
|
|
14
14
|
setSkill(skill: SprucebotLLmSkill<any>): void;
|
|
@@ -62,6 +62,14 @@ export interface SerializedSkill<StateSchema extends Schema = Schema, State exte
|
|
|
62
62
|
}
|
|
63
63
|
export type LlmCallbackMap = Record<string, LlmCallback>;
|
|
64
64
|
export interface LlmCallback {
|
|
65
|
-
cb: () => string | Promise<string>;
|
|
65
|
+
cb: (options?: Record<string, any>) => string | Promise<string>;
|
|
66
66
|
useThisWhenever: string;
|
|
67
|
+
parameters?: LlmCallbackParameter[];
|
|
67
68
|
}
|
|
69
|
+
export interface LlmCallbackParameter {
|
|
70
|
+
name: string;
|
|
71
|
+
type: ('string' | 'number' | 'boolean' | 'dateMs' | 'dateTimeMs') | (string & {});
|
|
72
|
+
isRequired?: boolean;
|
|
73
|
+
description?: string;
|
|
74
|
+
}
|
|
75
|
+
export type MessageResponseCallback = (message: string) => any;
|
|
@@ -4,7 +4,7 @@ export default class ResponseParser {
|
|
|
4
4
|
static setInstance(parser: ResponseParser): void;
|
|
5
5
|
static getInstance(): ResponseParser;
|
|
6
6
|
parse(response: string, callbacks?: LlmCallbackMap): Promise<ParsedResponse>;
|
|
7
|
-
private
|
|
7
|
+
private invokeCallbackAndDropInLegacyResults;
|
|
8
8
|
private doesIncludeDoneToken;
|
|
9
9
|
private parseState;
|
|
10
10
|
}
|
|
@@ -12,4 +12,5 @@ export interface ParsedResponse {
|
|
|
12
12
|
isDone: boolean;
|
|
13
13
|
state?: Record<string, any>;
|
|
14
14
|
message: string;
|
|
15
|
+
callbackResults?: string;
|
|
15
16
|
}
|
|
@@ -15,10 +15,26 @@ class ResponseParser {
|
|
|
15
15
|
async parse(response, callbacks) {
|
|
16
16
|
let message = response.replace(templates_1.DONE_TOKEN, '').trim();
|
|
17
17
|
let state;
|
|
18
|
+
let callbackResults;
|
|
18
19
|
for (const key of Object.keys(callbacks || {})) {
|
|
19
20
|
const match = message.match((0, renderPlaceholder_1.default)(key));
|
|
20
21
|
if (match) {
|
|
21
|
-
message = await this.
|
|
22
|
+
message = await this.invokeCallbackAndDropInLegacyResults(callbacks, key, message);
|
|
23
|
+
}
|
|
24
|
+
let simpleMatches = message.match(new RegExp(`<<\\s*${key}\\s*\/>>`, 'g'));
|
|
25
|
+
let data;
|
|
26
|
+
if (!simpleMatches) {
|
|
27
|
+
const matchWithJson = message
|
|
28
|
+
.matchAll(new RegExp(`<<\\s*${key}\\s*>>(.*?)<<\/\\s*${key}\\s*>>`, 'gs'))
|
|
29
|
+
.next().value;
|
|
30
|
+
simpleMatches = matchWithJson?.[0] ? [matchWithJson?.[0]] : null;
|
|
31
|
+
data = matchWithJson?.[1]
|
|
32
|
+
? JSON.parse(matchWithJson?.[1])
|
|
33
|
+
: undefined;
|
|
34
|
+
}
|
|
35
|
+
if (simpleMatches) {
|
|
36
|
+
callbackResults = await callbacks?.[key]?.cb(data);
|
|
37
|
+
message = message.replace(simpleMatches[0], '').trim();
|
|
22
38
|
}
|
|
23
39
|
}
|
|
24
40
|
const { match, fullMatch } = this.parseState(message);
|
|
@@ -30,9 +46,10 @@ class ResponseParser {
|
|
|
30
46
|
isDone: this.doesIncludeDoneToken(response),
|
|
31
47
|
state,
|
|
32
48
|
message,
|
|
49
|
+
callbackResults,
|
|
33
50
|
};
|
|
34
51
|
}
|
|
35
|
-
async
|
|
52
|
+
async invokeCallbackAndDropInLegacyResults(callbacks, key, message) {
|
|
36
53
|
const v = await callbacks?.[key]?.cb();
|
|
37
54
|
message = message.replace((0, renderPlaceholder_1.default)(key), v ?? '').trim();
|
|
38
55
|
return message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default function
|
|
1
|
+
export default function renderLegacyPlaceholder(key: string): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.default =
|
|
3
|
+
exports.default = renderLegacyPlaceholder;
|
|
4
4
|
const templates_1 = require("../bots/templates");
|
|
5
|
-
function
|
|
5
|
+
function renderLegacyPlaceholder(key) {
|
|
6
6
|
return `${templates_1.CALLBACK_BOUNDARY} ${key} ${templates_1.CALLBACK_BOUNDARY}`;
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
"upgradeIgnoreList": [
|
|
7
7
|
"@sprucelabs/spruce-core-schemas",
|
|
8
8
|
"@sprucelabs/spruce-skill-utils",
|
|
9
|
-
"@sprucelabs/spruce-test-fixtures"
|
|
10
|
-
"openai"
|
|
9
|
+
"@sprucelabs/spruce-test-fixtures"
|
|
11
10
|
]
|
|
12
11
|
},
|
|
13
|
-
"version": "
|
|
12
|
+
"version": "6.0.0",
|
|
14
13
|
"files": [
|
|
15
14
|
"build"
|
|
16
15
|
],
|
|
@@ -59,7 +58,7 @@
|
|
|
59
58
|
"@sprucelabs/mercury-types": "^47.0.641",
|
|
60
59
|
"@sprucelabs/schema": "^31.0.3",
|
|
61
60
|
"eta": "^3.5.0",
|
|
62
|
-
"openai": "^
|
|
61
|
+
"openai": "^4.77.0"
|
|
63
62
|
},
|
|
64
63
|
"devDependencies": {
|
|
65
64
|
"@sprucelabs/esm-postbuild": "^6.0.533",
|