@nest-langchain/openai-compatible 0.1.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 +130 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/openai-compatible.module.d.ts +29 -0
- package/dist/openai-compatible.module.js +153 -0
- package/dist/openai-compatible.module.js.map +1 -0
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.js +21 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +37 -0
- package/src/index.ts +2 -0
- package/src/openai-compatible.module.ts +270 -0
- package/src/tokens.ts +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @nest-langchain/openai-compatible
|
|
2
|
+
|
|
3
|
+
OpenAI-compatible Chat Completions providers를 Nest DI token으로 노출하는 선택 패키지입니다.
|
|
4
|
+
|
|
5
|
+
MiniMax, Kimi/Moonshot, GLM/Z.AI, OpenRouter, Together, Fireworks처럼 OpenAI Chat Completions 호환 endpoint를 제공하는 모델을 같은 방식으로 주입할 수 있습니다.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @nest-langchain/openai-compatible @langchain/openai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`@nest-langchain/core`와 `@nest-langchain/langgraph`는 provider SDK를 직접 의존하지 않습니다.
|
|
12
|
+
|
|
13
|
+
## Register Models
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Module } from '@nestjs/common';
|
|
17
|
+
import { OpenAICompatibleProviderModule } from '@nest-langchain/openai-compatible';
|
|
18
|
+
|
|
19
|
+
@Module({
|
|
20
|
+
imports: [
|
|
21
|
+
OpenAICompatibleProviderModule.forRoot({
|
|
22
|
+
models: [
|
|
23
|
+
{
|
|
24
|
+
name: 'minimax',
|
|
25
|
+
apiKey: process.env.MINIMAX_API_KEY,
|
|
26
|
+
baseURL: 'https://api.minimax.io/v1',
|
|
27
|
+
model: 'MiniMax-M3',
|
|
28
|
+
temperature: 1,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'kimi',
|
|
32
|
+
apiKey: process.env.MOONSHOT_API_KEY,
|
|
33
|
+
baseURL: 'https://api.moonshot.ai/v1',
|
|
34
|
+
model: 'kimi-k2.7',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'glm',
|
|
38
|
+
apiKey: process.env.ZAI_API_KEY,
|
|
39
|
+
baseURL: 'https://api.z.ai/api/paas/v4',
|
|
40
|
+
model: 'glm-5.2',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
export class AiModule {}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
GLM Coding Plan처럼 provider가 별도 coding endpoint를 요구하는 경우에는 해당 endpoint를 `baseURL`에 넣습니다.
|
|
50
|
+
|
|
51
|
+
## Constructor Injection
|
|
52
|
+
|
|
53
|
+
모델은 Nest DI provider로 등록됩니다. 소비 코드는 생성자 주입을 사용합니다.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { Injectable } from '@nestjs/common';
|
|
57
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
58
|
+
import { InjectOpenAICompatibleModel } from '@nest-langchain/openai-compatible';
|
|
59
|
+
|
|
60
|
+
@Injectable()
|
|
61
|
+
export class ProductWorkflow {
|
|
62
|
+
constructor(
|
|
63
|
+
@InjectOpenAICompatibleModel('minimax')
|
|
64
|
+
private readonly model: ChatOpenAI,
|
|
65
|
+
) {}
|
|
66
|
+
|
|
67
|
+
async run(prompt: string) {
|
|
68
|
+
return this.model.invoke(prompt);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`InjectOpenAICompatibleModel()`은 생성자 파라미터용 helper입니다. property injection 예시는 제공하지 않습니다.
|
|
74
|
+
|
|
75
|
+
## Environment Fallbacks
|
|
76
|
+
|
|
77
|
+
명시 옵션이 없으면 이름 기반 환경 변수를 읽습니다.
|
|
78
|
+
|
|
79
|
+
For `name: 'minimax'`:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
OPENAI_COMPATIBLE_MINIMAX_API_KEY
|
|
83
|
+
OPENAI_COMPATIBLE_MINIMAX_BASE_URL
|
|
84
|
+
OPENAI_COMPATIBLE_MINIMAX_MODEL
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
또는 짧은 provider prefix도 읽습니다.
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
MINIMAX_API_KEY
|
|
91
|
+
MINIMAX_BASE_URL
|
|
92
|
+
MINIMAX_MODEL
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
기본 unnamed model은 다음 env를 읽습니다.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
OPENAI_COMPATIBLE_API_KEY
|
|
99
|
+
OPENAI_COMPATIBLE_BASE_URL
|
|
100
|
+
OPENAI_COMPATIBLE_MODEL
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Kimi처럼 API key env 이름이 provider 이름과 다르면 명시할 수 있습니다.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
OpenAICompatibleProviderModule.forRoot({
|
|
107
|
+
name: 'kimi',
|
|
108
|
+
apiKeyEnv: 'MOONSHOT_API_KEY',
|
|
109
|
+
baseURLEnv: 'KIMI_BASE_URL',
|
|
110
|
+
modelEnv: 'KIMI_MODEL',
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Provider-Specific Parameters
|
|
115
|
+
|
|
116
|
+
OpenAI-compatible providers sometimes expose extra Chat Completions parameters. Pass those through `modelKwargs`.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
OpenAICompatibleProviderModule.forRoot({
|
|
120
|
+
name: 'minimax',
|
|
121
|
+
apiKey: process.env.MINIMAX_API_KEY,
|
|
122
|
+
baseURL: 'https://api.minimax.io/v1',
|
|
123
|
+
model: 'MiniMax-M3',
|
|
124
|
+
modelKwargs: {
|
|
125
|
+
reasoning_split: true,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`configuration` is forwarded to the underlying OpenAI client used by `@langchain/openai`, with `baseURL` set from the module options.
|
package/dist/index.d.ts
ADDED
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("./openai-compatible.module"), exports);
|
|
18
|
+
__exportStar(require("./tokens"), exports);
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6DAA2C;AAC3C,2CAAyB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DynamicModule } from '@nestjs/common';
|
|
2
|
+
export interface OpenAICompatibleClientConfiguration {
|
|
3
|
+
baseURL?: string;
|
|
4
|
+
defaultHeaders?: Record<string, string>;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface OpenAICompatibleChatModelOptions {
|
|
8
|
+
name?: string;
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
apiKeyEnv?: string;
|
|
11
|
+
baseURL?: string;
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
baseURLEnv?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
modelEnv?: string;
|
|
16
|
+
temperature?: number;
|
|
17
|
+
timeout?: number;
|
|
18
|
+
maxRetries?: number;
|
|
19
|
+
defaultHeaders?: Record<string, string>;
|
|
20
|
+
configuration?: OpenAICompatibleClientConfiguration;
|
|
21
|
+
modelKwargs?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export interface OpenAICompatibleProviderOptions {
|
|
24
|
+
models: OpenAICompatibleChatModelOptions[];
|
|
25
|
+
}
|
|
26
|
+
export type OpenAICompatibleProviderModuleOptions = OpenAICompatibleChatModelOptions | OpenAICompatibleProviderOptions;
|
|
27
|
+
export declare class OpenAICompatibleProviderModule {
|
|
28
|
+
static forRoot(options?: OpenAICompatibleProviderModuleOptions): DynamicModule;
|
|
29
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
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 OpenAICompatibleProviderModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.OpenAICompatibleProviderModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const openai_1 = require("@langchain/openai");
|
|
13
|
+
const tokens_1 = require("./tokens");
|
|
14
|
+
let OpenAICompatibleProviderModule = OpenAICompatibleProviderModule_1 = class OpenAICompatibleProviderModule {
|
|
15
|
+
static forRoot(options = {}) {
|
|
16
|
+
const modelOptions = normalizeModuleOptions(options);
|
|
17
|
+
const providers = modelOptions.map(createModelProvider);
|
|
18
|
+
return {
|
|
19
|
+
module: OpenAICompatibleProviderModule_1,
|
|
20
|
+
providers,
|
|
21
|
+
exports: providers.map((provider) => provider.provide),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
exports.OpenAICompatibleProviderModule = OpenAICompatibleProviderModule;
|
|
26
|
+
exports.OpenAICompatibleProviderModule = OpenAICompatibleProviderModule = OpenAICompatibleProviderModule_1 = __decorate([
|
|
27
|
+
(0, common_1.Module)({})
|
|
28
|
+
], OpenAICompatibleProviderModule);
|
|
29
|
+
function normalizeModuleOptions(options) {
|
|
30
|
+
const modelOptions = hasModelList(options) ? options.models : [options];
|
|
31
|
+
if (modelOptions.length === 0) {
|
|
32
|
+
throw new Error('At least one OpenAI-compatible model must be configured.');
|
|
33
|
+
}
|
|
34
|
+
const names = new Set();
|
|
35
|
+
for (const modelOption of modelOptions) {
|
|
36
|
+
const name = normalizeName(modelOption.name);
|
|
37
|
+
if (names.has(name)) {
|
|
38
|
+
throw new Error(`Duplicate OpenAI-compatible model name "${name}".`);
|
|
39
|
+
}
|
|
40
|
+
names.add(name);
|
|
41
|
+
}
|
|
42
|
+
return modelOptions;
|
|
43
|
+
}
|
|
44
|
+
function hasModelList(options) {
|
|
45
|
+
return 'models' in options && Array.isArray(options.models);
|
|
46
|
+
}
|
|
47
|
+
function createModelProvider(options) {
|
|
48
|
+
const name = normalizeName(options.name);
|
|
49
|
+
const provide = (0, tokens_1.getOpenAICompatibleModelToken)(name);
|
|
50
|
+
return {
|
|
51
|
+
provide,
|
|
52
|
+
useFactory: () => {
|
|
53
|
+
const apiKey = resolveApiKey(name, options);
|
|
54
|
+
const baseURL = resolveBaseURL(name, options);
|
|
55
|
+
const model = resolveModel(name, options);
|
|
56
|
+
const config = {
|
|
57
|
+
apiKey,
|
|
58
|
+
model,
|
|
59
|
+
temperature: options.temperature ?? 0,
|
|
60
|
+
configuration: resolveClientConfiguration(baseURL, options),
|
|
61
|
+
};
|
|
62
|
+
if (typeof options.timeout === 'number') {
|
|
63
|
+
config.timeout = options.timeout;
|
|
64
|
+
}
|
|
65
|
+
if (typeof options.maxRetries === 'number') {
|
|
66
|
+
config.maxRetries = options.maxRetries;
|
|
67
|
+
}
|
|
68
|
+
if (options.modelKwargs) {
|
|
69
|
+
config.modelKwargs = options.modelKwargs;
|
|
70
|
+
}
|
|
71
|
+
return new openai_1.ChatOpenAI(config);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function resolveApiKey(name, options) {
|
|
76
|
+
const apiKey = firstNonEmpty(options.apiKey, readEnv(options.apiKeyEnv), ...readDefaultEnvs(name, 'API_KEY'));
|
|
77
|
+
if (!apiKey) {
|
|
78
|
+
throw new Error(`OpenAI-compatible API key is required for model "${name}".`);
|
|
79
|
+
}
|
|
80
|
+
return apiKey;
|
|
81
|
+
}
|
|
82
|
+
function resolveBaseURL(name, options) {
|
|
83
|
+
const baseURL = firstNonEmpty(options.baseURL, options.baseUrl, stringValue(options.configuration?.baseURL), readEnv(options.baseURLEnv), ...readDefaultEnvs(name, 'BASE_URL'));
|
|
84
|
+
if (!baseURL) {
|
|
85
|
+
throw new Error(`OpenAI-compatible baseURL is required for model "${name}".`);
|
|
86
|
+
}
|
|
87
|
+
return baseURL;
|
|
88
|
+
}
|
|
89
|
+
function resolveModel(name, options) {
|
|
90
|
+
const model = firstNonEmpty(options.model, readEnv(options.modelEnv), ...readDefaultEnvs(name, 'MODEL'));
|
|
91
|
+
if (!model) {
|
|
92
|
+
throw new Error(`OpenAI-compatible model is required for "${name}".`);
|
|
93
|
+
}
|
|
94
|
+
return model;
|
|
95
|
+
}
|
|
96
|
+
function resolveClientConfiguration(baseURL, options) {
|
|
97
|
+
const defaultHeaders = mergeDefaultHeaders(options.configuration?.defaultHeaders, options.defaultHeaders);
|
|
98
|
+
const configuration = {
|
|
99
|
+
...options.configuration,
|
|
100
|
+
baseURL,
|
|
101
|
+
};
|
|
102
|
+
if (defaultHeaders) {
|
|
103
|
+
configuration.defaultHeaders = defaultHeaders;
|
|
104
|
+
}
|
|
105
|
+
return configuration;
|
|
106
|
+
}
|
|
107
|
+
function mergeDefaultHeaders(base, override) {
|
|
108
|
+
if (!base && !override) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...base,
|
|
113
|
+
...override,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function normalizeName(name = tokens_1.DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME) {
|
|
117
|
+
const trimmed = name.trim();
|
|
118
|
+
if (!trimmed) {
|
|
119
|
+
throw new Error('OpenAI-compatible model name is required.');
|
|
120
|
+
}
|
|
121
|
+
return trimmed;
|
|
122
|
+
}
|
|
123
|
+
function firstNonEmpty(...values) {
|
|
124
|
+
return values.find((value) => typeof value === 'string' && value.length > 0);
|
|
125
|
+
}
|
|
126
|
+
function readEnv(name) {
|
|
127
|
+
return name ? process.env[name] : undefined;
|
|
128
|
+
}
|
|
129
|
+
function readDefaultEnvs(name, suffix) {
|
|
130
|
+
if (name === tokens_1.DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME) {
|
|
131
|
+
return [process.env[`OPENAI_COMPATIBLE_${suffix}`]].filter(isString);
|
|
132
|
+
}
|
|
133
|
+
const envPrefix = toEnvPrefix(name);
|
|
134
|
+
return [
|
|
135
|
+
process.env[`OPENAI_COMPATIBLE_${envPrefix}_${suffix}`],
|
|
136
|
+
process.env[`${envPrefix}_${suffix}`],
|
|
137
|
+
].filter(isString);
|
|
138
|
+
}
|
|
139
|
+
function toEnvPrefix(name) {
|
|
140
|
+
return name
|
|
141
|
+
.trim()
|
|
142
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
143
|
+
.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
144
|
+
.replace(/^_+|_+$/g, '')
|
|
145
|
+
.toUpperCase();
|
|
146
|
+
}
|
|
147
|
+
function stringValue(value) {
|
|
148
|
+
return typeof value === 'string' ? value : undefined;
|
|
149
|
+
}
|
|
150
|
+
function isString(value) {
|
|
151
|
+
return typeof value === 'string';
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=openai-compatible.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-compatible.module.js","sourceRoot":"","sources":["../src/openai-compatible.module.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens.d.ts
ADDED
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME = void 0;
|
|
4
|
+
exports.getOpenAICompatibleModelToken = getOpenAICompatibleModelToken;
|
|
5
|
+
exports.InjectOpenAICompatibleModel = InjectOpenAICompatibleModel;
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
exports.DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME = 'default';
|
|
8
|
+
function getOpenAICompatibleModelToken(name = exports.DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME) {
|
|
9
|
+
return `nest-langchain:openai-compatible:${normalizeModelName(name)}:chat-model`;
|
|
10
|
+
}
|
|
11
|
+
function InjectOpenAICompatibleModel(name = exports.DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME) {
|
|
12
|
+
return (0, common_1.Inject)(getOpenAICompatibleModelToken(name));
|
|
13
|
+
}
|
|
14
|
+
function normalizeModelName(name) {
|
|
15
|
+
const trimmed = name.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
throw new Error('OpenAI-compatible model name is required.');
|
|
18
|
+
}
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":";;;AAIA,sEAIC;AAED,kEAIC;AAdD,2CAAwC;AAE3B,QAAA,oCAAoC,GAAG,SAAS,CAAC;AAE9D,SAAgB,6BAA6B,CAC3C,IAAI,GAAG,4CAAoC;IAE3C,OAAO,oCAAoC,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC;AACnF,CAAC;AAED,SAAgB,2BAA2B,CACzC,IAAI,GAAG,4CAAoC;IAE3C,OAAO,IAAA,eAAM,EAAC,6BAA6B,CAAC,IAAI,CAAC,CAAuB,CAAC;AAC3E,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAY;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nest-langchain/openai-compatible",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenAI-compatible provider tokens and model factory wiring for Nest LangChain.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "easdkr",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@langchain/openai": "^1.5.1",
|
|
30
|
+
"@nestjs/common": "^10 || ^11"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@langchain/openai": "^1.5.1",
|
|
34
|
+
"@nestjs/common": "^11.1.27",
|
|
35
|
+
"vitest": "^4.0.15"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { DynamicModule, Module, type Provider } from '@nestjs/common';
|
|
2
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME,
|
|
6
|
+
getOpenAICompatibleModelToken,
|
|
7
|
+
} from './tokens';
|
|
8
|
+
|
|
9
|
+
export interface OpenAICompatibleClientConfiguration {
|
|
10
|
+
baseURL?: string;
|
|
11
|
+
defaultHeaders?: Record<string, string>;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OpenAICompatibleChatModelOptions {
|
|
16
|
+
name?: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
apiKeyEnv?: string;
|
|
19
|
+
baseURL?: string;
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
baseURLEnv?: string;
|
|
22
|
+
model?: string;
|
|
23
|
+
modelEnv?: string;
|
|
24
|
+
temperature?: number;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
maxRetries?: number;
|
|
27
|
+
defaultHeaders?: Record<string, string>;
|
|
28
|
+
configuration?: OpenAICompatibleClientConfiguration;
|
|
29
|
+
modelKwargs?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface OpenAICompatibleProviderOptions {
|
|
33
|
+
models: OpenAICompatibleChatModelOptions[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type OpenAICompatibleProviderModuleOptions =
|
|
37
|
+
| OpenAICompatibleChatModelOptions
|
|
38
|
+
| OpenAICompatibleProviderOptions;
|
|
39
|
+
|
|
40
|
+
@Module({})
|
|
41
|
+
export class OpenAICompatibleProviderModule {
|
|
42
|
+
static forRoot(
|
|
43
|
+
options: OpenAICompatibleProviderModuleOptions = {},
|
|
44
|
+
): DynamicModule {
|
|
45
|
+
const modelOptions = normalizeModuleOptions(options);
|
|
46
|
+
const providers = modelOptions.map(createModelProvider);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
module: OpenAICompatibleProviderModule,
|
|
50
|
+
providers,
|
|
51
|
+
exports: providers.map((provider) => provider.provide),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type ChatOpenAIConstructor = new (
|
|
57
|
+
fields: Record<string, unknown>,
|
|
58
|
+
) => InstanceType<typeof ChatOpenAI>;
|
|
59
|
+
|
|
60
|
+
type ModelProvider = Provider & {
|
|
61
|
+
provide: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function normalizeModuleOptions(
|
|
65
|
+
options: OpenAICompatibleProviderModuleOptions,
|
|
66
|
+
): OpenAICompatibleChatModelOptions[] {
|
|
67
|
+
const modelOptions = hasModelList(options) ? options.models : [options];
|
|
68
|
+
|
|
69
|
+
if (modelOptions.length === 0) {
|
|
70
|
+
throw new Error('At least one OpenAI-compatible model must be configured.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const names = new Set<string>();
|
|
74
|
+
|
|
75
|
+
for (const modelOption of modelOptions) {
|
|
76
|
+
const name = normalizeName(modelOption.name);
|
|
77
|
+
|
|
78
|
+
if (names.has(name)) {
|
|
79
|
+
throw new Error(`Duplicate OpenAI-compatible model name "${name}".`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
names.add(name);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return modelOptions;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasModelList(
|
|
89
|
+
options: OpenAICompatibleProviderModuleOptions,
|
|
90
|
+
): options is OpenAICompatibleProviderOptions {
|
|
91
|
+
return 'models' in options && Array.isArray(options.models);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function createModelProvider(
|
|
95
|
+
options: OpenAICompatibleChatModelOptions,
|
|
96
|
+
): ModelProvider {
|
|
97
|
+
const name = normalizeName(options.name);
|
|
98
|
+
const provide = getOpenAICompatibleModelToken(name);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
provide,
|
|
102
|
+
useFactory: () => {
|
|
103
|
+
const apiKey = resolveApiKey(name, options);
|
|
104
|
+
const baseURL = resolveBaseURL(name, options);
|
|
105
|
+
const model = resolveModel(name, options);
|
|
106
|
+
|
|
107
|
+
const config: Record<string, unknown> = {
|
|
108
|
+
apiKey,
|
|
109
|
+
model,
|
|
110
|
+
temperature: options.temperature ?? 0,
|
|
111
|
+
configuration: resolveClientConfiguration(baseURL, options),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (typeof options.timeout === 'number') {
|
|
115
|
+
config.timeout = options.timeout;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (typeof options.maxRetries === 'number') {
|
|
119
|
+
config.maxRetries = options.maxRetries;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (options.modelKwargs) {
|
|
123
|
+
config.modelKwargs = options.modelKwargs;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return new (ChatOpenAI as ChatOpenAIConstructor)(config);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveApiKey(
|
|
132
|
+
name: string,
|
|
133
|
+
options: OpenAICompatibleChatModelOptions,
|
|
134
|
+
): string {
|
|
135
|
+
const apiKey = firstNonEmpty(
|
|
136
|
+
options.apiKey,
|
|
137
|
+
readEnv(options.apiKeyEnv),
|
|
138
|
+
...readDefaultEnvs(name, 'API_KEY'),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!apiKey) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`OpenAI-compatible API key is required for model "${name}".`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return apiKey;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function resolveBaseURL(
|
|
151
|
+
name: string,
|
|
152
|
+
options: OpenAICompatibleChatModelOptions,
|
|
153
|
+
): string {
|
|
154
|
+
const baseURL = firstNonEmpty(
|
|
155
|
+
options.baseURL,
|
|
156
|
+
options.baseUrl,
|
|
157
|
+
stringValue(options.configuration?.baseURL),
|
|
158
|
+
readEnv(options.baseURLEnv),
|
|
159
|
+
...readDefaultEnvs(name, 'BASE_URL'),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!baseURL) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`OpenAI-compatible baseURL is required for model "${name}".`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return baseURL;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function resolveModel(
|
|
172
|
+
name: string,
|
|
173
|
+
options: OpenAICompatibleChatModelOptions,
|
|
174
|
+
): string {
|
|
175
|
+
const model = firstNonEmpty(
|
|
176
|
+
options.model,
|
|
177
|
+
readEnv(options.modelEnv),
|
|
178
|
+
...readDefaultEnvs(name, 'MODEL'),
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (!model) {
|
|
182
|
+
throw new Error(`OpenAI-compatible model is required for "${name}".`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return model;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function resolveClientConfiguration(
|
|
189
|
+
baseURL: string,
|
|
190
|
+
options: OpenAICompatibleChatModelOptions,
|
|
191
|
+
): OpenAICompatibleClientConfiguration {
|
|
192
|
+
const defaultHeaders = mergeDefaultHeaders(
|
|
193
|
+
options.configuration?.defaultHeaders,
|
|
194
|
+
options.defaultHeaders,
|
|
195
|
+
);
|
|
196
|
+
const configuration: OpenAICompatibleClientConfiguration = {
|
|
197
|
+
...options.configuration,
|
|
198
|
+
baseURL,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (defaultHeaders) {
|
|
202
|
+
configuration.defaultHeaders = defaultHeaders;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return configuration;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function mergeDefaultHeaders(
|
|
209
|
+
base?: Record<string, string>,
|
|
210
|
+
override?: Record<string, string>,
|
|
211
|
+
): Record<string, string> | undefined {
|
|
212
|
+
if (!base && !override) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
...base,
|
|
218
|
+
...override,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function normalizeName(name = DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME): string {
|
|
223
|
+
const trimmed = name.trim();
|
|
224
|
+
|
|
225
|
+
if (!trimmed) {
|
|
226
|
+
throw new Error('OpenAI-compatible model name is required.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return trimmed;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function firstNonEmpty(
|
|
233
|
+
...values: Array<string | undefined>
|
|
234
|
+
): string | undefined {
|
|
235
|
+
return values.find((value) => typeof value === 'string' && value.length > 0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function readEnv(name: string | undefined): string | undefined {
|
|
239
|
+
return name ? process.env[name] : undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readDefaultEnvs(name: string, suffix: string): string[] {
|
|
243
|
+
if (name === DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME) {
|
|
244
|
+
return [process.env[`OPENAI_COMPATIBLE_${suffix}`]].filter(isString);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const envPrefix = toEnvPrefix(name);
|
|
248
|
+
|
|
249
|
+
return [
|
|
250
|
+
process.env[`OPENAI_COMPATIBLE_${envPrefix}_${suffix}`],
|
|
251
|
+
process.env[`${envPrefix}_${suffix}`],
|
|
252
|
+
].filter(isString);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function toEnvPrefix(name: string): string {
|
|
256
|
+
return name
|
|
257
|
+
.trim()
|
|
258
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
259
|
+
.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
260
|
+
.replace(/^_+|_+$/g, '')
|
|
261
|
+
.toUpperCase();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function stringValue(value: unknown): string | undefined {
|
|
265
|
+
return typeof value === 'string' ? value : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function isString(value: string | undefined): value is string {
|
|
269
|
+
return typeof value === 'string';
|
|
270
|
+
}
|
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Inject } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME = 'default';
|
|
4
|
+
|
|
5
|
+
export function getOpenAICompatibleModelToken(
|
|
6
|
+
name = DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME,
|
|
7
|
+
): string {
|
|
8
|
+
return `nest-langchain:openai-compatible:${normalizeModelName(name)}:chat-model`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function InjectOpenAICompatibleModel(
|
|
12
|
+
name = DEFAULT_OPENAI_COMPATIBLE_MODEL_NAME,
|
|
13
|
+
): ParameterDecorator {
|
|
14
|
+
return Inject(getOpenAICompatibleModelToken(name)) as ParameterDecorator;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeModelName(name: string): string {
|
|
18
|
+
const trimmed = name.trim();
|
|
19
|
+
|
|
20
|
+
if (!trimmed) {
|
|
21
|
+
throw new Error('OpenAI-compatible model name is required.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return trimmed;
|
|
25
|
+
}
|