avantgate 1.0.0 → 1.1.1
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/README.md +580 -287
- package/dist/agent/index.d.mts +704 -0
- package/dist/agent/index.d.ts +704 -0
- package/dist/agent/index.js +1724 -0
- package/dist/agent/index.mjs +1623 -0
- package/dist/chunk-CO26LNFD.mjs +521 -0
- package/dist/chunk-DVCF4CSV.mjs +56 -0
- package/dist/finance/index.d.mts +76 -0
- package/dist/finance/index.d.ts +76 -0
- package/dist/finance/index.js +536 -0
- package/dist/finance/index.mjs +20 -0
- package/dist/index.d.mts +215 -9
- package/dist/index.d.ts +215 -9
- package/dist/index.js +1356 -59
- package/dist/index.mjs +845 -113
- package/dist/strategy.interface-CB4_ZAuk.d.mts +16 -0
- package/dist/strategy.interface-CB4_ZAuk.d.ts +16 -0
- package/package.json +82 -55
package/dist/index.mjs
CHANGED
|
@@ -1,78 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
sanitizePII
|
|
3
|
+
} from "./chunk-DVCF4CSV.mjs";
|
|
4
|
+
import {
|
|
5
|
+
extractAndCleanJSON,
|
|
6
|
+
validateWithZod
|
|
7
|
+
} from "./chunk-CO26LNFD.mjs";
|
|
8
|
+
|
|
9
|
+
// src/types.ts
|
|
10
|
+
var ConfigurationError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ConfigurationError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var BudgetExceededError = class extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "BudgetExceededError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
1
23
|
// src/pricing.ts
|
|
2
|
-
var
|
|
3
|
-
// DeepSeek
|
|
4
|
-
"deepseek-chat": {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
var SEED_MODEL_PRICES = {
|
|
25
|
+
// DeepSeek Direct
|
|
26
|
+
"deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28, cacheHitUSDPerMillion: 0.014 },
|
|
27
|
+
"deepseek-reasoner": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19, cacheHitUSDPerMillion: 0.14 },
|
|
28
|
+
"deepseek/deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28, cacheHitUSDPerMillion: 0.014 },
|
|
29
|
+
"deepseek/deepseek-reasoner": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19, cacheHitUSDPerMillion: 0.14 },
|
|
30
|
+
// Mistral AI Direct
|
|
31
|
+
"mistral-small-latest": { promptUSDPerMillion: 0.2, completionUSDPerMillion: 0.6 },
|
|
32
|
+
"mistral-large-latest": { promptUSDPerMillion: 2, completionUSDPerMillion: 6 },
|
|
33
|
+
"mistral/mistral-small-latest": { promptUSDPerMillion: 0.2, completionUSDPerMillion: 0.6 },
|
|
34
|
+
"mistral/mistral-large-latest": { promptUSDPerMillion: 2, completionUSDPerMillion: 6 },
|
|
35
|
+
// OpenAI Direct
|
|
36
|
+
"gpt-4o-mini": { promptUSDPerMillion: 0.15, completionUSDPerMillion: 0.6 },
|
|
37
|
+
"gpt-4o": { promptUSDPerMillion: 2.5, completionUSDPerMillion: 10 },
|
|
38
|
+
"openai/gpt-4o-mini": { promptUSDPerMillion: 0.15, completionUSDPerMillion: 0.6 },
|
|
39
|
+
"openai/gpt-4o": { promptUSDPerMillion: 2.5, completionUSDPerMillion: 10 },
|
|
40
|
+
// OpenRouter Direct
|
|
41
|
+
"openrouter/deepseek/deepseek-chat": { promptUSDPerMillion: 0.14, completionUSDPerMillion: 0.28 },
|
|
42
|
+
"openrouter/deepseek/deepseek-r1": { promptUSDPerMillion: 0.55, completionUSDPerMillion: 2.19 },
|
|
43
|
+
"openrouter/anthropic/claude-3.5-sonnet": { promptUSDPerMillion: 3, completionUSDPerMillion: 15 },
|
|
44
|
+
// Local Ollama (Toujours $0)
|
|
45
|
+
"ollama": { promptUSDPerMillion: 0, completionUSDPerMillion: 0 }
|
|
46
|
+
};
|
|
47
|
+
function normalizeKey(identifier) {
|
|
48
|
+
return identifier.trim().toLowerCase().replace(":", "/");
|
|
49
|
+
}
|
|
50
|
+
var CachedPricingAdapter = class {
|
|
51
|
+
cache = /* @__PURE__ */ new Map();
|
|
52
|
+
ttlMs;
|
|
53
|
+
delegate;
|
|
54
|
+
constructor(delegate, ttlMs = 5 * 60 * 1e3) {
|
|
55
|
+
this.delegate = delegate;
|
|
56
|
+
this.ttlMs = ttlMs;
|
|
57
|
+
}
|
|
58
|
+
async fetchPrice(model, provider) {
|
|
59
|
+
const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
|
|
60
|
+
const cached = this.cache.get(key);
|
|
61
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
62
|
+
return cached.price;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const freshPrice = await this.delegate.fetchPrice(model, provider);
|
|
66
|
+
if (freshPrice) {
|
|
67
|
+
this.cache.set(key, { price: freshPrice, expiresAt: Date.now() + this.ttlMs });
|
|
68
|
+
return freshPrice;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
if (cached) {
|
|
72
|
+
return cached.price;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
peek(model, provider) {
|
|
78
|
+
const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
|
|
79
|
+
return this.cache.get(key)?.price;
|
|
80
|
+
}
|
|
81
|
+
clearCache() {
|
|
82
|
+
this.cache.clear();
|
|
83
|
+
}
|
|
84
|
+
invalidate(model, provider) {
|
|
85
|
+
if (!model) {
|
|
86
|
+
this.clearCache();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const key = provider ? normalizeKey(`${provider}/${model}`) : normalizeKey(model);
|
|
90
|
+
this.cache.delete(key);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var PricingRegistry = class {
|
|
94
|
+
static prices = /* @__PURE__ */ new Map();
|
|
95
|
+
static adapter;
|
|
96
|
+
static registerPrice(identifier, price) {
|
|
97
|
+
this.prices.set(normalizeKey(identifier), price);
|
|
98
|
+
}
|
|
99
|
+
static registerDistributorPrices(distributor, priceMap) {
|
|
100
|
+
for (const [model, price] of Object.entries(priceMap)) {
|
|
101
|
+
this.prices.set(normalizeKey(`${distributor}/${model}`), price);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
static registerPrices(prices) {
|
|
105
|
+
for (const [key, price] of Object.entries(prices)) {
|
|
106
|
+
this.prices.set(normalizeKey(key), price);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
static getPrice(model, provider) {
|
|
110
|
+
if (provider) {
|
|
111
|
+
const distributorKey = normalizeKey(`${provider}/${model}`);
|
|
112
|
+
const directDistributorPrice = this.prices.get(distributorKey);
|
|
113
|
+
if (directDistributorPrice) {
|
|
114
|
+
return directDistributorPrice;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return this.prices.get(normalizeKey(model));
|
|
118
|
+
}
|
|
119
|
+
static setAdapter(adapter) {
|
|
120
|
+
this.adapter = adapter;
|
|
121
|
+
}
|
|
122
|
+
static getAdapter() {
|
|
123
|
+
return this.adapter;
|
|
124
|
+
}
|
|
125
|
+
static clear() {
|
|
126
|
+
this.prices.clear();
|
|
127
|
+
this.adapter = void 0;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Réinitialise le registre en chargeant le catalogue d'exemple SEED_MODEL_PRICES.
|
|
131
|
+
*/
|
|
132
|
+
static loadSeedPrices() {
|
|
133
|
+
this.registerPrices(SEED_MODEL_PRICES);
|
|
134
|
+
}
|
|
135
|
+
static getAllPrices() {
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const [k, v] of this.prices.entries()) {
|
|
138
|
+
out[k] = v;
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var DEFAULT_MODEL_PRICES = new Proxy({}, {
|
|
144
|
+
get(_target, prop) {
|
|
145
|
+
return PricingRegistry.getPrice(prop);
|
|
22
146
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
promptUSDPerMillion: 0.15,
|
|
26
|
-
completionUSDPerMillion: 0.6
|
|
147
|
+
has(_target, prop) {
|
|
148
|
+
return Boolean(PricingRegistry.getPrice(prop));
|
|
27
149
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
completionUSDPerMillion: 10
|
|
150
|
+
ownKeys() {
|
|
151
|
+
return Object.keys(PricingRegistry.getAllPrices());
|
|
31
152
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
153
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
154
|
+
const price = PricingRegistry.getPrice(prop);
|
|
155
|
+
if (price) {
|
|
156
|
+
return { value: price, enumerable: true, configurable: true, writable: false };
|
|
157
|
+
}
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
function resolveModelPrice(model, options) {
|
|
162
|
+
const opts = typeof options === "string" ? { provider: options } : options ?? {};
|
|
163
|
+
if (opts.providerPricing) {
|
|
164
|
+
return opts.providerPricing;
|
|
165
|
+
}
|
|
166
|
+
if (opts.customPricing) {
|
|
167
|
+
if (opts.provider) {
|
|
168
|
+
const customDistributorKey = normalizeKey(`${opts.provider}/${model}`);
|
|
169
|
+
if (opts.customPricing[customDistributorKey]) {
|
|
170
|
+
return opts.customPricing[customDistributorKey];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (opts.customPricing[normalizeKey(model)] ?? opts.customPricing[model]) {
|
|
174
|
+
return opts.customPricing[normalizeKey(model)] ?? opts.customPricing[model];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
|
|
178
|
+
if (activeAdapter instanceof CachedPricingAdapter) {
|
|
179
|
+
const cached = activeAdapter.peek(model, opts.provider);
|
|
180
|
+
if (cached) {
|
|
181
|
+
return cached;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const registeredPrice = PricingRegistry.getPrice(model, opts.provider);
|
|
185
|
+
if (registeredPrice) {
|
|
186
|
+
return registeredPrice;
|
|
187
|
+
}
|
|
188
|
+
if (opts.provider === "ollama" || model.toLowerCase().includes("ollama")) {
|
|
189
|
+
return { promptUSDPerMillion: 0, completionUSDPerMillion: 0 };
|
|
190
|
+
}
|
|
191
|
+
return void 0;
|
|
192
|
+
}
|
|
193
|
+
async function resolveModelPriceAsync(model, options) {
|
|
194
|
+
const opts = typeof options === "string" ? { provider: options } : options ?? {};
|
|
195
|
+
const activeAdapter = opts.adapter ?? PricingRegistry.getAdapter();
|
|
196
|
+
if (activeAdapter) {
|
|
197
|
+
try {
|
|
198
|
+
const adapterPrice = await activeAdapter.fetchPrice(model, opts.provider);
|
|
199
|
+
if (adapterPrice) {
|
|
200
|
+
return adapterPrice;
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return resolveModelPrice(model, options);
|
|
206
|
+
}
|
|
207
|
+
function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0, options) {
|
|
208
|
+
const pricing = resolveModelPrice(model, options);
|
|
209
|
+
if (!pricing) {
|
|
210
|
+
return 0;
|
|
36
211
|
}
|
|
37
|
-
};
|
|
38
|
-
function calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens = 0) {
|
|
39
|
-
const pricing = DEFAULT_MODEL_PRICES[model] ?? {
|
|
40
|
-
promptUSDPerMillion: 0.5,
|
|
41
|
-
completionUSDPerMillion: 1.5
|
|
42
|
-
};
|
|
43
212
|
const promptCost = promptTokens / 1e6 * pricing.promptUSDPerMillion;
|
|
44
213
|
const completionCost = completionTokens / 1e6 * pricing.completionUSDPerMillion;
|
|
45
214
|
const cacheDiscount = cacheHitTokens && pricing.cacheHitUSDPerMillion ? cacheHitTokens / 1e6 * (pricing.promptUSDPerMillion - pricing.cacheHitUSDPerMillion) : 0;
|
|
46
215
|
return Math.max(0, promptCost + completionCost - cacheDiscount);
|
|
47
216
|
}
|
|
48
217
|
|
|
49
|
-
// src/sanitizer.ts
|
|
50
|
-
var EMAIL_REGEX = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/g;
|
|
51
|
-
var PHONE_FR_REGEX = /\b(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}\b/g;
|
|
52
|
-
var NIR_SSN_REGEX = /\b[12]\s*\d{2}\s*\d{2}\s*\d{2}\s*\d{3}\s*\d{3}(?:\s*\d{2})?\b/g;
|
|
53
|
-
var IBAN_REGEX = /\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/g;
|
|
54
|
-
function sanitizePII(input) {
|
|
55
|
-
let count = 0;
|
|
56
|
-
let result = input;
|
|
57
|
-
result = result.replace(EMAIL_REGEX, () => {
|
|
58
|
-
count++;
|
|
59
|
-
return "[REDACTED_EMAIL]";
|
|
60
|
-
});
|
|
61
|
-
result = result.replace(PHONE_FR_REGEX, () => {
|
|
62
|
-
count++;
|
|
63
|
-
return "[REDACTED_PHONE]";
|
|
64
|
-
});
|
|
65
|
-
result = result.replace(NIR_SSN_REGEX, () => {
|
|
66
|
-
count++;
|
|
67
|
-
return "[REDACTED_NIR]";
|
|
68
|
-
});
|
|
69
|
-
result = result.replace(IBAN_REGEX, () => {
|
|
70
|
-
count++;
|
|
71
|
-
return "[REDACTED_IBAN]";
|
|
72
|
-
});
|
|
73
|
-
return { text: result, maskedCount: count };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
218
|
// src/input-guard.ts
|
|
77
219
|
var INJECTION_PATTERNS = [
|
|
78
220
|
/ignore\s+(?:all\s+)?(?:previous|prior)\s+(?:instructions|prompts|rules)/i,
|
|
@@ -103,49 +245,144 @@ function validateUserInput(input, options) {
|
|
|
103
245
|
return { valid: true };
|
|
104
246
|
}
|
|
105
247
|
|
|
106
|
-
// src/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
248
|
+
// src/providers/http-client.ts
|
|
249
|
+
var DEFAULT_BASE_URLS = {
|
|
250
|
+
deepseek: "https://api.deepseek.com/v1",
|
|
251
|
+
mistral: "https://api.mistral.ai/v1",
|
|
252
|
+
openai: "https://api.openai.com/v1",
|
|
253
|
+
ollama: "http://localhost:11434/v1",
|
|
254
|
+
openrouter: "https://openrouter.ai/api/v1"
|
|
255
|
+
};
|
|
256
|
+
var HttpProviderClient = class {
|
|
257
|
+
name;
|
|
258
|
+
baseUrl;
|
|
259
|
+
apiKey;
|
|
260
|
+
providerType;
|
|
261
|
+
constructor(config) {
|
|
262
|
+
this.providerType = config.provider;
|
|
263
|
+
this.name = `${config.provider}-http`;
|
|
264
|
+
this.apiKey = config.apiKey;
|
|
265
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URLS[config.provider] || "https://api.openai.com/v1";
|
|
266
|
+
}
|
|
267
|
+
resolveEndpoint() {
|
|
268
|
+
const trimmed = this.baseUrl.replace(/\/+$/, "");
|
|
269
|
+
if (trimmed.endsWith("/chat/completions")) {
|
|
270
|
+
return trimmed;
|
|
271
|
+
}
|
|
272
|
+
return `${trimmed}/chat/completions`;
|
|
273
|
+
}
|
|
274
|
+
buildHeaders() {
|
|
275
|
+
const headers = {
|
|
276
|
+
"Content-Type": "application/json"
|
|
277
|
+
};
|
|
278
|
+
if (this.apiKey) {
|
|
279
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
280
|
+
}
|
|
281
|
+
if (this.providerType === "openrouter") {
|
|
282
|
+
headers["HTTP-Referer"] = "https://avantgate.dev";
|
|
283
|
+
headers["X-Title"] = "AvantGate";
|
|
284
|
+
}
|
|
285
|
+
return headers;
|
|
286
|
+
}
|
|
287
|
+
buildPayload(options, model) {
|
|
288
|
+
return {
|
|
289
|
+
model,
|
|
290
|
+
messages: options.messages.map((m) => ({
|
|
291
|
+
role: m.role,
|
|
292
|
+
content: m.content
|
|
293
|
+
})),
|
|
294
|
+
temperature: options.temperature ?? 0.2,
|
|
295
|
+
...options.responseFormat ? { response_format: options.responseFormat } : {}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
extractUsage(usageData) {
|
|
299
|
+
if (!usageData) {
|
|
300
|
+
return void 0;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
promptTokens: usageData.prompt_tokens,
|
|
304
|
+
completionTokens: usageData.completion_tokens,
|
|
305
|
+
totalTokens: usageData.total_tokens,
|
|
306
|
+
promptCacheHitTokens: usageData.prompt_tokens_details?.cached_tokens
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async complete(options) {
|
|
310
|
+
const endpoint = this.resolveEndpoint();
|
|
311
|
+
const model = options.model ?? "default";
|
|
312
|
+
const headers = this.buildHeaders();
|
|
313
|
+
const body = JSON.stringify(this.buildPayload(options, model));
|
|
314
|
+
const response = await fetch(endpoint, {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers,
|
|
317
|
+
body
|
|
318
|
+
});
|
|
319
|
+
if (!response.ok) {
|
|
320
|
+
const errorText = await response.text();
|
|
321
|
+
throw new Error(
|
|
322
|
+
`[AvantGate HTTP Provider ${this.providerType}] HTTP ${response.status} ${response.statusText}: ${errorText}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
const data = await response.json();
|
|
326
|
+
const text = data.choices?.[0]?.message?.content ?? "";
|
|
327
|
+
const usage = this.extractUsage(data.usage);
|
|
328
|
+
return { text, usage };
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
function createHttpProviderClient(config) {
|
|
332
|
+
return new HttpProviderClient(config);
|
|
142
333
|
}
|
|
143
334
|
|
|
144
335
|
// src/control-layer.ts
|
|
145
336
|
var AvantGateControlLayer = class {
|
|
146
337
|
config;
|
|
338
|
+
cachedPricingAdapter;
|
|
147
339
|
constructor(config) {
|
|
148
|
-
this.config =
|
|
340
|
+
this.config = {
|
|
341
|
+
...config,
|
|
342
|
+
primary: this.resolveProviderConfig(config.primary),
|
|
343
|
+
fallback: this.resolveProviderConfig(config.fallback),
|
|
344
|
+
emergencyFallback: this.resolveProviderConfig(config.emergencyFallback)
|
|
345
|
+
};
|
|
346
|
+
if (config.pricingAdapter) {
|
|
347
|
+
this.cachedPricingAdapter = new CachedPricingAdapter(
|
|
348
|
+
config.pricingAdapter,
|
|
349
|
+
config.pricingCacheTtlMs ?? 5 * 60 * 1e3
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
resolveProviderConfig(provider) {
|
|
354
|
+
if (!provider) return void 0;
|
|
355
|
+
if (provider.client) return provider;
|
|
356
|
+
if (provider.apiKey || provider.baseUrl || provider.provider === "ollama") {
|
|
357
|
+
return {
|
|
358
|
+
...provider,
|
|
359
|
+
client: createHttpProviderClient(provider)
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return provider;
|
|
363
|
+
}
|
|
364
|
+
findProviderConfig(provider, model) {
|
|
365
|
+
const list = [this.config.primary, this.config.fallback, this.config.emergencyFallback].filter(
|
|
366
|
+
(p) => Boolean(p)
|
|
367
|
+
);
|
|
368
|
+
if (provider) {
|
|
369
|
+
const matchProvider = list.find((p) => p.provider === provider);
|
|
370
|
+
if (matchProvider) return matchProvider;
|
|
371
|
+
}
|
|
372
|
+
if (model) {
|
|
373
|
+
const matchModel = list.find((p) => p.model === model);
|
|
374
|
+
if (matchModel) return matchModel;
|
|
375
|
+
}
|
|
376
|
+
return this.config.primary;
|
|
377
|
+
}
|
|
378
|
+
calculateCost(model, promptTokens, completionTokens, cacheHitTokens = 0, provider) {
|
|
379
|
+
const providerCfg = this.findProviderConfig(provider, model);
|
|
380
|
+
return calculateCostUSD(model, promptTokens, completionTokens, cacheHitTokens, {
|
|
381
|
+
provider: provider ?? providerCfg?.provider,
|
|
382
|
+
providerPricing: providerCfg?.pricing,
|
|
383
|
+
customPricing: this.config.customPricing,
|
|
384
|
+
adapter: this.cachedPricingAdapter
|
|
385
|
+
});
|
|
149
386
|
}
|
|
150
387
|
applySecurityGuards(userQuery) {
|
|
151
388
|
const guard = validateUserInput(userQuery, {
|
|
@@ -160,6 +397,46 @@ var AvantGateControlLayer = class {
|
|
|
160
397
|
}
|
|
161
398
|
return userQuery;
|
|
162
399
|
}
|
|
400
|
+
checkPreflightBudget(estimatedPromptTokens, targetModel, provider) {
|
|
401
|
+
if (this.config.maxTokenBudget !== void 0 && estimatedPromptTokens > this.config.maxTokenBudget) {
|
|
402
|
+
throw new BudgetExceededError(
|
|
403
|
+
`[AvantGate Budget Guard] Pre-flight token budget exceeded: estimated prompt (${estimatedPromptTokens} tokens) exceeds maxTokenBudget (${this.config.maxTokenBudget}).`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (this.config.maxCostUSD !== void 0) {
|
|
407
|
+
const isLocalFree = provider === "ollama" || targetModel.toLowerCase().includes("ollama");
|
|
408
|
+
const providerCfg = this.findProviderConfig(provider, targetModel);
|
|
409
|
+
const price = resolveModelPrice(targetModel, {
|
|
410
|
+
provider: provider ?? providerCfg?.provider,
|
|
411
|
+
providerPricing: providerCfg?.pricing,
|
|
412
|
+
customPricing: this.config.customPricing,
|
|
413
|
+
adapter: this.cachedPricingAdapter
|
|
414
|
+
});
|
|
415
|
+
if (!isLocalFree && !price) {
|
|
416
|
+
throw new ConfigurationError(
|
|
417
|
+
`[AvantGate Configuration Error] 'maxCostUSD' was set to $${this.config.maxCostUSD}, but no pricing was configured for model '${targetModel}'. Please define pricing in ProviderConfig, customPricing, or via PricingAdapter.`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
const estimatedPromptCost = this.calculateCost(targetModel, estimatedPromptTokens, 0, 0, provider);
|
|
421
|
+
if (estimatedPromptCost > this.config.maxCostUSD) {
|
|
422
|
+
throw new BudgetExceededError(
|
|
423
|
+
`[AvantGate Budget Guard] Pre-flight cost budget exceeded: estimated prompt cost ($${estimatedPromptCost.toFixed(6)}) exceeds maxCostUSD ($${this.config.maxCostUSD}).`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
checkPostExecutionBudget(tokensTotal, costUSD) {
|
|
429
|
+
if (this.config.maxTokenBudget !== void 0 && tokensTotal > this.config.maxTokenBudget) {
|
|
430
|
+
throw new BudgetExceededError(
|
|
431
|
+
`[AvantGate Budget Guard] Execution total tokens (${tokensTotal}) exceeded maxTokenBudget (${this.config.maxTokenBudget}).`
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
if (this.config.maxCostUSD !== void 0 && costUSD > this.config.maxCostUSD) {
|
|
435
|
+
throw new BudgetExceededError(
|
|
436
|
+
`[AvantGate Budget Guard] Execution cost ($${costUSD.toFixed(6)}) exceeded maxCostUSD ($${this.config.maxCostUSD}).`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
163
440
|
buildMessages(systemPrompt, query) {
|
|
164
441
|
const messages = [];
|
|
165
442
|
if (systemPrompt) {
|
|
@@ -172,7 +449,12 @@ var AvantGateControlLayer = class {
|
|
|
172
449
|
const promptTokens = rawUsage?.promptTokens ?? Math.ceil(query.length / 4);
|
|
173
450
|
const completionTokens = rawUsage?.completionTokens ?? Math.ceil(text.length / 4);
|
|
174
451
|
const totalTokens = rawUsage?.totalTokens ?? promptTokens + completionTokens;
|
|
175
|
-
return {
|
|
452
|
+
return {
|
|
453
|
+
promptTokens,
|
|
454
|
+
completionTokens,
|
|
455
|
+
totalTokens,
|
|
456
|
+
promptCacheHitTokens: rawUsage?.promptCacheHitTokens
|
|
457
|
+
};
|
|
176
458
|
}
|
|
177
459
|
getProviderChain() {
|
|
178
460
|
return [
|
|
@@ -184,7 +466,7 @@ var AvantGateControlLayer = class {
|
|
|
184
466
|
executeSimulation(query, systemPrompt) {
|
|
185
467
|
const promptTokens = Math.ceil(((systemPrompt?.length ?? 0) + query.length) / 4);
|
|
186
468
|
const completionTokens = 50;
|
|
187
|
-
const cost =
|
|
469
|
+
const cost = this.calculateCost(this.config.primary.model, promptTokens, completionTokens);
|
|
188
470
|
return {
|
|
189
471
|
text: `[AvantGate In-Process Engine] Response simulation for model: ${this.config.primary.model}`,
|
|
190
472
|
tokens: {
|
|
@@ -213,14 +495,15 @@ var AvantGateControlLayer = class {
|
|
|
213
495
|
attempts: 1
|
|
214
496
|
};
|
|
215
497
|
}
|
|
216
|
-
async executeProviderPipeline(messages, query, temperature) {
|
|
498
|
+
async executeProviderPipeline(messages, query, temperature, modelOverride) {
|
|
217
499
|
const chain = this.getProviderChain();
|
|
218
500
|
let lastError;
|
|
219
501
|
for (let index = 0; index < chain.length; index++) {
|
|
220
502
|
const providerConfig = chain[index];
|
|
503
|
+
const targetModel = index === 0 && modelOverride ? modelOverride : providerConfig.model;
|
|
221
504
|
try {
|
|
222
505
|
const response = await providerConfig.client.complete({
|
|
223
|
-
model:
|
|
506
|
+
model: targetModel,
|
|
224
507
|
messages,
|
|
225
508
|
temperature: temperature ?? 0.2
|
|
226
509
|
});
|
|
@@ -228,7 +511,7 @@ var AvantGateControlLayer = class {
|
|
|
228
511
|
return {
|
|
229
512
|
responseText: response.text,
|
|
230
513
|
usage,
|
|
231
|
-
modelUsed:
|
|
514
|
+
modelUsed: targetModel,
|
|
232
515
|
failoverOccurred: index > 0,
|
|
233
516
|
attempts: index + 1
|
|
234
517
|
};
|
|
@@ -239,10 +522,11 @@ var AvantGateControlLayer = class {
|
|
|
239
522
|
throw lastError;
|
|
240
523
|
}
|
|
241
524
|
assembleResult(output) {
|
|
242
|
-
const costUSD =
|
|
525
|
+
const costUSD = this.calculateCost(
|
|
243
526
|
output.modelUsed,
|
|
244
527
|
output.usage.promptTokens,
|
|
245
|
-
output.usage.completionTokens
|
|
528
|
+
output.usage.completionTokens,
|
|
529
|
+
output.usage.promptCacheHitTokens ?? 0
|
|
246
530
|
);
|
|
247
531
|
return {
|
|
248
532
|
text: output.responseText,
|
|
@@ -271,18 +555,27 @@ var AvantGateControlLayer = class {
|
|
|
271
555
|
});
|
|
272
556
|
}
|
|
273
557
|
/**
|
|
274
|
-
* Exécute une requête avec garde d'entrée, masquage PII, et calcul des coûts.
|
|
558
|
+
* Exécute une requête avec garde d'entrée, masquage PII, garde pré-vol et calcul des coûts.
|
|
275
559
|
*/
|
|
276
560
|
async execute(options) {
|
|
277
561
|
const sanitizedQuery = this.applySecurityGuards(options.userQuery);
|
|
278
562
|
const messages = this.buildMessages(options.systemPrompt, sanitizedQuery);
|
|
563
|
+
const promptLength = (options.systemPrompt?.length ?? 0) + sanitizedQuery.length;
|
|
564
|
+
const estimatedPromptTokens = Math.ceil(promptLength / 4);
|
|
565
|
+
this.checkPreflightBudget(estimatedPromptTokens, this.config.primary.model, this.config.primary.provider);
|
|
279
566
|
if (!options.providerOverride && this.getProviderChain().length === 0) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
567
|
+
if (this.config.mockSimulation) {
|
|
568
|
+
const simResult = this.executeSimulation(sanitizedQuery, options.systemPrompt);
|
|
569
|
+
await this.notifyAuditSink(simResult);
|
|
570
|
+
return simResult;
|
|
571
|
+
}
|
|
572
|
+
throw new ConfigurationError(
|
|
573
|
+
"[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
|
|
574
|
+
);
|
|
283
575
|
}
|
|
284
576
|
const output = options.providerOverride ? await this.executeOverrideProvider(options.providerOverride, messages, sanitizedQuery, options.temperature) : await this.executeProviderPipeline(messages, sanitizedQuery, options.temperature);
|
|
285
577
|
const result = this.assembleResult(output);
|
|
578
|
+
this.checkPostExecutionBudget(result.tokens.total, result.costUSD);
|
|
286
579
|
await this.notifyAuditSink(result);
|
|
287
580
|
return result;
|
|
288
581
|
}
|
|
@@ -296,7 +589,13 @@ var AvantGateControlLayer = class {
|
|
|
296
589
|
temperature: options.temperature ?? 0.1,
|
|
297
590
|
providerOverride: options.providerOverride
|
|
298
591
|
});
|
|
299
|
-
const
|
|
592
|
+
const isFinancial = Boolean(
|
|
593
|
+
this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
|
|
594
|
+
);
|
|
595
|
+
const parsedData = validateWithZod(rawResult.text, options.schema, {
|
|
596
|
+
financialNormalizer: isFinancial,
|
|
597
|
+
jurisdiction: this.config.features?.finance?.jurisdiction
|
|
598
|
+
});
|
|
300
599
|
return {
|
|
301
600
|
data: parsedData,
|
|
302
601
|
rawText: rawResult.text,
|
|
@@ -306,19 +605,452 @@ var AvantGateControlLayer = class {
|
|
|
306
605
|
failoverOccurred: rawResult.failoverOccurred
|
|
307
606
|
};
|
|
308
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Méthode unifiée de premier niveau pour l'extraction structurée sans code boilerplate.
|
|
610
|
+
* Gère le failover multi-fournisseurs, les retries, la validation Zod et la normalisation financière.
|
|
611
|
+
*/
|
|
612
|
+
async generateStructuredOutput(options) {
|
|
613
|
+
const maxRetries = options.maxRetries ?? this.config.retryOptions?.maxRetries ?? 2;
|
|
614
|
+
const modelToUse = options.model ?? this.config.primary.model;
|
|
615
|
+
const processedMessages = options.messages.map((msg) => {
|
|
616
|
+
if (msg.role === "user") {
|
|
617
|
+
return { ...msg, content: this.applySecurityGuards(msg.content) };
|
|
618
|
+
}
|
|
619
|
+
return msg;
|
|
620
|
+
});
|
|
621
|
+
const promptLength = processedMessages.reduce((sum, msg) => sum + msg.content.length, 0);
|
|
622
|
+
const estimatedPromptTokens = Math.ceil(promptLength / 4);
|
|
623
|
+
this.checkPreflightBudget(estimatedPromptTokens, modelToUse);
|
|
624
|
+
let lastError;
|
|
625
|
+
let accumulatedPromptTokens = 0;
|
|
626
|
+
let accumulatedCompletionTokens = 0;
|
|
627
|
+
let failoverOccurred = false;
|
|
628
|
+
let modelUsed = modelToUse;
|
|
629
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
630
|
+
try {
|
|
631
|
+
let responseText = "";
|
|
632
|
+
let attemptPromptTokens = 0;
|
|
633
|
+
let attemptCompletionTokens = 0;
|
|
634
|
+
if (options.providerOverride) {
|
|
635
|
+
const res = await options.providerOverride.complete({
|
|
636
|
+
model: modelToUse,
|
|
637
|
+
messages: processedMessages,
|
|
638
|
+
temperature: options.temperature ?? 0.1
|
|
639
|
+
});
|
|
640
|
+
responseText = res.text;
|
|
641
|
+
const usage = this.resolveTokens(res.usage, JSON.stringify(processedMessages), responseText);
|
|
642
|
+
attemptPromptTokens = usage.promptTokens;
|
|
643
|
+
attemptCompletionTokens = usage.completionTokens;
|
|
644
|
+
modelUsed = modelToUse;
|
|
645
|
+
} else if (this.getProviderChain().length === 0) {
|
|
646
|
+
if (this.config.mockSimulation) {
|
|
647
|
+
const sim = this.executeSimulation(JSON.stringify(processedMessages));
|
|
648
|
+
responseText = sim.text;
|
|
649
|
+
attemptPromptTokens = sim.tokens.prompt;
|
|
650
|
+
attemptCompletionTokens = sim.tokens.completion;
|
|
651
|
+
} else {
|
|
652
|
+
throw new ConfigurationError(
|
|
653
|
+
"[AvantGate Configuration Error] No active LLM provider configured. Provide a client implementing LLMProviderPort or configure credentials (apiKey / baseUrl)."
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
const output = await this.executeProviderPipeline(
|
|
658
|
+
processedMessages,
|
|
659
|
+
JSON.stringify(processedMessages),
|
|
660
|
+
options.temperature ?? 0.1,
|
|
661
|
+
options.model
|
|
662
|
+
);
|
|
663
|
+
responseText = output.responseText;
|
|
664
|
+
attemptPromptTokens = output.usage.promptTokens;
|
|
665
|
+
attemptCompletionTokens = output.usage.completionTokens;
|
|
666
|
+
modelUsed = output.modelUsed;
|
|
667
|
+
failoverOccurred = output.failoverOccurred;
|
|
668
|
+
}
|
|
669
|
+
accumulatedPromptTokens += attemptPromptTokens;
|
|
670
|
+
accumulatedCompletionTokens += attemptCompletionTokens;
|
|
671
|
+
const isFinancial = options.financialNormalizer ?? Boolean(
|
|
672
|
+
this.config.features?.finance?.enableFrenchAccounting || this.config.features?.finance
|
|
673
|
+
);
|
|
674
|
+
const parsedData = validateWithZod(responseText, options.schema, {
|
|
675
|
+
financialNormalizer: isFinancial,
|
|
676
|
+
jurisdiction: this.config.features?.finance?.jurisdiction
|
|
677
|
+
});
|
|
678
|
+
const totalTokens = accumulatedPromptTokens + accumulatedCompletionTokens;
|
|
679
|
+
const costUSD = this.calculateCost(modelUsed, accumulatedPromptTokens, accumulatedCompletionTokens);
|
|
680
|
+
this.checkPostExecutionBudget(totalTokens, costUSD);
|
|
681
|
+
const result = {
|
|
682
|
+
data: parsedData,
|
|
683
|
+
rawText: responseText,
|
|
684
|
+
tokens: {
|
|
685
|
+
prompt: accumulatedPromptTokens,
|
|
686
|
+
completion: accumulatedCompletionTokens,
|
|
687
|
+
total: totalTokens
|
|
688
|
+
},
|
|
689
|
+
costUSD,
|
|
690
|
+
modelUsed,
|
|
691
|
+
failoverOccurred
|
|
692
|
+
};
|
|
693
|
+
await this.notifyAuditSink({
|
|
694
|
+
text: responseText,
|
|
695
|
+
tokens: result.tokens,
|
|
696
|
+
costUSD: result.costUSD,
|
|
697
|
+
modelUsed: result.modelUsed,
|
|
698
|
+
failoverOccurred: result.failoverOccurred,
|
|
699
|
+
attempts: attempt + 1
|
|
700
|
+
});
|
|
701
|
+
return result;
|
|
702
|
+
} catch (err) {
|
|
703
|
+
lastError = err;
|
|
704
|
+
if (attempt < maxRetries) {
|
|
705
|
+
const delay = (this.config.retryOptions?.initialDelayMs ?? 200) * Math.pow(this.config.retryOptions?.backoffFactor ?? 1.5, attempt);
|
|
706
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
throw lastError;
|
|
711
|
+
}
|
|
309
712
|
};
|
|
310
713
|
function createLLMControlLayer(config) {
|
|
311
714
|
return new AvantGateControlLayer(config);
|
|
312
715
|
}
|
|
313
716
|
var createAvantGate = createLLMControlLayer;
|
|
717
|
+
|
|
718
|
+
// src/prompts/prompt-template.ts
|
|
719
|
+
var DEFAULT_JAILBREAK_PATTERNS = [
|
|
720
|
+
/ignore\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
|
|
721
|
+
/disregard\s+(all\s+)?(previous|prior)\s+(instructions|rules)/i,
|
|
722
|
+
/system\s+override/i,
|
|
723
|
+
/you\s+are\s+now\s+(unrestricted|DAN|jailbroken)/i,
|
|
724
|
+
/<\s*\|\s*im_start\s*\|>/i,
|
|
725
|
+
/\[SYSTEM_PROMPT\]/i
|
|
726
|
+
];
|
|
727
|
+
var PromptTemplate = class {
|
|
728
|
+
id;
|
|
729
|
+
version;
|
|
730
|
+
label;
|
|
731
|
+
description;
|
|
732
|
+
template;
|
|
733
|
+
inputSchema;
|
|
734
|
+
blockPatterns;
|
|
735
|
+
antiInjectionEnabled;
|
|
736
|
+
customSanitizer;
|
|
737
|
+
constructor(options) {
|
|
738
|
+
this.id = options.id;
|
|
739
|
+
this.version = options.version;
|
|
740
|
+
this.label = options.label || "production";
|
|
741
|
+
this.description = options.description;
|
|
742
|
+
this.template = options.template;
|
|
743
|
+
this.inputSchema = options.inputSchema;
|
|
744
|
+
this.antiInjectionEnabled = options.antiInjection?.enabled !== false;
|
|
745
|
+
this.blockPatterns = options.antiInjection?.blockPatterns || DEFAULT_JAILBREAK_PATTERNS;
|
|
746
|
+
this.customSanitizer = options.antiInjection?.sanitizer;
|
|
747
|
+
}
|
|
748
|
+
validateUserInput(variables) {
|
|
749
|
+
const threats = [];
|
|
750
|
+
if (this.inputSchema) {
|
|
751
|
+
const parsed = this.inputSchema.safeParse(variables);
|
|
752
|
+
if (!parsed.success) {
|
|
753
|
+
return {
|
|
754
|
+
isValid: false,
|
|
755
|
+
threats: parsed.error.issues.map(
|
|
756
|
+
(issue) => `${issue.path.join(".")}: ${issue.message}`
|
|
757
|
+
)
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (this.antiInjectionEnabled) {
|
|
762
|
+
for (const [key, val] of Object.entries(variables)) {
|
|
763
|
+
if (typeof val === "string") {
|
|
764
|
+
for (const pattern of this.blockPatterns) {
|
|
765
|
+
if (pattern.test(val)) {
|
|
766
|
+
threats.push(
|
|
767
|
+
`Potential prompt injection in variable "${key}" matching pattern ${pattern}`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return { isValid: threats.length === 0, threats };
|
|
775
|
+
}
|
|
776
|
+
format(variables) {
|
|
777
|
+
const check = this.validateUserInput(variables);
|
|
778
|
+
if (!check.isValid) {
|
|
779
|
+
throw new Error(
|
|
780
|
+
`[PromptTemplate:${this.id}] Validation failed: ${check.threats.join(", ")}`
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
const interpolated = this.template.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, key) => {
|
|
784
|
+
let value = variables[key];
|
|
785
|
+
if (value === void 0 || value === null) {
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
788
|
+
let stringValue = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
789
|
+
if (this.customSanitizer) {
|
|
790
|
+
stringValue = this.customSanitizer(stringValue);
|
|
791
|
+
}
|
|
792
|
+
return stringValue;
|
|
793
|
+
});
|
|
794
|
+
return interpolated.split("\n").filter((line, index, arr) => line.trim() !== "" || index > 0 && arr[index - 1].trim() !== "").join("\n");
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
// src/prompts/prompt-builder.ts
|
|
799
|
+
import { z } from "zod";
|
|
800
|
+
var PromptBuilder = class {
|
|
801
|
+
personaSlot = "";
|
|
802
|
+
rulesSlot = [];
|
|
803
|
+
retryHintSlot = "";
|
|
804
|
+
fewShotSlot = [];
|
|
805
|
+
pinnedFactsSlot = "";
|
|
806
|
+
contextSlot = "";
|
|
807
|
+
userPayloadSlot = "";
|
|
808
|
+
schemaContractText = "";
|
|
809
|
+
constructor(templateOrId) {
|
|
810
|
+
if (templateOrId instanceof PromptTemplate) {
|
|
811
|
+
this.personaSlot = templateOrId.template;
|
|
812
|
+
} else if (typeof templateOrId === "string") {
|
|
813
|
+
this.personaSlot = templateOrId.trim();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
withPersona(persona) {
|
|
817
|
+
this.personaSlot = persona.trim();
|
|
818
|
+
return this;
|
|
819
|
+
}
|
|
820
|
+
withRules(rules) {
|
|
821
|
+
if (Array.isArray(rules)) {
|
|
822
|
+
this.rulesSlot.push(...rules.map((rule) => rule.trim()).filter(Boolean));
|
|
823
|
+
} else if (rules) {
|
|
824
|
+
this.rulesSlot.push(rules.trim());
|
|
825
|
+
}
|
|
826
|
+
return this;
|
|
827
|
+
}
|
|
828
|
+
withRetryHint(hint) {
|
|
829
|
+
this.retryHintSlot = hint.trim();
|
|
830
|
+
return this;
|
|
831
|
+
}
|
|
832
|
+
withFewShot(examples) {
|
|
833
|
+
this.fewShotSlot = examples;
|
|
834
|
+
return this;
|
|
835
|
+
}
|
|
836
|
+
withPinnedFacts(facts) {
|
|
837
|
+
if (!facts) return this;
|
|
838
|
+
if (typeof facts === "string") {
|
|
839
|
+
this.pinnedFactsSlot = facts.trim();
|
|
840
|
+
return this;
|
|
841
|
+
}
|
|
842
|
+
this.pinnedFactsSlot = Object.entries(facts).filter(([, val]) => val !== void 0 && val !== null && val !== "").map(([key, val]) => `\u2022 ${key} : ${Array.isArray(val) ? val.join(", ") : String(val)}`).join("\n");
|
|
843
|
+
return this;
|
|
844
|
+
}
|
|
845
|
+
withContext(context) {
|
|
846
|
+
this.contextSlot = context.trim();
|
|
847
|
+
return this;
|
|
848
|
+
}
|
|
849
|
+
withUserPayload(payload) {
|
|
850
|
+
this.userPayloadSlot = payload.trim();
|
|
851
|
+
return this;
|
|
852
|
+
}
|
|
853
|
+
schemaContract(schema, options) {
|
|
854
|
+
let shapeDescription = "JSON Object";
|
|
855
|
+
if (schema instanceof z.ZodObject) {
|
|
856
|
+
shapeDescription = JSON.stringify(Object.keys(schema.shape));
|
|
857
|
+
}
|
|
858
|
+
this.schemaContractText = [
|
|
859
|
+
`[DIRECTIVE DE CONTRAT DE SORTIE JSON STRICT]`,
|
|
860
|
+
`Tu DOIS renvoyer UNIQUEMENT un objet JSON valide, sans balises markdown (\`\`\`json), sans texte avant ou apr\xE8s.`,
|
|
861
|
+
`Champs obligatoires et types attendus :`,
|
|
862
|
+
options?.schemaName ? `Sch\xE9ma : ${options.schemaName}` : "",
|
|
863
|
+
shapeDescription
|
|
864
|
+
].filter(Boolean).join("\n");
|
|
865
|
+
return this;
|
|
866
|
+
}
|
|
867
|
+
assembleMessages(pinnedFacts, context, userPayload) {
|
|
868
|
+
const messages = [];
|
|
869
|
+
let system0 = this.personaSlot || "Tu es un assistant expert.";
|
|
870
|
+
if (this.schemaContractText) {
|
|
871
|
+
system0 += `
|
|
872
|
+
|
|
873
|
+
${this.schemaContractText}`;
|
|
874
|
+
}
|
|
875
|
+
messages.push({ role: "system", content: system0 });
|
|
876
|
+
const dynamicParts = [];
|
|
877
|
+
if (this.rulesSlot.length > 0) {
|
|
878
|
+
dynamicParts.push(
|
|
879
|
+
`[R\xC8GLES ET CONSIGNES M\xC9TIER]
|
|
880
|
+
${this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n")}`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
if (this.retryHintSlot) {
|
|
884
|
+
dynamicParts.push(`[INSTRUCTION DE CORRECTION / RETRY]
|
|
885
|
+
${this.retryHintSlot}`);
|
|
886
|
+
}
|
|
887
|
+
if (dynamicParts.length > 0) {
|
|
888
|
+
messages.push({ role: "system", content: dynamicParts.join("\n\n") });
|
|
889
|
+
}
|
|
890
|
+
if (this.fewShotSlot.length > 0) {
|
|
891
|
+
const examplesContent = this.fewShotSlot.map(
|
|
892
|
+
(ex, idx) => `[Exemple ${idx + 1}]
|
|
893
|
+
Question: ${ex.question}
|
|
894
|
+
R\xE9ponse attendue: ${ex.answer}`
|
|
895
|
+
).join("\n\n");
|
|
896
|
+
messages.push({ role: "system", content: `[EXEMPLES DE R\xC9F\xC9RENCE]
|
|
897
|
+
${examplesContent}` });
|
|
898
|
+
}
|
|
899
|
+
const userParts = [];
|
|
900
|
+
if (pinnedFacts) {
|
|
901
|
+
userParts.push(`[FAITS STRUCTUR\xC9S]
|
|
902
|
+
${pinnedFacts}`);
|
|
903
|
+
}
|
|
904
|
+
if (context) {
|
|
905
|
+
userParts.push(`[CONTEXTE DOCUMENTAIRE]
|
|
906
|
+
${context}`);
|
|
907
|
+
}
|
|
908
|
+
if (userPayload) {
|
|
909
|
+
const labelNeeded = Boolean(pinnedFacts || context);
|
|
910
|
+
userParts.push(labelNeeded ? `[DONN\xC9ES \xC0 ANALYSER]
|
|
911
|
+
${userPayload}` : userPayload);
|
|
912
|
+
}
|
|
913
|
+
messages.push({ role: "user", content: userParts.join("\n\n") });
|
|
914
|
+
return messages;
|
|
915
|
+
}
|
|
916
|
+
toMessages() {
|
|
917
|
+
return this.assembleMessages(this.pinnedFactsSlot, this.contextSlot, this.userPayloadSlot);
|
|
918
|
+
}
|
|
919
|
+
build(budget) {
|
|
920
|
+
if (!budget) {
|
|
921
|
+
const messages2 = this.toMessages();
|
|
922
|
+
const promptText2 = messages2.map((msg) => `${msg.role.toUpperCase()}:
|
|
923
|
+
${msg.content}`).join("\n\n");
|
|
924
|
+
return {
|
|
925
|
+
messages: messages2,
|
|
926
|
+
promptText: promptText2,
|
|
927
|
+
allocatedTokens: Math.ceil(promptText2.length / 4),
|
|
928
|
+
isTruncated: false
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
budget.reset();
|
|
932
|
+
let isTruncated = false;
|
|
933
|
+
let truncatedSlot;
|
|
934
|
+
let system0 = this.personaSlot || "Tu es un assistant expert.";
|
|
935
|
+
if (this.schemaContractText) {
|
|
936
|
+
system0 += `
|
|
937
|
+
|
|
938
|
+
${this.schemaContractText}`;
|
|
939
|
+
}
|
|
940
|
+
budget.forceReserve("persona", system0);
|
|
941
|
+
if (this.rulesSlot.length > 0 || this.retryHintSlot) {
|
|
942
|
+
const rulesText = this.rulesSlot.map((rule, idx) => `${idx + 1}. ${rule}`).join("\n");
|
|
943
|
+
budget.forceReserve("rules", `${rulesText}
|
|
944
|
+
${this.retryHintSlot}`);
|
|
945
|
+
}
|
|
946
|
+
let payloadToUse = this.userPayloadSlot;
|
|
947
|
+
let factsToUse = this.pinnedFactsSlot;
|
|
948
|
+
let contextToUse = this.contextSlot;
|
|
949
|
+
if (payloadToUse) {
|
|
950
|
+
const payloadCost = budget.count(payloadToUse);
|
|
951
|
+
if (payloadCost > budget.remaining()) {
|
|
952
|
+
const charLimit = Math.max(100, budget.remaining() * 4);
|
|
953
|
+
payloadToUse = payloadToUse.slice(0, charLimit);
|
|
954
|
+
isTruncated = true;
|
|
955
|
+
truncatedSlot = "user";
|
|
956
|
+
}
|
|
957
|
+
budget.reserve("user", payloadToUse);
|
|
958
|
+
}
|
|
959
|
+
if (factsToUse) {
|
|
960
|
+
const factsCost = budget.count(factsToUse);
|
|
961
|
+
if (factsCost > budget.remaining()) {
|
|
962
|
+
const charLimit = Math.max(50, budget.remaining() * 4);
|
|
963
|
+
factsToUse = factsToUse.slice(0, charLimit);
|
|
964
|
+
isTruncated = true;
|
|
965
|
+
truncatedSlot = "pinnedFacts";
|
|
966
|
+
}
|
|
967
|
+
budget.reserve("pinnedFacts", factsToUse);
|
|
968
|
+
}
|
|
969
|
+
if (contextToUse) {
|
|
970
|
+
const contextCost = budget.count(contextToUse);
|
|
971
|
+
if (contextCost > budget.remaining()) {
|
|
972
|
+
const charLimit = Math.max(0, budget.remaining() * 4);
|
|
973
|
+
contextToUse = contextToUse.slice(0, charLimit);
|
|
974
|
+
isTruncated = true;
|
|
975
|
+
truncatedSlot = "context";
|
|
976
|
+
}
|
|
977
|
+
budget.reserve("context", contextToUse);
|
|
978
|
+
}
|
|
979
|
+
const messages = this.assembleMessages(factsToUse, contextToUse, payloadToUse);
|
|
980
|
+
const promptText = messages.map((msg) => `${msg.role.toUpperCase()}:
|
|
981
|
+
${msg.content}`).join("\n\n");
|
|
982
|
+
return {
|
|
983
|
+
messages,
|
|
984
|
+
promptText,
|
|
985
|
+
allocatedTokens: budget.getAllocated(),
|
|
986
|
+
isTruncated,
|
|
987
|
+
truncatedSlot
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// src/prompts/prompt-registry.ts
|
|
993
|
+
var PromptRegistry = class {
|
|
994
|
+
static registry = /* @__PURE__ */ new Map();
|
|
995
|
+
static register(templateOrOptions) {
|
|
996
|
+
const template = templateOrOptions instanceof PromptTemplate ? templateOrOptions : new PromptTemplate(templateOrOptions);
|
|
997
|
+
const existing = this.registry.get(template.id) || [];
|
|
998
|
+
const filtered = existing.filter((item) => item.version !== template.version);
|
|
999
|
+
filtered.push(template);
|
|
1000
|
+
filtered.sort((a, b) => b.version - a.version);
|
|
1001
|
+
this.registry.set(template.id, filtered);
|
|
1002
|
+
}
|
|
1003
|
+
static get(id, options) {
|
|
1004
|
+
const templates = this.registry.get(id);
|
|
1005
|
+
if (!templates || templates.length === 0) {
|
|
1006
|
+
throw new Error(`[PromptRegistry] No prompt template registered with id: "${id}"`);
|
|
1007
|
+
}
|
|
1008
|
+
if (options?.version !== void 0) {
|
|
1009
|
+
const match = templates.find((item) => item.version === options.version);
|
|
1010
|
+
if (!match) {
|
|
1011
|
+
throw new Error(
|
|
1012
|
+
`[PromptRegistry] Template "${id}" with version ${options.version} not found`
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
return match;
|
|
1016
|
+
}
|
|
1017
|
+
if (options?.label) {
|
|
1018
|
+
const match = templates.find((item) => item.label === options.label);
|
|
1019
|
+
if (match) {
|
|
1020
|
+
return match;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return templates[0];
|
|
1024
|
+
}
|
|
1025
|
+
static has(id) {
|
|
1026
|
+
return this.registry.has(id);
|
|
1027
|
+
}
|
|
1028
|
+
static clear() {
|
|
1029
|
+
this.registry.clear();
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
314
1032
|
export {
|
|
1033
|
+
BudgetExceededError as AvantGateBudgetExceededError,
|
|
1034
|
+
ConfigurationError as AvantGateConfigurationError,
|
|
315
1035
|
AvantGateControlLayer,
|
|
1036
|
+
BudgetExceededError,
|
|
1037
|
+
CachedPricingAdapter,
|
|
1038
|
+
ConfigurationError,
|
|
316
1039
|
DEFAULT_MODEL_PRICES,
|
|
1040
|
+
HttpProviderClient,
|
|
1041
|
+
PricingRegistry,
|
|
1042
|
+
PromptBuilder,
|
|
1043
|
+
PromptRegistry,
|
|
1044
|
+
PromptTemplate,
|
|
1045
|
+
SEED_MODEL_PRICES,
|
|
317
1046
|
AvantGateControlLayer as ZenLLMControlLayer,
|
|
318
1047
|
calculateCostUSD,
|
|
319
1048
|
createAvantGate,
|
|
1049
|
+
createHttpProviderClient,
|
|
320
1050
|
createLLMControlLayer,
|
|
321
1051
|
extractAndCleanJSON,
|
|
1052
|
+
resolveModelPrice,
|
|
1053
|
+
resolveModelPriceAsync,
|
|
322
1054
|
sanitizePII,
|
|
323
1055
|
validateUserInput,
|
|
324
1056
|
validateWithZod
|