Z.ai LLM & agent provider (#4573)

* wip zai llm provider

* cleanup + add zai agent provider

* lint

* change how caching works for failed models

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2025-11-20 15:57:03 -08:00 committed by GitHub
parent 7a7ec969d7
commit 49c29fb968
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 581 additions and 79 deletions

View File

@ -99,6 +99,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace
- [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui)
- [Apipie](https://apipie.ai/) - [Apipie](https://apipie.ai/)
- [xAI](https://x.ai/) - [xAI](https://x.ai/)
- [Z.AI (chat models)](https://z.ai/model-api)
- [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link)
- [PPIO](https://ppinfra.com?utm_source=github_anything-llm) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm)
- [Moonshot AI](https://www.moonshot.ai/) - [Moonshot AI](https://www.moonshot.ai/)

View File

@ -131,6 +131,10 @@ GID='1000'
# XAI_LLM_API_KEY='xai-your-api-key-here' # XAI_LLM_API_KEY='xai-your-api-key-here'
# XAI_LLM_MODEL_PREF='grok-beta' # XAI_LLM_MODEL_PREF='grok-beta'
# LLM_PROVIDER='zai'
# ZAI_API_KEY="your-zai-api-key-here"
# ZAI_MODEL_PREF="glm-4.5"
# LLM_PROVIDER='nvidia-nim' # LLM_PROVIDER='nvidia-nim'
# NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000' # NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000'
# NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct' # NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct'

View File

@ -0,0 +1,114 @@
import { useState, useEffect } from "react";
import System from "@/models/system";
export default function ZAiLLMOptions({ settings }) {
const [inputValue, setInputValue] = useState(settings?.ZAiApiKey);
const [apiKey, setApiKey] = useState(settings?.ZAiApiKey);
return (
<div className="flex gap-[36px] mt-1.5">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Z.AI API Key
</label>
<input
type="password"
name="ZAiApiKey"
className="border-none bg-theme-settings-input-bg text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="Z.AI API Key"
defaultValue={settings?.ZAiApiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
onChange={(e) => setInputValue(e.target.value)}
onBlur={() => setApiKey(inputValue)}
/>
</div>
{!settings?.credentialsOnly && (
<ZAiModelSelection settings={settings} apiKey={apiKey} />
)}
</div>
);
}
function ZAiModelSelection({ apiKey, settings }) {
const [customModels, setCustomModels] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function findCustomModels() {
if (!apiKey) {
setCustomModels([]);
setLoading(true);
return;
}
try {
setLoading(true);
const { models } = await System.customModels("zai", apiKey);
setCustomModels(models || []);
} catch (error) {
console.error("Failed to fetch custom models:", error);
setCustomModels([]);
} finally {
setLoading(false);
}
}
findCustomModels();
}, [apiKey]);
if (loading) {
return (
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Chat Model Selection
</label>
<select
name="ZAiModelPref"
disabled={true}
className="border-none bg-theme-settings-input-bg border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
<option disabled={true} selected={true}>
--loading available models--
</option>
</select>
<p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2">
Enter a valid API key to view all available models for your account.
</p>
</div>
);
}
return (
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
Chat Model Selection
</label>
<select
name="ZAiModelPref"
required={true}
className="border-none bg-theme-settings-input-bg border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
{customModels.length > 0 && (
<optgroup label="Available models">
{customModels.map((model) => {
return (
<option
key={model.id}
value={model.id}
selected={settings?.ZAiModelPref === model.id}
>
{model.id}
</option>
);
})}
</optgroup>
)}
</select>
<p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2">
Select the Z.AI model you want to use for your conversations.
</p>
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -29,6 +29,7 @@ import AWSBedrockLogo from "@/media/llmprovider/bedrock.png";
import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import DeepSeekLogo from "@/media/llmprovider/deepseek.png";
import APIPieLogo from "@/media/llmprovider/apipie.png"; import APIPieLogo from "@/media/llmprovider/apipie.png";
import XAILogo from "@/media/llmprovider/xai.png"; import XAILogo from "@/media/llmprovider/xai.png";
import ZAiLogo from "@/media/llmprovider/zai.png";
import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
import PPIOLogo from "@/media/llmprovider/ppio.png"; import PPIOLogo from "@/media/llmprovider/ppio.png";
import DellProAiStudioLogo from "@/media/llmprovider/dpais.png"; import DellProAiStudioLogo from "@/media/llmprovider/dpais.png";
@ -62,6 +63,7 @@ import AWSBedrockLLMOptions from "@/components/LLMSelection/AwsBedrockLLMOptions
import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions"; import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions";
import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions"; import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions";
import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions";
import ZAiLLMOptions from "@/components/LLMSelection/ZAiLLMOptions";
import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions";
import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions"; import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions";
import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions"; import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions";
@ -335,6 +337,14 @@ export const AVAILABLE_LLM_PROVIDERS = [
description: "Run xAI's powerful LLMs like Grok-2 and more.", description: "Run xAI's powerful LLMs like Grok-2 and more.",
requiredConfig: ["XAIApiKey", "XAIModelPref"], requiredConfig: ["XAIApiKey", "XAIModelPref"],
}, },
{
name: "Z.AI",
value: "zai",
logo: ZAiLogo,
options: (settings) => <ZAiLLMOptions settings={settings} />,
description: "Run Z.AI's powerful GLM models.",
requiredConfig: ["ZAiApiKey"],
},
{ {
name: "Generic OpenAI", name: "Generic OpenAI",
value: "generic-openai", value: "generic-openai",

View File

@ -25,6 +25,7 @@ import AWSBedrockLogo from "@/media/llmprovider/bedrock.png";
import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import DeepSeekLogo from "@/media/llmprovider/deepseek.png";
import APIPieLogo from "@/media/llmprovider/apipie.png"; import APIPieLogo from "@/media/llmprovider/apipie.png";
import XAILogo from "@/media/llmprovider/xai.png"; import XAILogo from "@/media/llmprovider/xai.png";
import ZAiLogo from "@/media/llmprovider/zai.png";
import CohereLogo from "@/media/llmprovider/cohere.png"; import CohereLogo from "@/media/llmprovider/cohere.png";
import ZillizLogo from "@/media/vectordbs/zilliz.png"; import ZillizLogo from "@/media/vectordbs/zilliz.png";
import AstraDBLogo from "@/media/vectordbs/astraDB.png"; import AstraDBLogo from "@/media/vectordbs/astraDB.png";
@ -231,6 +232,15 @@ export const LLM_SELECTION_PRIVACY = {
], ],
logo: XAILogo, logo: XAILogo,
}, },
zai: {
name: "Z.AI",
description: [
"Your content is processed in real-time and not stored on Z.AI servers",
"Your prompts and document text are visible to Z.AI during processing",
"Data is processed in accordance with Z.AI's API Services terms",
],
logo: ZAiLogo,
},
ppio: { ppio: {
name: "PPIO", name: "PPIO",
description: [ description: [

View File

@ -23,6 +23,7 @@ import DeepSeekLogo from "@/media/llmprovider/deepseek.png";
import APIPieLogo from "@/media/llmprovider/apipie.png"; import APIPieLogo from "@/media/llmprovider/apipie.png";
import NovitaLogo from "@/media/llmprovider/novita.png"; import NovitaLogo from "@/media/llmprovider/novita.png";
import XAILogo from "@/media/llmprovider/xai.png"; import XAILogo from "@/media/llmprovider/xai.png";
import ZAiLogo from "@/media/llmprovider/zai.png";
import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
import CohereLogo from "@/media/llmprovider/cohere.png"; import CohereLogo from "@/media/llmprovider/cohere.png";
import PPIOLogo from "@/media/llmprovider/ppio.png"; import PPIOLogo from "@/media/llmprovider/ppio.png";
@ -54,6 +55,7 @@ import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions";
import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions"; import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions";
import NovitaLLMOptions from "@/components/LLMSelection/NovitaLLMOptions"; import NovitaLLMOptions from "@/components/LLMSelection/NovitaLLMOptions";
import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions";
import ZAiLLMOptions from "@/components/LLMSelection/ZAiLLMOptions";
import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions";
import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions"; import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions";
import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions"; import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions";
@ -267,6 +269,13 @@ const LLMS = [
options: (settings) => <XAILLMOptions settings={settings} />, options: (settings) => <XAILLMOptions settings={settings} />,
description: "Run xAI's powerful LLMs like Grok-2 and more.", description: "Run xAI's powerful LLMs like Grok-2 and more.",
}, },
{
name: "Z.AI",
value: "zai",
logo: ZAiLogo,
options: (settings) => <ZAiLLMOptions settings={settings} />,
description: "Run Z.AI's powerful GLM models.",
},
{ {
name: "Moonshot AI", name: "Moonshot AI",
value: "moonshotai", value: "moonshotai",

View File

@ -34,6 +34,7 @@ const ENABLED_PROVIDERS = [
"moonshotai", "moonshotai",
"cometapi", "cometapi",
"foundry", "foundry",
"zai",
// TODO: More agent support. // TODO: More agent support.
// "cohere", // Has tool calling and will need to build explicit support // "cohere", // Has tool calling and will need to build explicit support
// "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested. // "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested.

View File

@ -102,6 +102,7 @@ AnythingLLM اسناد شما را به اشیایی به نام `workspaces` ت
- [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui)
- [Apipie](https://apipie.ai/) - [Apipie](https://apipie.ai/)
- [xAI](https://x.ai/) - [xAI](https://x.ai/)
- [Z.AI (chat models)](https://z.ai/model-api)
- [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link)
- [PPIO](https://ppinfra.com?utm_source=github_anything-llm) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm)

View File

@ -90,6 +90,8 @@ AnythingLLMは、ドキュメントを`ワークスペース`と呼ばれるオ
- [Groq](https://groq.com/) - [Groq](https://groq.com/)
- [Cohere](https://cohere.com/) - [Cohere](https://cohere.com/)
- [KoboldCPP](https://github.com/LostRuins/koboldcpp) - [KoboldCPP](https://github.com/LostRuins/koboldcpp)
- [xAI](https://x.ai/)
- [Z.AI (チャットモデル)](https://z.ai/model-api)
- [PPIO](https://ppinfra.com?utm_source=github_anything-llm) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm)
- [CometAPI (チャットモデル)](https://api.cometapi.com/) - [CometAPI (チャットモデル)](https://api.cometapi.com/)

View File

@ -99,6 +99,7 @@ AnythingLLM, belgelerinizi **"çalışma alanları" (workspaces)** adı verilen
- [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui)
- [Apipie](https://apipie.ai/) - [Apipie](https://apipie.ai/)
- [xAI](https://x.ai/) - [xAI](https://x.ai/)
- [Z.AI (chat models)](https://z.ai/model-api)
- [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link)
- [PPIO](https://ppinfra.com?utm_source=github_anything-llm) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm)

View File

@ -98,6 +98,7 @@ AnythingLLM将您的文档划分为称为`workspaces` (工作区)的对象。工
- [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui)
- [Apipie](https://apipie.ai/) - [Apipie](https://apipie.ai/)
- [xAI](https://x.ai/) - [xAI](https://x.ai/)
- [Z.AI (聊天模型)](https://z.ai/model-api)
- [Novita AI (聊天模型)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [Novita AI (聊天模型)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link)
- [PPIO (聊天模型)](https://ppinfra.com?utm_source=github_anything-llm) - [PPIO (聊天模型)](https://ppinfra.com?utm_source=github_anything-llm)
- [CometAPI (聊天模型)](https://api.cometapi.com/) - [CometAPI (聊天模型)](https://api.cometapi.com/)

View File

@ -134,6 +134,10 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long.
# XAI_LLM_API_KEY='xai-your-api-key-here' # XAI_LLM_API_KEY='xai-your-api-key-here'
# XAI_LLM_MODEL_PREF='grok-beta' # XAI_LLM_MODEL_PREF='grok-beta'
# LLM_PROVIDER='zai'
# ZAI_API_KEY="your-zai-api-key-here"
# ZAI_MODEL_PREF="glm-4.5"
# LLM_PROVIDER='nvidia-nim' # LLM_PROVIDER='nvidia-nim'
# NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000' # NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000'
# NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct' # NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct'

View File

@ -145,6 +145,9 @@ function getModelTag() {
case "moonshotai": case "moonshotai":
model = process.env.MOONSHOT_AI_MODEL_PREF; model = process.env.MOONSHOT_AI_MODEL_PREF;
break; break;
case "zai":
model = process.env.ZAI_MODEL_PREF;
break;
default: default:
model = "--"; model = "--";
break; break;

View File

@ -625,6 +625,10 @@ const SystemSettings = {
CometApiLLMApiKey: !!process.env.COMETAPI_LLM_API_KEY, CometApiLLMApiKey: !!process.env.COMETAPI_LLM_API_KEY,
CometApiLLMModelPref: process.env.COMETAPI_LLM_MODEL_PREF, CometApiLLMModelPref: process.env.COMETAPI_LLM_MODEL_PREF,
CometApiLLMTimeout: process.env.COMETAPI_LLM_TIMEOUT_MS, CometApiLLMTimeout: process.env.COMETAPI_LLM_TIMEOUT_MS,
// Z.AI Keys
ZAiApiKey: !!process.env.ZAI_API_KEY,
ZAiModelPref: process.env.ZAI_MODEL_PREF,
}; };
}, },

View File

@ -19,6 +19,7 @@ class ContextWindowFinder {
xai: "xai", xai: "xai",
deepseek: "deepseek", deepseek: "deepseek",
moonshot: "moonshot", moonshot: "moonshot",
zai: "vercel_ai_gateway", // Vercel has correct context windows for Z.AI models
}; };
static expiryMs = 1000 * 60 * 60 * 24 * 3; // 3 days static expiryMs = 1000 * 60 * 60 * 24 * 3; // 3 days
static remoteUrl = static remoteUrl =
@ -116,8 +117,9 @@ You can fix this by restarting AnythingLLM so the model map is re-pulled.
}); });
if (!remoteContexWindowMap) return null; if (!remoteContexWindowMap) return null;
const modelMap = this.#formatModelMap(remoteContexWindowMap); const modelMap = this.#validateModelMap(
this.#validateModelMap(modelMap); this.#formatModelMap(remoteContexWindowMap)
);
fs.writeFileSync(this.cacheFilePath, JSON.stringify(modelMap, null, 2)); fs.writeFileSync(this.cacheFilePath, JSON.stringify(modelMap, null, 2));
fs.writeFileSync(this.cacheFileExpiryPath, Date.now().toString()); fs.writeFileSync(this.cacheFileExpiryPath, Date.now().toString());
return modelMap; return modelMap;
@ -139,13 +141,17 @@ You can fix this by restarting AnythingLLM so the model map is re-pulled.
// Validate that the context window is a number // Validate that the context window is a number
for (const [model, contextWindow] of Object.entries(models)) { for (const [model, contextWindow] of Object.entries(models)) {
if (isNaN(contextWindow) || contextWindow <= 0) if (isNaN(contextWindow) || contextWindow <= 0) {
throw new Error( this.log(
`Invalid model map for ${provider} - context window is not a positive number for model ${model}` `${provider}:${model} - context window is not a positive number. Got ${contextWindow}.`
); );
delete models[model];
continue;
} }
} }
} }
return modelMap;
}
/** /**
* Formats the remote model map to a format that is compatible with how we store the model map * Formats the remote model map to a format that is compatible with how we store the model map

View File

@ -435,7 +435,8 @@ class OllamaAILLM {
type: "textResponseChunk", type: "textResponseChunk",
textResponse: "", textResponse: "",
close: true, close: true,
error: `Ollama:streaming - could not stream chat. ${error?.cause ?? error.message error: `Ollama:streaming - could not stream chat. ${
error?.cause ?? error.message
}`, }`,
}); });
response.removeListener("close", handleAbort); response.removeListener("close", handleAbort);

View File

@ -0,0 +1,179 @@
const { NativeEmbedder } = require("../../EmbeddingEngines/native");
const {
LLMPerformanceMonitor,
} = require("../../helpers/chat/LLMPerformanceMonitor");
const {
handleDefaultStreamResponseV2,
} = require("../../helpers/chat/responses");
const { MODEL_MAP } = require("../modelMap");
class ZAiLLM {
constructor(embedder = null, modelPreference = null) {
if (!process.env.ZAI_API_KEY) throw new Error("No Z.AI API key was set.");
this.className = "ZAiLLM";
const { OpenAI: OpenAIApi } = require("openai");
this.openai = new OpenAIApi({
baseURL: "https://api.z.ai/api/paas/v4",
apiKey: process.env.ZAI_API_KEY,
});
this.model = modelPreference || process.env.ZAI_MODEL_PREF || "glm-4.5";
this.limits = {
history: this.promptWindowLimit() * 0.15,
system: this.promptWindowLimit() * 0.15,
user: this.promptWindowLimit() * 0.7,
};
this.embedder = embedder ?? new NativeEmbedder();
this.defaultTemp = 0.7;
this.log(
`Initialized ${this.model} with context window ${this.promptWindowLimit()}`
);
}
log(text, ...args) {
console.log(`\x1b[36m[${this.className}]\x1b[0m ${text}`, ...args);
}
#appendContext(contextTexts = []) {
if (!contextTexts || !contextTexts.length) return "";
return (
"\nContext:\n" +
contextTexts
.map((text, i) => {
return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`;
})
.join("")
);
}
streamingEnabled() {
return "streamGetChatCompletion" in this;
}
static promptWindowLimit(modelName) {
return MODEL_MAP.get("zai", modelName) ?? 131072;
}
promptWindowLimit() {
return MODEL_MAP.get("zai", this.model) ?? 131072;
}
async isValidChatCompletionModel(modelName = "") {
return !!modelName; // name just needs to exist
}
/**
* Generates appropriate content array for a message + attachments.
* @param {{userPrompt:string, attachments: import("../../helpers").Attachment[]}}
* @returns {string|object[]}
*/
#generateContent({ userPrompt, attachments = [] }) {
if (!attachments.length) return userPrompt;
const content = [{ type: "text", text: userPrompt }];
for (let attachment of attachments) {
content.push({
type: "image_url",
image_url: {
url: attachment.contentString,
},
});
}
return content.flat();
}
/**
* Construct the user prompt for this model.
* @param {{attachments: import("../../helpers").Attachment[]}} param0
* @returns
*/
constructPrompt({
systemPrompt = "",
contextTexts = [],
chatHistory = [],
userPrompt = "",
attachments = [],
}) {
const prompt = {
role: "system",
content: `${systemPrompt}${this.#appendContext(contextTexts)}`,
};
return [
prompt,
...chatHistory,
{
role: "user",
content: this.#generateContent({ userPrompt, attachments }),
},
];
}
async getChatCompletion(messages = null, { temperature = 0.7 }) {
const result = await LLMPerformanceMonitor.measureAsyncFunction(
this.openai.chat.completions
.create({
model: this.model,
messages,
temperature,
})
.catch((e) => {
throw new Error(e.message);
})
);
if (
!result.output.hasOwnProperty("choices") ||
result.output.choices.length === 0
)
return null;
return {
textResponse: result.output.choices[0].message.content,
metrics: {
prompt_tokens: result.output.usage?.prompt_tokens || 0,
completion_tokens: result.output.usage?.completion_tokens || 0,
total_tokens: result.output.usage?.total_tokens || 0,
outputTps: result.output.usage?.completion_tokens / result.duration,
duration: result.duration,
},
};
}
async streamGetChatCompletion(messages = null, { temperature = 0.7 }) {
const measuredStreamRequest = await LLMPerformanceMonitor.measureStream(
this.openai.chat.completions.create({
model: this.model,
stream: true,
messages,
temperature,
}),
messages,
false
);
return measuredStreamRequest;
}
handleStream(response, stream, responseProps) {
return handleDefaultStreamResponseV2(response, stream, responseProps);
}
// Simple wrapper for dynamic embedder & normalize interface for all LLM implementations
async embedTextInput(textInput) {
return await this.embedder.embedTextInput(textInput);
}
async embedChunks(textChunks = []) {
return await this.embedder.embedChunks(textChunks);
}
async compressMessages(promptArgs = {}, rawHistory = []) {
const { messageArrayCompressor } = require("../../helpers/chat");
const messageArray = this.constructPrompt(promptArgs);
return await messageArrayCompressor(this, messageArray, rawHistory);
}
}
module.exports = {
ZAiLLM,
};

View File

@ -964,6 +964,8 @@ ${this.getHistory({ to: route.to })
return new Providers.ApiPieProvider({ model: config.model }); return new Providers.ApiPieProvider({ model: config.model });
case "xai": case "xai":
return new Providers.XAIProvider({ model: config.model }); return new Providers.XAIProvider({ model: config.model });
case "zai":
return new Providers.ZAIProvider({ model: config.model });
case "novita": case "novita":
return new Providers.NovitaProvider({ model: config.model }); return new Providers.NovitaProvider({ model: config.model });
case "ppio": case "ppio":

View File

@ -183,6 +183,14 @@ class Provider {
apiKey: process.env.XAI_LLM_API_KEY ?? null, apiKey: process.env.XAI_LLM_API_KEY ?? null,
...config, ...config,
}); });
case "zai":
return new ChatOpenAI({
configuration: {
baseURL: "https://api.z.ai/api/paas/v4",
},
apiKey: process.env.ZAI_API_KEY ?? null,
...config,
});
case "novita": case "novita":
return new ChatOpenAI({ return new ChatOpenAI({
configuration: { configuration: {

View File

@ -18,6 +18,7 @@ const DeepSeekProvider = require("./deepseek.js");
const LiteLLMProvider = require("./litellm.js"); const LiteLLMProvider = require("./litellm.js");
const ApiPieProvider = require("./apipie.js"); const ApiPieProvider = require("./apipie.js");
const XAIProvider = require("./xai.js"); const XAIProvider = require("./xai.js");
const ZAIProvider = require("./zai.js");
const NovitaProvider = require("./novita.js"); const NovitaProvider = require("./novita.js");
const NvidiaNimProvider = require("./nvidiaNim.js"); const NvidiaNimProvider = require("./nvidiaNim.js");
const PPIOProvider = require("./ppio.js"); const PPIOProvider = require("./ppio.js");
@ -48,6 +49,7 @@ module.exports = {
LiteLLMProvider, LiteLLMProvider,
ApiPieProvider, ApiPieProvider,
XAIProvider, XAIProvider,
ZAIProvider,
NovitaProvider, NovitaProvider,
CometApiProvider, CometApiProvider,
NvidiaNimProvider, NvidiaNimProvider,

View File

@ -0,0 +1,88 @@
const OpenAI = require("openai");
const Provider = require("./ai-provider.js");
const InheritMultiple = require("./helpers/classes.js");
const UnTooled = require("./helpers/untooled.js");
class ZAIProvider extends InheritMultiple([Provider, UnTooled]) {
model;
constructor(config = {}) {
const { model = "glm-4.5" } = config;
super();
const client = new OpenAI({
baseURL: "https://api.z.ai/api/paas/v4",
apiKey: process.env.ZAI_API_KEY,
maxRetries: 3,
});
this._client = client;
this.model = model;
this.verbose = true;
}
/**
* Create a completion based on the received messages.
*
* @param messages A list of messages to send to the API.
* @param functions
* @returns The completion.
*/
get client() {
return this._client;
}
get supportsAgentStreaming() {
return true;
}
async #handleFunctionCallChat({ messages = [] }) {
return await this.client.chat.completions
.create({
model: this.model,
messages,
})
.then((result) => {
if (!result.hasOwnProperty("choices"))
throw new Error("Z.AI chat: No results!");
if (result.choices.length === 0)
throw new Error("Z.AI chat: No results length!");
return result.choices[0].message.content;
})
.catch((_) => {
return null;
});
}
async #handleFunctionCallStream({ messages = [] }) {
return await this.client.chat.completions.create({
model: this.model,
stream: true,
messages,
});
}
async stream(messages, functions = [], eventHandler = null) {
return await UnTooled.prototype.stream.call(
this,
messages,
functions,
this.#handleFunctionCallStream.bind(this),
eventHandler
);
}
async complete(messages, functions = []) {
return await UnTooled.prototype.complete.call(
this,
messages,
functions,
this.#handleFunctionCallChat.bind(this)
);
}
getCost(_usage) {
return 0;
}
}
module.exports = ZAIProvider;

View File

@ -172,6 +172,10 @@ class AgentHandler {
if (!process.env.XAI_LLM_API_KEY) if (!process.env.XAI_LLM_API_KEY)
throw new Error("xAI API Key must be provided to use agents."); throw new Error("xAI API Key must be provided to use agents.");
break; break;
case "zai":
if (!process.env.ZAI_API_KEY)
throw new Error("Z.AI API Key must be provided to use agents.");
break;
case "novita": case "novita":
if (!process.env.NOVITA_LLM_API_KEY) if (!process.env.NOVITA_LLM_API_KEY)
throw new Error("Novita API Key must be provided to use agents."); throw new Error("Novita API Key must be provided to use agents.");
@ -275,6 +279,8 @@ class AgentHandler {
return process.env.APIPIE_LLM_MODEL_PREF ?? null; return process.env.APIPIE_LLM_MODEL_PREF ?? null;
case "xai": case "xai":
return process.env.XAI_LLM_MODEL_PREF ?? "grok-beta"; return process.env.XAI_LLM_MODEL_PREF ?? "grok-beta";
case "zai":
return process.env.ZAI_MODEL_PREF ?? "glm-4.5";
case "novita": case "novita":
return process.env.NOVITA_LLM_MODEL_PREF ?? "deepseek/deepseek-r1"; return process.env.NOVITA_LLM_MODEL_PREF ?? "deepseek/deepseek-r1";
case "nvidia-nim": case "nvidia-nim":

View File

@ -38,6 +38,7 @@ const SUPPORT_CUSTOM_MODELS = [
"moonshotai", "moonshotai",
"foundry", "foundry",
"cohere", "cohere",
"zai",
// Embedding Engines // Embedding Engines
"native-embedder", "native-embedder",
"cohere-embedder", "cohere-embedder",
@ -100,6 +101,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) {
return await getFoundryModels(basePath); return await getFoundryModels(basePath);
case "cohere": case "cohere":
return await getCohereModels(apiKey, "chat"); return await getCohereModels(apiKey, "chat");
case "zai":
return await getZAiModels(apiKey);
case "native-embedder": case "native-embedder":
return await getNativeEmbedderModels(); return await getNativeEmbedderModels();
case "cohere-embedder": case "cohere-embedder":
@ -798,6 +801,29 @@ async function getCohereModels(_apiKey = null, type = "chat") {
return { models, error: null }; return { models, error: null };
} }
async function getZAiModels(_apiKey = null) {
const { OpenAI: OpenAIApi } = require("openai");
const apiKey =
_apiKey === true
? process.env.ZAI_API_KEY
: _apiKey || process.env.ZAI_API_KEY || null;
const openai = new OpenAIApi({
baseURL: "https://api.z.ai/api/paas/v4",
apiKey,
});
const models = await openai.models
.list()
.then((results) => results.data)
.catch((e) => {
console.error(`Z.AI:listModels`, e.message);
return [];
});
// Api Key was successful so lets save it for future uses
if (models.length > 0 && !!apiKey) process.env.ZAI_API_KEY = apiKey;
return { models, error: null };
}
module.exports = { module.exports = {
getCustomModels, getCustomModels,
SUPPORT_CUSTOM_MODELS, SUPPORT_CUSTOM_MODELS,

View File

@ -222,6 +222,9 @@ function getLLMProvider({ provider = null, model = null } = {}) {
case "foundry": case "foundry":
const { FoundryLLM } = require("../AiProviders/foundry"); const { FoundryLLM } = require("../AiProviders/foundry");
return new FoundryLLM(embedder, model); return new FoundryLLM(embedder, model);
case "zai":
const { ZAiLLM } = require("../AiProviders/zai");
return new ZAiLLM(embedder, model);
default: default:
throw new Error( throw new Error(
`ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}` `ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}`
@ -378,6 +381,9 @@ function getLLMProviderClass({ provider = null } = {}) {
case "foundry": case "foundry":
const { FoundryLLM } = require("../AiProviders/foundry"); const { FoundryLLM } = require("../AiProviders/foundry");
return FoundryLLM; return FoundryLLM;
case "zai":
const { ZAiLLM } = require("../AiProviders/zai");
return ZAiLLM;
default: default:
return null; return null;
} }
@ -450,6 +456,8 @@ function getBaseLLMProviderModel({ provider = null } = {}) {
return process.env.COMETAPI_LLM_MODEL_PREF; return process.env.COMETAPI_LLM_MODEL_PREF;
case "foundry": case "foundry":
return process.env.FOUNDRY_MODEL_PREF; return process.env.FOUNDRY_MODEL_PREF;
case "zai":
return process.env.ZAI_MODEL_PREF;
default: default:
return null; return null;
} }

View File

@ -752,6 +752,16 @@ const KEY_MAPPING = {
envKey: "COMETAPI_LLM_TIMEOUT_MS", envKey: "COMETAPI_LLM_TIMEOUT_MS",
checks: [], checks: [],
}, },
// Z.AI Options
ZAiApiKey: {
envKey: "ZAI_API_KEY",
checks: [isNotEmpty],
},
ZAiModelPref: {
envKey: "ZAI_MODEL_PREF",
checks: [isNotEmpty],
},
}; };
function isNotEmpty(input = "") { function isNotEmpty(input = "") {
@ -863,6 +873,7 @@ function supportedLLM(input = "") {
"moonshotai", "moonshotai",
"cometapi", "cometapi",
"foundry", "foundry",
"zai",
].includes(input); ].includes(input);
return validSelection ? null : `${input} is not a valid LLM provider.`; return validSelection ? null : `${input} is not a valid LLM provider.`;
} }