nestjs-langchain 2.1.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 +6 -3
- package/dist/agent.d.ts +6 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +51 -19
- package/dist/agent.js.map +1 -1
- package/dist/constants.d.ts +15 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +15 -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 +3 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/messages.d.ts +3 -0
- package/dist/errors/messages.d.ts.map +1 -1
- package/dist/errors/messages.js +6 -0
- 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/index.d.ts +2 -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/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 +13 -0
- package/dist/interfaces/run-options.interface.d.ts.map +1 -1
- package/dist/output/run-output.d.ts +1 -1
- package/dist/output/run-output.d.ts.map +1 -1
- package/dist/run/index.d.ts +1 -0
- package/dist/run/index.d.ts.map +1 -1
- package/dist/run/index.js +1 -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/lib/agent.ts +68 -27
- package/lib/constants.ts +17 -1
- package/lib/errors/agent-aborted.error.ts +22 -0
- package/lib/errors/agent-recursion-limit.error.ts +24 -0
- package/lib/errors/index.ts +3 -0
- package/lib/errors/messages.ts +12 -0
- package/lib/errors/run-error.util.ts +57 -0
- package/lib/index.ts +3 -0
- package/lib/interfaces/langchain-module-options.interface.ts +6 -0
- package/lib/interfaces/run-options.interface.ts +13 -0
- package/lib/run/index.ts +1 -0
- package/lib/run/recursion-limit.util.ts +11 -0
- package/package.json +4 -4
|
@@ -64,6 +64,12 @@ export interface LangChainModuleOptions {
|
|
|
64
64
|
observer?: AgentObserver;
|
|
65
65
|
/** LangChain-native run callbacks, for handlers written against its API. */
|
|
66
66
|
callbacks?: AgentCallback[];
|
|
67
|
+
/**
|
|
68
|
+
* The most steps a run may take, each model call and each round of tool
|
|
69
|
+
* calls counting as one: LangGraph's 25 when omitted. A run's own
|
|
70
|
+
* `recursionLimit` overrides it.
|
|
71
|
+
*/
|
|
72
|
+
recursionLimit?: number;
|
|
67
73
|
}
|
|
68
74
|
/**
|
|
69
75
|
* A class building an agent's options, for `registerAsync({ useClass })` or
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"langchain-module-options.interface.d.ts","sourceRoot":"","sources":["../../lib/interfaces/langchain-module-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EACV,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,UAAU,aAAa;IACrB,uBAAuB;IACvB,MAAM,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,8DAA8D;IAC9D,SAAS,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACtC,kCAAkC;IAClC,qBAAqB,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;CAClD;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,sBAAsB,CAAC;AAEzE,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,KAAK,EAAE,WAAW,CAAC;IACnB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"langchain-module-options.interface.d.ts","sourceRoot":"","sources":["../../lib/interfaces/langchain-module-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EACV,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,UAAU,aAAa;IACrB,uBAAuB;IACvB,MAAM,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,8DAA8D;IAC9D,SAAS,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IACtC,kCAAkC;IAClC,qBAAqB,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;CAClD;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,aAAa,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,sBAAsB,CAAC;AAEzE,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,KAAK,EAAE,WAAW,CAAC;IACnB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAC5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,sBAAsB,IACpB,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC5D"}
|
|
@@ -6,6 +6,7 @@ import type { ZodType } from 'zod';
|
|
|
6
6
|
* @example
|
|
7
7
|
* ```ts
|
|
8
8
|
* const { output } = await agent.run(email, { output: Ticket }); // a Ticket
|
|
9
|
+
* await agent.run(question, { signal: AbortSignal.timeout(30_000) });
|
|
9
10
|
*
|
|
10
11
|
* const ask = (question: string, options?: RunOptions) =>
|
|
11
12
|
* agent.run(question, options); // `output` is unknown
|
|
@@ -17,5 +18,17 @@ export interface RunOptions {
|
|
|
17
18
|
* typed from it, instead of the model's text.
|
|
18
19
|
*/
|
|
19
20
|
output?: ZodType;
|
|
21
|
+
/**
|
|
22
|
+
* Aborts the run: `run()` throws an `AgentAbortedError`, the signal's
|
|
23
|
+
* `reason` in `cause`. A tool already running ends in the background, its
|
|
24
|
+
* result dropped.
|
|
25
|
+
*/
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
/**
|
|
28
|
+
* The most steps the run may take, each model call and each round of tool
|
|
29
|
+
* calls counting as one, instead of the agent's `recursionLimit`. Past it,
|
|
30
|
+
* `run()` throws an `AgentRecursionLimitError`.
|
|
31
|
+
*/
|
|
32
|
+
recursionLimit?: number;
|
|
20
33
|
}
|
|
21
34
|
//# sourceMappingURL=run-options.interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-options.interface.d.ts","sourceRoot":"","sources":["../../lib/interfaces/run-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC
|
|
1
|
+
{"version":3,"file":"run-options.interface.d.ts","sourceRoot":"","sources":["../../lib/interfaces/run-options.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -16,7 +16,7 @@ export declare class RunOutput {
|
|
|
16
16
|
*/
|
|
17
17
|
readonly strategy: TypedToolStrategy;
|
|
18
18
|
/** The tool the model answers through. */
|
|
19
|
-
readonly toolName
|
|
19
|
+
readonly toolName: "extract-output";
|
|
20
20
|
private readonly prefix;
|
|
21
21
|
/**
|
|
22
22
|
* @param schema The run's `output` schema.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-output.d.ts","sourceRoot":"","sources":["../../lib/output/run-output.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,iBAAiB,EAClB,MAAM,WAAW,CAAC;AACnB,OAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAqBjC;;;;;GAKG;AACH,qBAAa,SAAS;;IAkBlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAlBxB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"run-output.d.ts","sourceRoot":"","sources":["../../lib/output/run-output.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,iBAAiB,EAClB,MAAM,WAAW,CAAC;AACnB,OAAU,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAqBjC;;;;;GAKG;AACH,qBAAa,SAAS;;IAkBlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAlBxB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,mBAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC;;;;;OAKG;gBAEgB,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM;IAqChC,OAAO,CAAC,KAAK;IAUb;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAiBhC;;;;OAIG;IACH,IAAI,CAAC,kBAAkB,EAAE,OAAO,GAAG,OAAO;IAmB1C,OAAO,CAAC,OAAO;CAOhB"}
|
package/dist/run/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { answeredCalls } from './answered-calls.util.js';
|
|
2
|
+
export { isRecursionLimit } from './recursion-limit.util.js';
|
|
2
3
|
export { summariseRun } from './run-summary.util.js';
|
|
3
4
|
export { UsageAside } from './usage-aside.js';
|
|
4
5
|
export type { RunSummary } from './run-summary.util.js';
|
package/dist/run/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/run/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/run/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/run/index.js
CHANGED
package/dist/run/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/run/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../lib/run/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a `recursionLimit` is one LangGraph applies: it rejects a limit
|
|
3
|
+
* below 1, but runs on with `NaN`. See #194.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
* @param limit The option as given, from untyped JavaScript too.
|
|
7
|
+
* @returns `true` for an integer of at least 1, or no limit.
|
|
8
|
+
*/
|
|
9
|
+
export declare const isRecursionLimit: (limit: unknown) => boolean;
|
|
10
|
+
//# sourceMappingURL=recursion-limit.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recursion-limit.util.d.ts","sourceRoot":"","sources":["../../lib/run/recursion-limit.util.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,OAEoB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a `recursionLimit` is one LangGraph applies: it rejects a limit
|
|
3
|
+
* below 1, but runs on with `NaN`. See #194.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
* @param limit The option as given, from untyped JavaScript too.
|
|
7
|
+
* @returns `true` for an integer of at least 1, or no limit.
|
|
8
|
+
*/
|
|
9
|
+
export const isRecursionLimit = (limit) => limit === undefined ||
|
|
10
|
+
(typeof limit === 'number' && Number.isInteger(limit) && limit >= 1);
|
|
11
|
+
//# sourceMappingURL=recursion-limit.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recursion-limit.util.js","sourceRoot":"","sources":["../../lib/run/recursion-limit.util.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAW,EAAE,CAC1D,KAAK,KAAK,SAAS;IACnB,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC"}
|
package/lib/agent.ts
CHANGED
|
@@ -20,14 +20,15 @@ import {
|
|
|
20
20
|
} from './tools/index.js';
|
|
21
21
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
22
22
|
import { LOG_CONTEXT, messageOf, RunReporter } from './logging/index.js';
|
|
23
|
-
import {
|
|
23
|
+
import { DEFAULT_RECURSION_LIMIT } from './constants.js';
|
|
24
|
+
import { isRecursionLimit, summariseRun, UsageAside } from './run/index.js';
|
|
24
25
|
import { outputConfig, RunOutput, structuredOutput } from './output/index.js';
|
|
25
|
-
import { AgentRunError,
|
|
26
|
+
import { AgentRunError, toRunError } from './errors/index.js';
|
|
26
27
|
import {
|
|
27
28
|
agentNotBootstrapped,
|
|
28
29
|
agentPrefix,
|
|
29
|
-
agentRunFailed,
|
|
30
30
|
duplicateAgentName,
|
|
31
|
+
invalidRecursionLimit,
|
|
31
32
|
modelNeverReplied,
|
|
32
33
|
modelNotBuilt,
|
|
33
34
|
modelReplyEmpty,
|
|
@@ -62,9 +63,6 @@ export class Agent implements OnModuleInit {
|
|
|
62
63
|
private readonly logger = new Logger();
|
|
63
64
|
private readonly prefix: string;
|
|
64
65
|
private readonly reporter: RunReporter;
|
|
65
|
-
// Model failures, which only LangChain's callbacks see. Tool calls are
|
|
66
|
-
// reported by their wrapper and the tool middlewares.
|
|
67
|
-
private readonly modelErrors: BaseCallbackHandler;
|
|
68
66
|
|
|
69
67
|
/** @internal Built by `LangChainModule`, never by hand. */
|
|
70
68
|
constructor(
|
|
@@ -82,14 +80,6 @@ export class Agent implements OnModuleInit {
|
|
|
82
80
|
prefix: this.prefix,
|
|
83
81
|
observer: options.observer,
|
|
84
82
|
});
|
|
85
|
-
this.modelErrors = BaseCallbackHandler.fromMethods({
|
|
86
|
-
handleLLMError: (error: unknown) => {
|
|
87
|
-
this.reporter.logError(`the model failed: ${messageOf(error)}`);
|
|
88
|
-
this.reporter.notify((observer) =>
|
|
89
|
-
observer.onModelError?.({ agent: agentName, error }),
|
|
90
|
-
);
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
83
|
}
|
|
94
84
|
|
|
95
85
|
/**
|
|
@@ -100,6 +90,12 @@ export class Agent implements OnModuleInit {
|
|
|
100
90
|
async onModuleInit() {
|
|
101
91
|
this.assertUniqueName();
|
|
102
92
|
|
|
93
|
+
const { recursionLimit } = this.options;
|
|
94
|
+
|
|
95
|
+
if (!isRecursionLimit(recursionLimit)) {
|
|
96
|
+
throw new Error(invalidRecursionLimit(this.prefix, recursionLimit));
|
|
97
|
+
}
|
|
98
|
+
|
|
103
99
|
const tools = await this.toolDiscovery.getToolsFromModules(
|
|
104
100
|
this.options.tools ?? [],
|
|
105
101
|
this.reporter,
|
|
@@ -119,6 +115,11 @@ export class Agent implements OnModuleInit {
|
|
|
119
115
|
toolErrors(this.reporter),
|
|
120
116
|
structuredOutput(),
|
|
121
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 },
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
this.logger.log(
|
|
@@ -190,12 +191,15 @@ export class Agent implements OnModuleInit {
|
|
|
190
191
|
* as an `AgentRunError`, the original error in its `cause`.
|
|
191
192
|
*
|
|
192
193
|
* @param input The user's message.
|
|
193
|
-
* @param options With `output`, the schema the answer must match
|
|
194
|
+
* @param options With `output`, the schema the answer must match; with
|
|
195
|
+
* `signal`, what aborts the run; with `recursionLimit`, its most steps.
|
|
194
196
|
* @returns The model's answer, the object `output` parses when given, the
|
|
195
197
|
* tools it called and what the run cost.
|
|
196
198
|
* @example
|
|
197
199
|
* ```ts
|
|
198
|
-
* 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
|
+
* });
|
|
199
203
|
*
|
|
200
204
|
* const { output: ticket } = await agent.run(email, {
|
|
201
205
|
* output: z.object({ category: z.enum(['billing', 'bug', 'other']) }),
|
|
@@ -218,14 +222,31 @@ export class Agent implements OnModuleInit {
|
|
|
218
222
|
): Promise<CompletedRun<unknown>> {
|
|
219
223
|
const startedAt = Date.now();
|
|
220
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;
|
|
221
231
|
|
|
222
232
|
try {
|
|
223
233
|
// Read in the `try`, and `null` from untyped JavaScript taken as no
|
|
224
234
|
// options, as LangChain takes a `null` config. See #291.
|
|
225
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
|
+
|
|
226
244
|
const output = schema && new RunOutput(schema, this.agentName);
|
|
227
245
|
const aside = new UsageAside();
|
|
228
|
-
const { answer, messages } = await this.complete(input, output, aside
|
|
246
|
+
const { answer, messages } = await this.complete(input, output, aside, {
|
|
247
|
+
signal: options?.signal,
|
|
248
|
+
recursionLimit,
|
|
249
|
+
});
|
|
229
250
|
const finished = {
|
|
230
251
|
durationMs: durationMs(),
|
|
231
252
|
...summariseRun(messages, output?.toolName, aside.usage),
|
|
@@ -237,15 +258,12 @@ export class Agent implements OnModuleInit {
|
|
|
237
258
|
|
|
238
259
|
return { status: 'completed', output: answer, ...finished };
|
|
239
260
|
} catch (thrown) {
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.agentName,
|
|
247
|
-
{ cause },
|
|
248
|
-
);
|
|
261
|
+
const error = toRunError(thrown, {
|
|
262
|
+
agent: this.agentName,
|
|
263
|
+
prefix: this.prefix,
|
|
264
|
+
signal: options?.signal,
|
|
265
|
+
recursionLimit,
|
|
266
|
+
});
|
|
249
267
|
|
|
250
268
|
this.reporter.notify((observer) =>
|
|
251
269
|
observer.onRunError?.({
|
|
@@ -259,10 +277,29 @@ export class Agent implements OnModuleInit {
|
|
|
259
277
|
}
|
|
260
278
|
}
|
|
261
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
|
+
|
|
262
298
|
private async complete(
|
|
263
299
|
input: string,
|
|
264
300
|
output: RunOutput | undefined,
|
|
265
301
|
aside: UsageAside,
|
|
302
|
+
run: Pick<RunOptions, 'signal' | 'recursionLimit'>,
|
|
266
303
|
): Promise<{ answer: unknown; messages: AgentState['messages'] }> {
|
|
267
304
|
if (!this.#graph) {
|
|
268
305
|
throw new AgentRunError(
|
|
@@ -274,8 +311,12 @@ export class Agent implements OnModuleInit {
|
|
|
274
311
|
const state: AgentState = await this.#graph.invoke(
|
|
275
312
|
{ messages: [{ role: 'user', content: input }] },
|
|
276
313
|
{
|
|
277
|
-
callbacks: [
|
|
314
|
+
callbacks: [
|
|
315
|
+
this.modelErrors(run.signal),
|
|
316
|
+
...(this.options.callbacks ?? []),
|
|
317
|
+
],
|
|
278
318
|
configurable: { ...outputConfig(output), ...aside.config },
|
|
319
|
+
...run,
|
|
279
320
|
},
|
|
280
321
|
);
|
|
281
322
|
const { messages } = state;
|
package/lib/constants.ts
CHANGED
|
@@ -11,7 +11,23 @@ export const STRUCTURED_OUTPUT_PREFIX = 'extract-';
|
|
|
11
11
|
|
|
12
12
|
// Ours rather than LangChain's counter or the schema's title: the same tool on
|
|
13
13
|
// every run, under the prefix no `@Tool` can take. See #281.
|
|
14
|
-
|
|
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;
|
|
15
31
|
|
|
16
32
|
// The keys `@Module()` writes, restated: `@nestjs/common/constants` is
|
|
17
33
|
// internal. See #97.
|
|
@@ -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,4 +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';
|
|
2
5
|
export { StructuredOutputError } from './structured-output.error.js';
|
|
3
6
|
export { ToolConfigurationError } from './tool-configuration.error.js';
|
|
4
7
|
export { unwrapMiddlewareError } from './unwrap-middleware-error.util.js';
|
package/lib/errors/messages.ts
CHANGED
|
@@ -158,6 +158,18 @@ export const modelNeverReplied = (
|
|
|
158
158
|
export const modelReplyEmpty = (prefix: string): string =>
|
|
159
159
|
`The ${prefix}agent's model replied with no text content.`;
|
|
160
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
|
+
|
|
161
173
|
export const noOutputJsonSchemaForm = (
|
|
162
174
|
prefix: string,
|
|
163
175
|
reason: string,
|
|
@@ -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
|
+
};
|
package/lib/index.ts
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
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 { OUTPUT_TOOL } from './constants.js';
|
|
6
7
|
export {
|
|
8
|
+
AgentAbortedError,
|
|
9
|
+
AgentRecursionLimitError,
|
|
7
10
|
AgentRunError,
|
|
8
11
|
StructuredOutputError,
|
|
9
12
|
ToolConfigurationError,
|
|
@@ -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
|
/**
|
|
@@ -7,6 +7,7 @@ import type { ZodType } from 'zod';
|
|
|
7
7
|
* @example
|
|
8
8
|
* ```ts
|
|
9
9
|
* const { output } = await agent.run(email, { output: Ticket }); // a Ticket
|
|
10
|
+
* await agent.run(question, { signal: AbortSignal.timeout(30_000) });
|
|
10
11
|
*
|
|
11
12
|
* const ask = (question: string, options?: RunOptions) =>
|
|
12
13
|
* agent.run(question, options); // `output` is unknown
|
|
@@ -18,4 +19,16 @@ export interface RunOptions {
|
|
|
18
19
|
* typed from it, instead of the model's text.
|
|
19
20
|
*/
|
|
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;
|
|
21
34
|
}
|
package/lib/run/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { answeredCalls } from './answered-calls.util.js';
|
|
2
|
+
export { isRecursionLimit } from './recursion-limit.util.js';
|
|
2
3
|
export { summariseRun } from './run-summary.util.js';
|
|
3
4
|
export { UsageAside } from './usage-aside.js';
|
|
4
5
|
export type { RunSummary } from './run-summary.util.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a `recursionLimit` is one LangGraph applies: it rejects a limit
|
|
3
|
+
* below 1, but runs on with `NaN`. See #194.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
* @param limit The option as given, from untyped JavaScript too.
|
|
7
|
+
* @returns `true` for an integer of at least 1, or no limit.
|
|
8
|
+
*/
|
|
9
|
+
export const isRecursionLimit = (limit: unknown): boolean =>
|
|
10
|
+
limit === undefined ||
|
|
11
|
+
(typeof limit === 'number' && Number.isInteger(limit) && limit >= 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nestjs-langchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI agents for NestJS, powered by LangChain: declare agents as modules, give them your services as tools.",
|
|
6
6
|
"keywords": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"format": "prettier --write \"{lib,tests,scripts}/**/*.{ts,mjs}\" vitest.config.ts",
|
|
71
71
|
"lint": "oxlint --type-aware lib/ tests/ scripts/ vitest.config.ts --fix",
|
|
72
72
|
"lint:check": "oxlint --type-aware lib/ tests/ scripts/ vitest.config.ts",
|
|
73
|
-
"test": "vitest run --
|
|
73
|
+
"test": "vitest run && tsc --noEmit",
|
|
74
74
|
"test:watch": "vitest",
|
|
75
75
|
"release": "release-it",
|
|
76
76
|
"release:rc": "release-it --preRelease=rc --no-plugins.@release-it/conventional-changelog.infile",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"@nestjs/testing": "^12.0.1",
|
|
93
93
|
"@release-it/conventional-changelog": "12.0.2",
|
|
94
94
|
"@types/node": "^26.6.4",
|
|
95
|
-
"@vitest/coverage-v8": "^
|
|
95
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
96
96
|
"eslint-plugin-jsdoc": "^65.2.0",
|
|
97
97
|
"husky": "9.1.7",
|
|
98
98
|
"langchain": "^1.5.4",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"rxjs": "^7.8.2",
|
|
108
108
|
"semver": "^7.8.5",
|
|
109
109
|
"typescript": "^6.0.3",
|
|
110
|
-
"vitest": "^
|
|
110
|
+
"vitest": "^5.0.5",
|
|
111
111
|
"zod": "^4.0.0"
|
|
112
112
|
},
|
|
113
113
|
"peerDependencies": {
|