wingbot 3.74.8 → 3.75.9-alpha.2
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/.claude/settings.local.json +3 -1
- package/index.js +6 -1
- package/jsconfig.json +2 -1
- package/package.json +1 -1
- package/src/BuildRouter.js +43 -7
- package/src/GlobalIntents.js +198 -0
- package/src/LLM.js +19 -3
- package/src/LLMDispatcher.js +508 -0
- package/src/LLMMockProvider.js +44 -1
- package/src/LLMRouter.js +325 -0
- package/src/LLMSession.js +87 -21
- package/src/LLMType.js +13 -13
- package/src/Processor.js +18 -14
- package/src/ReducerWrapper.js +24 -0
- package/src/Request.js +88 -231
- package/src/Responder.js +28 -8
- package/src/Router.js +21 -26
- package/src/Tester.js +43 -2
- package/src/prompt.js +138 -0
- package/src/resolvers/bounce.js +4 -2
- package/src/resolvers/expected.js +4 -3
- package/src/tools/routeToEvents.js +1 -0
- package/src/wingbot/CustomEntityDetectionModel.js +1 -1
package/src/LLMRouter.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author David Menger
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const GlobalIntents = require('./GlobalIntents');
|
|
7
|
+
const LLMType = require('./LLMType');
|
|
8
|
+
|
|
9
|
+
/** @typedef {import('./GlobalIntents').GlobalIntentsMap} GlobalIntentsMap */
|
|
10
|
+
/** @typedef {import('./GlobalIntents').ResolvedGlobalIntentsMap} ResolvedGlobalIntentsMap */
|
|
11
|
+
/** @typedef {import('./GlobalIntents').GlobalIntentResolved} GlobalIntentResolved */
|
|
12
|
+
/** @typedef {import('./GlobalIntents').MatchingResolver} MatchingResolver */
|
|
13
|
+
/** @typedef {import('./GlobalIntents').MatcherResult} MatcherResult */
|
|
14
|
+
/** @typedef {import('./LLMDispatcher').ILLMRouter} ILLMRouter */
|
|
15
|
+
/** @typedef {import('./Request')} Request */
|
|
16
|
+
/** @typedef {import('./LLM')} LLM */
|
|
17
|
+
/** @typedef {import('./prompt').Prompt} Prompt */
|
|
18
|
+
/** @typedef {import('./Router').RoutingInstruction} RoutingInstruction */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {object} NLPActionInfo
|
|
22
|
+
* @prop {MatcherResult} intent
|
|
23
|
+
* @prop {boolean} aboveConfidence
|
|
24
|
+
* @prop {boolean} winner
|
|
25
|
+
* @prop {string} [title]
|
|
26
|
+
* @prop {string|string[]} [match]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} BaseRouteAction
|
|
31
|
+
* @prop {string} action
|
|
32
|
+
* @prop {object} [data]
|
|
33
|
+
* @prop {object} [setState]
|
|
34
|
+
* @prop {object} [_aiKeys]
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} LLMRouting
|
|
39
|
+
* @prop {LLMRoutingCache} globals
|
|
40
|
+
* @prop {ILLMRouter|null} router
|
|
41
|
+
* @prop {Map<string, LLMRoutingCache>} locals
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @typedef {Pick<GlobalIntentResolved, 'action'|'classification'>} LLMAction
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {BaseRouteAction & Partial<NLPActionInfo>} RoutingAction
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @callback RouterResolver
|
|
54
|
+
* @param {LLM} llm
|
|
55
|
+
* @param {Request} req
|
|
56
|
+
* @param {LLMRoutingInput} routing
|
|
57
|
+
*
|
|
58
|
+
* @returns {Promise<RoutingAction|null>}
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {Pick<LLMRoutingCache, 'actionList'|'actionListString'|'byAction'>} LLMRoutingInput
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {object} LLMRoutingCache
|
|
67
|
+
* @prop {boolean} [keepUserInInteractionsWithBounceAllowed]
|
|
68
|
+
* @prop {string} actionListString
|
|
69
|
+
* @prop {LLMAction[]} actionList
|
|
70
|
+
* @prop {ILLMRouter|null} router
|
|
71
|
+
* @prop {Map<string, GlobalIntentResolved>} byAction
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @typedef {object} LLMRouterOptions
|
|
76
|
+
* @prop {boolean} [keepUserInInteractionsWithBounceAllowed]
|
|
77
|
+
* @prop {boolean} [global]
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {Omit<LLMRouterOptions, 'global'>} GILLMExtension
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @class LLMRouter
|
|
86
|
+
*/
|
|
87
|
+
class LLMRouter {
|
|
88
|
+
|
|
89
|
+
// lets give it "reduce" method? - can forward that info to dispatcher
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @param {RouterResolver} routeResolver
|
|
94
|
+
* @param {Omit<LLMRouterOptions, 'global'>} [options]
|
|
95
|
+
*/
|
|
96
|
+
static global (routeResolver, options = {}) {
|
|
97
|
+
return new LLMRouter(routeResolver, {
|
|
98
|
+
...options,
|
|
99
|
+
global: true
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
*
|
|
105
|
+
* @param {RouterResolver} [routeResolver]
|
|
106
|
+
* @param {LLMRouterOptions} [options]
|
|
107
|
+
*/
|
|
108
|
+
constructor (routeResolver = null, options = {}) {
|
|
109
|
+
const { global = false, ...rest } = options;
|
|
110
|
+
this._route = routeResolver;
|
|
111
|
+
this._global = global;
|
|
112
|
+
|
|
113
|
+
/** @typedef {GlobalIntentsMap} */
|
|
114
|
+
this.globalIntents = new Map();
|
|
115
|
+
|
|
116
|
+
if (global) {
|
|
117
|
+
const gi = GlobalIntents.create({
|
|
118
|
+
router: this._route ? this : null,
|
|
119
|
+
...rest
|
|
120
|
+
});
|
|
121
|
+
this.globalIntents.set(gi.id, gi);
|
|
122
|
+
} else if (Object.keys(rest).length) {
|
|
123
|
+
const gi = GlobalIntents.create({
|
|
124
|
+
local: true,
|
|
125
|
+
...rest
|
|
126
|
+
});
|
|
127
|
+
this.globalIntents.set(gi.id, gi);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @type {object}
|
|
133
|
+
*/
|
|
134
|
+
static _cachedStructuredOutput = null;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
* @returns {object}
|
|
139
|
+
*/
|
|
140
|
+
static structuredOutput () {
|
|
141
|
+
if (LLMRouter._cachedStructuredOutput === null) {
|
|
142
|
+
LLMRouter._cachedStructuredOutput = LLMType
|
|
143
|
+
.object({
|
|
144
|
+
action: LLMType.string()
|
|
145
|
+
.description('recommended action')
|
|
146
|
+
}, 'conversation routing information')
|
|
147
|
+
.toJSON();
|
|
148
|
+
}
|
|
149
|
+
return LLMRouter._cachedStructuredOutput;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
*
|
|
154
|
+
* prompt wi
|
|
155
|
+
*
|
|
156
|
+
* @param {Prompt} prompt
|
|
157
|
+
* @returns {RouterResolver}
|
|
158
|
+
*/
|
|
159
|
+
static defaultRoute (prompt) {
|
|
160
|
+
/** @type {RouterResolver} */
|
|
161
|
+
const route = async (llm, req, routing) => {
|
|
162
|
+
const res = await llm.session('routing')
|
|
163
|
+
.setData(routing)
|
|
164
|
+
.systemPrompt(prompt)
|
|
165
|
+
.generateStructured(LLMRouter.structuredOutput());
|
|
166
|
+
|
|
167
|
+
if (!routing.byAction.has(LLMRouter._normByAction(res.action))) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return res;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return route;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
*
|
|
179
|
+
* @returns {RoutingInstruction}
|
|
180
|
+
*/
|
|
181
|
+
reduce () {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
on () {
|
|
186
|
+
// acually does nothing too
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param {LLM} llm
|
|
192
|
+
* @param {Request} req
|
|
193
|
+
* @param {LLMRoutingInput} routing
|
|
194
|
+
* @returns {Promise<RoutingAction|null>}
|
|
195
|
+
*/
|
|
196
|
+
async resolve (llm, req, routing) { // eslint-disable-line no-unused-vars
|
|
197
|
+
if (!this._route) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return this._route(llm, req, routing);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
*
|
|
205
|
+
* @param {string} path
|
|
206
|
+
* @param {string} classification
|
|
207
|
+
* @returns {MatchingResolver}
|
|
208
|
+
*/
|
|
209
|
+
route (path, classification) {
|
|
210
|
+
return GlobalIntents.resolver(path, {
|
|
211
|
+
classification,
|
|
212
|
+
router: this._route ? this : null,
|
|
213
|
+
local: !this._global
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
*
|
|
219
|
+
* @param {string} path
|
|
220
|
+
* @param {string} classification
|
|
221
|
+
* @returns {MatchingResolver}
|
|
222
|
+
*/
|
|
223
|
+
static route (path, classification) {
|
|
224
|
+
return GlobalIntents.resolver(path, {
|
|
225
|
+
classification
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static _normByAction (act) {
|
|
230
|
+
return act.replace(/^\/|\/$/g, '');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
*
|
|
235
|
+
* @param {GlobalIntentResolved[]} resolvedLlm
|
|
236
|
+
* @returns {LLMRoutingCache}
|
|
237
|
+
*/
|
|
238
|
+
static _prepareRoutingInput (resolvedLlm) {
|
|
239
|
+
const byAction = new Map();
|
|
240
|
+
let router = null;
|
|
241
|
+
let keepUserInInteractionsWithBounceAllowed;
|
|
242
|
+
|
|
243
|
+
const actionList = resolvedLlm.map((gi) => {
|
|
244
|
+
const { action, classification } = gi;
|
|
245
|
+
byAction.set(LLMRouter._normByAction(gi.action), gi);
|
|
246
|
+
|
|
247
|
+
if (typeof gi.keepUserInInteractionsWithBounceAllowed === 'boolean') {
|
|
248
|
+
keepUserInInteractionsWithBounceAllowed = true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (router && router !== gi.router) {
|
|
252
|
+
throw new Error(`Multiple local routers on "${gi.localPath}"`);
|
|
253
|
+
} else {
|
|
254
|
+
router = gi.router;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { action, classification };
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const actionListString = actionList
|
|
261
|
+
.map((gi) => `- \`${gi.action}\`: ${gi.classification}`)
|
|
262
|
+
.join('\n');
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
...(typeof keepUserInInteractionsWithBounceAllowed === 'boolean'
|
|
266
|
+
? { keepUserInInteractionsWithBounceAllowed }
|
|
267
|
+
: {}),
|
|
268
|
+
actionListString,
|
|
269
|
+
actionList,
|
|
270
|
+
byAction,
|
|
271
|
+
router
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
*
|
|
277
|
+
* @param {ResolvedGlobalIntentsMap} globalIntents
|
|
278
|
+
* @returns {LLMRouting}
|
|
279
|
+
*/
|
|
280
|
+
static prepareRouting (globalIntents) {
|
|
281
|
+
/** @type {Map<string, GlobalIntentResolved[]>} */
|
|
282
|
+
const locals = new Map();
|
|
283
|
+
/** @type {GlobalIntentResolved[]} */
|
|
284
|
+
const globals = [];
|
|
285
|
+
/** @type {ILLMRouter} */
|
|
286
|
+
let router = null;
|
|
287
|
+
|
|
288
|
+
for (const gi of globalIntents.values()) {
|
|
289
|
+
if (!gi.classification
|
|
290
|
+
&& !gi.router
|
|
291
|
+
&& typeof gi.keepUserInInteractionsWithBounceAllowed === 'undefined') continue;
|
|
292
|
+
|
|
293
|
+
if (gi.local) {
|
|
294
|
+
let local = locals.get(gi.localPath);
|
|
295
|
+
if (!local) {
|
|
296
|
+
local = [];
|
|
297
|
+
locals.set(gi.localPath, local);
|
|
298
|
+
}
|
|
299
|
+
local.push(gi);
|
|
300
|
+
} else if (gi.classification) {
|
|
301
|
+
globals.push(gi);
|
|
302
|
+
} else if (gi.router) { // public default router without classification
|
|
303
|
+
if (router !== null) {
|
|
304
|
+
throw new Error(`Detected more than one global router on '${gi.localPath}'. Only single global router allowed.`);
|
|
305
|
+
}
|
|
306
|
+
router = gi.router;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
router,
|
|
312
|
+
locals: new Map(
|
|
313
|
+
Array.from(locals)
|
|
314
|
+
.map(([localPath, locs]) => [
|
|
315
|
+
localPath,
|
|
316
|
+
LLMRouter._prepareRoutingInput(locs)
|
|
317
|
+
])
|
|
318
|
+
),
|
|
319
|
+
globals: LLMRouter._prepareRoutingInput(globals)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
module.exports = LLMRouter;
|
package/src/LLMSession.js
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
const {
|
|
7
7
|
FILTER_SCOPE_CONVERSATION, ROLE_ASSISTANT, ROLE_USER, ROLE_SYSTEM
|
|
8
8
|
} = require('./LLMConsts');
|
|
9
|
+
const stateData = require('./utils/stateData');
|
|
9
10
|
|
|
10
11
|
/** @typedef {import('./Responder').QuickReply} QuickReply */
|
|
11
12
|
/** @typedef {import('./LLM').LLMCallPreset} LLMCallPreset */
|
|
12
13
|
/** @typedef {import('./LLM')} LLM */
|
|
13
14
|
/** @typedef {import('./LLM').LLMLogOptions} LLMLogOptions */
|
|
14
15
|
|
|
16
|
+
/** @typedef {import('./Request')} Request */
|
|
17
|
+
/** @typedef {import('./Responder')} Responder */
|
|
18
|
+
|
|
15
19
|
/** @typedef {'user'|'assistant'} LLMChatRole */
|
|
16
20
|
/** @typedef {'system'} LLMSystemRole */
|
|
17
21
|
/** @typedef {'tool'} LLMToolRole */
|
|
@@ -29,7 +33,9 @@ const {
|
|
|
29
33
|
* @prop {string} args - JSON string
|
|
30
34
|
*/
|
|
31
35
|
|
|
32
|
-
/** @typedef {
|
|
36
|
+
/** @typedef {import('./prompt').Renderer} Renderer */
|
|
37
|
+
|
|
38
|
+
/** @typedef {string|Promise<string>|Renderer} PossiblyAsyncContent */
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* @template {LLMRole} [R=LLMRole]
|
|
@@ -112,7 +118,6 @@ const {
|
|
|
112
118
|
* @prop {string} name - The function name
|
|
113
119
|
* @prop {string} [description] - What the function does
|
|
114
120
|
* @prop {FnParamsObject} [parameters] - Parameter schema
|
|
115
|
-
* @prop {boolean} [strict]
|
|
116
121
|
*/
|
|
117
122
|
|
|
118
123
|
/**
|
|
@@ -155,6 +160,16 @@ const {
|
|
|
155
160
|
* - toJson
|
|
156
161
|
*/
|
|
157
162
|
|
|
163
|
+
/**
|
|
164
|
+
* @typedef {object} SessionOptions
|
|
165
|
+
* @prop {LLMFilter[]} [filters]
|
|
166
|
+
* @prop {SendCallback} [onSend]
|
|
167
|
+
* @prop {Request} [req]
|
|
168
|
+
* @prop {Responder} [res]
|
|
169
|
+
* @prop {object} [data]
|
|
170
|
+
* @prop {LLMCallPreset} [preset]
|
|
171
|
+
*/
|
|
172
|
+
|
|
158
173
|
/**
|
|
159
174
|
* @class LLMSession
|
|
160
175
|
* @implements {PromiseLike<LLMMessage<any>>}
|
|
@@ -165,10 +180,18 @@ class LLMSession {
|
|
|
165
180
|
*
|
|
166
181
|
* @param {LLM} llm
|
|
167
182
|
* @param {(PossiblyAsyncLLMMessage|AsyncLLMMessage)[]} [chat]
|
|
168
|
-
* @param {
|
|
169
|
-
* @param {LLMFilter[]} [filters=[]]
|
|
183
|
+
* @param {SessionOptions} [options]
|
|
170
184
|
*/
|
|
171
|
-
constructor (llm, chat = [],
|
|
185
|
+
constructor (llm, chat = [], options = {}) {
|
|
186
|
+
const {
|
|
187
|
+
onSend = null,
|
|
188
|
+
filters = [],
|
|
189
|
+
data = {},
|
|
190
|
+
req = null,
|
|
191
|
+
res = null,
|
|
192
|
+
preset = undefined
|
|
193
|
+
} = options;
|
|
194
|
+
|
|
172
195
|
this._llm = llm;
|
|
173
196
|
|
|
174
197
|
this._onSend = onSend;
|
|
@@ -194,6 +217,36 @@ class LLMSession {
|
|
|
194
217
|
|
|
195
218
|
/** @type {Map<string,ObjectTool>} */
|
|
196
219
|
this._tools = new Map();
|
|
220
|
+
|
|
221
|
+
this._data = { ...data };
|
|
222
|
+
|
|
223
|
+
this._req = req || llm?.req;
|
|
224
|
+
this._res = res || llm?.res;
|
|
225
|
+
|
|
226
|
+
this._preset = preset;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
*
|
|
231
|
+
* @param {object} data
|
|
232
|
+
* @returns {this}
|
|
233
|
+
*/
|
|
234
|
+
setData (data) {
|
|
235
|
+
this._job(() => {
|
|
236
|
+
Object.assign(this._data, data);
|
|
237
|
+
}, true);
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
_resolveData () {
|
|
242
|
+
const dataFromReqRes = this._req || this._res
|
|
243
|
+
? stateData(this._req, this._res)
|
|
244
|
+
: {};
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
...dataFromReqRes,
|
|
248
|
+
...this._data
|
|
249
|
+
};
|
|
197
250
|
}
|
|
198
251
|
|
|
199
252
|
_job (task, runNowAndSyncWhenQueueIsEmpty = false) {
|
|
@@ -297,19 +350,15 @@ class LLMSession {
|
|
|
297
350
|
.map(({
|
|
298
351
|
name,
|
|
299
352
|
description = null,
|
|
300
|
-
parameters = {}
|
|
301
|
-
strict = true
|
|
353
|
+
parameters = {}
|
|
302
354
|
}) => ({
|
|
303
355
|
name,
|
|
304
356
|
...(description && { description }),
|
|
305
|
-
|
|
357
|
+
strict: true,
|
|
306
358
|
parameters: {
|
|
307
359
|
type: 'object',
|
|
308
360
|
properties: {},
|
|
309
361
|
additionalProperties: false,
|
|
310
|
-
required: 'properties' in parameters
|
|
311
|
-
? Object.keys(parameters.properties)
|
|
312
|
-
: [],
|
|
313
362
|
...parameters
|
|
314
363
|
}
|
|
315
364
|
}));
|
|
@@ -447,11 +496,15 @@ class LLMSession {
|
|
|
447
496
|
return;
|
|
448
497
|
}
|
|
449
498
|
if ('content' in m
|
|
499
|
+
&& typeof m.content !== 'function'
|
|
450
500
|
&& (m.content instanceof Promise
|
|
451
501
|
|| (typeof m.content !== 'string' && m.content && 'then' in m.content))) {
|
|
452
502
|
return;
|
|
453
503
|
}
|
|
454
|
-
if (filtered
|
|
504
|
+
if (filtered
|
|
505
|
+
&& this._filters.length >= 0
|
|
506
|
+
&& 'content' in m
|
|
507
|
+
&& (typeof m.content === 'string' || typeof m.content === 'function')) {
|
|
455
508
|
const content = this._filters.reduce((text, filter) => {
|
|
456
509
|
if (!text) {
|
|
457
510
|
return text;
|
|
@@ -463,7 +516,7 @@ class LLMSession {
|
|
|
463
516
|
}
|
|
464
517
|
const res = filter.filter(text, m.role);
|
|
465
518
|
return res === true ? text : res;
|
|
466
|
-
}, m.content);
|
|
519
|
+
}, typeof m.content === 'function' ? m.content.toString() : m.content);
|
|
467
520
|
|
|
468
521
|
if (typeof content === 'string') {
|
|
469
522
|
// @ts-ignore
|
|
@@ -472,6 +525,11 @@ class LLMSession {
|
|
|
472
525
|
content
|
|
473
526
|
});
|
|
474
527
|
}
|
|
528
|
+
} else if ('content' in m && typeof m.content === 'function') {
|
|
529
|
+
ret.push({
|
|
530
|
+
...m,
|
|
531
|
+
content: m.content.toString()
|
|
532
|
+
});
|
|
475
533
|
} else {
|
|
476
534
|
// @ts-ignore
|
|
477
535
|
ret.push(m);
|
|
@@ -520,6 +578,9 @@ class LLMSession {
|
|
|
520
578
|
if (typeof m.content === 'string') {
|
|
521
579
|
return m.content;
|
|
522
580
|
}
|
|
581
|
+
if (typeof m.content === 'function') {
|
|
582
|
+
return m.content.toString();
|
|
583
|
+
}
|
|
523
584
|
return '<Promise>';
|
|
524
585
|
}
|
|
525
586
|
|
|
@@ -627,12 +688,17 @@ class LLMSession {
|
|
|
627
688
|
})();
|
|
628
689
|
return wrapped;
|
|
629
690
|
}
|
|
630
|
-
if (!('content' in msg) || !this._contentIsPromise(msg.content)) {
|
|
691
|
+
if (!('content' in msg) || (!this._contentIsPromise(msg.content) && typeof msg.content !== 'function')) {
|
|
631
692
|
return msg;
|
|
632
693
|
}
|
|
694
|
+
|
|
695
|
+
const contentPromise = typeof msg.content === 'function'
|
|
696
|
+
? msg.content(this._resolveData())
|
|
697
|
+
: msg.content;
|
|
698
|
+
|
|
633
699
|
const ret = {
|
|
634
700
|
...msg,
|
|
635
|
-
content: Promise.resolve(
|
|
701
|
+
content: Promise.resolve(contentPromise)
|
|
636
702
|
.then((r) => {
|
|
637
703
|
// @ts-ignore
|
|
638
704
|
ret.content = r;
|
|
@@ -688,7 +754,7 @@ class LLMSession {
|
|
|
688
754
|
|
|
689
755
|
/**
|
|
690
756
|
*
|
|
691
|
-
* @param {string|Promise<string
|
|
757
|
+
* @param {string|Promise<string>|Renderer} content
|
|
692
758
|
* @returns {this}
|
|
693
759
|
*/
|
|
694
760
|
user (content) {
|
|
@@ -701,7 +767,7 @@ class LLMSession {
|
|
|
701
767
|
|
|
702
768
|
/**
|
|
703
769
|
*
|
|
704
|
-
* @param {string|Promise<string
|
|
770
|
+
* @param {string|Promise<string>|Renderer} content
|
|
705
771
|
* @returns {this}
|
|
706
772
|
*/
|
|
707
773
|
assistant (content) {
|
|
@@ -714,7 +780,7 @@ class LLMSession {
|
|
|
714
780
|
|
|
715
781
|
/**
|
|
716
782
|
*
|
|
717
|
-
* @param {string|Promise<string
|
|
783
|
+
* @param {string|Promise<string>|Renderer} content
|
|
718
784
|
* @returns {this}
|
|
719
785
|
*/
|
|
720
786
|
systemPrompt (content) {
|
|
@@ -747,7 +813,7 @@ class LLMSession {
|
|
|
747
813
|
* @param {LLMLogOptions} [logOptions]
|
|
748
814
|
* @returns {this}
|
|
749
815
|
*/
|
|
750
|
-
generate (providerOptions =
|
|
816
|
+
generate (providerOptions = this._preset, logOptions = {}) {
|
|
751
817
|
this._job(() => this._generate(providerOptions, logOptions));
|
|
752
818
|
return this;
|
|
753
819
|
}
|
|
@@ -759,7 +825,7 @@ class LLMSession {
|
|
|
759
825
|
* @param {LLMLogOptions} [logOptions]
|
|
760
826
|
* @returns {this}
|
|
761
827
|
*/
|
|
762
|
-
generateStructured (output, providerOptions =
|
|
828
|
+
generateStructured (output, providerOptions = this._preset, logOptions = {}) {
|
|
763
829
|
|
|
764
830
|
const responseFormat = 'toJSON' in output && typeof output.toJSON === 'function'
|
|
765
831
|
? output.toJSON()
|
|
@@ -787,7 +853,7 @@ class LLMSession {
|
|
|
787
853
|
* @param {LLMLogOptions} [logOptions]
|
|
788
854
|
* @returns {Promise<LLMMessage<any>>}
|
|
789
855
|
*/
|
|
790
|
-
async _generate (providerOptions =
|
|
856
|
+
async _generate (providerOptions = this._preset, logOptions = {}) {
|
|
791
857
|
let result = await this._llm.generate(this, providerOptions, logOptions);
|
|
792
858
|
|
|
793
859
|
if (result.toolCalls?.length) {
|
package/src/LLMType.js
CHANGED
|
@@ -196,10 +196,11 @@ class LLMType {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* Marks field as optional when used as an object property.
|
|
199
|
+
* Marks field as optional when used as an object property. In the
|
|
200
|
+
* generated schema this surfaces as a nullable type (e.g. `['string', 'null']`)
|
|
201
|
+
* — strict mode still requires the key to be present, just allows `null`.
|
|
200
202
|
*
|
|
201
|
-
*
|
|
202
|
-
* because requiredness is evaluated by parent object schemas.
|
|
203
|
+
* Outside an object-property context it has no practical effect.
|
|
203
204
|
*
|
|
204
205
|
* @example
|
|
205
206
|
* const input = LLMType.object({
|
|
@@ -215,10 +216,9 @@ class LLMType {
|
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
/**
|
|
218
|
-
* Returns JSON Schema representation
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* `toJSON()` on each nested `LLMType` instance.
|
|
219
|
+
* Returns JSON Schema representation in OpenAI strict-mode form:
|
|
220
|
+
* every property is listed in `required`, and properties marked
|
|
221
|
+
* `optional()` are made nullable instead of being dropped from `required`.
|
|
222
222
|
*
|
|
223
223
|
* @example
|
|
224
224
|
* const schema = LLMType.object({
|
|
@@ -246,18 +246,18 @@ class LLMType {
|
|
|
246
246
|
const properties = {};
|
|
247
247
|
const required = [];
|
|
248
248
|
Object.entries(this._properties).forEach(([key, child]) => {
|
|
249
|
-
|
|
250
|
-
if (child._required) {
|
|
251
|
-
|
|
249
|
+
const childSchema = child.toJSON();
|
|
250
|
+
if (!child._required) {
|
|
251
|
+
childSchema.type = [childSchema.type, 'null'];
|
|
252
252
|
}
|
|
253
|
+
properties[key] = childSchema;
|
|
254
|
+
required.push(key);
|
|
253
255
|
});
|
|
254
256
|
|
|
255
257
|
schema.type = 'object';
|
|
256
258
|
schema.properties = properties;
|
|
257
259
|
schema.additionalProperties = false;
|
|
258
|
-
|
|
259
|
-
schema.required = required;
|
|
260
|
-
}
|
|
260
|
+
schema.required = required;
|
|
261
261
|
break;
|
|
262
262
|
}
|
|
263
263
|
|