Skip to content

Commit 18ff365

Browse files
committed
feat(langchain): add near ai initChatModel provider
1 parent 48ea76f commit 18ff365

4 files changed

Lines changed: 169 additions & 13 deletions

File tree

.changeset/sharp-trees-push.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"langchain": patch
3+
"@langchain/classic": patch
4+
---
5+
6+
Add NEAR AI Cloud as a supported OpenAI-compatible provider for `initChatModel`.

libs/langchain-classic/src/chat_models/universal.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
import { type StructuredToolInterface } from "@langchain/core/tools";
3333
import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
3434
import { ChatResult } from "@langchain/core/outputs";
35+
import { getEnvironmentVariable } from "@langchain/core/utils/env";
3536

3637
// TODO: remove once `EventStreamCallbackHandlerInput` is exposed in core
3738
interface EventStreamCallbackHandlerInput extends Omit<
@@ -48,6 +49,50 @@ export interface ConfigurableChatModelCallOptions extends BaseChatModelCallOptio
4849
)[];
4950
}
5051

52+
type ModelProviderConfig = {
53+
package: string;
54+
className: string;
55+
hasCircularDependency?: boolean;
56+
transformParams?: (
57+
params: Record<string, unknown>
58+
) => Record<string, unknown>;
59+
};
60+
61+
const NEARAI_BASE_URL = "https://cloud-api.near.ai/v1";
62+
63+
function getNearAIParams(
64+
params: Record<string, unknown>
65+
): Record<string, unknown> {
66+
const configuration =
67+
typeof params.configuration === "object" && params.configuration !== null
68+
? (params.configuration as Record<string, unknown>)
69+
: {};
70+
const configApiKey = configuration.apiKey;
71+
const apiKey =
72+
params.apiKey ??
73+
(typeof configApiKey === "string" || typeof configApiKey === "function"
74+
? configApiKey
75+
: undefined) ??
76+
getEnvironmentVariable("NEARAI_API_KEY");
77+
78+
if (!apiKey) {
79+
throw new Error(
80+
'NEAR AI API key not found. Please set the NEARAI_API_KEY environment variable or pass the key into "apiKey" field.'
81+
);
82+
}
83+
84+
return {
85+
...params,
86+
apiKey,
87+
streamUsage: params.streamUsage ?? false,
88+
configuration: {
89+
baseURL: NEARAI_BASE_URL,
90+
...configuration,
91+
apiKey,
92+
},
93+
};
94+
}
95+
5196
// Configuration map for model providers
5297
export const MODEL_PROVIDER_CONFIG = {
5398
openai: {
@@ -106,6 +151,11 @@ export const MODEL_PROVIDER_CONFIG = {
106151
package: "@langchain/deepseek",
107152
className: "ChatDeepSeek",
108153
},
154+
nearai: {
155+
package: "@langchain/openai",
156+
className: "ChatOpenAICompletions",
157+
transformParams: getNearAIParams,
158+
},
109159
xai: {
110160
package: "@langchain/xai",
111161
className: "ChatXAI",
@@ -129,11 +179,6 @@ const SUPPORTED_PROVIDERS = Object.keys(
129179
MODEL_PROVIDER_CONFIG
130180
) as (keyof typeof MODEL_PROVIDER_CONFIG)[];
131181
export type ChatModelProvider = keyof typeof MODEL_PROVIDER_CONFIG;
132-
type ModelProviderConfig = {
133-
package: string;
134-
className: string;
135-
hasCircularDependency?: boolean;
136-
};
137182

138183
/**
139184
* Helper function to get a chat model class by its class name or model provider.
@@ -218,12 +263,15 @@ async function _initChatModelHelper(
218263
}
219264

220265
const { modelProvider: _unused, ...passedParams } = params;
266+
const providerParams = config.transformParams
267+
? config.transformParams(passedParams)
268+
: passedParams;
221269
// Pass modelProviderCopy to use direct lookup and avoid className collision
222270
const ProviderClass = await getChatModelByClassName(
223271
config.className,
224272
modelProviderCopy
225273
);
226-
return new ProviderClass({ model, ...passedParams });
274+
return new ProviderClass({ model, ...providerParams });
227275
}
228276

229277
/**
@@ -687,6 +735,7 @@ export async function initChatModel<
687735
* - perplexity (@langchain/perplexity)
688736
* - cerebras (@langchain/cerebras)
689737
* - deepseek (@langchain/deepseek)
738+
* - nearai (@langchain/openai)
690739
* - xai (@langchain/xai)
691740
* @param {string[] | "any"} [fields.configurableFields] - Which model parameters are configurable:
692741
* - undefined: No configurable fields.

libs/langchain/src/chat_models/tests/universal.test.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1-
import { describe, it, expect } from "vitest";
1+
import { afterEach, describe, it, expect } from "vitest";
22
import { initChatModel } from "../universal.js";
33

4+
const originalNearAIApiKey = process.env.NEARAI_API_KEY;
5+
const originalOpenAIApiKey = process.env.OPENAI_API_KEY;
6+
7+
function restoreEnv(name: string, value: string | undefined) {
8+
if (value === undefined) {
9+
delete process.env[name];
10+
} else {
11+
process.env[name] = value;
12+
}
13+
}
14+
15+
afterEach(() => {
16+
restoreEnv("NEARAI_API_KEY", originalNearAIApiKey);
17+
restoreEnv("OPENAI_API_KEY", originalOpenAIApiKey);
18+
});
19+
420
describe("Will appropriately infer a model profiles", () => {
521
it("when provided a profile", async () => {
622
const model = await initChatModel("gpt-4o-mini", {
@@ -18,3 +34,39 @@ describe("Will appropriately infer a model profiles", () => {
1834
expect(model.profile.maxInputTokens).toBeDefined();
1935
});
2036
});
37+
38+
describe("NEAR AI provider", () => {
39+
it("configures the OpenAI-compatible chat completions endpoint", async () => {
40+
process.env.NEARAI_API_KEY = "nearai-test-key";
41+
process.env.OPENAI_API_KEY = "openai-test-key";
42+
43+
const model = await initChatModel("nearai:anthropic/claude-haiku-4-5", {
44+
maxTokens: 16,
45+
temperature: 0,
46+
});
47+
const innerModel = await (
48+
model as {
49+
_getModelInstance(): Promise<{
50+
identifyingParams(): Record<string, unknown>;
51+
}>;
52+
}
53+
)._getModelInstance();
54+
55+
const params = innerModel.identifyingParams();
56+
expect(params.apiKey).toBe("nearai-test-key");
57+
expect(params.baseURL).toBe("https://cloud-api.near.ai/v1");
58+
expect(params.model).toBe("anthropic/claude-haiku-4-5");
59+
expect(params.model_name).toBe("anthropic/claude-haiku-4-5");
60+
expect(params.max_tokens).toBe(16);
61+
expect(params.max_completion_tokens).toBeUndefined();
62+
});
63+
64+
it("does not fall back to OPENAI_API_KEY", async () => {
65+
delete process.env.NEARAI_API_KEY;
66+
process.env.OPENAI_API_KEY = "openai-test-key";
67+
68+
await expect(
69+
initChatModel("nearai:anthropic/claude-haiku-4-5")
70+
).rejects.toThrow("NEARAI_API_KEY");
71+
});
72+
});

libs/langchain/src/chat_models/universal.ts

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { type StructuredToolInterface } from "@langchain/core/tools";
3333
import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
3434
import { ChatResult } from "@langchain/core/outputs";
3535
import { ModelProfile } from "@langchain/core/language_models/profile";
36+
import { getEnvironmentVariable } from "@langchain/core/utils/env";
3637

3738
// TODO: remove once `EventStreamCallbackHandlerInput` is exposed in core
3839
interface EventStreamCallbackHandlerInput extends Omit<
@@ -49,6 +50,50 @@ export interface ConfigurableChatModelCallOptions extends BaseChatModelCallOptio
4950
)[];
5051
}
5152

53+
type ModelProviderConfig = {
54+
package: string;
55+
className: string;
56+
hasCircularDependency?: boolean;
57+
transformParams?: (
58+
params: Record<string, unknown>
59+
) => Record<string, unknown>;
60+
};
61+
62+
const NEARAI_BASE_URL = "https://cloud-api.near.ai/v1";
63+
64+
function getNearAIParams(
65+
params: Record<string, unknown>
66+
): Record<string, unknown> {
67+
const configuration =
68+
typeof params.configuration === "object" && params.configuration !== null
69+
? (params.configuration as Record<string, unknown>)
70+
: {};
71+
const configApiKey = configuration.apiKey;
72+
const apiKey =
73+
params.apiKey ??
74+
(typeof configApiKey === "string" || typeof configApiKey === "function"
75+
? configApiKey
76+
: undefined) ??
77+
getEnvironmentVariable("NEARAI_API_KEY");
78+
79+
if (!apiKey) {
80+
throw new Error(
81+
'NEAR AI API key not found. Please set the NEARAI_API_KEY environment variable or pass the key into "apiKey" field.'
82+
);
83+
}
84+
85+
return {
86+
...params,
87+
apiKey,
88+
streamUsage: params.streamUsage ?? false,
89+
configuration: {
90+
baseURL: NEARAI_BASE_URL,
91+
...configuration,
92+
apiKey,
93+
},
94+
};
95+
}
96+
5297
// Configuration map for model providers
5398
export const MODEL_PROVIDER_CONFIG = {
5499
openai: {
@@ -111,6 +156,11 @@ export const MODEL_PROVIDER_CONFIG = {
111156
package: "@langchain/deepseek",
112157
className: "ChatDeepSeek",
113158
},
159+
nearai: {
160+
package: "@langchain/openai",
161+
className: "ChatOpenAICompletions",
162+
transformParams: getNearAIParams,
163+
},
114164
xai: {
115165
package: "@langchain/xai",
116166
className: "ChatXAI",
@@ -138,11 +188,6 @@ const SUPPORTED_PROVIDERS = Object.keys(
138188
MODEL_PROVIDER_CONFIG
139189
) as (keyof typeof MODEL_PROVIDER_CONFIG)[];
140190
export type ChatModelProvider = keyof typeof MODEL_PROVIDER_CONFIG;
141-
type ModelProviderConfig = {
142-
package: string;
143-
className: string;
144-
hasCircularDependency?: boolean;
145-
};
146191

147192
/**
148193
* Helper function to get a chat model class by its class name or model provider.
@@ -227,12 +272,15 @@ async function _initChatModelHelper(
227272
}
228273

229274
const { modelProvider: _unused, ...passedParams } = params;
275+
const providerParams = config.transformParams
276+
? config.transformParams(passedParams)
277+
: passedParams;
230278
// Pass modelProviderCopy to use direct lookup and avoid className collision
231279
const ProviderClass = await getChatModelByClassName(
232280
config.className,
233281
modelProviderCopy
234282
);
235-
return new ProviderClass({ model, ...passedParams });
283+
return new ProviderClass({ model, ...providerParams });
236284
}
237285

238286
/**
@@ -754,6 +802,7 @@ export async function initChatModel<
754802
* - perplexity (@langchain/perplexity)
755803
* - cerebras (@langchain/cerebras)
756804
* - deepseek (@langchain/deepseek)
805+
* - nearai (@langchain/openai)
757806
* - xai (@langchain/xai)
758807
* @param {string[] | "any"} [fields.configurableFields] - Which model parameters are configurable:
759808
* - undefined: No configurable fields.

0 commit comments

Comments
 (0)