ai.matey.patterns 0.2.0
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/dist/cjs/batch-processor.js +136 -0
- package/dist/cjs/batch-processor.js.map +1 -0
- package/dist/cjs/complexity-router.js +85 -0
- package/dist/cjs/complexity-router.js.map +1 -0
- package/dist/cjs/cost-optimizer.js +66 -0
- package/dist/cjs/cost-optimizer.js.map +1 -0
- package/dist/cjs/failover.js +55 -0
- package/dist/cjs/failover.js.map +1 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/parallel-aggregator.js +68 -0
- package/dist/cjs/parallel-aggregator.js.map +1 -0
- package/dist/esm/batch-processor.js +133 -0
- package/dist/esm/batch-processor.js.map +1 -0
- package/dist/esm/complexity-router.js +81 -0
- package/dist/esm/complexity-router.js.map +1 -0
- package/dist/esm/cost-optimizer.js +63 -0
- package/dist/esm/cost-optimizer.js.map +1 -0
- package/dist/esm/failover.js +52 -0
- package/dist/esm/failover.js.map +1 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/parallel-aggregator.js +65 -0
- package/dist/esm/parallel-aggregator.js.map +1 -0
- package/dist/types/batch-processor.d.ts +66 -0
- package/dist/types/batch-processor.d.ts.map +1 -0
- package/dist/types/complexity-router.d.ts +55 -0
- package/dist/types/complexity-router.d.ts.map +1 -0
- package/dist/types/cost-optimizer.d.ts +57 -0
- package/dist/types/cost-optimizer.d.ts.map +1 -0
- package/dist/types/failover.d.ts +40 -0
- package/dist/types/failover.d.ts.map +1 -0
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/parallel-aggregator.d.ts +45 -0
- package/dist/types/parallel-aggregator.d.ts.map +1 -0
- package/package.json +71 -0
- package/readme.md +54 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# ai.matey.patterns
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- aef9f4a: New `ai.matey.patterns` package: complexity routing, parallel aggregation, failover middleware,
|
|
8
|
+
cost optimization with budget windows, and batch processing. Router's `dispatchParallel` now
|
|
9
|
+
actually honors the `fastest` strategy (previously returned the first-registered success).
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [dae4d01]
|
|
14
|
+
- Updated dependencies [e7df1d0]
|
|
15
|
+
- Updated dependencies [f227db2]
|
|
16
|
+
- Updated dependencies [2912b7d]
|
|
17
|
+
- Updated dependencies [aef9f4a]
|
|
18
|
+
- Updated dependencies [78731bb]
|
|
19
|
+
- Updated dependencies [b7e2312]
|
|
20
|
+
- ai.matey.types@0.3.0
|
|
21
|
+
- ai.matey.core@0.3.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AI Matey Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Batch Processing with Rate Limiting
|
|
4
|
+
*
|
|
5
|
+
* High-throughput request processing with bounded concurrency, an optional
|
|
6
|
+
* token-bucket rate limit, retries, and progress reporting. Zero-dependency.
|
|
7
|
+
* Extracted from docs/PATTERNS.md §6.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createBatchProcessor = createBatchProcessor;
|
|
13
|
+
/**
|
|
14
|
+
* Create a batch processor.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const processor = createBatchProcessor({
|
|
19
|
+
* execute: (request) => bridge.chat(request),
|
|
20
|
+
* concurrency: 5,
|
|
21
|
+
* requestsPerSecond: 10,
|
|
22
|
+
* retries: 2,
|
|
23
|
+
* });
|
|
24
|
+
* const results = await processor.addAll(requests);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function createBatchProcessor(config) {
|
|
28
|
+
const concurrency = config.concurrency ?? 5;
|
|
29
|
+
const maxQueueSize = config.maxQueueSize ?? Infinity;
|
|
30
|
+
const retries = config.retries ?? 0;
|
|
31
|
+
const queue = [];
|
|
32
|
+
let inFlight = 0;
|
|
33
|
+
let completed = 0;
|
|
34
|
+
let failed = 0;
|
|
35
|
+
let disposed = false;
|
|
36
|
+
const drainWaiters = [];
|
|
37
|
+
// Token bucket (only when rate limiting is configured)
|
|
38
|
+
let tokens = config.requestsPerSecond ?? Infinity;
|
|
39
|
+
let refillTimer;
|
|
40
|
+
if (config.requestsPerSecond) {
|
|
41
|
+
const perTick = config.requestsPerSecond / 10;
|
|
42
|
+
refillTimer = setInterval(() => {
|
|
43
|
+
tokens = Math.min(config.requestsPerSecond, tokens + perTick);
|
|
44
|
+
pump();
|
|
45
|
+
}, 100);
|
|
46
|
+
// Do not hold the process open for an idle processor
|
|
47
|
+
if (typeof refillTimer === 'object' && 'unref' in refillTimer) {
|
|
48
|
+
refillTimer.unref();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const stats = () => ({
|
|
52
|
+
completed,
|
|
53
|
+
failed,
|
|
54
|
+
pending: queue.length,
|
|
55
|
+
inFlight,
|
|
56
|
+
});
|
|
57
|
+
const notifyDrainWaiters = () => {
|
|
58
|
+
if (queue.length === 0 && inFlight === 0) {
|
|
59
|
+
while (drainWaiters.length > 0) {
|
|
60
|
+
drainWaiters.shift()?.();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const runItem = (item) => {
|
|
65
|
+
inFlight++;
|
|
66
|
+
item.attempts++;
|
|
67
|
+
config
|
|
68
|
+
.execute(item.request)
|
|
69
|
+
.then((result) => {
|
|
70
|
+
completed++;
|
|
71
|
+
item.resolve(result);
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
if (item.attempts <= retries && !disposed) {
|
|
75
|
+
// Requeue for retry (does not consume a fresh queue slot check)
|
|
76
|
+
queue.push(item);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
failed++;
|
|
80
|
+
item.reject(error);
|
|
81
|
+
})
|
|
82
|
+
.finally(() => {
|
|
83
|
+
inFlight--;
|
|
84
|
+
config.onProgress?.(stats());
|
|
85
|
+
pump();
|
|
86
|
+
notifyDrainWaiters();
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const pump = () => {
|
|
90
|
+
while (!disposed && inFlight < concurrency && queue.length > 0 && tokens >= 1) {
|
|
91
|
+
if (config.requestsPerSecond) {
|
|
92
|
+
tokens -= 1;
|
|
93
|
+
}
|
|
94
|
+
const item = queue.shift();
|
|
95
|
+
if (item) {
|
|
96
|
+
runItem(item);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
add(request) {
|
|
102
|
+
if (disposed) {
|
|
103
|
+
return Promise.reject(new Error('Batch processor disposed'));
|
|
104
|
+
}
|
|
105
|
+
if (queue.length >= maxQueueSize) {
|
|
106
|
+
return Promise.reject(new Error(`Queue full (max ${maxQueueSize})`));
|
|
107
|
+
}
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
queue.push({ request, resolve, reject, attempts: 0 });
|
|
110
|
+
pump();
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
addAll(requests) {
|
|
114
|
+
return Promise.allSettled(requests.map((request) => this.add(request)));
|
|
115
|
+
},
|
|
116
|
+
drain() {
|
|
117
|
+
if (queue.length === 0 && inFlight === 0) {
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
}
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
drainWaiters.push(resolve);
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
stats,
|
|
125
|
+
dispose() {
|
|
126
|
+
disposed = true;
|
|
127
|
+
if (refillTimer) {
|
|
128
|
+
clearInterval(refillTimer);
|
|
129
|
+
}
|
|
130
|
+
while (queue.length > 0) {
|
|
131
|
+
queue.shift()?.reject(new Error('Batch processor disposed'));
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=batch-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-processor.js","sourceRoot":"","sources":["../../src/batch-processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Complexity-Based Routing
|
|
4
|
+
*
|
|
5
|
+
* Route requests to different backends by estimated query complexity —
|
|
6
|
+
* cheap/fast models for simple queries, capable models for hard ones.
|
|
7
|
+
* Extracted from docs/PATTERNS.md §1.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.defaultComplexityAnalyzer = defaultComplexityAnalyzer;
|
|
13
|
+
exports.createComplexityRouter = createComplexityRouter;
|
|
14
|
+
const ai_matey_core_1 = require("ai.matey.core");
|
|
15
|
+
/**
|
|
16
|
+
* Heuristic complexity score (0-100) from message length, question depth,
|
|
17
|
+
* and reasoning keywords.
|
|
18
|
+
*/
|
|
19
|
+
function defaultComplexityAnalyzer(request) {
|
|
20
|
+
const text = request.messages
|
|
21
|
+
.map((message) => typeof message.content === 'string'
|
|
22
|
+
? message.content
|
|
23
|
+
: message.content.map((block) => (block.type === 'text' ? block.text : '')).join(' '))
|
|
24
|
+
.join(' ');
|
|
25
|
+
let score = 0;
|
|
26
|
+
// Length: up to 40 points at ~2000 chars
|
|
27
|
+
score += Math.min(40, text.length / 50);
|
|
28
|
+
// Reasoning indicators: up to 40 points
|
|
29
|
+
const reasoningKeywords = [
|
|
30
|
+
'why',
|
|
31
|
+
'how',
|
|
32
|
+
'explain',
|
|
33
|
+
'analyze',
|
|
34
|
+
'compare',
|
|
35
|
+
'evaluate',
|
|
36
|
+
'design',
|
|
37
|
+
'architect',
|
|
38
|
+
'prove',
|
|
39
|
+
'derive',
|
|
40
|
+
'step by step',
|
|
41
|
+
'trade-off',
|
|
42
|
+
'tradeoff',
|
|
43
|
+
];
|
|
44
|
+
const lower = text.toLowerCase();
|
|
45
|
+
const hits = reasoningKeywords.filter((keyword) => lower.includes(keyword)).length;
|
|
46
|
+
score += Math.min(40, hits * 10);
|
|
47
|
+
// Code blocks / structured content: up to 20 points
|
|
48
|
+
if (lower.includes('```') || lower.includes('function ') || lower.includes('class ')) {
|
|
49
|
+
score += 20;
|
|
50
|
+
}
|
|
51
|
+
return Math.min(100, Math.round(score));
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a Router that picks its backend by query complexity.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const router = createComplexityRouter({
|
|
59
|
+
* tiers: [
|
|
60
|
+
* { backend: 'fast', maxComplexity: 30 },
|
|
61
|
+
* { backend: 'balanced', maxComplexity: 70 },
|
|
62
|
+
* { backend: 'powerful', maxComplexity: 100 },
|
|
63
|
+
* ],
|
|
64
|
+
* backends: { fast, balanced, powerful },
|
|
65
|
+
* });
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
function createComplexityRouter(config) {
|
|
69
|
+
const analyzer = config.analyzer ?? defaultComplexityAnalyzer;
|
|
70
|
+
const tiers = [...config.tiers].sort((a, b) => a.maxComplexity - b.maxComplexity);
|
|
71
|
+
const router = new ai_matey_core_1.Router({
|
|
72
|
+
...config.routerConfig,
|
|
73
|
+
routingStrategy: 'custom',
|
|
74
|
+
customRouter: (request, availableBackends) => {
|
|
75
|
+
const complexity = analyzer(request);
|
|
76
|
+
const tier = tiers.find((candidate) => complexity <= candidate.maxComplexity && availableBackends.includes(candidate.backend)) ?? tiers[tiers.length - 1];
|
|
77
|
+
return Promise.resolve(tier?.backend ?? null);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
for (const [name, adapter] of Object.entries(config.backends)) {
|
|
81
|
+
router.register(name, adapter);
|
|
82
|
+
}
|
|
83
|
+
return router;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=complexity-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity-router.js","sourceRoot":"","sources":["../../src/complexity-router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cost-Optimized Provider Selection
|
|
4
|
+
*
|
|
5
|
+
* A cost-optimizing Router plus a budget-window middleware: route to the
|
|
6
|
+
* cheapest capable backend and enforce a spending ceiling. Extracted from
|
|
7
|
+
* docs/PATTERNS.md §4.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createCostOptimizer = createCostOptimizer;
|
|
13
|
+
const ai_matey_core_1 = require("ai.matey.core");
|
|
14
|
+
const ai_matey_errors_1 = require("ai.matey.errors");
|
|
15
|
+
/**
|
|
16
|
+
* Create a cost-optimized router with an optional budget window.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const { router, middleware, recordSpend } = createCostOptimizer({
|
|
21
|
+
* backends: { cheap, premium },
|
|
22
|
+
* budget: { limitUSD: 10, windowMs: 3600_000 },
|
|
23
|
+
* });
|
|
24
|
+
* const bridge = new Bridge(frontend, router).use(middleware);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function createCostOptimizer(config) {
|
|
28
|
+
const router = new ai_matey_core_1.Router({
|
|
29
|
+
trackCost: true,
|
|
30
|
+
capabilityBasedRouting: true,
|
|
31
|
+
optimization: 'cost',
|
|
32
|
+
...config.routerConfig,
|
|
33
|
+
});
|
|
34
|
+
for (const [name, adapter] of Object.entries(config.backends)) {
|
|
35
|
+
router.register(name, adapter);
|
|
36
|
+
}
|
|
37
|
+
// Sliding-window spend ledger
|
|
38
|
+
const windowMs = config.budget?.windowMs ?? 3_600_000;
|
|
39
|
+
let entries = [];
|
|
40
|
+
const prune = () => {
|
|
41
|
+
const cutoff = Date.now() - windowMs;
|
|
42
|
+
entries = entries.filter((entry) => entry.at >= cutoff);
|
|
43
|
+
};
|
|
44
|
+
const getSpend = () => {
|
|
45
|
+
prune();
|
|
46
|
+
return entries.reduce((sum, entry) => sum + entry.costUSD, 0);
|
|
47
|
+
};
|
|
48
|
+
const recordSpend = (costUSD) => {
|
|
49
|
+
entries.push({ at: Date.now(), costUSD });
|
|
50
|
+
};
|
|
51
|
+
const middleware = async (_context, next) => {
|
|
52
|
+
if (config.budget && getSpend() >= config.budget.limitUSD) {
|
|
53
|
+
if ((config.budget.onExceeded ?? 'error') === 'error') {
|
|
54
|
+
throw new ai_matey_errors_1.AdapterError({
|
|
55
|
+
code: ai_matey_errors_1.ErrorCode.RATE_LIMIT_EXCEEDED,
|
|
56
|
+
message: `Budget of $${config.budget.limitUSD} exceeded for the current window`,
|
|
57
|
+
isRetryable: true,
|
|
58
|
+
provenance: { router: router.metadata.name },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
64
|
+
return { router, middleware, getSpend, recordSpend };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=cost-optimizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-optimizer.js","sourceRoot":"","sources":["../../src/cost-optimizer.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAyDH,kDA6CC;AApGD,iDAAuC;AACvC,qDAA0D;AA0C1D;;;;;;;;;;;GAWG;AACH,SAAgB,mBAAmB,CAAC,MAA2B;IAC7D,MAAM,MAAM,GAAG,IAAI,sBAAM,CAAC;QACxB,SAAS,EAAE,IAAI;QACf,sBAAsB,EAAE,IAAI;QAC5B,YAAY,EAAE,MAAM;QACpB,GAAG,MAAM,CAAC,YAAY;KACvB,CAAC,CAAC;IAEH,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9D,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,8BAA8B;IAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,IAAI,SAAS,CAAC;IACtD,IAAI,OAAO,GAA2C,EAAE,CAAC;IAEzD,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC;QACrC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,MAAM,CAAC,CAAC;IAC1D,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,GAAW,EAAE;QAC5B,KAAK,EAAE,CAAC;QACR,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,OAAe,EAAQ,EAAE;QAC5C,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,MAAM,UAAU,GAAe,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;QACtD,IAAI,MAAM,CAAC,MAAM,IAAI,QAAQ,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,IAAI,OAAO,CAAC,KAAK,OAAO,EAAE,CAAC;gBACtD,MAAM,IAAI,8BAAY,CAAC;oBACrB,IAAI,EAAE,2BAAS,CAAC,mBAAmB;oBACnC,OAAO,EAAE,cAAc,MAAM,CAAC,MAAM,CAAC,QAAQ,kCAAkC;oBAC/E,WAAW,EAAE,IAAI;oBACjB,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;iBAC7C,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC,CAAC;IAEF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Automatic Failover Middleware
|
|
4
|
+
*
|
|
5
|
+
* Bridge-level failover: when the primary backend fails, retry the request
|
|
6
|
+
* against fallback adapters in order. For Router users, prefer the Router's
|
|
7
|
+
* built-in fallback chains — this pattern serves plain-Bridge setups.
|
|
8
|
+
* Extracted from docs/PATTERNS.md §3.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.createFailoverMiddleware = createFailoverMiddleware;
|
|
14
|
+
const ai_matey_errors_1 = require("ai.matey.errors");
|
|
15
|
+
/**
|
|
16
|
+
* Create failover middleware for a Bridge.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* bridge.use(createFailoverMiddleware({
|
|
21
|
+
* fallbacks: [new AnthropicBackendAdapter({ apiKey }), new GroqBackendAdapter({ apiKey })],
|
|
22
|
+
* }));
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function createFailoverMiddleware(config) {
|
|
26
|
+
const shouldFailover = config.shouldFailover ??
|
|
27
|
+
((error) => (error instanceof ai_matey_errors_1.AdapterError ? error.isRetryable : true));
|
|
28
|
+
return async (context, next) => {
|
|
29
|
+
try {
|
|
30
|
+
return await next();
|
|
31
|
+
}
|
|
32
|
+
catch (primaryError) {
|
|
33
|
+
const error = primaryError instanceof Error ? primaryError : new Error(String(primaryError));
|
|
34
|
+
if (!shouldFailover(error)) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
let lastError = error;
|
|
38
|
+
for (const fallback of config.fallbacks) {
|
|
39
|
+
config.onFailover?.({ to: fallback.metadata.name, error: lastError });
|
|
40
|
+
try {
|
|
41
|
+
return await fallback.execute(context.request, context.signal);
|
|
42
|
+
}
|
|
43
|
+
catch (fallbackError) {
|
|
44
|
+
lastError =
|
|
45
|
+
fallbackError instanceof Error ? fallbackError : new Error(String(fallbackError));
|
|
46
|
+
if (!shouldFailover(lastError)) {
|
|
47
|
+
throw lastError;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
throw lastError;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=failover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover.js","sourceRoot":"","sources":["../../src/failover.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAgCH,4DAgCC;AA9DD,qDAA+C;AAoB/C;;;;;;;;;GASG;AACH,SAAgB,wBAAwB,CAAC,MAAsB;IAC7D,MAAM,cAAc,GAClB,MAAM,CAAC,cAAc;QACrB,CAAC,CAAC,KAAY,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,8BAAY,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEjF,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,YAAY,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,YAAY,YAAY,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;YAE7F,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC;YACd,CAAC;YAED,IAAI,SAAS,GAAU,KAAK,CAAC;YAC7B,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACxC,MAAM,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;gBACtE,IAAI,CAAC;oBACH,OAAO,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;gBACjE,CAAC;gBAAC,OAAO,aAAa,EAAE,CAAC;oBACvB,SAAS;wBACP,aAAa,YAAY,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;oBACpF,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;wBAC/B,MAAM,SAAS,CAAC;oBAClB,CAAC;gBACH,CAAC;YACH,CAAC;YAED,MAAM,SAAS,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ai.matey.patterns
|
|
4
|
+
*
|
|
5
|
+
* Production integration patterns, extracted from the validated pattern
|
|
6
|
+
* library (docs/PATTERNS.md) into importable utilities.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.createBatchProcessor = exports.createCostOptimizer = exports.createFailoverMiddleware = exports.createParallelAggregator = exports.defaultComplexityAnalyzer = exports.createComplexityRouter = void 0;
|
|
12
|
+
var complexity_router_js_1 = require("./complexity-router.js");
|
|
13
|
+
Object.defineProperty(exports, "createComplexityRouter", { enumerable: true, get: function () { return complexity_router_js_1.createComplexityRouter; } });
|
|
14
|
+
Object.defineProperty(exports, "defaultComplexityAnalyzer", { enumerable: true, get: function () { return complexity_router_js_1.defaultComplexityAnalyzer; } });
|
|
15
|
+
var parallel_aggregator_js_1 = require("./parallel-aggregator.js");
|
|
16
|
+
Object.defineProperty(exports, "createParallelAggregator", { enumerable: true, get: function () { return parallel_aggregator_js_1.createParallelAggregator; } });
|
|
17
|
+
var failover_js_1 = require("./failover.js");
|
|
18
|
+
Object.defineProperty(exports, "createFailoverMiddleware", { enumerable: true, get: function () { return failover_js_1.createFailoverMiddleware; } });
|
|
19
|
+
var cost_optimizer_js_1 = require("./cost-optimizer.js");
|
|
20
|
+
Object.defineProperty(exports, "createCostOptimizer", { enumerable: true, get: function () { return cost_optimizer_js_1.createCostOptimizer; } });
|
|
21
|
+
var batch_processor_js_1 = require("./batch-processor.js");
|
|
22
|
+
Object.defineProperty(exports, "createBatchProcessor", { enumerable: true, get: function () { return batch_processor_js_1.createBatchProcessor; } });
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,+DAKgC;AAJ9B,8HAAA,sBAAsB,OAAA;AACtB,iIAAA,yBAAyB,OAAA;AAK3B,mEAIkC;AAHhC,kIAAA,wBAAwB,OAAA;AAK1B,6CAA8E;AAArE,uHAAA,wBAAwB,OAAA;AAEjC,yDAI6B;AAH3B,wHAAA,mBAAmB,OAAA;AAKrB,2DAK8B;AAJ5B,0HAAA,oBAAoB,OAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Parallel Provider Aggregation
|
|
4
|
+
*
|
|
5
|
+
* Query several backends at once and aggregate: fastest-wins, all-results,
|
|
6
|
+
* or a custom judge. The aggregator is itself a BackendAdapter, so it plugs
|
|
7
|
+
* into a Bridge like any single backend. Extracted from docs/PATTERNS.md §2.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.createParallelAggregator = createParallelAggregator;
|
|
13
|
+
const ai_matey_core_1 = require("ai.matey.core");
|
|
14
|
+
/**
|
|
15
|
+
* Create a BackendAdapter that fans requests out to all configured
|
|
16
|
+
* backends in parallel.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const consensus = createParallelAggregator({
|
|
21
|
+
* backends: { openai, anthropic, gemini },
|
|
22
|
+
* strategy: (results) => results[0].response, // custom judge
|
|
23
|
+
* });
|
|
24
|
+
* const bridge = new Bridge(new OpenAIFrontendAdapter(), consensus);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function createParallelAggregator(config) {
|
|
28
|
+
const strategy = config.strategy ?? 'fastest';
|
|
29
|
+
const router = new ai_matey_core_1.Router();
|
|
30
|
+
for (const [name, adapter] of Object.entries(config.backends)) {
|
|
31
|
+
router.register(name, adapter);
|
|
32
|
+
}
|
|
33
|
+
const metadata = {
|
|
34
|
+
name: 'parallel-aggregator',
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
provider: 'ai.matey.patterns',
|
|
37
|
+
capabilities: {
|
|
38
|
+
streaming: false,
|
|
39
|
+
multiModal: true,
|
|
40
|
+
tools: true,
|
|
41
|
+
systemMessageStrategy: 'in-messages',
|
|
42
|
+
supportsMultipleSystemMessages: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
metadata,
|
|
47
|
+
fromIR: (request) => request,
|
|
48
|
+
toIR: () => {
|
|
49
|
+
throw new Error('toIR() not applicable for parallel aggregator');
|
|
50
|
+
},
|
|
51
|
+
async execute(request, signal) {
|
|
52
|
+
if (typeof strategy === 'function') {
|
|
53
|
+
const result = await router.dispatchParallel(request, { strategy: 'all', timeout: config.timeout }, signal);
|
|
54
|
+
return strategy([...(result.allResponses ?? [])]);
|
|
55
|
+
}
|
|
56
|
+
const result = await router.dispatchParallel(request, {
|
|
57
|
+
strategy: strategy === 'fastest' ? 'fastest' : 'all',
|
|
58
|
+
timeout: config.timeout,
|
|
59
|
+
}, signal);
|
|
60
|
+
return result.response;
|
|
61
|
+
},
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/require-await, require-yield -- adapter interface requires an async generator; aggregation cannot stream
|
|
63
|
+
async *executeStream() {
|
|
64
|
+
throw new Error('Parallel aggregation does not support streaming; use a single backend for streams');
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=parallel-aggregator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel-aggregator.js","sourceRoot":"","sources":["../../src/parallel-aggregator.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAiDH,4DAyDC;AAxGD,iDAAuC;AAkCvC;;;;;;;;;;;;GAYG;AACH,SAAgB,wBAAwB,CAAC,MAAgC;IACvE,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC;IAE9C,MAAM,MAAM,GAAG,IAAI,sBAAM,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9D,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,QAAQ,GAAoB;QAChC,IAAI,EAAE,qBAAqB;QAC3B,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,mBAAmB;QAC7B,YAAY,EAAE;YACZ,SAAS,EAAE,KAAK;YAChB,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,IAAI;YACX,qBAAqB,EAAE,aAAa;YACpC,8BAA8B,EAAE,IAAI;SACrC;KACF,CAAC;IAEF,OAAO;QACL,QAAQ;QAER,MAAM,EAAE,CAAC,OAAsB,EAAE,EAAE,CAAC,OAAO;QAC3C,IAAI,EAAE,GAAG,EAAE;YACT,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,OAAsB,EAAE,MAAoB;YACxD,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;gBACnC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAC1C,OAAO,EACP,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,EAC5C,MAAM,CACP,CAAC;gBACF,OAAO,QAAQ,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACpD,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAC1C,OAAO,EACP;gBACE,QAAQ,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK;gBACpD,OAAO,EAAE,MAAM,CAAC,OAAO;aACxB,EACD,MAAM,CACP,CAAC;YACF,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;QAED,uJAAuJ;QACvJ,KAAK,CAAC,CAAC,aAAa;YAClB,MAAM,IAAI,KAAK,CACb,mFAAmF,CACpF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Processing with Rate Limiting
|
|
3
|
+
*
|
|
4
|
+
* High-throughput request processing with bounded concurrency, an optional
|
|
5
|
+
* token-bucket rate limit, retries, and progress reporting. Zero-dependency.
|
|
6
|
+
* Extracted from docs/PATTERNS.md §6.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Create a batch processor.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const processor = createBatchProcessor({
|
|
16
|
+
* execute: (request) => bridge.chat(request),
|
|
17
|
+
* concurrency: 5,
|
|
18
|
+
* requestsPerSecond: 10,
|
|
19
|
+
* retries: 2,
|
|
20
|
+
* });
|
|
21
|
+
* const results = await processor.addAll(requests);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function createBatchProcessor(config) {
|
|
25
|
+
const concurrency = config.concurrency ?? 5;
|
|
26
|
+
const maxQueueSize = config.maxQueueSize ?? Infinity;
|
|
27
|
+
const retries = config.retries ?? 0;
|
|
28
|
+
const queue = [];
|
|
29
|
+
let inFlight = 0;
|
|
30
|
+
let completed = 0;
|
|
31
|
+
let failed = 0;
|
|
32
|
+
let disposed = false;
|
|
33
|
+
const drainWaiters = [];
|
|
34
|
+
// Token bucket (only when rate limiting is configured)
|
|
35
|
+
let tokens = config.requestsPerSecond ?? Infinity;
|
|
36
|
+
let refillTimer;
|
|
37
|
+
if (config.requestsPerSecond) {
|
|
38
|
+
const perTick = config.requestsPerSecond / 10;
|
|
39
|
+
refillTimer = setInterval(() => {
|
|
40
|
+
tokens = Math.min(config.requestsPerSecond, tokens + perTick);
|
|
41
|
+
pump();
|
|
42
|
+
}, 100);
|
|
43
|
+
// Do not hold the process open for an idle processor
|
|
44
|
+
if (typeof refillTimer === 'object' && 'unref' in refillTimer) {
|
|
45
|
+
refillTimer.unref();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const stats = () => ({
|
|
49
|
+
completed,
|
|
50
|
+
failed,
|
|
51
|
+
pending: queue.length,
|
|
52
|
+
inFlight,
|
|
53
|
+
});
|
|
54
|
+
const notifyDrainWaiters = () => {
|
|
55
|
+
if (queue.length === 0 && inFlight === 0) {
|
|
56
|
+
while (drainWaiters.length > 0) {
|
|
57
|
+
drainWaiters.shift()?.();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const runItem = (item) => {
|
|
62
|
+
inFlight++;
|
|
63
|
+
item.attempts++;
|
|
64
|
+
config
|
|
65
|
+
.execute(item.request)
|
|
66
|
+
.then((result) => {
|
|
67
|
+
completed++;
|
|
68
|
+
item.resolve(result);
|
|
69
|
+
})
|
|
70
|
+
.catch((error) => {
|
|
71
|
+
if (item.attempts <= retries && !disposed) {
|
|
72
|
+
// Requeue for retry (does not consume a fresh queue slot check)
|
|
73
|
+
queue.push(item);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
failed++;
|
|
77
|
+
item.reject(error);
|
|
78
|
+
})
|
|
79
|
+
.finally(() => {
|
|
80
|
+
inFlight--;
|
|
81
|
+
config.onProgress?.(stats());
|
|
82
|
+
pump();
|
|
83
|
+
notifyDrainWaiters();
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const pump = () => {
|
|
87
|
+
while (!disposed && inFlight < concurrency && queue.length > 0 && tokens >= 1) {
|
|
88
|
+
if (config.requestsPerSecond) {
|
|
89
|
+
tokens -= 1;
|
|
90
|
+
}
|
|
91
|
+
const item = queue.shift();
|
|
92
|
+
if (item) {
|
|
93
|
+
runItem(item);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
add(request) {
|
|
99
|
+
if (disposed) {
|
|
100
|
+
return Promise.reject(new Error('Batch processor disposed'));
|
|
101
|
+
}
|
|
102
|
+
if (queue.length >= maxQueueSize) {
|
|
103
|
+
return Promise.reject(new Error(`Queue full (max ${maxQueueSize})`));
|
|
104
|
+
}
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
queue.push({ request, resolve, reject, attempts: 0 });
|
|
107
|
+
pump();
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
addAll(requests) {
|
|
111
|
+
return Promise.allSettled(requests.map((request) => this.add(request)));
|
|
112
|
+
},
|
|
113
|
+
drain() {
|
|
114
|
+
if (queue.length === 0 && inFlight === 0) {
|
|
115
|
+
return Promise.resolve();
|
|
116
|
+
}
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
drainWaiters.push(resolve);
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
stats,
|
|
122
|
+
dispose() {
|
|
123
|
+
disposed = true;
|
|
124
|
+
if (refillTimer) {
|
|
125
|
+
clearInterval(refillTimer);
|
|
126
|
+
}
|
|
127
|
+
while (queue.length > 0) {
|
|
128
|
+
queue.shift()?.reject(new Error('Batch processor disposed'));
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=batch-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-processor.js","sourceRoot":"","sources":["../../src/batch-processor.ts"],"names":[],"mappings":"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"}
|