@agentica/core 0.45.0-dev.20260426 → 0.45.1
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/lib/index.mjs +136 -40
- package/lib/index.mjs.map +1 -1
- package/lib/orchestrate/cancel.js +102 -12
- package/lib/orchestrate/cancel.js.map +1 -1
- package/lib/orchestrate/select.js +102 -12
- package/lib/orchestrate/select.js.map +1 -1
- package/lib/utils/ChatGptCompletionMessageUtil.d.ts +12 -0
- package/lib/utils/ChatGptCompletionMessageUtil.js +20 -6
- package/lib/utils/ChatGptCompletionMessageUtil.js.map +1 -1
- package/lib/utils/ChatGptCompletionStreamingUtil.js +6 -2
- package/lib/utils/ChatGptCompletionStreamingUtil.js.map +1 -1
- package/package.json +3 -3
- package/src/orchestrate/cancel.ts +152 -38
- package/src/orchestrate/select.ts +136 -20
- package/src/utils/ChatGptCompletionMessageUtil.ts +19 -5
- package/src/utils/ChatGptCompletionStreamingUtil.ts +6 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { IJsonParseResult } from "@typia/interface";
|
|
1
2
|
import type OpenAI from "openai";
|
|
2
3
|
import type { ILlmFunction, IValidation } from "typia";
|
|
3
4
|
|
|
5
|
+
import { dedent, LlmJson } from "@typia/utils";
|
|
4
6
|
import typia from "typia";
|
|
5
7
|
|
|
6
8
|
import type { AgenticaContext } from "../context/AgenticaContext";
|
|
@@ -25,11 +27,19 @@ const FUNCTION: ILlmFunction = typia.llm.application<
|
|
|
25
27
|
__IChatCancelFunctionsApplication
|
|
26
28
|
>().functions[0]!;
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
type IFailure
|
|
31
|
+
= | {
|
|
32
|
+
kind: "parse";
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
failure: IJsonParseResult.IFailure;
|
|
36
|
+
}
|
|
37
|
+
| {
|
|
38
|
+
kind: "validation";
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
validation: IValidation.IFailure;
|
|
42
|
+
};
|
|
33
43
|
|
|
34
44
|
export async function cancel(
|
|
35
45
|
ctx: AgenticaContext,
|
|
@@ -185,15 +195,56 @@ async function step(
|
|
|
185
195
|
continue;
|
|
186
196
|
}
|
|
187
197
|
|
|
188
|
-
|
|
198
|
+
// LENIENT JSON PARSING
|
|
199
|
+
//
|
|
200
|
+
// A malformed JSON string is reported back to the LLM as a parse
|
|
201
|
+
// failure, mirroring `call.ts`. On success the parsed `.data` (never
|
|
202
|
+
// the `IJsonParseResult` wrapper itself) is forwarded to validation.
|
|
203
|
+
const parsed: IJsonParseResult<unknown> = FUNCTION.parse(
|
|
204
|
+
tc.function.arguments,
|
|
205
|
+
);
|
|
206
|
+
if (parsed.success === false) {
|
|
207
|
+
failures.push({
|
|
208
|
+
kind: "parse",
|
|
209
|
+
id: tc.id,
|
|
210
|
+
name: tc.function.name,
|
|
211
|
+
failure: parsed,
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
189
215
|
const validation: IValidation<__IChatFunctionReference.IProps>
|
|
190
|
-
= FUNCTION.validate(
|
|
216
|
+
= FUNCTION.validate(parsed.data) as IValidation<__IChatFunctionReference.IProps>;
|
|
191
217
|
if (validation.success === false) {
|
|
192
218
|
failures.push({
|
|
219
|
+
kind: "validation",
|
|
193
220
|
id: tc.id,
|
|
194
221
|
name: tc.function.name,
|
|
195
222
|
validation,
|
|
196
223
|
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// FUNCTION EXISTENCE
|
|
228
|
+
//
|
|
229
|
+
// `typia` only proves that `name` is a `string`; it cannot know which
|
|
230
|
+
// functions are currently selected. A name that is not stacked would
|
|
231
|
+
// otherwise be silently dropped by `cancelFunctionFromContext`, so
|
|
232
|
+
// report it back to the LLM as an `IValidation.IFailure`.
|
|
233
|
+
const referenceErrors: IValidation.IError[] = validateFunctionExistence(
|
|
234
|
+
ctx,
|
|
235
|
+
validation.data,
|
|
236
|
+
);
|
|
237
|
+
if (referenceErrors.length > 0) {
|
|
238
|
+
failures.push({
|
|
239
|
+
kind: "validation",
|
|
240
|
+
id: tc.id,
|
|
241
|
+
name: tc.function.name,
|
|
242
|
+
validation: {
|
|
243
|
+
success: false,
|
|
244
|
+
data: validation.data,
|
|
245
|
+
errors: referenceErrors,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
197
248
|
}
|
|
198
249
|
}
|
|
199
250
|
}
|
|
@@ -217,15 +268,18 @@ async function step(
|
|
|
217
268
|
continue;
|
|
218
269
|
}
|
|
219
270
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
271
|
+
// Reuse the lenient parser + validator so that arguments accepted
|
|
272
|
+
// by the VALIDATION retry above are processed consistently here.
|
|
273
|
+
const parsed: IJsonParseResult<unknown> = FUNCTION.parse(
|
|
274
|
+
tc.function.arguments,
|
|
275
|
+
);
|
|
276
|
+
const validation: IValidation<__IChatFunctionReference.IProps>
|
|
277
|
+
= FUNCTION.validate(parsed.data) as IValidation<__IChatFunctionReference.IProps>;
|
|
278
|
+
if (validation.success === false) {
|
|
225
279
|
continue;
|
|
226
280
|
}
|
|
227
281
|
|
|
228
|
-
for (const reference of
|
|
282
|
+
for (const reference of validation.data.functions) {
|
|
229
283
|
cancelFunctionFromContext(
|
|
230
284
|
ctx,
|
|
231
285
|
reference,
|
|
@@ -240,32 +294,92 @@ async function step(
|
|
|
240
294
|
function emendMessages(failures: IFailure[]): OpenAI.ChatCompletionMessageParam[] {
|
|
241
295
|
return failures
|
|
242
296
|
.map(f => [
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
297
|
+
{
|
|
298
|
+
role: "assistant",
|
|
299
|
+
tool_calls: [
|
|
300
|
+
{
|
|
301
|
+
type: "function",
|
|
302
|
+
id: f.id,
|
|
303
|
+
function: {
|
|
304
|
+
name: f.name,
|
|
305
|
+
arguments: f.kind === "parse"
|
|
306
|
+
? f.failure.input
|
|
307
|
+
: JSON.stringify(f.validation.data),
|
|
253
308
|
},
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
} satisfies OpenAI.ChatCompletionAssistantMessageParam,
|
|
312
|
+
{
|
|
313
|
+
role: "tool",
|
|
314
|
+
tool_call_id: f.id,
|
|
315
|
+
content: f.kind === "parse"
|
|
316
|
+
? dedent`
|
|
317
|
+
Invalid JSON format.
|
|
318
|
+
|
|
319
|
+
Here is the detailed parsing failure information,
|
|
320
|
+
including error messages and their locations within the input:
|
|
321
|
+
|
|
322
|
+
\`\`\`json
|
|
323
|
+
${JSON.stringify(f.failure.errors)}
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
And here is the partially parsed data that was successfully
|
|
327
|
+
extracted before the error occurred:
|
|
328
|
+
|
|
329
|
+
\`\`\`json
|
|
330
|
+
${JSON.stringify(f.failure.data)}
|
|
331
|
+
\`\`\`
|
|
332
|
+
`
|
|
333
|
+
: [
|
|
334
|
+
"🚨 VALIDATION FAILURE: Your function arguments do not conform to the required schema.",
|
|
335
|
+
"",
|
|
336
|
+
"Each error below is computed absolute truth from rigorous type validation.",
|
|
337
|
+
"You must fix ALL errors to achieve 100% schema compliance.",
|
|
338
|
+
"",
|
|
339
|
+
LlmJson.stringify(f.validation),
|
|
340
|
+
].join("\n"),
|
|
341
|
+
} satisfies OpenAI.ChatCompletionToolMessageParam,
|
|
342
|
+
{
|
|
343
|
+
role: "system",
|
|
344
|
+
content: f.kind === "parse"
|
|
345
|
+
? AgenticaSystemPrompt.JSON_PARSE_ERROR.replace(
|
|
346
|
+
"${{FAILURE}}",
|
|
347
|
+
JSON.stringify(f.failure),
|
|
348
|
+
)
|
|
349
|
+
: AgenticaSystemPrompt.VALIDATE,
|
|
350
|
+
} satisfies OpenAI.ChatCompletionSystemMessageParam,
|
|
269
351
|
])
|
|
270
352
|
.flat();
|
|
271
353
|
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Validate that every function to cancel is actually selected right now.
|
|
357
|
+
*
|
|
358
|
+
* `typia` validation only proves that `__IChatFunctionReference.name` is a
|
|
359
|
+
* `string`; it cannot know which functions are currently stacked. Without this
|
|
360
|
+
* check a name that is not selected would be silently dropped by
|
|
361
|
+
* `cancelFunctionFromContext`. The returned errors are fed back to the LLM
|
|
362
|
+
* through `emendMessages`, exactly like a type validation error - with the
|
|
363
|
+
* list of cancellable function names in `expected`.
|
|
364
|
+
*/
|
|
365
|
+
function validateFunctionExistence(
|
|
366
|
+
ctx: AgenticaContext,
|
|
367
|
+
data: __IChatFunctionReference.IProps,
|
|
368
|
+
): IValidation.IError[] {
|
|
369
|
+
const cancellable: string[] = ctx.stack.map(s => s.operation.name);
|
|
370
|
+
const expected: string = cancellable.length === 0
|
|
371
|
+
? "never"
|
|
372
|
+
: cancellable.map(name => JSON.stringify(name)).join(" | ");
|
|
373
|
+
return data.functions.flatMap((reference, i): IValidation.IError[] =>
|
|
374
|
+
cancellable.includes(reference.name)
|
|
375
|
+
? []
|
|
376
|
+
: [{
|
|
377
|
+
path: `$input.functions[${i}].name`,
|
|
378
|
+
expected,
|
|
379
|
+
value: reference.name,
|
|
380
|
+
description: cancellable.length === 0
|
|
381
|
+
? `Function "${reference.name}" cannot be cancelled because no function is currently selected.`
|
|
382
|
+
: `Function "${reference.name}" is not in the current selection, so it cannot be cancelled.`,
|
|
383
|
+
}],
|
|
384
|
+
);
|
|
385
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { IJsonParseResult } from "@typia/interface";
|
|
1
2
|
import type OpenAI from "openai";
|
|
2
3
|
import type { ILlmFunction, IValidation } from "typia";
|
|
3
4
|
|
|
5
|
+
import { dedent, LlmJson } from "@typia/utils";
|
|
4
6
|
import typia from "typia";
|
|
5
7
|
|
|
6
8
|
import type { AgenticaContext } from "../context/AgenticaContext";
|
|
@@ -28,11 +30,19 @@ const FUNCTION: ILlmFunction = typia.llm.application<
|
|
|
28
30
|
__IChatSelectFunctionsApplication
|
|
29
31
|
>().functions[0]!;
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
type IFailure
|
|
34
|
+
= | {
|
|
35
|
+
kind: "parse";
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
failure: IJsonParseResult.IFailure;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
kind: "validation";
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
validation: IValidation.IFailure;
|
|
45
|
+
};
|
|
36
46
|
|
|
37
47
|
export async function select(
|
|
38
48
|
ctx: AgenticaContext,
|
|
@@ -241,15 +251,57 @@ async function step(
|
|
|
241
251
|
if (tc.type !== "function" || tc.function.name !== "selectFunctions") {
|
|
242
252
|
continue;
|
|
243
253
|
}
|
|
244
|
-
|
|
254
|
+
// LENIENT JSON PARSING
|
|
255
|
+
//
|
|
256
|
+
// A malformed JSON string is reported back to the LLM as a parse
|
|
257
|
+
// failure, mirroring `call.ts`. On success the parsed `.data` (never
|
|
258
|
+
// the `IJsonParseResult` wrapper itself) is forwarded to validation.
|
|
259
|
+
const parsed: IJsonParseResult<unknown> = FUNCTION.parse(
|
|
260
|
+
tc.function.arguments,
|
|
261
|
+
);
|
|
262
|
+
if (parsed.success === false) {
|
|
263
|
+
failures.push({
|
|
264
|
+
kind: "parse",
|
|
265
|
+
id: tc.id,
|
|
266
|
+
name: tc.function.name,
|
|
267
|
+
failure: parsed,
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
245
271
|
const validation: IValidation<__IChatFunctionReference.IProps>
|
|
246
|
-
= FUNCTION.validate(
|
|
272
|
+
= FUNCTION.validate(parsed.data) as IValidation<__IChatFunctionReference.IProps>;
|
|
247
273
|
if (validation.success === false) {
|
|
248
274
|
failures.push({
|
|
275
|
+
kind: "validation",
|
|
249
276
|
id: tc.id,
|
|
250
277
|
name: tc.function.name,
|
|
251
278
|
validation,
|
|
252
279
|
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// FUNCTION EXISTENCE
|
|
284
|
+
//
|
|
285
|
+
// `typia` only proves that `name` is a `string`; it cannot know which
|
|
286
|
+
// functions exist at runtime. A hallucinated name would otherwise be
|
|
287
|
+
// silently dropped by `selectFunctionFromContext`, so report it back
|
|
288
|
+
// to the LLM as an `IValidation.IFailure`, just like a type error.
|
|
289
|
+
const referenceErrors: IValidation.IError[] = validateFunctionExistence(
|
|
290
|
+
ctx,
|
|
291
|
+
operations,
|
|
292
|
+
validation.data,
|
|
293
|
+
);
|
|
294
|
+
if (referenceErrors.length > 0) {
|
|
295
|
+
failures.push({
|
|
296
|
+
kind: "validation",
|
|
297
|
+
id: tc.id,
|
|
298
|
+
name: tc.function.name,
|
|
299
|
+
validation: {
|
|
300
|
+
success: false,
|
|
301
|
+
data: validation.data,
|
|
302
|
+
errors: referenceErrors,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
253
305
|
}
|
|
254
306
|
}
|
|
255
307
|
}
|
|
@@ -273,14 +325,17 @@ async function step(
|
|
|
273
325
|
continue;
|
|
274
326
|
}
|
|
275
327
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
328
|
+
// Reuse the lenient parser + validator so that arguments accepted
|
|
329
|
+
// by the VALIDATION retry above are processed consistently here.
|
|
330
|
+
const parsed: IJsonParseResult<unknown> = FUNCTION.parse(
|
|
331
|
+
tc.function.arguments,
|
|
332
|
+
);
|
|
333
|
+
const validation: IValidation<__IChatFunctionReference.IProps>
|
|
334
|
+
= FUNCTION.validate(parsed.data) as IValidation<__IChatFunctionReference.IProps>;
|
|
335
|
+
if (validation.success === false) {
|
|
281
336
|
continue;
|
|
282
337
|
}
|
|
283
|
-
for (const reference of
|
|
338
|
+
for (const reference of validation.data.functions) {
|
|
284
339
|
selectFunctionFromContext(
|
|
285
340
|
ctx,
|
|
286
341
|
reference,
|
|
@@ -303,24 +358,85 @@ function emendMessages(failures: IFailure[]): OpenAI.ChatCompletionMessageParam[
|
|
|
303
358
|
id: f.id,
|
|
304
359
|
function: {
|
|
305
360
|
name: f.name,
|
|
306
|
-
arguments:
|
|
361
|
+
arguments: f.kind === "parse"
|
|
362
|
+
? f.failure.input
|
|
363
|
+
: JSON.stringify(f.validation.data),
|
|
307
364
|
},
|
|
308
365
|
},
|
|
309
366
|
],
|
|
310
367
|
} satisfies OpenAI.ChatCompletionAssistantMessageParam,
|
|
311
368
|
{
|
|
312
369
|
role: "tool",
|
|
313
|
-
content: JSON.stringify(f.validation.errors),
|
|
314
370
|
tool_call_id: f.id,
|
|
371
|
+
content: f.kind === "parse"
|
|
372
|
+
? dedent`
|
|
373
|
+
Invalid JSON format.
|
|
374
|
+
|
|
375
|
+
Here is the detailed parsing failure information,
|
|
376
|
+
including error messages and their locations within the input:
|
|
377
|
+
|
|
378
|
+
\`\`\`json
|
|
379
|
+
${JSON.stringify(f.failure.errors)}
|
|
380
|
+
\`\`\`
|
|
381
|
+
|
|
382
|
+
And here is the partially parsed data that was successfully
|
|
383
|
+
extracted before the error occurred:
|
|
384
|
+
|
|
385
|
+
\`\`\`json
|
|
386
|
+
${JSON.stringify(f.failure.data)}
|
|
387
|
+
\`\`\`
|
|
388
|
+
`
|
|
389
|
+
: [
|
|
390
|
+
"🚨 VALIDATION FAILURE: Your function arguments do not conform to the required schema.",
|
|
391
|
+
"",
|
|
392
|
+
"Each error below is computed absolute truth from rigorous type validation.",
|
|
393
|
+
"You must fix ALL errors to achieve 100% schema compliance.",
|
|
394
|
+
"",
|
|
395
|
+
LlmJson.stringify(f.validation),
|
|
396
|
+
].join("\n"),
|
|
315
397
|
} satisfies OpenAI.ChatCompletionToolMessageParam,
|
|
316
398
|
{
|
|
317
399
|
role: "system",
|
|
318
|
-
content:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
400
|
+
content: f.kind === "parse"
|
|
401
|
+
? AgenticaSystemPrompt.JSON_PARSE_ERROR.replace(
|
|
402
|
+
"${{FAILURE}}",
|
|
403
|
+
JSON.stringify(f.failure),
|
|
404
|
+
)
|
|
405
|
+
: AgenticaSystemPrompt.VALIDATE,
|
|
323
406
|
} satisfies OpenAI.ChatCompletionSystemMessageParam,
|
|
324
407
|
])
|
|
325
408
|
.flat();
|
|
326
409
|
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Validate that every selected function actually exists.
|
|
413
|
+
*
|
|
414
|
+
* `typia` validation only proves that `__IChatFunctionReference.name` is a
|
|
415
|
+
* `string`; it cannot know which functions exist at runtime. Without this
|
|
416
|
+
* check a hallucinated name would be silently dropped by
|
|
417
|
+
* `selectFunctionFromContext`. The returned errors are fed back to the LLM
|
|
418
|
+
* through `emendMessages`, exactly like a type validation error - with the
|
|
419
|
+
* list of valid function names in `expected`.
|
|
420
|
+
*/
|
|
421
|
+
function validateFunctionExistence(
|
|
422
|
+
ctx: AgenticaContext,
|
|
423
|
+
candidates: AgenticaOperation[],
|
|
424
|
+
data: __IChatFunctionReference.IProps,
|
|
425
|
+
): IValidation.IError[] {
|
|
426
|
+
const expected: string = candidates
|
|
427
|
+
.map(op => JSON.stringify(op.name))
|
|
428
|
+
.join(" | ");
|
|
429
|
+
return data.functions.flatMap((reference, i): IValidation.IError[] =>
|
|
430
|
+
ctx.operations.flat.has(reference.name)
|
|
431
|
+
? []
|
|
432
|
+
: [{
|
|
433
|
+
path: `$input.functions[${i}].name`,
|
|
434
|
+
expected,
|
|
435
|
+
value: reference.name,
|
|
436
|
+
description: [
|
|
437
|
+
`Function "${reference.name}" does not exist.`,
|
|
438
|
+
"Select only from the functions provided by getApiFunctions().",
|
|
439
|
+
].join(" "),
|
|
440
|
+
}],
|
|
441
|
+
);
|
|
442
|
+
}
|
|
@@ -95,13 +95,13 @@ function accumulate(origin: ChatCompletion, chunk: ChatCompletionChunk): ChatCom
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function
|
|
98
|
+
function mergeChunks(chunks: ChatCompletionChunk[]): ChatCompletion {
|
|
99
99
|
const firstChunk = chunks[0];
|
|
100
100
|
if (firstChunk === undefined) {
|
|
101
101
|
throw new Error("No chunks received");
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
return chunks.reduce(accumulate, {
|
|
105
105
|
id: firstChunk.id,
|
|
106
106
|
choices: [],
|
|
107
107
|
created: firstChunk.created,
|
|
@@ -111,17 +111,29 @@ function merge(chunks: ChatCompletionChunk[]): ChatCompletion {
|
|
|
111
111
|
service_tier: firstChunk.service_tier,
|
|
112
112
|
system_fingerprint: firstChunk.system_fingerprint,
|
|
113
113
|
} as ChatCompletion);
|
|
114
|
+
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Replace any tool call's empty `arguments` with `"{}"`.
|
|
118
|
+
*
|
|
119
|
+
* MUST only be applied to a final, fully streamed completion. Running it while
|
|
120
|
+
* more chunks may still arrive would seed `"{}"` into a not-yet-complete tool
|
|
121
|
+
* call, and the remaining streamed argument chunks would then be appended onto
|
|
122
|
+
* it - corrupting the arguments into `{}{...}`.
|
|
123
|
+
*/
|
|
124
|
+
function fixEmptyToolArguments(completion: ChatCompletion): ChatCompletion {
|
|
125
|
+
completion.choices?.forEach((choice) => {
|
|
117
126
|
choice.message.tool_calls?.filter(tc => tc.type === "function").forEach((toolCall) => {
|
|
118
127
|
if (toolCall.function.arguments === "") {
|
|
119
128
|
toolCall.function.arguments = "{}";
|
|
120
129
|
}
|
|
121
130
|
});
|
|
122
131
|
});
|
|
132
|
+
return completion;
|
|
133
|
+
}
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
function merge(chunks: ChatCompletionChunk[]): ChatCompletion {
|
|
136
|
+
return fixEmptyToolArguments(mergeChunks(chunks));
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
function mergeChoice(acc: ChatCompletion.Choice, cur: ChatCompletionChunk.Choice): ChatCompletion.Choice {
|
|
@@ -221,6 +233,8 @@ export const ChatGptCompletionMessageUtil = {
|
|
|
221
233
|
transformCompletionChunk,
|
|
222
234
|
accumulate,
|
|
223
235
|
merge,
|
|
236
|
+
mergeChunks,
|
|
237
|
+
fixEmptyToolArguments,
|
|
224
238
|
mergeChoice,
|
|
225
239
|
mergeToolCalls,
|
|
226
240
|
};
|
|
@@ -56,7 +56,11 @@ async function reduceStreamingWithDispatch(stream: ReadableStream<ChatCompletion
|
|
|
56
56
|
};
|
|
57
57
|
if (acc.object === "chat.completion.chunk") {
|
|
58
58
|
registerContext([acc, chunk].flatMap(v => v.choices ?? []));
|
|
59
|
-
|
|
59
|
+
// Use `mergeChunks`, NOT `merge`: `merge` runs the empty-arguments
|
|
60
|
+
// fixup, which mid-stream seeds a still-incomplete tool call with "{}"
|
|
61
|
+
// so the remaining streamed argument chunks append onto it (`{}{...}`).
|
|
62
|
+
// The fixup is applied once, to the final completion, below.
|
|
63
|
+
return ChatGptCompletionMessageUtil.mergeChunks([acc, chunk]);
|
|
60
64
|
}
|
|
61
65
|
registerContext(chunk.choices ?? []);
|
|
62
66
|
return ChatGptCompletionMessageUtil.accumulate(acc, chunk);
|
|
@@ -85,7 +89,7 @@ async function reduceStreamingWithDispatch(stream: ReadableStream<ChatCompletion
|
|
|
85
89
|
});
|
|
86
90
|
return completion;
|
|
87
91
|
}
|
|
88
|
-
return nullableCompletion;
|
|
92
|
+
return ChatGptCompletionMessageUtil.fixEmptyToolArguments(nullableCompletion);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
export { reduceStreamingWithDispatch };
|