@animus-labs/cortex 0.2.2 → 0.2.4
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/cortex-agent.d.ts +1 -0
- package/dist/cortex-agent.d.ts.map +1 -1
- package/dist/cortex-agent.js +29 -9
- package/dist/cortex-agent.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/provider-manager.d.ts +39 -3
- package/dist/provider-manager.d.ts.map +1 -1
- package/dist/provider-manager.js +187 -56
- package/dist/provider-manager.js.map +1 -1
- package/dist/provider-registry.d.ts +7 -9
- package/dist/provider-registry.d.ts.map +1 -1
- package/dist/provider-registry.js +11 -19
- package/dist/provider-registry.js.map +1 -1
- package/dist/utility-model-inference.d.ts +5 -0
- package/dist/utility-model-inference.d.ts.map +1 -0
- package/dist/utility-model-inference.js +174 -0
- package/dist/utility-model-inference.js.map +1 -0
- package/package.json +1 -1
- package/src/cortex-agent.ts +28 -9
- package/src/index.ts +4 -1
- package/src/provider-manager.ts +266 -64
- package/src/provider-registry.ts +12 -19
- package/src/utility-model-inference.ts +203 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
type RawModel = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
const MIN_UTILITY_CONTEXT_WINDOW = 32_000;
|
|
4
|
+
|
|
5
|
+
const SPECIAL_PURPOSE_MODEL_PATTERN = /(?:embedding|embed|rerank|moderation|whisper|tts|audio|speech|image-generation|vision|live|deep-research|safety|safeguard|guard|search|transcrib)/i;
|
|
6
|
+
|
|
7
|
+
const UTILITY_TERMS: Array<{ pattern: RegExp; score: number }> = [
|
|
8
|
+
{ pattern: /(?:^|[\s._/-])flash[\s._/-]?lite(?:$|[\s._/-])/, score: 110 },
|
|
9
|
+
{ pattern: /(?:^|[\s._/-])nano(?:$|[\s._/-])/, score: 100 },
|
|
10
|
+
{ pattern: /(?:^|[\s._/-])mini(?:$|[\s._/-])/, score: 95 },
|
|
11
|
+
{ pattern: /(?:^|[\s._/-])haiku(?:$|[\s._/-])/, score: 95 },
|
|
12
|
+
{ pattern: /(?:^|[\s._/-])small(?:$|[\s._/-])/, score: 85 },
|
|
13
|
+
{ pattern: /(?:^|[\s._/-])fast(?:$|[\s._/-])/, score: 80 },
|
|
14
|
+
{ pattern: /(?:^|[\s._/-])spark(?:$|[\s._/-])/, score: 80 },
|
|
15
|
+
{ pattern: /(?:^|[\s._/-])instant(?:$|[\s._/-])/, score: 75 },
|
|
16
|
+
{ pattern: /(?:^|[\s._/-])lite(?:$|[\s._/-])/, score: 70 },
|
|
17
|
+
{ pattern: /(?:^|[\s._/-])flash(?:$|[\s._/-])/, score: 65 },
|
|
18
|
+
{ pattern: /(?:^|[\s._/-])(?:7|8)b(?:$|[\s._/-])/, score: 65 },
|
|
19
|
+
{ pattern: /(?:^|[\s._/-])(?:12|20)b(?:$|[\s._/-])/, score: 45 },
|
|
20
|
+
{ pattern: /(?:^|[\s._/-])32b(?:$|[\s._/-])/, score: 25 },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
interface UtilityCandidate {
|
|
24
|
+
model: RawModel;
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
utilityScore: number;
|
|
28
|
+
recencyScore: number;
|
|
29
|
+
costScore: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function inferUtilityModel(models: readonly RawModel[] | null | undefined): RawModel | null {
|
|
33
|
+
if (!Array.isArray(models)) return null;
|
|
34
|
+
|
|
35
|
+
const capable = models
|
|
36
|
+
.map(toUtilityCandidate)
|
|
37
|
+
.filter((candidate): candidate is UtilityCandidate => candidate !== null);
|
|
38
|
+
|
|
39
|
+
const utilityCandidates = capable.filter(candidate => candidate.utilityScore > 0);
|
|
40
|
+
if (utilityCandidates.length > 0) {
|
|
41
|
+
return [...utilityCandidates].sort(compareUtilityCandidates)[0]!.model;
|
|
42
|
+
}
|
|
43
|
+
if (capable.length === 0) return null;
|
|
44
|
+
|
|
45
|
+
return [...capable].sort(compareFallbackCandidates)[0]!.model;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function inferUtilityModelId(models: readonly RawModel[] | null | undefined): string | null {
|
|
49
|
+
const model = inferUtilityModel(models);
|
|
50
|
+
const id = model?.['id'];
|
|
51
|
+
if (typeof id === 'string') return id;
|
|
52
|
+
const name = model?.['name'];
|
|
53
|
+
return typeof name === 'string' ? name : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toUtilityCandidate(model: RawModel): UtilityCandidate | null {
|
|
57
|
+
const id = getString(model['id']) ?? getString(model['name']);
|
|
58
|
+
if (!id) return null;
|
|
59
|
+
|
|
60
|
+
const name = getString(model['name']) ?? id;
|
|
61
|
+
const searchable = `${id} ${name}`.toLowerCase();
|
|
62
|
+
if (SPECIAL_PURPOSE_MODEL_PATTERN.test(searchable)) return null;
|
|
63
|
+
|
|
64
|
+
if (!supportsText(model)) return null;
|
|
65
|
+
|
|
66
|
+
const contextWindow = getNumber(model['contextWindow']) ?? 0;
|
|
67
|
+
if (contextWindow > 0 && contextWindow < MIN_UTILITY_CONTEXT_WINDOW) return null;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
model,
|
|
71
|
+
id,
|
|
72
|
+
name,
|
|
73
|
+
utilityScore: inferUtilityScore(searchable),
|
|
74
|
+
recencyScore: inferRecencyScore(searchable),
|
|
75
|
+
costScore: inferCostScore(model),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function compareUtilityCandidates(a: UtilityCandidate, b: UtilityCandidate): number {
|
|
80
|
+
if (b.recencyScore !== a.recencyScore) return b.recencyScore - a.recencyScore;
|
|
81
|
+
if (a.costScore !== b.costScore) return a.costScore - b.costScore;
|
|
82
|
+
if (b.utilityScore !== a.utilityScore) return b.utilityScore - a.utilityScore;
|
|
83
|
+
return a.id.localeCompare(b.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function compareFallbackCandidates(a: UtilityCandidate, b: UtilityCandidate): number {
|
|
87
|
+
if (a.costScore !== b.costScore) return a.costScore - b.costScore;
|
|
88
|
+
if (b.recencyScore !== a.recencyScore) return b.recencyScore - a.recencyScore;
|
|
89
|
+
return a.id.localeCompare(b.id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function supportsText(model: RawModel): boolean {
|
|
93
|
+
const input = model['input'];
|
|
94
|
+
if (!Array.isArray(input)) return true;
|
|
95
|
+
return input.includes('text');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function inferUtilityScore(searchable: string): number {
|
|
99
|
+
let score = 0;
|
|
100
|
+
for (const term of UTILITY_TERMS) {
|
|
101
|
+
if (term.pattern.test(searchable)) {
|
|
102
|
+
score = Math.max(score, term.score);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return score;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function inferRecencyScore(searchable: string): number {
|
|
109
|
+
const dateScore = inferDateScore(searchable);
|
|
110
|
+
const versionScore = inferVersionScore(searchable);
|
|
111
|
+
return Math.max(dateScore, versionScore);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function inferDateScore(searchable: string): number {
|
|
115
|
+
let score = 0;
|
|
116
|
+
|
|
117
|
+
for (const match of searchable.matchAll(/20\d{6}/g)) {
|
|
118
|
+
const value = Number(match[0]);
|
|
119
|
+
if (isValidDateScore(value)) {
|
|
120
|
+
score = Math.max(score, value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const match of searchable.matchAll(/(20\d{2})[-_/](0[1-9]|1[0-2])/g)) {
|
|
125
|
+
score = Math.max(score, Number(match[1]) * 10_000 + Number(match[2]) * 100);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const match of searchable.matchAll(/(0[1-9]|1[0-2])[-_/](20\d{2})/g)) {
|
|
129
|
+
score = Math.max(score, Number(match[2]) * 10_000 + Number(match[1]) * 100);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const match of searchable.matchAll(/(?:^|[^\d])(\d{4})(?:$|[^\da-z])/g)) {
|
|
133
|
+
const raw = match[1]!;
|
|
134
|
+
const year = Number(raw.slice(0, 2));
|
|
135
|
+
const month = Number(raw.slice(2, 4));
|
|
136
|
+
if (year >= 20 && year <= 40 && month >= 1 && month <= 12) {
|
|
137
|
+
score = Math.max(score, 20_000_000 + year * 10_000 + month * 100);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return score;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function inferVersionScore(searchable: string): number {
|
|
145
|
+
const scrubbed = searchable
|
|
146
|
+
.replace(/20\d{6}/g, ' ')
|
|
147
|
+
.replace(/(20\d{2})[-_/](0[1-9]|1[0-2])/g, ' ')
|
|
148
|
+
.replace(/(0[1-9]|1[0-2])[-_/](20\d{2})/g, ' ')
|
|
149
|
+
.replace(/(?:^|[^\d])(\d{4})(?:$|[^\da-z])/g, ' ')
|
|
150
|
+
.replace(/\b\d+(?:\.\d+)?b\b/g, ' ');
|
|
151
|
+
|
|
152
|
+
let score = 0;
|
|
153
|
+
for (const match of scrubbed.matchAll(/\d+(?:\.\d+)+/g)) {
|
|
154
|
+
score = Math.max(score, scoreVersionParts(match[0]!.split('.').map(Number)));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const tokens = scrubbed.split(/[^a-z0-9]+/).filter(Boolean);
|
|
158
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
159
|
+
if (!/^\d+$/.test(tokens[i]!)) continue;
|
|
160
|
+
const parts: number[] = [];
|
|
161
|
+
for (let j = i; j < tokens.length && /^\d+$/.test(tokens[j]!) && parts.length < 4; j++) {
|
|
162
|
+
parts.push(Number(tokens[j]));
|
|
163
|
+
}
|
|
164
|
+
if (parts.length >= 2) {
|
|
165
|
+
score = Math.max(score, scoreVersionParts(parts));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return score;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function scoreVersionParts(parts: number[]): number {
|
|
173
|
+
const weights = [1_000_000, 10_000, 100, 1];
|
|
174
|
+
return parts.slice(0, weights.length).reduce((score, part, index) => (
|
|
175
|
+
Number.isFinite(part) ? score + part * weights[index]! : score
|
|
176
|
+
), 0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function inferCostScore(model: RawModel): number {
|
|
180
|
+
const cost = model['cost'] ?? model['pricing'];
|
|
181
|
+
if (!cost || typeof cost !== 'object') return Number.MAX_SAFE_INTEGER;
|
|
182
|
+
|
|
183
|
+
const rawCost = cost as RawModel;
|
|
184
|
+
const input = getNumber(rawCost['input']) ?? 0;
|
|
185
|
+
const output = getNumber(rawCost['output']) ?? 0;
|
|
186
|
+
if (input === 0 && output === 0) return Number.MAX_SAFE_INTEGER - 1;
|
|
187
|
+
return input + output * 3;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function isValidDateScore(value: number): boolean {
|
|
191
|
+
const year = Math.floor(value / 10_000);
|
|
192
|
+
const month = Math.floor((value % 10_000) / 100);
|
|
193
|
+
const day = value % 100;
|
|
194
|
+
return year >= 2020 && year <= 2040 && month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getString(value: unknown): string | null {
|
|
198
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getNumber(value: unknown): number | null {
|
|
202
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
203
|
+
}
|