@antseed/cli 0.1.197 → 0.1.198
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/dist/gateway/console-api/handlers/network-mapping.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/network-mapping.js +2 -0
- package/dist/gateway/console-api/handlers/network-mapping.js.map +1 -1
- package/dist/gateway/console-api/handlers/network.d.ts.map +1 -1
- package/dist/gateway/console-api/handlers/network.js +16 -2
- package/dist/gateway/console-api/handlers/network.js.map +1 -1
- package/dist/gateway/console-api/handlers/network.test.js +31 -4
- package/dist/gateway/console-api/handlers/network.test.js.map +1 -1
- package/dist/gateway/console-api/types.d.ts +6 -0
- package/dist/gateway/console-api/types.d.ts.map +1 -1
- package/dist/gateway/console-api/types.js.map +1 -1
- package/dist/gateway/routing-through-gateway.test.js +34 -0
- package/dist/gateway/routing-through-gateway.test.js.map +1 -1
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +4 -1
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/services/usage.js +1 -1
- package/dist/gateway/services/usage.js.map +1 -1
- package/dist/gateway/store-data.test.js +1 -1
- package/dist/gateway/store-data.test.js.map +1 -1
- package/dist/gateway/store-migrations.d.ts.map +1 -1
- package/dist/gateway/store-migrations.js +5 -0
- package/dist/gateway/store-migrations.js.map +1 -1
- package/dist/gateway/store.d.ts +4 -0
- package/dist/gateway/store.d.ts.map +1 -1
- package/dist/gateway/store.js +20 -4
- package/dist/gateway/store.js.map +1 -1
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +74 -6
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/gateway-policy.test.js +75 -1
- package/dist/proxy/gateway-policy.test.js.map +1 -1
- package/dist/proxy/route-selection.d.ts +79 -0
- package/dist/proxy/route-selection.d.ts.map +1 -0
- package/dist/proxy/route-selection.js +79 -0
- package/dist/proxy/route-selection.js.map +1 -0
- package/dist/proxy/telemetry.d.ts +3 -2
- package/dist/proxy/telemetry.d.ts.map +1 -1
- package/dist/proxy/telemetry.js +7 -4
- package/dist/proxy/telemetry.js.map +1 -1
- package/package.json +7 -7
|
@@ -9,6 +9,7 @@ import { readFileSync, watchFile, unwatchFile } from 'node:fs';
|
|
|
9
9
|
import { readFile, writeFile, rename, mkdir, readdir, stat, unlink } from 'node:fs/promises';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, ANTSEED_ATTEST_PATH, adaptPeerFaultErrorResponse, computeTrustScore, decodeSweepRequest, faultAttributionOf, faultCodeOf, isModelRouteCoolingDown, isModelRouteEligible, modelRouteReputationScore, modelRouteTotalPrice, normalizedModelReputationScore, peerSupportsCooperativeClose, isRoutingSelection, DEFAULT_BUYER_IDENTITY, rankModelRoutes, SellerHealthBook, orderAdaptiveRoutes, adaptiveFirstTokenTimeoutMs, sanitizePeerDisplayName, VIDEO_JOB_RETIRED_HEADER, } from '@antseed/node';
|
|
12
|
+
import { encodeServedSelection, ROUTE_SELECTION_HEADER } from './route-selection.js';
|
|
12
13
|
import { canonicalModelKey } from '@antseed/node/model-identity';
|
|
13
14
|
import { createStreamingAdapter, detectRequestServiceApiProtocol, extractRequestBodyFields, transformRequest, transformResponse, } from './service-api-adapter.js';
|
|
14
15
|
import { DEBUG, log, extractRequestedService, summarizeRequestShape, summarizeErrorResponse, requestWantsStreaming, parsePeerPinnedService, rewritePeerPinnedServiceInBody, substituteRoutedModelAlias, overrideRoutedModelInBody, ROUTED_MODEL_ALIAS, SYSTEM_PROXY_SOURCE_HEADER, SYSTEM_ROUTED_MODEL_HEADER, BUYER_IDENTITY_HEADER, MAX_VIDEO_COST_HEADER, normalizePeerId, takeRoutingOptions, } from './request-utils.js';
|
|
@@ -1322,7 +1323,9 @@ export class BuyerProxy {
|
|
|
1322
1323
|
now,
|
|
1323
1324
|
affinityKey: input.affinityKey ?? null,
|
|
1324
1325
|
...this._adaptiveRouting,
|
|
1325
|
-
|
|
1326
|
+
// The preview keeps the real sharing pool (so it can show who shares the
|
|
1327
|
+
// traffic) but always draws its first seller and never probes.
|
|
1328
|
+
...(input.deterministic ? { random: () => 0, explorationRate: 0 } : {}),
|
|
1326
1329
|
});
|
|
1327
1330
|
// Without routing preferences the buyer ranks by reputation, as before;
|
|
1328
1331
|
// health only moves sellers that fail often behind the others.
|
|
@@ -1351,6 +1354,7 @@ export class BuyerProxy {
|
|
|
1351
1354
|
// Conversation affinity is a soft preference: it only reorders sellers the
|
|
1352
1355
|
// policy already allows, and yields to an explicit per-model route.
|
|
1353
1356
|
const preferredPeerId = input.preferredPeerId;
|
|
1357
|
+
let conversationFirst = false;
|
|
1354
1358
|
if (preferredPeerId && !findModelRoute(effectivePolicy, requestedService)) {
|
|
1355
1359
|
const preferredIndex = candidates.findIndex((candidate) => (candidate.peer.peerId.toLowerCase() === preferredPeerId
|
|
1356
1360
|
&& !isCoolingDown(this._peerHealth.get(candidate.peer.peerId), now)
|
|
@@ -1360,7 +1364,17 @@ export class BuyerProxy {
|
|
|
1360
1364
|
if (preferred)
|
|
1361
1365
|
candidates.unshift(preferred);
|
|
1362
1366
|
}
|
|
1363
|
-
|
|
1367
|
+
conversationFirst = preferredIndex >= 0;
|
|
1368
|
+
}
|
|
1369
|
+
// How the eligible sellers were put in order. Only the buyer's adaptive
|
|
1370
|
+
// order, unsorted by the policy and with no route chain, spreads traffic.
|
|
1371
|
+
const policySort = effectivePolicy?.sort && effectivePolicy.sort !== 'balanced' ? effectivePolicy.sort : null;
|
|
1372
|
+
const selectionOrdering = policySort ?? (routingPreferences ? 'expected-cost' : 'trust');
|
|
1373
|
+
const spreads = selectionOrdering === 'expected-cost' && !chainPosition?.size;
|
|
1374
|
+
const pool = spreads ? adaptive.pool : [];
|
|
1375
|
+
const poolWeight = (candidate) => 1 / Math.max(healthOf(candidate).score, 1e-9) ** 2;
|
|
1376
|
+
const poolTotal = pool.reduce((sum, candidate) => sum + poolWeight(candidate), 0);
|
|
1377
|
+
const selections = new Map();
|
|
1364
1378
|
candidates.forEach((candidate, index) => {
|
|
1365
1379
|
const verdict = verdicts.get(candidate.peerId);
|
|
1366
1380
|
if (!verdict)
|
|
@@ -1375,8 +1389,8 @@ export class BuyerProxy {
|
|
|
1375
1389
|
verdict.reasons.push('cooling down (tried last)');
|
|
1376
1390
|
else if (health.unhealthy)
|
|
1377
1391
|
verdict.reasons.push(`failing lately: ${health.stats?.lastFailureReason ?? 'failed requests'} (tried after healthy sellers)`);
|
|
1378
|
-
if (
|
|
1379
|
-
verdict.reasons.push(`shares traffic with ${
|
|
1392
|
+
if (pool.length > 1 && pool.includes(candidate))
|
|
1393
|
+
verdict.reasons.push(`shares traffic with ${pool.length - 1} other seller(s)`);
|
|
1380
1394
|
if (modelRouteTotalPrice(candidate) === 0)
|
|
1381
1395
|
verdict.reasons.push('free');
|
|
1382
1396
|
verdict.reasons.push(`success ~${Math.round(health.successProbability * 100)}%${health.stats ? '' : ' (no data yet)'}`);
|
|
@@ -1386,8 +1400,49 @@ export class BuyerProxy {
|
|
|
1386
1400
|
const throughput = this._peerThroughput(candidate.peerId, candidate.serviceId);
|
|
1387
1401
|
if (throughput !== null)
|
|
1388
1402
|
verdict.reasons.push(`~${Math.round(throughput)} tok/s`);
|
|
1403
|
+
const healthKey = this._healthServiceKey(candidate.serviceId);
|
|
1404
|
+
const stats = health.stats;
|
|
1405
|
+
const inPool = pool.includes(candidate);
|
|
1406
|
+
const maxConcurrency = typeof candidate.peer.maxConcurrency === 'number' && candidate.peer.maxConcurrency > 0 ? candidate.peer.maxConcurrency : null;
|
|
1407
|
+
const selection = {
|
|
1408
|
+
expectedCostUsd: health.score,
|
|
1409
|
+
expectedPriceUsd: health.expectedPriceUsd,
|
|
1410
|
+
expectedSeconds: health.expectedSeconds,
|
|
1411
|
+
successRate: health.successProbability,
|
|
1412
|
+
observations: stats ? stats.successes + stats.failures : 0,
|
|
1413
|
+
record: !stats ? 'none' : this._sellerHealth.hasRecord(candidate.peerId, healthKey) ? 'model' : 'other-models',
|
|
1414
|
+
firstTokenMs: latency,
|
|
1415
|
+
tokensPerSecond: throughput,
|
|
1416
|
+
lastSuccessAt: stats && stats.lastSuccessAt > 0 ? stats.lastSuccessAt : null,
|
|
1417
|
+
lastFailureAt: stats && stats.lastFailureAt > 0 ? stats.lastFailureAt : null,
|
|
1418
|
+
lastFailureReason: stats?.lastFailureReason ?? null,
|
|
1419
|
+
failing: health.unhealthy,
|
|
1420
|
+
coolingDown: isCooling(candidate),
|
|
1421
|
+
stale: health.stale,
|
|
1422
|
+
ordering: selectionOrdering,
|
|
1423
|
+
sharesTraffic: inPool && pool.length > 1,
|
|
1424
|
+
trafficShare: inPool && pool.length > 1 && poolTotal > 0 ? poolWeight(candidate) / poolTotal : null,
|
|
1425
|
+
poolSize: pool.length,
|
|
1426
|
+
channel: modelRouteTotalPrice(candidate) === 0 ? 'free' : openChannels ? (openChannels.has(candidate.peerId.toLowerCase()) ? 'open' : 'new') : null,
|
|
1427
|
+
inFlight: this._inFlightBySeller.get(candidate.peerId.toLowerCase()) ?? 0,
|
|
1428
|
+
maxConcurrency,
|
|
1429
|
+
routeChainPosition: position !== undefined ? position + 1 : null,
|
|
1430
|
+
};
|
|
1431
|
+
verdict.selection = selection;
|
|
1432
|
+
selections.set(candidate, selection);
|
|
1389
1433
|
});
|
|
1390
|
-
|
|
1434
|
+
// How the first seller came to be first, for the request's record.
|
|
1435
|
+
const first = candidates[0];
|
|
1436
|
+
let firstPick = 'order';
|
|
1437
|
+
if (first) {
|
|
1438
|
+
if (first === adaptive.explored)
|
|
1439
|
+
firstPick = 'exploration';
|
|
1440
|
+
else if (conversationFirst || (input.affinityKey && pool.length > 1 && pool.includes(first) && first === adaptive.ordered[0]))
|
|
1441
|
+
firstPick = 'conversation';
|
|
1442
|
+
else if (first === adaptive.ordered[0] && pool.includes(first))
|
|
1443
|
+
firstPick = pool.length > 1 ? 'spread' : 'best';
|
|
1444
|
+
}
|
|
1445
|
+
return { candidates, verdicts: [...verdicts.values()], effectivePolicy, explored: adaptive.explored, selections, firstPick };
|
|
1391
1446
|
}
|
|
1392
1447
|
/**
|
|
1393
1448
|
* Without routing preferences: best effective reputation first, and only
|
|
@@ -1445,6 +1500,7 @@ export class BuyerProxy {
|
|
|
1445
1500
|
inputUsdPerMillion: verdict.offer.inputUsdPerMillion ?? null,
|
|
1446
1501
|
outputUsdPerMillion: verdict.offer.outputUsdPerMillion ?? null,
|
|
1447
1502
|
trustScore: normalizedModelReputationScore(verdict.peer),
|
|
1503
|
+
selection: modelAllowed && verdict.eligible ? verdict.selection ?? null : null,
|
|
1448
1504
|
}))
|
|
1449
1505
|
.sort((a, b) => (a.rank ?? Number.POSITIVE_INFINITY) - (b.rank ?? Number.POSITIVE_INFINITY)
|
|
1450
1506
|
|| a.peerId.localeCompare(b.peerId));
|
|
@@ -3771,7 +3827,7 @@ export class BuyerProxy {
|
|
|
3771
3827
|
({ candidatePeers: modelPeers, routePlanByPeerId: modelPlans } = selectModelPeers(discoveredPeers));
|
|
3772
3828
|
}
|
|
3773
3829
|
const router = this._node.router;
|
|
3774
|
-
const { candidates, effectivePolicy, explored } = this._planModelRoutes({
|
|
3830
|
+
const { candidates, effectivePolicy, explored, selections, firstPick } = this._planModelRoutes({
|
|
3775
3831
|
modelPeers,
|
|
3776
3832
|
modelPlans,
|
|
3777
3833
|
request: serializedReq,
|
|
@@ -3866,6 +3922,18 @@ export class BuyerProxy {
|
|
|
3866
3922
|
continue;
|
|
3867
3923
|
}
|
|
3868
3924
|
}
|
|
3925
|
+
// Why this seller serves the request, for the gateway's request log. A
|
|
3926
|
+
// seller that fails is replaced by the next one's before any header goes out.
|
|
3927
|
+
const selection = selections.get(selected);
|
|
3928
|
+
if (selection && !res.headersSent) {
|
|
3929
|
+
res.setHeader(ROUTE_SELECTION_HEADER, encodeServedSelection({
|
|
3930
|
+
...selection,
|
|
3931
|
+
rank: index + 1,
|
|
3932
|
+
of: candidates.length,
|
|
3933
|
+
attempt: index,
|
|
3934
|
+
pick: index === 0 ? firstPick : failedSellers.size > 0 || lastRetry !== null ? 'failover' : 'order',
|
|
3935
|
+
}));
|
|
3936
|
+
}
|
|
3869
3937
|
for (let peerAttempt = 0; peerAttempt < MODEL_RATE_LIMIT_MAX_ATTEMPTS_PER_PEER; peerAttempt += 1) {
|
|
3870
3938
|
log(`Auto-selected peer ${selected.peer.peerId.slice(0, 12)}... for model="${requestedService}" `
|
|
3871
3939
|
+ `service="${selected.serviceId}" reputation=${selected.reputation} `
|