@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 'Updating state works similar to all function calls. Use the following syntax:\n@updateState { "updates": "here" }\n. 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.';
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:\n@callback { "name": "callbackName", "options": {} }\nMake 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": {} }`;
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 'Updating state works similar to all function calls. Use the following syntax:\n@updateState { "updates": "here" }\n. 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.';
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:\n@callback { "name": "callbackName", "options": {} }\nMake 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": {} }`;
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;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "eta"
9
9
  ]
10
10
  },
11
- "version": "15.1.8",
11
+ "version": "15.1.9",
12
12
  "files": [
13
13
  "build"
14
14
  ],