@flutchai/flutch-sdk 0.6.11 → 0.7.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 +29 -0
- package/dist/index.cjs +425 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -3
- package/dist/index.d.ts +69 -3
- package/dist/index.js +411 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,6 +229,35 @@ curl http://localhost:3000/registry
|
|
|
229
229
|
|
|
230
230
|
## Advanced Usage
|
|
231
231
|
|
|
232
|
+
### Tool search for agents with many tools
|
|
233
|
+
|
|
234
|
+
Every bound tool's schema is sent on every turn. With dozens of tools that is most of the prompt, and models pick the wrong tool more often past 30–50 of them. Turn on tool search and the model sees a few tools plus `search_tools`, and finds the rest when a task needs them:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { isToolSearchCall } from "@flutchai/flutch-sdk";
|
|
238
|
+
|
|
239
|
+
const modelConfig = {
|
|
240
|
+
modelId,
|
|
241
|
+
toolsConfig,
|
|
242
|
+
toolSearch: { enabled: true, alwaysLoaded: ["list_sites", "list_products"] },
|
|
243
|
+
};
|
|
244
|
+
const model = await modelInitializer.initializeChatModel(modelConfig);
|
|
245
|
+
|
|
246
|
+
// In the tools node: search runs in the SDK, against the same catalog.
|
|
247
|
+
if (isToolSearchCall(toolCall.name)) {
|
|
248
|
+
const { content } = await modelInitializer.searchTools(
|
|
249
|
+
modelConfig,
|
|
250
|
+
toolCall.args,
|
|
251
|
+
config
|
|
252
|
+
);
|
|
253
|
+
messages.push(
|
|
254
|
+
new ToolMessage({ content, tool_call_id: toolCall.id, name: toolCall.name })
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Pass the same config to `searchTools` that built the model — it names the catalog. Found tools are read from the conversation history, so the graph keeps no extra state. Claude through the Anthropic Messages API loads found tools as deferred definitions (`defer_loading` + `tool_reference`) and keeps the prompt cache; other providers bind the core tools plus the found ones each turn. `alwaysLoaded` also takes a comma-separated string; `maxResults` (default 5) and `minTools` (default 15) tune it.
|
|
260
|
+
|
|
232
261
|
### Using GraphTypeUtils
|
|
233
262
|
|
|
234
263
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,8 @@ var dispatch = require('@langchain/core/callbacks/dispatch');
|
|
|
21
21
|
var tools = require('@langchain/core/tools');
|
|
22
22
|
var axios2 = require('axios');
|
|
23
23
|
var manager = require('@langchain/core/callbacks/manager');
|
|
24
|
+
var runnables = require('@langchain/core/runnables');
|
|
25
|
+
var zod = require('zod');
|
|
24
26
|
var openai = require('@langchain/openai');
|
|
25
27
|
var aws = require('@langchain/aws');
|
|
26
28
|
var core$1 = require('cohere-ai/core');
|
|
@@ -5414,6 +5416,10 @@ exports.EventProcessor = class _EventProcessor {
|
|
|
5414
5416
|
...acc.attachments,
|
|
5415
5417
|
...attachments
|
|
5416
5418
|
];
|
|
5419
|
+
this.sendDelta(channel, {
|
|
5420
|
+
type: "attachments_added",
|
|
5421
|
+
attachments
|
|
5422
|
+
}, onPartial);
|
|
5417
5423
|
this.logger.debug("[ATTACHMENTS] Extracted from send_attachments event", {
|
|
5418
5424
|
extractedCount: attachments.length,
|
|
5419
5425
|
totalAccCount: acc.attachments.length
|
|
@@ -5428,7 +5434,8 @@ exports.EventProcessor = class _EventProcessor {
|
|
|
5428
5434
|
if (chunk.content) {
|
|
5429
5435
|
blocks.push(...this.normalizeContentBlocks(chunk.content));
|
|
5430
5436
|
}
|
|
5431
|
-
|
|
5437
|
+
const contentHasToolBlocks = blocks.some((block) => block?.type === "tool_use" || block?.type === "tool_call" || block?.type === "input_json_delta");
|
|
5438
|
+
if (!contentHasToolBlocks && Array.isArray(chunk.tool_call_chunks) && chunk.tool_call_chunks.length > 0) {
|
|
5432
5439
|
blocks.push(...this.toolCallChunksToBlocks(chunk.tool_call_chunks));
|
|
5433
5440
|
}
|
|
5434
5441
|
if (blocks.length > 0) {
|
|
@@ -7081,6 +7088,314 @@ exports.McpRuntimeHttpClient = _ts_decorate25([
|
|
|
7081
7088
|
])
|
|
7082
7089
|
], exports.McpRuntimeHttpClient);
|
|
7083
7090
|
|
|
7091
|
+
// src/tools/tool-search/tool-search.catalog.ts
|
|
7092
|
+
var K1 = 1.2;
|
|
7093
|
+
var B = 0.75;
|
|
7094
|
+
var NAME_WEIGHT = 3;
|
|
7095
|
+
var STEM_LENGTH = 5;
|
|
7096
|
+
var HIT_DESCRIPTION_CHARS = 240;
|
|
7097
|
+
function toolSearchTerms(text) {
|
|
7098
|
+
return text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word.length > 1).map((word) => word.slice(0, STEM_LENGTH));
|
|
7099
|
+
}
|
|
7100
|
+
__name(toolSearchTerms, "toolSearchTerms");
|
|
7101
|
+
function schemaParams(schema) {
|
|
7102
|
+
const json = schema;
|
|
7103
|
+
const properties = json?.properties ?? json?.shape ?? {};
|
|
7104
|
+
return Object.entries(properties).flatMap(([name, property]) => property?.description ? [
|
|
7105
|
+
name,
|
|
7106
|
+
property.description
|
|
7107
|
+
] : [
|
|
7108
|
+
name
|
|
7109
|
+
]);
|
|
7110
|
+
}
|
|
7111
|
+
__name(schemaParams, "schemaParams");
|
|
7112
|
+
function shortDescription(description) {
|
|
7113
|
+
const flat = description.replace(/\s+/g, " ").trim();
|
|
7114
|
+
if (flat.length <= HIT_DESCRIPTION_CHARS) return flat;
|
|
7115
|
+
const cut = flat.slice(0, HIT_DESCRIPTION_CHARS);
|
|
7116
|
+
const sentenceEnd = cut.lastIndexOf(". ");
|
|
7117
|
+
return sentenceEnd > HIT_DESCRIPTION_CHARS / 2 ? cut.slice(0, sentenceEnd + 1) : `${cut.trimEnd()}\u2026`;
|
|
7118
|
+
}
|
|
7119
|
+
__name(shortDescription, "shortDescription");
|
|
7120
|
+
var ToolSearchCatalog = class {
|
|
7121
|
+
static {
|
|
7122
|
+
__name(this, "ToolSearchCatalog");
|
|
7123
|
+
}
|
|
7124
|
+
entries;
|
|
7125
|
+
documents;
|
|
7126
|
+
lengths;
|
|
7127
|
+
averageLength;
|
|
7128
|
+
documentFrequency = /* @__PURE__ */ new Map();
|
|
7129
|
+
constructor(tools) {
|
|
7130
|
+
this.entries = tools.map((tool) => ({
|
|
7131
|
+
name: tool.name,
|
|
7132
|
+
description: tool.description ?? "",
|
|
7133
|
+
params: schemaParams(tool.schema)
|
|
7134
|
+
}));
|
|
7135
|
+
this.documents = this.entries.map((entry) => {
|
|
7136
|
+
const nameTerms = toolSearchTerms(entry.name);
|
|
7137
|
+
const terms = [
|
|
7138
|
+
...Array.from({
|
|
7139
|
+
length: NAME_WEIGHT
|
|
7140
|
+
}, () => nameTerms).flat(),
|
|
7141
|
+
...toolSearchTerms(entry.description),
|
|
7142
|
+
...entry.params.flatMap(toolSearchTerms)
|
|
7143
|
+
];
|
|
7144
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7145
|
+
for (const term of terms) counts.set(term, (counts.get(term) ?? 0) + 1);
|
|
7146
|
+
return counts;
|
|
7147
|
+
});
|
|
7148
|
+
this.lengths = this.documents.map((counts) => Array.from(counts.values()).reduce((sum, n) => sum + n, 0));
|
|
7149
|
+
this.averageLength = this.lengths.reduce((sum, n) => sum + n, 0) / Math.max(this.lengths.length, 1);
|
|
7150
|
+
for (const counts of this.documents) {
|
|
7151
|
+
for (const term of counts.keys()) {
|
|
7152
|
+
this.documentFrequency.set(term, (this.documentFrequency.get(term) ?? 0) + 1);
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7156
|
+
get size() {
|
|
7157
|
+
return this.entries.length;
|
|
7158
|
+
}
|
|
7159
|
+
has(name) {
|
|
7160
|
+
return this.entries.some((entry) => entry.name === name);
|
|
7161
|
+
}
|
|
7162
|
+
/**
|
|
7163
|
+
* The best `limit` tools for a query, best first. A query that names a tool
|
|
7164
|
+
* exactly returns that tool first whatever its score. Tools that share no
|
|
7165
|
+
* word with the query are never returned.
|
|
7166
|
+
*/
|
|
7167
|
+
search(query, limit) {
|
|
7168
|
+
const terms = Array.from(new Set(toolSearchTerms(query)));
|
|
7169
|
+
const exact = query.split(/[\s,]+/).map((word) => word.trim()).filter((word) => this.has(word));
|
|
7170
|
+
const total = this.entries.length;
|
|
7171
|
+
const scored = this.entries.map((entry, index) => {
|
|
7172
|
+
const counts = this.documents[index];
|
|
7173
|
+
const length = this.lengths[index];
|
|
7174
|
+
let score = 0;
|
|
7175
|
+
for (const term of terms) {
|
|
7176
|
+
const frequency = counts.get(term);
|
|
7177
|
+
if (!frequency) continue;
|
|
7178
|
+
const df = this.documentFrequency.get(term) ?? 0;
|
|
7179
|
+
const idf = Math.log(1 + (total - df + 0.5) / (df + 0.5));
|
|
7180
|
+
score += idf * frequency * (K1 + 1) / (frequency + K1 * (1 - B + B * length / this.averageLength));
|
|
7181
|
+
}
|
|
7182
|
+
if (exact.includes(entry.name)) score += 1e3;
|
|
7183
|
+
return {
|
|
7184
|
+
entry,
|
|
7185
|
+
score
|
|
7186
|
+
};
|
|
7187
|
+
}).filter(({ score }) => score > 0).sort((a, b) => b.score - a.score);
|
|
7188
|
+
return scored.slice(0, Math.max(1, limit)).map(({ entry }) => ({
|
|
7189
|
+
name: entry.name,
|
|
7190
|
+
description: shortDescription(entry.description)
|
|
7191
|
+
}));
|
|
7192
|
+
}
|
|
7193
|
+
/**
|
|
7194
|
+
* What the catalog covers, as name prefixes with counts:
|
|
7195
|
+
* "funnel_* (4), recipe_* (5), rl_* (41)". Goes into the search tool's
|
|
7196
|
+
* description so the model knows a search is worth making. Sorted, so the
|
|
7197
|
+
* description — and the prompt cache — stays the same between requests.
|
|
7198
|
+
*/
|
|
7199
|
+
summary(maxGroups = 20) {
|
|
7200
|
+
const groups = /* @__PURE__ */ new Map();
|
|
7201
|
+
for (const entry of this.entries) {
|
|
7202
|
+
const prefix = entry.name.includes("_") ? `${entry.name.split("_")[0]}_*` : entry.name;
|
|
7203
|
+
groups.set(prefix, (groups.get(prefix) ?? 0) + 1);
|
|
7204
|
+
}
|
|
7205
|
+
const sorted = Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
7206
|
+
const shown = sorted.slice(0, maxGroups).map(([prefix, count]) => count > 1 ? `${prefix} (${count})` : prefix);
|
|
7207
|
+
return sorted.length > maxGroups ? `${shown.join(", ")}, \u2026` : shown.join(", ");
|
|
7208
|
+
}
|
|
7209
|
+
};
|
|
7210
|
+
|
|
7211
|
+
// src/tools/tool-search/tool-search.result.ts
|
|
7212
|
+
var TOOL_SEARCH_NAME = "search_tools";
|
|
7213
|
+
function isToolSearchCall(name) {
|
|
7214
|
+
return name === TOOL_SEARCH_NAME;
|
|
7215
|
+
}
|
|
7216
|
+
__name(isToolSearchCall, "isToolSearchCall");
|
|
7217
|
+
function formatToolSearchResult(hits) {
|
|
7218
|
+
if (hits.length === 0) {
|
|
7219
|
+
return JSON.stringify({
|
|
7220
|
+
tools: [],
|
|
7221
|
+
note: "No tools matched. Search again with other words: what the tool does, the object it works on, or a name prefix."
|
|
7222
|
+
});
|
|
7223
|
+
}
|
|
7224
|
+
return JSON.stringify({
|
|
7225
|
+
tools: hits,
|
|
7226
|
+
note: "These tools are available now. Call them directly."
|
|
7227
|
+
});
|
|
7228
|
+
}
|
|
7229
|
+
__name(formatToolSearchResult, "formatToolSearchResult");
|
|
7230
|
+
function parseToolSearchResult(content) {
|
|
7231
|
+
const text = contentText(content);
|
|
7232
|
+
if (!text) return [];
|
|
7233
|
+
try {
|
|
7234
|
+
const parsed = JSON.parse(text);
|
|
7235
|
+
return (parsed.tools ?? []).map((tool) => tool?.name).filter((name) => typeof name === "string");
|
|
7236
|
+
} catch {
|
|
7237
|
+
return [];
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
__name(parseToolSearchResult, "parseToolSearchResult");
|
|
7241
|
+
function contentText(content) {
|
|
7242
|
+
if (typeof content === "string") return content;
|
|
7243
|
+
if (Array.isArray(content)) {
|
|
7244
|
+
return content.map((part) => typeof part === "string" ? part : part && typeof part === "object" && "text" in part ? String(part.text) : "").join("");
|
|
7245
|
+
}
|
|
7246
|
+
return "";
|
|
7247
|
+
}
|
|
7248
|
+
__name(contentText, "contentText");
|
|
7249
|
+
function loadedToolNames(messages) {
|
|
7250
|
+
const searchCallIds = /* @__PURE__ */ new Set();
|
|
7251
|
+
for (const message of messages) {
|
|
7252
|
+
const calls = message.tool_calls;
|
|
7253
|
+
for (const call of calls ?? []) {
|
|
7254
|
+
if (isToolSearchCall(call.name) && call.id) searchCallIds.add(call.id);
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
const loaded = [];
|
|
7258
|
+
for (const message of messages) {
|
|
7259
|
+
if (message._getType() !== "tool") continue;
|
|
7260
|
+
const tool = message;
|
|
7261
|
+
const isSearch = isToolSearchCall(tool.name) || tool.tool_call_id !== void 0 && searchCallIds.has(tool.tool_call_id);
|
|
7262
|
+
if (!isSearch) continue;
|
|
7263
|
+
for (const name of parseToolSearchResult(tool.content)) {
|
|
7264
|
+
if (!loaded.includes(name)) loaded.push(name);
|
|
7265
|
+
}
|
|
7266
|
+
}
|
|
7267
|
+
return loaded;
|
|
7268
|
+
}
|
|
7269
|
+
__name(loadedToolNames, "loadedToolNames");
|
|
7270
|
+
var DEFAULT_TOOL_SEARCH_RESULTS = 5;
|
|
7271
|
+
var DEFAULT_TOOL_SEARCH_MIN_TOOLS = 15;
|
|
7272
|
+
function toolSearchApplies(options, toolCount) {
|
|
7273
|
+
return !!options?.enabled && toolCount >= (options.minTools ?? DEFAULT_TOOL_SEARCH_MIN_TOOLS);
|
|
7274
|
+
}
|
|
7275
|
+
__name(toolSearchApplies, "toolSearchApplies");
|
|
7276
|
+
function splitTools(tools, options, extraCore = []) {
|
|
7277
|
+
const listed = typeof options.alwaysLoaded === "string" ? options.alwaysLoaded.split(",") : options.alwaysLoaded ?? [];
|
|
7278
|
+
const always = /* @__PURE__ */ new Set([
|
|
7279
|
+
...listed.map((name) => name.trim()).filter(Boolean),
|
|
7280
|
+
...extraCore.map((tool) => tool.name)
|
|
7281
|
+
]);
|
|
7282
|
+
return {
|
|
7283
|
+
core: tools.filter((tool) => always.has(tool.name)),
|
|
7284
|
+
deferred: tools.filter((tool) => !always.has(tool.name))
|
|
7285
|
+
};
|
|
7286
|
+
}
|
|
7287
|
+
__name(splitTools, "splitTools");
|
|
7288
|
+
function createToolSearchTool(catalog, maxResults = DEFAULT_TOOL_SEARCH_RESULTS) {
|
|
7289
|
+
return new tools.DynamicStructuredTool({
|
|
7290
|
+
name: TOOL_SEARCH_NAME,
|
|
7291
|
+
description: `Find more tools. You see only some of your tools; ${catalog.size} more are found by search: ${catalog.summary()}. Describe the task in a few words (what to do and with what, e.g. "customer review quotes by theme"), or give exact tool names. Found tools become callable right away and stay available for the rest of the conversation. Search before saying you cannot do something.`,
|
|
7292
|
+
schema: zod.z.object({
|
|
7293
|
+
query: zod.z.string().describe("What the tool should do, in a few words, or tool names separated by commas"),
|
|
7294
|
+
limit: zod.z.number().int().min(1).max(20).optional().describe(`How many tools to return (default ${maxResults})`)
|
|
7295
|
+
}),
|
|
7296
|
+
func: /* @__PURE__ */ __name(async ({ query, limit }) => formatToolSearchResult(catalog.search(query, limit ?? maxResults)), "func")
|
|
7297
|
+
});
|
|
7298
|
+
}
|
|
7299
|
+
__name(createToolSearchTool, "createToolSearchTool");
|
|
7300
|
+
function deferredCopy(tool) {
|
|
7301
|
+
const copy = Object.create(tool);
|
|
7302
|
+
copy.extras = {
|
|
7303
|
+
...tool.extras ?? {},
|
|
7304
|
+
defer_loading: true
|
|
7305
|
+
};
|
|
7306
|
+
return copy;
|
|
7307
|
+
}
|
|
7308
|
+
__name(deferredCopy, "deferredCopy");
|
|
7309
|
+
function bindToolsWithSearch(params) {
|
|
7310
|
+
const { model, core, deferred, searchTool, mode, bindOptions } = params;
|
|
7311
|
+
const bind = /* @__PURE__ */ __name((tools) => bindOptions ? model.bindTools(tools, bindOptions) : model.bindTools(tools), "bind");
|
|
7312
|
+
if (mode === "deferred") {
|
|
7313
|
+
return bind([
|
|
7314
|
+
searchTool,
|
|
7315
|
+
...core,
|
|
7316
|
+
...deferred.map(deferredCopy)
|
|
7317
|
+
]);
|
|
7318
|
+
}
|
|
7319
|
+
const byName = new Map(deferred.map((tool) => [
|
|
7320
|
+
tool.name,
|
|
7321
|
+
tool
|
|
7322
|
+
]));
|
|
7323
|
+
const bound = /* @__PURE__ */ new Map();
|
|
7324
|
+
const forHistory = /* @__PURE__ */ __name((messages) => {
|
|
7325
|
+
const found = loadedToolNames(messages).filter((name) => byName.has(name));
|
|
7326
|
+
const key = found.join(",");
|
|
7327
|
+
let runnable = bound.get(key);
|
|
7328
|
+
if (!runnable) {
|
|
7329
|
+
runnable = bind([
|
|
7330
|
+
...core,
|
|
7331
|
+
searchTool,
|
|
7332
|
+
...found.map((name) => byName.get(name))
|
|
7333
|
+
]);
|
|
7334
|
+
bound.set(key, runnable);
|
|
7335
|
+
}
|
|
7336
|
+
return runnable;
|
|
7337
|
+
}, "forHistory");
|
|
7338
|
+
return runnables.RunnableLambda.from(async (input, config) => {
|
|
7339
|
+
const messages = Array.isArray(input) ? input : [];
|
|
7340
|
+
return forHistory(messages).invoke(input, config);
|
|
7341
|
+
}).withConfig({
|
|
7342
|
+
runName: "ToolSearchModel"
|
|
7343
|
+
});
|
|
7344
|
+
}
|
|
7345
|
+
__name(bindToolsWithSearch, "bindToolsWithSearch");
|
|
7346
|
+
|
|
7347
|
+
// src/tools/tool-search/tool-reference.fetch.ts
|
|
7348
|
+
function rewriteToolReferences(body) {
|
|
7349
|
+
if (!body.includes(TOOL_SEARCH_NAME)) return body;
|
|
7350
|
+
let parsed;
|
|
7351
|
+
try {
|
|
7352
|
+
parsed = JSON.parse(body);
|
|
7353
|
+
} catch {
|
|
7354
|
+
return body;
|
|
7355
|
+
}
|
|
7356
|
+
const known = new Set((parsed.tools ?? []).map((tool) => tool.name));
|
|
7357
|
+
const searchIds = /* @__PURE__ */ new Set();
|
|
7358
|
+
for (const message of parsed.messages ?? []) {
|
|
7359
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
7360
|
+
for (const block of message.content) {
|
|
7361
|
+
if (block.type === "tool_use" && block.name === TOOL_SEARCH_NAME && block.id) {
|
|
7362
|
+
searchIds.add(block.id);
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
if (searchIds.size === 0) return body;
|
|
7367
|
+
let changed = false;
|
|
7368
|
+
for (const message of parsed.messages ?? []) {
|
|
7369
|
+
if (message.role !== "user" || !Array.isArray(message.content)) continue;
|
|
7370
|
+
for (const block of message.content) {
|
|
7371
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
7372
|
+
if (!searchIds.has(block.tool_use_id)) continue;
|
|
7373
|
+
const names = parseToolSearchResult(block.content).filter((name) => known.has(name));
|
|
7374
|
+
if (names.length === 0) continue;
|
|
7375
|
+
block.content = names.map((name) => ({
|
|
7376
|
+
type: "tool_reference",
|
|
7377
|
+
tool_name: name
|
|
7378
|
+
}));
|
|
7379
|
+
changed = true;
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
return changed ? JSON.stringify(parsed) : body;
|
|
7383
|
+
}
|
|
7384
|
+
__name(rewriteToolReferences, "rewriteToolReferences");
|
|
7385
|
+
function withToolReferences(inner) {
|
|
7386
|
+
return (input, init) => {
|
|
7387
|
+
if (init && typeof init.body === "string") {
|
|
7388
|
+
const body = rewriteToolReferences(init.body);
|
|
7389
|
+
if (body !== init.body) return inner(input, {
|
|
7390
|
+
...init,
|
|
7391
|
+
body
|
|
7392
|
+
});
|
|
7393
|
+
}
|
|
7394
|
+
return inner(input, init);
|
|
7395
|
+
};
|
|
7396
|
+
}
|
|
7397
|
+
__name(withToolReferences, "withToolReferences");
|
|
7398
|
+
|
|
7084
7399
|
// src/models/enums.ts
|
|
7085
7400
|
var ModelProvider = /* @__PURE__ */ (function(ModelProvider2) {
|
|
7086
7401
|
ModelProvider2["MISTRAL"] = "mistral";
|
|
@@ -7447,6 +7762,8 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7447
7762
|
modelConfigCache = /* @__PURE__ */ new Map();
|
|
7448
7763
|
// Cache for model instances to avoid recreating identical models
|
|
7449
7764
|
modelInstanceCache = /* @__PURE__ */ new Map();
|
|
7765
|
+
// Tool-search catalogs, keyed like the model instance that uses them
|
|
7766
|
+
toolSearchCatalogs = /* @__PURE__ */ new Map();
|
|
7450
7767
|
static DEFAULT_ENV_MAP = {
|
|
7451
7768
|
[ModelProvider.OPENAI]: "OPENAI_API_KEY",
|
|
7452
7769
|
[ModelProvider.ANTHROPIC]: "ANTHROPIC_API_KEY",
|
|
@@ -7522,7 +7839,8 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7522
7839
|
const withMcp = config.mcpServers && config.mcpServers.length > 0 ? `${base}:mcp:${JSON.stringify(config.mcpServers)}` : base;
|
|
7523
7840
|
const withSampling = config.supportsSamplingParams === void 0 ? withMcp : `${withMcp}:sp:${config.supportsSamplingParams}`;
|
|
7524
7841
|
const withCache = config.promptCache === void 0 && config.promptCacheTtl === void 0 ? withSampling : `${withSampling}:pc:${config.promptCache ?? "auto"}:${config.promptCacheTtl ?? "default"}`;
|
|
7525
|
-
|
|
7842
|
+
const withEffort = config.effort === void 0 ? withCache : `${withCache}:eff:${config.effort}`;
|
|
7843
|
+
return config.toolSearch?.enabled ? `${withEffort}:ts:${JSON.stringify(config.toolSearch)}` : withEffort;
|
|
7526
7844
|
}
|
|
7527
7845
|
// Chat model creators
|
|
7528
7846
|
chatModelCreators = {
|
|
@@ -7556,10 +7874,13 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7556
7874
|
},
|
|
7557
7875
|
anthropicApiKey: apiToken || this.resolveApiKey(ModelProvider.ANTHROPIC),
|
|
7558
7876
|
...routerURL && {
|
|
7559
|
-
anthropicApiUrl: routerURL
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7877
|
+
anthropicApiUrl: routerURL
|
|
7878
|
+
},
|
|
7879
|
+
// Tool search results become `tool_reference` blocks on the wire
|
|
7880
|
+
// (LangChain cannot express them). A request without a search passes
|
|
7881
|
+
// through untouched.
|
|
7882
|
+
clientOptions: {
|
|
7883
|
+
fetch: withToolReferences(routerURL ? flutchFetch : fetch)
|
|
7563
7884
|
}
|
|
7564
7885
|
});
|
|
7565
7886
|
},
|
|
@@ -7803,7 +8124,11 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7803
8124
|
// Converse takes cache control as a per-call option; the Anthropic
|
|
7804
8125
|
// client already got it as a constructor field, and handing it the
|
|
7805
8126
|
// same value twice would put an unknown key in its call options.
|
|
7806
|
-
finalConfig.useBedrock ? promptCacheControl : void 0
|
|
8127
|
+
finalConfig.useBedrock ? promptCacheControl : void 0,
|
|
8128
|
+
config.toolSearch?.enabled ? {
|
|
8129
|
+
options: config.toolSearch,
|
|
8130
|
+
cacheKey
|
|
8131
|
+
} : void 0
|
|
7807
8132
|
);
|
|
7808
8133
|
this.modelInstanceCache.set(cacheKey, boundModel);
|
|
7809
8134
|
return boundModel;
|
|
@@ -7829,8 +8154,9 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7829
8154
|
* - Runnable when tools are bound (model.bindTools returns Runnable)
|
|
7830
8155
|
* - BaseChatModel when no tools
|
|
7831
8156
|
*/
|
|
7832
|
-
async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl) {
|
|
8157
|
+
async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl, toolSearch) {
|
|
7833
8158
|
const allTools = [];
|
|
8159
|
+
const mcpToolsFound = [];
|
|
7834
8160
|
const enabledToolsConfig = (toolsConfig || []).filter((tc) => tc.enabled !== false);
|
|
7835
8161
|
const hasInlineServers = !!(mcpServers && mcpServers.length > 0);
|
|
7836
8162
|
if (enabledToolsConfig.length > 0 || hasInlineServers) {
|
|
@@ -7840,6 +8166,7 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7840
8166
|
const mcpTools = await mcpToolFilter.getFilteredTools(enabledToolsConfig, mcpServers, mcpContext);
|
|
7841
8167
|
this.logger.debug(`Successfully fetched ${mcpTools.length} tools with dynamic schemas from MCP Runtime`);
|
|
7842
8168
|
allTools.push(...mcpTools);
|
|
8169
|
+
mcpToolsFound.push(...mcpTools);
|
|
7843
8170
|
} catch (error) {
|
|
7844
8171
|
this.logger.error(`Failed to fetch tools from MCP Runtime: ${error instanceof Error ? error.message : String(error)}`);
|
|
7845
8172
|
}
|
|
@@ -7853,11 +8180,86 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7853
8180
|
const boundOptions = {
|
|
7854
8181
|
cache_control: promptCacheControl
|
|
7855
8182
|
};
|
|
8183
|
+
if (toolSearch && toolSearchApplies(toolSearch.options, allTools.length)) {
|
|
8184
|
+
const { core, deferred } = splitTools(mcpToolsFound, toolSearch.options, customTools ?? []);
|
|
8185
|
+
if (deferred.length > 0) {
|
|
8186
|
+
const maxResults = toolSearch.options.maxResults ?? DEFAULT_TOOL_SEARCH_RESULTS;
|
|
8187
|
+
const catalog = new ToolSearchCatalog(deferred);
|
|
8188
|
+
this.toolSearchCatalogs.set(toolSearch.cacheKey, {
|
|
8189
|
+
catalog,
|
|
8190
|
+
maxResults
|
|
8191
|
+
});
|
|
8192
|
+
const mode = model instanceof anthropic.ChatAnthropic ? "deferred" : "rebind";
|
|
8193
|
+
this.logger.debug(`Tool search (${mode}): ${core.length + (customTools?.length ?? 0)} tools in sight, ${deferred.length} found on demand`);
|
|
8194
|
+
return bindToolsWithSearch({
|
|
8195
|
+
model,
|
|
8196
|
+
core: [
|
|
8197
|
+
...core,
|
|
8198
|
+
...customTools ?? []
|
|
8199
|
+
],
|
|
8200
|
+
deferred,
|
|
8201
|
+
searchTool: createToolSearchTool(catalog, maxResults),
|
|
8202
|
+
mode,
|
|
8203
|
+
bindOptions: promptCacheControl ? boundOptions : void 0
|
|
8204
|
+
});
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
7856
8207
|
const modelWithTools = promptCacheControl ? model.bindTools(allTools, boundOptions) : model.bindTools(allTools);
|
|
7857
8208
|
return modelWithTools;
|
|
7858
8209
|
}
|
|
7859
8210
|
return model;
|
|
7860
8211
|
}
|
|
8212
|
+
/**
|
|
8213
|
+
* Runs a `search_tools` call. `config` must be the one the model was
|
|
8214
|
+
* initialized with (same tools, same `toolSearch`): it names the catalog.
|
|
8215
|
+
* Emits tool start/end events like `McpRuntimeHttpClient.executeToolWithEvents`,
|
|
8216
|
+
* so the call shows as a finished step in the chat.
|
|
8217
|
+
*
|
|
8218
|
+
* Never throws: a failure comes back as the tool's answer.
|
|
8219
|
+
*/
|
|
8220
|
+
async searchTools(config, args, runConfig) {
|
|
8221
|
+
const parsedConfig = manager.parseCallbackConfigArg(runConfig);
|
|
8222
|
+
const callbackManager = manager.CallbackManager.configure(parsedConfig.callbacks);
|
|
8223
|
+
const runManager = await callbackManager?.handleToolStart({
|
|
8224
|
+
name: TOOL_SEARCH_NAME,
|
|
8225
|
+
lc: 1,
|
|
8226
|
+
type: "not_implemented",
|
|
8227
|
+
id: [
|
|
8228
|
+
"langchain",
|
|
8229
|
+
"tools",
|
|
8230
|
+
TOOL_SEARCH_NAME
|
|
8231
|
+
]
|
|
8232
|
+
}, JSON.stringify(args ?? {}), parsedConfig.runId, void 0, parsedConfig.tags, parsedConfig.metadata, TOOL_SEARCH_NAME);
|
|
8233
|
+
try {
|
|
8234
|
+
const key = this.generateModelCacheKey(config);
|
|
8235
|
+
if (!this.toolSearchCatalogs.has(key)) {
|
|
8236
|
+
await this.initializeChatModel(config);
|
|
8237
|
+
}
|
|
8238
|
+
const entry = this.toolSearchCatalogs.get(key);
|
|
8239
|
+
const query = typeof args?.query === "string" ? args.query : "";
|
|
8240
|
+
const limit = Number(args?.limit);
|
|
8241
|
+
const content = !entry ? JSON.stringify({
|
|
8242
|
+
error: "Tool search is not available for this agent: every tool is already loaded."
|
|
8243
|
+
}) : !query.trim() ? JSON.stringify({
|
|
8244
|
+
error: "query is required"
|
|
8245
|
+
}) : formatToolSearchResult(entry.catalog.search(query, Number.isFinite(limit) && limit > 0 ? limit : entry.maxResults));
|
|
8246
|
+
await runManager?.handleToolEnd(content);
|
|
8247
|
+
return {
|
|
8248
|
+
content,
|
|
8249
|
+
success: !!entry && !!query.trim()
|
|
8250
|
+
};
|
|
8251
|
+
} catch (error) {
|
|
8252
|
+
await runManager?.handleToolError(error);
|
|
8253
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8254
|
+
this.logger.error(`Tool search failed: ${message}`);
|
|
8255
|
+
return {
|
|
8256
|
+
content: JSON.stringify({
|
|
8257
|
+
error: `Tool search failed: ${message}`
|
|
8258
|
+
}),
|
|
8259
|
+
success: false
|
|
8260
|
+
};
|
|
8261
|
+
}
|
|
8262
|
+
}
|
|
7861
8263
|
async initializeRerankModel(config) {
|
|
7862
8264
|
const cacheKey = this.generateModelCacheKey(config);
|
|
7863
8265
|
const cachedModel = this.modelInstanceCache.get(cacheKey);
|
|
@@ -8656,6 +9058,8 @@ exports.CallbackStore = CallbackStore;
|
|
|
8656
9058
|
exports.ChatFeature = ChatFeature;
|
|
8657
9059
|
exports.DEFAULT_ATTACHMENT_THRESHOLD = DEFAULT_ATTACHMENT_THRESHOLD;
|
|
8658
9060
|
exports.DEFAULT_ROUTER_URL = DEFAULT_ROUTER_URL;
|
|
9061
|
+
exports.DEFAULT_TOOL_SEARCH_MIN_TOOLS = DEFAULT_TOOL_SEARCH_MIN_TOOLS;
|
|
9062
|
+
exports.DEFAULT_TOOL_SEARCH_RESULTS = DEFAULT_TOOL_SEARCH_RESULTS;
|
|
8659
9063
|
exports.DEFAULT_TRACER_OPTIONS = DEFAULT_TRACER_OPTIONS;
|
|
8660
9064
|
exports.Endpoint = Endpoint;
|
|
8661
9065
|
exports.FileTokenStore = FileTokenStore;
|
|
@@ -8676,6 +9080,8 @@ exports.PROMPT_CACHE_MIN_VERSIONS = PROMPT_CACHE_MIN_VERSIONS;
|
|
|
8676
9080
|
exports.RetrieverSearchType = RetrieverSearchType;
|
|
8677
9081
|
exports.SAMPLING_PARAM_FREE_THRESHOLDS = SAMPLING_PARAM_FREE_THRESHOLDS;
|
|
8678
9082
|
exports.StreamChannel = StreamChannel;
|
|
9083
|
+
exports.TOOL_SEARCH_NAME = TOOL_SEARCH_NAME;
|
|
9084
|
+
exports.ToolSearchCatalog = ToolSearchCatalog;
|
|
8679
9085
|
exports.UIEndpoint = UIEndpoint;
|
|
8680
9086
|
exports.UniversalCallbackService = UniversalCallbackService;
|
|
8681
9087
|
exports.VoyageAIRerank = VoyageAIRerank;
|
|
@@ -8683,6 +9089,7 @@ exports.WithCallbacks = WithCallbacks;
|
|
|
8683
9089
|
exports.WithEndpoints = WithEndpoints;
|
|
8684
9090
|
exports.WithUIEndpoints = WithUIEndpoints;
|
|
8685
9091
|
exports._internals = _internals;
|
|
9092
|
+
exports.bindToolsWithSearch = bindToolsWithSearch;
|
|
8686
9093
|
exports.bootstrap = bootstrap;
|
|
8687
9094
|
exports.buildEffortRequestFields = buildEffortRequestFields;
|
|
8688
9095
|
exports.buildOAuthAuthorizationUrl = buildOAuthAuthorizationUrl;
|
|
@@ -8692,6 +9099,7 @@ exports.createEndpointDescriptors = createEndpointDescriptors;
|
|
|
8692
9099
|
exports.createGraphAttachment = createGraphAttachment;
|
|
8693
9100
|
exports.createMongoClientAdapter = createMongoClientAdapter;
|
|
8694
9101
|
exports.createStaticMessage = createStaticMessage;
|
|
9102
|
+
exports.createToolSearchTool = createToolSearchTool;
|
|
8695
9103
|
exports.decryptTokens = decryptTokens;
|
|
8696
9104
|
exports.dispatchAttachments = dispatchAttachments;
|
|
8697
9105
|
exports.encryptTokens = encryptTokens;
|
|
@@ -8701,6 +9109,7 @@ exports.findEndpointMethod = findEndpointMethod;
|
|
|
8701
9109
|
exports.flutchFetch = flutchFetch;
|
|
8702
9110
|
exports.flutchHeaders = flutchHeaders;
|
|
8703
9111
|
exports.flutchMistralHook = flutchMistralHook;
|
|
9112
|
+
exports.formatToolSearchResult = formatToolSearchResult;
|
|
8704
9113
|
exports.generateAttachmentSummary = generateAttachmentSummary;
|
|
8705
9114
|
exports.generateModelCacheKey = generateModelCacheKey;
|
|
8706
9115
|
exports.getAttachmentData = getAttachmentData;
|
|
@@ -8718,11 +9127,14 @@ exports.isInternalMode = isInternalMode;
|
|
|
8718
9127
|
exports.isReasoningBlock = isReasoningBlock;
|
|
8719
9128
|
exports.isReasoningModel = isReasoningModel;
|
|
8720
9129
|
exports.isSendableReasoningBlock = isSendableReasoningBlock;
|
|
9130
|
+
exports.isToolSearchCall = isToolSearchCall;
|
|
8721
9131
|
exports.joinTextSteps = joinTextSteps;
|
|
8722
9132
|
exports.loadOAuthProviders = loadOAuthProviders;
|
|
9133
|
+
exports.loadedToolNames = loadedToolNames;
|
|
8723
9134
|
exports.modelAcceptsSamplingParams = modelAcceptsSamplingParams;
|
|
8724
9135
|
exports.modelSupportsPromptCache = modelSupportsPromptCache;
|
|
8725
9136
|
exports.normalizeToolConfigs = normalizeToolConfigs;
|
|
9137
|
+
exports.parseToolSearchResult = parseToolSearchResult;
|
|
8726
9138
|
exports.redactConnectionString = redactConnectionString;
|
|
8727
9139
|
exports.registerFinanceExampleCallback = registerFinanceExampleCallback;
|
|
8728
9140
|
exports.registerUIEndpointsFromClass = registerUIEndpointsFromClass;
|
|
@@ -8730,13 +9142,18 @@ exports.resolveOAuthProviderConfig = resolveOAuthProviderConfig;
|
|
|
8730
9142
|
exports.resolvePromptCacheControl = resolvePromptCacheControl;
|
|
8731
9143
|
exports.resolveRouterURL = resolveRouterURL;
|
|
8732
9144
|
exports.resolveSamplingParams = resolveSamplingParams;
|
|
9145
|
+
exports.rewriteToolReferences = rewriteToolReferences;
|
|
8733
9146
|
exports.sanitizeMessageReasoning = sanitizeMessageReasoning;
|
|
8734
9147
|
exports.sanitizeReasoningForBedrock = sanitizeReasoningForBedrock;
|
|
8735
9148
|
exports.sanitizeTraceData = sanitizeTraceData;
|
|
9149
|
+
exports.splitTools = splitTools;
|
|
8736
9150
|
exports.storeAttachmentData = storeAttachmentData;
|
|
8737
9151
|
exports.toOptionalNumber = toOptionalNumber;
|
|
9152
|
+
exports.toolSearchApplies = toolSearchApplies;
|
|
9153
|
+
exports.toolSearchTerms = toolSearchTerms;
|
|
8738
9154
|
exports.traceApiCall = traceApiCall;
|
|
8739
9155
|
exports.withFlutchContext = withFlutchContext;
|
|
9156
|
+
exports.withToolReferences = withToolReferences;
|
|
8740
9157
|
exports.wrapCohereFetcher = wrapCohereFetcher;
|
|
8741
9158
|
//# sourceMappingURL=index.cjs.map
|
|
8742
9159
|
//# sourceMappingURL=index.cjs.map
|