@backtest-kit/ollama 14.1.0 → 15.0.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/README.md +287 -287
- package/build/index.cjs +224 -160
- package/build/index.mjs +226 -162
- package/package.json +114 -112
- package/types.d.ts +16 -14
package/build/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { event, validateToolArguments, RoundRobin, addCompletion } from 'agent-swarm-kit';
|
|
2
2
|
import { scoped } from 'di-scoped';
|
|
3
|
-
import path, { join } from 'path';
|
|
3
|
+
import path, { join, isAbsolute } from 'path';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import { createActivator } from 'di-kit';
|
|
6
|
-
import { memoize, ToolRegistry, Subject, trycatch, errorData, getErrorMessage, iterateDocuments, distinctDocuments, resolveDocuments,
|
|
6
|
+
import { memoize, ToolRegistry, Subject, trycatch, errorData, getErrorMessage, iterateDocuments, distinctDocuments, resolveDocuments, randomString, fetchApi, singleshot, str, timeout } from 'functools-kit';
|
|
7
7
|
import { MarkdownWriter, getSymbol, getContext, getMode } from 'backtest-kit';
|
|
8
8
|
import MarkdownIt from 'markdown-it';
|
|
9
9
|
import sanitizeHtml from 'sanitize-html';
|
|
@@ -294,7 +294,9 @@ const TYPES = {
|
|
|
294
294
|
...publicServices$1,
|
|
295
295
|
};
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
// Symbol.for keeps the brand check working when CJS and ESM copies of the
|
|
298
|
+
// package end up in the same process (dual-package hazard).
|
|
299
|
+
const PROMPT_TYPE_SYMBOL = Symbol.for("backtest-kit.ollama.prompt-type");
|
|
298
300
|
class Prompt {
|
|
299
301
|
constructor(source) {
|
|
300
302
|
this.source = source;
|
|
@@ -314,7 +316,7 @@ Prompt.isPrompt = (value) => {
|
|
|
314
316
|
};
|
|
315
317
|
|
|
316
318
|
const require = createRequire(import.meta.url);
|
|
317
|
-
const REQUIRE_MODULE_FN = memoize(([module]) => module, (module) => {
|
|
319
|
+
const REQUIRE_MODULE_FN = memoize(([module]) => join(module.baseDir, module.path), (module) => {
|
|
318
320
|
const modulePath = require.resolve(join(module.baseDir, module.path));
|
|
319
321
|
return require(modulePath);
|
|
320
322
|
});
|
|
@@ -334,7 +336,8 @@ class PromptCacheService {
|
|
|
334
336
|
module,
|
|
335
337
|
});
|
|
336
338
|
if (module) {
|
|
337
|
-
REQUIRE_MODULE_FN.clear(module);
|
|
339
|
+
REQUIRE_MODULE_FN.clear(join(module.baseDir, module.path));
|
|
340
|
+
return;
|
|
338
341
|
}
|
|
339
342
|
REQUIRE_MODULE_FN.clear();
|
|
340
343
|
};
|
|
@@ -895,6 +898,13 @@ const toPlainString = async (content) => {
|
|
|
895
898
|
typographer: true,
|
|
896
899
|
});
|
|
897
900
|
let telegramHtml = md.render(markdown);
|
|
901
|
+
// sanitize-html transformTags cannot insert text content, so list markers
|
|
902
|
+
// are injected into the HTML as text before sanitizing.
|
|
903
|
+
telegramHtml = telegramHtml.replace(/<ol[^>]*>[\s\S]*?<\/ol>/g, (block) => {
|
|
904
|
+
let counter = 0;
|
|
905
|
+
return block.replace(/<li>/g, () => `<li>${++counter}. `);
|
|
906
|
+
});
|
|
907
|
+
telegramHtml = telegramHtml.replace(/<li>(?!\d+\. )/g, "<li>• ");
|
|
898
908
|
telegramHtml = sanitizeHtml(telegramHtml, {
|
|
899
909
|
allowedTags: [
|
|
900
910
|
"b",
|
|
@@ -923,7 +933,7 @@ const toPlainString = async (content) => {
|
|
|
923
933
|
em: "",
|
|
924
934
|
p: () => "",
|
|
925
935
|
ul: () => "",
|
|
926
|
-
li: () => "
|
|
936
|
+
li: () => "",
|
|
927
937
|
ol: () => "",
|
|
928
938
|
hr: () => "\n",
|
|
929
939
|
br: () => "\n",
|
|
@@ -1192,6 +1202,10 @@ class OptimizerTemplateService {
|
|
|
1192
1202
|
``,
|
|
1193
1203
|
` await dumpJson(resultId, messages, result);`,
|
|
1194
1204
|
``,
|
|
1205
|
+
` if (result.position === "wait") {`,
|
|
1206
|
+
` return null;`,
|
|
1207
|
+
` }`,
|
|
1208
|
+
``,
|
|
1195
1209
|
` result.id = resultId;`,
|
|
1196
1210
|
``,
|
|
1197
1211
|
` return result;`,
|
|
@@ -1878,8 +1892,9 @@ const GET_STRATEGY_DATA_FN = async (symbol, self) => {
|
|
|
1878
1892
|
const strategyList = [];
|
|
1879
1893
|
const totalSources = self.params.rangeTrain.length * self.params.source.length;
|
|
1880
1894
|
let processedSources = 0;
|
|
1881
|
-
for (const { startDate, endDate } of self.params.rangeTrain) {
|
|
1895
|
+
for (const { note, startDate, endDate } of self.params.rangeTrain) {
|
|
1882
1896
|
const messageList = [];
|
|
1897
|
+
const sourceNames = [];
|
|
1883
1898
|
for (const source of self.params.source) {
|
|
1884
1899
|
// Emit progress event at the start of processing each source
|
|
1885
1900
|
await self.onProgress({
|
|
@@ -1907,6 +1922,7 @@ const GET_STRATEGY_DATA_FN = async (symbol, self) => {
|
|
|
1907
1922
|
role: "assistant",
|
|
1908
1923
|
content: assistantContent,
|
|
1909
1924
|
});
|
|
1925
|
+
sourceNames.push(DEFAULT_SOURCE_NAME);
|
|
1910
1926
|
processedSources++;
|
|
1911
1927
|
}
|
|
1912
1928
|
else {
|
|
@@ -1928,18 +1944,19 @@ const GET_STRATEGY_DATA_FN = async (symbol, self) => {
|
|
|
1928
1944
|
role: "assistant",
|
|
1929
1945
|
content: assistantContent,
|
|
1930
1946
|
});
|
|
1947
|
+
sourceNames.push(name || DEFAULT_SOURCE_NAME);
|
|
1931
1948
|
processedSources++;
|
|
1932
1949
|
}
|
|
1933
|
-
const name = "name" in source
|
|
1934
|
-
? source.name || DEFAULT_SOURCE_NAME
|
|
1935
|
-
: DEFAULT_SOURCE_NAME;
|
|
1936
|
-
strategyList.push({
|
|
1937
|
-
symbol,
|
|
1938
|
-
name,
|
|
1939
|
-
messages: messageList,
|
|
1940
|
-
strategy: await self.params.getPrompt(symbol, messageList),
|
|
1941
|
-
});
|
|
1942
1950
|
}
|
|
1951
|
+
// One strategy per training range: getPrompt receives the complete
|
|
1952
|
+
// conversation history with all sources (see IOptimizerSchema.getPrompt).
|
|
1953
|
+
// Messages are snapshotted so later ranges cannot mutate this strategy.
|
|
1954
|
+
strategyList.push({
|
|
1955
|
+
symbol,
|
|
1956
|
+
name: note || sourceNames.join("+") || DEFAULT_SOURCE_NAME,
|
|
1957
|
+
messages: [...messageList],
|
|
1958
|
+
strategy: await self.params.getPrompt(symbol, messageList),
|
|
1959
|
+
});
|
|
1943
1960
|
}
|
|
1944
1961
|
// Emit final progress event (100%)
|
|
1945
1962
|
await self.onProgress({
|
|
@@ -2045,7 +2062,7 @@ const GET_STRATEGY_CODE_FN = async (symbol, self) => {
|
|
|
2045
2062
|
const GET_STRATEGY_DUMP_FN = async (symbol, path, self) => {
|
|
2046
2063
|
const report = await self.getCode(symbol);
|
|
2047
2064
|
try {
|
|
2048
|
-
const dir = join(process.cwd(), path);
|
|
2065
|
+
const dir = isAbsolute(path) ? path : join(process.cwd(), path);
|
|
2049
2066
|
await mkdir(dir, { recursive: true });
|
|
2050
2067
|
const filename = `${self.params.optimizerName}_${symbol}.mjs`;
|
|
2051
2068
|
const filepath = join(dir, filename);
|
|
@@ -2435,17 +2452,17 @@ var InferenceName$1 = InferenceName;
|
|
|
2435
2452
|
* });
|
|
2436
2453
|
* ```
|
|
2437
2454
|
*/
|
|
2438
|
-
const
|
|
2455
|
+
const GET_CLIENT_FN$9 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
2456
|
+
baseURL: "https://api.x.ai/v1",
|
|
2457
|
+
apiKey,
|
|
2458
|
+
}));
|
|
2459
|
+
const getGrok = () => {
|
|
2439
2460
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
2440
2461
|
if (Array.isArray(apiKey)) {
|
|
2441
|
-
getGrok.clear();
|
|
2442
2462
|
throw new Error("Grok provider does not support token rotation");
|
|
2443
2463
|
}
|
|
2444
|
-
return
|
|
2445
|
-
|
|
2446
|
-
apiKey: apiKey,
|
|
2447
|
-
});
|
|
2448
|
-
});
|
|
2464
|
+
return GET_CLIENT_FN$9(apiKey);
|
|
2465
|
+
};
|
|
2449
2466
|
|
|
2450
2467
|
/**
|
|
2451
2468
|
* Global configuration parameters for the Ollama package.
|
|
@@ -2581,7 +2598,7 @@ class GrokProvider {
|
|
|
2581
2598
|
},
|
|
2582
2599
|
});
|
|
2583
2600
|
const result = {
|
|
2584
|
-
content: content,
|
|
2601
|
+
content: content ?? "",
|
|
2585
2602
|
mode,
|
|
2586
2603
|
agentName,
|
|
2587
2604
|
role,
|
|
@@ -2741,8 +2758,8 @@ class GrokProvider {
|
|
|
2741
2758
|
Authorization: `Bearer ${this.contextService.context.apiKey}`,
|
|
2742
2759
|
},
|
|
2743
2760
|
body: JSON.stringify({
|
|
2761
|
+
model: this.contextService.context.model,
|
|
2744
2762
|
messages,
|
|
2745
|
-
context: this.contextService.context,
|
|
2746
2763
|
max_tokens: 5000,
|
|
2747
2764
|
response_format: format,
|
|
2748
2765
|
}),
|
|
@@ -3128,7 +3145,12 @@ class HfProvider {
|
|
|
3128
3145
|
continue;
|
|
3129
3146
|
}
|
|
3130
3147
|
set(validation.data, "_thinking", reasoning_content);
|
|
3131
|
-
|
|
3148
|
+
{
|
|
3149
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
3150
|
+
// markdown files and forwarded to downstream consumers.
|
|
3151
|
+
const { inference, model } = this.contextService.context;
|
|
3152
|
+
set(validation.data, "_context", { inference, model });
|
|
3153
|
+
}
|
|
3132
3154
|
const result = {
|
|
3133
3155
|
role: "assistant",
|
|
3134
3156
|
content: JSON.stringify(validation.data),
|
|
@@ -3163,10 +3185,12 @@ class HfProvider {
|
|
|
3163
3185
|
* @throws Error if no API keys are provided in context
|
|
3164
3186
|
*/
|
|
3165
3187
|
class OllamaWrapper {
|
|
3166
|
-
constructor(_config) {
|
|
3188
|
+
constructor(_config, apiKeys) {
|
|
3167
3189
|
this._config = _config;
|
|
3168
|
-
|
|
3169
|
-
|
|
3190
|
+
if (!apiKeys?.length) {
|
|
3191
|
+
throw new Error("OllamaRotate required apiKey[] to process token rotation");
|
|
3192
|
+
}
|
|
3193
|
+
this._chatFn = RoundRobin.create(apiKeys, (token) => {
|
|
3170
3194
|
const ollama = new Ollama({
|
|
3171
3195
|
...this._config,
|
|
3172
3196
|
headers: {
|
|
@@ -3182,9 +3206,6 @@ class OllamaWrapper {
|
|
|
3182
3206
|
}
|
|
3183
3207
|
};
|
|
3184
3208
|
});
|
|
3185
|
-
if (!engine$1.contextService.context.apiKey) {
|
|
3186
|
-
throw new Error("OllamaRotate required apiKey[] to process token rotation");
|
|
3187
|
-
}
|
|
3188
3209
|
}
|
|
3189
3210
|
/**
|
|
3190
3211
|
* Executes a chat request with automatic token rotation.
|
|
@@ -3217,10 +3238,28 @@ class OllamaWrapper {
|
|
|
3217
3238
|
* // Next request will use a different API key
|
|
3218
3239
|
* ```
|
|
3219
3240
|
*/
|
|
3220
|
-
const
|
|
3241
|
+
const GET_WRAPPER_FN = memoize(([apiKeys]) => apiKeys.join(","), (apiKeys) => new OllamaWrapper({
|
|
3221
3242
|
host: "https://ollama.com",
|
|
3222
|
-
}));
|
|
3243
|
+
}, apiKeys));
|
|
3244
|
+
const getOllamaRotate = () => {
|
|
3245
|
+
const apiKey = engine$1.contextService.context.apiKey;
|
|
3246
|
+
if (!Array.isArray(apiKey)) {
|
|
3247
|
+
throw new Error("OllamaRotate required apiKey[] to process token rotation");
|
|
3248
|
+
}
|
|
3249
|
+
return GET_WRAPPER_FN(apiKey);
|
|
3250
|
+
};
|
|
3223
3251
|
|
|
3252
|
+
const GET_CLIENT_FN$8 = memoize(([apiKey]) => `${apiKey ?? ""}`, (apiKey) => {
|
|
3253
|
+
if (!apiKey) {
|
|
3254
|
+
return new Ollama();
|
|
3255
|
+
}
|
|
3256
|
+
return new Ollama({
|
|
3257
|
+
host: "https://ollama.com",
|
|
3258
|
+
headers: {
|
|
3259
|
+
Authorization: `Bearer ${apiKey}`,
|
|
3260
|
+
},
|
|
3261
|
+
});
|
|
3262
|
+
});
|
|
3224
3263
|
/**
|
|
3225
3264
|
* Creates and caches an Ollama client with flexible configuration.
|
|
3226
3265
|
*
|
|
@@ -3266,21 +3305,13 @@ const getOllamaRotate = singleshot(() => new OllamaWrapper({
|
|
|
3266
3305
|
* });
|
|
3267
3306
|
* ```
|
|
3268
3307
|
*/
|
|
3269
|
-
const getOllama =
|
|
3308
|
+
const getOllama = () => {
|
|
3270
3309
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
3271
3310
|
if (Array.isArray(apiKey)) {
|
|
3272
3311
|
return getOllamaRotate();
|
|
3273
3312
|
}
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
}
|
|
3277
|
-
return new Ollama({
|
|
3278
|
-
host: "https://ollama.com",
|
|
3279
|
-
headers: {
|
|
3280
|
-
Authorization: `Bearer ${apiKey}`,
|
|
3281
|
-
},
|
|
3282
|
-
});
|
|
3283
|
-
});
|
|
3313
|
+
return GET_CLIENT_FN$8(apiKey);
|
|
3314
|
+
};
|
|
3284
3315
|
|
|
3285
3316
|
/**
|
|
3286
3317
|
* Maximum number of retry attempts for outline completion when model fails to use tools correctly.
|
|
@@ -3614,7 +3645,12 @@ class OllamaProvider {
|
|
|
3614
3645
|
attempt++;
|
|
3615
3646
|
continue;
|
|
3616
3647
|
}
|
|
3617
|
-
|
|
3648
|
+
{
|
|
3649
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
3650
|
+
// markdown files and forwarded to downstream consumers.
|
|
3651
|
+
const { inference, model } = this.contextService.context;
|
|
3652
|
+
set(validation.data, "_context", { inference, model });
|
|
3653
|
+
}
|
|
3618
3654
|
const result = {
|
|
3619
3655
|
role: "assistant",
|
|
3620
3656
|
content: JSON.stringify(validation.data),
|
|
@@ -3633,6 +3669,10 @@ class OllamaProvider {
|
|
|
3633
3669
|
}
|
|
3634
3670
|
}
|
|
3635
3671
|
|
|
3672
|
+
const GET_CLIENT_FN$7 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
3673
|
+
baseURL: "https://api.anthropic.com/v1/",
|
|
3674
|
+
apiKey,
|
|
3675
|
+
}));
|
|
3636
3676
|
/**
|
|
3637
3677
|
* Creates and caches an OpenAI-compatible client for Claude (Anthropic) API.
|
|
3638
3678
|
*
|
|
@@ -3660,17 +3700,13 @@ class OllamaProvider {
|
|
|
3660
3700
|
* });
|
|
3661
3701
|
* ```
|
|
3662
3702
|
*/
|
|
3663
|
-
const getClaude =
|
|
3703
|
+
const getClaude = () => {
|
|
3664
3704
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
3665
3705
|
if (Array.isArray(apiKey)) {
|
|
3666
|
-
getClaude.clear();
|
|
3667
3706
|
throw new Error("Claude provider does not support token rotation");
|
|
3668
3707
|
}
|
|
3669
|
-
return
|
|
3670
|
-
|
|
3671
|
-
apiKey,
|
|
3672
|
-
});
|
|
3673
|
-
});
|
|
3708
|
+
return GET_CLIENT_FN$7(apiKey);
|
|
3709
|
+
};
|
|
3674
3710
|
|
|
3675
3711
|
/**
|
|
3676
3712
|
* Maximum number of retry attempts for outline completion when model fails to use tools correctly.
|
|
@@ -3773,7 +3809,7 @@ class ClaudeProvider {
|
|
|
3773
3809
|
}
|
|
3774
3810
|
const { choices: [{ message: { content, role, tool_calls }, },], } = await claude.chat.completions.create(requestOptions);
|
|
3775
3811
|
const result = {
|
|
3776
|
-
content: content,
|
|
3812
|
+
content: content ?? "",
|
|
3777
3813
|
mode,
|
|
3778
3814
|
agentName,
|
|
3779
3815
|
role,
|
|
@@ -3854,7 +3890,7 @@ class ClaudeProvider {
|
|
|
3854
3890
|
};
|
|
3855
3891
|
// Debug logging
|
|
3856
3892
|
if (GLOBAL_CONFIG.CC_ENABLE_DEBUG) {
|
|
3857
|
-
await fs.appendFile("./
|
|
3893
|
+
await fs.appendFile("./debug_claude_provider_stream.txt", JSON.stringify({ params, answer: result }, null, 2) + "\n\n");
|
|
3858
3894
|
}
|
|
3859
3895
|
return result;
|
|
3860
3896
|
}
|
|
@@ -3957,7 +3993,12 @@ class ClaudeProvider {
|
|
|
3957
3993
|
attempt++;
|
|
3958
3994
|
continue;
|
|
3959
3995
|
}
|
|
3960
|
-
|
|
3996
|
+
{
|
|
3997
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
3998
|
+
// markdown files and forwarded to downstream consumers.
|
|
3999
|
+
const { inference, model } = this.contextService.context;
|
|
4000
|
+
set(validation.data, "_context", { inference, model });
|
|
4001
|
+
}
|
|
3961
4002
|
const result = {
|
|
3962
4003
|
role: "assistant",
|
|
3963
4004
|
content: JSON.stringify(validation.data),
|
|
@@ -3976,6 +4017,9 @@ class ClaudeProvider {
|
|
|
3976
4017
|
}
|
|
3977
4018
|
}
|
|
3978
4019
|
|
|
4020
|
+
const GET_CLIENT_FN$6 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
4021
|
+
apiKey,
|
|
4022
|
+
}));
|
|
3979
4023
|
/**
|
|
3980
4024
|
* Creates and caches an OpenAI client for OpenAI API.
|
|
3981
4025
|
*
|
|
@@ -4003,16 +4047,13 @@ class ClaudeProvider {
|
|
|
4003
4047
|
* });
|
|
4004
4048
|
* ```
|
|
4005
4049
|
*/
|
|
4006
|
-
const getOpenAi =
|
|
4050
|
+
const getOpenAi = () => {
|
|
4007
4051
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
4008
4052
|
if (Array.isArray(apiKey)) {
|
|
4009
|
-
getOpenAi.clear();
|
|
4010
4053
|
throw new Error("OpenAI provider does not support token rotation");
|
|
4011
4054
|
}
|
|
4012
|
-
return
|
|
4013
|
-
|
|
4014
|
-
});
|
|
4015
|
-
});
|
|
4055
|
+
return GET_CLIENT_FN$6(apiKey);
|
|
4056
|
+
};
|
|
4016
4057
|
|
|
4017
4058
|
/**
|
|
4018
4059
|
* Provider for OpenAI GPT models (GPT-4, GPT-4 Turbo, GPT-3.5, etc.).
|
|
@@ -4129,7 +4170,7 @@ class GPT5Provider {
|
|
|
4129
4170
|
tools: tools,
|
|
4130
4171
|
});
|
|
4131
4172
|
const result = {
|
|
4132
|
-
content: content,
|
|
4173
|
+
content: content ?? "",
|
|
4133
4174
|
mode,
|
|
4134
4175
|
agentName,
|
|
4135
4176
|
role,
|
|
@@ -4354,17 +4395,17 @@ class GPT5Provider {
|
|
|
4354
4395
|
* });
|
|
4355
4396
|
* ```
|
|
4356
4397
|
*/
|
|
4357
|
-
const
|
|
4398
|
+
const GET_CLIENT_FN$5 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
4399
|
+
baseURL: "https://api.deepseek.com",
|
|
4400
|
+
apiKey,
|
|
4401
|
+
}));
|
|
4402
|
+
const getDeepseek = () => {
|
|
4358
4403
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
4359
4404
|
if (Array.isArray(apiKey)) {
|
|
4360
|
-
getDeepseek.clear();
|
|
4361
4405
|
throw new Error("Deepseek provider does not support token rotation");
|
|
4362
4406
|
}
|
|
4363
|
-
return
|
|
4364
|
-
|
|
4365
|
-
apiKey: apiKey,
|
|
4366
|
-
});
|
|
4367
|
-
});
|
|
4407
|
+
return GET_CLIENT_FN$5(apiKey);
|
|
4408
|
+
};
|
|
4368
4409
|
|
|
4369
4410
|
/**
|
|
4370
4411
|
* Maximum number of retry attempts for outline completion.
|
|
@@ -4447,7 +4488,7 @@ class DeepseekProvider {
|
|
|
4447
4488
|
tools: formattedTools?.length ? formattedTools : undefined,
|
|
4448
4489
|
});
|
|
4449
4490
|
const result = {
|
|
4450
|
-
content: content,
|
|
4491
|
+
content: content ?? "",
|
|
4451
4492
|
mode,
|
|
4452
4493
|
agentName,
|
|
4453
4494
|
role,
|
|
@@ -4630,7 +4671,12 @@ class DeepseekProvider {
|
|
|
4630
4671
|
attempt++;
|
|
4631
4672
|
continue;
|
|
4632
4673
|
}
|
|
4633
|
-
|
|
4674
|
+
{
|
|
4675
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
4676
|
+
// markdown files and forwarded to downstream consumers.
|
|
4677
|
+
const { inference, model } = this.contextService.context;
|
|
4678
|
+
set(validation.data, "_context", { inference, model });
|
|
4679
|
+
}
|
|
4634
4680
|
const result = {
|
|
4635
4681
|
role: "assistant",
|
|
4636
4682
|
content: JSON.stringify(validation.data),
|
|
@@ -4676,17 +4722,17 @@ class DeepseekProvider {
|
|
|
4676
4722
|
* });
|
|
4677
4723
|
* ```
|
|
4678
4724
|
*/
|
|
4679
|
-
const
|
|
4725
|
+
const GET_CLIENT_FN$4 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
4726
|
+
baseURL: "https://api.mistral.ai/v1",
|
|
4727
|
+
apiKey,
|
|
4728
|
+
}));
|
|
4729
|
+
const getMistral = () => {
|
|
4680
4730
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
4681
4731
|
if (Array.isArray(apiKey)) {
|
|
4682
|
-
getMistral.clear();
|
|
4683
4732
|
throw new Error("Mistral provider does not support token rotation");
|
|
4684
4733
|
}
|
|
4685
|
-
return
|
|
4686
|
-
|
|
4687
|
-
apiKey: apiKey,
|
|
4688
|
-
});
|
|
4689
|
-
});
|
|
4734
|
+
return GET_CLIENT_FN$4(apiKey);
|
|
4735
|
+
};
|
|
4690
4736
|
|
|
4691
4737
|
/**
|
|
4692
4738
|
* Maximum number of retry attempts for outline completion.
|
|
@@ -4769,7 +4815,7 @@ class MistralProvider {
|
|
|
4769
4815
|
tools: formattedTools?.length ? formattedTools : undefined,
|
|
4770
4816
|
});
|
|
4771
4817
|
const result = {
|
|
4772
|
-
content: content,
|
|
4818
|
+
content: content ?? "",
|
|
4773
4819
|
mode,
|
|
4774
4820
|
agentName,
|
|
4775
4821
|
role,
|
|
@@ -4952,7 +4998,12 @@ class MistralProvider {
|
|
|
4952
4998
|
attempt++;
|
|
4953
4999
|
continue;
|
|
4954
5000
|
}
|
|
4955
|
-
|
|
5001
|
+
{
|
|
5002
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
5003
|
+
// markdown files and forwarded to downstream consumers.
|
|
5004
|
+
const { inference, model } = this.contextService.context;
|
|
5005
|
+
set(validation.data, "_context", { inference, model });
|
|
5006
|
+
}
|
|
4956
5007
|
const result = {
|
|
4957
5008
|
role: "assistant",
|
|
4958
5009
|
content: JSON.stringify(validation.data),
|
|
@@ -4998,17 +5049,17 @@ class MistralProvider {
|
|
|
4998
5049
|
* });
|
|
4999
5050
|
* ```
|
|
5000
5051
|
*/
|
|
5001
|
-
const
|
|
5052
|
+
const GET_CLIENT_FN$3 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
5053
|
+
baseURL: "https://api.perplexity.ai",
|
|
5054
|
+
apiKey,
|
|
5055
|
+
}));
|
|
5056
|
+
const getPerplexity = () => {
|
|
5002
5057
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
5003
5058
|
if (Array.isArray(apiKey)) {
|
|
5004
|
-
getPerplexity.clear();
|
|
5005
5059
|
throw new Error("Perplexity provider does not support token rotation");
|
|
5006
5060
|
}
|
|
5007
|
-
return
|
|
5008
|
-
|
|
5009
|
-
apiKey: apiKey,
|
|
5010
|
-
});
|
|
5011
|
-
});
|
|
5061
|
+
return GET_CLIENT_FN$3(apiKey);
|
|
5062
|
+
};
|
|
5012
5063
|
|
|
5013
5064
|
/**
|
|
5014
5065
|
* Provider for Perplexity AI models via OpenAI-compatible API.
|
|
@@ -5124,7 +5175,7 @@ class PerplexityProvider {
|
|
|
5124
5175
|
});
|
|
5125
5176
|
const { choices: [{ message: { content, role, tool_calls }, },], } = result;
|
|
5126
5177
|
const finalResult = {
|
|
5127
|
-
content: content,
|
|
5178
|
+
content: content ?? "",
|
|
5128
5179
|
mode,
|
|
5129
5180
|
agentName,
|
|
5130
5181
|
role,
|
|
@@ -5251,6 +5302,10 @@ class PerplexityProvider {
|
|
|
5251
5302
|
}
|
|
5252
5303
|
}
|
|
5253
5304
|
|
|
5305
|
+
const GET_CLIENT_FN$2 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
5306
|
+
baseURL: "https://api.cohere.ai/compatibility/v1",
|
|
5307
|
+
apiKey,
|
|
5308
|
+
}));
|
|
5254
5309
|
/**
|
|
5255
5310
|
* Creates and caches an OpenAI-compatible client for Cohere API.
|
|
5256
5311
|
*
|
|
@@ -5278,17 +5333,13 @@ class PerplexityProvider {
|
|
|
5278
5333
|
* });
|
|
5279
5334
|
* ```
|
|
5280
5335
|
*/
|
|
5281
|
-
const getCohere =
|
|
5336
|
+
const getCohere = () => {
|
|
5282
5337
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
5283
5338
|
if (Array.isArray(apiKey)) {
|
|
5284
|
-
getCohere.clear();
|
|
5285
5339
|
throw new Error("Cohere provider does not support token rotation");
|
|
5286
5340
|
}
|
|
5287
|
-
return
|
|
5288
|
-
|
|
5289
|
-
apiKey: apiKey,
|
|
5290
|
-
});
|
|
5291
|
-
});
|
|
5341
|
+
return GET_CLIENT_FN$2(apiKey);
|
|
5342
|
+
};
|
|
5292
5343
|
|
|
5293
5344
|
/**
|
|
5294
5345
|
* Provider for Cohere AI models via OpenAI-compatible API.
|
|
@@ -5398,7 +5449,7 @@ class CohereProvider {
|
|
|
5398
5449
|
});
|
|
5399
5450
|
const { choices: [{ message: { content, role, tool_calls }, },], } = result;
|
|
5400
5451
|
const finalResult = {
|
|
5401
|
-
content: content,
|
|
5452
|
+
content: content ?? "",
|
|
5402
5453
|
mode,
|
|
5403
5454
|
agentName,
|
|
5404
5455
|
role,
|
|
@@ -5678,7 +5729,7 @@ class AlibabaProvider {
|
|
|
5678
5729
|
});
|
|
5679
5730
|
const { choices: [{ message: { content, role, tool_calls }, },], } = responseData;
|
|
5680
5731
|
const result = {
|
|
5681
|
-
content: content,
|
|
5732
|
+
content: content ?? "",
|
|
5682
5733
|
mode,
|
|
5683
5734
|
agentName,
|
|
5684
5735
|
role,
|
|
@@ -5759,7 +5810,7 @@ class AlibabaProvider {
|
|
|
5759
5810
|
});
|
|
5760
5811
|
}
|
|
5761
5812
|
const result = {
|
|
5762
|
-
content: content,
|
|
5813
|
+
content: content ?? "",
|
|
5763
5814
|
mode,
|
|
5764
5815
|
agentName,
|
|
5765
5816
|
role,
|
|
@@ -5887,7 +5938,12 @@ class AlibabaProvider {
|
|
|
5887
5938
|
attempt++;
|
|
5888
5939
|
continue;
|
|
5889
5940
|
}
|
|
5890
|
-
|
|
5941
|
+
{
|
|
5942
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
5943
|
+
// markdown files and forwarded to downstream consumers.
|
|
5944
|
+
const { inference, model } = this.contextService.context;
|
|
5945
|
+
set(validation.data, "_context", { inference, model });
|
|
5946
|
+
}
|
|
5891
5947
|
const result = {
|
|
5892
5948
|
role: "assistant",
|
|
5893
5949
|
content: JSON.stringify(validation.data),
|
|
@@ -5906,6 +5962,10 @@ class AlibabaProvider {
|
|
|
5906
5962
|
}
|
|
5907
5963
|
}
|
|
5908
5964
|
|
|
5965
|
+
const GET_CLIENT_FN$1 = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new OpenAI({
|
|
5966
|
+
apiKey,
|
|
5967
|
+
baseURL: "https://api.z.ai/api/paas/v4/",
|
|
5968
|
+
}));
|
|
5909
5969
|
/**
|
|
5910
5970
|
* Creates and caches an OpenAI-compatible client for Z.ai GLM-4 API.
|
|
5911
5971
|
*
|
|
@@ -5948,17 +6008,13 @@ class AlibabaProvider {
|
|
|
5948
6008
|
* });
|
|
5949
6009
|
* ```
|
|
5950
6010
|
*/
|
|
5951
|
-
const getZAi =
|
|
6011
|
+
const getZAi = () => {
|
|
5952
6012
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
5953
6013
|
if (Array.isArray(apiKey)) {
|
|
5954
|
-
getZAi.clear();
|
|
5955
6014
|
throw new Error("Z.ai provider does not support token rotation");
|
|
5956
6015
|
}
|
|
5957
|
-
return
|
|
5958
|
-
|
|
5959
|
-
baseURL: "https://api.z.ai/api/paas/v4/"
|
|
5960
|
-
});
|
|
5961
|
-
});
|
|
6016
|
+
return GET_CLIENT_FN$1(apiKey);
|
|
6017
|
+
};
|
|
5962
6018
|
|
|
5963
6019
|
/**
|
|
5964
6020
|
* GLM-4 provider implementation for Z.ai API integration.
|
|
@@ -6090,7 +6146,7 @@ class GLM4Provider {
|
|
|
6090
6146
|
tools: tools,
|
|
6091
6147
|
});
|
|
6092
6148
|
const result = {
|
|
6093
|
-
content: content,
|
|
6149
|
+
content: content ?? "",
|
|
6094
6150
|
mode,
|
|
6095
6151
|
agentName,
|
|
6096
6152
|
role,
|
|
@@ -6324,16 +6380,16 @@ class GLM4Provider {
|
|
|
6324
6380
|
* });
|
|
6325
6381
|
* ```
|
|
6326
6382
|
*/
|
|
6327
|
-
const
|
|
6383
|
+
const GET_CLIENT_FN = memoize(([apiKey]) => `${apiKey}`, (apiKey) => new Groq({
|
|
6384
|
+
apiKey,
|
|
6385
|
+
}));
|
|
6386
|
+
const getGroq = () => {
|
|
6328
6387
|
const apiKey = engine$1.contextService.context.apiKey;
|
|
6329
6388
|
if (Array.isArray(apiKey)) {
|
|
6330
|
-
getGroq.clear();
|
|
6331
6389
|
throw new Error("Groq provider does not support token rotation");
|
|
6332
6390
|
}
|
|
6333
|
-
return
|
|
6334
|
-
|
|
6335
|
-
});
|
|
6336
|
-
});
|
|
6391
|
+
return GET_CLIENT_FN(apiKey);
|
|
6392
|
+
};
|
|
6337
6393
|
|
|
6338
6394
|
/**
|
|
6339
6395
|
* Maximum number of retry attempts for outline completion.
|
|
@@ -6416,7 +6472,7 @@ class GroqProvider {
|
|
|
6416
6472
|
tools: formattedTools?.length ? formattedTools : undefined,
|
|
6417
6473
|
});
|
|
6418
6474
|
const result = {
|
|
6419
|
-
content: content,
|
|
6475
|
+
content: content ?? "",
|
|
6420
6476
|
mode,
|
|
6421
6477
|
agentName,
|
|
6422
6478
|
role,
|
|
@@ -6594,7 +6650,12 @@ class GroqProvider {
|
|
|
6594
6650
|
attempt++;
|
|
6595
6651
|
continue;
|
|
6596
6652
|
}
|
|
6597
|
-
|
|
6653
|
+
{
|
|
6654
|
+
// apiKey must never reach the outline result: it gets dumped to
|
|
6655
|
+
// markdown files and forwarded to downstream consumers.
|
|
6656
|
+
const { inference, model } = this.contextService.context;
|
|
6657
|
+
set(validation.data, "_context", { inference, model });
|
|
6658
|
+
}
|
|
6598
6659
|
const result = {
|
|
6599
6660
|
role: "assistant",
|
|
6600
6661
|
content: JSON.stringify(validation.data),
|
|
@@ -6814,7 +6875,7 @@ addCompletion({
|
|
|
6814
6875
|
getCompletion: async (params) => {
|
|
6815
6876
|
const result = await LOCAL_RUNNER_FN$1(params);
|
|
6816
6877
|
if (typeof result === "symbol") {
|
|
6817
|
-
throw new Error(`${CompletionName.
|
|
6878
|
+
throw new Error(`${CompletionName.RunnerStreamCompletion} inference timeout`);
|
|
6818
6879
|
}
|
|
6819
6880
|
return result;
|
|
6820
6881
|
},
|
|
@@ -6882,7 +6943,7 @@ addCompletion({
|
|
|
6882
6943
|
* ```
|
|
6883
6944
|
*/
|
|
6884
6945
|
const ollama = (fn, model, apiKey) => {
|
|
6885
|
-
const wrappedFn = async (args) => {
|
|
6946
|
+
const wrappedFn = async (...args) => {
|
|
6886
6947
|
return await ContextService.runInContext(async () => {
|
|
6887
6948
|
return await fn(...args);
|
|
6888
6949
|
}, {
|
|
@@ -6914,7 +6975,7 @@ const ollama = (fn, model, apiKey) => {
|
|
|
6914
6975
|
* ```
|
|
6915
6976
|
*/
|
|
6916
6977
|
const grok = (fn, model, apiKey) => {
|
|
6917
|
-
const wrappedFn = async (args) => {
|
|
6978
|
+
const wrappedFn = async (...args) => {
|
|
6918
6979
|
return await ContextService.runInContext(async () => {
|
|
6919
6980
|
return await fn(...args);
|
|
6920
6981
|
}, {
|
|
@@ -6946,7 +7007,7 @@ const grok = (fn, model, apiKey) => {
|
|
|
6946
7007
|
* ```
|
|
6947
7008
|
*/
|
|
6948
7009
|
const hf = (fn, model, apiKey) => {
|
|
6949
|
-
const wrappedFn = async (args) => {
|
|
7010
|
+
const wrappedFn = async (...args) => {
|
|
6950
7011
|
return await ContextService.runInContext(async () => {
|
|
6951
7012
|
return await fn(...args);
|
|
6952
7013
|
}, {
|
|
@@ -6978,7 +7039,7 @@ const hf = (fn, model, apiKey) => {
|
|
|
6978
7039
|
* ```
|
|
6979
7040
|
*/
|
|
6980
7041
|
const claude = (fn, model, apiKey) => {
|
|
6981
|
-
const wrappedFn = async (args) => {
|
|
7042
|
+
const wrappedFn = async (...args) => {
|
|
6982
7043
|
return await ContextService.runInContext(async () => {
|
|
6983
7044
|
return await fn(...args);
|
|
6984
7045
|
}, {
|
|
@@ -7010,7 +7071,7 @@ const claude = (fn, model, apiKey) => {
|
|
|
7010
7071
|
* ```
|
|
7011
7072
|
*/
|
|
7012
7073
|
const gpt5 = (fn, model, apiKey) => {
|
|
7013
|
-
const wrappedFn = async (args) => {
|
|
7074
|
+
const wrappedFn = async (...args) => {
|
|
7014
7075
|
return await ContextService.runInContext(async () => {
|
|
7015
7076
|
return await fn(...args);
|
|
7016
7077
|
}, {
|
|
@@ -7042,7 +7103,7 @@ const gpt5 = (fn, model, apiKey) => {
|
|
|
7042
7103
|
* ```
|
|
7043
7104
|
*/
|
|
7044
7105
|
const deepseek = (fn, model, apiKey) => {
|
|
7045
|
-
const wrappedFn = async (args) => {
|
|
7106
|
+
const wrappedFn = async (...args) => {
|
|
7046
7107
|
return await ContextService.runInContext(async () => {
|
|
7047
7108
|
return await fn(...args);
|
|
7048
7109
|
}, {
|
|
@@ -7074,7 +7135,7 @@ const deepseek = (fn, model, apiKey) => {
|
|
|
7074
7135
|
* ```
|
|
7075
7136
|
*/
|
|
7076
7137
|
const mistral = (fn, model, apiKey) => {
|
|
7077
|
-
const wrappedFn = async (args) => {
|
|
7138
|
+
const wrappedFn = async (...args) => {
|
|
7078
7139
|
return await ContextService.runInContext(async () => {
|
|
7079
7140
|
return await fn(...args);
|
|
7080
7141
|
}, {
|
|
@@ -7106,7 +7167,7 @@ const mistral = (fn, model, apiKey) => {
|
|
|
7106
7167
|
* ```
|
|
7107
7168
|
*/
|
|
7108
7169
|
const perplexity = (fn, model, apiKey) => {
|
|
7109
|
-
const wrappedFn = async (args) => {
|
|
7170
|
+
const wrappedFn = async (...args) => {
|
|
7110
7171
|
return await ContextService.runInContext(async () => {
|
|
7111
7172
|
return await fn(...args);
|
|
7112
7173
|
}, {
|
|
@@ -7138,7 +7199,7 @@ const perplexity = (fn, model, apiKey) => {
|
|
|
7138
7199
|
* ```
|
|
7139
7200
|
*/
|
|
7140
7201
|
const cohere = (fn, model, apiKey) => {
|
|
7141
|
-
const wrappedFn = async (args) => {
|
|
7202
|
+
const wrappedFn = async (...args) => {
|
|
7142
7203
|
return await ContextService.runInContext(async () => {
|
|
7143
7204
|
return await fn(...args);
|
|
7144
7205
|
}, {
|
|
@@ -7170,7 +7231,7 @@ const cohere = (fn, model, apiKey) => {
|
|
|
7170
7231
|
* ```
|
|
7171
7232
|
*/
|
|
7172
7233
|
const alibaba = (fn, model, apiKey) => {
|
|
7173
|
-
const wrappedFn = async (args) => {
|
|
7234
|
+
const wrappedFn = async (...args) => {
|
|
7174
7235
|
return await ContextService.runInContext(async () => {
|
|
7175
7236
|
return await fn(...args);
|
|
7176
7237
|
}, {
|
|
@@ -7181,26 +7242,6 @@ const alibaba = (fn, model, apiKey) => {
|
|
|
7181
7242
|
};
|
|
7182
7243
|
return wrappedFn;
|
|
7183
7244
|
};
|
|
7184
|
-
/**
|
|
7185
|
-
* Wrap async function with Zhipu AI GLM-4 inference context.
|
|
7186
|
-
*
|
|
7187
|
-
* Creates a higher-order function that executes the provided async function
|
|
7188
|
-
* within a Zhipu AI GLM-4 inference context via OpenAI-compatible Z.ai API.
|
|
7189
|
-
*
|
|
7190
|
-
* @template T - Async function type
|
|
7191
|
-
* @param fn - Async function to wrap
|
|
7192
|
-
* @param model - GLM-4 model name (e.g., "glm-4-plus", "glm-4-air")
|
|
7193
|
-
* @param apiKey - Single API key or array of keys
|
|
7194
|
-
* @returns Wrapped function with same signature as input
|
|
7195
|
-
*
|
|
7196
|
-
* @example
|
|
7197
|
-
* ```typescript
|
|
7198
|
-
* import { glm4 } from '@backtest-kit/ollama';
|
|
7199
|
-
*
|
|
7200
|
-
* const wrappedFn = glm4(myAsyncFn, 'glm-4-plus', process.env.ZAI_API_KEY);
|
|
7201
|
-
* const result = await wrappedFn(args);
|
|
7202
|
-
* ```
|
|
7203
|
-
*/
|
|
7204
7245
|
/**
|
|
7205
7246
|
* Wrap async function with Groq inference context.
|
|
7206
7247
|
*
|
|
@@ -7222,7 +7263,7 @@ const alibaba = (fn, model, apiKey) => {
|
|
|
7222
7263
|
* ```
|
|
7223
7264
|
*/
|
|
7224
7265
|
const groq = (fn, model, apiKey) => {
|
|
7225
|
-
const wrappedFn = async (args) => {
|
|
7266
|
+
const wrappedFn = async (...args) => {
|
|
7226
7267
|
return await ContextService.runInContext(async () => {
|
|
7227
7268
|
return await fn(...args);
|
|
7228
7269
|
}, {
|
|
@@ -7233,8 +7274,28 @@ const groq = (fn, model, apiKey) => {
|
|
|
7233
7274
|
};
|
|
7234
7275
|
return wrappedFn;
|
|
7235
7276
|
};
|
|
7277
|
+
/**
|
|
7278
|
+
* Wrap async function with Zhipu AI GLM-4 inference context.
|
|
7279
|
+
*
|
|
7280
|
+
* Creates a higher-order function that executes the provided async function
|
|
7281
|
+
* within a Zhipu AI GLM-4 inference context via OpenAI-compatible Z.ai API.
|
|
7282
|
+
*
|
|
7283
|
+
* @template T - Async function type
|
|
7284
|
+
* @param fn - Async function to wrap
|
|
7285
|
+
* @param model - GLM-4 model name (e.g., "glm-4-plus", "glm-4-air")
|
|
7286
|
+
* @param apiKey - Single API key or array of keys
|
|
7287
|
+
* @returns Wrapped function with same signature as input
|
|
7288
|
+
*
|
|
7289
|
+
* @example
|
|
7290
|
+
* ```typescript
|
|
7291
|
+
* import { glm4 } from '@backtest-kit/ollama';
|
|
7292
|
+
*
|
|
7293
|
+
* const wrappedFn = glm4(myAsyncFn, 'glm-4-plus', process.env.ZAI_API_KEY);
|
|
7294
|
+
* const result = await wrappedFn(args);
|
|
7295
|
+
* ```
|
|
7296
|
+
*/
|
|
7236
7297
|
const glm4 = (fn, model, apiKey) => {
|
|
7237
|
-
const wrappedFn = async (args) => {
|
|
7298
|
+
const wrappedFn = async (...args) => {
|
|
7238
7299
|
return await ContextService.runInContext(async () => {
|
|
7239
7300
|
return await fn(...args);
|
|
7240
7301
|
}, {
|
|
@@ -7287,7 +7348,8 @@ const DUMP_SIGNAL_METHOD_NAME = "dump.dumpSignal";
|
|
|
7287
7348
|
*
|
|
7288
7349
|
* @example
|
|
7289
7350
|
* ```typescript
|
|
7290
|
-
* import {
|
|
7351
|
+
* import { getCandles } from "backtest-kit";
|
|
7352
|
+
* import { dumpSignal } from "@backtest-kit/ollama";
|
|
7291
7353
|
* import { v4 as uuid } from "uuid";
|
|
7292
7354
|
*
|
|
7293
7355
|
* addStrategy({
|
|
@@ -7434,7 +7496,9 @@ async function validate(args = {}) {
|
|
|
7434
7496
|
return await validateInternal(args);
|
|
7435
7497
|
}
|
|
7436
7498
|
|
|
7437
|
-
|
|
7499
|
+
// Symbol.for keeps the brand check working when CJS and ESM copies of the
|
|
7500
|
+
// package end up in the same process (dual-package hazard).
|
|
7501
|
+
const MODULE_TYPE_SYMBOL = Symbol.for("backtest-kit.ollama.module-type");
|
|
7438
7502
|
class Module {
|
|
7439
7503
|
constructor(path, baseDir) {
|
|
7440
7504
|
this.path = path;
|
|
@@ -7495,15 +7559,15 @@ async function commitPrompt(source, history) {
|
|
|
7495
7559
|
const systemPrompts = await engine$1.resolvePromptService.getSystemPrompt(prompt, symbol, strategyName, exchangeName, frameName, isBacktest);
|
|
7496
7560
|
const userPrompt = await engine$1.resolvePromptService.getUserPrompt(prompt, symbol, strategyName, exchangeName, frameName, isBacktest);
|
|
7497
7561
|
if (systemPrompts.length > 0) {
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7562
|
+
// Single unshift with spread: per-item unshift in a loop would REVERSE
|
|
7563
|
+
// the system prompt order at the head of the history.
|
|
7564
|
+
const systemMessages = systemPrompts
|
|
7565
|
+
.filter((content) => content.trim())
|
|
7566
|
+
.map((content) => ({
|
|
7567
|
+
role: "system",
|
|
7568
|
+
content,
|
|
7569
|
+
}));
|
|
7570
|
+
history.unshift(...systemMessages);
|
|
7507
7571
|
}
|
|
7508
7572
|
if (userPrompt && userPrompt.trim()) {
|
|
7509
7573
|
history.push({
|
|
@@ -7683,7 +7747,7 @@ const LIST_OPTIMIZERS_METHOD_NAME = "list.listOptimizerSchema";
|
|
|
7683
7747
|
*
|
|
7684
7748
|
* @example
|
|
7685
7749
|
* ```typescript
|
|
7686
|
-
* import { listOptimizers, addOptimizer } from "backtest-kit";
|
|
7750
|
+
* import { listOptimizers, addOptimizer } from "@backtest-kit/ollama";
|
|
7687
7751
|
*
|
|
7688
7752
|
* addOptimizer({
|
|
7689
7753
|
* optimizerName: "llm-strategy-generator",
|