@stackfactor/agent-utils 1.2.20 → 1.3.2
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 +68 -40
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/langChain.d.ts +26 -60
- package/dist/cjs/langChain.d.ts.map +1 -1
- package/dist/cjs/langChain.js +68 -289
- package/dist/cjs/tavily.d.ts +91 -0
- package/dist/cjs/tavily.d.ts.map +1 -0
- package/dist/cjs/tavily.js +404 -0
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/langChain.d.ts +26 -60
- package/dist/esm/langChain.d.ts.map +1 -1
- package/dist/esm/langChain.js +65 -288
- package/dist/esm/tavily.d.ts +91 -0
- package/dist/esm/tavily.d.ts.map +1 -0
- package/dist/esm/tavily.js +395 -0
- package/package.json +3 -1
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildTavilyTools = exports.assertTavilyUsable = exports.getTavilyOptions = void 0;
|
|
7
|
+
const langchain_1 = require("langchain");
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const const_js_1 = __importDefault(require("./const.js"));
|
|
10
|
+
const errorHandling_js_1 = __importDefault(require("./errorHandling.js"));
|
|
11
|
+
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
12
|
+
const runtimeContext_js_1 = require("./runtimeContext.js");
|
|
13
|
+
const TAVILY_API_BASE = "https://api.tavily.com";
|
|
14
|
+
/** Tavily list price per credit, used when the integration defines no rate. */
|
|
15
|
+
const DEFAULT_CREDIT_COST_USD = 0.008;
|
|
16
|
+
/**
|
|
17
|
+
* Context budgets, in characters, applied to everything a tool hands back to
|
|
18
|
+
* the model. Tavily returns whatever the page holds, and unlike a provider's
|
|
19
|
+
* native search there is nothing between its response and the context window,
|
|
20
|
+
* so these caps are the only thing standing between one `extract` call and a
|
|
21
|
+
* 100-page PDF. Roughly four characters per token: 60k ≈ 15k tokens per URL,
|
|
22
|
+
* 150k ≈ 37k tokens per call.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_MAX_CHARS_PER_URL = 60_000;
|
|
25
|
+
const DEFAULT_MAX_CHARS_TOTAL = 150_000;
|
|
26
|
+
/** Kept deliberately small — search finds documents, `extract` reads them. */
|
|
27
|
+
const DEFAULT_MAX_RESULTS = 5;
|
|
28
|
+
/** Tavily's own default when a query is supplied. */
|
|
29
|
+
const DEFAULT_CHUNKS_PER_SOURCE = 3;
|
|
30
|
+
/** Crawl returns full content per page, so the page count is the cost driver. */
|
|
31
|
+
const DEFAULT_CRAWL_LIMIT = 10;
|
|
32
|
+
/** Seconds. Tavily's own per-request default; crawls legitimately run long. */
|
|
33
|
+
const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
34
|
+
const DEFAULT_CRAWL_TIMEOUT_SECONDS = 120;
|
|
35
|
+
/**
|
|
36
|
+
* Normalizes `config.tavily` into an options object, returning `null` when the
|
|
37
|
+
* Tavily tools are off so callers can use it as the single enablement gate.
|
|
38
|
+
* @param config - The integration config object
|
|
39
|
+
* @returns Normalized options, or `null` when Tavily is not enabled
|
|
40
|
+
*/
|
|
41
|
+
const getTavilyOptions = (config) => {
|
|
42
|
+
const tavily = config?.tavily;
|
|
43
|
+
if (!tavily)
|
|
44
|
+
return null;
|
|
45
|
+
return tavily === true ? {} : tavily;
|
|
46
|
+
};
|
|
47
|
+
exports.getTavilyOptions = getTavilyOptions;
|
|
48
|
+
/**
|
|
49
|
+
* Fails fast on the two ways a Tavily config cannot work. Tavily tools are
|
|
50
|
+
* executed by the agent loop in this process, so outside agentic mode the model
|
|
51
|
+
* would be handed tools whose calls nothing answers: the turn ends silently and
|
|
52
|
+
* looks like the model ignored them. Called once at config time rather than
|
|
53
|
+
* discovered per request.
|
|
54
|
+
* @param config - The integration config object
|
|
55
|
+
* @param options - Normalized options from `getTavilyOptions`
|
|
56
|
+
*/
|
|
57
|
+
const assertTavilyUsable = (config, options) => {
|
|
58
|
+
if (config?.agentic !== true) {
|
|
59
|
+
throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.BAD_REQUEST, "Tavily tools run client-side and are only executed by the agent loop; set config.agentic to true to use them.");
|
|
60
|
+
}
|
|
61
|
+
if (!config?.tavilyAPIKey) {
|
|
62
|
+
throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, "Tavily tools are enabled but config.tavilyAPIKey is missing.");
|
|
63
|
+
}
|
|
64
|
+
const unknown = (options.tools || []).filter((name) => !["search", "extract", "crawl", "map"].includes(name));
|
|
65
|
+
if (unknown.length > 0) {
|
|
66
|
+
throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.BAD_REQUEST, `Unknown Tavily tool(s): ${unknown.join(", ")}. Valid values are search, extract, crawl, map.`);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.assertTavilyUsable = assertTavilyUsable;
|
|
70
|
+
/** Retries for a transient (429/5xx) Tavily response before giving up. */
|
|
71
|
+
const MAX_TAVILY_RETRIES = 2;
|
|
72
|
+
/** Base delay for retry backoff; doubled on each subsequent attempt. */
|
|
73
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
74
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
75
|
+
/**
|
|
76
|
+
* POSTs to one Tavily endpoint. The REST API is snake_case while the published
|
|
77
|
+
* SDK's option names are camelCase, so bodies here are written in the wire
|
|
78
|
+
* format the API actually accepts. `include_usage` is always set: the response
|
|
79
|
+
* then carries the exact credits the call consumed, which is what the tracker
|
|
80
|
+
* bills instead of an estimate. `timeout` is always set from the caller's
|
|
81
|
+
* budget so Tavily's own (shorter) per-endpoint defaults don't cut a call off
|
|
82
|
+
* before the time this library already told the caller it had.
|
|
83
|
+
*
|
|
84
|
+
* Rate-limit (429) and server (5xx) responses are retried with exponential
|
|
85
|
+
* backoff — everything else, including a caller-cancelled request, fails
|
|
86
|
+
* immediately.
|
|
87
|
+
* @param endpoint - The Tavily endpoint name, e.g. `"search"`
|
|
88
|
+
* @param body - Request body in the API's snake_case form; `undefined` values
|
|
89
|
+
* are dropped so Tavily applies its own defaults
|
|
90
|
+
* @param apiKey - The Tavily API key
|
|
91
|
+
* @param timeoutSeconds - Per-request timeout forwarded to Tavily
|
|
92
|
+
* @returns The parsed JSON response
|
|
93
|
+
*/
|
|
94
|
+
const callTavily = async (endpoint, body, apiKey, timeoutSeconds) => {
|
|
95
|
+
const payload = {
|
|
96
|
+
include_usage: true,
|
|
97
|
+
timeout: timeoutSeconds,
|
|
98
|
+
};
|
|
99
|
+
for (const [key, value] of Object.entries(body)) {
|
|
100
|
+
if (value !== undefined && value !== null)
|
|
101
|
+
payload[key] = value;
|
|
102
|
+
}
|
|
103
|
+
for (let attempt = 0;; attempt++) {
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await fetch(`${TAVILY_API_BASE}/${endpoint}`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: {
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
Authorization: `Bearer ${apiKey}`,
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify(payload),
|
|
113
|
+
// Honour caller cancellation the same way the LLM calls do, so an
|
|
114
|
+
// abandoned run stops paying for in-flight crawls.
|
|
115
|
+
signal: (0, runtimeContext_js_1.getAbortSignal)() ?? undefined,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, `Tavily ${endpoint} request failed: ${err?.message || String(err)}`);
|
|
120
|
+
}
|
|
121
|
+
if (response.ok)
|
|
122
|
+
return response.json();
|
|
123
|
+
const retryable = response.status === 429 || response.status >= 500;
|
|
124
|
+
if (retryable && attempt < MAX_TAVILY_RETRIES) {
|
|
125
|
+
await sleep(RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const detail = await response.text().catch(() => "");
|
|
129
|
+
throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, `Tavily ${endpoint} returned ${response.status}: ${detail.slice(0, 500)}`);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Adds one Tavily call's credits to the tracker and records the URLs it
|
|
134
|
+
* returned. Credits come from the response's own `usage` block, priced with the
|
|
135
|
+
* `tavily-credit-costs` constant (USD per credit) and accumulated under
|
|
136
|
+
* `tavilyCredits`. Sources land on `tracker.webSearchSources`, deduplicated by
|
|
137
|
+
* URL across the whole run, so callers have the citation list Tavily's terms
|
|
138
|
+
* require when this output is shown to end users.
|
|
139
|
+
* @param tracker - The caller's usage accumulator; no-ops when absent
|
|
140
|
+
* @param config - Config object carrying the `tavily-credit-costs` constant
|
|
141
|
+
* @param credits - Credits reported by the Tavily response
|
|
142
|
+
* @param sources - URLs (and titles where known) the call returned
|
|
143
|
+
*/
|
|
144
|
+
const recordTavilyUsage = (tracker, config, credits, sources) => {
|
|
145
|
+
if (!tracker)
|
|
146
|
+
return;
|
|
147
|
+
if (typeof tracker.cost !== "number")
|
|
148
|
+
tracker.cost = 0;
|
|
149
|
+
if (!tracker.tokens || typeof tracker.tokens !== "object")
|
|
150
|
+
tracker.tokens = {};
|
|
151
|
+
if (Number.isFinite(credits) && credits > 0) {
|
|
152
|
+
const configured = Number(config?.["tavily-credit-costs"]);
|
|
153
|
+
const rate = Number.isFinite(configured)
|
|
154
|
+
? configured
|
|
155
|
+
: DEFAULT_CREDIT_COST_USD;
|
|
156
|
+
const addedCost = credits * rate;
|
|
157
|
+
if (Number.isFinite(addedCost) && addedCost > 0)
|
|
158
|
+
tracker.cost += addedCost;
|
|
159
|
+
tracker.tokens.tavilyCredits =
|
|
160
|
+
(tracker.tokens.tavilyCredits || 0) + credits;
|
|
161
|
+
}
|
|
162
|
+
if (sources.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
const existing = tracker.webSearchSources || (tracker.webSearchSources = []);
|
|
165
|
+
for (const source of sources) {
|
|
166
|
+
if (!source?.url)
|
|
167
|
+
continue;
|
|
168
|
+
if (!existing.some((entry) => entry.url === source.url)) {
|
|
169
|
+
existing.push(source);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Trims one document to whatever the per-URL and per-call budgets allow,
|
|
175
|
+
* appending a note when anything was cut.
|
|
176
|
+
* @param text - The document text
|
|
177
|
+
* @param budget - Mutable budget for the current tool call
|
|
178
|
+
* @returns The text, truncated with an inline note when over budget
|
|
179
|
+
*/
|
|
180
|
+
const applyBudget = (text, budget) => {
|
|
181
|
+
const body = typeof text === "string" ? text : "";
|
|
182
|
+
if (!body)
|
|
183
|
+
return "";
|
|
184
|
+
const allowance = Math.min(budget.perUrl, budget.remaining);
|
|
185
|
+
if (allowance <= 0) {
|
|
186
|
+
return "[omitted: this call's character budget is exhausted — request fewer URLs at a time]";
|
|
187
|
+
}
|
|
188
|
+
if (body.length <= allowance) {
|
|
189
|
+
budget.remaining -= body.length;
|
|
190
|
+
return body;
|
|
191
|
+
}
|
|
192
|
+
budget.remaining -= allowance;
|
|
193
|
+
return `${body.slice(0, allowance)}\n\n[truncated: ${allowance.toLocaleString()} of ${body.length.toLocaleString()} characters shown — narrow the query or request fewer URLs to see the parts that matter]`;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Renders documents for the model as titled markdown sections. Tavily's own
|
|
197
|
+
* result objects carry `score`, `published_date`, `favicon` and `id` alongside
|
|
198
|
+
* the content; none of that informs an answer, so only the title, URL and text
|
|
199
|
+
* are forwarded and the rest is dropped rather than billed as input tokens.
|
|
200
|
+
* @param documents - Documents to render
|
|
201
|
+
* @param budget - Mutable budget for the current tool call
|
|
202
|
+
* @returns A markdown string, or a not-found notice when there is nothing
|
|
203
|
+
*/
|
|
204
|
+
const renderDocuments = (documents, budget) => {
|
|
205
|
+
const sections = [];
|
|
206
|
+
for (const document of documents) {
|
|
207
|
+
const body = applyBudget(document.text, budget);
|
|
208
|
+
if (!body)
|
|
209
|
+
continue;
|
|
210
|
+
const heading = document.title
|
|
211
|
+
? `## ${document.title}\n${document.url}`
|
|
212
|
+
: `## ${document.url}`;
|
|
213
|
+
sections.push(`${heading}\n\n${body}`);
|
|
214
|
+
}
|
|
215
|
+
if (sections.length === 0)
|
|
216
|
+
return "No content was returned.";
|
|
217
|
+
return sections.join("\n\n---\n\n");
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Builds the LangChain tools for the Tavily endpoints named in
|
|
221
|
+
* `options.tools`, closed over the run's config and usage tracker. Tools are
|
|
222
|
+
* created per run rather than shared, because each one bills into that run's
|
|
223
|
+
* tracker.
|
|
224
|
+
*
|
|
225
|
+
* `web_extract` covers both of the reading modes Tavily supports through one
|
|
226
|
+
* tool: with a `query` the API returns only the matching chunks of each page,
|
|
227
|
+
* and without one it returns the whole document. Splitting that into two tools
|
|
228
|
+
* would duplicate the description tokens on every request to express a
|
|
229
|
+
* distinction the API already makes with one optional field.
|
|
230
|
+
*
|
|
231
|
+
* @param config - Config object carrying `tavilyAPIKey` and cost constants
|
|
232
|
+
* @param options - Normalized options from `getTavilyOptions`
|
|
233
|
+
* @param usageTracker - Optional accumulator billed for every Tavily call
|
|
234
|
+
* @returns LangChain tool instances to append to the agent's tool list
|
|
235
|
+
*/
|
|
236
|
+
const buildTavilyTools = (config, options, usageTracker = null) => {
|
|
237
|
+
const apiKey = config.tavilyAPIKey;
|
|
238
|
+
const enabled = options.tools || ["search", "extract"];
|
|
239
|
+
const format = options.format || "markdown";
|
|
240
|
+
const maxCharsPerUrl = options.maxCharsPerUrl ?? DEFAULT_MAX_CHARS_PER_URL;
|
|
241
|
+
const maxCharsTotal = options.maxCharsTotal ?? DEFAULT_MAX_CHARS_TOTAL;
|
|
242
|
+
const crawlLimit = options.crawlLimit ?? DEFAULT_CRAWL_LIMIT;
|
|
243
|
+
const newBudget = () => ({
|
|
244
|
+
remaining: maxCharsTotal,
|
|
245
|
+
perUrl: maxCharsPerUrl,
|
|
246
|
+
});
|
|
247
|
+
const tools = [];
|
|
248
|
+
if (enabled.includes("search")) {
|
|
249
|
+
tools.push((0, langchain_1.tool)(async ({ query, maxResults }) => {
|
|
250
|
+
const data = await callTavily("search", {
|
|
251
|
+
query,
|
|
252
|
+
search_depth: options.searchDepth,
|
|
253
|
+
max_results: maxResults || options.maxResults || DEFAULT_MAX_RESULTS,
|
|
254
|
+
include_domains: options.includeDomains,
|
|
255
|
+
exclude_domains: options.excludeDomains,
|
|
256
|
+
// Snippets only. Full page text is `web_extract`'s job, and
|
|
257
|
+
// asking for it here would pay for every result to find one.
|
|
258
|
+
include_raw_content: false,
|
|
259
|
+
}, apiKey, DEFAULT_TIMEOUT_SECONDS);
|
|
260
|
+
const results = data?.results || [];
|
|
261
|
+
recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, results.map((result) => ({
|
|
262
|
+
url: result?.url,
|
|
263
|
+
...(result?.title ? { title: result.title } : {}),
|
|
264
|
+
})));
|
|
265
|
+
if (results.length === 0)
|
|
266
|
+
return "No results found.";
|
|
267
|
+
const budget = newBudget();
|
|
268
|
+
return renderDocuments(results.map((result) => ({
|
|
269
|
+
url: result?.url,
|
|
270
|
+
title: result?.title,
|
|
271
|
+
text: result?.content || "",
|
|
272
|
+
})), budget);
|
|
273
|
+
}, {
|
|
274
|
+
name: "web_search",
|
|
275
|
+
description: "Search the web and return short relevant snippets with their URLs. Use this to find documents; to read one in full or to pull specific passages from it, follow up with web_extract on the URL.",
|
|
276
|
+
schema: zod_1.z.object({
|
|
277
|
+
query: zod_1.z.string().describe("The search query."),
|
|
278
|
+
maxResults: zod_1.z
|
|
279
|
+
.number()
|
|
280
|
+
.int()
|
|
281
|
+
.min(1)
|
|
282
|
+
.max(20)
|
|
283
|
+
.optional()
|
|
284
|
+
.describe("How many results to return. Defaults to 5."),
|
|
285
|
+
}),
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
if (enabled.includes("extract")) {
|
|
289
|
+
tools.push((0, langchain_1.tool)(async ({ urls, query }) => {
|
|
290
|
+
const data = await callTavily("extract", {
|
|
291
|
+
urls,
|
|
292
|
+
extract_depth: options.extractDepth,
|
|
293
|
+
format,
|
|
294
|
+
// `query` switches the API from whole-document extraction to
|
|
295
|
+
// returning only the chunks that match it.
|
|
296
|
+
query: query || undefined,
|
|
297
|
+
chunks_per_source: query
|
|
298
|
+
? options.chunksPerSource || DEFAULT_CHUNKS_PER_SOURCE
|
|
299
|
+
: undefined,
|
|
300
|
+
}, apiKey, DEFAULT_TIMEOUT_SECONDS);
|
|
301
|
+
const results = data?.results || [];
|
|
302
|
+
recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, results.map((result) => ({
|
|
303
|
+
url: result?.url,
|
|
304
|
+
...(result?.title ? { title: result.title } : {}),
|
|
305
|
+
})));
|
|
306
|
+
const budget = newBudget();
|
|
307
|
+
let rendered = renderDocuments(results.map((result) => ({
|
|
308
|
+
url: result?.url,
|
|
309
|
+
title: result?.title,
|
|
310
|
+
text: result?.raw_content || "",
|
|
311
|
+
})), budget);
|
|
312
|
+
// Surface per-URL failures: the model asked for these pages, and
|
|
313
|
+
// silently returning fewer than requested invites it to treat a
|
|
314
|
+
// fetch failure as an absence of content.
|
|
315
|
+
const failed = data?.failed_results || [];
|
|
316
|
+
if (failed.length > 0) {
|
|
317
|
+
const lines = failed
|
|
318
|
+
.map((entry) => `- ${entry?.url}: ${entry?.error}`)
|
|
319
|
+
.join("\n");
|
|
320
|
+
rendered += `\n\n---\n\nFailed to extract:\n${lines}`;
|
|
321
|
+
}
|
|
322
|
+
return rendered;
|
|
323
|
+
}, {
|
|
324
|
+
name: "web_extract",
|
|
325
|
+
description: "Read the content of specific URLs. Pass a query to get back only the passages relevant to it — much cheaper and usually sufficient. Omit the query to retrieve the whole document, which is appropriate when you need its full structure. Long documents are truncated, and the result says so when that happens.",
|
|
326
|
+
schema: zod_1.z.object({
|
|
327
|
+
urls: zod_1.z
|
|
328
|
+
.array(zod_1.z.string())
|
|
329
|
+
.min(1)
|
|
330
|
+
.max(20)
|
|
331
|
+
.describe("The URLs to read."),
|
|
332
|
+
query: zod_1.z
|
|
333
|
+
.string()
|
|
334
|
+
.optional()
|
|
335
|
+
.describe("What to look for in each page. Omit to retrieve whole documents."),
|
|
336
|
+
}),
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
if (enabled.includes("map")) {
|
|
340
|
+
tools.push((0, langchain_1.tool)(async ({ url, instructions }) => {
|
|
341
|
+
const data = await callTavily("map", { url, instructions: instructions || undefined, limit: crawlLimit }, apiKey, DEFAULT_TIMEOUT_SECONDS);
|
|
342
|
+
const urls = data?.results || [];
|
|
343
|
+
recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, urls.map((entry) => ({ url: entry })));
|
|
344
|
+
if (urls.length === 0)
|
|
345
|
+
return "No URLs found.";
|
|
346
|
+
return `Found ${urls.length} URL(s) under ${data?.base_url || url}:\n${urls
|
|
347
|
+
.map((entry) => `- ${entry}`)
|
|
348
|
+
.join("\n")}`;
|
|
349
|
+
}, {
|
|
350
|
+
name: "web_map",
|
|
351
|
+
description: "List the URLs available under a site or section, without retrieving any page content. Use this to discover what a documentation site or document library holds, then read the ones you want with web_extract.",
|
|
352
|
+
schema: zod_1.z.object({
|
|
353
|
+
url: zod_1.z.string().describe("The site or section URL to map."),
|
|
354
|
+
instructions: zod_1.z
|
|
355
|
+
.string()
|
|
356
|
+
.optional()
|
|
357
|
+
.describe("Plain-language description of the pages worth including."),
|
|
358
|
+
}),
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
if (enabled.includes("crawl")) {
|
|
362
|
+
tools.push((0, langchain_1.tool)(async ({ url, instructions, limit }) => {
|
|
363
|
+
const data = await callTavily("crawl", {
|
|
364
|
+
url,
|
|
365
|
+
instructions: instructions || undefined,
|
|
366
|
+
limit: Math.min(limit || crawlLimit, crawlLimit),
|
|
367
|
+
extract_depth: options.extractDepth,
|
|
368
|
+
format,
|
|
369
|
+
}, apiKey, DEFAULT_CRAWL_TIMEOUT_SECONDS);
|
|
370
|
+
const results = data?.results || [];
|
|
371
|
+
recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, results.map((result) => ({ url: result?.url })));
|
|
372
|
+
const budget = newBudget();
|
|
373
|
+
return renderDocuments(results.map((result) => ({
|
|
374
|
+
url: result?.url,
|
|
375
|
+
title: result?.title,
|
|
376
|
+
text: result?.raw_content || "",
|
|
377
|
+
})), budget);
|
|
378
|
+
}, {
|
|
379
|
+
name: "web_crawl",
|
|
380
|
+
description: "Follow links from a starting URL and return the content of the pages found. This returns full page content for every page visited, so prefer web_map followed by web_extract when you only need a few pages. Use the instructions argument to describe which pages matter.",
|
|
381
|
+
schema: zod_1.z.object({
|
|
382
|
+
url: zod_1.z.string().describe("The URL to start crawling from."),
|
|
383
|
+
instructions: zod_1.z
|
|
384
|
+
.string()
|
|
385
|
+
.optional()
|
|
386
|
+
.describe("Plain-language description of the pages worth following."),
|
|
387
|
+
limit: zod_1.z
|
|
388
|
+
.number()
|
|
389
|
+
.int()
|
|
390
|
+
.min(1)
|
|
391
|
+
.optional()
|
|
392
|
+
.describe("Maximum pages to return; capped by the configured crawl limit."),
|
|
393
|
+
}),
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
logger_js_1.default.log(null, logger_js_1.default.levels.info, `Tavily tools enabled: ${tools.map((entry) => entry.name).join(", ")}`);
|
|
397
|
+
return tools;
|
|
398
|
+
};
|
|
399
|
+
exports.buildTavilyTools = buildTavilyTools;
|
|
400
|
+
exports.default = {
|
|
401
|
+
assertTavilyUsable: exports.assertTavilyUsable,
|
|
402
|
+
buildTavilyTools: exports.buildTavilyTools,
|
|
403
|
+
getTavilyOptions: exports.getTavilyOptions,
|
|
404
|
+
};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ export { constants };
|
|
|
9
9
|
export { errorHandling, AppError };
|
|
10
10
|
export type { ParsedError } from "./errorHandling.js";
|
|
11
11
|
export { langChain };
|
|
12
|
-
export type { UsageTracker
|
|
12
|
+
export type { UsageTracker } from "./langChain.js";
|
|
13
|
+
export type { TavilyConfig, TavilyToolName } from "./tavily.js";
|
|
13
14
|
export { logger };
|
|
14
15
|
export { serve };
|
|
15
16
|
export { callAgent };
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;AAEnC,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;AAEnC,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/dist/esm/langChain.d.ts
CHANGED
|
@@ -10,11 +10,10 @@ export type UsageTracker = {
|
|
|
10
10
|
[tokenKey: string]: number;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* the rest of the non-text content blocks.
|
|
13
|
+
* URLs returned by the Tavily tools, deduplicated across every call in the
|
|
14
|
+
* run. Only present once a web tool has actually run. Tavily's terms require
|
|
15
|
+
* the original sources to be cited when its content is shown to end users, so
|
|
16
|
+
* they are surfaced here rather than left buried in the tool messages.
|
|
18
17
|
*/
|
|
19
18
|
webSearchSources?: {
|
|
20
19
|
url: string;
|
|
@@ -22,64 +21,31 @@ export type UsageTracker = {
|
|
|
22
21
|
}[];
|
|
23
22
|
};
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* Instantiates and returns the appropriate LangChain chat model based on the model
|
|
25
|
+
* name prefix. `claude-` maps to `ChatAnthropic`, `gemini-` maps to
|
|
26
|
+
* `ChatGoogleGenerativeAI`, and `gpt-` maps to `ChatOpenAI`. DeepSeek (`deepseek-`),
|
|
27
|
+
* Kimi/Moonshot (`kimi-`, `moonshot-`), and GLM/Zhipu (`glm-`) models are routed
|
|
28
|
+
* through `ChatOpenAI` against each provider's OpenAI-compatible endpoint. When a Zod
|
|
29
|
+
* `schema` is provided, native structured output is configured per provider: OpenAI
|
|
30
|
+
* via `response_format` with `json_schema`, Anthropic via `output_config.format`, and
|
|
31
|
+
* Gemini via JSON mode (`json: true`) plus `responseSchema`. In every case the model
|
|
32
|
+
* emits JSON as the message text, so the caller's parse/validate pipeline is unchanged.
|
|
33
|
+
* The schema is ignored for the OpenAI-compatible providers (DeepSeek/Kimi/GLM), which
|
|
34
|
+
* have no native structured-output support here. Throws a `BAD_REQUEST` error for
|
|
35
|
+
* unrecognised model names.
|
|
36
|
+
* @param modelName - The model identifier, e.g. `"gpt-4o"`, `"claude-3-5-sonnet"`,
|
|
37
|
+
* `"gemini-1.5-pro"`, `"deepseek-chat"`, `"kimi-k2-0905-preview"`, `"glm-4.6"`
|
|
38
|
+
* @param config - Configuration object containing API keys (`openAIAPIKey`,
|
|
39
|
+
* `anthropicAPIKey`, `googleAPIKey`, `deepSeekAPIKey`, `kimiAPIKey`, `glmAPIKey`),
|
|
40
|
+
* optional `maxTokens`, and optional `temperature`
|
|
41
|
+
* @param schema - Optional Zod schema used to configure native structured JSON output
|
|
42
|
+
* for GPT / Claude / Gemini models; ignored for OpenAI-compatible providers
|
|
43
|
+
* @returns A configured LangChain chat model (or bound runnable) instance
|
|
29
44
|
*/
|
|
30
|
-
export
|
|
31
|
-
/** Anthropic only: hard cap on searches per request (`max_uses`). */
|
|
32
|
-
maxUses?: number;
|
|
33
|
-
/** Anthropic (`allowed_domains`) and OpenAI (`filters.allowed_domains`). */
|
|
34
|
-
allowedDomains?: string[];
|
|
35
|
-
/** Anthropic only (`blocked_domains`); cannot be combined with `allowedDomains`. */
|
|
36
|
-
blockedDomains?: string[];
|
|
37
|
-
/** Anthropic and OpenAI: approximate location used to localize results. */
|
|
38
|
-
userLocation?: {
|
|
39
|
-
city?: string;
|
|
40
|
-
region?: string;
|
|
41
|
-
/** Two-letter ISO 3166-1 alpha-2 code, e.g. `"US"`. */
|
|
42
|
-
country?: string;
|
|
43
|
-
/** IANA timezone ID, e.g. `"America/Los_Angeles"`. */
|
|
44
|
-
timezone?: string;
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* OpenAI only: how much of the context window search results may consume.
|
|
48
|
-
* OpenAI's default is `"medium"`; `"low"` minimizes context at some cost to
|
|
49
|
-
* answer quality, `"high"` is the expensive end.
|
|
50
|
-
*/
|
|
51
|
-
searchContextSize?: "low" | "medium" | "high";
|
|
52
|
-
/** Gemini only: RFC 3339 window the search is restricted to. */
|
|
53
|
-
timeRange?: {
|
|
54
|
-
startTime: string;
|
|
55
|
-
endTime: string;
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Anthropic only: opt out of dynamic filtering by forcing the search to be
|
|
59
|
-
* called directly (`allowed_callers: ["direct"]`). Defaults to `true` on
|
|
60
|
-
* models that support it — see `supportsAnthropicDynamicFiltering`. Turning
|
|
61
|
-
* this off means every raw search result lands in the context window.
|
|
62
|
-
*/
|
|
63
|
-
dynamicFiltering?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Anthropic only: whether search result blocks consumed by dynamic filtering
|
|
66
|
-
* are echoed back in the response. Defaults to `"excluded"`, which drops them
|
|
67
|
-
* and cuts the output tokens billed for content nothing downstream reads.
|
|
68
|
-
*/
|
|
69
|
-
responseInclusion?: "full" | "excluded";
|
|
70
|
-
/**
|
|
71
|
-
* Anthropic only: pin the dated tool version instead of letting
|
|
72
|
-
* `buildWebSearchTool` pick per model. `web_search_20250305` is basic search,
|
|
73
|
-
* `web_search_20260209` adds dynamic filtering, `web_search_20260318` adds
|
|
74
|
-
* response-inclusion control. Pinning a filtering version on a model that
|
|
75
|
-
* cannot do programmatic tool calling returns a 400 unless
|
|
76
|
-
* `dynamicFiltering: false` is also set.
|
|
77
|
-
*/
|
|
78
|
-
toolVersion?: string;
|
|
79
|
-
};
|
|
45
|
+
export declare const getLLMModel: (modelName: string, config: any, schema?: any) => any;
|
|
80
46
|
declare const _default: {
|
|
81
47
|
checkIfAIProviderConfigured: (config: any) => void;
|
|
82
|
-
createAgent: (name: string, modelName: string, systemPrompt: string, tools: any[], responseFormat: any, config: any) => any;
|
|
48
|
+
createAgent: (name: string, modelName: string, systemPrompt: string, tools: any[], responseFormat: any, config: any, usageTracker?: UsageTracker | null) => any;
|
|
83
49
|
runAgent: (agent: any, prompt: string, config: any, onProgress?: Function | null, usageTracker?: UsageTracker | null) => Promise<any>;
|
|
84
50
|
runPromptWithModel: (modelName: string, config: any, prompt: any, onProgressReport: any, minPercent?: number, maxPercent?: number, expectsJsonResponse?: boolean, schema?: any, agentName?: string, tools?: any[], usageTracker?: UsageTracker | null) => Promise<any>;
|
|
85
51
|
runPromptWithModelForImageGeneration: (modelName: string, config: any, prompt: string, options?: any, usageTracker?: UsageTracker | null) => Promise<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":"AA4GA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACvC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtD,CAAC;AA2lBF;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,eAAO,MAAM,WAAW,GACtB,WAAW,MAAM,EACjB,QAAQ,GAAG,EACX,SAAQ,GAAU,KACjB,GAoHF,CAAC;;0CAzuB2C,GAAG,KAAG,IAAI;wBA4vB/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,iBACG,YAAY,GAAG,IAAI,KAChC,GAAG;sBAoCG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,iBACb,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;oCAkYF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,iBACE,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;sDA0tBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,iBACE,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;0CApgC8B,GAAG,KAAG,MAAM;+CAzuB9C,YAAY,GAAG,IAAI,GAAG,SAAS,aAC7B,MAAM,kBACD,MAAM,UACd,GAAG,KACV,IAAI;mCAyvBU,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AA2iCT,wBASE"}
|