@stackfactor/agent-utils 1.4.3 → 1.8.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 +81 -6
- package/dist/cjs/errorHandling.d.ts.map +1 -1
- package/dist/cjs/errorHandling.js +10 -5
- package/dist/cjs/index.d.ts +8 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/langChain.d.ts +25 -5
- package/dist/cjs/langChain.d.ts.map +1 -1
- package/dist/cjs/langChain.js +244 -179
- package/dist/cjs/runtimeContext.d.ts +3 -0
- package/dist/cjs/runtimeContext.d.ts.map +1 -1
- package/dist/cjs/serve.d.ts +88 -0
- package/dist/cjs/serve.d.ts.map +1 -1
- package/dist/cjs/serve.js +230 -3
- package/dist/cjs/stackfactorKnowledge.d.ts +182 -0
- package/dist/cjs/stackfactorKnowledge.d.ts.map +1 -0
- package/dist/cjs/stackfactorKnowledge.js +274 -0
- package/dist/cjs/tavily.d.ts +4 -0
- package/dist/cjs/tavily.d.ts.map +1 -1
- package/dist/cjs/tavily.js +17 -18
- package/dist/cjs/usage.d.ts +74 -0
- package/dist/cjs/usage.d.ts.map +1 -0
- package/dist/cjs/usage.js +160 -0
- package/dist/esm/errorHandling.d.ts.map +1 -1
- package/dist/esm/errorHandling.js +10 -5
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/langChain.d.ts +25 -5
- package/dist/esm/langChain.d.ts.map +1 -1
- package/dist/esm/langChain.js +244 -179
- package/dist/esm/runtimeContext.d.ts +3 -0
- package/dist/esm/runtimeContext.d.ts.map +1 -1
- package/dist/esm/serve.d.ts +88 -0
- package/dist/esm/serve.d.ts.map +1 -1
- package/dist/esm/serve.js +226 -3
- package/dist/esm/stackfactorKnowledge.d.ts +182 -0
- package/dist/esm/stackfactorKnowledge.d.ts.map +1 -0
- package/dist/esm/stackfactorKnowledge.js +266 -0
- package/dist/esm/tavily.d.ts +4 -0
- package/dist/esm/tavily.d.ts.map +1 -1
- package/dist/esm/tavily.js +17 -18
- package/dist/esm/usage.d.ts +74 -0
- package/dist/esm/usage.d.ts.map +1 -0
- package/dist/esm/usage.js +150 -0
- package/package.json +14 -8
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
//
|
|
2
|
+
// StackFactor knowledge — grounding generated content in the knowledge bases
|
|
3
|
+
//
|
|
4
|
+
// A generation agent grounded in StackFactor (the Additional Information data
|
|
5
|
+
// source "StackFactor", `dataSource: "RAG"`) writes from knowledge-base
|
|
6
|
+
// passages instead of a web search, and cites every passage it used as a
|
|
7
|
+
// STACKFACTOR reference. A later agent generating for the same skill (its
|
|
8
|
+
// assessments, its learning content) reads those references and searches the
|
|
9
|
+
// documents they cite. Designed with Candin, 2026-10-08:
|
|
10
|
+
//
|
|
11
|
+
// - which knowledge bases: every one the person can open that holds the
|
|
12
|
+
// skill (skill coverage), never a fixed number of them;
|
|
13
|
+
// - which references: one per passage used, with no cap per document or per
|
|
14
|
+
// microskill;
|
|
15
|
+
// - access: the platform's search judges the person the run acts for, so a
|
|
16
|
+
// run reads only what that person may open. A knowledge base the person
|
|
17
|
+
// cannot read is skipped and reported, never an error: the agent
|
|
18
|
+
// generates without it.
|
|
19
|
+
//
|
|
20
|
+
// A reference points at the content and never copies it: the passage is
|
|
21
|
+
// searched again, under the reader's own access, whenever it is used. It is
|
|
22
|
+
// EDIT-only (`visibility: "EDIT"`): the platform strips it from what is
|
|
23
|
+
// published, so a knowledge base's titles never reach a learner; agents read
|
|
24
|
+
// it from the draft.
|
|
25
|
+
//
|
|
26
|
+
// The search itself is the caller's (`search`: client-api
|
|
27
|
+
// `knowledgeBaseAgent.search`, 1.11 or later for `documentIds`), so this
|
|
28
|
+
// module needs no particular client-api.
|
|
29
|
+
//
|
|
30
|
+
/** What a STACKFACTOR reference to a knowledge base document carries (backend-core REFERENCE). */
|
|
31
|
+
export const STACKFACTOR_REFERENCE = Object.freeze({
|
|
32
|
+
KIND: "STACKFACTOR",
|
|
33
|
+
TARGET_ELEMENT_TYPE: "KNOWLEDGE_BASE_DOCUMENT",
|
|
34
|
+
ORIGIN: "KB_EXTRACTION",
|
|
35
|
+
VISIBILITY: "EDIT",
|
|
36
|
+
});
|
|
37
|
+
/** How many documents one search may be kept to (the backend's bound); more are split. */
|
|
38
|
+
export const SEARCH_DOCUMENT_IDS_MAX = 500;
|
|
39
|
+
/** How long a query may be (the backend's bound); longer ones are cut. */
|
|
40
|
+
export const QUERY_MAX_LENGTH = 4000;
|
|
41
|
+
/** How many passages one search asks for when the caller does not say. */
|
|
42
|
+
export const DEFAULT_TOP_K = 8;
|
|
43
|
+
/** How many searches run at once. */
|
|
44
|
+
const CONCURRENCY = 4;
|
|
45
|
+
/** Statuses that mean the person may not read the knowledge base, or it is gone. */
|
|
46
|
+
const NO_ACCESS = new Set([401, 403, 404]);
|
|
47
|
+
const text = (value) => (typeof value === "string" ? value.trim() : "");
|
|
48
|
+
const id = (value) => typeof value === "string" ? value.trim() : value != null && typeof value === "object" ? String(value) : "";
|
|
49
|
+
/**
|
|
50
|
+
* The knowledge bases, and the documents in them, that a list of references
|
|
51
|
+
* cites: its STACKFACTOR references to knowledge base documents, by their
|
|
52
|
+
* `knowledgeBase` ids, or by their `target` when only that is there. Any other
|
|
53
|
+
* reference is passed over. Each knowledge base once, its documents once each,
|
|
54
|
+
* in the order they first appear.
|
|
55
|
+
* @param references A skill's (or a microskill's) references
|
|
56
|
+
* @returns The sources to search
|
|
57
|
+
*/
|
|
58
|
+
export const sourcesFromReferences = (references) => {
|
|
59
|
+
const byBase = new Map();
|
|
60
|
+
for (const reference of Array.isArray(references) ? references : []) {
|
|
61
|
+
if (reference?.kind !== STACKFACTOR_REFERENCE.KIND)
|
|
62
|
+
continue;
|
|
63
|
+
const target = reference.target ?? {};
|
|
64
|
+
const isDocumentTarget = target.elementType === STACKFACTOR_REFERENCE.TARGET_ELEMENT_TYPE;
|
|
65
|
+
const knowledgeBaseId = id(reference.knowledgeBase?.knowledgeBaseId) || (isDocumentTarget ? id(target.parentId) : "");
|
|
66
|
+
const documentId = id(reference.knowledgeBase?.documentId) || (isDocumentTarget ? id(target.elementId) : "");
|
|
67
|
+
if (!knowledgeBaseId || !documentId)
|
|
68
|
+
continue;
|
|
69
|
+
const documents = byBase.get(knowledgeBaseId) ?? new Set();
|
|
70
|
+
documents.add(documentId);
|
|
71
|
+
byBase.set(knowledgeBaseId, documents);
|
|
72
|
+
}
|
|
73
|
+
return [...byBase.entries()].map(([knowledgeBaseId, documents]) => ({
|
|
74
|
+
knowledgeBaseId,
|
|
75
|
+
documentIds: [...documents],
|
|
76
|
+
}));
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The knowledge bases skill coverage found (client-api
|
|
80
|
+
* `knowledgeBase.getSkillCoverage`): every one the person can open that holds
|
|
81
|
+
* the skill, each searched whole.
|
|
82
|
+
* @param coverage The coverage answer, `{ knowledgeBases: [{ _id }] }`
|
|
83
|
+
* @returns The sources to search
|
|
84
|
+
*/
|
|
85
|
+
export const sourcesFromCoverage = (coverage) => {
|
|
86
|
+
const bases = coverage?.knowledgeBases;
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const sources = [];
|
|
89
|
+
for (const base of Array.isArray(bases) ? bases : []) {
|
|
90
|
+
const knowledgeBaseId = id(base?._id);
|
|
91
|
+
if (!knowledgeBaseId || seen.has(knowledgeBaseId))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(knowledgeBaseId);
|
|
94
|
+
sources.push({ knowledgeBaseId });
|
|
95
|
+
}
|
|
96
|
+
return sources;
|
|
97
|
+
};
|
|
98
|
+
/** The documents of a source in batches the backend accepts; one batch of none for a whole knowledge base. */
|
|
99
|
+
const batchesOf = (source) => {
|
|
100
|
+
if (!Array.isArray(source.documentIds))
|
|
101
|
+
return [undefined];
|
|
102
|
+
const batches = [];
|
|
103
|
+
for (let at = 0; at < source.documentIds.length; at += SEARCH_DOCUMENT_IDS_MAX) {
|
|
104
|
+
batches.push(source.documentIds.slice(at, at + SEARCH_DOCUMENT_IDS_MAX));
|
|
105
|
+
}
|
|
106
|
+
return batches;
|
|
107
|
+
};
|
|
108
|
+
/** Runs `worker` over `items`, at most `limit` at a time. */
|
|
109
|
+
const inPool = async (items, limit, worker) => {
|
|
110
|
+
let next = 0;
|
|
111
|
+
const run = async () => {
|
|
112
|
+
while (next < items.length) {
|
|
113
|
+
const index = next++;
|
|
114
|
+
await worker(items[index]);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, run));
|
|
118
|
+
};
|
|
119
|
+
/** The status of a failed platform call, from an axios error or one shaped like it. */
|
|
120
|
+
const statusOf = (error) => {
|
|
121
|
+
const status = error?.response?.status ?? error?.status;
|
|
122
|
+
return typeof status === "number" ? status : null;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Searches every source for every query, under the person's access, and hands
|
|
126
|
+
* back the passages found, best first, each once, tagged "k1", "k2"… for the
|
|
127
|
+
* model to cite. A source with documents is searched within them (in batches of
|
|
128
|
+
* SEARCH_DOCUMENT_IDS_MAX); one without, whole. A source the person may not
|
|
129
|
+
* read (401, 403, 404) is skipped: none of its passages are used and no more of
|
|
130
|
+
* its searches are made. A search that fails otherwise is reported and the
|
|
131
|
+
* passages its other searches found stand. Either way it is in `skipped`, once
|
|
132
|
+
* per knowledge base, and the passages of the others stand.
|
|
133
|
+
* @param options `sources`, `queries`, `search` (client-api
|
|
134
|
+
* `knowledgeBaseAgent.search`), `token` (the run's AGENT token), `topK`
|
|
135
|
+
* per search (default DEFAULT_TOP_K)
|
|
136
|
+
* @returns `{ passages, skipped }`
|
|
137
|
+
*/
|
|
138
|
+
export const retrievePassages = async (options) => {
|
|
139
|
+
const queries = [
|
|
140
|
+
...new Set((Array.isArray(options.queries) ? options.queries : [])
|
|
141
|
+
.map((query) => text(query).slice(0, QUERY_MAX_LENGTH))
|
|
142
|
+
.filter(Boolean)),
|
|
143
|
+
];
|
|
144
|
+
const sources = Array.isArray(options.sources) ? options.sources : [];
|
|
145
|
+
const topK = options.topK ?? DEFAULT_TOP_K;
|
|
146
|
+
const searches = [];
|
|
147
|
+
for (const source of sources) {
|
|
148
|
+
for (const documentIds of batchesOf(source)) {
|
|
149
|
+
if (documentIds && documentIds.length === 0)
|
|
150
|
+
continue;
|
|
151
|
+
for (const query of queries) {
|
|
152
|
+
searches.push({ source, query, documentIds, order: searches.length });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const found = new Map();
|
|
157
|
+
const skipped = new Map();
|
|
158
|
+
await inPool(searches, CONCURRENCY, async ({ source, query, documentIds, order }) => {
|
|
159
|
+
if (skipped.get(source.knowledgeBaseId)?.noAccess)
|
|
160
|
+
return;
|
|
161
|
+
try {
|
|
162
|
+
const answer = await options.search(source.knowledgeBaseId, query, documentIds ? { topK, documentIds } : { topK }, options.token);
|
|
163
|
+
(Array.isArray(answer?.chunks) ? answer.chunks : []).forEach((chunk, rank) => {
|
|
164
|
+
const chunkId = id(chunk?._id);
|
|
165
|
+
if (!chunkId || !id(chunk?.documentId))
|
|
166
|
+
return;
|
|
167
|
+
const known = found.get(chunkId);
|
|
168
|
+
const better = !known || (chunk.score ?? 0) > (known.chunk.score ?? 0);
|
|
169
|
+
// Ties keep the first search's rank, so the order is the searches' own.
|
|
170
|
+
found.set(chunkId, {
|
|
171
|
+
chunk: better ? chunk : known.chunk,
|
|
172
|
+
knowledgeBaseId: source.knowledgeBaseId,
|
|
173
|
+
order: known ? Math.min(known.order, order * 1000 + rank) : order * 1000 + rank,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const status = statusOf(error);
|
|
179
|
+
const noAccess = status !== null && NO_ACCESS.has(status);
|
|
180
|
+
// A refusal outranks a failure reported before it.
|
|
181
|
+
if (skipped.has(source.knowledgeBaseId) && !noAccess)
|
|
182
|
+
return;
|
|
183
|
+
skipped.set(source.knowledgeBaseId, {
|
|
184
|
+
knowledgeBaseId: source.knowledgeBaseId,
|
|
185
|
+
message: typeof error?.message === "string" ? error.message : String(error),
|
|
186
|
+
noAccess,
|
|
187
|
+
status,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
const passages = [...found.entries()]
|
|
192
|
+
.filter(([, entry]) => !skipped.get(entry.knowledgeBaseId)?.noAccess)
|
|
193
|
+
.sort(([, a], [, b]) => (b.chunk.score ?? 0) - (a.chunk.score ?? 0) || a.order - b.order)
|
|
194
|
+
.map(([chunkId, { chunk, knowledgeBaseId }], index) => ({
|
|
195
|
+
chunkId,
|
|
196
|
+
documentId: id(chunk.documentId),
|
|
197
|
+
documentName: text(chunk.documentName) || null,
|
|
198
|
+
knowledgeBaseId: id(chunk.knowledgeBaseId) || knowledgeBaseId,
|
|
199
|
+
pageNumber: typeof chunk.pageNumber === "number" ? chunk.pageNumber : null,
|
|
200
|
+
score: typeof chunk.score === "number" ? chunk.score : null,
|
|
201
|
+
sectionTitle: text(chunk.sectionTitle) || null,
|
|
202
|
+
skills: (Array.isArray(chunk.skills) ? chunk.skills : []).map((skill) => ({
|
|
203
|
+
microSkills: (Array.isArray(skill?.microSkills) ? skill.microSkills : []).filter((name) => typeof name === "string"),
|
|
204
|
+
name: typeof skill?.name === "string" ? skill.name : null,
|
|
205
|
+
})),
|
|
206
|
+
tag: `k${index + 1}`,
|
|
207
|
+
text: typeof chunk.text === "string" ? chunk.text : "",
|
|
208
|
+
}));
|
|
209
|
+
return { passages, skipped: [...skipped.values()] };
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* The passages as a prompt shows them: tag, where each came from, and its text.
|
|
213
|
+
* @param passages From `retrievePassages`
|
|
214
|
+
* @returns `[{ tag, document, section, text }]`, for the model's input
|
|
215
|
+
*/
|
|
216
|
+
export const passagesForPrompt = (passages) => (Array.isArray(passages) ? passages : []).map((passage) => ({
|
|
217
|
+
document: passage.documentName,
|
|
218
|
+
section: passage.sectionTitle,
|
|
219
|
+
tag: passage.tag,
|
|
220
|
+
text: passage.text,
|
|
221
|
+
}));
|
|
222
|
+
/**
|
|
223
|
+
* STACKFACTOR references for passages: one per passage, in their order, with no
|
|
224
|
+
* cap. With `tags`, only the passages the model cited by tag (any tag it made
|
|
225
|
+
* up is passed over); without, every passage.
|
|
226
|
+
* @param passages From `retrievePassages`
|
|
227
|
+
* @param tags The tags the model cited ("k3", "K3" and " k3 " alike)
|
|
228
|
+
* @returns The references, ready for a skill's, a microskill's or a question's `references`
|
|
229
|
+
*/
|
|
230
|
+
export const referencesFor = (passages, tags) => {
|
|
231
|
+
const list = Array.isArray(passages) ? passages : [];
|
|
232
|
+
const cited = Array.isArray(tags)
|
|
233
|
+
? new Set(tags.map((tag) => text(tag).toLowerCase()).filter(Boolean))
|
|
234
|
+
: null;
|
|
235
|
+
return list
|
|
236
|
+
.filter((passage) => !cited || cited.has(passage.tag.toLowerCase()))
|
|
237
|
+
.map((passage) => ({
|
|
238
|
+
description: passage.sectionTitle,
|
|
239
|
+
kind: STACKFACTOR_REFERENCE.KIND,
|
|
240
|
+
knowledgeBase: {
|
|
241
|
+
chunkId: passage.chunkId,
|
|
242
|
+
documentId: passage.documentId,
|
|
243
|
+
knowledgeBaseId: passage.knowledgeBaseId,
|
|
244
|
+
sectionTitle: passage.sectionTitle,
|
|
245
|
+
},
|
|
246
|
+
origin: STACKFACTOR_REFERENCE.ORIGIN,
|
|
247
|
+
target: {
|
|
248
|
+
elementId: passage.documentId,
|
|
249
|
+
elementType: STACKFACTOR_REFERENCE.TARGET_ELEMENT_TYPE,
|
|
250
|
+
parentId: passage.knowledgeBaseId,
|
|
251
|
+
},
|
|
252
|
+
title: passage.documentName || passage.sectionTitle || "Knowledge base document",
|
|
253
|
+
visibility: STACKFACTOR_REFERENCE.VISIBILITY,
|
|
254
|
+
}));
|
|
255
|
+
};
|
|
256
|
+
export default {
|
|
257
|
+
DEFAULT_TOP_K,
|
|
258
|
+
QUERY_MAX_LENGTH,
|
|
259
|
+
SEARCH_DOCUMENT_IDS_MAX,
|
|
260
|
+
STACKFACTOR_REFERENCE,
|
|
261
|
+
passagesForPrompt,
|
|
262
|
+
referencesFor,
|
|
263
|
+
retrievePassages,
|
|
264
|
+
sourcesFromCoverage,
|
|
265
|
+
sourcesFromReferences,
|
|
266
|
+
};
|
package/dist/esm/tavily.d.ts
CHANGED
|
@@ -86,6 +86,10 @@ declare const _default: {
|
|
|
86
86
|
assertTavilyUsable: (config: any, options: TavilyConfig) => void;
|
|
87
87
|
buildTavilyTools: (config: any, options: TavilyConfig, usageTracker?: UsageTracker | null) => any[];
|
|
88
88
|
getTavilyOptions: (config: any) => TavilyConfig | null;
|
|
89
|
+
recordTavilyUsage: (tracker: UsageTracker | null | undefined, _config: any, credits: number, sources: {
|
|
90
|
+
url: string;
|
|
91
|
+
title?: string;
|
|
92
|
+
}[]) => void;
|
|
89
93
|
};
|
|
90
94
|
export default _default;
|
|
91
95
|
//# sourceMappingURL=tavily.d.ts.map
|
package/dist/esm/tavily.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../src/tavily.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../src/tavily.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA0BnD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;IAC3D,kFAAkF;IAClF,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,GAAG,KAAG,YAAY,GAAG,IAI7D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,SAAS,YAAY,KAAG,IAsBvE,CAAC;AAsLF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,GAAG,EACX,SAAS,YAAY,EACrB,eAAc,YAAY,GAAG,IAAW,KACvC,GAAG,EA2PL,CAAC;;iCA5dyC,GAAG,WAAW,YAAY,KAAG,IAAI;+BA8NlE,GAAG,WACF,YAAY,iBACP,YAAY,GAAG,IAAI,KAChC,GAAG,EAAE;+BAhPiC,GAAG,KAAG,YAAY,GAAG,IAAI;iCAsIvD,YAAY,GAAG,IAAI,GAAG,SAAS,WAC/B,GAAG,WACH,MAAM,WACN;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KACzC,IAAI;;AAmWP,wBAKE"}
|
package/dist/esm/tavily.js
CHANGED
|
@@ -4,9 +4,8 @@ import constants from "./const.js";
|
|
|
4
4
|
import errorHandlingHelper from "./errorHandling.js";
|
|
5
5
|
import logger from "./logger.js";
|
|
6
6
|
import { getAbortSignal } from "./runtimeContext.js";
|
|
7
|
+
import { recordUsage } from "./usage.js";
|
|
7
8
|
const TAVILY_API_BASE = "https://api.tavily.com";
|
|
8
|
-
/** Tavily list price per credit, used when the integration defines no rate. */
|
|
9
|
-
const DEFAULT_CREDIT_COST_USD = 0.008;
|
|
10
9
|
/**
|
|
11
10
|
* Context budgets, in characters, applied to everything a tool hands back to
|
|
12
11
|
* the model. Tavily returns whatever the page holds, and unlike a provider's
|
|
@@ -121,19 +120,25 @@ const callTavily = async (endpoint, body, apiKey, timeoutSeconds) => {
|
|
|
121
120
|
throw errorHandlingHelper.create(constants.HTTP_CODES.INTERNAL_SERVER_ERROR, `Tavily ${endpoint} returned ${response.status}: ${detail.slice(0, 500)}`);
|
|
122
121
|
}
|
|
123
122
|
};
|
|
123
|
+
/** The name Tavily's credits are counted and priced under. */
|
|
124
|
+
const TAVILY_MODEL = "tavily";
|
|
124
125
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* `tavilyCredits`. Sources land on
|
|
129
|
-
* URL across the whole run, so
|
|
130
|
-
* require when this output is
|
|
131
|
-
*
|
|
132
|
-
* @param
|
|
126
|
+
* Counts one Tavily call's credits and records the URLs it returned. Credits
|
|
127
|
+
* come from the response's own `usage` block; they are counted into the
|
|
128
|
+
* request's usage ledger, which is reported to the platform, and into the
|
|
129
|
+
* caller's tracker under `tavilyCredits`. Sources land on
|
|
130
|
+
* `tracker.webSearchSources`, deduplicated by URL across the whole run, so
|
|
131
|
+
* callers have the citation list Tavily's terms require when this output is
|
|
132
|
+
* shown to end users.
|
|
133
|
+
* @param tracker - The caller's usage accumulator, when it passed one
|
|
134
|
+
* @param _config - No longer read: it carried the rate, which is the platform's to say
|
|
133
135
|
* @param credits - Credits reported by the Tavily response
|
|
134
136
|
* @param sources - URLs (and titles where known) the call returned
|
|
135
137
|
*/
|
|
136
|
-
const recordTavilyUsage = (tracker,
|
|
138
|
+
const recordTavilyUsage = (tracker, _config, credits, sources) => {
|
|
139
|
+
if (Number.isFinite(credits) && credits > 0) {
|
|
140
|
+
recordUsage(TAVILY_MODEL, "credits", credits);
|
|
141
|
+
}
|
|
137
142
|
if (!tracker)
|
|
138
143
|
return;
|
|
139
144
|
if (typeof tracker.cost !== "number")
|
|
@@ -141,13 +146,6 @@ const recordTavilyUsage = (tracker, config, credits, sources) => {
|
|
|
141
146
|
if (!tracker.tokens || typeof tracker.tokens !== "object")
|
|
142
147
|
tracker.tokens = {};
|
|
143
148
|
if (Number.isFinite(credits) && credits > 0) {
|
|
144
|
-
const configured = Number(config?.["tavily-credit-costs"]);
|
|
145
|
-
const rate = Number.isFinite(configured)
|
|
146
|
-
? configured
|
|
147
|
-
: DEFAULT_CREDIT_COST_USD;
|
|
148
|
-
const addedCost = credits * rate;
|
|
149
|
-
if (Number.isFinite(addedCost) && addedCost > 0)
|
|
150
|
-
tracker.cost += addedCost;
|
|
151
149
|
tracker.tokens.tavilyCredits =
|
|
152
150
|
(tracker.tokens.tavilyCredits || 0) + credits;
|
|
153
151
|
}
|
|
@@ -392,4 +390,5 @@ export default {
|
|
|
392
390
|
assertTavilyUsable,
|
|
393
391
|
buildTavilyTools,
|
|
394
392
|
getTavilyOptions,
|
|
393
|
+
recordTavilyUsage,
|
|
395
394
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type RequestContext } from "./runtimeContext.js";
|
|
2
|
+
/**
|
|
3
|
+
* What an agent used, and how the platform gets to know.
|
|
4
|
+
*
|
|
5
|
+
* An agent says WHAT it used — a model, a unit, an amount — and the platform
|
|
6
|
+
* says what that costs, who is charged and what it was for. Everything that
|
|
7
|
+
* calls a model through this package counts what the call used into the
|
|
8
|
+
* request's {@link UsageLedger}; {@link serve} and {@link runOnce} report the
|
|
9
|
+
* ledger to the platform while the work runs and when it ends, whether it
|
|
10
|
+
* ended well or not. An agent does nothing for this to happen.
|
|
11
|
+
*
|
|
12
|
+
* An agent that calls a provider itself — its own SDK, an HTTP request —
|
|
13
|
+
* counts what that used with {@link recordUsage}.
|
|
14
|
+
*/
|
|
15
|
+
/** What usage is counted in; the units the platform has rates for. */
|
|
16
|
+
export declare const USAGE_UNITS: readonly ["inputTokens", "outputTokens", "imageInputTokens", "imageOutputTokens", "characters", "seconds", "credits"];
|
|
17
|
+
export type UsageUnit = (typeof USAGE_UNITS)[number];
|
|
18
|
+
export interface UsageEntry {
|
|
19
|
+
model: string;
|
|
20
|
+
unit: UsageUnit;
|
|
21
|
+
amount: number;
|
|
22
|
+
}
|
|
23
|
+
/** Sends one report to the platform. Rejects when the platform did not take it. */
|
|
24
|
+
export type ReportUsage = (usage: UsageEntry[], reportId: string, token: string) => Promise<unknown>;
|
|
25
|
+
/** How often work that is still running reports what it used so far. */
|
|
26
|
+
export declare const USAGE_REPORT_INTERVAL_MS = 60000;
|
|
27
|
+
/**
|
|
28
|
+
* What one request used and has not reported yet, summed by model and unit.
|
|
29
|
+
*/
|
|
30
|
+
export declare class UsageLedger {
|
|
31
|
+
private pending;
|
|
32
|
+
/** Adds to what is pending. Anything that is not a positive amount of a known unit is ignored. */
|
|
33
|
+
add(model: string, unit: UsageUnit, amount: number): void;
|
|
34
|
+
/** What is pending, which then no longer is. */
|
|
35
|
+
take(): UsageEntry[];
|
|
36
|
+
/** True when there is nothing to report. */
|
|
37
|
+
get isEmpty(): boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Counts what the running request used. For an agent that calls a provider
|
|
41
|
+
* itself; what is called through `langChain` is counted already. Outside a
|
|
42
|
+
* request there is nothing to count into, and nothing happens.
|
|
43
|
+
* @param model - The model's id, as its provider names it
|
|
44
|
+
* @param unit - What the amount is counted in
|
|
45
|
+
* @param amount - How much
|
|
46
|
+
*/
|
|
47
|
+
export declare const recordUsage: (model: string, unit: UsageUnit, amount: number) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Reports what the request used and has not reported yet. Never throws, and
|
|
50
|
+
* never changes what the work answers: the work happened, and a platform that
|
|
51
|
+
* cannot be told what it used right now is told by the next report, or not at
|
|
52
|
+
* all — which is logged as an error, with what was used.
|
|
53
|
+
*
|
|
54
|
+
* @param context - The request whose ledger is reported
|
|
55
|
+
* @param options - `report` sends a report (default: the platform's route);
|
|
56
|
+
* `last` says no other report follows, so what cannot be sent is given up on
|
|
57
|
+
* rather than kept for the next one
|
|
58
|
+
*/
|
|
59
|
+
export declare const reportUsage: (context: Pick<RequestContext, "usage" | "request"> | undefined, options?: {
|
|
60
|
+
report?: ReportUsage;
|
|
61
|
+
last?: boolean;
|
|
62
|
+
retryBaseMs?: number;
|
|
63
|
+
}) => Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Reports what the request used so far every {@link USAGE_REPORT_INTERVAL_MS}
|
|
66
|
+
* while the work runs, so work that dies has reported most of what it used.
|
|
67
|
+
* @returns Stops it
|
|
68
|
+
*/
|
|
69
|
+
export declare const reportUsageWhileRunning: (context: Pick<RequestContext, "usage" | "request">, options?: {
|
|
70
|
+
report?: ReportUsage;
|
|
71
|
+
intervalMs?: number;
|
|
72
|
+
retryBaseMs?: number;
|
|
73
|
+
}) => (() => void);
|
|
74
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/usage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEtE;;;;;;;;;;;;GAYG;AAEH,sEAAsE;AACtE,eAAO,MAAM,WAAW,uHAQd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,UAAU,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,KACV,OAAO,CAAC,OAAO,CAAC,CAAC;AAOtB,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAI/C;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAiC;IAEhD,kGAAkG;IAClG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAkBzD,gDAAgD;IAChD,IAAI,IAAI,UAAU,EAAE;IAMpB,4CAA4C;IAC5C,IAAI,OAAO,IAAI,OAAO,CAErB;CACF;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,MAAM,KAAG,IAE5E,CAAC;AA8BF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,GACtB,SAAS,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC,GAAG,SAAS,EAC9D,UAAS;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,KAC3E,OAAO,CAAC,IAAI,CAuCd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAClC,SAAS,IAAI,CAAC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC,EAClD,UAAS;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,KAChF,CAAC,MAAM,IAAI,CAMb,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { integration as clientIntegration } from "@stackfactor/client-api";
|
|
3
|
+
import logger from "./logger.js";
|
|
4
|
+
import { getContext } from "./runtimeContext.js";
|
|
5
|
+
/**
|
|
6
|
+
* What an agent used, and how the platform gets to know.
|
|
7
|
+
*
|
|
8
|
+
* An agent says WHAT it used — a model, a unit, an amount — and the platform
|
|
9
|
+
* says what that costs, who is charged and what it was for. Everything that
|
|
10
|
+
* calls a model through this package counts what the call used into the
|
|
11
|
+
* request's {@link UsageLedger}; {@link serve} and {@link runOnce} report the
|
|
12
|
+
* ledger to the platform while the work runs and when it ends, whether it
|
|
13
|
+
* ended well or not. An agent does nothing for this to happen.
|
|
14
|
+
*
|
|
15
|
+
* An agent that calls a provider itself — its own SDK, an HTTP request —
|
|
16
|
+
* counts what that used with {@link recordUsage}.
|
|
17
|
+
*/
|
|
18
|
+
/** What usage is counted in; the units the platform has rates for. */
|
|
19
|
+
export const USAGE_UNITS = [
|
|
20
|
+
"inputTokens",
|
|
21
|
+
"outputTokens",
|
|
22
|
+
"imageInputTokens",
|
|
23
|
+
"imageOutputTokens",
|
|
24
|
+
"characters",
|
|
25
|
+
"seconds",
|
|
26
|
+
"credits",
|
|
27
|
+
];
|
|
28
|
+
/** Entries one report may carry (the platform's limit). */
|
|
29
|
+
const REPORT_ENTRIES_MAX = 100;
|
|
30
|
+
/** How often a report is tried before it is given up on, and the first wait between tries. */
|
|
31
|
+
const REPORT_ATTEMPTS = 3;
|
|
32
|
+
const REPORT_RETRY_BASE_MS = 500;
|
|
33
|
+
/** How often work that is still running reports what it used so far. */
|
|
34
|
+
export const USAGE_REPORT_INTERVAL_MS = 60_000;
|
|
35
|
+
const keyOf = (model, unit) => `${model}\n${unit}`;
|
|
36
|
+
/**
|
|
37
|
+
* What one request used and has not reported yet, summed by model and unit.
|
|
38
|
+
*/
|
|
39
|
+
export class UsageLedger {
|
|
40
|
+
pending = new Map();
|
|
41
|
+
/** Adds to what is pending. Anything that is not a positive amount of a known unit is ignored. */
|
|
42
|
+
add(model, unit, amount) {
|
|
43
|
+
if (typeof model !== "string" ||
|
|
44
|
+
model.trim().length === 0 ||
|
|
45
|
+
!USAGE_UNITS.includes(unit) ||
|
|
46
|
+
typeof amount !== "number" ||
|
|
47
|
+
!Number.isFinite(amount) ||
|
|
48
|
+
amount <= 0) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const name = model.trim();
|
|
52
|
+
const key = keyOf(name, unit);
|
|
53
|
+
const entry = this.pending.get(key);
|
|
54
|
+
if (entry)
|
|
55
|
+
entry.amount += amount;
|
|
56
|
+
else
|
|
57
|
+
this.pending.set(key, { model: name, unit, amount });
|
|
58
|
+
}
|
|
59
|
+
/** What is pending, which then no longer is. */
|
|
60
|
+
take() {
|
|
61
|
+
const entries = [...this.pending.values()];
|
|
62
|
+
this.pending = new Map();
|
|
63
|
+
return entries;
|
|
64
|
+
}
|
|
65
|
+
/** True when there is nothing to report. */
|
|
66
|
+
get isEmpty() {
|
|
67
|
+
return this.pending.size === 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Counts what the running request used. For an agent that calls a provider
|
|
72
|
+
* itself; what is called through `langChain` is counted already. Outside a
|
|
73
|
+
* request there is nothing to count into, and nothing happens.
|
|
74
|
+
* @param model - The model's id, as its provider names it
|
|
75
|
+
* @param unit - What the amount is counted in
|
|
76
|
+
* @param amount - How much
|
|
77
|
+
*/
|
|
78
|
+
export const recordUsage = (model, unit, amount) => {
|
|
79
|
+
getContext()?.usage?.add(model, unit, amount);
|
|
80
|
+
};
|
|
81
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
+
const defaultReport = (usage, reportId, token) => clientIntegration.reportRunUsage(usage, reportId, token);
|
|
83
|
+
/**
|
|
84
|
+
* Sends one report, again when it fails, under the same name every time: the
|
|
85
|
+
* platform charges a report it has seen only once.
|
|
86
|
+
*/
|
|
87
|
+
const send = async (entries, token, report, retryBaseMs) => {
|
|
88
|
+
const reportId = randomUUID();
|
|
89
|
+
for (let attempt = 1;; attempt++) {
|
|
90
|
+
try {
|
|
91
|
+
await report(entries, reportId, token);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (attempt >= REPORT_ATTEMPTS)
|
|
96
|
+
throw error;
|
|
97
|
+
await delay(retryBaseMs * 2 ** (attempt - 1));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Reports what the request used and has not reported yet. Never throws, and
|
|
103
|
+
* never changes what the work answers: the work happened, and a platform that
|
|
104
|
+
* cannot be told what it used right now is told by the next report, or not at
|
|
105
|
+
* all — which is logged as an error, with what was used.
|
|
106
|
+
*
|
|
107
|
+
* @param context - The request whose ledger is reported
|
|
108
|
+
* @param options - `report` sends a report (default: the platform's route);
|
|
109
|
+
* `last` says no other report follows, so what cannot be sent is given up on
|
|
110
|
+
* rather than kept for the next one
|
|
111
|
+
*/
|
|
112
|
+
export const reportUsage = async (context, options = {}) => {
|
|
113
|
+
const ledger = context?.usage;
|
|
114
|
+
if (!ledger || ledger.isEmpty)
|
|
115
|
+
return;
|
|
116
|
+
const token = context.request?.authToken ?? context.request?.authorization;
|
|
117
|
+
const entries = ledger.take();
|
|
118
|
+
if (!token) {
|
|
119
|
+
logger.log(null, logger.levels.error, `usage was not reported, the request carries no token: ${JSON.stringify(entries)}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (let start = 0; start < entries.length; start += REPORT_ENTRIES_MAX) {
|
|
123
|
+
const chunk = entries.slice(start, start + REPORT_ENTRIES_MAX);
|
|
124
|
+
try {
|
|
125
|
+
await send(chunk, token, options.report ?? defaultReport, options.retryBaseMs ?? REPORT_RETRY_BASE_MS);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (options.last) {
|
|
129
|
+
logger.log(null, logger.levels.error, `usage could not be reported (${error?.message ?? error}): ${JSON.stringify(chunk)}`);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
for (const entry of chunk)
|
|
133
|
+
ledger.add(entry.model, entry.unit, entry.amount);
|
|
134
|
+
logger.log(null, logger.levels.warn, `usage could not be reported, it is kept for the next report: ${error?.message ?? error}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Reports what the request used so far every {@link USAGE_REPORT_INTERVAL_MS}
|
|
141
|
+
* while the work runs, so work that dies has reported most of what it used.
|
|
142
|
+
* @returns Stops it
|
|
143
|
+
*/
|
|
144
|
+
export const reportUsageWhileRunning = (context, options = {}) => {
|
|
145
|
+
const timer = setInterval(() => {
|
|
146
|
+
void reportUsage(context, { report: options.report, retryBaseMs: options.retryBaseMs });
|
|
147
|
+
}, options.intervalMs ?? USAGE_REPORT_INTERVAL_MS);
|
|
148
|
+
timer.unref?.();
|
|
149
|
+
return () => clearInterval(timer);
|
|
150
|
+
};
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.8.0",
|
|
7
7
|
"description": "",
|
|
8
8
|
"main": "dist/cjs/index.js",
|
|
9
9
|
"module": "dist/esm/index.js",
|
|
@@ -14,14 +14,15 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"clean": "rm -rf dist",
|
|
16
16
|
"build": "npm run clean && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json && echo '{\"type\":\"module\"}' > dist/esm/package.json",
|
|
17
|
-
"test": "npm run build && node --test
|
|
17
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
18
18
|
"work:start": "bash scripts/work-start.sh",
|
|
19
19
|
"work:commit": "bash scripts/work-commit.sh",
|
|
20
20
|
"work:release": "bash scripts/release.sh",
|
|
21
21
|
"release": "bash scripts/release.sh",
|
|
22
22
|
"release:fanout": "bash scripts/release-fanout.sh",
|
|
23
|
-
"ar-auth": "npm config set @stackfactor:registry https://us-central1-npm.pkg.dev/virtual-development-team/sf-devenv-npm-private/; TOKEN=$(gcloud auth print-access-token 2>/dev/null || curl -sfH 'Metadata-Flavor: Google' http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token | sed -n 's/.*\"access_token\":\"\\([^\"]*\\)\".*/\\1/p'); test -n \"$TOKEN\" || { echo 'ar-auth: no credential
|
|
24
|
-
"reinstall": "npm run ar-auth && npm ci"
|
|
23
|
+
"ar-auth": "npm config set @stackfactor:registry https://us-central1-npm.pkg.dev/virtual-development-team/sf-devenv-npm-private/; TOKEN=$(gcloud auth print-access-token 2>/dev/null || curl -sfH 'Metadata-Flavor: Google' http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token | sed -n 's/.*\"access_token\":\"\\([^\"]*\\)\".*/\\1/p'); test -n \"$TOKEN\" || { echo 'ar-auth: no credential \u2014 run: gcloud auth login' >&2; exit 1; }; npm config set //us-central1-npm.pkg.dev/virtual-development-team/sf-devenv-npm-private/:_authToken \"$TOKEN\"; npm config set //us-central1-npm.pkg.dev/virtual-development-team/:_authToken \"$TOKEN\"; echo 'ar-auth: Artifact Registry scope + token configured'",
|
|
24
|
+
"reinstall": "npm run ar-auth && npm ci",
|
|
25
|
+
"test:coverage": "npm run build && node --experimental-test-coverage --test --test-reporter=tap test/*.test.js"
|
|
25
26
|
},
|
|
26
27
|
"repository": {
|
|
27
28
|
"type": "git",
|
|
@@ -49,21 +50,26 @@
|
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@anthropic-ai/sdk": "^0.74.0",
|
|
51
52
|
"@google/genai": "^1.46.0",
|
|
52
|
-
"@grpc/grpc-js": "^1.14.
|
|
53
|
+
"@grpc/grpc-js": "^1.14.5",
|
|
53
54
|
"@grpc/proto-loader": "^0.7.15",
|
|
54
55
|
"@langchain/anthropic": "^1.3.25",
|
|
56
|
+
"@langchain/core": "^1.2.9",
|
|
55
57
|
"@langchain/google-genai": "^2.1.26",
|
|
56
58
|
"@langchain/openai": "^1.3.0",
|
|
57
59
|
"@opentelemetry/api": "^1.9.0",
|
|
58
|
-
"@stackfactor/client-api": "^1.
|
|
60
|
+
"@stackfactor/client-api": "^1.6.0",
|
|
59
61
|
"langchain": "^1.2.35",
|
|
60
|
-
"mongoose": "^9.
|
|
62
|
+
"mongoose": "^9.10.4",
|
|
61
63
|
"openai": "^6.32.0",
|
|
62
64
|
"winston": "^3.19.0",
|
|
63
65
|
"zod": "^4.3.6"
|
|
64
66
|
},
|
|
65
67
|
"devDependencies": {
|
|
66
|
-
"@langchain/core": "^1.2.9",
|
|
67
68
|
"typescript": "^5.9.3"
|
|
69
|
+
},
|
|
70
|
+
"overrides": {
|
|
71
|
+
"mongodb": {
|
|
72
|
+
"gcp-metadata": ">=7.0.1"
|
|
73
|
+
}
|
|
68
74
|
}
|
|
69
75
|
}
|