@agentica/core 0.32.2 → 0.32.3-dev.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.
Files changed (43) hide show
  1. package/lib/index.mjs +320 -342
  2. package/lib/index.mjs.map +1 -1
  3. package/lib/orchestrate/call.js +87 -107
  4. package/lib/orchestrate/call.js.map +1 -1
  5. package/lib/orchestrate/describe.js +5 -50
  6. package/lib/orchestrate/describe.js.map +1 -1
  7. package/lib/orchestrate/initialize.js +5 -50
  8. package/lib/orchestrate/initialize.js.map +1 -1
  9. package/lib/orchestrate/select.js +107 -126
  10. package/lib/orchestrate/select.js.map +1 -1
  11. package/lib/utils/AssistantMessageEmptyError.d.ts +7 -0
  12. package/lib/utils/AssistantMessageEmptyError.js +17 -0
  13. package/lib/utils/AssistantMessageEmptyError.js.map +1 -0
  14. package/lib/utils/ChatGptCompletionStreamingUtil.d.ts +8 -0
  15. package/lib/utils/ChatGptCompletionStreamingUtil.js +86 -0
  16. package/lib/utils/ChatGptCompletionStreamingUtil.js.map +1 -0
  17. package/lib/utils/ChatGptCompletionStreamingUtil.spec.d.ts +1 -0
  18. package/lib/utils/ChatGptCompletionStreamingUtil.spec.js +855 -0
  19. package/lib/utils/ChatGptCompletionStreamingUtil.spec.js.map +1 -0
  20. package/lib/utils/MPSC.js +8 -6
  21. package/lib/utils/MPSC.js.map +1 -1
  22. package/lib/utils/StreamUtil.d.ts +1 -1
  23. package/lib/utils/StreamUtil.js +2 -2
  24. package/lib/utils/StreamUtil.js.map +1 -1
  25. package/lib/utils/__retry.d.ts +1 -0
  26. package/lib/utils/__retry.js +30 -0
  27. package/lib/utils/__retry.js.map +1 -0
  28. package/lib/utils/__retry.spec.d.ts +1 -0
  29. package/lib/utils/__retry.spec.js +172 -0
  30. package/lib/utils/__retry.spec.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/orchestrate/call.ts +88 -114
  33. package/src/orchestrate/describe.ts +7 -65
  34. package/src/orchestrate/initialize.ts +4 -64
  35. package/src/orchestrate/select.ts +111 -138
  36. package/src/utils/AssistantMessageEmptyError.ts +13 -0
  37. package/src/utils/ChatGptCompletionMessageUtil.ts +1 -1
  38. package/src/utils/ChatGptCompletionStreamingUtil.spec.ts +908 -0
  39. package/src/utils/ChatGptCompletionStreamingUtil.ts +90 -0
  40. package/src/utils/MPSC.ts +8 -6
  41. package/src/utils/StreamUtil.ts +2 -2
  42. package/src/utils/__retry.spec.ts +198 -0
  43. package/src/utils/__retry.ts +18 -0
package/lib/index.mjs CHANGED
@@ -808,93 +808,30 @@ function isAgenticaContext(ctx) {
808
808
  return typeof ctx.initialize === "function";
809
809
  }
810
810
 
811
- class AsyncQueueClosedError extends Error {
812
- constructor(message) {
813
- super(message);
814
- this.name = "AsyncQueueClosedError";
815
- }
811
+ function __get_retry(limit) {
812
+ const retryFn = async (fn, prevError, attempt = 0) => {
813
+ try {
814
+ return await fn(prevError);
815
+ } catch (error) {
816
+ if (attempt >= limit - 1) {
817
+ throw error;
818
+ }
819
+ return retryFn(fn, error, attempt + 1);
820
+ }
821
+ };
822
+ return retryFn;
816
823
  }
817
824
 
818
- class AsyncQueue {
825
+ class AssistantMessageEmptyError extends Error {
819
826
  constructor() {
820
- this.queue = [];
821
- this.resolvers = [];
822
- this.closeResolvers = [];
823
- this.emptyResolvers = [];
824
- this.closed = false;
825
- }
826
- enqueue(item) {
827
- if (this.closed) {
828
- console.error(new AsyncQueueClosedError("Cannot enqueue item: queue is closed."));
829
- return;
830
- }
831
- this.queue.push(item);
832
- if (this.resolvers.length > 0) {
833
- this.resolvers.shift()?.({
834
- value: this.queue.shift(),
835
- done: false
836
- });
837
- }
827
+ super();
838
828
  }
839
- async dequeue() {
840
- const item = (() => {
841
- if (!this.isEmpty()) {
842
- return {
843
- value: this.queue.shift(),
844
- done: false
845
- };
846
- }
847
- if (this.isClosed()) {
848
- return {
849
- value: undefined,
850
- done: true
851
- };
852
- }
853
- return null;
854
- })();
855
- if (this.isEmpty() && this.emptyResolvers.length !== 0) {
856
- this.emptyResolvers.forEach((resolve => resolve()));
857
- this.emptyResolvers = [];
858
- }
859
- if (item !== null) {
860
- return item;
861
- }
862
- return new Promise((resolve => this.resolvers.push(resolve)));
863
- }
864
- isEmpty() {
865
- return this.queue.length === 0;
866
- }
867
- isClosed() {
868
- return this.closed;
869
- }
870
- done() {
871
- return this.isClosed() && this.isEmpty();
872
- }
873
- close() {
874
- this.closed = true;
875
- while (this.resolvers.length > 0) {
876
- this.resolvers.shift()?.({
877
- value: undefined,
878
- done: true
879
- });
880
- }
881
- this.closeResolvers.forEach((resolve => resolve()));
882
- }
883
- async waitUntilEmpty() {
884
- if (this.isEmpty()) {
885
- return Promise.resolve();
886
- }
887
- return new Promise((resolve => {
888
- this.emptyResolvers.push(resolve);
889
- }));
890
- }
891
- async waitClosed() {
892
- if (this.isClosed()) {
893
- return Promise.resolve();
894
- }
895
- return new Promise((resolve => {
896
- this.closeResolvers.push(resolve);
897
- }));
829
+ }
830
+
831
+ class AssistantMessageEmptyWithReasoningError extends AssistantMessageEmptyError {
832
+ constructor(reasoning) {
833
+ super();
834
+ this.reasoning = reasoning;
898
835
  }
899
836
  }
900
837
 
@@ -1100,17 +1037,109 @@ const ChatGptCompletionMessageUtil = {
1100
1037
  mergeToolCalls
1101
1038
  };
1102
1039
 
1040
+ class AsyncQueueClosedError extends Error {
1041
+ constructor(message) {
1042
+ super(message);
1043
+ this.name = "AsyncQueueClosedError";
1044
+ }
1045
+ }
1046
+
1047
+ class AsyncQueue {
1048
+ constructor() {
1049
+ this.queue = [];
1050
+ this.resolvers = [];
1051
+ this.closeResolvers = [];
1052
+ this.emptyResolvers = [];
1053
+ this.closed = false;
1054
+ }
1055
+ enqueue(item) {
1056
+ if (this.closed) {
1057
+ console.error(new AsyncQueueClosedError("Cannot enqueue item: queue is closed."));
1058
+ return;
1059
+ }
1060
+ this.queue.push(item);
1061
+ if (this.resolvers.length > 0) {
1062
+ this.resolvers.shift()?.({
1063
+ value: this.queue.shift(),
1064
+ done: false
1065
+ });
1066
+ }
1067
+ }
1068
+ async dequeue() {
1069
+ const item = (() => {
1070
+ if (!this.isEmpty()) {
1071
+ return {
1072
+ value: this.queue.shift(),
1073
+ done: false
1074
+ };
1075
+ }
1076
+ if (this.isClosed()) {
1077
+ return {
1078
+ value: undefined,
1079
+ done: true
1080
+ };
1081
+ }
1082
+ return null;
1083
+ })();
1084
+ if (this.isEmpty() && this.emptyResolvers.length !== 0) {
1085
+ this.emptyResolvers.forEach((resolve => resolve()));
1086
+ this.emptyResolvers = [];
1087
+ }
1088
+ if (item !== null) {
1089
+ return item;
1090
+ }
1091
+ return new Promise((resolve => this.resolvers.push(resolve)));
1092
+ }
1093
+ isEmpty() {
1094
+ return this.queue.length === 0;
1095
+ }
1096
+ isClosed() {
1097
+ return this.closed;
1098
+ }
1099
+ done() {
1100
+ return this.isClosed() && this.isEmpty();
1101
+ }
1102
+ close() {
1103
+ this.closed = true;
1104
+ while (this.resolvers.length > 0) {
1105
+ this.resolvers.shift()?.({
1106
+ value: undefined,
1107
+ done: true
1108
+ });
1109
+ }
1110
+ this.closeResolvers.forEach((resolve => resolve()));
1111
+ }
1112
+ async waitUntilEmpty() {
1113
+ if (this.isEmpty()) {
1114
+ return Promise.resolve();
1115
+ }
1116
+ return new Promise((resolve => {
1117
+ this.emptyResolvers.push(resolve);
1118
+ }));
1119
+ }
1120
+ async waitClosed() {
1121
+ if (this.isClosed()) {
1122
+ return Promise.resolve();
1123
+ }
1124
+ return new Promise((resolve => {
1125
+ this.closeResolvers.push(resolve);
1126
+ }));
1127
+ }
1128
+ }
1129
+
1103
1130
  class MPSC {
1104
1131
  constructor() {
1105
1132
  this.queue = new AsyncQueue;
1106
1133
  this.consumer = new ReadableStream({
1107
- pull: async controller => {
1108
- const {value, done} = await this.queue.dequeue();
1109
- if (done === true) {
1110
- controller.close();
1111
- return;
1134
+ start: async controller => {
1135
+ while (true) {
1136
+ const {value, done} = await this.queue.dequeue();
1137
+ if (done === true) {
1138
+ controller.close();
1139
+ return;
1140
+ }
1141
+ controller.enqueue(value);
1112
1142
  }
1113
- controller.enqueue(value);
1114
1143
  }
1115
1144
  });
1116
1145
  }
@@ -1158,10 +1187,10 @@ async function reduce(stream, reducer, initial) {
1158
1187
  return acc;
1159
1188
  }
1160
1189
 
1161
- function from(value) {
1190
+ function from(...value) {
1162
1191
  const stream = new ReadableStream({
1163
1192
  start: controller => {
1164
- controller.enqueue(value);
1193
+ value.forEach((v => controller.enqueue(v)));
1165
1194
  controller.close();
1166
1195
  }
1167
1196
  });
@@ -1214,6 +1243,70 @@ var index$2 = Object.freeze({
1214
1243
  toAsyncGenerator
1215
1244
  });
1216
1245
 
1246
+ async function reduceStreamingWithDispatch(stream, eventProcessor) {
1247
+ const streamContext = new Map;
1248
+ const nullableCompletion = await StreamUtil.reduce(stream, (async (accPromise, chunk) => {
1249
+ const acc = await accPromise;
1250
+ const registerContext = choices => {
1251
+ for (const choice of choices) {
1252
+ if (choice.delta.content != null && choice.delta.content !== "") {
1253
+ if (streamContext.has(choice.index)) {
1254
+ const context = streamContext.get(choice.index);
1255
+ context.content += choice.delta.content;
1256
+ context.mpsc.produce(choice.delta.content);
1257
+ } else {
1258
+ const mpsc = new MPSC;
1259
+ streamContext.set(choice.index, {
1260
+ content: choice.delta.content,
1261
+ mpsc
1262
+ });
1263
+ mpsc.produce(choice.delta.content);
1264
+ eventProcessor({
1265
+ stream: streamDefaultReaderToAsyncGenerator(mpsc.consumer.getReader()),
1266
+ done: () => mpsc.done(),
1267
+ get: () => streamContext.get(choice.index)?.content ?? "",
1268
+ join: async () => {
1269
+ await mpsc.waitClosed();
1270
+ return streamContext.get(choice.index).content;
1271
+ }
1272
+ });
1273
+ }
1274
+ }
1275
+ if (choice.finish_reason != null) {
1276
+ const context = streamContext.get(choice.index);
1277
+ if (context != null) {
1278
+ context.mpsc.close();
1279
+ }
1280
+ }
1281
+ }
1282
+ };
1283
+ if (acc.object === "chat.completion.chunk") {
1284
+ registerContext([ acc, chunk ].flatMap((v => v.choices)));
1285
+ return ChatGptCompletionMessageUtil.merge([ acc, chunk ]);
1286
+ }
1287
+ registerContext(chunk.choices);
1288
+ return ChatGptCompletionMessageUtil.accumulate(acc, chunk);
1289
+ }));
1290
+ if (nullableCompletion == null) {
1291
+ throw new Error("StreamUtil.reduce did not produce a ChatCompletion. Possible causes: the input stream was empty, invalid, or closed prematurely. " + "To debug: check that the stream is properly initialized and contains valid ChatCompletionChunk data. " + "You may also enable verbose logging upstream to inspect the stream contents. " + `Stream locked: ${stream.locked}.`);
1292
+ }
1293
+ if (nullableCompletion.object === "chat.completion.chunk") {
1294
+ const completion = ChatGptCompletionMessageUtil.merge([ nullableCompletion ]);
1295
+ completion.choices.forEach((choice => {
1296
+ if (choice.message.content != null && choice.message.content !== "") {
1297
+ eventProcessor({
1298
+ stream: toAsyncGenerator(choice.message.content),
1299
+ done: () => true,
1300
+ get: () => choice.message.content,
1301
+ join: async () => choice.message.content
1302
+ });
1303
+ }
1304
+ }));
1305
+ return completion;
1306
+ }
1307
+ return nullableCompletion;
1308
+ }
1309
+
1217
1310
  function createOperationSelection(props) {
1218
1311
  return {
1219
1312
  operation: props.operation,
@@ -1242,87 +1335,66 @@ function cancelFunctionFromContext(ctx, reference) {
1242
1335
  }
1243
1336
 
1244
1337
  async function call(ctx, operations) {
1245
- const stream = await ctx.request("call", {
1246
- messages: [ {
1247
- role: "system",
1248
- content: AgenticaDefaultPrompt.write(ctx.config)
1249
- }, ...ctx.histories.map(decodeHistory).flat(), {
1250
- role: "user",
1251
- content: ctx.prompt.contents.map(decodeUserMessageContent)
1252
- }, ...ctx.config?.systemPrompt?.execute === null ? [] : [ {
1253
- role: "system",
1254
- content: ctx.config?.systemPrompt?.execute?.(ctx.histories) ?? AgenticaSystemPrompt.EXECUTE
1255
- } ] ],
1256
- tools: operations.map((s => ({
1257
- type: "function",
1258
- function: {
1259
- name: s.name,
1260
- description: s.function.description,
1261
- parameters: "separated" in s.function && s.function.separated !== undefined ? s.function.separated.llm ?? {
1262
- type: "object",
1263
- properties: {},
1264
- required: [],
1265
- additionalProperties: false,
1266
- $defs: {}
1267
- } : s.function.parameters
1268
- }
1269
- }))),
1270
- tool_choice: "auto"
1271
- });
1272
- const selectContext = [];
1273
- const nullableCompletion = await StreamUtil.reduce(stream, (async (accPromise, chunk) => {
1274
- const acc = await accPromise;
1275
- const registerContext = choices => {
1276
- for (const choice of choices) {
1277
- if (choice.finish_reason != null) {
1278
- selectContext[choice.index]?.mpsc.close();
1279
- continue;
1280
- }
1281
- if (choice.delta.content == null || choice.delta.content === "") {
1282
- continue;
1283
- }
1284
- if (selectContext[choice.index] != null) {
1285
- selectContext[choice.index].content += choice.delta.content;
1286
- selectContext[choice.index].mpsc.produce(choice.delta.content);
1287
- continue;
1288
- }
1289
- const mpsc = new MPSC;
1290
- selectContext[choice.index] = {
1291
- content: choice.delta.content,
1292
- mpsc
1293
- };
1294
- mpsc.produce(choice.delta.content);
1295
- const event = createAssistantMessageEvent({
1296
- stream: streamDefaultReaderToAsyncGenerator(mpsc.consumer.getReader()),
1297
- done: () => mpsc.done(),
1298
- get: () => selectContext[choice.index]?.content ?? "",
1299
- join: async () => {
1300
- await mpsc.waitClosed();
1301
- return selectContext[choice.index].content;
1302
- }
1303
- });
1304
- ctx.dispatch(event);
1305
- }
1306
- };
1307
- if (acc.object === "chat.completion.chunk") {
1308
- registerContext([ acc, chunk ].flatMap((v => v.choices)));
1309
- return ChatGptCompletionMessageUtil.merge([ acc, chunk ]);
1338
+ const _retryFn = __get_retry(ctx.config?.retry ?? AgenticaConstant.RETRY);
1339
+ const retryFn = async fn => _retryFn(fn).catch((e => {
1340
+ if (e instanceof AssistantMessageEmptyError) {
1341
+ return Symbol("emptyAssistantMessage");
1310
1342
  }
1311
- registerContext(chunk.choices);
1312
- return ChatGptCompletionMessageUtil.accumulate(acc, chunk);
1343
+ throw e;
1313
1344
  }));
1314
- const completion = nullableCompletion;
1315
- const emptyAssistantMessages = completion.choices.filter((v => v.message.tool_calls == null && v.message.content === ""));
1316
- if (emptyAssistantMessages.length > 0) {
1317
- emptyAssistantMessages.forEach((v => {
1318
- const event = createAssistantMessageEvent({
1319
- stream: toAsyncGenerator(v.message.content ?? ""),
1320
- done: () => true,
1321
- get: () => v.message.content ?? "",
1322
- join: async () => v.message.content ?? ""
1323
- });
1345
+ const completion = await retryFn((async prevError => {
1346
+ const stream = await ctx.request("call", {
1347
+ messages: [ {
1348
+ role: "system",
1349
+ content: AgenticaDefaultPrompt.write(ctx.config)
1350
+ }, ...ctx.histories.map(decodeHistory).flat(), {
1351
+ role: "user",
1352
+ content: ctx.prompt.contents.map(decodeUserMessageContent)
1353
+ }, ...prevError instanceof AssistantMessageEmptyWithReasoningError ? [ {
1354
+ role: "assistant",
1355
+ content: prevError.reasoning
1356
+ } ] : [], ...ctx.config?.systemPrompt?.execute === null ? [] : [ {
1357
+ role: "system",
1358
+ content: ctx.config?.systemPrompt?.execute?.(ctx.histories) ?? AgenticaSystemPrompt.EXECUTE
1359
+ } ] ],
1360
+ tools: operations.map((s => ({
1361
+ type: "function",
1362
+ function: {
1363
+ name: s.name,
1364
+ description: s.function.description,
1365
+ parameters: "separated" in s.function && s.function.separated !== undefined ? s.function.separated.llm ?? {
1366
+ type: "object",
1367
+ properties: {},
1368
+ required: [],
1369
+ additionalProperties: false,
1370
+ $defs: {}
1371
+ } : s.function.parameters
1372
+ }
1373
+ }))),
1374
+ tool_choice: "auto"
1375
+ });
1376
+ const completion = await reduceStreamingWithDispatch(stream, (props => {
1377
+ const event = createAssistantMessageEvent(props);
1324
1378
  ctx.dispatch(event);
1325
1379
  }));
1380
+ const allAssistantMessagesEmpty = completion.choices.every((v => v.message.tool_calls == null && v.message.content === ""));
1381
+ if (allAssistantMessagesEmpty) {
1382
+ const firstChoice = completion.choices.at(0);
1383
+ if (firstChoice?.message?.reasoning != null) {
1384
+ throw new AssistantMessageEmptyWithReasoningError(firstChoice?.message?.reasoning ?? "");
1385
+ }
1386
+ throw new AssistantMessageEmptyError;
1387
+ }
1388
+ return completion;
1389
+ }));
1390
+ if (typeof completion === "symbol") {
1391
+ const event = createAssistantMessageEvent({
1392
+ stream: toAsyncGenerator(""),
1393
+ done: () => true,
1394
+ get: () => "",
1395
+ join: async () => ""
1396
+ });
1397
+ ctx.dispatch(event);
1326
1398
  return [];
1327
1399
  }
1328
1400
  const executes = [];
@@ -1872,48 +1944,12 @@ async function describe(ctx, histories) {
1872
1944
  content: ctx.config?.systemPrompt?.describe?.(histories) ?? AgenticaSystemPrompt.DESCRIBE
1873
1945
  } ]
1874
1946
  });
1875
- const describeContext = [];
1876
- await StreamUtil.reduce(completionStream, (async (accPromise, chunk) => {
1877
- const acc = await accPromise;
1878
- const registerContext = choices => {
1879
- for (const choice of choices) {
1880
- if (choice.finish_reason != null) {
1881
- describeContext[choice.index].mpsc.close();
1882
- continue;
1883
- }
1884
- if (choice.delta.content == null) {
1885
- continue;
1886
- }
1887
- if (describeContext[choice.index] != null) {
1888
- describeContext[choice.index].content += choice.delta.content;
1889
- describeContext[choice.index].mpsc.produce(choice.delta.content);
1890
- continue;
1891
- }
1892
- const mpsc = new MPSC;
1893
- describeContext[choice.index] = {
1894
- content: choice.delta.content,
1895
- mpsc
1896
- };
1897
- mpsc.produce(choice.delta.content);
1898
- const event = createDescribeEvent({
1899
- executes: histories,
1900
- stream: streamDefaultReaderToAsyncGenerator(mpsc.consumer.getReader()),
1901
- done: () => mpsc.done(),
1902
- get: () => describeContext[choice.index]?.content ?? "",
1903
- join: async () => {
1904
- await mpsc.waitClosed();
1905
- return describeContext[choice.index].content;
1906
- }
1907
- });
1908
- ctx.dispatch(event);
1909
- }
1910
- };
1911
- if (acc.object === "chat.completion.chunk") {
1912
- registerContext([ acc, chunk ].flatMap((v => v.choices)));
1913
- return ChatGptCompletionMessageUtil.merge([ acc, chunk ]);
1914
- }
1915
- registerContext(chunk.choices);
1916
- return ChatGptCompletionMessageUtil.accumulate(acc, chunk);
1947
+ await reduceStreamingWithDispatch(completionStream, (props => {
1948
+ const event = createDescribeEvent({
1949
+ executes: histories,
1950
+ ...props
1951
+ });
1952
+ ctx.dispatch(event);
1917
1953
  }));
1918
1954
  }
1919
1955
 
@@ -2578,47 +2614,9 @@ async function initialize(ctx) {
2578
2614
  } ],
2579
2615
  tool_choice: "auto"
2580
2616
  });
2581
- const textContext = [];
2582
- const completion = await StreamUtil.reduce(completionStream, (async (accPromise, chunk) => {
2583
- const acc = await accPromise;
2584
- const registerContext = choices => {
2585
- for (const choice of choices) {
2586
- if (choice.finish_reason != null) {
2587
- textContext[choice.index]?.mpsc.close();
2588
- continue;
2589
- }
2590
- if (choice.delta.content == null || choice.delta.content.length === 0) {
2591
- continue;
2592
- }
2593
- if (textContext[choice.index] != null) {
2594
- textContext[choice.index].content += choice.delta.content;
2595
- textContext[choice.index].mpsc.produce(choice.delta.content);
2596
- continue;
2597
- }
2598
- const mpsc = new MPSC;
2599
- textContext[choice.index] = {
2600
- content: choice.delta.content,
2601
- mpsc
2602
- };
2603
- mpsc.produce(choice.delta.content);
2604
- const event = createAssistantMessageEvent({
2605
- stream: streamDefaultReaderToAsyncGenerator(mpsc.consumer.getReader()),
2606
- done: () => mpsc.done(),
2607
- get: () => textContext[choice.index].content,
2608
- join: async () => {
2609
- await mpsc.waitClosed();
2610
- return textContext[choice.index].content;
2611
- }
2612
- });
2613
- ctx.dispatch(event);
2614
- }
2615
- };
2616
- if (acc.object === "chat.completion.chunk") {
2617
- registerContext([ acc, chunk ].flatMap((v => v.choices)));
2618
- return ChatGptCompletionMessageUtil.merge([ acc, chunk ]);
2619
- }
2620
- registerContext(chunk.choices);
2621
- return ChatGptCompletionMessageUtil.accumulate(acc, chunk);
2617
+ const completion = await reduceStreamingWithDispatch(completionStream, (props => {
2618
+ const event = createAssistantMessageEvent(props);
2619
+ ctx.dispatch(event);
2622
2620
  }));
2623
2621
  if (completion === null) {
2624
2622
  throw new Error("No completion received");
@@ -2766,103 +2764,83 @@ async function select(ctx) {
2766
2764
  }
2767
2765
 
2768
2766
  async function step(ctx, operations, retry, failures) {
2769
- const completionStream = await ctx.request("select", {
2770
- messages: [ {
2771
- role: "system",
2772
- content: AgenticaDefaultPrompt.write(ctx.config)
2773
- }, {
2774
- role: "assistant",
2775
- tool_calls: [ {
2767
+ const _retryFn = __get_retry(ctx.config?.retry ?? AgenticaConstant.RETRY);
2768
+ const retryFn = async fn => _retryFn(fn).catch((e => {
2769
+ if (e instanceof AssistantMessageEmptyError) {
2770
+ return Symbol("emptyAssistantMessage");
2771
+ }
2772
+ throw e;
2773
+ }));
2774
+ const completion = await retryFn((async prevError => {
2775
+ const stream = await ctx.request("select", {
2776
+ messages: [ {
2777
+ role: "system",
2778
+ content: AgenticaDefaultPrompt.write(ctx.config)
2779
+ }, {
2780
+ role: "assistant",
2781
+ tool_calls: [ {
2782
+ type: "function",
2783
+ id: "getApiFunctions",
2784
+ function: {
2785
+ name: "getApiFunctions",
2786
+ arguments: JSON.stringify({})
2787
+ }
2788
+ } ]
2789
+ }, {
2790
+ role: "tool",
2791
+ tool_call_id: "getApiFunctions",
2792
+ content: JSON.stringify(operations.map((op => ({
2793
+ name: op.name,
2794
+ description: op.function.description,
2795
+ ...op.protocol === "http" ? {
2796
+ method: op.function.method,
2797
+ path: op.function.path,
2798
+ tags: op.function.tags
2799
+ } : {}
2800
+ }))))
2801
+ }, ...ctx.histories.map(decodeHistory).flat(), {
2802
+ role: "user",
2803
+ content: ctx.prompt.contents.map(decodeUserMessageContent)
2804
+ }, ...prevError instanceof AssistantMessageEmptyWithReasoningError ? [ {
2805
+ role: "assistant",
2806
+ content: prevError.reasoning
2807
+ } ] : [], {
2808
+ role: "system",
2809
+ content: ctx.config?.systemPrompt?.select?.(ctx.histories) ?? AgenticaSystemPrompt.SELECT
2810
+ }, ...emendMessages(failures ?? []) ],
2811
+ tools: [ {
2776
2812
  type: "function",
2777
- id: "getApiFunctions",
2778
2813
  function: {
2779
- name: "getApiFunctions",
2780
- arguments: JSON.stringify({})
2781
- }
2782
- } ]
2783
- }, {
2784
- role: "tool",
2785
- tool_call_id: "getApiFunctions",
2786
- content: JSON.stringify(operations.map((op => ({
2787
- name: op.name,
2788
- description: op.function.description,
2789
- ...op.protocol === "http" ? {
2790
- method: op.function.method,
2791
- path: op.function.path,
2792
- tags: op.function.tags
2793
- } : {}
2794
- }))))
2795
- }, ...ctx.histories.map(decodeHistory).flat(), {
2796
- role: "user",
2797
- content: ctx.prompt.contents.map(decodeUserMessageContent)
2798
- }, {
2799
- role: "system",
2800
- content: ctx.config?.systemPrompt?.select?.(ctx.histories) ?? AgenticaSystemPrompt.SELECT
2801
- }, ...emendMessages(failures ?? []) ],
2802
- tools: [ {
2803
- type: "function",
2804
- function: {
2805
- name: CONTAINER.functions[0].name,
2806
- description: CONTAINER.functions[0].description,
2807
- parameters: CONTAINER.functions[0].parameters
2808
- }
2809
- } ],
2810
- tool_choice: retry === 0 ? "auto" : "required"
2811
- });
2812
- const selectContext = [];
2813
- const nullableCompletion = await StreamUtil.reduce(completionStream, (async (accPromise, chunk) => {
2814
- const acc = await accPromise;
2815
- const registerContext = choices => {
2816
- for (const choice of choices) {
2817
- if (choice.finish_reason != null) {
2818
- selectContext[choice.index]?.mpsc.close();
2819
- continue;
2814
+ name: CONTAINER.functions[0].name,
2815
+ description: CONTAINER.functions[0].description,
2816
+ parameters: CONTAINER.functions[0].parameters
2820
2817
  }
2821
- if (choice.delta.content == null || choice.delta.content === "") {
2822
- continue;
2823
- }
2824
- if (selectContext[choice.index] != null) {
2825
- selectContext[choice.index].content += choice.delta.content;
2826
- selectContext[choice.index].mpsc.produce(choice.delta.content);
2827
- continue;
2828
- }
2829
- const mpsc = new MPSC;
2830
- selectContext[choice.index] = {
2831
- content: choice.delta.content,
2832
- mpsc
2833
- };
2834
- mpsc.produce(choice.delta.content);
2835
- const event = createAssistantMessageEvent({
2836
- stream: streamDefaultReaderToAsyncGenerator(mpsc.consumer.getReader()),
2837
- done: () => mpsc.done(),
2838
- get: () => selectContext[choice.index]?.content ?? "",
2839
- join: async () => {
2840
- await mpsc.waitClosed();
2841
- return selectContext[choice.index].content;
2842
- }
2843
- });
2844
- ctx.dispatch(event);
2818
+ } ],
2819
+ tool_choice: retry === 0 ? "auto" : "required"
2820
+ });
2821
+ const completion = await reduceStreamingWithDispatch(stream, (props => {
2822
+ const event = createAssistantMessageEvent(props);
2823
+ ctx.dispatch(event);
2824
+ }));
2825
+ const allAssistantMessagesEmpty = completion.choices.every((v => v.message.tool_calls == null && v.message.content === ""));
2826
+ if (allAssistantMessagesEmpty) {
2827
+ const firstChoice = completion.choices.at(0);
2828
+ if (firstChoice?.message?.reasoning != null) {
2829
+ throw new AssistantMessageEmptyWithReasoningError(firstChoice?.message?.reasoning ?? "");
2845
2830
  }
2846
- };
2847
- if (acc.object === "chat.completion.chunk") {
2848
- registerContext([ acc, chunk ].flatMap((v => v.choices)));
2849
- return ChatGptCompletionMessageUtil.merge([ acc, chunk ]);
2831
+ throw new AssistantMessageEmptyError;
2850
2832
  }
2851
- registerContext(chunk.choices);
2852
- return ChatGptCompletionMessageUtil.accumulate(acc, chunk);
2833
+ return completion;
2853
2834
  }));
2854
- const completion = nullableCompletion;
2855
- const emptyAssistantMessages = completion.choices.filter((v => v.message.tool_calls == null && v.message.content === ""));
2856
- if (emptyAssistantMessages.length > 0) {
2857
- emptyAssistantMessages.forEach((v => {
2858
- const event = createAssistantMessageEvent({
2859
- stream: toAsyncGenerator(v.message.content ?? ""),
2860
- done: () => true,
2861
- get: () => v.message.content ?? "",
2862
- join: async () => v.message.content ?? ""
2863
- });
2864
- ctx.dispatch(event);
2865
- }));
2835
+ if (typeof completion === "symbol") {
2836
+ const event = createAssistantMessageEvent({
2837
+ stream: toAsyncGenerator(""),
2838
+ done: () => true,
2839
+ get: () => "",
2840
+ join: async () => ""
2841
+ });
2842
+ ctx.dispatch(event);
2843
+ return;
2866
2844
  }
2867
2845
  if (retry++ < (ctx.config?.retry ?? AgenticaConstant.RETRY)) {
2868
2846
  const failures = [];