@ax-llm/ax 12.0.18 → 12.0.20

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/index.cjs CHANGED
@@ -770,6 +770,16 @@ var apiCall = async (api, json) => {
770
770
  var import_crypto2 = __toESM(require("crypto"), 1);
771
771
  var import_api2 = require("@opentelemetry/api");
772
772
 
773
+ // dsp/globals.ts
774
+ var axGlobals = {
775
+ signatureStrict: true,
776
+ // Controls reservedNames enforcement in signature parsing/validation
777
+ tracer: void 0,
778
+ // Global OpenTelemetry tracer for all AI operations
779
+ meter: void 0
780
+ // Global OpenTelemetry meter for metrics collection
781
+ };
782
+
773
783
  // trace/trace.ts
774
784
  var axSpanAttributes = {
775
785
  // LLM
@@ -1203,6 +1213,291 @@ var logResponseDelta = (delta, logger = defaultLogger) => {
1203
1213
  logger(delta, { tags: ["responseContent"] });
1204
1214
  };
1205
1215
 
1216
+ // ai/metrics.ts
1217
+ var createMetricsInstruments = (meter) => {
1218
+ return {
1219
+ latencyHistogram: meter.createHistogram("ax_llm_request_duration_ms", {
1220
+ description: "Duration of LLM requests in milliseconds",
1221
+ unit: "ms"
1222
+ }),
1223
+ errorCounter: meter.createCounter("ax_llm_errors_total", {
1224
+ description: "Total number of LLM request errors"
1225
+ }),
1226
+ requestCounter: meter.createCounter("ax_llm_requests_total", {
1227
+ description: "Total number of LLM requests"
1228
+ }),
1229
+ tokenCounter: meter.createCounter("ax_llm_tokens_total", {
1230
+ description: "Total number of LLM tokens consumed"
1231
+ }),
1232
+ inputTokenCounter: meter.createCounter("ax_llm_input_tokens_total", {
1233
+ description: "Total number of input/prompt tokens consumed"
1234
+ }),
1235
+ outputTokenCounter: meter.createCounter("ax_llm_output_tokens_total", {
1236
+ description: "Total number of output/completion tokens generated"
1237
+ }),
1238
+ errorRateGauge: meter.createGauge("ax_llm_error_rate", {
1239
+ description: "Current error rate as a percentage (0-100)"
1240
+ }),
1241
+ meanLatencyGauge: meter.createGauge("ax_llm_mean_latency_ms", {
1242
+ description: "Mean latency of LLM requests in milliseconds",
1243
+ unit: "ms"
1244
+ }),
1245
+ p95LatencyGauge: meter.createGauge("ax_llm_p95_latency_ms", {
1246
+ description: "95th percentile latency of LLM requests in milliseconds",
1247
+ unit: "ms"
1248
+ }),
1249
+ p99LatencyGauge: meter.createGauge("ax_llm_p99_latency_ms", {
1250
+ description: "99th percentile latency of LLM requests in milliseconds",
1251
+ unit: "ms"
1252
+ }),
1253
+ streamingRequestsCounter: meter.createCounter(
1254
+ "ax_llm_streaming_requests_total",
1255
+ {
1256
+ description: "Total number of streaming LLM requests"
1257
+ }
1258
+ ),
1259
+ functionCallsCounter: meter.createCounter("ax_llm_function_calls_total", {
1260
+ description: "Total number of function/tool calls made"
1261
+ }),
1262
+ functionCallLatencyHistogram: meter.createHistogram(
1263
+ "ax_llm_function_call_latency_ms",
1264
+ {
1265
+ description: "Latency of function calls in milliseconds",
1266
+ unit: "ms"
1267
+ }
1268
+ ),
1269
+ requestSizeHistogram: meter.createHistogram("ax_llm_request_size_bytes", {
1270
+ description: "Size of LLM request payloads in bytes",
1271
+ unit: "By"
1272
+ }),
1273
+ responseSizeHistogram: meter.createHistogram("ax_llm_response_size_bytes", {
1274
+ description: "Size of LLM response payloads in bytes",
1275
+ unit: "By"
1276
+ }),
1277
+ temperatureGauge: meter.createGauge("ax_llm_temperature_gauge", {
1278
+ description: "Temperature setting used for LLM requests"
1279
+ }),
1280
+ maxTokensGauge: meter.createGauge("ax_llm_max_tokens_gauge", {
1281
+ description: "Maximum tokens setting used for LLM requests"
1282
+ }),
1283
+ estimatedCostCounter: meter.createCounter("ax_llm_estimated_cost_total", {
1284
+ description: "Estimated cost of LLM requests in USD",
1285
+ unit: "$"
1286
+ }),
1287
+ promptLengthHistogram: meter.createHistogram("ax_llm_prompt_length_chars", {
1288
+ description: "Length of prompts in characters"
1289
+ }),
1290
+ contextWindowUsageGauge: meter.createGauge(
1291
+ "ax_llm_context_window_usage_ratio",
1292
+ {
1293
+ description: "Context window utilization ratio (0-1)"
1294
+ }
1295
+ ),
1296
+ timeoutsCounter: meter.createCounter("ax_llm_timeouts_total", {
1297
+ description: "Total number of timed out LLM requests"
1298
+ }),
1299
+ abortsCounter: meter.createCounter("ax_llm_aborts_total", {
1300
+ description: "Total number of aborted LLM requests"
1301
+ }),
1302
+ thinkingBudgetUsageCounter: meter.createCounter(
1303
+ "ax_llm_thinking_budget_usage_total",
1304
+ {
1305
+ description: "Total thinking budget tokens used"
1306
+ }
1307
+ ),
1308
+ multimodalRequestsCounter: meter.createCounter(
1309
+ "ax_llm_multimodal_requests_total",
1310
+ {
1311
+ description: "Total number of multimodal requests (with images/audio)"
1312
+ }
1313
+ )
1314
+ };
1315
+ };
1316
+ var recordLatencyMetric = (instruments, type, duration, aiService, model) => {
1317
+ if (instruments.latencyHistogram) {
1318
+ instruments.latencyHistogram.record(duration, {
1319
+ operation: type,
1320
+ ai_service: aiService,
1321
+ ...model ? { model } : {}
1322
+ });
1323
+ }
1324
+ };
1325
+ var recordLatencyStatsMetrics = (instruments, type, meanLatency, p95Latency, p99Latency, aiService, model) => {
1326
+ const labels = {
1327
+ operation: type,
1328
+ ai_service: aiService,
1329
+ ...model ? { model } : {}
1330
+ };
1331
+ if (instruments.meanLatencyGauge) {
1332
+ instruments.meanLatencyGauge.record(meanLatency, labels);
1333
+ }
1334
+ if (instruments.p95LatencyGauge) {
1335
+ instruments.p95LatencyGauge.record(p95Latency, labels);
1336
+ }
1337
+ if (instruments.p99LatencyGauge) {
1338
+ instruments.p99LatencyGauge.record(p99Latency, labels);
1339
+ }
1340
+ };
1341
+ var recordErrorMetric = (instruments, type, aiService, model) => {
1342
+ if (instruments.errorCounter) {
1343
+ instruments.errorCounter.add(1, {
1344
+ operation: type,
1345
+ ai_service: aiService,
1346
+ ...model ? { model } : {}
1347
+ });
1348
+ }
1349
+ };
1350
+ var recordErrorRateMetric = (instruments, type, errorRate, aiService, model) => {
1351
+ if (instruments.errorRateGauge) {
1352
+ instruments.errorRateGauge.record(errorRate * 100, {
1353
+ // Convert to percentage
1354
+ operation: type,
1355
+ ai_service: aiService,
1356
+ ...model ? { model } : {}
1357
+ });
1358
+ }
1359
+ };
1360
+ var recordRequestMetric = (instruments, type, aiService, model) => {
1361
+ if (instruments.requestCounter) {
1362
+ instruments.requestCounter.add(1, {
1363
+ operation: type,
1364
+ ai_service: aiService,
1365
+ ...model ? { model } : {}
1366
+ });
1367
+ }
1368
+ };
1369
+ var recordTokenMetric = (instruments, type, tokens, aiService, model) => {
1370
+ const labels = {
1371
+ ai_service: aiService,
1372
+ ...model ? { model } : {}
1373
+ };
1374
+ if (instruments.tokenCounter) {
1375
+ instruments.tokenCounter.add(tokens, {
1376
+ token_type: type,
1377
+ ...labels
1378
+ });
1379
+ }
1380
+ if (type === "input" && instruments.inputTokenCounter) {
1381
+ instruments.inputTokenCounter.add(tokens, labels);
1382
+ }
1383
+ if (type === "output" && instruments.outputTokenCounter) {
1384
+ instruments.outputTokenCounter.add(tokens, labels);
1385
+ }
1386
+ };
1387
+ var recordStreamingRequestMetric = (instruments, type, isStreaming, aiService, model) => {
1388
+ if (isStreaming && instruments.streamingRequestsCounter) {
1389
+ instruments.streamingRequestsCounter.add(1, {
1390
+ operation: type,
1391
+ ai_service: aiService,
1392
+ ...model ? { model } : {}
1393
+ });
1394
+ }
1395
+ };
1396
+ var recordFunctionCallMetric = (instruments, functionName, latency, aiService, model) => {
1397
+ const labels = {
1398
+ function_name: functionName,
1399
+ ...aiService ? { ai_service: aiService } : {},
1400
+ ...model ? { model } : {}
1401
+ };
1402
+ if (instruments.functionCallsCounter) {
1403
+ instruments.functionCallsCounter.add(1, labels);
1404
+ }
1405
+ if (latency && instruments.functionCallLatencyHistogram) {
1406
+ instruments.functionCallLatencyHistogram.record(latency, labels);
1407
+ }
1408
+ };
1409
+ var recordRequestSizeMetric = (instruments, type, sizeBytes, aiService, model) => {
1410
+ if (instruments.requestSizeHistogram) {
1411
+ instruments.requestSizeHistogram.record(sizeBytes, {
1412
+ operation: type,
1413
+ ai_service: aiService,
1414
+ ...model ? { model } : {}
1415
+ });
1416
+ }
1417
+ };
1418
+ var recordResponseSizeMetric = (instruments, type, sizeBytes, aiService, model) => {
1419
+ if (instruments.responseSizeHistogram) {
1420
+ instruments.responseSizeHistogram.record(sizeBytes, {
1421
+ operation: type,
1422
+ ai_service: aiService,
1423
+ ...model ? { model } : {}
1424
+ });
1425
+ }
1426
+ };
1427
+ var recordModelConfigMetrics = (instruments, temperature, maxTokens, aiService, model) => {
1428
+ const labels = {
1429
+ ...aiService ? { ai_service: aiService } : {},
1430
+ ...model ? { model } : {}
1431
+ };
1432
+ if (temperature !== void 0 && instruments.temperatureGauge) {
1433
+ instruments.temperatureGauge.record(temperature, labels);
1434
+ }
1435
+ if (maxTokens !== void 0 && instruments.maxTokensGauge) {
1436
+ instruments.maxTokensGauge.record(maxTokens, labels);
1437
+ }
1438
+ };
1439
+ var recordEstimatedCostMetric = (instruments, type, costUSD, aiService, model) => {
1440
+ if (instruments.estimatedCostCounter) {
1441
+ instruments.estimatedCostCounter.add(costUSD, {
1442
+ operation: type,
1443
+ ai_service: aiService,
1444
+ ...model ? { model } : {}
1445
+ });
1446
+ }
1447
+ };
1448
+ var recordPromptLengthMetric = (instruments, lengthChars, aiService, model) => {
1449
+ if (instruments.promptLengthHistogram) {
1450
+ instruments.promptLengthHistogram.record(lengthChars, {
1451
+ ai_service: aiService,
1452
+ ...model ? { model } : {}
1453
+ });
1454
+ }
1455
+ };
1456
+ var recordContextWindowUsageMetric = (instruments, usageRatio, aiService, model) => {
1457
+ if (instruments.contextWindowUsageGauge) {
1458
+ instruments.contextWindowUsageGauge.record(usageRatio, {
1459
+ ai_service: aiService,
1460
+ ...model ? { model } : {}
1461
+ });
1462
+ }
1463
+ };
1464
+ var recordTimeoutMetric = (instruments, type, aiService, model) => {
1465
+ if (instruments.timeoutsCounter) {
1466
+ instruments.timeoutsCounter.add(1, {
1467
+ operation: type,
1468
+ ai_service: aiService,
1469
+ ...model ? { model } : {}
1470
+ });
1471
+ }
1472
+ };
1473
+ var recordAbortMetric = (instruments, type, aiService, model) => {
1474
+ if (instruments.abortsCounter) {
1475
+ instruments.abortsCounter.add(1, {
1476
+ operation: type,
1477
+ ai_service: aiService,
1478
+ ...model ? { model } : {}
1479
+ });
1480
+ }
1481
+ };
1482
+ var recordThinkingBudgetUsageMetric = (instruments, tokensUsed, aiService, model) => {
1483
+ if (instruments.thinkingBudgetUsageCounter) {
1484
+ instruments.thinkingBudgetUsageCounter.add(tokensUsed, {
1485
+ ai_service: aiService,
1486
+ ...model ? { model } : {}
1487
+ });
1488
+ }
1489
+ };
1490
+ var recordMultimodalRequestMetric = (instruments, hasImages, hasAudio, aiService, model) => {
1491
+ if ((hasImages || hasAudio) && instruments.multimodalRequestsCounter) {
1492
+ instruments.multimodalRequestsCounter.add(1, {
1493
+ ai_service: aiService,
1494
+ has_images: hasImages.toString(),
1495
+ has_audio: hasAudio.toString(),
1496
+ ...model ? { model } : {}
1497
+ });
1498
+ }
1499
+ };
1500
+
1206
1501
  // ai/base.ts
1207
1502
  var axBaseAIDefaultConfig = () => structuredClone({
1208
1503
  temperature: 0,
@@ -1233,7 +1528,8 @@ var AxBaseAI = class {
1233
1528
  this.apiURL = apiURL;
1234
1529
  this.headers = headers;
1235
1530
  this.supportFor = supportFor;
1236
- this.tracer = options.tracer;
1531
+ this.tracer = options.tracer ?? axGlobals.tracer;
1532
+ this.meter = options.meter ?? axGlobals.meter;
1237
1533
  this.modelInfo = modelInfo;
1238
1534
  this.models = models;
1239
1535
  this.id = import_crypto2.default.randomUUID();
@@ -1244,6 +1540,7 @@ var AxBaseAI = class {
1244
1540
  throw new Error("No model defined");
1245
1541
  }
1246
1542
  this.setOptions(options);
1543
+ this.initializeMetricsInstruments();
1247
1544
  if (models) {
1248
1545
  validateModels(models);
1249
1546
  }
@@ -1252,11 +1549,14 @@ var AxBaseAI = class {
1252
1549
  rt;
1253
1550
  fetch;
1254
1551
  tracer;
1552
+ meter;
1255
1553
  timeout;
1256
1554
  excludeContentFromTrace;
1257
1555
  models;
1258
1556
  abortSignal;
1259
1557
  logger = defaultLogger2;
1558
+ // OpenTelemetry metrics instruments
1559
+ metricsInstruments;
1260
1560
  modelInfo;
1261
1561
  modelUsage;
1262
1562
  embedModelUsage;
@@ -1298,6 +1598,11 @@ var AxBaseAI = class {
1298
1598
  }
1299
1599
  }
1300
1600
  };
1601
+ initializeMetricsInstruments() {
1602
+ if (this.meter) {
1603
+ this.metricsInstruments = createMetricsInstruments(this.meter);
1604
+ }
1605
+ }
1301
1606
  setName(name) {
1302
1607
  this.name = name;
1303
1608
  }
@@ -1315,10 +1620,12 @@ var AxBaseAI = class {
1315
1620
  this.rt = options.rateLimiter;
1316
1621
  this.fetch = options.fetch;
1317
1622
  this.timeout = options.timeout;
1318
- this.tracer = options.tracer;
1623
+ this.tracer = options.tracer ?? axGlobals.tracer;
1624
+ this.meter = options.meter ?? axGlobals.meter;
1319
1625
  this.excludeContentFromTrace = options.excludeContentFromTrace;
1320
1626
  this.abortSignal = options.abortSignal;
1321
1627
  this.logger = options.logger ?? defaultLogger2;
1628
+ this.initializeMetricsInstruments();
1322
1629
  }
1323
1630
  getOptions() {
1324
1631
  return {
@@ -1326,6 +1633,7 @@ var AxBaseAI = class {
1326
1633
  rateLimiter: this.rt,
1327
1634
  fetch: this.fetch,
1328
1635
  tracer: this.tracer,
1636
+ meter: this.meter,
1329
1637
  timeout: this.timeout,
1330
1638
  excludeContentFromTrace: this.excludeContentFromTrace,
1331
1639
  abortSignal: this.abortSignal,
@@ -1390,6 +1698,25 @@ var AxBaseAI = class {
1390
1698
  metrics.mean = metrics.samples.reduce((a, b) => a + b, 0) / metrics.samples.length;
1391
1699
  metrics.p95 = this.calculatePercentile(metrics.samples, 95);
1392
1700
  metrics.p99 = this.calculatePercentile(metrics.samples, 99);
1701
+ if (this.metricsInstruments) {
1702
+ const model = type === "chat" ? this.lastUsedChatModel : this.lastUsedEmbedModel;
1703
+ recordLatencyMetric(
1704
+ this.metricsInstruments,
1705
+ type,
1706
+ duration,
1707
+ this.name,
1708
+ model
1709
+ );
1710
+ recordLatencyStatsMetrics(
1711
+ this.metricsInstruments,
1712
+ type,
1713
+ metrics.mean,
1714
+ metrics.p95,
1715
+ metrics.p99,
1716
+ this.name,
1717
+ model
1718
+ );
1719
+ }
1393
1720
  }
1394
1721
  // Method to update error metrics
1395
1722
  updateErrorMetrics(type, isError) {
@@ -1399,6 +1726,317 @@ var AxBaseAI = class {
1399
1726
  metrics.count++;
1400
1727
  }
1401
1728
  metrics.rate = metrics.count / metrics.total;
1729
+ if (this.metricsInstruments) {
1730
+ const model = type === "chat" ? this.lastUsedChatModel : this.lastUsedEmbedModel;
1731
+ recordRequestMetric(this.metricsInstruments, type, this.name, model);
1732
+ if (isError) {
1733
+ recordErrorMetric(this.metricsInstruments, type, this.name, model);
1734
+ }
1735
+ recordErrorRateMetric(
1736
+ this.metricsInstruments,
1737
+ type,
1738
+ metrics.rate,
1739
+ this.name,
1740
+ model
1741
+ );
1742
+ }
1743
+ }
1744
+ // Method to record token usage metrics
1745
+ recordTokenUsage(modelUsage) {
1746
+ if (this.metricsInstruments && modelUsage?.tokens) {
1747
+ const { promptTokens, completionTokens, totalTokens, thoughtsTokens } = modelUsage.tokens;
1748
+ if (promptTokens) {
1749
+ recordTokenMetric(
1750
+ this.metricsInstruments,
1751
+ "input",
1752
+ promptTokens,
1753
+ this.name,
1754
+ modelUsage.model
1755
+ );
1756
+ }
1757
+ if (completionTokens) {
1758
+ recordTokenMetric(
1759
+ this.metricsInstruments,
1760
+ "output",
1761
+ completionTokens,
1762
+ this.name,
1763
+ modelUsage.model
1764
+ );
1765
+ }
1766
+ if (totalTokens) {
1767
+ recordTokenMetric(
1768
+ this.metricsInstruments,
1769
+ "total",
1770
+ totalTokens,
1771
+ this.name,
1772
+ modelUsage.model
1773
+ );
1774
+ }
1775
+ if (thoughtsTokens) {
1776
+ recordTokenMetric(
1777
+ this.metricsInstruments,
1778
+ "thoughts",
1779
+ thoughtsTokens,
1780
+ this.name,
1781
+ modelUsage.model
1782
+ );
1783
+ }
1784
+ }
1785
+ }
1786
+ // Helper method to calculate request size in bytes
1787
+ calculateRequestSize(req) {
1788
+ try {
1789
+ return new TextEncoder().encode(JSON.stringify(req)).length;
1790
+ } catch {
1791
+ return 0;
1792
+ }
1793
+ }
1794
+ // Helper method to calculate response size in bytes
1795
+ calculateResponseSize(response) {
1796
+ try {
1797
+ return new TextEncoder().encode(JSON.stringify(response)).length;
1798
+ } catch {
1799
+ return 0;
1800
+ }
1801
+ }
1802
+ // Helper method to detect multimodal content
1803
+ detectMultimodalContent(req) {
1804
+ let hasImages = false;
1805
+ let hasAudio = false;
1806
+ if (req.chatPrompt && Array.isArray(req.chatPrompt)) {
1807
+ for (const message of req.chatPrompt) {
1808
+ if (message.role === "user" && Array.isArray(message.content)) {
1809
+ for (const part of message.content) {
1810
+ if (part.type === "image") {
1811
+ hasImages = true;
1812
+ } else if (part.type === "audio") {
1813
+ hasAudio = true;
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1818
+ }
1819
+ return { hasImages, hasAudio };
1820
+ }
1821
+ // Helper method to calculate prompt length
1822
+ calculatePromptLength(req) {
1823
+ let totalLength = 0;
1824
+ if (req.chatPrompt && Array.isArray(req.chatPrompt)) {
1825
+ for (const message of req.chatPrompt) {
1826
+ if (message.role === "system" || message.role === "assistant") {
1827
+ if (message.content) {
1828
+ totalLength += message.content.length;
1829
+ }
1830
+ } else if (message.role === "user") {
1831
+ if (typeof message.content === "string") {
1832
+ totalLength += message.content.length;
1833
+ } else if (Array.isArray(message.content)) {
1834
+ for (const part of message.content) {
1835
+ if (part.type === "text") {
1836
+ totalLength += part.text.length;
1837
+ }
1838
+ }
1839
+ }
1840
+ } else if (message.role === "function") {
1841
+ if (message.result) {
1842
+ totalLength += message.result.length;
1843
+ }
1844
+ }
1845
+ }
1846
+ }
1847
+ return totalLength;
1848
+ }
1849
+ // Helper method to calculate context window usage
1850
+ calculateContextWindowUsage(model, modelUsage) {
1851
+ if (!modelUsage?.tokens?.promptTokens) return 0;
1852
+ const modelInfo = this.modelInfo.find(
1853
+ (info) => info.name === model
1854
+ );
1855
+ if (!modelInfo?.contextWindow) return 0;
1856
+ return modelUsage.tokens.promptTokens / modelInfo.contextWindow;
1857
+ }
1858
+ // Helper method to estimate cost
1859
+ estimateCost(model, modelUsage) {
1860
+ if (!modelUsage?.tokens) return 0;
1861
+ const modelInfo = this.modelInfo.find(
1862
+ (info) => info.name === model
1863
+ );
1864
+ if (!modelInfo || !modelInfo.promptTokenCostPer1M && !modelInfo.completionTokenCostPer1M)
1865
+ return 0;
1866
+ const { promptTokens = 0, completionTokens = 0 } = modelUsage.tokens;
1867
+ const promptCostPer1M = modelInfo.promptTokenCostPer1M || 0;
1868
+ const completionCostPer1M = modelInfo.completionTokenCostPer1M || 0;
1869
+ return promptTokens * promptCostPer1M / 1e6 + completionTokens * completionCostPer1M / 1e6;
1870
+ }
1871
+ // Helper method to estimate cost by model name
1872
+ estimateCostByName(modelName, modelUsage) {
1873
+ if (!modelUsage?.tokens) return 0;
1874
+ const modelInfo = this.modelInfo.find((info) => info.name === modelName);
1875
+ if (!modelInfo || !modelInfo.promptTokenCostPer1M && !modelInfo.completionTokenCostPer1M)
1876
+ return 0;
1877
+ const { promptTokens = 0, completionTokens = 0 } = modelUsage.tokens;
1878
+ const promptCostPer1M = modelInfo.promptTokenCostPer1M || 0;
1879
+ const completionCostPer1M = modelInfo.completionTokenCostPer1M || 0;
1880
+ return promptTokens * promptCostPer1M / 1e6 + completionTokens * completionCostPer1M / 1e6;
1881
+ }
1882
+ // Helper method to record function call metrics
1883
+ recordFunctionCallMetrics(functionCalls, model) {
1884
+ if (!this.metricsInstruments || !functionCalls) return;
1885
+ for (const call of functionCalls) {
1886
+ if (call && typeof call === "object" && "function" in call && call.function && typeof call.function === "object" && "name" in call.function) {
1887
+ recordFunctionCallMetric(
1888
+ this.metricsInstruments,
1889
+ call.function.name,
1890
+ void 0,
1891
+ // latency would need to be tracked separately
1892
+ this.name,
1893
+ model
1894
+ );
1895
+ }
1896
+ }
1897
+ }
1898
+ // Helper method to record timeout metrics
1899
+ recordTimeoutMetric(type) {
1900
+ if (this.metricsInstruments) {
1901
+ const model = type === "chat" ? this.lastUsedChatModel : this.lastUsedEmbedModel;
1902
+ recordTimeoutMetric(this.metricsInstruments, type, this.name, model);
1903
+ }
1904
+ }
1905
+ // Helper method to record abort metrics
1906
+ recordAbortMetric(type) {
1907
+ if (this.metricsInstruments) {
1908
+ const model = type === "chat" ? this.lastUsedChatModel : this.lastUsedEmbedModel;
1909
+ recordAbortMetric(this.metricsInstruments, type, this.name, model);
1910
+ }
1911
+ }
1912
+ // Comprehensive method to record all chat-related metrics
1913
+ recordChatMetrics(req, options, result) {
1914
+ if (!this.metricsInstruments) return;
1915
+ const model = this.lastUsedChatModel;
1916
+ const modelConfig = this.lastUsedModelConfig;
1917
+ const isStreaming = modelConfig?.stream ?? false;
1918
+ recordStreamingRequestMetric(
1919
+ this.metricsInstruments,
1920
+ "chat",
1921
+ isStreaming,
1922
+ this.name,
1923
+ model
1924
+ );
1925
+ const { hasImages, hasAudio } = this.detectMultimodalContent(req);
1926
+ recordMultimodalRequestMetric(
1927
+ this.metricsInstruments,
1928
+ hasImages,
1929
+ hasAudio,
1930
+ this.name,
1931
+ model
1932
+ );
1933
+ const promptLength = this.calculatePromptLength(req);
1934
+ recordPromptLengthMetric(
1935
+ this.metricsInstruments,
1936
+ promptLength,
1937
+ this.name,
1938
+ model
1939
+ );
1940
+ recordModelConfigMetrics(
1941
+ this.metricsInstruments,
1942
+ modelConfig?.temperature,
1943
+ modelConfig?.maxTokens,
1944
+ this.name,
1945
+ model
1946
+ );
1947
+ if (options?.thinkingTokenBudget && this.modelUsage?.tokens?.thoughtsTokens) {
1948
+ recordThinkingBudgetUsageMetric(
1949
+ this.metricsInstruments,
1950
+ this.modelUsage.tokens.thoughtsTokens,
1951
+ this.name,
1952
+ model
1953
+ );
1954
+ }
1955
+ const requestSize = this.calculateRequestSize(req);
1956
+ recordRequestSizeMetric(
1957
+ this.metricsInstruments,
1958
+ "chat",
1959
+ requestSize,
1960
+ this.name,
1961
+ model
1962
+ );
1963
+ if (result && !isStreaming) {
1964
+ const chatResponse = result;
1965
+ const responseSize = this.calculateResponseSize(chatResponse);
1966
+ recordResponseSizeMetric(
1967
+ this.metricsInstruments,
1968
+ "chat",
1969
+ responseSize,
1970
+ this.name,
1971
+ model
1972
+ );
1973
+ if (chatResponse.results) {
1974
+ for (const chatResult of chatResponse.results) {
1975
+ if (chatResult.functionCalls) {
1976
+ this.recordFunctionCallMetrics(
1977
+ chatResult.functionCalls,
1978
+ this.lastUsedChatModel
1979
+ );
1980
+ }
1981
+ }
1982
+ }
1983
+ const contextUsage = this.calculateContextWindowUsage(
1984
+ this.lastUsedChatModel,
1985
+ chatResponse.modelUsage
1986
+ );
1987
+ if (contextUsage > 0) {
1988
+ recordContextWindowUsageMetric(
1989
+ this.metricsInstruments,
1990
+ contextUsage,
1991
+ this.name,
1992
+ model
1993
+ );
1994
+ }
1995
+ const estimatedCost = this.estimateCost(
1996
+ this.lastUsedChatModel,
1997
+ chatResponse.modelUsage
1998
+ );
1999
+ if (estimatedCost > 0) {
2000
+ recordEstimatedCostMetric(
2001
+ this.metricsInstruments,
2002
+ "chat",
2003
+ estimatedCost,
2004
+ this.name,
2005
+ model
2006
+ );
2007
+ }
2008
+ }
2009
+ }
2010
+ // Comprehensive method to record all embed-related metrics
2011
+ recordEmbedMetrics(req, result) {
2012
+ if (!this.metricsInstruments) return;
2013
+ const model = this.lastUsedEmbedModel;
2014
+ const requestSize = this.calculateRequestSize(req);
2015
+ recordRequestSizeMetric(
2016
+ this.metricsInstruments,
2017
+ "embed",
2018
+ requestSize,
2019
+ this.name,
2020
+ model
2021
+ );
2022
+ const responseSize = this.calculateResponseSize(result);
2023
+ recordResponseSizeMetric(
2024
+ this.metricsInstruments,
2025
+ "embed",
2026
+ responseSize,
2027
+ this.name,
2028
+ model
2029
+ );
2030
+ const estimatedCost = this.estimateCostByName(model, result.modelUsage);
2031
+ if (estimatedCost > 0) {
2032
+ recordEstimatedCostMetric(
2033
+ this.metricsInstruments,
2034
+ "embed",
2035
+ estimatedCost,
2036
+ this.name,
2037
+ model
2038
+ );
2039
+ }
1402
2040
  }
1403
2041
  // Public method to get metrics
1404
2042
  getMetrics() {
@@ -1407,16 +2045,27 @@ var AxBaseAI = class {
1407
2045
  async chat(req, options) {
1408
2046
  const startTime = performance.now();
1409
2047
  let isError = false;
2048
+ let result;
1410
2049
  try {
1411
- const result = await this._chat1(req, options);
2050
+ result = await this._chat1(req, options);
1412
2051
  return result;
1413
2052
  } catch (error) {
1414
2053
  isError = true;
2054
+ if (error instanceof Error) {
2055
+ if (error.message.includes("timeout") || error.name === "TimeoutError") {
2056
+ this.recordTimeoutMetric("chat");
2057
+ } else if (error.message.includes("abort") || error.name === "AbortError") {
2058
+ this.recordAbortMetric("chat");
2059
+ }
2060
+ }
1415
2061
  throw error;
1416
2062
  } finally {
1417
2063
  const duration = performance.now() - startTime;
1418
2064
  this.updateLatencyMetrics("chat", duration);
1419
2065
  this.updateErrorMetrics("chat", isError);
2066
+ if (!isError) {
2067
+ this.recordChatMetrics(req, options, result);
2068
+ }
1420
2069
  }
1421
2070
  }
1422
2071
  async _chat1(req, options) {
@@ -1563,6 +2212,7 @@ var AxBaseAI = class {
1563
2212
  }
1564
2213
  }
1565
2214
  this.modelUsage = res2.modelUsage;
2215
+ this.recordTokenUsage(res2.modelUsage);
1566
2216
  if (span?.isRecording()) {
1567
2217
  setChatResponseEvents(res2, span, this.excludeContentFromTrace);
1568
2218
  }
@@ -1605,6 +2255,7 @@ var AxBaseAI = class {
1605
2255
  }
1606
2256
  if (res.modelUsage) {
1607
2257
  this.modelUsage = res.modelUsage;
2258
+ this.recordTokenUsage(res.modelUsage);
1608
2259
  }
1609
2260
  if (span?.isRecording()) {
1610
2261
  setChatResponseEvents(res, span, this.excludeContentFromTrace);
@@ -1621,15 +2272,27 @@ var AxBaseAI = class {
1621
2272
  async embed(req, options) {
1622
2273
  const startTime = performance.now();
1623
2274
  let isError = false;
2275
+ let result;
1624
2276
  try {
1625
- return this._embed1(req, options);
2277
+ result = await this._embed1(req, options);
2278
+ return result;
1626
2279
  } catch (error) {
1627
2280
  isError = true;
2281
+ if (error instanceof Error) {
2282
+ if (error.message.includes("timeout") || error.name === "TimeoutError") {
2283
+ this.recordTimeoutMetric("embed");
2284
+ } else if (error.message.includes("abort") || error.name === "AbortError") {
2285
+ this.recordAbortMetric("embed");
2286
+ }
2287
+ }
1628
2288
  throw error;
1629
2289
  } finally {
1630
2290
  const duration = performance.now() - startTime;
1631
2291
  this.updateLatencyMetrics("embed", duration);
1632
2292
  this.updateErrorMetrics("embed", isError);
2293
+ if (!isError) {
2294
+ this.recordEmbedMetrics(req, result);
2295
+ }
1633
2296
  }
1634
2297
  }
1635
2298
  async _embed1(req, options) {
@@ -1704,6 +2367,7 @@ var AxBaseAI = class {
1704
2367
  }
1705
2368
  }
1706
2369
  this.embedModelUsage = res.modelUsage;
2370
+ this.recordTokenUsage(res.modelUsage);
1707
2371
  if (span?.isRecording() && res.modelUsage?.tokens) {
1708
2372
  span.addEvent(axSpanEvents.GEN_AI_USAGE, {
1709
2373
  [axSpanAttributes.LLM_USAGE_INPUT_TOKENS]: res.modelUsage.tokens.promptTokens,
@@ -8062,12 +8726,6 @@ function mergeFunctionCalls(functionCalls, functionCallDeltas) {
8062
8726
  // dsp/sig.ts
8063
8727
  var import_crypto3 = require("crypto");
8064
8728
 
8065
- // dsp/globals.ts
8066
- var axGlobals = {
8067
- signatureStrict: true
8068
- // Controls reservedNames enforcement in signature parsing/validation
8069
- };
8070
-
8071
8729
  // dsp/parser.ts
8072
8730
  var SignatureValidationError = class extends Error {
8073
8731
  constructor(message, position, context3, suggestion) {
@@ -13188,34 +13846,35 @@ var AxFlow = class extends AxProgramWithSignature {
13188
13846
  constructor(signature = "userInput:string -> flowOutput:string") {
13189
13847
  super(signature);
13190
13848
  }
13191
- /**
13192
- * Declares a reusable computational node and its input/output signature.
13193
- * Returns a new AxFlow type that tracks this node in the TNodes registry.
13194
- *
13195
- * @param name - The name of the node
13196
- * @param signature - Signature string in the same format as AxSignature
13197
- * @param options - Optional program forward options (same as AxGen)
13198
- * @returns New AxFlow instance with updated TNodes type
13199
- *
13200
- * @example
13201
- * ```typescript
13202
- * flow.node('summarizer', 'text:string -> summary:string')
13203
- * flow.node('analyzer', 'text:string -> analysis:string, confidence:number', { debug: true })
13204
- * ```
13205
- */
13206
- node(name, signature, options) {
13207
- if (!signature) {
13208
- throw new Error(
13209
- `Invalid signature for node '${name}': signature cannot be empty`
13849
+ // Implementation
13850
+ node(name, signatureOrAxGen, options) {
13851
+ if (signatureOrAxGen instanceof AxGen) {
13852
+ this.nodes.set(name, {
13853
+ inputs: {},
13854
+ outputs: {}
13855
+ });
13856
+ this.nodeGenerators.set(
13857
+ name,
13858
+ signatureOrAxGen
13210
13859
  );
13860
+ } else {
13861
+ const signature = signatureOrAxGen;
13862
+ if (!signature) {
13863
+ throw new Error(
13864
+ `Invalid signature for node '${name}': signature cannot be empty`
13865
+ );
13866
+ }
13867
+ this.nodes.set(name, {
13868
+ inputs: {},
13869
+ outputs: {}
13870
+ });
13871
+ this.nodeGenerators.set(name, new AxGen(signature, options));
13211
13872
  }
13212
- this.nodes.set(name, {
13213
- inputs: {},
13214
- outputs: {}
13215
- });
13216
- this.nodeGenerators.set(name, new AxGen(signature, options));
13217
13873
  return this;
13218
13874
  }
13875
+ n(name, signatureOrAxGen, options) {
13876
+ return this.node(name, signatureOrAxGen, options);
13877
+ }
13219
13878
  /**
13220
13879
  * Applies a synchronous transformation to the state object.
13221
13880
  * Returns a new AxFlow type with the evolved state.
@@ -13246,6 +13905,12 @@ var AxFlow = class extends AxProgramWithSignature {
13246
13905
  }
13247
13906
  return this;
13248
13907
  }
13908
+ /**
13909
+ * Short alias for map()
13910
+ */
13911
+ m(transform) {
13912
+ return this.map(transform);
13913
+ }
13249
13914
  /**
13250
13915
  * Labels a step for later reference (useful for feedback loops).
13251
13916
  *
@@ -13265,6 +13930,12 @@ var AxFlow = class extends AxProgramWithSignature {
13265
13930
  this.stepLabels.set(label, this.flowDefinition.length);
13266
13931
  return this;
13267
13932
  }
13933
+ /**
13934
+ * Short alias for label()
13935
+ */
13936
+ l(label) {
13937
+ return this.label(label);
13938
+ }
13268
13939
  /**
13269
13940
  * Executes a previously defined node with full type safety.
13270
13941
  * The node name must exist in TNodes, and the mapping function is typed based on the node's signature.
@@ -13313,6 +13984,12 @@ var AxFlow = class extends AxProgramWithSignature {
13313
13984
  }
13314
13985
  return this;
13315
13986
  }
13987
+ /**
13988
+ * Short alias for execute()
13989
+ */
13990
+ e(nodeName, mapping, dynamicContext) {
13991
+ return this.execute(nodeName, mapping, dynamicContext);
13992
+ }
13316
13993
  /**
13317
13994
  * Starts a conditional branch based on a predicate function.
13318
13995
  *
@@ -13340,6 +14017,12 @@ var AxFlow = class extends AxProgramWithSignature {
13340
14017
  };
13341
14018
  return this;
13342
14019
  }
14020
+ /**
14021
+ * Short alias for branch()
14022
+ */
14023
+ b(predicate) {
14024
+ return this.branch(predicate);
14025
+ }
13343
14026
  /**
13344
14027
  * Defines a branch case for the current branch context.
13345
14028
  *
@@ -13354,10 +14037,33 @@ var AxFlow = class extends AxProgramWithSignature {
13354
14037
  this.branchContext.branches.set(value, []);
13355
14038
  return this;
13356
14039
  }
14040
+ /**
14041
+ * Short alias for when()
14042
+ */
14043
+ w(value) {
14044
+ return this.when(value);
14045
+ }
13357
14046
  /**
13358
14047
  * Ends the current branch and merges all branch paths back into the main flow.
14048
+ * Optionally specify the explicit merged state type for better type safety.
13359
14049
  *
13360
- * @returns this (for chaining)
14050
+ * @param explicitMergedType - Optional type hint for the merged state (defaults to current TState)
14051
+ * @returns AxFlow instance with the merged state type
14052
+ *
14053
+ * @example
14054
+ * ```typescript
14055
+ * // Default behavior - preserves current TState
14056
+ * flow.branch(state => state.type)
14057
+ * .when('simple').execute('simpleProcessor', ...)
14058
+ * .when('complex').execute('complexProcessor', ...)
14059
+ * .merge()
14060
+ *
14061
+ * // Explicit type - specify exact merged state shape
14062
+ * flow.branch(state => state.type)
14063
+ * .when('simple').map(state => ({ result: state.simpleResult, method: 'simple' }))
14064
+ * .when('complex').map(state => ({ result: state.complexResult, method: 'complex' }))
14065
+ * .merge<{ result: string; method: string }>()
14066
+ * ```
13361
14067
  */
13362
14068
  merge() {
13363
14069
  if (!this.branchContext) {
@@ -13379,6 +14085,12 @@ var AxFlow = class extends AxProgramWithSignature {
13379
14085
  });
13380
14086
  return this;
13381
14087
  }
14088
+ /**
14089
+ * Short alias for merge()
14090
+ */
14091
+ mg() {
14092
+ return this.merge();
14093
+ }
13382
14094
  /**
13383
14095
  * Executes multiple operations in parallel and merges their results.
13384
14096
  * Both typed and legacy untyped branches are supported.
@@ -13428,6 +14140,12 @@ var AxFlow = class extends AxProgramWithSignature {
13428
14140
  }
13429
14141
  };
13430
14142
  }
14143
+ /**
14144
+ * Short alias for parallel()
14145
+ */
14146
+ p(branches) {
14147
+ return this.parallel(branches);
14148
+ }
13431
14149
  /**
13432
14150
  * Creates a feedback loop that jumps back to a labeled step if a condition is met.
13433
14151
  *
@@ -13473,32 +14191,46 @@ var AxFlow = class extends AxProgramWithSignature {
13473
14191
  });
13474
14192
  return this;
13475
14193
  }
14194
+ /**
14195
+ * Short alias for feedback()
14196
+ */
14197
+ fb(condition, targetLabel, maxIterations = 10) {
14198
+ return this.feedback(condition, targetLabel, maxIterations);
14199
+ }
13476
14200
  /**
13477
14201
  * Marks the beginning of a loop block.
13478
14202
  *
13479
14203
  * @param condition - Function that takes the current state and returns a boolean
14204
+ * @param maxIterations - Maximum number of iterations to prevent infinite loops (default: 100)
13480
14205
  * @returns this (for chaining)
13481
14206
  *
13482
14207
  * @example
13483
14208
  * ```typescript
13484
- * flow.while(state => state.iterations < 3)
14209
+ * flow.while(state => state.iterations < 3, 10)
13485
14210
  * .map(state => ({ ...state, iterations: (state.iterations || 0) + 1 }))
13486
14211
  * .endWhile()
13487
14212
  * ```
13488
14213
  */
13489
- while(condition) {
14214
+ while(condition, maxIterations = 100) {
13490
14215
  const loopStartIndex = this.flowDefinition.length;
13491
14216
  this.loopStack.push(loopStartIndex);
13492
14217
  const placeholderStep = Object.assign(
13493
14218
  (state) => state,
13494
14219
  {
13495
14220
  _condition: condition,
14221
+ _maxIterations: maxIterations,
13496
14222
  _isLoopStart: true
13497
14223
  }
13498
14224
  );
13499
14225
  this.flowDefinition.push(placeholderStep);
13500
14226
  return this;
13501
14227
  }
14228
+ /**
14229
+ * Short alias for while()
14230
+ */
14231
+ wh(condition, maxIterations = 100) {
14232
+ return this.while(condition, maxIterations);
14233
+ }
13502
14234
  /**
13503
14235
  * Marks the end of a loop block.
13504
14236
  *
@@ -13514,18 +14246,32 @@ var AxFlow = class extends AxProgramWithSignature {
13514
14246
  throw new Error("Loop start step not found or invalid");
13515
14247
  }
13516
14248
  const condition = placeholderStep._condition;
14249
+ const maxIterations = placeholderStep._maxIterations;
13517
14250
  const loopBodySteps = this.flowDefinition.splice(loopStartIndex + 1);
13518
14251
  this.flowDefinition[loopStartIndex] = async (state, context3) => {
13519
14252
  let currentState = state;
13520
- while (condition(currentState)) {
14253
+ let iterations = 0;
14254
+ while (condition(currentState) && iterations < maxIterations) {
14255
+ iterations++;
13521
14256
  for (const step of loopBodySteps) {
13522
14257
  currentState = await step(currentState, context3);
13523
14258
  }
13524
14259
  }
14260
+ if (iterations >= maxIterations && condition(currentState)) {
14261
+ throw new Error(
14262
+ `While loop exceeded maximum iterations (${maxIterations}). Consider increasing maxIterations or ensuring the loop condition eventually becomes false.`
14263
+ );
14264
+ }
13525
14265
  return currentState;
13526
14266
  };
13527
14267
  return this;
13528
14268
  }
14269
+ /**
14270
+ * Short alias for endWhile()
14271
+ */
14272
+ end() {
14273
+ return this.endWhile();
14274
+ }
13529
14275
  /**
13530
14276
  * Executes the flow with the given AI service and input values.
13531
14277
  *