nestjs-langchain 1.0.2 → 2.0.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/LICENSE +21 -21
- package/README.md +344 -254
- package/dist/agent.d.ts +63 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +189 -0
- package/dist/agent.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/decorators/index.d.ts +4 -2
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +3 -18
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/inject-agent.decorator.d.ts +12 -0
- package/dist/decorators/inject-agent.decorator.d.ts.map +1 -0
- package/dist/decorators/inject-agent.decorator.js +15 -7
- package/dist/decorators/inject-agent.decorator.js.map +1 -0
- package/dist/decorators/tool.decorator.d.ts +54 -6
- package/dist/decorators/tool.decorator.d.ts.map +1 -0
- package/dist/decorators/tool.decorator.js +40 -16
- package/dist/decorators/tool.decorator.js.map +1 -0
- package/dist/errors/agent-run.error.d.ts +27 -0
- package/dist/errors/agent-run.error.d.ts.map +1 -0
- package/dist/errors/agent-run.error.js +26 -0
- package/dist/errors/agent-run.error.js.map +1 -0
- package/dist/errors/index.d.ts +3 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/messages.d.ts +26 -0
- package/dist/errors/messages.d.ts.map +1 -0
- package/dist/errors/messages.js +67 -0
- package/dist/errors/messages.js.map +1 -0
- package/dist/errors/tool-configuration.error.d.ts +23 -0
- package/dist/errors/tool-configuration.error.d.ts.map +1 -0
- package/dist/errors/tool-configuration.error.js +27 -0
- package/dist/errors/tool-configuration.error.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -19
- package/dist/index.js.map +1 -0
- package/dist/interfaces/agent-observer.interface.d.ts +107 -0
- package/dist/interfaces/agent-observer.interface.d.ts.map +1 -0
- package/dist/interfaces/agent-observer.interface.js +2 -0
- package/dist/interfaces/agent-observer.interface.js.map +1 -0
- package/dist/interfaces/completed-run.interface.d.ts +14 -0
- package/dist/interfaces/completed-run.interface.d.ts.map +1 -0
- package/dist/interfaces/completed-run.interface.js +2 -0
- package/dist/interfaces/completed-run.interface.js.map +1 -0
- package/dist/interfaces/langchain-module-options.interface.d.ts +73 -5
- package/dist/interfaces/langchain-module-options.interface.d.ts.map +1 -0
- package/dist/interfaces/langchain-module-options.interface.js +2 -2
- package/dist/interfaces/langchain-module-options.interface.js.map +1 -0
- package/dist/langchain.module-definition.d.ts +26 -6
- package/dist/langchain.module-definition.d.ts.map +1 -0
- package/dist/langchain.module-definition.js +26 -13
- package/dist/langchain.module-definition.js.map +1 -0
- package/dist/langchain.module.d.ts +60 -3
- package/dist/langchain.module.d.ts.map +1 -0
- package/dist/langchain.module.js +87 -24
- package/dist/langchain.module.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/observe-tool-call.util.d.ts +13 -0
- package/dist/logging/observe-tool-call.util.d.ts.map +1 -0
- package/dist/logging/observe-tool-call.util.js +34 -0
- package/dist/logging/observe-tool-call.util.js.map +1 -0
- package/dist/logging/run-reporter.d.ts +26 -0
- package/dist/logging/run-reporter.d.ts.map +1 -0
- package/dist/logging/run-reporter.js +39 -0
- package/dist/logging/run-reporter.js.map +1 -0
- package/dist/run/index.d.ts +3 -0
- package/dist/run/index.d.ts.map +1 -0
- package/dist/run/index.js +2 -0
- package/dist/run/index.js.map +1 -0
- package/dist/run/run-summary.util.d.ts +31 -0
- package/dist/run/run-summary.util.d.ts.map +1 -0
- package/dist/run/run-summary.util.js +28 -0
- package/dist/run/run-summary.util.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/tool-discovery.service.d.ts +31 -0
- package/dist/tools/tool-discovery.service.d.ts.map +1 -0
- package/dist/tools/tool-discovery.service.js +132 -0
- package/dist/tools/tool-discovery.service.js.map +1 -0
- package/dist/tools/tool-errors.middleware.d.ts +21 -0
- package/dist/tools/tool-errors.middleware.d.ts.map +1 -0
- package/dist/tools/tool-errors.middleware.js +55 -0
- package/dist/tools/tool-errors.middleware.js.map +1 -0
- package/dist/tools/tool-method.util.d.ts +21 -0
- package/dist/tools/tool-method.util.d.ts.map +1 -0
- package/dist/tools/tool-method.util.js +39 -0
- package/dist/tools/tool-method.util.js.map +1 -0
- package/dist/tools/tool-modules.util.d.ts +14 -0
- package/dist/tools/tool-modules.util.d.ts.map +1 -0
- package/dist/tools/tool-modules.util.js +61 -0
- package/dist/tools/tool-modules.util.js.map +1 -0
- package/dist/tools/tool-name.util.d.ts +10 -0
- package/dist/tools/tool-name.util.d.ts.map +1 -0
- package/dist/tools/tool-name.util.js +35 -0
- package/dist/tools/tool-name.util.js.map +1 -0
- package/dist/tools/tool-schema.factory.d.ts +4 -0
- package/dist/tools/tool-schema.factory.d.ts.map +1 -0
- package/dist/tools/tool-schema.factory.js +87 -0
- package/dist/tools/tool-schema.factory.js.map +1 -0
- package/dist/tools/tool.factory.d.ts +9 -0
- package/dist/tools/tool.factory.d.ts.map +1 -0
- package/dist/tools/tool.factory.js +34 -0
- package/dist/tools/tool.factory.js.map +1 -0
- package/lib/agent.ts +257 -0
- package/lib/constants.ts +15 -0
- package/lib/decorators/index.ts +3 -0
- package/lib/decorators/inject-agent.decorator.ts +15 -0
- package/lib/decorators/tool.decorator.ts +86 -0
- package/lib/errors/agent-run.error.ts +29 -0
- package/lib/errors/index.ts +2 -0
- package/lib/errors/messages.ts +158 -0
- package/lib/errors/tool-configuration.error.ts +24 -0
- package/lib/index.ts +26 -0
- package/lib/interfaces/agent-observer.interface.ts +113 -0
- package/lib/interfaces/completed-run.interface.ts +14 -0
- package/lib/interfaces/langchain-module-options.interface.ts +91 -0
- package/lib/langchain.module-definition.ts +36 -0
- package/lib/langchain.module.ts +138 -0
- package/lib/logging/index.ts +2 -0
- package/lib/logging/observe-tool-call.util.ts +59 -0
- package/lib/logging/run-reporter.ts +56 -0
- package/lib/run/index.ts +2 -0
- package/lib/run/run-summary.util.ts +51 -0
- package/lib/tools/index.ts +2 -0
- package/lib/tools/tool-discovery.service.ts +191 -0
- package/lib/tools/tool-errors.middleware.ts +68 -0
- package/lib/tools/tool-method.util.ts +81 -0
- package/lib/tools/tool-modules.util.ts +104 -0
- package/lib/tools/tool-name.util.ts +57 -0
- package/lib/tools/tool-schema.factory.ts +142 -0
- package/lib/tools/tool.factory.ts +56 -0
- package/package.json +112 -91
- package/dist/factories/message.factory.d.ts +0 -7
- package/dist/factories/message.factory.js +0 -28
- package/dist/langchain.service.d.ts +0 -18
- package/dist/langchain.service.js +0 -69
- package/dist/tool-discovery.service.d.ts +0 -11
- package/dist/tool-discovery.service.js +0 -79
- package/dist/types/message.d.ts +0 -69
- package/dist/types/message.js +0 -104
package/lib/agent.ts
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { DiscoveryService } from '@nestjs/core';
|
|
3
|
+
import { createAgent, initChatModel } from 'langchain';
|
|
4
|
+
import {
|
|
5
|
+
AGENT_NAME_TOKEN,
|
|
6
|
+
MODULE_OPTIONS_TOKEN,
|
|
7
|
+
UNNAMED_AGENT,
|
|
8
|
+
} from './langchain.module-definition.js';
|
|
9
|
+
import {
|
|
10
|
+
LangChainModuleOptions,
|
|
11
|
+
ModelOption,
|
|
12
|
+
} from './interfaces/langchain-module-options.interface.js';
|
|
13
|
+
import { CompletedRun } from './interfaces/completed-run.interface.js';
|
|
14
|
+
import {
|
|
15
|
+
ToolDiscoveryService,
|
|
16
|
+
toolErrors,
|
|
17
|
+
unknownTools,
|
|
18
|
+
} from './tools/index.js';
|
|
19
|
+
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
20
|
+
import { LOG_CONTEXT, messageOf, RunReporter } from './logging/index.js';
|
|
21
|
+
import { summariseRun } from './run/index.js';
|
|
22
|
+
import { AgentRunError } from './errors/index.js';
|
|
23
|
+
import {
|
|
24
|
+
agentNotBootstrapped,
|
|
25
|
+
agentPrefix,
|
|
26
|
+
agentRunFailed,
|
|
27
|
+
duplicateAgentName,
|
|
28
|
+
modelNeverReplied,
|
|
29
|
+
modelNotBuilt,
|
|
30
|
+
modelReplyEmpty,
|
|
31
|
+
notAChatModel,
|
|
32
|
+
} from './errors/messages.js';
|
|
33
|
+
|
|
34
|
+
// Derived, not imported from `@langchain/core`. See #52.
|
|
35
|
+
type AgentModel = Parameters<typeof createAgent>[0]['model'];
|
|
36
|
+
type ChatModel = Exclude<AgentModel, string>;
|
|
37
|
+
type Graph = ReturnType<typeof createAgent>;
|
|
38
|
+
type AgentState = Awaited<ReturnType<Graph['invoke']>>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* One agent, registered through `LangChainModule`. Inject it directly for the
|
|
42
|
+
* unnamed agent, or with `@InjectAgent(name)` for a named one.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* constructor(private readonly agent: Agent) {}
|
|
47
|
+
*
|
|
48
|
+
* async ask(question: string): Promise<string> {
|
|
49
|
+
* const { output } = await this.agent.run(question);
|
|
50
|
+
* return output;
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
@Injectable()
|
|
55
|
+
export class Agent implements OnModuleInit {
|
|
56
|
+
// Assembled in `onModuleInit`, once every tool module's providers exist.
|
|
57
|
+
#graph?: Graph;
|
|
58
|
+
// Without a context, since Nest appends the instance's to each call's own.
|
|
59
|
+
private readonly logger = new Logger();
|
|
60
|
+
private readonly prefix: string;
|
|
61
|
+
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
|
+
/** @internal Built by `LangChainModule`, never by hand. */
|
|
67
|
+
constructor(
|
|
68
|
+
@Inject(MODULE_OPTIONS_TOKEN)
|
|
69
|
+
private readonly options: LangChainModuleOptions,
|
|
70
|
+
@Inject(AGENT_NAME_TOKEN)
|
|
71
|
+
private readonly agentName: string,
|
|
72
|
+
private readonly toolDiscovery: ToolDiscoveryService,
|
|
73
|
+
private readonly discovery: DiscoveryService,
|
|
74
|
+
) {
|
|
75
|
+
this.prefix = agentPrefix(agentName);
|
|
76
|
+
this.reporter = new RunReporter({
|
|
77
|
+
logger: this.logger,
|
|
78
|
+
agent: agentName,
|
|
79
|
+
prefix: this.prefix,
|
|
80
|
+
observer: options.observer,
|
|
81
|
+
});
|
|
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
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Called by Nest: builds the agent, once the tool modules' providers exist.
|
|
94
|
+
*
|
|
95
|
+
* @internal
|
|
96
|
+
*/
|
|
97
|
+
async onModuleInit() {
|
|
98
|
+
this.assertUniqueName();
|
|
99
|
+
|
|
100
|
+
const tools = await this.toolDiscovery.getToolsFromModules(
|
|
101
|
+
this.options.tools ?? [],
|
|
102
|
+
this.reporter,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
this.#graph = createAgent({
|
|
106
|
+
model: await this.resolveModel(this.options.model),
|
|
107
|
+
tools,
|
|
108
|
+
systemPrompt: this.options.systemPrompt,
|
|
109
|
+
// The first wraps the next: `toolErrors` sits right around the tool.
|
|
110
|
+
middleware: [unknownTools(this.reporter), toolErrors(this.reporter)],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.logger.log(
|
|
114
|
+
`${this.prefix}ready with ${tools.length} tool${tools.length === 1 ? '' : 's'}`,
|
|
115
|
+
LOG_CONTEXT,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Every registration provides its own name, so a name found twice in the
|
|
120
|
+
// container is two agents behind one token.
|
|
121
|
+
private assertUniqueName(): void {
|
|
122
|
+
const registered = this.discovery
|
|
123
|
+
.getProviders()
|
|
124
|
+
.filter(
|
|
125
|
+
({ token, instance }) =>
|
|
126
|
+
token === AGENT_NAME_TOKEN && instance === this.agentName,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
if (registered.length > 1) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
duplicateAgentName(
|
|
132
|
+
this.agentName === UNNAMED_AGENT ? undefined : this.agentName,
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build the model from its configuration, or hand back the instance the
|
|
140
|
+
* caller already built. Recognised by the members `createAgent` checks,
|
|
141
|
+
* never by `instanceof`, which two copies of `@langchain/core` break.
|
|
142
|
+
* See #52.
|
|
143
|
+
*
|
|
144
|
+
* @param option The `model` option.
|
|
145
|
+
* @returns The model `createAgent` receives.
|
|
146
|
+
*/
|
|
147
|
+
private async resolveModel(option: ModelOption): Promise<AgentModel> {
|
|
148
|
+
// The types forbid a primitive, an untyped configuration does not. See #236.
|
|
149
|
+
const given: unknown = option;
|
|
150
|
+
|
|
151
|
+
if (typeof given !== 'object' || given === null) {
|
|
152
|
+
throw new Error(notAChatModel(this.prefix));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!('invoke' in option)) {
|
|
156
|
+
const { model, ...fields } = option;
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
return await initChatModel(model, fields);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error(modelNotBuilt(this.prefix, error), { cause: error });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (
|
|
166
|
+
typeof option.invoke !== 'function' ||
|
|
167
|
+
typeof option.bindTools !== 'function' ||
|
|
168
|
+
typeof option._streamResponseChunks !== 'function'
|
|
169
|
+
) {
|
|
170
|
+
throw new Error(notAChatModel(this.prefix));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The same object, typed from the declarations `createAgent` reads.
|
|
174
|
+
return option as unknown as ChatModel;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Runs the agent to completion on one user message. Every failure is thrown
|
|
179
|
+
* as an `AgentRunError`, the original error in its `cause`.
|
|
180
|
+
*
|
|
181
|
+
* @param input The user's message.
|
|
182
|
+
* @returns The model's answer, the tools it called and what the run cost.
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const { output, tools } = await agent.run('What is 6 times 7?');
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
async run(input: string): Promise<CompletedRun> {
|
|
189
|
+
const startedAt = Date.now();
|
|
190
|
+
const durationMs = () => Date.now() - startedAt;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const { answer, messages } = await this.complete(input);
|
|
194
|
+
const finished = {
|
|
195
|
+
durationMs: durationMs(),
|
|
196
|
+
...summariseRun(messages),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
this.reporter.notify((observer) =>
|
|
200
|
+
observer.onRunFinish?.({ agent: this.agentName, ...finished }),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
return { status: 'completed', output: answer, ...finished };
|
|
204
|
+
} catch (thrown) {
|
|
205
|
+
const error =
|
|
206
|
+
thrown instanceof AgentRunError
|
|
207
|
+
? thrown
|
|
208
|
+
: new AgentRunError(
|
|
209
|
+
agentRunFailed(this.prefix, thrown),
|
|
210
|
+
this.agentName,
|
|
211
|
+
{ cause: thrown },
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
this.reporter.notify((observer) =>
|
|
215
|
+
observer.onRunError?.({
|
|
216
|
+
agent: this.agentName,
|
|
217
|
+
durationMs: durationMs(),
|
|
218
|
+
error,
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async complete(
|
|
227
|
+
input: string,
|
|
228
|
+
): Promise<{ answer: string; messages: AgentState['messages'] }> {
|
|
229
|
+
if (!this.#graph) {
|
|
230
|
+
throw new AgentRunError(
|
|
231
|
+
agentNotBootstrapped(this.prefix),
|
|
232
|
+
this.agentName,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const { messages }: AgentState = await this.#graph.invoke(
|
|
237
|
+
{ messages: [{ role: 'user', content: input }] },
|
|
238
|
+
{ callbacks: [this.modelErrors, ...(this.options.callbacks ?? [])] },
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const last = messages[messages.length - 1];
|
|
242
|
+
|
|
243
|
+
if (!last || last.type !== 'ai') {
|
|
244
|
+
throw new AgentRunError(
|
|
245
|
+
modelNeverReplied(this.prefix, last?.type),
|
|
246
|
+
this.agentName,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// `text`, not `content`: block answers are an array.
|
|
251
|
+
if (!last.text) {
|
|
252
|
+
throw new AgentRunError(modelReplyEmpty(this.prefix), this.agentName);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { answer: last.text, messages };
|
|
256
|
+
}
|
|
257
|
+
}
|
package/lib/constants.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Namespaced like the other tokens, so another library's marks are not read.
|
|
2
|
+
export const TOOL_METADATA = 'nestjs-langchain:tool';
|
|
3
|
+
export const TOOL_PARAMS_METADATA = 'nestjs-langchain:tool-params';
|
|
4
|
+
|
|
5
|
+
// Emitted by `emitDecoratorMetadata` on every decorated method.
|
|
6
|
+
export const PARAM_TYPES_METADATA = 'design:paramtypes';
|
|
7
|
+
|
|
8
|
+
// The keys `@Module()` writes, restated: `@nestjs/common/constants` is
|
|
9
|
+
// internal. See #97.
|
|
10
|
+
export const MODULE_METADATA_KEYS = [
|
|
11
|
+
'imports',
|
|
12
|
+
'providers',
|
|
13
|
+
'controllers',
|
|
14
|
+
'exports',
|
|
15
|
+
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Inject } from '@nestjs/common';
|
|
2
|
+
import { getAgentToken } from '../langchain.module-definition.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Injects the agent registered under `name`. The agent registered without a
|
|
6
|
+
* name is injected as `Agent`, with no decorator.
|
|
7
|
+
*
|
|
8
|
+
* @param name The `name` the agent was registered with.
|
|
9
|
+
* @returns The parameter decorator.
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* constructor(@InjectAgent('MATH_AGENT') private readonly math: Agent) {}
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export const InjectAgent = (name: string) => Inject(getAgentToken(name));
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { SetMetadata } from '@nestjs/common';
|
|
2
|
+
import type { ZodType } from 'zod';
|
|
3
|
+
import { TOOL_METADATA, TOOL_PARAMS_METADATA } from '../constants.js';
|
|
4
|
+
import { toolParamOutsideMethod } from '../errors/messages.js';
|
|
5
|
+
|
|
6
|
+
/** The options of `@Tool()`. */
|
|
7
|
+
export interface ToolOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The name the model calls, the method name by default. Matches
|
|
10
|
+
* `/^[a-zA-Z0-9_-]{1,64}$/` and does not start with `extract-`, which
|
|
11
|
+
* LangChain reserves for structured output.
|
|
12
|
+
*/
|
|
13
|
+
name?: string;
|
|
14
|
+
/** What the tool does, which the model reads to decide when to call it. */
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Exposes a provider method as a tool to the agents whose `tools` list its
|
|
20
|
+
* module. A method decorator only: discovery never reads one placed on a
|
|
21
|
+
* class.
|
|
22
|
+
*
|
|
23
|
+
* @param options The tool's description and, optionally, its name.
|
|
24
|
+
* @returns The method decorator.
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* \@Tool({ description: 'Doubles a number.' })
|
|
28
|
+
* double(@ToolParam({ name: 'x' }) x: number): number {
|
|
29
|
+
* return x * 2;
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const Tool = (options: ToolOptions): MethodDecorator =>
|
|
34
|
+
SetMetadata(TOOL_METADATA, options);
|
|
35
|
+
|
|
36
|
+
/** The options of `@ToolParam()`. */
|
|
37
|
+
export interface ToolParamOptions {
|
|
38
|
+
/** The argument's name in the schema the model fills in. */
|
|
39
|
+
name: string;
|
|
40
|
+
/** What the argument means, read by the model. */
|
|
41
|
+
description?: string;
|
|
42
|
+
/** Required unless the signature resolves to string, number or boolean. */
|
|
43
|
+
schema?: ZodType;
|
|
44
|
+
/** Lets the model leave the argument out; declare the parameter `x?: T`. */
|
|
45
|
+
optional?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** What `@ToolParam()` records for one parameter. */
|
|
49
|
+
export interface ToolParamMetadata extends ToolParamOptions {
|
|
50
|
+
/** The parameter's position in the method's signature. */
|
|
51
|
+
index: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Declares a parameter of a `@Tool()` method as an argument of the tool.
|
|
56
|
+
*
|
|
57
|
+
* @param options The argument's name and, optionally, its description,
|
|
58
|
+
* schema and whether it is optional.
|
|
59
|
+
* @returns The parameter decorator.
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* const Tags = z.array(z.string());
|
|
63
|
+
*
|
|
64
|
+
* count(@ToolParam({ name: 'tags', schema: Tags }) tags: string[]): number {
|
|
65
|
+
* return tags.length;
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function ToolParam(options: ToolParamOptions): ParameterDecorator {
|
|
70
|
+
return (target, propertyKey, parameterIndex) => {
|
|
71
|
+
if (propertyKey === undefined) {
|
|
72
|
+
throw new Error(toolParamOutsideMethod());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Own metadata only: a subclass redeclaring a method starts afresh.
|
|
76
|
+
const existing: ToolParamMetadata[] =
|
|
77
|
+
Reflect.getOwnMetadata(TOOL_PARAMS_METADATA, target, propertyKey) ?? [];
|
|
78
|
+
|
|
79
|
+
Reflect.defineMetadata(
|
|
80
|
+
TOOL_PARAMS_METADATA,
|
|
81
|
+
[...existing, { ...options, index: parameterIndex }],
|
|
82
|
+
target,
|
|
83
|
+
propertyKey,
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A run of an agent failed. The base class of every run failure: everything
|
|
3
|
+
* `run()` throws is one, with the original error, if any, in `cause`.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* try {
|
|
8
|
+
* await agent.run(question);
|
|
9
|
+
* } catch (error) {
|
|
10
|
+
* if (error instanceof AgentRunError) {
|
|
11
|
+
* console.error(error.agent, error.cause);
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class AgentRunError extends Error {
|
|
17
|
+
/** The error that made the run fail, when it came from elsewhere. */
|
|
18
|
+
declare readonly cause?: unknown;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
message: string,
|
|
22
|
+
/** `'default'` when the agent was registered without a name. */
|
|
23
|
+
readonly agent: string,
|
|
24
|
+
options?: { cause?: unknown },
|
|
25
|
+
) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = 'AgentRunError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Message construction, kept together so the wording stays consistent.
|
|
2
|
+
import { UNNAMED_AGENT } from '../langchain.module-definition.js';
|
|
3
|
+
|
|
4
|
+
// `'MATH '` for a named agent, empty for the unnamed one: "the MATH agent".
|
|
5
|
+
export const agentPrefix = (agent: string): string =>
|
|
6
|
+
agent === UNNAMED_AGENT ? '' : `${agent} `;
|
|
7
|
+
|
|
8
|
+
export const toolProblems = (prefix: string, problems: string[]): string =>
|
|
9
|
+
`nestjs-langchain found ${problems.length} ` +
|
|
10
|
+
`problem${problems.length === 1 ? '' : 's'} ` +
|
|
11
|
+
`with the tools of the ${prefix}agent:\n` +
|
|
12
|
+
problems.map((problem) => ` - ${problem}`).join('\n');
|
|
13
|
+
|
|
14
|
+
export const notAToolModule = (entry: string): string =>
|
|
15
|
+
`${entry} is listed in \`tools\` but is not a module in the Nest context. ` +
|
|
16
|
+
'Pass a class decorated with @Module, imported alongside LangChainModule.';
|
|
17
|
+
|
|
18
|
+
export const toolModuleNotImported = (entry: string): string =>
|
|
19
|
+
`${entry} is listed in \`tools\` but is not imported into the Nest context. ` +
|
|
20
|
+
'Add it to the `imports` of the module registering the agent.';
|
|
21
|
+
|
|
22
|
+
export const severalToolModuleInstances = (
|
|
23
|
+
entry: string,
|
|
24
|
+
count: number,
|
|
25
|
+
): string =>
|
|
26
|
+
`${entry} is listed in \`tools\`, but the Nest context holds ${count} ` +
|
|
27
|
+
'instances of it, so their tools cannot be told apart. Import it once, or ' +
|
|
28
|
+
'give each configuration its own module class.';
|
|
29
|
+
|
|
30
|
+
export const toolModuleWithoutTools = (entry: string): string =>
|
|
31
|
+
`${entry} is listed in \`tools\` but declares no @Tool method. ` +
|
|
32
|
+
'Tools are read from the providers a module declares, not from the ' +
|
|
33
|
+
'modules it imports.';
|
|
34
|
+
|
|
35
|
+
export const toolParamWithoutTool = (where: string): string =>
|
|
36
|
+
`${where} has @ToolParam but no @Tool, so the model never sees it. ` +
|
|
37
|
+
'Add @Tool({ description }) to expose it, or remove the @ToolParam.';
|
|
38
|
+
|
|
39
|
+
export const toolParamOutsideMethod = (): string =>
|
|
40
|
+
'@ToolParam can only be used on method parameters.';
|
|
41
|
+
|
|
42
|
+
export const toolOnNonSingleton = (
|
|
43
|
+
provider: string,
|
|
44
|
+
scope: 'request' | 'transient' | 'request-dependency',
|
|
45
|
+
methods: string[],
|
|
46
|
+
): string =>
|
|
47
|
+
`${provider} ${
|
|
48
|
+
{
|
|
49
|
+
request: 'is request-scoped',
|
|
50
|
+
transient: 'is transient',
|
|
51
|
+
'request-dependency': 'depends on a request-scoped provider',
|
|
52
|
+
}[scope]
|
|
53
|
+
}, so its @Tool methods (${methods.join(', ')}) cannot be called by an ` +
|
|
54
|
+
'agent, which is a singleton. Make it a default-scoped provider.';
|
|
55
|
+
|
|
56
|
+
export const duplicateToolName = (
|
|
57
|
+
name: string,
|
|
58
|
+
first: string,
|
|
59
|
+
second: string,
|
|
60
|
+
): string =>
|
|
61
|
+
`Two tools are named "${name}": ${first} and ${second}. ` +
|
|
62
|
+
'Give one of them a `name` in @Tool().';
|
|
63
|
+
|
|
64
|
+
export const invalidToolName = (
|
|
65
|
+
where: string,
|
|
66
|
+
name: string,
|
|
67
|
+
pattern: string,
|
|
68
|
+
fromMethodName: boolean,
|
|
69
|
+
): string =>
|
|
70
|
+
`${where}: "${name}" is not a valid tool name, providers match ${pattern}` +
|
|
71
|
+
(fromMethodName ? '. Pass a `name` to @Tool().' : '.');
|
|
72
|
+
|
|
73
|
+
export const reservedToolName = (
|
|
74
|
+
where: string,
|
|
75
|
+
name: string,
|
|
76
|
+
prefix: string,
|
|
77
|
+
): string =>
|
|
78
|
+
`${where}: "${name}" starts with "${prefix}", a prefix LangChain reserves ` +
|
|
79
|
+
'for structured output, so the agent would never run it. Give it another ' +
|
|
80
|
+
'`name` in @Tool().';
|
|
81
|
+
|
|
82
|
+
export const cannotInferSchema = (
|
|
83
|
+
where: string,
|
|
84
|
+
param: string,
|
|
85
|
+
declared: string,
|
|
86
|
+
): string =>
|
|
87
|
+
`${where}: cannot infer a schema for "${param}" declared as ${declared}. ` +
|
|
88
|
+
'Pass a `schema` to @ToolParam, or use string, number or boolean.';
|
|
89
|
+
|
|
90
|
+
export const schemaContradictsSignature = (
|
|
91
|
+
where: string,
|
|
92
|
+
param: string,
|
|
93
|
+
declared: string[],
|
|
94
|
+
expected: string,
|
|
95
|
+
): string =>
|
|
96
|
+
`${where}: the schema for "${param}" describes ${declared.join(' | ')} ` +
|
|
97
|
+
`but the signature declares ${expected}.`;
|
|
98
|
+
|
|
99
|
+
export const noJsonSchemaForm = (
|
|
100
|
+
where: string,
|
|
101
|
+
param: string,
|
|
102
|
+
reason: string,
|
|
103
|
+
): 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.';
|
|
107
|
+
|
|
108
|
+
export const duplicateParamName = (
|
|
109
|
+
where: string,
|
|
110
|
+
name: string,
|
|
111
|
+
first: number,
|
|
112
|
+
second: number,
|
|
113
|
+
): string =>
|
|
114
|
+
`${where}: parameters ${first + 1} and ${second + 1} are both named "${name}". ` +
|
|
115
|
+
'Give each @ToolParam its own name.';
|
|
116
|
+
|
|
117
|
+
export const notAChatModel = (prefix: string): string =>
|
|
118
|
+
`The ${prefix}agent's \`model\` is neither a LangChain chat model nor a ` +
|
|
119
|
+
"`{ model: 'provider:name' }` configuration. Pass a chat model instance " +
|
|
120
|
+
'(e.g. `new ChatOpenAI(...)`) or a configuration.';
|
|
121
|
+
|
|
122
|
+
export const modelNotBuilt = (prefix: string, cause: unknown): string =>
|
|
123
|
+
`The ${prefix}agent's model could not be built: ` +
|
|
124
|
+
(cause instanceof Error ? cause.message : String(cause));
|
|
125
|
+
|
|
126
|
+
export const agentNotBootstrapped = (prefix: string): string =>
|
|
127
|
+
`The ${prefix}agent ran before the application bootstrapped. ` +
|
|
128
|
+
'Call `app.init()` or `app.listen()` first.';
|
|
129
|
+
|
|
130
|
+
export const duplicateAgentName = (name: string | undefined): string =>
|
|
131
|
+
name === undefined
|
|
132
|
+
? 'Two agents are registered without a name. ' +
|
|
133
|
+
'Give all but one a `name` and inject them with @InjectAgent().'
|
|
134
|
+
: `Two agents are registered as "${name}". ` +
|
|
135
|
+
'Give each LangChainModule registration its own `name`.';
|
|
136
|
+
|
|
137
|
+
export const reservedAgentName = (name: string): string =>
|
|
138
|
+
name === ''
|
|
139
|
+
? "An agent's `name` cannot be empty. " +
|
|
140
|
+
'Omit it to register the unnamed agent.'
|
|
141
|
+
: `"${name}" cannot be an agent's \`name\`: it stands for the agent ` +
|
|
142
|
+
'registered without one. Pick another name.';
|
|
143
|
+
|
|
144
|
+
export const agentRunFailed = (prefix: string, cause: unknown): string =>
|
|
145
|
+
`The ${prefix}agent's run failed: ` +
|
|
146
|
+
(cause instanceof Error ? cause.message : String(cause));
|
|
147
|
+
|
|
148
|
+
export const modelNeverReplied = (
|
|
149
|
+
prefix: string,
|
|
150
|
+
last: string | undefined,
|
|
151
|
+
): string =>
|
|
152
|
+
`The ${prefix}agent's loop ended before the model replied. ` +
|
|
153
|
+
`The last message was ${last ? `a ${last} message` : 'never produced'}.`;
|
|
154
|
+
|
|
155
|
+
export const modelReplyEmpty = (prefix: string): string =>
|
|
156
|
+
`The ${prefix}agent's model replied with no text content.`;
|
|
157
|
+
|
|
158
|
+
export const unknownTool = (): string => 'not a tool of this agent';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { agentPrefix, toolProblems } from './messages.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* An agent's tools are misconfigured, which fails the bootstrap. Lists every
|
|
5
|
+
* problem found while assembling them, not only the first.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const app = await Test.createTestingModule({ imports: [AppModule] }).compile();
|
|
10
|
+
*
|
|
11
|
+
* await expect(app.init()).rejects.toThrow(ToolConfigurationError);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export class ToolConfigurationError extends Error {
|
|
15
|
+
constructor(
|
|
16
|
+
/** One message per problem, in the order they were found. */
|
|
17
|
+
readonly problems: string[],
|
|
18
|
+
/** `'default'` when the agent was registered without a name. */
|
|
19
|
+
readonly agent: string,
|
|
20
|
+
) {
|
|
21
|
+
super(toolProblems(agentPrefix(agent), problems));
|
|
22
|
+
this.name = 'ToolConfigurationError';
|
|
23
|
+
}
|
|
24
|
+
}
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The public surface, the one path `exports` opens. Named, so each export is
|
|
2
|
+
// a decision. See #82.
|
|
3
|
+
export { LangChainModule } from './langchain.module.js';
|
|
4
|
+
export { getAgentToken } from './langchain.module-definition.js';
|
|
5
|
+
export { Agent } from './agent.js';
|
|
6
|
+
export { AgentRunError, ToolConfigurationError } from './errors/index.js';
|
|
7
|
+
export { InjectAgent, Tool, ToolParam } from './decorators/index.js';
|
|
8
|
+
export type { ToolOptions, ToolParamOptions } from './decorators/index.js';
|
|
9
|
+
export type {
|
|
10
|
+
AgentObserver,
|
|
11
|
+
ModelErrorEvent,
|
|
12
|
+
RunErrorEvent,
|
|
13
|
+
RunFinishEvent,
|
|
14
|
+
ToolEndEvent,
|
|
15
|
+
ToolErrorEvent,
|
|
16
|
+
ToolStartEvent,
|
|
17
|
+
} from './interfaces/agent-observer.interface.js';
|
|
18
|
+
export type { CompletedRun } from './interfaces/completed-run.interface.js';
|
|
19
|
+
export type {
|
|
20
|
+
AgentCallback,
|
|
21
|
+
LangChainModuleOptions,
|
|
22
|
+
LangChainOptionsFactory,
|
|
23
|
+
ModelConfig,
|
|
24
|
+
ModelOption,
|
|
25
|
+
ToolModule,
|
|
26
|
+
} from './interfaces/langchain-module-options.interface.js';
|