@flutchai/flutch-sdk 0.6.12 → 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 +419 -7
- 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 +405 -8
- 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');
|
|
@@ -7086,6 +7088,314 @@ exports.McpRuntimeHttpClient = _ts_decorate25([
|
|
|
7086
7088
|
])
|
|
7087
7089
|
], exports.McpRuntimeHttpClient);
|
|
7088
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
|
+
|
|
7089
7399
|
// src/models/enums.ts
|
|
7090
7400
|
var ModelProvider = /* @__PURE__ */ (function(ModelProvider2) {
|
|
7091
7401
|
ModelProvider2["MISTRAL"] = "mistral";
|
|
@@ -7452,6 +7762,8 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7452
7762
|
modelConfigCache = /* @__PURE__ */ new Map();
|
|
7453
7763
|
// Cache for model instances to avoid recreating identical models
|
|
7454
7764
|
modelInstanceCache = /* @__PURE__ */ new Map();
|
|
7765
|
+
// Tool-search catalogs, keyed like the model instance that uses them
|
|
7766
|
+
toolSearchCatalogs = /* @__PURE__ */ new Map();
|
|
7455
7767
|
static DEFAULT_ENV_MAP = {
|
|
7456
7768
|
[ModelProvider.OPENAI]: "OPENAI_API_KEY",
|
|
7457
7769
|
[ModelProvider.ANTHROPIC]: "ANTHROPIC_API_KEY",
|
|
@@ -7527,7 +7839,8 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7527
7839
|
const withMcp = config.mcpServers && config.mcpServers.length > 0 ? `${base}:mcp:${JSON.stringify(config.mcpServers)}` : base;
|
|
7528
7840
|
const withSampling = config.supportsSamplingParams === void 0 ? withMcp : `${withMcp}:sp:${config.supportsSamplingParams}`;
|
|
7529
7841
|
const withCache = config.promptCache === void 0 && config.promptCacheTtl === void 0 ? withSampling : `${withSampling}:pc:${config.promptCache ?? "auto"}:${config.promptCacheTtl ?? "default"}`;
|
|
7530
|
-
|
|
7842
|
+
const withEffort = config.effort === void 0 ? withCache : `${withCache}:eff:${config.effort}`;
|
|
7843
|
+
return config.toolSearch?.enabled ? `${withEffort}:ts:${JSON.stringify(config.toolSearch)}` : withEffort;
|
|
7531
7844
|
}
|
|
7532
7845
|
// Chat model creators
|
|
7533
7846
|
chatModelCreators = {
|
|
@@ -7561,10 +7874,13 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7561
7874
|
},
|
|
7562
7875
|
anthropicApiKey: apiToken || this.resolveApiKey(ModelProvider.ANTHROPIC),
|
|
7563
7876
|
...routerURL && {
|
|
7564
|
-
anthropicApiUrl: routerURL
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
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)
|
|
7568
7884
|
}
|
|
7569
7885
|
});
|
|
7570
7886
|
},
|
|
@@ -7808,7 +8124,11 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7808
8124
|
// Converse takes cache control as a per-call option; the Anthropic
|
|
7809
8125
|
// client already got it as a constructor field, and handing it the
|
|
7810
8126
|
// same value twice would put an unknown key in its call options.
|
|
7811
|
-
finalConfig.useBedrock ? promptCacheControl : void 0
|
|
8127
|
+
finalConfig.useBedrock ? promptCacheControl : void 0,
|
|
8128
|
+
config.toolSearch?.enabled ? {
|
|
8129
|
+
options: config.toolSearch,
|
|
8130
|
+
cacheKey
|
|
8131
|
+
} : void 0
|
|
7812
8132
|
);
|
|
7813
8133
|
this.modelInstanceCache.set(cacheKey, boundModel);
|
|
7814
8134
|
return boundModel;
|
|
@@ -7834,8 +8154,9 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7834
8154
|
* - Runnable when tools are bound (model.bindTools returns Runnable)
|
|
7835
8155
|
* - BaseChatModel when no tools
|
|
7836
8156
|
*/
|
|
7837
|
-
async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl) {
|
|
8157
|
+
async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl, toolSearch) {
|
|
7838
8158
|
const allTools = [];
|
|
8159
|
+
const mcpToolsFound = [];
|
|
7839
8160
|
const enabledToolsConfig = (toolsConfig || []).filter((tc) => tc.enabled !== false);
|
|
7840
8161
|
const hasInlineServers = !!(mcpServers && mcpServers.length > 0);
|
|
7841
8162
|
if (enabledToolsConfig.length > 0 || hasInlineServers) {
|
|
@@ -7845,6 +8166,7 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7845
8166
|
const mcpTools = await mcpToolFilter.getFilteredTools(enabledToolsConfig, mcpServers, mcpContext);
|
|
7846
8167
|
this.logger.debug(`Successfully fetched ${mcpTools.length} tools with dynamic schemas from MCP Runtime`);
|
|
7847
8168
|
allTools.push(...mcpTools);
|
|
8169
|
+
mcpToolsFound.push(...mcpTools);
|
|
7848
8170
|
} catch (error) {
|
|
7849
8171
|
this.logger.error(`Failed to fetch tools from MCP Runtime: ${error instanceof Error ? error.message : String(error)}`);
|
|
7850
8172
|
}
|
|
@@ -7858,11 +8180,86 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7858
8180
|
const boundOptions = {
|
|
7859
8181
|
cache_control: promptCacheControl
|
|
7860
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
|
+
}
|
|
7861
8207
|
const modelWithTools = promptCacheControl ? model.bindTools(allTools, boundOptions) : model.bindTools(allTools);
|
|
7862
8208
|
return modelWithTools;
|
|
7863
8209
|
}
|
|
7864
8210
|
return model;
|
|
7865
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
|
+
}
|
|
7866
8263
|
async initializeRerankModel(config) {
|
|
7867
8264
|
const cacheKey = this.generateModelCacheKey(config);
|
|
7868
8265
|
const cachedModel = this.modelInstanceCache.get(cacheKey);
|
|
@@ -8661,6 +9058,8 @@ exports.CallbackStore = CallbackStore;
|
|
|
8661
9058
|
exports.ChatFeature = ChatFeature;
|
|
8662
9059
|
exports.DEFAULT_ATTACHMENT_THRESHOLD = DEFAULT_ATTACHMENT_THRESHOLD;
|
|
8663
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;
|
|
8664
9063
|
exports.DEFAULT_TRACER_OPTIONS = DEFAULT_TRACER_OPTIONS;
|
|
8665
9064
|
exports.Endpoint = Endpoint;
|
|
8666
9065
|
exports.FileTokenStore = FileTokenStore;
|
|
@@ -8681,6 +9080,8 @@ exports.PROMPT_CACHE_MIN_VERSIONS = PROMPT_CACHE_MIN_VERSIONS;
|
|
|
8681
9080
|
exports.RetrieverSearchType = RetrieverSearchType;
|
|
8682
9081
|
exports.SAMPLING_PARAM_FREE_THRESHOLDS = SAMPLING_PARAM_FREE_THRESHOLDS;
|
|
8683
9082
|
exports.StreamChannel = StreamChannel;
|
|
9083
|
+
exports.TOOL_SEARCH_NAME = TOOL_SEARCH_NAME;
|
|
9084
|
+
exports.ToolSearchCatalog = ToolSearchCatalog;
|
|
8684
9085
|
exports.UIEndpoint = UIEndpoint;
|
|
8685
9086
|
exports.UniversalCallbackService = UniversalCallbackService;
|
|
8686
9087
|
exports.VoyageAIRerank = VoyageAIRerank;
|
|
@@ -8688,6 +9089,7 @@ exports.WithCallbacks = WithCallbacks;
|
|
|
8688
9089
|
exports.WithEndpoints = WithEndpoints;
|
|
8689
9090
|
exports.WithUIEndpoints = WithUIEndpoints;
|
|
8690
9091
|
exports._internals = _internals;
|
|
9092
|
+
exports.bindToolsWithSearch = bindToolsWithSearch;
|
|
8691
9093
|
exports.bootstrap = bootstrap;
|
|
8692
9094
|
exports.buildEffortRequestFields = buildEffortRequestFields;
|
|
8693
9095
|
exports.buildOAuthAuthorizationUrl = buildOAuthAuthorizationUrl;
|
|
@@ -8697,6 +9099,7 @@ exports.createEndpointDescriptors = createEndpointDescriptors;
|
|
|
8697
9099
|
exports.createGraphAttachment = createGraphAttachment;
|
|
8698
9100
|
exports.createMongoClientAdapter = createMongoClientAdapter;
|
|
8699
9101
|
exports.createStaticMessage = createStaticMessage;
|
|
9102
|
+
exports.createToolSearchTool = createToolSearchTool;
|
|
8700
9103
|
exports.decryptTokens = decryptTokens;
|
|
8701
9104
|
exports.dispatchAttachments = dispatchAttachments;
|
|
8702
9105
|
exports.encryptTokens = encryptTokens;
|
|
@@ -8706,6 +9109,7 @@ exports.findEndpointMethod = findEndpointMethod;
|
|
|
8706
9109
|
exports.flutchFetch = flutchFetch;
|
|
8707
9110
|
exports.flutchHeaders = flutchHeaders;
|
|
8708
9111
|
exports.flutchMistralHook = flutchMistralHook;
|
|
9112
|
+
exports.formatToolSearchResult = formatToolSearchResult;
|
|
8709
9113
|
exports.generateAttachmentSummary = generateAttachmentSummary;
|
|
8710
9114
|
exports.generateModelCacheKey = generateModelCacheKey;
|
|
8711
9115
|
exports.getAttachmentData = getAttachmentData;
|
|
@@ -8723,11 +9127,14 @@ exports.isInternalMode = isInternalMode;
|
|
|
8723
9127
|
exports.isReasoningBlock = isReasoningBlock;
|
|
8724
9128
|
exports.isReasoningModel = isReasoningModel;
|
|
8725
9129
|
exports.isSendableReasoningBlock = isSendableReasoningBlock;
|
|
9130
|
+
exports.isToolSearchCall = isToolSearchCall;
|
|
8726
9131
|
exports.joinTextSteps = joinTextSteps;
|
|
8727
9132
|
exports.loadOAuthProviders = loadOAuthProviders;
|
|
9133
|
+
exports.loadedToolNames = loadedToolNames;
|
|
8728
9134
|
exports.modelAcceptsSamplingParams = modelAcceptsSamplingParams;
|
|
8729
9135
|
exports.modelSupportsPromptCache = modelSupportsPromptCache;
|
|
8730
9136
|
exports.normalizeToolConfigs = normalizeToolConfigs;
|
|
9137
|
+
exports.parseToolSearchResult = parseToolSearchResult;
|
|
8731
9138
|
exports.redactConnectionString = redactConnectionString;
|
|
8732
9139
|
exports.registerFinanceExampleCallback = registerFinanceExampleCallback;
|
|
8733
9140
|
exports.registerUIEndpointsFromClass = registerUIEndpointsFromClass;
|
|
@@ -8735,13 +9142,18 @@ exports.resolveOAuthProviderConfig = resolveOAuthProviderConfig;
|
|
|
8735
9142
|
exports.resolvePromptCacheControl = resolvePromptCacheControl;
|
|
8736
9143
|
exports.resolveRouterURL = resolveRouterURL;
|
|
8737
9144
|
exports.resolveSamplingParams = resolveSamplingParams;
|
|
9145
|
+
exports.rewriteToolReferences = rewriteToolReferences;
|
|
8738
9146
|
exports.sanitizeMessageReasoning = sanitizeMessageReasoning;
|
|
8739
9147
|
exports.sanitizeReasoningForBedrock = sanitizeReasoningForBedrock;
|
|
8740
9148
|
exports.sanitizeTraceData = sanitizeTraceData;
|
|
9149
|
+
exports.splitTools = splitTools;
|
|
8741
9150
|
exports.storeAttachmentData = storeAttachmentData;
|
|
8742
9151
|
exports.toOptionalNumber = toOptionalNumber;
|
|
9152
|
+
exports.toolSearchApplies = toolSearchApplies;
|
|
9153
|
+
exports.toolSearchTerms = toolSearchTerms;
|
|
8743
9154
|
exports.traceApiCall = traceApiCall;
|
|
8744
9155
|
exports.withFlutchContext = withFlutchContext;
|
|
9156
|
+
exports.withToolReferences = withToolReferences;
|
|
8745
9157
|
exports.wrapCohereFetcher = wrapCohereFetcher;
|
|
8746
9158
|
//# sourceMappingURL=index.cjs.map
|
|
8747
9159
|
//# sourceMappingURL=index.cjs.map
|