@flutchai/flutch-sdk 0.6.12 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/index.cjs +422 -9
- 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 +408 -10
- 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";
|
|
@@ -7267,7 +7577,8 @@ function buildOpenAIModelConfig(modelName, temperature, maxTokens, apiToken) {
|
|
|
7267
7577
|
temperature: 1,
|
|
7268
7578
|
maxCompletionTokens: maxTokens,
|
|
7269
7579
|
streaming: true,
|
|
7270
|
-
|
|
7580
|
+
useResponsesApi: true,
|
|
7581
|
+
apiKey: apiToken
|
|
7271
7582
|
};
|
|
7272
7583
|
}
|
|
7273
7584
|
return {
|
|
@@ -7279,7 +7590,7 @@ function buildOpenAIModelConfig(modelName, temperature, maxTokens, apiToken) {
|
|
|
7279
7590
|
},
|
|
7280
7591
|
maxTokens,
|
|
7281
7592
|
streaming: true,
|
|
7282
|
-
|
|
7593
|
+
apiKey: apiToken
|
|
7283
7594
|
};
|
|
7284
7595
|
}
|
|
7285
7596
|
__name(buildOpenAIModelConfig, "buildOpenAIModelConfig");
|
|
@@ -7452,6 +7763,8 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7452
7763
|
modelConfigCache = /* @__PURE__ */ new Map();
|
|
7453
7764
|
// Cache for model instances to avoid recreating identical models
|
|
7454
7765
|
modelInstanceCache = /* @__PURE__ */ new Map();
|
|
7766
|
+
// Tool-search catalogs, keyed like the model instance that uses them
|
|
7767
|
+
toolSearchCatalogs = /* @__PURE__ */ new Map();
|
|
7455
7768
|
static DEFAULT_ENV_MAP = {
|
|
7456
7769
|
[ModelProvider.OPENAI]: "OPENAI_API_KEY",
|
|
7457
7770
|
[ModelProvider.ANTHROPIC]: "ANTHROPIC_API_KEY",
|
|
@@ -7527,7 +7840,8 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7527
7840
|
const withMcp = config.mcpServers && config.mcpServers.length > 0 ? `${base}:mcp:${JSON.stringify(config.mcpServers)}` : base;
|
|
7528
7841
|
const withSampling = config.supportsSamplingParams === void 0 ? withMcp : `${withMcp}:sp:${config.supportsSamplingParams}`;
|
|
7529
7842
|
const withCache = config.promptCache === void 0 && config.promptCacheTtl === void 0 ? withSampling : `${withSampling}:pc:${config.promptCache ?? "auto"}:${config.promptCacheTtl ?? "default"}`;
|
|
7530
|
-
|
|
7843
|
+
const withEffort = config.effort === void 0 ? withCache : `${withCache}:eff:${config.effort}`;
|
|
7844
|
+
return config.toolSearch?.enabled ? `${withEffort}:ts:${JSON.stringify(config.toolSearch)}` : withEffort;
|
|
7531
7845
|
}
|
|
7532
7846
|
// Chat model creators
|
|
7533
7847
|
chatModelCreators = {
|
|
@@ -7561,10 +7875,13 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7561
7875
|
},
|
|
7562
7876
|
anthropicApiKey: apiToken || this.resolveApiKey(ModelProvider.ANTHROPIC),
|
|
7563
7877
|
...routerURL && {
|
|
7564
|
-
anthropicApiUrl: routerURL
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7878
|
+
anthropicApiUrl: routerURL
|
|
7879
|
+
},
|
|
7880
|
+
// Tool search results become `tool_reference` blocks on the wire
|
|
7881
|
+
// (LangChain cannot express them). A request without a search passes
|
|
7882
|
+
// through untouched.
|
|
7883
|
+
clientOptions: {
|
|
7884
|
+
fetch: withToolReferences(routerURL ? flutchFetch : fetch)
|
|
7568
7885
|
}
|
|
7569
7886
|
});
|
|
7570
7887
|
},
|
|
@@ -7808,7 +8125,11 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7808
8125
|
// Converse takes cache control as a per-call option; the Anthropic
|
|
7809
8126
|
// client already got it as a constructor field, and handing it the
|
|
7810
8127
|
// same value twice would put an unknown key in its call options.
|
|
7811
|
-
finalConfig.useBedrock ? promptCacheControl : void 0
|
|
8128
|
+
finalConfig.useBedrock ? promptCacheControl : void 0,
|
|
8129
|
+
config.toolSearch?.enabled ? {
|
|
8130
|
+
options: config.toolSearch,
|
|
8131
|
+
cacheKey
|
|
8132
|
+
} : void 0
|
|
7812
8133
|
);
|
|
7813
8134
|
this.modelInstanceCache.set(cacheKey, boundModel);
|
|
7814
8135
|
return boundModel;
|
|
@@ -7834,8 +8155,9 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7834
8155
|
* - Runnable when tools are bound (model.bindTools returns Runnable)
|
|
7835
8156
|
* - BaseChatModel when no tools
|
|
7836
8157
|
*/
|
|
7837
|
-
async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl) {
|
|
8158
|
+
async bindToolsToModel(model, toolsConfig, customTools, mcpServers, mcpContext, promptCacheControl, toolSearch) {
|
|
7838
8159
|
const allTools = [];
|
|
8160
|
+
const mcpToolsFound = [];
|
|
7839
8161
|
const enabledToolsConfig = (toolsConfig || []).filter((tc) => tc.enabled !== false);
|
|
7840
8162
|
const hasInlineServers = !!(mcpServers && mcpServers.length > 0);
|
|
7841
8163
|
if (enabledToolsConfig.length > 0 || hasInlineServers) {
|
|
@@ -7845,6 +8167,7 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7845
8167
|
const mcpTools = await mcpToolFilter.getFilteredTools(enabledToolsConfig, mcpServers, mcpContext);
|
|
7846
8168
|
this.logger.debug(`Successfully fetched ${mcpTools.length} tools with dynamic schemas from MCP Runtime`);
|
|
7847
8169
|
allTools.push(...mcpTools);
|
|
8170
|
+
mcpToolsFound.push(...mcpTools);
|
|
7848
8171
|
} catch (error) {
|
|
7849
8172
|
this.logger.error(`Failed to fetch tools from MCP Runtime: ${error instanceof Error ? error.message : String(error)}`);
|
|
7850
8173
|
}
|
|
@@ -7858,11 +8181,86 @@ var ModelInitializer = class _ModelInitializer {
|
|
|
7858
8181
|
const boundOptions = {
|
|
7859
8182
|
cache_control: promptCacheControl
|
|
7860
8183
|
};
|
|
8184
|
+
if (toolSearch && toolSearchApplies(toolSearch.options, allTools.length)) {
|
|
8185
|
+
const { core, deferred } = splitTools(mcpToolsFound, toolSearch.options, customTools ?? []);
|
|
8186
|
+
if (deferred.length > 0) {
|
|
8187
|
+
const maxResults = toolSearch.options.maxResults ?? DEFAULT_TOOL_SEARCH_RESULTS;
|
|
8188
|
+
const catalog = new ToolSearchCatalog(deferred);
|
|
8189
|
+
this.toolSearchCatalogs.set(toolSearch.cacheKey, {
|
|
8190
|
+
catalog,
|
|
8191
|
+
maxResults
|
|
8192
|
+
});
|
|
8193
|
+
const mode = model instanceof anthropic.ChatAnthropic ? "deferred" : "rebind";
|
|
8194
|
+
this.logger.debug(`Tool search (${mode}): ${core.length + (customTools?.length ?? 0)} tools in sight, ${deferred.length} found on demand`);
|
|
8195
|
+
return bindToolsWithSearch({
|
|
8196
|
+
model,
|
|
8197
|
+
core: [
|
|
8198
|
+
...core,
|
|
8199
|
+
...customTools ?? []
|
|
8200
|
+
],
|
|
8201
|
+
deferred,
|
|
8202
|
+
searchTool: createToolSearchTool(catalog, maxResults),
|
|
8203
|
+
mode,
|
|
8204
|
+
bindOptions: promptCacheControl ? boundOptions : void 0
|
|
8205
|
+
});
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
7861
8208
|
const modelWithTools = promptCacheControl ? model.bindTools(allTools, boundOptions) : model.bindTools(allTools);
|
|
7862
8209
|
return modelWithTools;
|
|
7863
8210
|
}
|
|
7864
8211
|
return model;
|
|
7865
8212
|
}
|
|
8213
|
+
/**
|
|
8214
|
+
* Runs a `search_tools` call. `config` must be the one the model was
|
|
8215
|
+
* initialized with (same tools, same `toolSearch`): it names the catalog.
|
|
8216
|
+
* Emits tool start/end events like `McpRuntimeHttpClient.executeToolWithEvents`,
|
|
8217
|
+
* so the call shows as a finished step in the chat.
|
|
8218
|
+
*
|
|
8219
|
+
* Never throws: a failure comes back as the tool's answer.
|
|
8220
|
+
*/
|
|
8221
|
+
async searchTools(config, args, runConfig) {
|
|
8222
|
+
const parsedConfig = manager.parseCallbackConfigArg(runConfig);
|
|
8223
|
+
const callbackManager = manager.CallbackManager.configure(parsedConfig.callbacks);
|
|
8224
|
+
const runManager = await callbackManager?.handleToolStart({
|
|
8225
|
+
name: TOOL_SEARCH_NAME,
|
|
8226
|
+
lc: 1,
|
|
8227
|
+
type: "not_implemented",
|
|
8228
|
+
id: [
|
|
8229
|
+
"langchain",
|
|
8230
|
+
"tools",
|
|
8231
|
+
TOOL_SEARCH_NAME
|
|
8232
|
+
]
|
|
8233
|
+
}, JSON.stringify(args ?? {}), parsedConfig.runId, void 0, parsedConfig.tags, parsedConfig.metadata, TOOL_SEARCH_NAME);
|
|
8234
|
+
try {
|
|
8235
|
+
const key = this.generateModelCacheKey(config);
|
|
8236
|
+
if (!this.toolSearchCatalogs.has(key)) {
|
|
8237
|
+
await this.initializeChatModel(config);
|
|
8238
|
+
}
|
|
8239
|
+
const entry = this.toolSearchCatalogs.get(key);
|
|
8240
|
+
const query = typeof args?.query === "string" ? args.query : "";
|
|
8241
|
+
const limit = Number(args?.limit);
|
|
8242
|
+
const content = !entry ? JSON.stringify({
|
|
8243
|
+
error: "Tool search is not available for this agent: every tool is already loaded."
|
|
8244
|
+
}) : !query.trim() ? JSON.stringify({
|
|
8245
|
+
error: "query is required"
|
|
8246
|
+
}) : formatToolSearchResult(entry.catalog.search(query, Number.isFinite(limit) && limit > 0 ? limit : entry.maxResults));
|
|
8247
|
+
await runManager?.handleToolEnd(content);
|
|
8248
|
+
return {
|
|
8249
|
+
content,
|
|
8250
|
+
success: !!entry && !!query.trim()
|
|
8251
|
+
};
|
|
8252
|
+
} catch (error) {
|
|
8253
|
+
await runManager?.handleToolError(error);
|
|
8254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8255
|
+
this.logger.error(`Tool search failed: ${message}`);
|
|
8256
|
+
return {
|
|
8257
|
+
content: JSON.stringify({
|
|
8258
|
+
error: `Tool search failed: ${message}`
|
|
8259
|
+
}),
|
|
8260
|
+
success: false
|
|
8261
|
+
};
|
|
8262
|
+
}
|
|
8263
|
+
}
|
|
7866
8264
|
async initializeRerankModel(config) {
|
|
7867
8265
|
const cacheKey = this.generateModelCacheKey(config);
|
|
7868
8266
|
const cachedModel = this.modelInstanceCache.get(cacheKey);
|
|
@@ -8661,6 +9059,8 @@ exports.CallbackStore = CallbackStore;
|
|
|
8661
9059
|
exports.ChatFeature = ChatFeature;
|
|
8662
9060
|
exports.DEFAULT_ATTACHMENT_THRESHOLD = DEFAULT_ATTACHMENT_THRESHOLD;
|
|
8663
9061
|
exports.DEFAULT_ROUTER_URL = DEFAULT_ROUTER_URL;
|
|
9062
|
+
exports.DEFAULT_TOOL_SEARCH_MIN_TOOLS = DEFAULT_TOOL_SEARCH_MIN_TOOLS;
|
|
9063
|
+
exports.DEFAULT_TOOL_SEARCH_RESULTS = DEFAULT_TOOL_SEARCH_RESULTS;
|
|
8664
9064
|
exports.DEFAULT_TRACER_OPTIONS = DEFAULT_TRACER_OPTIONS;
|
|
8665
9065
|
exports.Endpoint = Endpoint;
|
|
8666
9066
|
exports.FileTokenStore = FileTokenStore;
|
|
@@ -8681,6 +9081,8 @@ exports.PROMPT_CACHE_MIN_VERSIONS = PROMPT_CACHE_MIN_VERSIONS;
|
|
|
8681
9081
|
exports.RetrieverSearchType = RetrieverSearchType;
|
|
8682
9082
|
exports.SAMPLING_PARAM_FREE_THRESHOLDS = SAMPLING_PARAM_FREE_THRESHOLDS;
|
|
8683
9083
|
exports.StreamChannel = StreamChannel;
|
|
9084
|
+
exports.TOOL_SEARCH_NAME = TOOL_SEARCH_NAME;
|
|
9085
|
+
exports.ToolSearchCatalog = ToolSearchCatalog;
|
|
8684
9086
|
exports.UIEndpoint = UIEndpoint;
|
|
8685
9087
|
exports.UniversalCallbackService = UniversalCallbackService;
|
|
8686
9088
|
exports.VoyageAIRerank = VoyageAIRerank;
|
|
@@ -8688,6 +9090,7 @@ exports.WithCallbacks = WithCallbacks;
|
|
|
8688
9090
|
exports.WithEndpoints = WithEndpoints;
|
|
8689
9091
|
exports.WithUIEndpoints = WithUIEndpoints;
|
|
8690
9092
|
exports._internals = _internals;
|
|
9093
|
+
exports.bindToolsWithSearch = bindToolsWithSearch;
|
|
8691
9094
|
exports.bootstrap = bootstrap;
|
|
8692
9095
|
exports.buildEffortRequestFields = buildEffortRequestFields;
|
|
8693
9096
|
exports.buildOAuthAuthorizationUrl = buildOAuthAuthorizationUrl;
|
|
@@ -8697,6 +9100,7 @@ exports.createEndpointDescriptors = createEndpointDescriptors;
|
|
|
8697
9100
|
exports.createGraphAttachment = createGraphAttachment;
|
|
8698
9101
|
exports.createMongoClientAdapter = createMongoClientAdapter;
|
|
8699
9102
|
exports.createStaticMessage = createStaticMessage;
|
|
9103
|
+
exports.createToolSearchTool = createToolSearchTool;
|
|
8700
9104
|
exports.decryptTokens = decryptTokens;
|
|
8701
9105
|
exports.dispatchAttachments = dispatchAttachments;
|
|
8702
9106
|
exports.encryptTokens = encryptTokens;
|
|
@@ -8706,6 +9110,7 @@ exports.findEndpointMethod = findEndpointMethod;
|
|
|
8706
9110
|
exports.flutchFetch = flutchFetch;
|
|
8707
9111
|
exports.flutchHeaders = flutchHeaders;
|
|
8708
9112
|
exports.flutchMistralHook = flutchMistralHook;
|
|
9113
|
+
exports.formatToolSearchResult = formatToolSearchResult;
|
|
8709
9114
|
exports.generateAttachmentSummary = generateAttachmentSummary;
|
|
8710
9115
|
exports.generateModelCacheKey = generateModelCacheKey;
|
|
8711
9116
|
exports.getAttachmentData = getAttachmentData;
|
|
@@ -8723,11 +9128,14 @@ exports.isInternalMode = isInternalMode;
|
|
|
8723
9128
|
exports.isReasoningBlock = isReasoningBlock;
|
|
8724
9129
|
exports.isReasoningModel = isReasoningModel;
|
|
8725
9130
|
exports.isSendableReasoningBlock = isSendableReasoningBlock;
|
|
9131
|
+
exports.isToolSearchCall = isToolSearchCall;
|
|
8726
9132
|
exports.joinTextSteps = joinTextSteps;
|
|
8727
9133
|
exports.loadOAuthProviders = loadOAuthProviders;
|
|
9134
|
+
exports.loadedToolNames = loadedToolNames;
|
|
8728
9135
|
exports.modelAcceptsSamplingParams = modelAcceptsSamplingParams;
|
|
8729
9136
|
exports.modelSupportsPromptCache = modelSupportsPromptCache;
|
|
8730
9137
|
exports.normalizeToolConfigs = normalizeToolConfigs;
|
|
9138
|
+
exports.parseToolSearchResult = parseToolSearchResult;
|
|
8731
9139
|
exports.redactConnectionString = redactConnectionString;
|
|
8732
9140
|
exports.registerFinanceExampleCallback = registerFinanceExampleCallback;
|
|
8733
9141
|
exports.registerUIEndpointsFromClass = registerUIEndpointsFromClass;
|
|
@@ -8735,13 +9143,18 @@ exports.resolveOAuthProviderConfig = resolveOAuthProviderConfig;
|
|
|
8735
9143
|
exports.resolvePromptCacheControl = resolvePromptCacheControl;
|
|
8736
9144
|
exports.resolveRouterURL = resolveRouterURL;
|
|
8737
9145
|
exports.resolveSamplingParams = resolveSamplingParams;
|
|
9146
|
+
exports.rewriteToolReferences = rewriteToolReferences;
|
|
8738
9147
|
exports.sanitizeMessageReasoning = sanitizeMessageReasoning;
|
|
8739
9148
|
exports.sanitizeReasoningForBedrock = sanitizeReasoningForBedrock;
|
|
8740
9149
|
exports.sanitizeTraceData = sanitizeTraceData;
|
|
9150
|
+
exports.splitTools = splitTools;
|
|
8741
9151
|
exports.storeAttachmentData = storeAttachmentData;
|
|
8742
9152
|
exports.toOptionalNumber = toOptionalNumber;
|
|
9153
|
+
exports.toolSearchApplies = toolSearchApplies;
|
|
9154
|
+
exports.toolSearchTerms = toolSearchTerms;
|
|
8743
9155
|
exports.traceApiCall = traceApiCall;
|
|
8744
9156
|
exports.withFlutchContext = withFlutchContext;
|
|
9157
|
+
exports.withToolReferences = withToolReferences;
|
|
8745
9158
|
exports.wrapCohereFetcher = wrapCohereFetcher;
|
|
8746
9159
|
//# sourceMappingURL=index.cjs.map
|
|
8747
9160
|
//# sourceMappingURL=index.cjs.map
|