@sprucelabs/sprucebot-llm 15.1.8 → 15.1.9
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/README.md
CHANGED
|
@@ -16,6 +16,7 @@ A TypeScript library for leveraging large language models to do... anything!
|
|
|
16
16
|
* Unlimited use cases
|
|
17
17
|
* Skill architecture for extensibility
|
|
18
18
|
* Leverage Skills to get your bot to complete any task!
|
|
19
|
+
* [Swap skills mid-conversation](#swapping-skills) to chain multi-step flows
|
|
19
20
|
* Multiple adapter support
|
|
20
21
|
* [OpenAI](#openai-adapter-configuration) - GPT-4o, o1, and other OpenAI models
|
|
21
22
|
* [Anthropic](#anthropic-adapter) - Claude models with prompt caching support
|
|
@@ -467,6 +468,127 @@ const bookingBot = bots.Bot({
|
|
|
467
468
|
|
|
468
469
|
If you are using reasoning models that accept `reasoning_effort`, you can set it via `OPENAI_REASONING_EFFORT` or `adapter.setReasoningEffort(...)`.
|
|
469
470
|
|
|
471
|
+
## Swapping Skills
|
|
472
|
+
|
|
473
|
+
You can replace the active skill on a bot at any time — even mid-conversation — by calling `bot.setSkill(newSkill)`. This is useful for multi-step flows where each phase of the conversation has a different job, state schema, or set of callbacks.
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
const greetingSkill = bots.Skill({
|
|
477
|
+
yourJobIfYouChooseToAcceptItIs: 'to greet the user and ask for their name',
|
|
478
|
+
weAreDoneWhen: 'you have collected the user\'s name',
|
|
479
|
+
stateSchema: buildSchema({
|
|
480
|
+
id: 'greeting',
|
|
481
|
+
fields: {
|
|
482
|
+
firstName: { type: 'text', label: 'First name' },
|
|
483
|
+
},
|
|
484
|
+
}),
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
const bookingSkill = bots.Skill({
|
|
488
|
+
yourJobIfYouChooseToAcceptItIs: 'to book an appointment for the user',
|
|
489
|
+
weAreDoneWhen: 'the appointment is booked',
|
|
490
|
+
callbacks: {
|
|
491
|
+
book: {
|
|
492
|
+
cb: async (options) => {
|
|
493
|
+
await bookAppointment(options)
|
|
494
|
+
return 'Appointment booked!'
|
|
495
|
+
},
|
|
496
|
+
useThisWhenever: 'you are ready to book the appointment',
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
const bot = bots.Bot({
|
|
502
|
+
youAre: 'a helpful scheduling assistant',
|
|
503
|
+
skill: greetingSkill,
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
// Listen for the greeting phase to complete, then swap to booking
|
|
507
|
+
await greetingSkill.on('did-update-state', async () => {
|
|
508
|
+
if (greetingSkill.getState()?.firstName) {
|
|
509
|
+
bot.setSkill(bookingSkill)
|
|
510
|
+
}
|
|
511
|
+
})
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Pre-populating state before swapping
|
|
515
|
+
|
|
516
|
+
Skills manage their own state independently of the bot. You can create a skill, set its initial state, and then hand it to the bot — useful when you already know some context going into the next phase:
|
|
517
|
+
|
|
518
|
+
```ts
|
|
519
|
+
const bookingSkill = bots.Skill({
|
|
520
|
+
yourJobIfYouChooseToAcceptItIs: 'to book an appointment',
|
|
521
|
+
weAreDoneWhen: 'the appointment is booked',
|
|
522
|
+
stateSchema: buildSchema({
|
|
523
|
+
id: 'booking',
|
|
524
|
+
fields: {
|
|
525
|
+
guestName: { type: 'text', label: 'Guest name' },
|
|
526
|
+
time: { type: 'text', label: 'Appointment time' },
|
|
527
|
+
},
|
|
528
|
+
}),
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
// Pre-load state from the previous phase before the bot ever uses this skill
|
|
532
|
+
await bookingSkill.updateState({
|
|
533
|
+
guestName: greetingSkill.getState()?.firstName,
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
// Now swap — the LLM will already know guestName when it starts
|
|
537
|
+
bot.setSkill(bookingSkill)
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
You can also read and update a skill's state at any point, regardless of whether it's active:
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
// Check what the skill has collected so far
|
|
544
|
+
console.log(bookingSkill.getState())
|
|
545
|
+
// { guestName: 'Taylor', time: undefined }
|
|
546
|
+
|
|
547
|
+
// Inject external data directly (e.g. from your own API)
|
|
548
|
+
await bookingSkill.updateState({ time: '10am' })
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
`updateState` merges — it only overwrites the fields you pass, leaving others intact. And since skills are plain objects, you can maintain references to them alongside the bot and keep them in sync independently.
|
|
552
|
+
|
|
553
|
+
### Key behaviors when swapping skills
|
|
554
|
+
|
|
555
|
+
**`getIsDone()` resets to `false`** — swapping in a new skill always marks the bot as not done, so the conversation continues even if the previous skill had completed:
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
bot.markAsDone()
|
|
559
|
+
console.log(bot.getIsDone()) // true
|
|
560
|
+
|
|
561
|
+
bot.setSkill(bookingSkill)
|
|
562
|
+
console.log(bot.getIsDone()) // false — ready for the next phase
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Message history is preserved** — the full conversation history carries over. Only the active skill (its instructions, state schema, callbacks, and model) changes:
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
// Before swap: greeting skill active
|
|
569
|
+
await bot.sendMessage('Hi there!')
|
|
570
|
+
|
|
571
|
+
// Swap to a new skill mid-conversation
|
|
572
|
+
bot.setSkill(bookingSkill)
|
|
573
|
+
|
|
574
|
+
// After swap: booking skill active, but previous messages still in history
|
|
575
|
+
await bot.sendMessage('I\'d like to book for Tuesday')
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**The new skill's state replaces the old one** — after a swap, `bot.serialize().skill` reflects the new skill's full configuration:
|
|
579
|
+
|
|
580
|
+
```ts
|
|
581
|
+
const skill2 = bots.Skill({
|
|
582
|
+
stateSchema: carSchema,
|
|
583
|
+
yourJobIfYouChooseToAcceptItIs: 'to help the user pick a car',
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
bot.setSkill(skill2)
|
|
587
|
+
|
|
588
|
+
// bot.serialize().skill now reflects skill2's schema and state
|
|
589
|
+
console.log(bot.serialize().skill)
|
|
590
|
+
```
|
|
591
|
+
|
|
470
592
|
## Serialization and Persistence
|
|
471
593
|
|
|
472
594
|
You can snapshot a bot's full state — including message history, skill configuration, and any accumulated state — and later restore it. This is useful for persisting conversations across process restarts, saving/loading sessions, or transferring bot state.
|
|
@@ -100,9 +100,31 @@ export default class ResponseParserV2 {
|
|
|
100
100
|
return `@results ${JSON.stringify(callbackOptions)}\n`;
|
|
101
101
|
}
|
|
102
102
|
getStateUpdateInstructions() {
|
|
103
|
-
return
|
|
103
|
+
return `Updating state works similar to all function calls. Use the following syntax:
|
|
104
|
+
@updateState { "field1": "value1", "field2": "value2" }
|
|
105
|
+
Make sure to json encode only the fields you want to change. You can update state once and do it at the end of any messages you send. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON.
|
|
106
|
+
Good example:
|
|
107
|
+
@updateState { "favoriteColor": "blue", "firstName": "Taylor" }
|
|
108
|
+
Bad examples:
|
|
109
|
+
@updateState
|
|
110
|
+
{ "favoriteColor": "blue" }
|
|
111
|
+
@updateState {
|
|
112
|
+
"favoriteColor": "blue"
|
|
113
|
+
}
|
|
114
|
+
@updateState { favoriteColor: "blue" }`;
|
|
104
115
|
}
|
|
105
116
|
getFunctionCallInstructions() {
|
|
106
|
-
return `A function call is done using the following syntax
|
|
117
|
+
return `A function call is done using the following syntax:
|
|
118
|
+
@callback { "name": "callbackName", "options": {} }
|
|
119
|
+
Make sure to json encode the options and include the name of the callback you want to call. You can call as many callbacks as you want in a single response by including multiple @callback lines. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON. Also, do NOT call something like @myCallback. You would call it like this: @callback { "name": "myCallback", "options": {} }
|
|
120
|
+
Good example:
|
|
121
|
+
@callback { "name": "lookupWeather", "options": { "zip": "80524" } }
|
|
122
|
+
Bad examples:
|
|
123
|
+
@lookupWeather { "zip": "80524" }
|
|
124
|
+
@callback
|
|
125
|
+
{ "name": "lookupWeather", "options": { "zip": "80524" } }
|
|
126
|
+
@callback { "name": "lookupWeather", "options": {
|
|
127
|
+
"zip": "80524"
|
|
128
|
+
} }`;
|
|
107
129
|
}
|
|
108
130
|
}
|
|
@@ -92,10 +92,32 @@ class ResponseParserV2 {
|
|
|
92
92
|
return `@results ${JSON.stringify(callbackOptions)}\n`;
|
|
93
93
|
}
|
|
94
94
|
getStateUpdateInstructions() {
|
|
95
|
-
return
|
|
95
|
+
return `Updating state works similar to all function calls. Use the following syntax:
|
|
96
|
+
@updateState { "field1": "value1", "field2": "value2" }
|
|
97
|
+
Make sure to json encode only the fields you want to change. You can update state once and do it at the end of any messages you send. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON.
|
|
98
|
+
Good example:
|
|
99
|
+
@updateState { "favoriteColor": "blue", "firstName": "Taylor" }
|
|
100
|
+
Bad examples:
|
|
101
|
+
@updateState
|
|
102
|
+
{ "favoriteColor": "blue" }
|
|
103
|
+
@updateState {
|
|
104
|
+
"favoriteColor": "blue"
|
|
105
|
+
}
|
|
106
|
+
@updateState { favoriteColor: "blue" }`;
|
|
96
107
|
}
|
|
97
108
|
getFunctionCallInstructions() {
|
|
98
|
-
return `A function call is done using the following syntax
|
|
109
|
+
return `A function call is done using the following syntax:
|
|
110
|
+
@callback { "name": "callbackName", "options": {} }
|
|
111
|
+
Make sure to json encode the options and include the name of the callback you want to call. You can call as many callbacks as you want in a single response by including multiple @callback lines. IMPORTANT: JSON must be on a single line. Do NOT use multi-line or formatted JSON. Also, do NOT call something like @myCallback. You would call it like this: @callback { "name": "myCallback", "options": {} }
|
|
112
|
+
Good example:
|
|
113
|
+
@callback { "name": "lookupWeather", "options": { "zip": "80524" } }
|
|
114
|
+
Bad examples:
|
|
115
|
+
@lookupWeather { "zip": "80524" }
|
|
116
|
+
@callback
|
|
117
|
+
{ "name": "lookupWeather", "options": { "zip": "80524" } }
|
|
118
|
+
@callback { "name": "lookupWeather", "options": {
|
|
119
|
+
"zip": "80524"
|
|
120
|
+
} }`;
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
exports.default = ResponseParserV2;
|