@p-sw/brainbox 0.1.2-alpha.11 → 0.1.2-alpha.13
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/index.js +281 -156
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -489,15 +489,100 @@ function stripThinkTags(content) {
|
|
|
489
489
|
}
|
|
490
490
|
function parseModelJson(content) {
|
|
491
491
|
const cleaned = stripThinkTags(content);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
492
|
+
const candidates = [cleaned];
|
|
493
|
+
const fence = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
494
|
+
if (fence?.[1])
|
|
495
|
+
candidates.push(fence[1].trim());
|
|
496
|
+
const extracted = extractJsonSlice(cleaned);
|
|
497
|
+
if (extracted)
|
|
498
|
+
candidates.push(extracted);
|
|
499
|
+
let lastErr;
|
|
500
|
+
for (const candidate of candidates) {
|
|
501
|
+
try {
|
|
502
|
+
return decodeJsonValue(candidate);
|
|
503
|
+
} catch (err) {
|
|
504
|
+
lastErr = err;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
throw lastErr instanceof Error ? lastErr : new Error("Failed to parse model JSON");
|
|
508
|
+
}
|
|
509
|
+
function decodeJsonValue(text) {
|
|
510
|
+
let value = JSON.parse(text);
|
|
511
|
+
for (let i = 0;i < 2 && typeof value === "string"; i++) {
|
|
512
|
+
const inner = value.trim();
|
|
513
|
+
if (!(inner.startsWith("{") && inner.endsWith("}") || inner.startsWith("[") && inner.endsWith("]") || inner.startsWith('"') && inner.endsWith('"'))) {
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
value = JSON.parse(inner);
|
|
517
|
+
}
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
function extractJsonSlice(text) {
|
|
521
|
+
const start = text.search(/[\{\[]/);
|
|
522
|
+
if (start < 0)
|
|
523
|
+
return;
|
|
524
|
+
const open = text[start];
|
|
525
|
+
const close = open === "{" ? "}" : "]";
|
|
526
|
+
let depth = 0;
|
|
527
|
+
let inString = false;
|
|
528
|
+
let escape = false;
|
|
529
|
+
for (let i = start;i < text.length; i++) {
|
|
530
|
+
const ch = text[i];
|
|
531
|
+
if (inString) {
|
|
532
|
+
if (escape) {
|
|
533
|
+
escape = false;
|
|
534
|
+
} else if (ch === "\\") {
|
|
535
|
+
escape = true;
|
|
536
|
+
} else if (ch === '"') {
|
|
537
|
+
inString = false;
|
|
538
|
+
}
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (ch === '"') {
|
|
542
|
+
inString = true;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (ch === open)
|
|
546
|
+
depth++;
|
|
547
|
+
else if (ch === close) {
|
|
548
|
+
depth--;
|
|
549
|
+
if (depth === 0)
|
|
550
|
+
return text.slice(start, i + 1);
|
|
498
551
|
}
|
|
499
|
-
throw first;
|
|
500
552
|
}
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
function schemaToolName(name) {
|
|
556
|
+
const cleaned = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^(\d)/, "_$1");
|
|
557
|
+
return cleaned.length > 0 ? cleaned : "submit_result";
|
|
558
|
+
}
|
|
559
|
+
function buildStructuredJsonRequest(options) {
|
|
560
|
+
const toolName = schemaToolName(options.jsonSchemaName);
|
|
561
|
+
return {
|
|
562
|
+
toolName,
|
|
563
|
+
tool: {
|
|
564
|
+
name: toolName,
|
|
565
|
+
description: `Submit the structured ${options.jsonSchemaName} result.`,
|
|
566
|
+
parameters: options.jsonSchema ?? {
|
|
567
|
+
type: "object",
|
|
568
|
+
additionalProperties: true
|
|
569
|
+
}
|
|
570
|
+
},
|
|
571
|
+
instruction: `${options.instruction}
|
|
572
|
+
|
|
573
|
+
You MUST call the \`${toolName}\` tool exactly once with the complete answer.
|
|
574
|
+
Do not write the JSON as plain text or inside a markdown code fence.`
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function parseStructuredJsonResult(choice, toolName) {
|
|
578
|
+
const call = choice.message.toolCalls?.find((c) => c.function.name === toolName) ?? choice.message.toolCalls?.[0];
|
|
579
|
+
if (call?.function.arguments) {
|
|
580
|
+
return parseModelJson(call.function.arguments);
|
|
581
|
+
}
|
|
582
|
+
if (choice.message.content) {
|
|
583
|
+
return parseModelJson(choice.message.content);
|
|
584
|
+
}
|
|
585
|
+
throw new Error("Empty response from model");
|
|
501
586
|
}
|
|
502
587
|
function readAuthString(auth, key, envName) {
|
|
503
588
|
const fromAuth = typeof auth?.[key] === "string" ? auth[key] : undefined;
|
|
@@ -512,6 +597,42 @@ function readAuthString(auth, key, envName) {
|
|
|
512
597
|
}
|
|
513
598
|
|
|
514
599
|
class LLMExecutor {
|
|
600
|
+
async chatWithToolExecution(model, options) {
|
|
601
|
+
const messages = options.messages.slice();
|
|
602
|
+
const maxSteps = options.maxSteps ?? 20;
|
|
603
|
+
let last;
|
|
604
|
+
for (let step = 0;step < maxSteps; step += 1) {
|
|
605
|
+
log2.debug(`chatWithToolExecution: step ${step + 1}/${maxSteps} msgs=${messages.length}`);
|
|
606
|
+
last = await this.chatWithTools(model, {
|
|
607
|
+
instruction: options.instruction,
|
|
608
|
+
messages,
|
|
609
|
+
tools: options.tools,
|
|
610
|
+
caller: options.caller,
|
|
611
|
+
reasoningEffort: options.reasoningEffort,
|
|
612
|
+
parallelToolCalls: options.parallelToolCalls,
|
|
613
|
+
toolChoice: options.toolChoice
|
|
614
|
+
});
|
|
615
|
+
const toolCalls = last.message.toolCalls ?? [];
|
|
616
|
+
log2.debug(`chatWithToolExecution: step ${step + 1} toolCalls=${toolCalls.length}`);
|
|
617
|
+
if (toolCalls.length === 0)
|
|
618
|
+
return last;
|
|
619
|
+
messages.push({
|
|
620
|
+
role: "assistant",
|
|
621
|
+
content: last.message.content,
|
|
622
|
+
toolCalls
|
|
623
|
+
});
|
|
624
|
+
for (const call of toolCalls) {
|
|
625
|
+
const content = await options.executeTool(call);
|
|
626
|
+
messages.push({
|
|
627
|
+
role: "tool",
|
|
628
|
+
toolCallId: call.id,
|
|
629
|
+
content
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
log2.warn(`chatWithToolExecution: reached maxSteps (${maxSteps}) without final reply`);
|
|
634
|
+
return last;
|
|
635
|
+
}
|
|
515
636
|
static providers = [];
|
|
516
637
|
static registerProvider(p) {
|
|
517
638
|
LLMExecutor.providers.push(p);
|
|
@@ -781,6 +902,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
781
902
|
chatPath;
|
|
782
903
|
noBearerPrefix;
|
|
783
904
|
reasoningEffortInQuery;
|
|
905
|
+
supportsResponseFormat;
|
|
784
906
|
constructor(opts) {
|
|
785
907
|
super();
|
|
786
908
|
this.providerName = opts.providerName;
|
|
@@ -790,6 +912,7 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
790
912
|
this.chatPath = opts.chatPath ?? "/chat/completions";
|
|
791
913
|
this.noBearerPrefix = opts.noBearerPrefix ?? false;
|
|
792
914
|
this.reasoningEffortInQuery = opts.reasoningEffortInQuery ?? false;
|
|
915
|
+
this.supportsResponseFormat = opts.supportsResponseFormat ?? true;
|
|
793
916
|
this.models = {
|
|
794
917
|
conversation: opts.conversationModel,
|
|
795
918
|
identity: opts.identityModel
|
|
@@ -861,6 +984,9 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
861
984
|
return data;
|
|
862
985
|
}
|
|
863
986
|
async call(model, options) {
|
|
987
|
+
if ("jsonSchemaName" in options && !this.supportsResponseFormat) {
|
|
988
|
+
return this.callJsonViaTool(model, options);
|
|
989
|
+
}
|
|
864
990
|
const jsonMode = "jsonSchemaName" in options;
|
|
865
991
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
866
992
|
log4.debug(`call: provider=${this.providerName} model=${model} jsonSchema=${jsonMode ? options.jsonSchemaName : "-"} msgLen=${options.message.length}`);
|
|
@@ -886,6 +1012,19 @@ class OpenAICompatibleExecutor extends LLMExecutor {
|
|
|
886
1012
|
log4.debug(`call: response ${content.length} chars`);
|
|
887
1013
|
return jsonMode ? parseModelJson(content) : content;
|
|
888
1014
|
}
|
|
1015
|
+
async callJsonViaTool(model, options) {
|
|
1016
|
+
const { toolName, tool, instruction } = buildStructuredJsonRequest(options);
|
|
1017
|
+
log4.debug(`callJsonViaTool: provider=${this.providerName} model=${model} tool=${toolName}`);
|
|
1018
|
+
const choice = await this.chatWithTools(model, {
|
|
1019
|
+
caller: options.caller ?? options.jsonSchemaName,
|
|
1020
|
+
instruction,
|
|
1021
|
+
messages: [{ role: "user", content: options.message }],
|
|
1022
|
+
tools: [tool],
|
|
1023
|
+
reasoningEffort: "none",
|
|
1024
|
+
parallelToolCalls: false
|
|
1025
|
+
});
|
|
1026
|
+
return parseStructuredJsonResult(choice, toolName);
|
|
1027
|
+
}
|
|
889
1028
|
async chatWithTools(model, options) {
|
|
890
1029
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
891
1030
|
log4.debug(`chatWithTools: provider=${this.providerName} model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
@@ -1219,7 +1358,8 @@ function minimaxOpts(providerName, baseURL, opts) {
|
|
|
1219
1358
|
baseURL,
|
|
1220
1359
|
apiKey: opts.apiKey,
|
|
1221
1360
|
conversationModel: opts.conversationModel,
|
|
1222
|
-
identityModel: opts.identityModel
|
|
1361
|
+
identityModel: opts.identityModel,
|
|
1362
|
+
supportsResponseFormat: false
|
|
1223
1363
|
};
|
|
1224
1364
|
}
|
|
1225
1365
|
|
|
@@ -1295,7 +1435,8 @@ class LmStudioExecutor extends OpenAICompatibleExecutor {
|
|
|
1295
1435
|
baseURL: "http://127.0.0.1:1234/v1",
|
|
1296
1436
|
apiKey: opts.apiKey || "lm-studio",
|
|
1297
1437
|
conversationModel: opts.conversationModel,
|
|
1298
|
-
identityModel: opts.identityModel
|
|
1438
|
+
identityModel: opts.identityModel,
|
|
1439
|
+
supportsResponseFormat: false
|
|
1299
1440
|
});
|
|
1300
1441
|
}
|
|
1301
1442
|
}
|
|
@@ -1308,7 +1449,8 @@ class OllamaExecutor extends OpenAICompatibleExecutor {
|
|
|
1308
1449
|
baseURL: "http://127.0.0.1:11434/v1",
|
|
1309
1450
|
apiKey: opts.apiKey || "ollama",
|
|
1310
1451
|
conversationModel: opts.conversationModel,
|
|
1311
|
-
identityModel: opts.identityModel
|
|
1452
|
+
identityModel: opts.identityModel,
|
|
1453
|
+
supportsResponseFormat: false
|
|
1312
1454
|
});
|
|
1313
1455
|
}
|
|
1314
1456
|
}
|
|
@@ -1334,7 +1476,8 @@ class LlamaCppExecutor extends OpenAICompatibleExecutor {
|
|
|
1334
1476
|
baseURL: "http://127.0.0.1:8080/v1",
|
|
1335
1477
|
apiKey: opts.apiKey || "llama.cpp",
|
|
1336
1478
|
conversationModel: opts.conversationModel,
|
|
1337
|
-
identityModel: opts.identityModel
|
|
1479
|
+
identityModel: opts.identityModel,
|
|
1480
|
+
supportsResponseFormat: false
|
|
1338
1481
|
});
|
|
1339
1482
|
}
|
|
1340
1483
|
}
|
|
@@ -1573,17 +1716,17 @@ var AnthropicAuthSchema = z4.object({
|
|
|
1573
1716
|
apiVersion: z4.string().optional()
|
|
1574
1717
|
}).loose();
|
|
1575
1718
|
function toAnthropicMessages(messages) {
|
|
1576
|
-
let
|
|
1577
|
-
const
|
|
1719
|
+
let system2;
|
|
1720
|
+
const msgs2 = [];
|
|
1578
1721
|
for (const m of messages) {
|
|
1579
1722
|
if (m.role === "system") {
|
|
1580
|
-
|
|
1723
|
+
system2 = (system2 ? system2 + `
|
|
1581
1724
|
|
|
1582
1725
|
` : "") + m.content;
|
|
1583
1726
|
continue;
|
|
1584
1727
|
}
|
|
1585
1728
|
if (m.role === "user") {
|
|
1586
|
-
|
|
1729
|
+
msgs2.push({ role: "user", content: m.content });
|
|
1587
1730
|
continue;
|
|
1588
1731
|
}
|
|
1589
1732
|
if (m.role === "assistant") {
|
|
@@ -1606,11 +1749,11 @@ function toAnthropicMessages(messages) {
|
|
|
1606
1749
|
});
|
|
1607
1750
|
}
|
|
1608
1751
|
}
|
|
1609
|
-
|
|
1752
|
+
msgs2.push({ role: "assistant", content: blocks });
|
|
1610
1753
|
continue;
|
|
1611
1754
|
}
|
|
1612
1755
|
if (m.role === "tool") {
|
|
1613
|
-
|
|
1756
|
+
msgs2.push({
|
|
1614
1757
|
role: "user",
|
|
1615
1758
|
content: [
|
|
1616
1759
|
{
|
|
@@ -1622,7 +1765,7 @@ function toAnthropicMessages(messages) {
|
|
|
1622
1765
|
});
|
|
1623
1766
|
}
|
|
1624
1767
|
}
|
|
1625
|
-
return { system, msgs };
|
|
1768
|
+
return { system: system2, msgs: msgs2 };
|
|
1626
1769
|
}
|
|
1627
1770
|
function toAnthropicTool(t) {
|
|
1628
1771
|
return {
|
|
@@ -1669,10 +1812,25 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1669
1812
|
return JSON.parse(responseRaw);
|
|
1670
1813
|
}
|
|
1671
1814
|
async call(model, options) {
|
|
1672
|
-
|
|
1815
|
+
if ("jsonSchemaName" in options) {
|
|
1816
|
+
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
1817
|
+
instruction: options.instruction,
|
|
1818
|
+
jsonSchemaName: options.jsonSchemaName,
|
|
1819
|
+
jsonSchema: options.jsonSchema
|
|
1820
|
+
});
|
|
1821
|
+
const choice = await this.chatWithTools(model, {
|
|
1822
|
+
caller: options.caller ?? options.jsonSchemaName,
|
|
1823
|
+
instruction,
|
|
1824
|
+
messages: [{ role: "user", content: options.message }],
|
|
1825
|
+
tools: [tool],
|
|
1826
|
+
reasoningEffort: "none",
|
|
1827
|
+
toolChoice: { type: "tool", name: toolName }
|
|
1828
|
+
});
|
|
1829
|
+
return parseStructuredJsonResult(choice, toolName);
|
|
1830
|
+
}
|
|
1673
1831
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1674
1832
|
const log5 = logger.child("llm:anthropic");
|
|
1675
|
-
log5.debug(`call: model=${model} jsonSchema
|
|
1833
|
+
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
1676
1834
|
const outputCap = 4096;
|
|
1677
1835
|
const body = {
|
|
1678
1836
|
model,
|
|
@@ -1696,21 +1854,27 @@ class AnthropicExecutor extends LLMExecutor {
|
|
|
1696
1854
|
if (!text) {
|
|
1697
1855
|
throw new Error("Empty response from model");
|
|
1698
1856
|
}
|
|
1699
|
-
return
|
|
1857
|
+
return text;
|
|
1700
1858
|
}
|
|
1701
1859
|
async chatWithTools(model, options) {
|
|
1702
1860
|
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
1703
1861
|
const log5 = logger.child("llm:anthropic");
|
|
1704
1862
|
log5.debug(`chatWithTools: model=${model} msgs=${options.messages.length} tools=${options.tools.length}`);
|
|
1705
|
-
const { system, msgs } = toAnthropicMessages(options.messages);
|
|
1863
|
+
const { system: system2, msgs: msgs2 } = toAnthropicMessages(options.messages);
|
|
1706
1864
|
const outputCap = 4096;
|
|
1707
1865
|
const body = {
|
|
1708
1866
|
model,
|
|
1709
1867
|
max_tokens: outputCap,
|
|
1710
|
-
system:
|
|
1711
|
-
messages:
|
|
1868
|
+
system: system2 ?? options.instruction,
|
|
1869
|
+
messages: msgs2,
|
|
1712
1870
|
tools: options.tools.map(toAnthropicTool)
|
|
1713
1871
|
};
|
|
1872
|
+
if (options.toolChoice) {
|
|
1873
|
+
body["tool_choice"] = {
|
|
1874
|
+
type: "tool",
|
|
1875
|
+
name: options.toolChoice.name
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1714
1878
|
if (reasoning !== "none") {
|
|
1715
1879
|
const budget = REASONING_BUDGET[reasoning];
|
|
1716
1880
|
body["max_tokens"] = budget + outputCap;
|
|
@@ -1789,58 +1953,6 @@ function signRequest(opts) {
|
|
|
1789
1953
|
headers["X-Amz-Security-Token"] = opts.sessionToken;
|
|
1790
1954
|
return headers;
|
|
1791
1955
|
}
|
|
1792
|
-
function toAnthropicMessages2(messages) {
|
|
1793
|
-
let system;
|
|
1794
|
-
const msgs = [];
|
|
1795
|
-
for (const m of messages) {
|
|
1796
|
-
if (m.role === "system") {
|
|
1797
|
-
system = (system ? system + `
|
|
1798
|
-
|
|
1799
|
-
` : "") + m.content;
|
|
1800
|
-
continue;
|
|
1801
|
-
}
|
|
1802
|
-
if (m.role === "user") {
|
|
1803
|
-
msgs.push({ role: "user", content: [{ type: "text", text: m.content }] });
|
|
1804
|
-
continue;
|
|
1805
|
-
}
|
|
1806
|
-
if (m.role === "assistant") {
|
|
1807
|
-
const blocks = [];
|
|
1808
|
-
if (m.content)
|
|
1809
|
-
blocks.push({ type: "text", text: m.content });
|
|
1810
|
-
if (m.toolCalls) {
|
|
1811
|
-
for (const c of m.toolCalls) {
|
|
1812
|
-
let input = {};
|
|
1813
|
-
try {
|
|
1814
|
-
input = c.function.arguments ? JSON.parse(c.function.arguments) : {};
|
|
1815
|
-
} catch {
|
|
1816
|
-
input = {};
|
|
1817
|
-
}
|
|
1818
|
-
blocks.push({
|
|
1819
|
-
type: "tool_use",
|
|
1820
|
-
id: c.id,
|
|
1821
|
-
name: c.function.name,
|
|
1822
|
-
input
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
msgs.push({ role: "assistant", content: blocks });
|
|
1827
|
-
continue;
|
|
1828
|
-
}
|
|
1829
|
-
if (m.role === "tool") {
|
|
1830
|
-
msgs.push({
|
|
1831
|
-
role: "user",
|
|
1832
|
-
content: [
|
|
1833
|
-
{
|
|
1834
|
-
type: "tool_result",
|
|
1835
|
-
tool_use_id: m.toolCallId,
|
|
1836
|
-
content: [{ type: "text", text: m.content }]
|
|
1837
|
-
}
|
|
1838
|
-
]
|
|
1839
|
-
});
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
return { system, msgs };
|
|
1843
|
-
}
|
|
1844
1956
|
function toAnthropicTool2(t) {
|
|
1845
1957
|
return {
|
|
1846
1958
|
name: t.name,
|
|
@@ -1909,9 +2021,24 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1909
2021
|
return JSON.parse(responseRaw);
|
|
1910
2022
|
}
|
|
1911
2023
|
async call(model, options) {
|
|
1912
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
1913
2024
|
const log5 = logger.child("llm:bedrock");
|
|
1914
|
-
|
|
2025
|
+
if ("jsonSchemaName" in options) {
|
|
2026
|
+
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2027
|
+
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2028
|
+
instruction: options.instruction,
|
|
2029
|
+
jsonSchemaName: options.jsonSchemaName,
|
|
2030
|
+
jsonSchema: options.jsonSchema
|
|
2031
|
+
});
|
|
2032
|
+
const choice = await this.chatWithTools(model, {
|
|
2033
|
+
caller: options.caller ?? options.jsonSchemaName,
|
|
2034
|
+
instruction,
|
|
2035
|
+
messages: [{ role: "user", content: options.message }],
|
|
2036
|
+
tools: [tool],
|
|
2037
|
+
toolChoice: { type: "tool", name: toolName }
|
|
2038
|
+
});
|
|
2039
|
+
return parseStructuredJsonResult(choice, toolName);
|
|
2040
|
+
}
|
|
2041
|
+
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
1915
2042
|
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
1916
2043
|
throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
|
|
1917
2044
|
}
|
|
@@ -1926,7 +2053,7 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1926
2053
|
if (!text) {
|
|
1927
2054
|
throw new Error("Empty response from model");
|
|
1928
2055
|
}
|
|
1929
|
-
return
|
|
2056
|
+
return text;
|
|
1930
2057
|
}
|
|
1931
2058
|
async chatWithTools(model, options) {
|
|
1932
2059
|
const log5 = logger.child("llm:bedrock");
|
|
@@ -1934,7 +2061,6 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1934
2061
|
if (!model.startsWith("anthropic.") && !model.startsWith("us.anthropic.")) {
|
|
1935
2062
|
throw new Error(`bedrock provider currently only supports Anthropic models (got ${model})`);
|
|
1936
2063
|
}
|
|
1937
|
-
const { system, msgs } = toAnthropicMessages2(options.messages);
|
|
1938
2064
|
const body = {
|
|
1939
2065
|
anthropic_version: "bedrock-2023-05-31",
|
|
1940
2066
|
max_tokens: 4096,
|
|
@@ -1942,6 +2068,12 @@ class BedrockExecutor extends LLMExecutor {
|
|
|
1942
2068
|
messages: msgs,
|
|
1943
2069
|
tools: options.tools.map(toAnthropicTool2)
|
|
1944
2070
|
};
|
|
2071
|
+
if (options.toolChoice) {
|
|
2072
|
+
body.tool_choice = {
|
|
2073
|
+
type: "tool",
|
|
2074
|
+
name: options.toolChoice.name
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
1945
2077
|
const data = await this.invoke(model, body, resolveLlmCaller(options));
|
|
1946
2078
|
const { text, toolCalls } = extractAnthropicContent(data);
|
|
1947
2079
|
return { message: { content: text || undefined, toolCalls } };
|
|
@@ -2208,9 +2340,23 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2208
2340
|
return JSON.parse(responseRaw);
|
|
2209
2341
|
}
|
|
2210
2342
|
async call(model, options) {
|
|
2211
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
2212
2343
|
const log5 = logger.child("llm:gitlab-duo");
|
|
2213
|
-
|
|
2344
|
+
if ("jsonSchemaName" in options) {
|
|
2345
|
+
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2346
|
+
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2347
|
+
instruction: options.instruction,
|
|
2348
|
+
jsonSchemaName: options.jsonSchemaName,
|
|
2349
|
+
jsonSchema: options.jsonSchema
|
|
2350
|
+
});
|
|
2351
|
+
const choice = await this.chatWithTools(model, {
|
|
2352
|
+
caller: options.caller ?? options.jsonSchemaName,
|
|
2353
|
+
instruction,
|
|
2354
|
+
messages: [{ role: "user", content: options.message }],
|
|
2355
|
+
tools: [tool]
|
|
2356
|
+
});
|
|
2357
|
+
return parseStructuredJsonResult(choice, toolName);
|
|
2358
|
+
}
|
|
2359
|
+
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
2214
2360
|
const body = {
|
|
2215
2361
|
model,
|
|
2216
2362
|
messages: [
|
|
@@ -2226,7 +2372,7 @@ class GitLabDuoExecutor extends LLMExecutor {
|
|
|
2226
2372
|
if (!content) {
|
|
2227
2373
|
throw new Error("Empty response from model");
|
|
2228
2374
|
}
|
|
2229
|
-
return
|
|
2375
|
+
return content;
|
|
2230
2376
|
}
|
|
2231
2377
|
async chatWithTools(model, options) {
|
|
2232
2378
|
const log5 = logger.child("llm:gitlab-duo");
|
|
@@ -2325,10 +2471,25 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2325
2471
|
return JSON.parse(responseRaw);
|
|
2326
2472
|
}
|
|
2327
2473
|
async call(model, options) {
|
|
2328
|
-
const jsonMode = "jsonSchemaName" in options;
|
|
2329
|
-
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
2330
2474
|
const log5 = logger.child("llm:snowflake-cortex");
|
|
2331
|
-
|
|
2475
|
+
if ("jsonSchemaName" in options) {
|
|
2476
|
+
log5.debug(`call: model=${model} jsonSchema=${options.jsonSchemaName} via tool`);
|
|
2477
|
+
const { toolName, tool, instruction } = buildStructuredJsonRequest({
|
|
2478
|
+
instruction: options.instruction,
|
|
2479
|
+
jsonSchemaName: options.jsonSchemaName,
|
|
2480
|
+
jsonSchema: options.jsonSchema
|
|
2481
|
+
});
|
|
2482
|
+
const choice = await this.chatWithTools(model, {
|
|
2483
|
+
caller: options.caller ?? options.jsonSchemaName,
|
|
2484
|
+
instruction,
|
|
2485
|
+
messages: [{ role: "user", content: options.message }],
|
|
2486
|
+
tools: [tool],
|
|
2487
|
+
reasoningEffort: "none"
|
|
2488
|
+
});
|
|
2489
|
+
return parseStructuredJsonResult(choice, toolName);
|
|
2490
|
+
}
|
|
2491
|
+
const reasoning = defaultReasoningEffort(options.reasoningEffort, model, this.models.identity);
|
|
2492
|
+
log5.debug(`call: model=${model} jsonSchema=- msgLen=${options.message.length}`);
|
|
2332
2493
|
const body = {
|
|
2333
2494
|
model,
|
|
2334
2495
|
messages: [
|
|
@@ -2347,7 +2508,7 @@ class SnowflakeCortexExecutor extends LLMExecutor {
|
|
|
2347
2508
|
if (!content) {
|
|
2348
2509
|
throw new Error("Empty response from model");
|
|
2349
2510
|
}
|
|
2350
|
-
return
|
|
2511
|
+
return content;
|
|
2351
2512
|
}
|
|
2352
2513
|
async chatWithTools(model, options) {
|
|
2353
2514
|
const log5 = logger.child("llm:snowflake-cortex");
|
|
@@ -3016,75 +3177,46 @@ class Brain {
|
|
|
3016
3177
|
content: userPrompt
|
|
3017
3178
|
}
|
|
3018
3179
|
];
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
choice = await llm.chatWithTools(llm.models.conversation, {
|
|
3024
|
-
caller: initiate ? "start-conversation" : "send-message",
|
|
3025
|
-
instruction: `${this.brainbase.baseSystemPrompt}
|
|
3180
|
+
try {
|
|
3181
|
+
await llm.chatWithToolExecution(llm.models.conversation, {
|
|
3182
|
+
caller: initiate ? "start-conversation" : "send-message",
|
|
3183
|
+
instruction: `${this.brainbase.baseSystemPrompt}
|
|
3026
3184
|
|
|
3027
3185
|
${instruction}`,
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
}
|
|
3044
|
-
messages.push(stripAssistantForHistory(assistantMessage));
|
|
3045
|
-
for (const call of toolCalls) {
|
|
3046
|
-
if (call.function.name === "addReplyMessage") {
|
|
3047
|
-
const content = parseAddReplyMessageArguments(call.function.arguments);
|
|
3048
|
-
if (content !== null) {
|
|
3049
|
-
log7.debug(`sendMessage: addReplyMessage[${replyMessages.length}] (${content.length} chars)`);
|
|
3050
|
-
send(content);
|
|
3051
|
-
replyMessages.push(content);
|
|
3052
|
-
} else {
|
|
3186
|
+
messages,
|
|
3187
|
+
tools,
|
|
3188
|
+
maxSteps,
|
|
3189
|
+
executeTool: async (call) => {
|
|
3190
|
+
if (call.function.name === "addReplyMessage") {
|
|
3191
|
+
const content = parseAddReplyMessageArguments(call.function.arguments);
|
|
3192
|
+
if (content !== null) {
|
|
3193
|
+
log7.debug(`sendMessage: addReplyMessage[${replyMessages.length}] (${content.length} chars)`);
|
|
3194
|
+
await send(content);
|
|
3195
|
+
replyMessages.push(content);
|
|
3196
|
+
return JSON.stringify({
|
|
3197
|
+
ok: true,
|
|
3198
|
+
index: replyMessages.length - 1
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3053
3201
|
log7.debug(`sendMessage: addReplyMessage rejected (invalid arguments: ${call.function.arguments})`);
|
|
3202
|
+
return JSON.stringify({ ok: false, error: "invalid arguments" });
|
|
3054
3203
|
}
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
});
|
|
3060
|
-
|
|
3061
|
-
}
|
|
3062
|
-
if (call.function.name === "searchMemory") {
|
|
3063
|
-
log7.debug(`sendMessage: searchMemory tool call`);
|
|
3064
|
-
const result = await this.executeSearchTool(call.function.arguments);
|
|
3065
|
-
messages.push({
|
|
3066
|
-
role: "tool",
|
|
3067
|
-
toolCallId: call.id,
|
|
3068
|
-
content: result
|
|
3069
|
-
});
|
|
3070
|
-
continue;
|
|
3071
|
-
}
|
|
3072
|
-
log7.debug(`sendMessage: unknown tool "${call.function.name}"`);
|
|
3073
|
-
messages.push({
|
|
3074
|
-
role: "tool",
|
|
3075
|
-
toolCallId: call.id,
|
|
3076
|
-
content: JSON.stringify({
|
|
3204
|
+
if (call.function.name === "searchMemory") {
|
|
3205
|
+
log7.debug(`sendMessage: searchMemory tool call`);
|
|
3206
|
+
return this.executeSearchTool(call.function.arguments);
|
|
3207
|
+
}
|
|
3208
|
+
log7.debug(`sendMessage: unknown tool "${call.function.name}"`);
|
|
3209
|
+
return JSON.stringify({
|
|
3077
3210
|
ok: false,
|
|
3078
3211
|
error: `Unknown tool: ${call.function.name}`
|
|
3079
|
-
})
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
}
|
|
3212
|
+
});
|
|
3213
|
+
}
|
|
3214
|
+
});
|
|
3215
|
+
} catch (error) {
|
|
3216
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
3217
|
+
logger.error(`sendMessage: LLM call failed: ${reason}`);
|
|
3086
3218
|
}
|
|
3087
|
-
|
|
3219
|
+
log7.debug(`sendMessage: done with ${replyMessages.length} replies`);
|
|
3088
3220
|
return replyMessages;
|
|
3089
3221
|
}
|
|
3090
3222
|
async buildMemoryBlock() {
|
|
@@ -3336,13 +3468,6 @@ function parseSearchArguments(json) {
|
|
|
3336
3468
|
}
|
|
3337
3469
|
return null;
|
|
3338
3470
|
}
|
|
3339
|
-
function stripAssistantForHistory(message) {
|
|
3340
|
-
return {
|
|
3341
|
-
role: "assistant",
|
|
3342
|
-
content: message.content,
|
|
3343
|
-
toolCalls: message.toolCalls
|
|
3344
|
-
};
|
|
3345
|
-
}
|
|
3346
3471
|
|
|
3347
3472
|
// src/channel/discord.ts
|
|
3348
3473
|
import {
|