nestjs-langchain 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Martin-Bruel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,254 @@
1
+ <p align="center">
2
+ <a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
3
+ </p>
4
+
5
+ <p align="center">
6
+ A <a href="https://nestjs.com/">Nest</a> module wrapper for building AI agent with <a href="https://www.langchain.com/">LangChain</a>.
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/Martin-Bruel/nestjs-langchain/pulse"><img src="https://img.shields.io/github/commit-activity/m/Martin-Bruel/nestjs-langchain" alt="GitHub commit activity"></a>
11
+ <a href="https://github.com/Martin-Bruel/nestjs-langchain/graphs/contributors"><img src="https://img.shields.io/github/contributors/Martin-Bruel/nestjs-langchain" alt="Contributors"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/Martin-Bruel/nestjs-langchain">
16
+ <a href="https://github.com/Martin-Bruel/nestjs-langchain/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/Martin-Bruel/nestjs-langchain.svg"></a>
17
+ <a href="https://github.com/Martin-Bruel/nestjs-langchain/pulls"><img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/Martin-Bruel/nestjs-langchain"></a>
18
+ </p>
19
+ </p>
20
+
21
+ **Table of Contents**
22
+
23
+ - [Description](#description)
24
+ - [Installation](#installation)
25
+ - [Quick start](#quick-start)
26
+ - [Defining tools](#defining-tools)
27
+ - [Multi-agent support](#multi-agent-support)
28
+ - [Async configuration](#async-configuration)
29
+ - [Contributing](#contributing)
30
+ - [License](#license)
31
+
32
+ ## Description
33
+
34
+ **nestjs-langchain** is a powerful, decorator-driven wrapper that integrates LangChain agents seamlessly into the NestJS ecosystem. It allows you to transform standard NestJS services into AI tools and manage multiple isolated agents with distinct roles and configurations.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install --save nestjs-langchain langchain @langchain/<ai-provider>
40
+ ```
41
+
42
+ Having troubles configuring `nestjs-langchain`? Clone this repository and `cd` in a sample:
43
+
44
+ ```bash
45
+ cd samples/chat
46
+ npm install
47
+ npm run start
48
+ ```
49
+
50
+ ## Quick start
51
+
52
+ ### 1. Register the module
53
+
54
+ You can register the `LangChainModule` in your `AppModule` or any specific feature module.
55
+
56
+ ```ts
57
+ import { LangChainModule } from 'nestjs-langchain';
58
+
59
+ @Module({
60
+ imports: [
61
+ LangChainModule.register({
62
+ model: {
63
+ model: 'your-model-name', // Syntax: provider:model-name
64
+ apiKey: 'your-api-key',
65
+ },
66
+ systemPrompt: 'your-system-prompt',
67
+ }),
68
+ ],
69
+ controllers: [AppController],
70
+ providers: [AppService],
71
+ })
72
+ export class AppModule {}
73
+ ```
74
+
75
+ > **_NOTE:_** The model property allows you to select your AI engine based on the provider (OpenAI, Anthropic, Google, etc.). You can find the list of all available integrations here:
76
+ > 👉 [LangChain Chat Integrations](https://docs.langchain.com/oss/javascript/integrations/chat/index)
77
+
78
+ ### 2. Usage
79
+
80
+ Once registered, simply inject the `LangChainService` to interact with your agent.
81
+
82
+ ```ts
83
+ @Injectable()
84
+ export class AppService {
85
+ constructor(private readonly agent: LangChainService) {}
86
+
87
+ async ask(question: string) {
88
+ return await this.agent.run(question);
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Defining Tools
94
+
95
+ Tools allow your AI agents to interact with the real world, fetch live data, and perform complex tasks, significantly enriching their responses beyond their static training data.
96
+
97
+ ### 1. Define the tool
98
+
99
+ You can easily turn any NestJS service method into an AI tool using the @Tool() decorator.
100
+
101
+ ```ts
102
+ import { Tool, ToolParam } from 'nestjs-langchain';
103
+
104
+ @Injectable()
105
+ export class MathService {
106
+ @Tool({ description: 'Adds two numbers together.' })
107
+ add(
108
+ @ToolParam({
109
+ name: 'a',
110
+ description: 'The first number to add.',
111
+ type: 'number',
112
+ })
113
+ a: number,
114
+ @ToolParam({
115
+ name: 'b',
116
+ description: 'The second number to add.',
117
+ type: 'number',
118
+ })
119
+ b: number,
120
+ ): number {
121
+ return a + b;
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### 2. Attach tool to the agent
127
+
128
+ To make tools available to your agent, simply add the corresponding module to the tools array option of the LangChainModule during the registration.
129
+
130
+ ```ts
131
+ import { LangChainModule } from 'nestjs-langchain';
132
+ import { MathModule } from './math/math.module';
133
+
134
+ @Module({
135
+ imports: [
136
+ LangChainModule.register({
137
+ model: {
138
+ model: 'your-model-name', // Syntax: provider:model-name
139
+ apiKey: 'your-api-key',
140
+ },
141
+ systemPrompt: 'your-system-prompt',
142
+ tools: [MathModule],
143
+ }),
144
+ MathModule,
145
+ ],
146
+ controllers: [AppController],
147
+ providers: [AppService],
148
+ })
149
+ export class AppModule {}
150
+ ```
151
+
152
+ > **_NOTE:_** Any module passed to the tools array of LangChainModule must also be imported into the NestJS context (usually in the same @Module decorator). This ensures that the services containing your @Tool() decorators are correctly instantiated and managed by the NestJS dependency injection system.
153
+
154
+ ## Multi-Agent Support
155
+
156
+ If you need multiple agents with different roles in the same application, you can register them with unique names. Each agent is a distinct instance with its own configuration, system prompt, and specific set of tools. This isolation prevents "tool confusion" where an agent might try to use irrelevant tools for a given task, improving accuracy and reducing token costs.
157
+
158
+ For example, you can have a "Support Agent" with access to your database and a "Math Agent" with access to calculation tools.
159
+
160
+ ### 1. Register a specific agent
161
+
162
+ To register a specific agent you must define a name:
163
+
164
+ ```ts
165
+ import { LangChainModule } from 'nestjs-langchain';
166
+ import { MathModule } from './math/math.module';
167
+ import { MongoModule } from './mongo/mongo.module';
168
+
169
+ @Module({
170
+ imports: [
171
+ LangChainModule.register({
172
+ name: 'MATH_AGENT',
173
+ model: {
174
+ model: 'your-model-name', // Syntax: provider:model-name
175
+ apiKey: 'your-api-key',
176
+ },
177
+ systemPrompt: 'your-system-prompt',
178
+ tools: [MathModule],
179
+ }),
180
+ LangChainModule.register({
181
+ name: 'MONGO_AGENT',
182
+ model: {
183
+ model: 'your-model-name', // Syntax: provider:model-name
184
+ apiKey: 'your-api-key',
185
+ },
186
+ systemPrompt: 'your-system-prompt',
187
+ tools: [MongoModule],
188
+ }),
189
+ MathModule,
190
+ MongoModule,
191
+ ],
192
+ controllers: [AppController],
193
+ providers: [AppService],
194
+ })
195
+ export class AppModule {}
196
+ ```
197
+
198
+ ### 2. Use a specific agent
199
+
200
+ o use a specific agent in your services, use the @InjectAgent() decorator with the corresponding name:
201
+
202
+ ```ts
203
+ @Injectable()
204
+ export class AppService {
205
+ constructor(
206
+ @InjectAgent('MATH_AGENT') private readonly mathAgent: LangChainService,
207
+ @InjectAgent('SUPPORT_AGENT')
208
+ private readonly supportAgent: LangChainService,
209
+ ) {}
210
+
211
+ async solveProblem(query: string) {
212
+ return this.mathAgent.run(query);
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Async Configuration
218
+
219
+ To inject configuration from a ConfigService or other providers, use registerAsync:
220
+
221
+ ```ts
222
+ import { LangChainModule } from 'nestjs-langchain';
223
+ import { MathModule } from './math/math.module';
224
+ import { MongoModule } from './mongo/mongo.module';
225
+
226
+ @Module({
227
+ imports: [
228
+ LangChainModule.registerAsync({
229
+ imports: [ConfigModule],
230
+ useFactory: (configService: ConfigService) => ({
231
+ model: {
232
+ model: 'openai:gpt-5-mini',
233
+ apiKey: configService.get<string>('OPENAI_API_KEY'),
234
+ },
235
+ systemPrompt: 'your-system-prompt',
236
+ tools: [MongoModule],
237
+ }),
238
+ inject: [ConfigService],
239
+ name: 'MONGO',
240
+ }),
241
+ ],
242
+ })
243
+ export class AppModule {}
244
+ ```
245
+
246
+ > **_NOTE:_** The name property is required at the root of registerAsync because it defines the injection token used by @InjectAgent(). It cannot be determined dynamically inside the factory.
247
+
248
+ ## Contributing
249
+
250
+ All types of contributions are encouraged and valued. See the Contributing guidelines, the community looks forward to your contributions!
251
+
252
+ ## License
253
+
254
+ This project is released under the under terms of the MIT License.
@@ -0,0 +1,2 @@
1
+ export * from './inject-agent.decorator';
2
+ export * from './tool.decorator';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./inject-agent.decorator"), exports);
18
+ __exportStar(require("./tool.decorator"), exports);
@@ -0,0 +1 @@
1
+ export declare const InjectAgent: (name: string) => PropertyDecorator & ParameterDecorator;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InjectAgent = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const langchain_module_definition_1 = require("../langchain.module-definition");
6
+ const InjectAgent = (name) => (0, common_1.Inject)((0, langchain_module_definition_1.getAgentToken)(name));
7
+ exports.InjectAgent = InjectAgent;
@@ -0,0 +1,13 @@
1
+ export declare const TOOL_METADATA = "TOOL_METADATA";
2
+ export declare const TOOL_PARAM_METADATA = "TOOL_PARAM_METADATA";
3
+ export interface ToolOptions {
4
+ description: string;
5
+ }
6
+ export declare const Tool: (options: ToolOptions) => import("@nestjs/common").CustomDecorator<string>;
7
+ export interface ToolParamOptions {
8
+ name: string;
9
+ type: 'string' | 'number' | 'boolean' | 'object';
10
+ description: string;
11
+ }
12
+ export declare const TOOL_PARAMS_METADATA = "LANGCHAIN_TOOL_PARAMS";
13
+ export declare function ToolParam(options: ToolParamOptions): ParameterDecorator;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOOL_PARAMS_METADATA = exports.Tool = exports.TOOL_PARAM_METADATA = exports.TOOL_METADATA = void 0;
4
+ exports.ToolParam = ToolParam;
5
+ const common_1 = require("@nestjs/common");
6
+ exports.TOOL_METADATA = 'TOOL_METADATA';
7
+ exports.TOOL_PARAM_METADATA = 'TOOL_PARAM_METADATA';
8
+ const Tool = (options) => (0, common_1.SetMetadata)(exports.TOOL_METADATA, options);
9
+ exports.Tool = Tool;
10
+ exports.TOOL_PARAMS_METADATA = 'LANGCHAIN_TOOL_PARAMS';
11
+ function ToolParam(options) {
12
+ return (target, propertyKey, parameterIndex) => {
13
+ if (propertyKey === undefined) {
14
+ throw new Error('ToolParam can only be used on method parameters');
15
+ }
16
+ const existingParams = Reflect.getMetadata(exports.TOOL_PARAMS_METADATA, target, propertyKey) || [];
17
+ // On enregistre l'index pour savoir quel argument remplir plus tard
18
+ existingParams.push({ ...options, index: parameterIndex });
19
+ Reflect.defineMetadata(exports.TOOL_PARAMS_METADATA, existingParams, target, propertyKey);
20
+ };
21
+ }
@@ -0,0 +1,7 @@
1
+ import { AIMessage, Message, ToolMessage } from '../types/message';
2
+ export declare class MessageFactory {
3
+ static fromLangChain(lcMessage: any): Message | AIMessage | ToolMessage;
4
+ private static mapBaseMessage;
5
+ private static mapAIMessage;
6
+ private static mapToolMessage;
7
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageFactory = void 0;
4
+ const message_1 = require("../types/message");
5
+ class MessageFactory {
6
+ static fromLangChain(lcMessage) {
7
+ if (lcMessage.name === 'model') {
8
+ return this.mapAIMessage(lcMessage);
9
+ }
10
+ else if (lcMessage.tool_call_id) {
11
+ return this.mapToolMessage(lcMessage);
12
+ }
13
+ return this.mapBaseMessage(lcMessage);
14
+ }
15
+ static mapBaseMessage(lcMessage) {
16
+ const message = new message_1.Message(lcMessage.id, lcMessage.name, lcMessage.content, message_1.MessageType.UNKNOWN);
17
+ return message;
18
+ }
19
+ static mapAIMessage(lcMessage) {
20
+ return new message_1.AIMessage(lcMessage.id, lcMessage.name, lcMessage.content, lcMessage.response_metadata, lcMessage.usage_metadata, lcMessage.tool_calls.map((tc) => {
21
+ return new message_1.ToolCall(tc.id, tc.name, tc.args);
22
+ }), lcMessage.invalid_tool_calls);
23
+ }
24
+ static mapToolMessage(lcMessage) {
25
+ return new message_1.ToolMessage(lcMessage.id, lcMessage.name, lcMessage.content, lcMessage.tool_call_id);
26
+ }
27
+ }
28
+ exports.MessageFactory = MessageFactory;
@@ -0,0 +1,3 @@
1
+ export * from './langchain.module';
2
+ export * from './decorators';
3
+ export * from './langchain.service';
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./langchain.module"), exports);
18
+ __exportStar(require("./decorators"), exports);
19
+ __exportStar(require("./langchain.service"), exports);
@@ -0,0 +1,14 @@
1
+ import { Type } from '@nestjs/common/interfaces/type.interface';
2
+ export type Model = {
3
+ model: string;
4
+ apiKey: string;
5
+ temperature?: number;
6
+ maxTokens?: number;
7
+ timeout?: number;
8
+ maxRetries?: number;
9
+ };
10
+ export interface LangChainModuleOptions {
11
+ model: Model;
12
+ systemPrompt?: string;
13
+ tools?: Type<any>[];
14
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import { LangChainModuleOptions } from './interfaces/langchain-module-options.interface';
2
+ export declare const ConfigurableModuleClass: import("@nestjs/common").ConfigurableModuleCls<LangChainModuleOptions, "register", "create", {
3
+ name: string;
4
+ }>, MODULE_OPTIONS_TOKEN: string | symbol, OPTIONS_TYPE: LangChainModuleOptions & Partial<{
5
+ name: string;
6
+ }>, ASYNC_OPTIONS_TYPE: import("@nestjs/common").ConfigurableModuleAsyncOptions<LangChainModuleOptions, "create"> & Partial<{
7
+ name: string;
8
+ }>;
9
+ export declare const getAgentToken: (name: string) => string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var _a;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.getAgentToken = exports.ASYNC_OPTIONS_TYPE = exports.OPTIONS_TYPE = exports.MODULE_OPTIONS_TOKEN = exports.ConfigurableModuleClass = void 0;
5
+ const common_1 = require("@nestjs/common");
6
+ _a = new common_1.ConfigurableModuleBuilder()
7
+ .setExtras({ name: 'default' }, (definition, extras) => ({
8
+ ...definition,
9
+ tag: extras.name,
10
+ }))
11
+ .build(), exports.ConfigurableModuleClass = _a.ConfigurableModuleClass, exports.MODULE_OPTIONS_TOKEN = _a.MODULE_OPTIONS_TOKEN, exports.OPTIONS_TYPE = _a.OPTIONS_TYPE, exports.ASYNC_OPTIONS_TYPE = _a.ASYNC_OPTIONS_TYPE;
12
+ const getAgentToken = (name) => `LANGCHAIN_AGENT_${name.toUpperCase()}`;
13
+ exports.getAgentToken = getAgentToken;
@@ -0,0 +1,7 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { ASYNC_OPTIONS_TYPE, ConfigurableModuleClass, OPTIONS_TYPE } from './langchain.module-definition';
3
+ export declare class LangChainModule extends ConfigurableModuleClass {
4
+ static register(options: typeof OPTIONS_TYPE): DynamicModule;
5
+ static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule;
6
+ private static addDynamicAgentProvider;
7
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.LangChainModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const langchain_module_definition_1 = require("./langchain.module-definition");
12
+ const langchain_service_1 = require("./langchain.service");
13
+ const tool_discovery_service_1 = require("./tool-discovery.service");
14
+ const metadata_scanner_1 = require("@nestjs/core/metadata-scanner");
15
+ const core_1 = require("@nestjs/core");
16
+ let LangChainModule = class LangChainModule extends langchain_module_definition_1.ConfigurableModuleClass {
17
+ static register(options) {
18
+ const dynamicModule = super.register(options);
19
+ return this.addDynamicAgentProvider(dynamicModule, options.name);
20
+ }
21
+ static registerAsync(options) {
22
+ const dynamicModule = super.registerAsync(options);
23
+ return this.addDynamicAgentProvider(dynamicModule, options.name);
24
+ }
25
+ static addDynamicAgentProvider(dynamicModule, name) {
26
+ if (!name)
27
+ return dynamicModule;
28
+ const agentToken = (0, langchain_module_definition_1.getAgentToken)(name);
29
+ const agentProvider = {
30
+ provide: agentToken,
31
+ useExisting: langchain_service_1.LangChainService,
32
+ };
33
+ return {
34
+ ...dynamicModule,
35
+ providers: [...(dynamicModule.providers || []), agentProvider],
36
+ exports: [agentToken],
37
+ };
38
+ }
39
+ };
40
+ exports.LangChainModule = LangChainModule;
41
+ exports.LangChainModule = LangChainModule = __decorate([
42
+ (0, common_1.Module)({
43
+ imports: [core_1.DiscoveryModule],
44
+ providers: [langchain_service_1.LangChainService, tool_discovery_service_1.ToolDiscoveryService, metadata_scanner_1.MetadataScanner],
45
+ exports: [langchain_service_1.LangChainService],
46
+ })
47
+ ], LangChainModule);
@@ -0,0 +1,18 @@
1
+ import { OnModuleInit } from '@nestjs/common';
2
+ import { LangChainModuleOptions } from './interfaces/langchain-module-options.interface';
3
+ import { ToolDiscoveryService } from './tool-discovery.service';
4
+ export declare class LangChainService implements OnModuleInit {
5
+ private readonly options;
6
+ private readonly toolDiscovery;
7
+ private agent;
8
+ private readonly logger;
9
+ constructor(options: LangChainModuleOptions, toolDiscovery: ToolDiscoveryService);
10
+ /**
11
+ * Discover tools and initialize the agent
12
+ */
13
+ onModuleInit(): Promise<void>;
14
+ /**
15
+ * Run the agent with the given input and stream the response
16
+ */
17
+ run(input: string): Promise<string>;
18
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var LangChainService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.LangChainService = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const langchain_1 = require("langchain");
19
+ const message_1 = require("./types/message");
20
+ const message_factory_1 = require("./factories/message.factory");
21
+ const langchain_module_definition_1 = require("./langchain.module-definition");
22
+ const tool_discovery_service_1 = require("./tool-discovery.service");
23
+ let LangChainService = LangChainService_1 = class LangChainService {
24
+ constructor(options, toolDiscovery) {
25
+ this.options = options;
26
+ this.toolDiscovery = toolDiscovery;
27
+ this.logger = new common_1.Logger(LangChainService_1.name);
28
+ }
29
+ /**
30
+ * Discover tools and initialize the agent
31
+ */
32
+ async onModuleInit() {
33
+ const tools = this.toolDiscovery.getToolsFromModules(this.options.tools || []);
34
+ const modelName = this.options.model.model;
35
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
36
+ const { model: _, ...modelFields } = this.options.model;
37
+ const model = await (0, langchain_1.initChatModel)(modelName, modelFields);
38
+ this.agent = (0, langchain_1.createAgent)({
39
+ model,
40
+ tools,
41
+ systemPrompt: this.options.systemPrompt,
42
+ });
43
+ this.logger.log(`Agent initialized with ${tools.length} tools.`);
44
+ }
45
+ /**
46
+ * Run the agent with the given input and stream the response
47
+ */
48
+ async run(input) {
49
+ const humanMessage = new message_1.HumanMessage('no_id', 'user', input);
50
+ humanMessage.content = input;
51
+ common_1.Logger.verbose(humanMessage.getLogString());
52
+ for await (const chunk of await this.agent.stream({
53
+ messages: [{ role: 'user', content: input }],
54
+ }, { streamMode: 'updates' })) {
55
+ const message = message_factory_1.MessageFactory.fromLangChain(Object.values(chunk)[0].messages[0]);
56
+ common_1.Logger.verbose(message.getLogString());
57
+ if (message.isFinished()) {
58
+ return message.content;
59
+ }
60
+ }
61
+ throw new Error('No response from agent.');
62
+ }
63
+ };
64
+ exports.LangChainService = LangChainService;
65
+ exports.LangChainService = LangChainService = LangChainService_1 = __decorate([
66
+ (0, common_1.Injectable)(),
67
+ __param(0, (0, common_1.Inject)(langchain_module_definition_1.MODULE_OPTIONS_TOKEN)),
68
+ __metadata("design:paramtypes", [Object, tool_discovery_service_1.ToolDiscoveryService])
69
+ ], LangChainService);
@@ -0,0 +1,11 @@
1
+ import { Type } from '@nestjs/common';
2
+ import { DynamicStructuredTool } from '@langchain/core/tools';
3
+ import { DiscoveryService } from '@nestjs/core/discovery/discovery-service';
4
+ import { MetadataScanner } from '@nestjs/core';
5
+ export declare class ToolDiscoveryService {
6
+ private readonly discoveryService;
7
+ private readonly metadataScanner;
8
+ constructor(discoveryService: DiscoveryService, metadataScanner: MetadataScanner);
9
+ private generateZodSchema;
10
+ getToolsFromModules(allowedModules: Type<any>[]): DynamicStructuredTool[];
11
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ToolDiscoveryService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const tools_1 = require("@langchain/core/tools");
18
+ const tool_decorator_1 = require("./decorators/tool.decorator");
19
+ const zod_1 = __importDefault(require("zod"));
20
+ const discovery_service_1 = require("@nestjs/core/discovery/discovery-service");
21
+ const core_1 = require("@nestjs/core");
22
+ let ToolDiscoveryService = class ToolDiscoveryService {
23
+ constructor(discoveryService, metadataScanner) {
24
+ this.discoveryService = discoveryService;
25
+ this.metadataScanner = metadataScanner;
26
+ }
27
+ generateZodSchema(params) {
28
+ const schemaObject = {};
29
+ params.forEach((param) => {
30
+ switch (param.type) {
31
+ case 'number':
32
+ schemaObject[param.name] = zod_1.default.number().describe(param.description);
33
+ break;
34
+ case 'boolean':
35
+ schemaObject[param.name] = zod_1.default.boolean().describe(param.description);
36
+ break;
37
+ default:
38
+ schemaObject[param.name] = zod_1.default.string().describe(param.description);
39
+ }
40
+ });
41
+ return zod_1.default.object(schemaObject);
42
+ }
43
+ getToolsFromModules(allowedModules) {
44
+ const providers = this.discoveryService.getProviders();
45
+ const allowedModuleNames = allowedModules.map((m) => m.name);
46
+ const tools = [];
47
+ providers.forEach((wrapper) => {
48
+ const { instance, host } = wrapper;
49
+ if (!instance || !host || !allowedModuleNames.includes(host.name)) {
50
+ return;
51
+ }
52
+ const methodNames = this.metadataScanner.getAllMethodNames(Object.getPrototypeOf(instance));
53
+ methodNames.forEach((name) => {
54
+ const metadata = Reflect.getMetadata(tool_decorator_1.TOOL_METADATA, instance[name]);
55
+ const paramsMeta = Reflect.getMetadata(tool_decorator_1.TOOL_PARAMS_METADATA, instance, name) || [];
56
+ const zodSchema = this.generateZodSchema(paramsMeta);
57
+ if (metadata) {
58
+ tools.push(new tools_1.DynamicStructuredTool({
59
+ name: name,
60
+ description: metadata.description,
61
+ schema: zodSchema,
62
+ func: (args) => {
63
+ const sortedParams = paramsMeta.sort((a, b) => a.index - b.index);
64
+ const orderedArgs = sortedParams.map((p) => args[p.name]);
65
+ return instance[name](...orderedArgs);
66
+ },
67
+ }));
68
+ }
69
+ });
70
+ });
71
+ return tools;
72
+ }
73
+ };
74
+ exports.ToolDiscoveryService = ToolDiscoveryService;
75
+ exports.ToolDiscoveryService = ToolDiscoveryService = __decorate([
76
+ (0, common_1.Injectable)(),
77
+ __metadata("design:paramtypes", [discovery_service_1.DiscoveryService,
78
+ core_1.MetadataScanner])
79
+ ], ToolDiscoveryService);
@@ -0,0 +1,69 @@
1
+ export declare class Message {
2
+ id: string;
3
+ name: string;
4
+ content: string;
5
+ type: MessageType;
6
+ constructor(id: string, name: string, content: string, type: MessageType);
7
+ getLogString(): string;
8
+ isFinished(): boolean;
9
+ }
10
+ export declare class HumanMessage extends Message {
11
+ constructor(id: string, name: string, content: string);
12
+ getLogString(): string;
13
+ isFinished(): boolean;
14
+ }
15
+ export declare class AIMessage extends Message {
16
+ response_metadata: ResponseMetadata;
17
+ usage_metadata: UsageMetadata;
18
+ tool_calls: ToolCall[];
19
+ invalid_tool_calls: any[];
20
+ constructor(id: string, name: string, content: string, response_metadata: ResponseMetadata, usage_metadata: UsageMetadata, tool_calls: ToolCall[], invalid_tool_calls: any[]);
21
+ getLogString(): string;
22
+ isFinished(): boolean;
23
+ }
24
+ export declare class ToolMessage extends Message {
25
+ constructor(id: string, name: string, content: string, tool_call_id: string);
26
+ tool_call_id: string;
27
+ getLogString(): string;
28
+ }
29
+ export declare class ToolCall {
30
+ name: string;
31
+ args: any;
32
+ id: string;
33
+ constructor(id: string, name: string, args: any);
34
+ toString(): string;
35
+ }
36
+ export declare class ResponseMetadata {
37
+ tokenUsage: {
38
+ promptTokens: number;
39
+ completionTokens: number;
40
+ totalTokens: number;
41
+ };
42
+ finish_reason: string;
43
+ model_provider: string;
44
+ model_name: string;
45
+ constructor(tokenUsage: {
46
+ promptTokens: number;
47
+ completionTokens: number;
48
+ totalTokens: number;
49
+ }, finish_reason: string, model_provider: string, model_name: string);
50
+ }
51
+ export declare enum FinishReason {
52
+ STOP = "stop",
53
+ TOOL_CALLS = "tool_calls",
54
+ UNKNOWN = "unknown"
55
+ }
56
+ export declare class UsageMetadata {
57
+ constructor(output_tokens: number, input_tokens: number, total_tokens: number, input_token_details: any, output_token_details: any);
58
+ output_tokens: number;
59
+ input_tokens: number;
60
+ total_tokens: number;
61
+ input_token_details: any;
62
+ output_token_details: any;
63
+ }
64
+ export declare enum MessageType {
65
+ AI = "ai",
66
+ HUMAN = "human",
67
+ TOOL = "tool",
68
+ UNKNOWN = "unknown"
69
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageType = exports.UsageMetadata = exports.FinishReason = exports.ResponseMetadata = exports.ToolCall = exports.ToolMessage = exports.AIMessage = exports.HumanMessage = exports.Message = void 0;
4
+ class Message {
5
+ constructor(id, name, content, type) {
6
+ this.id = id;
7
+ this.name = name;
8
+ this.content = content;
9
+ this.type = type;
10
+ }
11
+ getLogString() {
12
+ return `Message [ID: ${this.id}, Name: ${this.name}, Type: ${this.type}, Content: ${this.content}]`;
13
+ }
14
+ isFinished() {
15
+ return false;
16
+ }
17
+ }
18
+ exports.Message = Message;
19
+ class HumanMessage extends Message {
20
+ constructor(id, name, content) {
21
+ super(id, name, content, MessageType.HUMAN);
22
+ }
23
+ getLogString() {
24
+ return `HumanMessage: ${this.content}`;
25
+ }
26
+ isFinished() {
27
+ return false;
28
+ }
29
+ }
30
+ exports.HumanMessage = HumanMessage;
31
+ class AIMessage extends Message {
32
+ constructor(id, name, content, response_metadata, usage_metadata, tool_calls, invalid_tool_calls) {
33
+ super(id, name, content, MessageType.AI);
34
+ this.response_metadata = response_metadata;
35
+ this.usage_metadata = usage_metadata;
36
+ this.tool_calls = tool_calls;
37
+ this.invalid_tool_calls = invalid_tool_calls;
38
+ }
39
+ getLogString() {
40
+ if (this.response_metadata?.finish_reason === FinishReason.TOOL_CALLS) {
41
+ return `AIMessage: Call tools: ${this.tool_calls
42
+ .map((tc) => tc.toString())
43
+ .join(', ')}`;
44
+ }
45
+ return `AIMessage: ${this.content}`;
46
+ }
47
+ isFinished() {
48
+ return this.response_metadata?.finish_reason === FinishReason.STOP;
49
+ }
50
+ }
51
+ exports.AIMessage = AIMessage;
52
+ class ToolMessage extends Message {
53
+ constructor(id, name, content, tool_call_id) {
54
+ super(id, name, content, MessageType.TOOL);
55
+ this.tool_call_id = tool_call_id;
56
+ }
57
+ getLogString() {
58
+ return `ToolMessage call tool ${this.name} with args: ${this.content}`;
59
+ }
60
+ }
61
+ exports.ToolMessage = ToolMessage;
62
+ class ToolCall {
63
+ constructor(id, name, args) {
64
+ this.name = name;
65
+ this.args = args;
66
+ this.id = id;
67
+ }
68
+ toString() {
69
+ return `${this.name} with args ${JSON.stringify(this.args)}`;
70
+ }
71
+ }
72
+ exports.ToolCall = ToolCall;
73
+ class ResponseMetadata {
74
+ constructor(tokenUsage, finish_reason, model_provider, model_name) {
75
+ this.tokenUsage = tokenUsage;
76
+ this.finish_reason = finish_reason;
77
+ this.model_provider = model_provider;
78
+ this.model_name = model_name;
79
+ }
80
+ }
81
+ exports.ResponseMetadata = ResponseMetadata;
82
+ var FinishReason;
83
+ (function (FinishReason) {
84
+ FinishReason["STOP"] = "stop";
85
+ FinishReason["TOOL_CALLS"] = "tool_calls";
86
+ FinishReason["UNKNOWN"] = "unknown";
87
+ })(FinishReason || (exports.FinishReason = FinishReason = {}));
88
+ class UsageMetadata {
89
+ constructor(output_tokens, input_tokens, total_tokens, input_token_details, output_token_details) {
90
+ this.output_tokens = output_tokens;
91
+ this.input_tokens = input_tokens;
92
+ this.total_tokens = total_tokens;
93
+ this.input_token_details = input_token_details;
94
+ this.output_token_details = output_token_details;
95
+ }
96
+ }
97
+ exports.UsageMetadata = UsageMetadata;
98
+ var MessageType;
99
+ (function (MessageType) {
100
+ MessageType["AI"] = "ai";
101
+ MessageType["HUMAN"] = "human";
102
+ MessageType["TOOL"] = "tool";
103
+ MessageType["UNKNOWN"] = "unknown";
104
+ })(MessageType || (exports.MessageType = MessageType = {}));
package/package.json CHANGED
@@ -1,12 +1,91 @@
1
- {
2
- "name": "nestjs-langchain",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "index.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
- "keywords": [],
10
- "author": "",
11
- "license": "ISC"
12
- }
1
+ {
2
+ "name": "nestjs-langchain",
3
+ "version": "1.0.2",
4
+ "description": "NestJS librairy to build agent with LangChain",
5
+ "keywords": [
6
+ "nestjs",
7
+ "langchain",
8
+ "agents",
9
+ "ai"
10
+ ],
11
+ "author": "Martin Bruel",
12
+ "license": "MIT",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "rimraf -rf dist && tsc -p tsconfig.json",
24
+ "format": "prettier --write \"{lib,test}/**/*.ts\"",
25
+ "lint": "eslint \"lib/**/*.ts\" --fix",
26
+ "prepublish:npm": "npm run build",
27
+ "publish:npm": "npm publish --access public",
28
+ "prepublish:next": "npm run build",
29
+ "publish:next": "npm publish --access public --tag next",
30
+ "test": "jest",
31
+ "test:integration": "jest --config ./tests/jest-e2e.json --runInBand",
32
+ "prerelease": "npm run build",
33
+ "release": "release-it",
34
+ "prepare": "husky"
35
+ },
36
+ "dependencies": {
37
+ "zod": "^4.3.6"
38
+ },
39
+ "devDependencies": {
40
+ "@commitlint/cli": "20.3.1",
41
+ "@commitlint/config-conventional": "^20.4.1",
42
+ "@eslint/eslintrc": "3.3.3",
43
+ "@eslint/js": "9.39.2",
44
+ "@nestjs/cli": "^11.0.16",
45
+ "@nestjs/common": "^11.1.12",
46
+ "@nestjs/core": "^11.1.12",
47
+ "@nestjs/platform-express": "^11.1.12",
48
+ "@nestjs/schematics": "^11.0.9",
49
+ "@nestjs/testing": "^11.1.12",
50
+ "@types/express": "^5.0.6",
51
+ "@types/jest": "^30.0.0",
52
+ "@types/lodash": "4.17.23",
53
+ "@types/node": "^25.1.0",
54
+ "@types/supertest": "^6.0.3",
55
+ "@typescript-eslint/eslint-plugin": "^8.54.0",
56
+ "@typescript-eslint/parser": "^8.54.0",
57
+ "eslint": "^9.39.2",
58
+ "eslint-config-prettier": "^10.1.8",
59
+ "eslint-plugin-prettier": "^5.5.5",
60
+ "globals": "17.2.0",
61
+ "husky": "9.1.7",
62
+ "jest": "^30.2.0",
63
+ "joi": "18.0.2",
64
+ "lint-staged": "16.2.7",
65
+ "prettier": "^3.8.1",
66
+ "reflect-metadata": "^0.2.2",
67
+ "release-it": "19.2.4",
68
+ "rimraf": "6.1.2",
69
+ "rxjs": "^7.8.2",
70
+ "source-map-support": "^0.5.21",
71
+ "supertest": "^7.2.2",
72
+ "ts-jest": "^29.4.6",
73
+ "ts-loader": "^9.5.4",
74
+ "ts-node": "^10.9.2",
75
+ "tsconfig-paths": "^4.2.0",
76
+ "typescript": "^5.9.3",
77
+ "typescript-eslint": "^8.54.0"
78
+ },
79
+ "peerDependencies": {
80
+ "@nestjs/common": "^11.1.12",
81
+ "@nestjs/core": "^11.1.12",
82
+ "langchain": "^1.2.15",
83
+ "reflect-metadata": "^0.2.2"
84
+ },
85
+ "lint-staged": {
86
+ "*.ts": [
87
+ "eslint --fix",
88
+ "prettier --write"
89
+ ]
90
+ }
91
+ }