nestjs-langchain 2.0.0 → 2.2.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 +71 -6
- package/dist/agent.d.ts +21 -4
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +73 -33
- package/dist/agent.js.map +1 -1
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +21 -0
- package/dist/constants.js.map +1 -1
- package/dist/errors/agent-aborted.error.d.ts +21 -0
- package/dist/errors/agent-aborted.error.d.ts.map +1 -0
- package/dist/errors/agent-aborted.error.js +22 -0
- package/dist/errors/agent-aborted.error.js.map +1 -0
- package/dist/errors/agent-recursion-limit.error.d.ts +23 -0
- package/dist/errors/agent-recursion-limit.error.d.ts.map +1 -0
- package/dist/errors/agent-recursion-limit.error.js +24 -0
- package/dist/errors/agent-recursion-limit.error.js.map +1 -0
- package/dist/errors/index.d.ts +5 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +5 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/messages.d.ts +10 -0
- package/dist/errors/messages.d.ts.map +1 -1
- package/dist/errors/messages.js +24 -3
- package/dist/errors/messages.js.map +1 -1
- package/dist/errors/run-error.util.d.ts +21 -0
- package/dist/errors/run-error.util.d.ts.map +1 -0
- package/dist/errors/run-error.util.js +33 -0
- package/dist/errors/run-error.util.js.map +1 -0
- package/dist/errors/structured-output.error.d.ts +22 -0
- package/dist/errors/structured-output.error.d.ts.map +1 -0
- package/dist/errors/structured-output.error.js +23 -0
- package/dist/errors/structured-output.error.js.map +1 -0
- package/dist/errors/unwrap-middleware-error.util.d.ts +10 -0
- package/dist/errors/unwrap-middleware-error.util.d.ts.map +1 -0
- package/dist/errors/unwrap-middleware-error.util.js +17 -0
- package/dist/errors/unwrap-middleware-error.util.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/completed-run.interface.d.ts +1 -1
- package/dist/interfaces/completed-run.interface.d.ts.map +1 -1
- package/dist/interfaces/langchain-module-options.interface.d.ts +6 -0
- package/dist/interfaces/langchain-module-options.interface.d.ts.map +1 -1
- package/dist/interfaces/run-options.interface.d.ts +34 -0
- package/dist/interfaces/run-options.interface.d.ts.map +1 -0
- package/dist/interfaces/run-options.interface.js +2 -0
- package/dist/interfaces/run-options.interface.js.map +1 -0
- package/dist/output/index.d.ts +3 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +3 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/run-output.d.ts +43 -0
- package/dist/output/run-output.d.ts.map +1 -0
- package/dist/output/run-output.js +107 -0
- package/dist/output/run-output.js.map +1 -0
- package/dist/output/structured-output.middleware.d.ts +22 -0
- package/dist/output/structured-output.middleware.d.ts.map +1 -0
- package/dist/output/structured-output.middleware.js +40 -0
- package/dist/output/structured-output.middleware.js.map +1 -0
- package/dist/run/answered-calls.util.d.ts +10 -0
- package/dist/run/answered-calls.util.d.ts.map +1 -0
- package/dist/run/answered-calls.util.js +12 -0
- package/dist/run/answered-calls.util.js.map +1 -0
- package/dist/run/index.d.ts +3 -0
- package/dist/run/index.d.ts.map +1 -1
- package/dist/run/index.js +3 -0
- package/dist/run/index.js.map +1 -1
- package/dist/run/recursion-limit.util.d.ts +10 -0
- package/dist/run/recursion-limit.util.d.ts.map +1 -0
- package/dist/run/recursion-limit.util.js +11 -0
- package/dist/run/recursion-limit.util.js.map +1 -0
- package/dist/run/run-summary.util.d.ts +10 -3
- package/dist/run/run-summary.util.d.ts.map +1 -1
- package/dist/run/run-summary.util.js +18 -6
- package/dist/run/run-summary.util.js.map +1 -1
- package/dist/run/usage-aside.d.ts +23 -0
- package/dist/run/usage-aside.d.ts.map +1 -0
- package/dist/run/usage-aside.js +33 -0
- package/dist/run/usage-aside.js.map +1 -0
- package/dist/tools/tool-errors.middleware.d.ts +7 -1
- package/dist/tools/tool-errors.middleware.d.ts.map +1 -1
- package/dist/tools/tool-errors.middleware.js +81 -16
- package/dist/tools/tool-errors.middleware.js.map +1 -1
- package/dist/tools/tool-name.util.d.ts.map +1 -1
- package/dist/tools/tool-name.util.js +1 -3
- package/dist/tools/tool-name.util.js.map +1 -1
- package/lib/agent.ts +119 -32
- package/lib/constants.ts +24 -0
- package/lib/errors/agent-aborted.error.ts +22 -0
- package/lib/errors/agent-recursion-limit.error.ts +24 -0
- package/lib/errors/index.ts +5 -0
- package/lib/errors/messages.ts +55 -3
- package/lib/errors/run-error.util.ts +57 -0
- package/lib/errors/structured-output.error.ts +23 -0
- package/lib/errors/unwrap-middleware-error.util.ts +19 -0
- package/lib/index.ts +9 -1
- package/lib/interfaces/completed-run.interface.ts +1 -1
- package/lib/interfaces/langchain-module-options.interface.ts +6 -0
- package/lib/interfaces/run-options.interface.ts +34 -0
- package/lib/output/index.ts +5 -0
- package/lib/output/run-output.ts +154 -0
- package/lib/output/structured-output.middleware.ts +44 -0
- package/lib/run/answered-calls.util.ts +16 -0
- package/lib/run/index.ts +3 -0
- package/lib/run/recursion-limit.util.ts +11 -0
- package/lib/run/run-summary.util.ts +33 -11
- package/lib/run/usage-aside.ts +41 -0
- package/lib/tools/tool-errors.middleware.ts +104 -10
- package/lib/tools/tool-name.util.ts +1 -4
- package/package.json +31 -12
package/lib/agent.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
2
2
|
import { DiscoveryService } from '@nestjs/core';
|
|
3
3
|
import { createAgent, initChatModel } from 'langchain';
|
|
4
|
+
import type { z, ZodType } from 'zod';
|
|
4
5
|
import {
|
|
5
6
|
AGENT_NAME_TOKEN,
|
|
6
7
|
MODULE_OPTIONS_TOKEN,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
ModelOption,
|
|
12
13
|
} from './interfaces/langchain-module-options.interface.js';
|
|
13
14
|
import { CompletedRun } from './interfaces/completed-run.interface.js';
|
|
15
|
+
import { RunOptions } from './interfaces/run-options.interface.js';
|
|
14
16
|
import {
|
|
15
17
|
ToolDiscoveryService,
|
|
16
18
|
toolErrors,
|
|
@@ -18,13 +20,15 @@ import {
|
|
|
18
20
|
} from './tools/index.js';
|
|
19
21
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
20
22
|
import { LOG_CONTEXT, messageOf, RunReporter } from './logging/index.js';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
+
import { DEFAULT_RECURSION_LIMIT } from './constants.js';
|
|
24
|
+
import { isRecursionLimit, summariseRun, UsageAside } from './run/index.js';
|
|
25
|
+
import { outputConfig, RunOutput, structuredOutput } from './output/index.js';
|
|
26
|
+
import { AgentRunError, toRunError } from './errors/index.js';
|
|
23
27
|
import {
|
|
24
28
|
agentNotBootstrapped,
|
|
25
29
|
agentPrefix,
|
|
26
|
-
agentRunFailed,
|
|
27
30
|
duplicateAgentName,
|
|
31
|
+
invalidRecursionLimit,
|
|
28
32
|
modelNeverReplied,
|
|
29
33
|
modelNotBuilt,
|
|
30
34
|
modelReplyEmpty,
|
|
@@ -59,9 +63,6 @@ export class Agent implements OnModuleInit {
|
|
|
59
63
|
private readonly logger = new Logger();
|
|
60
64
|
private readonly prefix: string;
|
|
61
65
|
private readonly reporter: RunReporter;
|
|
62
|
-
// Model failures, which only LangChain's callbacks see. Tool calls are
|
|
63
|
-
// reported by their wrapper and the tool middlewares.
|
|
64
|
-
private readonly modelErrors: BaseCallbackHandler;
|
|
65
66
|
|
|
66
67
|
/** @internal Built by `LangChainModule`, never by hand. */
|
|
67
68
|
constructor(
|
|
@@ -79,14 +80,6 @@ export class Agent implements OnModuleInit {
|
|
|
79
80
|
prefix: this.prefix,
|
|
80
81
|
observer: options.observer,
|
|
81
82
|
});
|
|
82
|
-
this.modelErrors = BaseCallbackHandler.fromMethods({
|
|
83
|
-
handleLLMError: (error: unknown) => {
|
|
84
|
-
this.reporter.logError(`the model failed: ${messageOf(error)}`);
|
|
85
|
-
this.reporter.notify((observer) =>
|
|
86
|
-
observer.onModelError?.({ agent: agentName, error }),
|
|
87
|
-
);
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
83
|
}
|
|
91
84
|
|
|
92
85
|
/**
|
|
@@ -97,6 +90,12 @@ export class Agent implements OnModuleInit {
|
|
|
97
90
|
async onModuleInit() {
|
|
98
91
|
this.assertUniqueName();
|
|
99
92
|
|
|
93
|
+
const { recursionLimit } = this.options;
|
|
94
|
+
|
|
95
|
+
if (!isRecursionLimit(recursionLimit)) {
|
|
96
|
+
throw new Error(invalidRecursionLimit(this.prefix, recursionLimit));
|
|
97
|
+
}
|
|
98
|
+
|
|
100
99
|
const tools = await this.toolDiscovery.getToolsFromModules(
|
|
101
100
|
this.options.tools ?? [],
|
|
102
101
|
this.reporter,
|
|
@@ -106,8 +105,21 @@ export class Agent implements OnModuleInit {
|
|
|
106
105
|
model: await this.resolveModel(this.options.model),
|
|
107
106
|
tools,
|
|
108
107
|
systemPrompt: this.options.systemPrompt,
|
|
109
|
-
// The first wraps the next: `toolErrors` sits right around the tool
|
|
110
|
-
|
|
108
|
+
// The first wraps the next: `toolErrors` sits right around the tool,
|
|
109
|
+
// `structuredOutput` right around the model.
|
|
110
|
+
middleware: [
|
|
111
|
+
unknownTools(
|
|
112
|
+
this.reporter,
|
|
113
|
+
tools.map((tool) => tool.name),
|
|
114
|
+
),
|
|
115
|
+
toolErrors(this.reporter),
|
|
116
|
+
structuredOutput(),
|
|
117
|
+
],
|
|
118
|
+
}).withConfig({
|
|
119
|
+
// Traces and callbacks tell agents apart; the unnamed one keeps
|
|
120
|
+
// LangGraph's name. See #235.
|
|
121
|
+
runName: this.agentName === UNNAMED_AGENT ? undefined : this.agentName,
|
|
122
|
+
metadata: { agent: this.agentName },
|
|
111
123
|
});
|
|
112
124
|
|
|
113
125
|
this.logger.log(
|
|
@@ -179,21 +191,65 @@ export class Agent implements OnModuleInit {
|
|
|
179
191
|
* as an `AgentRunError`, the original error in its `cause`.
|
|
180
192
|
*
|
|
181
193
|
* @param input The user's message.
|
|
182
|
-
* @
|
|
194
|
+
* @param options With `output`, the schema the answer must match; with
|
|
195
|
+
* `signal`, what aborts the run; with `recursionLimit`, its most steps.
|
|
196
|
+
* @returns The model's answer, the object `output` parses when given, the
|
|
197
|
+
* tools it called and what the run cost.
|
|
183
198
|
* @example
|
|
184
199
|
* ```ts
|
|
185
|
-
* const { output, tools } = await agent.run('What is 6 times 7?'
|
|
200
|
+
* const { output, tools } = await agent.run('What is 6 times 7?', {
|
|
201
|
+
* signal: AbortSignal.timeout(30_000),
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* const { output: ticket } = await agent.run(email, {
|
|
205
|
+
* output: z.object({ category: z.enum(['billing', 'bug', 'other']) }),
|
|
206
|
+
* });
|
|
186
207
|
* ```
|
|
187
208
|
*/
|
|
188
|
-
|
|
209
|
+
run<S extends ZodType>(
|
|
210
|
+
input: string,
|
|
211
|
+
options: RunOptions & { output: S },
|
|
212
|
+
): Promise<CompletedRun<z.output<S>>>;
|
|
213
|
+
run(
|
|
214
|
+
input: string,
|
|
215
|
+
options?: RunOptions & { output?: undefined },
|
|
216
|
+
): Promise<CompletedRun>;
|
|
217
|
+
// Options typed `RunOptions`, whose schema is not known statically. See #280.
|
|
218
|
+
run(input: string, options?: RunOptions): Promise<CompletedRun<unknown>>;
|
|
219
|
+
async run(
|
|
220
|
+
input: string,
|
|
221
|
+
options?: RunOptions,
|
|
222
|
+
): Promise<CompletedRun<unknown>> {
|
|
189
223
|
const startedAt = Date.now();
|
|
190
224
|
const durationMs = () => Date.now() - startedAt;
|
|
225
|
+
// Always passed: left out, a run from another agent's tool would take
|
|
226
|
+
// that run's limit. See #315.
|
|
227
|
+
const recursionLimit =
|
|
228
|
+
options?.recursionLimit ??
|
|
229
|
+
this.options.recursionLimit ??
|
|
230
|
+
DEFAULT_RECURSION_LIMIT;
|
|
191
231
|
|
|
192
232
|
try {
|
|
193
|
-
|
|
233
|
+
// Read in the `try`, and `null` from untyped JavaScript taken as no
|
|
234
|
+
// options, as LangChain takes a `null` config. See #291.
|
|
235
|
+
const schema = options?.output;
|
|
236
|
+
|
|
237
|
+
if (!isRecursionLimit(recursionLimit)) {
|
|
238
|
+
throw new AgentRunError(
|
|
239
|
+
invalidRecursionLimit(this.prefix, recursionLimit),
|
|
240
|
+
this.agentName,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const output = schema && new RunOutput(schema, this.agentName);
|
|
245
|
+
const aside = new UsageAside();
|
|
246
|
+
const { answer, messages } = await this.complete(input, output, aside, {
|
|
247
|
+
signal: options?.signal,
|
|
248
|
+
recursionLimit,
|
|
249
|
+
});
|
|
194
250
|
const finished = {
|
|
195
251
|
durationMs: durationMs(),
|
|
196
|
-
...summariseRun(messages),
|
|
252
|
+
...summariseRun(messages, output?.toolName, aside.usage),
|
|
197
253
|
};
|
|
198
254
|
|
|
199
255
|
this.reporter.notify((observer) =>
|
|
@@ -202,14 +258,12 @@ export class Agent implements OnModuleInit {
|
|
|
202
258
|
|
|
203
259
|
return { status: 'completed', output: answer, ...finished };
|
|
204
260
|
} catch (thrown) {
|
|
205
|
-
const error =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
{ cause: thrown },
|
|
212
|
-
);
|
|
261
|
+
const error = toRunError(thrown, {
|
|
262
|
+
agent: this.agentName,
|
|
263
|
+
prefix: this.prefix,
|
|
264
|
+
signal: options?.signal,
|
|
265
|
+
recursionLimit,
|
|
266
|
+
});
|
|
213
267
|
|
|
214
268
|
this.reporter.notify((observer) =>
|
|
215
269
|
observer.onRunError?.({
|
|
@@ -223,9 +277,30 @@ export class Agent implements OnModuleInit {
|
|
|
223
277
|
}
|
|
224
278
|
}
|
|
225
279
|
|
|
280
|
+
// Model failures, which only LangChain's callbacks see; tool calls are
|
|
281
|
+
// reported by their wrapper and the tool middlewares. One handler per run:
|
|
282
|
+
// a call the run's signal cuts short is no failure. See #318.
|
|
283
|
+
private modelErrors(signal: AbortSignal | undefined): BaseCallbackHandler {
|
|
284
|
+
return BaseCallbackHandler.fromMethods({
|
|
285
|
+
handleLLMError: (error: unknown) => {
|
|
286
|
+
if (signal?.aborted) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.reporter.logError(`the model failed: ${messageOf(error)}`);
|
|
291
|
+
this.reporter.notify((observer) =>
|
|
292
|
+
observer.onModelError?.({ agent: this.agentName, error }),
|
|
293
|
+
);
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
226
298
|
private async complete(
|
|
227
299
|
input: string,
|
|
228
|
-
|
|
300
|
+
output: RunOutput | undefined,
|
|
301
|
+
aside: UsageAside,
|
|
302
|
+
run: Pick<RunOptions, 'signal' | 'recursionLimit'>,
|
|
303
|
+
): Promise<{ answer: unknown; messages: AgentState['messages'] }> {
|
|
229
304
|
if (!this.#graph) {
|
|
230
305
|
throw new AgentRunError(
|
|
231
306
|
agentNotBootstrapped(this.prefix),
|
|
@@ -233,10 +308,18 @@ export class Agent implements OnModuleInit {
|
|
|
233
308
|
);
|
|
234
309
|
}
|
|
235
310
|
|
|
236
|
-
const
|
|
311
|
+
const state: AgentState = await this.#graph.invoke(
|
|
237
312
|
{ messages: [{ role: 'user', content: input }] },
|
|
238
|
-
{
|
|
313
|
+
{
|
|
314
|
+
callbacks: [
|
|
315
|
+
this.modelErrors(run.signal),
|
|
316
|
+
...(this.options.callbacks ?? []),
|
|
317
|
+
],
|
|
318
|
+
configurable: { ...outputConfig(output), ...aside.config },
|
|
319
|
+
...run,
|
|
320
|
+
},
|
|
239
321
|
);
|
|
322
|
+
const { messages } = state;
|
|
240
323
|
|
|
241
324
|
const last = messages[messages.length - 1];
|
|
242
325
|
|
|
@@ -247,6 +330,10 @@ export class Agent implements OnModuleInit {
|
|
|
247
330
|
);
|
|
248
331
|
}
|
|
249
332
|
|
|
333
|
+
if (output) {
|
|
334
|
+
return { answer: output.read(state.structuredResponse), messages };
|
|
335
|
+
}
|
|
336
|
+
|
|
250
337
|
// `text`, not `content`: block answers are an array.
|
|
251
338
|
if (!last.text) {
|
|
252
339
|
throw new AgentRunError(modelReplyEmpty(this.prefix), this.agentName);
|
package/lib/constants.ts
CHANGED
|
@@ -5,6 +5,30 @@ export const TOOL_PARAMS_METADATA = 'nestjs-langchain:tool-params';
|
|
|
5
5
|
// Emitted by `emitDecoratorMetadata` on every decorated method.
|
|
6
6
|
export const PARAM_TYPES_METADATA = 'design:paramtypes';
|
|
7
7
|
|
|
8
|
+
// The agent routes a call with this prefix to structured output, never to the
|
|
9
|
+
// tool. Restated: LangChain does not export it. See #213.
|
|
10
|
+
export const STRUCTURED_OUTPUT_PREFIX = 'extract-';
|
|
11
|
+
|
|
12
|
+
// Ours rather than LangChain's counter or the schema's title: the same tool on
|
|
13
|
+
// every run, under the prefix no `@Tool` can take. See #281.
|
|
14
|
+
/**
|
|
15
|
+
* The name of the tool a run with an `output` schema answers through: a test
|
|
16
|
+
* faking the model queues a call to it.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const model = fakeModel().respondWithTools([
|
|
21
|
+
* { name: OUTPUT_TOOL, args: { category: 'bug' } },
|
|
22
|
+
* ]);
|
|
23
|
+
*
|
|
24
|
+
* const { output } = await agent.run(email, { output: Ticket });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const OUTPUT_TOOL = `${STRUCTURED_OUTPUT_PREFIX}output` as const;
|
|
28
|
+
|
|
29
|
+
// LangChain's default, restated: it does not export it.
|
|
30
|
+
export const DEFAULT_RECURSION_LIMIT = 25;
|
|
31
|
+
|
|
8
32
|
// The keys `@Module()` writes, restated: `@nestjs/common/constants` is
|
|
9
33
|
// internal. See #97.
|
|
10
34
|
export const MODULE_METADATA_KEYS = [
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AgentRunError } from './agent-run.error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The run's `signal` was aborted. Its `reason` is in `cause`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* try {
|
|
9
|
+
* await agent.run(question, { signal: AbortSignal.timeout(30_000) });
|
|
10
|
+
* } catch (error) {
|
|
11
|
+
* if (error instanceof AgentAbortedError) {
|
|
12
|
+
* console.warn('gave up:', error.cause);
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export class AgentAbortedError extends AgentRunError {
|
|
18
|
+
constructor(message: string, agent: string, options?: { cause?: unknown }) {
|
|
19
|
+
super(message, agent, options);
|
|
20
|
+
this.name = 'AgentAbortedError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AgentRunError } from './agent-run.error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The run took its `recursionLimit` of steps without an answer: the model kept
|
|
5
|
+
* calling tools. LangGraph's error is in `cause`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* try {
|
|
10
|
+
* await agent.run(question, { recursionLimit: 10 });
|
|
11
|
+
* } catch (error) {
|
|
12
|
+
* if (error instanceof AgentRecursionLimitError) {
|
|
13
|
+
* return 'This question needs too many steps.';
|
|
14
|
+
* }
|
|
15
|
+
* throw error;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export class AgentRecursionLimitError extends AgentRunError {
|
|
20
|
+
constructor(message: string, agent: string, options?: { cause?: unknown }) {
|
|
21
|
+
super(message, agent, options);
|
|
22
|
+
this.name = 'AgentRecursionLimitError';
|
|
23
|
+
}
|
|
24
|
+
}
|
package/lib/errors/index.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
export { AgentAbortedError } from './agent-aborted.error.js';
|
|
2
|
+
export { AgentRecursionLimitError } from './agent-recursion-limit.error.js';
|
|
1
3
|
export { AgentRunError } from './agent-run.error.js';
|
|
4
|
+
export { toRunError } from './run-error.util.js';
|
|
5
|
+
export { StructuredOutputError } from './structured-output.error.js';
|
|
2
6
|
export { ToolConfigurationError } from './tool-configuration.error.js';
|
|
7
|
+
export { unwrapMiddlewareError } from './unwrap-middleware-error.util.js';
|
package/lib/errors/messages.ts
CHANGED
|
@@ -96,14 +96,17 @@ export const schemaContradictsSignature = (
|
|
|
96
96
|
`${where}: the schema for "${param}" describes ${declared.join(' | ')} ` +
|
|
97
97
|
`but the signature declares ${expected}.`;
|
|
98
98
|
|
|
99
|
+
// The advice of both messages on a schema with no JSON Schema form.
|
|
100
|
+
const jsonSchemaAdvice =
|
|
101
|
+
'Declare a schema JSON Schema can express and convert it with .transform(), ' +
|
|
102
|
+
'such as z.iso.date() or z.iso.datetime() for a date.';
|
|
103
|
+
|
|
99
104
|
export const noJsonSchemaForm = (
|
|
100
105
|
where: string,
|
|
101
106
|
param: string,
|
|
102
107
|
reason: string,
|
|
103
108
|
): string =>
|
|
104
|
-
`${where}: "${param}" has no JSON Schema form (${reason}).
|
|
105
|
-
'Declare a schema JSON Schema can express and convert it with .transform(), ' +
|
|
106
|
-
'such as z.iso.datetime() for a date.';
|
|
109
|
+
`${where}: "${param}" has no JSON Schema form (${reason}). ${jsonSchemaAdvice}`;
|
|
107
110
|
|
|
108
111
|
export const duplicateParamName = (
|
|
109
112
|
where: string,
|
|
@@ -155,4 +158,53 @@ export const modelNeverReplied = (
|
|
|
155
158
|
export const modelReplyEmpty = (prefix: string): string =>
|
|
156
159
|
`The ${prefix}agent's model replied with no text content.`;
|
|
157
160
|
|
|
161
|
+
export const runAborted = (prefix: string): string =>
|
|
162
|
+
`The ${prefix}agent's run was aborted.`;
|
|
163
|
+
|
|
164
|
+
export const recursionLimitReached = (prefix: string, limit: number): string =>
|
|
165
|
+
`The ${prefix}agent's run reached its recursion limit of ${limit} steps ` +
|
|
166
|
+
'without an answer. Raise `recursionLimit`, or check what makes the model ' +
|
|
167
|
+
'keep calling tools.';
|
|
168
|
+
|
|
169
|
+
export const invalidRecursionLimit = (prefix: string, limit: unknown): string =>
|
|
170
|
+
`The ${prefix}agent's \`recursionLimit\` must be an integer of at least 1, ` +
|
|
171
|
+
`not ${typeof limit === 'string' ? `"${limit}"` : String(limit)}.`;
|
|
172
|
+
|
|
173
|
+
export const noOutputJsonSchemaForm = (
|
|
174
|
+
prefix: string,
|
|
175
|
+
reason: string,
|
|
176
|
+
): string =>
|
|
177
|
+
`The ${prefix}agent's \`output\` schema has no JSON Schema form (${reason}). ` +
|
|
178
|
+
jsonSchemaAdvice;
|
|
179
|
+
|
|
180
|
+
export const outputNotAnObject = (prefix: string): string =>
|
|
181
|
+
`The ${prefix}agent's \`output\` schema is not an object with named fields: ` +
|
|
182
|
+
"the model fills it in as a tool's parameters. Wrap it, such as " +
|
|
183
|
+
'`z.object({ items: z.array(...) })`.';
|
|
184
|
+
|
|
185
|
+
export const structuredOutputMissing = (prefix: string): string =>
|
|
186
|
+
`The ${prefix}agent's model replied without the requested structured output.`;
|
|
187
|
+
|
|
188
|
+
export const structuredOutputInvalid = (
|
|
189
|
+
prefix: string,
|
|
190
|
+
reason: string,
|
|
191
|
+
): string =>
|
|
192
|
+
`The ${prefix}agent's model replied with an output that does not match ` +
|
|
193
|
+
`the schema: ${reason}`;
|
|
194
|
+
|
|
195
|
+
export const structuredOutputMultiple = (
|
|
196
|
+
prefix: string,
|
|
197
|
+
count: number,
|
|
198
|
+
): string =>
|
|
199
|
+
`The ${prefix}agent's model replied with ${count} outputs instead of one.`;
|
|
200
|
+
|
|
158
201
|
export const unknownTool = (): string => 'not a tool of this agent';
|
|
202
|
+
|
|
203
|
+
// LangChain's answer to a tool the agent does not have, restated:
|
|
204
|
+
// `getInvalidToolError` is not exported. See #264.
|
|
205
|
+
export const invalidTool = (tool: string, tools: string[]): string =>
|
|
206
|
+
`Error: ${tool} is not a valid tool, try one of [${tools.join(', ')}].`;
|
|
207
|
+
|
|
208
|
+
export const toolNotRun = (tool: string): string =>
|
|
209
|
+
`Error: '${tool}' was not run: it came with the final answer. ` +
|
|
210
|
+
'Call it on its own first if it is needed.';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AgentAbortedError } from './agent-aborted.error.js';
|
|
2
|
+
import { AgentRecursionLimitError } from './agent-recursion-limit.error.js';
|
|
3
|
+
import { AgentRunError } from './agent-run.error.js';
|
|
4
|
+
import {
|
|
5
|
+
agentRunFailed,
|
|
6
|
+
recursionLimitReached,
|
|
7
|
+
runAborted,
|
|
8
|
+
} from './messages.js';
|
|
9
|
+
import { unwrapMiddlewareError } from './unwrap-middleware-error.util.js';
|
|
10
|
+
|
|
11
|
+
interface FailedRun {
|
|
12
|
+
agent: string;
|
|
13
|
+
/** Empty for the unnamed agent, `'MATH '` otherwise. */
|
|
14
|
+
prefix: string;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
/** The limit the run was given. */
|
|
17
|
+
recursionLimit: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The `AgentRunError` `run()` throws for what the agent's graph threw.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
* @param thrown What the graph threw.
|
|
25
|
+
* @param run The run that failed.
|
|
26
|
+
* @returns The error itself when it is one already, else the subclass of its
|
|
27
|
+
* kind, the original in `cause`.
|
|
28
|
+
*/
|
|
29
|
+
export const toRunError = (
|
|
30
|
+
thrown: unknown,
|
|
31
|
+
{ agent, prefix, signal, recursionLimit }: FailedRun,
|
|
32
|
+
): AgentRunError => {
|
|
33
|
+
const cause = unwrapMiddlewareError(thrown);
|
|
34
|
+
|
|
35
|
+
if (cause instanceof AgentRunError) {
|
|
36
|
+
return cause;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The reason itself, which LangChain may have wrapped on its way out.
|
|
40
|
+
if (signal?.aborted) {
|
|
41
|
+
return new AgentAbortedError(runAborted(prefix), agent, {
|
|
42
|
+
cause: signal.reason,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// By name, as LangGraph tells its errors apart; it is not a dependency of
|
|
47
|
+
// ours. See #194.
|
|
48
|
+
if (cause instanceof Error && cause.name === 'GraphRecursionError') {
|
|
49
|
+
return new AgentRecursionLimitError(
|
|
50
|
+
recursionLimitReached(prefix, recursionLimit),
|
|
51
|
+
agent,
|
|
52
|
+
{ cause },
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new AgentRunError(agentRunFailed(prefix, cause), agent, { cause });
|
|
57
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AgentRunError } from './agent-run.error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The model's answer to a run with an `output` schema never came, or does not
|
|
5
|
+
* match the schema. LangChain's or Zod's error, if any, is in `cause`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* try {
|
|
10
|
+
* const { output } = await agent.run(ticket, { output: Classification });
|
|
11
|
+
* } catch (error) {
|
|
12
|
+
* if (error instanceof StructuredOutputError) {
|
|
13
|
+
* console.error(error.message, error.cause);
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class StructuredOutputError extends AgentRunError {
|
|
19
|
+
constructor(message: string, agent: string, options?: { cause?: unknown }) {
|
|
20
|
+
super(message, agent, options);
|
|
21
|
+
this.name = 'StructuredOutputError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MiddlewareError } from 'langchain';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The error as thrown, without the `MiddlewareError` LangChain wraps it in at
|
|
5
|
+
* each `wrapModelCall` middleware it crosses, a provider error included.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
* @param error What the agent's graph threw.
|
|
9
|
+
* @returns The innermost error.
|
|
10
|
+
*/
|
|
11
|
+
export const unwrapMiddlewareError = (error: unknown): unknown => {
|
|
12
|
+
let unwrapped = error;
|
|
13
|
+
|
|
14
|
+
while (MiddlewareError.isInstance(unwrapped) && unwrapped.cause) {
|
|
15
|
+
unwrapped = unwrapped.cause;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return unwrapped;
|
|
19
|
+
};
|
package/lib/index.ts
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
export { LangChainModule } from './langchain.module.js';
|
|
4
4
|
export { getAgentToken } from './langchain.module-definition.js';
|
|
5
5
|
export { Agent } from './agent.js';
|
|
6
|
-
export {
|
|
6
|
+
export { OUTPUT_TOOL } from './constants.js';
|
|
7
|
+
export {
|
|
8
|
+
AgentAbortedError,
|
|
9
|
+
AgentRecursionLimitError,
|
|
10
|
+
AgentRunError,
|
|
11
|
+
StructuredOutputError,
|
|
12
|
+
ToolConfigurationError,
|
|
13
|
+
} from './errors/index.js';
|
|
7
14
|
export { InjectAgent, Tool, ToolParam } from './decorators/index.js';
|
|
8
15
|
export type { ToolOptions, ToolParamOptions } from './decorators/index.js';
|
|
9
16
|
export type {
|
|
@@ -16,6 +23,7 @@ export type {
|
|
|
16
23
|
ToolStartEvent,
|
|
17
24
|
} from './interfaces/agent-observer.interface.js';
|
|
18
25
|
export type { CompletedRun } from './interfaces/completed-run.interface.js';
|
|
26
|
+
export type { RunOptions } from './interfaces/run-options.interface.js';
|
|
19
27
|
export type {
|
|
20
28
|
AgentCallback,
|
|
21
29
|
LangChainModuleOptions,
|
|
@@ -7,7 +7,7 @@ import type { RunSummary } from '../run/index.js';
|
|
|
7
7
|
export interface CompletedRun<TOutput = string> extends RunSummary {
|
|
8
8
|
/** Always `'completed'`: a run that fails throws instead. */
|
|
9
9
|
status: 'completed';
|
|
10
|
-
/** The model's text answer. */
|
|
10
|
+
/** The model's text answer, or the object the run's `output` schema parsed. */
|
|
11
11
|
output: TOutput;
|
|
12
12
|
/** How long the run took. */
|
|
13
13
|
durationMs: number;
|
|
@@ -73,6 +73,12 @@ export interface LangChainModuleOptions {
|
|
|
73
73
|
observer?: AgentObserver;
|
|
74
74
|
/** LangChain-native run callbacks, for handlers written against its API. */
|
|
75
75
|
callbacks?: AgentCallback[];
|
|
76
|
+
/**
|
|
77
|
+
* The most steps a run may take, each model call and each round of tool
|
|
78
|
+
* calls counting as one: LangGraph's 25 when omitted. A run's own
|
|
79
|
+
* `recursionLimit` overrides it.
|
|
80
|
+
*/
|
|
81
|
+
recursionLimit?: number;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
/**
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ZodType } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The options of one run. Passed inline, they type `output` from the schema;
|
|
5
|
+
* typed `RunOptions`, `output` is `unknown`.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const { output } = await agent.run(email, { output: Ticket }); // a Ticket
|
|
10
|
+
* await agent.run(question, { signal: AbortSignal.timeout(30_000) });
|
|
11
|
+
*
|
|
12
|
+
* const ask = (question: string, options?: RunOptions) =>
|
|
13
|
+
* agent.run(question, options); // `output` is unknown
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export interface RunOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The schema the answer must match. `output` is then the object it parses,
|
|
19
|
+
* typed from it, instead of the model's text.
|
|
20
|
+
*/
|
|
21
|
+
output?: ZodType;
|
|
22
|
+
/**
|
|
23
|
+
* Aborts the run: `run()` throws an `AgentAbortedError`, the signal's
|
|
24
|
+
* `reason` in `cause`. A tool already running ends in the background, its
|
|
25
|
+
* result dropped.
|
|
26
|
+
*/
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
/**
|
|
29
|
+
* The most steps the run may take, each model call and each round of tool
|
|
30
|
+
* calls counting as one, instead of the agent's `recursionLimit`. Past it,
|
|
31
|
+
* `run()` throws an `AgentRecursionLimitError`.
|
|
32
|
+
*/
|
|
33
|
+
recursionLimit?: number;
|
|
34
|
+
}
|