From 49c29fb968ea7b9c39b140adf4d4bf3ae031e481 Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Thu, 20 Nov 2025 15:57:03 -0800 Subject: [PATCH] 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 --- README.md | 1 + docker/.env.example | 4 + .../LLMSelection/ZAiLLMOptions/index.jsx | 114 +++++++++++ frontend/src/media/llmprovider/zai.png | Bin 0 -> 8024 bytes .../GeneralSettings/LLMPreference/index.jsx | 10 + .../Steps/DataHandling/index.jsx | 10 + .../Steps/LLMPreference/index.jsx | 9 + .../AgentConfig/AgentLLMSelection/index.jsx | 1 + locales/README.fa-IR.md | 1 + locales/README.ja-JP.md | 2 + locales/README.tr-TR.md | 145 +++++++------- locales/README.zh-CN.md | 1 + server/.env.example | 4 + server/endpoints/utils.js | 3 + server/models/systemSettings.js | 4 + server/utils/AiProviders/modelMap/index.js | 16 +- server/utils/AiProviders/ollama/index.js | 5 +- server/utils/AiProviders/zai/index.js | 179 ++++++++++++++++++ server/utils/agents/aibitat/index.js | 2 + .../agents/aibitat/providers/ai-provider.js | 8 + .../utils/agents/aibitat/providers/index.js | 2 + server/utils/agents/aibitat/providers/zai.js | 88 +++++++++ server/utils/agents/index.js | 6 + server/utils/helpers/customModels.js | 26 +++ server/utils/helpers/index.js | 8 + server/utils/helpers/updateENV.js | 11 ++ 26 files changed, 581 insertions(+), 79 deletions(-) create mode 100644 frontend/src/components/LLMSelection/ZAiLLMOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/zai.png create mode 100644 server/utils/AiProviders/zai/index.js create mode 100644 server/utils/agents/aibitat/providers/zai.js diff --git a/README.md b/README.md index 71088855..8221d34f 100644 --- a/README.md +++ b/README.md @@ -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) - [Apipie](https://apipie.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) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) - [Moonshot AI](https://www.moonshot.ai/) diff --git a/docker/.env.example b/docker/.env.example index 90d9c96e..35731600 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -131,6 +131,10 @@ GID='1000' # XAI_LLM_API_KEY='xai-your-api-key-here' # 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' # NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000' # NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct' diff --git a/frontend/src/components/LLMSelection/ZAiLLMOptions/index.jsx b/frontend/src/components/LLMSelection/ZAiLLMOptions/index.jsx new file mode 100644 index 00000000..c31826f4 --- /dev/null +++ b/frontend/src/components/LLMSelection/ZAiLLMOptions/index.jsx @@ -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 ( +
+
+ + setInputValue(e.target.value)} + onBlur={() => setApiKey(inputValue)} + /> +
+ + {!settings?.credentialsOnly && ( + + )} +
+ ); +} + +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 ( +
+ + +

+ Enter a valid API key to view all available models for your account. +

+
+ ); + } + + return ( +
+ + +

+ Select the Z.AI model you want to use for your conversations. +

+
+ ); +} diff --git a/frontend/src/media/llmprovider/zai.png b/frontend/src/media/llmprovider/zai.png new file mode 100644 index 0000000000000000000000000000000000000000..e83c6da4c0a5cc078c76200285517120773d6e46 GIT binary patch literal 8024 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

, + description: "Run Z.AI's powerful GLM models.", + requiredConfig: ["ZAiApiKey"], + }, { name: "Generic OpenAI", value: "generic-openai", diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index b12979a8..fbb767c8 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -25,6 +25,7 @@ import AWSBedrockLogo from "@/media/llmprovider/bedrock.png"; import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import APIPieLogo from "@/media/llmprovider/apipie.png"; import XAILogo from "@/media/llmprovider/xai.png"; +import ZAiLogo from "@/media/llmprovider/zai.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import ZillizLogo from "@/media/vectordbs/zilliz.png"; import AstraDBLogo from "@/media/vectordbs/astraDB.png"; @@ -231,6 +232,15 @@ export const LLM_SELECTION_PRIVACY = { ], 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: { name: "PPIO", description: [ diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index 7a16985f..ed4b02f7 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -23,6 +23,7 @@ import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import APIPieLogo from "@/media/llmprovider/apipie.png"; import NovitaLogo from "@/media/llmprovider/novita.png"; import XAILogo from "@/media/llmprovider/xai.png"; +import ZAiLogo from "@/media/llmprovider/zai.png"; import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import CohereLogo from "@/media/llmprovider/cohere.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 NovitaLLMOptions from "@/components/LLMSelection/NovitaLLMOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; +import ZAiLLMOptions from "@/components/LLMSelection/ZAiLLMOptions"; import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions"; import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions"; @@ -267,6 +269,13 @@ const LLMS = [ options: (settings) => , description: "Run xAI's powerful LLMs like Grok-2 and more.", }, + { + name: "Z.AI", + value: "zai", + logo: ZAiLogo, + options: (settings) => , + description: "Run Z.AI's powerful GLM models.", + }, { name: "Moonshot AI", value: "moonshotai", diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx index 467bae85..a1309203 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx @@ -34,6 +34,7 @@ const ENABLED_PROVIDERS = [ "moonshotai", "cometapi", "foundry", + "zai", // TODO: More agent 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. diff --git a/locales/README.fa-IR.md b/locales/README.fa-IR.md index c28abf64..342aed2d 100644 --- a/locales/README.fa-IR.md +++ b/locales/README.fa-IR.md @@ -102,6 +102,7 @@ AnythingLLM اسناد شما را به اشیایی به نام `workspaces` ت - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Apipie](https://apipie.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) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) diff --git a/locales/README.ja-JP.md b/locales/README.ja-JP.md index d6fef0fa..afb4e5af 100644 --- a/locales/README.ja-JP.md +++ b/locales/README.ja-JP.md @@ -90,6 +90,8 @@ AnythingLLMは、ドキュメントを`ワークスペース`と呼ばれるオ - [Groq](https://groq.com/) - [Cohere](https://cohere.com/) - [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) - [CometAPI (チャットモデル)](https://api.cometapi.com/) diff --git a/locales/README.tr-TR.md b/locales/README.tr-TR.md index 9f539779..1743db31 100644 --- a/locales/README.tr-TR.md +++ b/locales/README.tr-TR.md @@ -33,9 +33,9 @@ Belgelerinizle sohbet edin, yapay zeka ajanlarını kullanın, son derece özell

-

-👉 Masaüstü için AnythingLLM (Mac, Windows ve Linux)! Şimdi İndir -

+

+👉 Masaüstü için AnythingLLM (Mac, Windows ve Linux)! Şimdi İndir +

Herhangi bir belgeyi, kaynağı veya içeriği sohbet sırasında herhangi bir büyük dil modelinin referans olarak kullanabileceği bir bağlama dönüştürmenizi sağlayan tam kapsamlı bir uygulama. Bu uygulama, kullanmak istediğiniz LLM veya Vektör Veritabanını seçmenize olanak tanırken, çok kullanıcılı yönetim ve yetkilendirme desteği de sunar. @@ -48,29 +48,29 @@ Herhangi bir belgeyi, kaynağı veya içeriği sohbet sırasında herhangi bir b -### Ürün Genel Bakışı +### Ürün Genel Bakışı -AnythingLLM, ticari hazır büyük dil modellerini veya popüler açık kaynak LLM'leri ve vektör veritabanı çözümlerini kullanarak, hiçbir ödün vermeden özel bir ChatGPT oluşturmanıza olanak tanıyan tam kapsamlı bir uygulamadır. Bu uygulamayı yerel olarak çalıştırabilir veya uzaktan barındırarak sağladığınız belgelerle akıllı sohbetler gerçekleştirebilirsiniz. +AnythingLLM, ticari hazır büyük dil modellerini veya popüler açık kaynak LLM'leri ve vektör veritabanı çözümlerini kullanarak, hiçbir ödün vermeden özel bir ChatGPT oluşturmanıza olanak tanıyan tam kapsamlı bir uygulamadır. Bu uygulamayı yerel olarak çalıştırabilir veya uzaktan barındırarak sağladığınız belgelerle akıllı sohbetler gerçekleştirebilirsiniz. -AnythingLLM, belgelerinizi **"çalışma alanları" (workspaces)** adı verilen nesnelere ayırır. Bir çalışma alanı, bir sohbet dizisi gibi çalışır ancak belgelerinizi kapsülleyen bir yapı sunar. Çalışma alanları belgeleri paylaşabilir, ancak birbirleriyle iletişim kurmaz, böylece her çalışma alanının bağlamını temiz tutabilirsiniz. +AnythingLLM, belgelerinizi **"çalışma alanları" (workspaces)** adı verilen nesnelere ayırır. Bir çalışma alanı, bir sohbet dizisi gibi çalışır ancak belgelerinizi kapsülleyen bir yapı sunar. Çalışma alanları belgeleri paylaşabilir, ancak birbirleriyle iletişim kurmaz, böylece her çalışma alanının bağlamını temiz tutabilirsiniz. -## AnythingLLM’in Harika Özellikleri +## AnythingLLM’in Harika Özellikleri -- 🆕 [**Özel Yapay Zeka Ajanları**](https://docs.anythingllm.com/agent/custom/introduction) -- 🆕 [**Kod yazmadan AI Ajanı oluşturma aracı**](https://docs.anythingllm.com/agent-flows/overview) -- 🖼️ **Çoklu-mod desteği (hem kapalı kaynak hem de açık kaynak LLM'ler!)** -- 👤 Çok kullanıcılı destek ve yetkilendirme _(Yalnızca Docker sürümünde)_ -- 🦾 Çalışma alanı içinde ajanlar (web'de gezinme vb.) -- 💬 [Web sitenize gömülebilir özel sohbet aracı](https://github.com/Mintplex-Labs/anythingllm-embed/blob/main/README.md) _(Yalnızca Docker sürümünde)_ -- 📖 Çoklu belge türü desteği (PDF, TXT, DOCX vb.) -- Sade ve kullanışlı sohbet arayüzü, sürükle-bırak özelliği ve net kaynak gösterimi. -- %100 bulut konuşlandırmaya hazır. -- [Tüm popüler kapalı ve açık kaynak LLM sağlayıcılarıyla](#supported-llms-embedder-models-speech-models-and-vector-databases) uyumlu. -- Büyük belgeleri yönetirken zaman ve maliyet tasarrufu sağlayan dahili optimizasyonlar. -- Özel entegrasyonlar için tam kapsamlı Geliştirici API’si. +- 🆕 [**Özel Yapay Zeka Ajanları**](https://docs.anythingllm.com/agent/custom/introduction) +- 🆕 [**Kod yazmadan AI Ajanı oluşturma aracı**](https://docs.anythingllm.com/agent-flows/overview) +- 🖼️ **Çoklu-mod desteği (hem kapalı kaynak hem de açık kaynak LLM'ler!)** +- 👤 Çok kullanıcılı destek ve yetkilendirme _(Yalnızca Docker sürümünde)_ +- 🦾 Çalışma alanı içinde ajanlar (web'de gezinme vb.) +- 💬 [Web sitenize gömülebilir özel sohbet aracı](https://github.com/Mintplex-Labs/anythingllm-embed/blob/main/README.md) _(Yalnızca Docker sürümünde)_ +- 📖 Çoklu belge türü desteği (PDF, TXT, DOCX vb.) +- Sade ve kullanışlı sohbet arayüzü, sürükle-bırak özelliği ve net kaynak gösterimi. +- %100 bulut konuşlandırmaya hazır. +- [Tüm popüler kapalı ve açık kaynak LLM sağlayıcılarıyla](#supported-llms-embedder-models-speech-models-and-vector-databases) uyumlu. +- Büyük belgeleri yönetirken zaman ve maliyet tasarrufu sağlayan dahili optimizasyonlar. +- Özel entegrasyonlar için tam kapsamlı Geliştirici API’si. - Ve çok daha fazlası... Kurup keşfedin! -### Desteklenen LLM'ler, Embedding Modelleri, Konuşma Modelleri ve Vektör Veritabanları +### Desteklenen LLM'ler, Embedding Modelleri, Konuşma Modelleri ve Vektör Veritabanları **Büyük Dil Modelleri (LLMs):** @@ -99,6 +99,7 @@ AnythingLLM, belgelerinizi **"çalışma alanları" (workspaces)** adı verilen - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Apipie](https://apipie.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) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) @@ -141,18 +142,18 @@ AnythingLLM, belgelerinizi **"çalışma alanları" (workspaces)** adı verilen - [Milvus](https://milvus.io) - [Zilliz](https://zilliz.com) -### Teknik Genel Bakış +### Teknik Genel Bakış -Bu monorepo üç ana bölümden oluşmaktadır: +Bu monorepo üç ana bölümden oluşmaktadır: -- **`frontend`**: ViteJS + React tabanlı bir ön yüz, LLM'in kullanabileceği tüm içeriği kolayca oluşturup yönetmenizi sağlar. -- **`server`**: NodeJS ve Express tabanlı bir sunucu, tüm etkileşimleri yönetir ve vektör veritabanı işlemleri ile LLM entegrasyonlarını gerçekleştirir. -- **`collector`**: Kullanıcı arayüzünden gelen belgeleri işleyen ve ayrıştıran NodeJS Express tabanlı bir sunucu. -- **`docker`**: Docker kurulum talimatları, derleme süreci ve kaynak koddan nasıl derleneceğine dair bilgiler içerir. -- **`embed`**: [Web gömme widget’ı](https://github.com/Mintplex-Labs/anythingllm-embed) oluşturma ve entegrasyonu için alt modül. +- **`frontend`**: ViteJS + React tabanlı bir ön yüz, LLM'in kullanabileceği tüm içeriği kolayca oluşturup yönetmenizi sağlar. +- **`server`**: NodeJS ve Express tabanlı bir sunucu, tüm etkileşimleri yönetir ve vektör veritabanı işlemleri ile LLM entegrasyonlarını gerçekleştirir. +- **`collector`**: Kullanıcı arayüzünden gelen belgeleri işleyen ve ayrıştıran NodeJS Express tabanlı bir sunucu. +- **`docker`**: Docker kurulum talimatları, derleme süreci ve kaynak koddan nasıl derleneceğine dair bilgiler içerir. +- **`embed`**: [Web gömme widget’ı](https://github.com/Mintplex-Labs/anythingllm-embed) oluşturma ve entegrasyonu için alt modül. - **`browser-extension`**: [Chrome tarayıcı eklentisi](https://github.com/Mintplex-Labs/anythingllm-extension) için alt modül. -## 🛳 Kendi Sunucunuzda Barındırma +## 🛳 Kendi Sunucunuzda Barındırma Mintplex Labs ve topluluk, AnythingLLM'i yerel olarak çalıştırmak için çeşitli dağıtım yöntemleri, betikler ve şablonlar sunmaktadır. Aşağıdaki tabloya göz atarak tercih ettiğiniz ortamda nasıl dağıtım yapabileceğinizi öğrenebilir veya otomatik dağıtım seçeneklerini keşfedebilirsiniz. | Docker | AWS | GCP | Digital Ocean | Render.com | @@ -163,86 +164,86 @@ Mintplex Labs ve topluluk, AnythingLLM'i yerel olarak çalıştırmak için çe | --- | --- | --- | | [![Deploy on Railway][railway-btn]][railway-deploy] | [![Deploy on RepoCloud][repocloud-btn]][repocloud-deploy] | [![Deploy on Elestio][elestio-btn]][elestio-deploy] | -[veya Docker kullanmadan üretim ortamında AnythingLLM kurun →](../BARE_METAL.md) +[veya Docker kullanmadan üretim ortamında AnythingLLM kurun →](../BARE_METAL.md) -## Geliştirme İçin Kurulum +## Geliştirme İçin Kurulum -- `yarn setup` → Uygulamanın her bileşeni için gerekli `.env` dosyalarını oluşturur (repo’nun kök dizininden çalıştırılmalıdır). - - Devam etmeden önce bu dosyaları doldurun. **Özellikle `server/.env.development` dosyasının doldurulduğundan emin olun**, aksi takdirde sistem düzgün çalışmaz. -- `yarn dev:server` → Sunucuyu yerel olarak başlatır (repo’nun kök dizininden çalıştırılmalıdır). -- `yarn dev:frontend` → Ön yüzü yerel olarak çalıştırır (repo’nun kök dizininden çalıştırılmalıdır). -- `yarn dev:collector` → Belge toplayıcıyı çalıştırır (repo’nun kök dizininden çalıştırılmalıdır). +- `yarn setup` → Uygulamanın her bileşeni için gerekli `.env` dosyalarını oluşturur (repo’nun kök dizininden çalıştırılmalıdır). + - Devam etmeden önce bu dosyaları doldurun. **Özellikle `server/.env.development` dosyasının doldurulduğundan emin olun**, aksi takdirde sistem düzgün çalışmaz. +- `yarn dev:server` → Sunucuyu yerel olarak başlatır (repo’nun kök dizininden çalıştırılmalıdır). +- `yarn dev:frontend` → Ön yüzü yerel olarak çalıştırır (repo’nun kök dizininden çalıştırılmalıdır). +- `yarn dev:collector` → Belge toplayıcıyı çalıştırır (repo’nun kök dizininden çalıştırılmalıdır). -[Belgeler hakkında bilgi edinin](../server/storage/documents/DOCUMENTS.md) +[Belgeler hakkında bilgi edinin](../server/storage/documents/DOCUMENTS.md) -[Vektör önbellekleme hakkında bilgi edinin](../server/storage/vector-cache/VECTOR_CACHE.md) +[Vektör önbellekleme hakkında bilgi edinin](../server/storage/vector-cache/VECTOR_CACHE.md) -## Harici Uygulamalar ve Entegrasyonlar +## Harici Uygulamalar ve Entegrasyonlar -_Bu uygulamalar Mintplex Labs tarafından yönetilmemektedir, ancak AnythingLLM ile uyumludur. Burada listelenmeleri bir onay anlamına gelmez._ +_Bu uygulamalar Mintplex Labs tarafından yönetilmemektedir, ancak AnythingLLM ile uyumludur. Burada listelenmeleri bir onay anlamına gelmez._ -- [Midori AI Alt Sistem Yöneticisi](https://io.midori-ai.xyz/subsystem/anythingllm/) - Docker konteyner teknolojisini kullanarak yapay zeka sistemlerini verimli bir şekilde dağıtmanın pratik bir yolu. -- [Coolify](https://coolify.io/docs/services/anythingllm/) - Tek tıklamayla AnythingLLM dağıtımı yapmanıza olanak tanır. +- [Midori AI Alt Sistem Yöneticisi](https://io.midori-ai.xyz/subsystem/anythingllm/) - Docker konteyner teknolojisini kullanarak yapay zeka sistemlerini verimli bir şekilde dağıtmanın pratik bir yolu. +- [Coolify](https://coolify.io/docs/services/anythingllm/) - Tek tıklamayla AnythingLLM dağıtımı yapmanıza olanak tanır. - [GPTLocalhost for Microsoft Word](https://gptlocalhost.com/demo/) - AnythingLLM’i Microsoft Word içinde kullanmanıza olanak tanıyan yerel bir Word eklentisi. -## Telemetri ve Gizlilik +## Telemetri ve Gizlilik -Mintplex Labs Inc. tarafından geliştirilen AnythingLLM, anonim kullanım bilgilerini toplayan bir telemetri özelliği içermektedir. +Mintplex Labs Inc. tarafından geliştirilen AnythingLLM, anonim kullanım bilgilerini toplayan bir telemetri özelliği içermektedir. -
-AnythingLLM için Telemetri ve Gizlilik hakkında daha fazla bilgi +
+AnythingLLM için Telemetri ve Gizlilik hakkında daha fazla bilgi -### Neden? +### Neden? -Bu bilgileri, AnythingLLM’in nasıl kullanıldığını anlamak, yeni özellikler ve hata düzeltmelerine öncelik vermek ve uygulamanın performansını ve kararlılığını iyileştirmek için kullanıyoruz. +Bu bilgileri, AnythingLLM’in nasıl kullanıldığını anlamak, yeni özellikler ve hata düzeltmelerine öncelik vermek ve uygulamanın performansını ve kararlılığını iyileştirmek için kullanıyoruz. -### Telemetriden Çıkış Yapma (Opt-Out) +### Telemetriden Çıkış Yapma (Opt-Out) -Sunucu veya Docker `.env` ayarlarında `DISABLE_TELEMETRY` değerini "true" olarak ayarlayarak telemetriyi devre dışı bırakabilirsiniz. Ayrıca, uygulama içinde **Kenar Çubuğu > Gizlilik** bölümüne giderek de bu özelliği kapatabilirsiniz. +Sunucu veya Docker `.env` ayarlarında `DISABLE_TELEMETRY` değerini "true" olarak ayarlayarak telemetriyi devre dışı bırakabilirsiniz. Ayrıca, uygulama içinde **Kenar Çubuğu > Gizlilik** bölümüne giderek de bu özelliği kapatabilirsiniz. -### Hangi Verileri Açıkça Takip Ediyoruz? +### Hangi Verileri Açıkça Takip Ediyoruz? -Yalnızca ürün ve yol haritası kararlarını almamıza yardımcı olacak kullanım detaylarını takip ediyoruz: +Yalnızca ürün ve yol haritası kararlarını almamıza yardımcı olacak kullanım detaylarını takip ediyoruz: -- Kurulum türü (Docker veya Masaüstü) -- Bir belgenin eklenme veya kaldırılma olayı. **Belgenin içeriği hakkında hiçbir bilgi toplanmaz**, yalnızca olayın gerçekleştiği kaydedilir. Bu, kullanım sıklığını anlamamıza yardımcı olur. -- Kullanılan vektör veritabanı türü. Hangi sağlayıcının daha çok tercih edildiğini belirlemek için bu bilgiyi topluyoruz. -- Kullanılan LLM türü. En popüler modelleri belirleyerek bu sağlayıcılara öncelik verebilmemizi sağlar. -- Sohbet başlatılması. Bu en sık gerçekleşen "olay" olup, projenin günlük etkinliği hakkında genel bir fikir edinmemize yardımcı olur. **Yalnızca olay kaydedilir, sohbetin içeriği veya doğası hakkında hiçbir bilgi toplanmaz.** +- Kurulum türü (Docker veya Masaüstü) +- Bir belgenin eklenme veya kaldırılma olayı. **Belgenin içeriği hakkında hiçbir bilgi toplanmaz**, yalnızca olayın gerçekleştiği kaydedilir. Bu, kullanım sıklığını anlamamıza yardımcı olur. +- Kullanılan vektör veritabanı türü. Hangi sağlayıcının daha çok tercih edildiğini belirlemek için bu bilgiyi topluyoruz. +- Kullanılan LLM türü. En popüler modelleri belirleyerek bu sağlayıcılara öncelik verebilmemizi sağlar. +- Sohbet başlatılması. Bu en sık gerçekleşen "olay" olup, projenin günlük etkinliği hakkında genel bir fikir edinmemize yardımcı olur. **Yalnızca olay kaydedilir, sohbetin içeriği veya doğası hakkında hiçbir bilgi toplanmaz.** -Bu verileri doğrulamak için kod içinde **`Telemetry.sendTelemetry` çağrılarını** inceleyebilirsiniz. Ayrıca, bu olaylar günlük kaydına yazıldığı için hangi verilerin gönderildiğini görebilirsiniz (eğer etkinleştirilmişse). **IP adresi veya diğer tanımlayıcı bilgiler toplanmaz.** Telemetri sağlayıcısı, açık kaynaklı bir telemetri toplama hizmeti olan [PostHog](https://posthog.com/)‘dur. +Bu verileri doğrulamak için kod içinde **`Telemetry.sendTelemetry` çağrılarını** inceleyebilirsiniz. Ayrıca, bu olaylar günlük kaydına yazıldığı için hangi verilerin gönderildiğini görebilirsiniz (eğer etkinleştirilmişse). **IP adresi veya diğer tanımlayıcı bilgiler toplanmaz.** Telemetri sağlayıcısı, açık kaynaklı bir telemetri toplama hizmeti olan [PostHog](https://posthog.com/)‘dur. [Kaynak kodda tüm telemetri olaylarını görüntüle](https://github.com/search?q=repo%3AMintplex-Labs%2Fanything-llm%20.sendTelemetry\(&type=code)
-## 👋 Katkıda Bulunma +## 👋 Katkıda Bulunma -- Bir **issue** oluşturun. -- `-` formatında bir **PR (Pull Request)** oluşturun. -- Çekirdek ekipten **LGTM (Looks Good To Me)** onayı alın. +- Bir **issue** oluşturun. +- `-` formatında bir **PR (Pull Request)** oluşturun. +- Çekirdek ekipten **LGTM (Looks Good To Me)** onayı alın. -## 🌟 Katkıda Bulunanlar +## 🌟 Katkıda Bulunanlar -[![anythingllm contributors](https://contrib.rocks/image?repo=mintplex-labs/anything-llm)](https://github.com/mintplex-labs/anything-llm/graphs/contributors) +[![anythingllm contributors](https://contrib.rocks/image?repo=mintplex-labs/anything-llm)](https://github.com/mintplex-labs/anything-llm/graphs/contributors) -[![Star History Chart](https://api.star-history.com/svg?repos=mintplex-labs/anything-llm&type=Timeline)](https://star-history.com/#mintplex-labs/anything-llm&Date) +[![Star History Chart](https://api.star-history.com/svg?repos=mintplex-labs/anything-llm&type=Timeline)](https://star-history.com/#mintplex-labs/anything-llm&Date) -## 🔗 Diğer Ürünler +## 🔗 Diğer Ürünler -- **[VectorAdmin][vector-admin]:** Vektör veritabanlarını yönetmek için hepsi bir arada GUI ve araç paketi. -- **[OpenAI Assistant Swarm][assistant-swarm]:** Tüm OpenAI asistanlarınızı tek bir ajan tarafından yönetilen bir yapay zeka ordusuna dönüştürün. +- **[VectorAdmin][vector-admin]:** Vektör veritabanlarını yönetmek için hepsi bir arada GUI ve araç paketi. +- **[OpenAI Assistant Swarm][assistant-swarm]:** Tüm OpenAI asistanlarınızı tek bir ajan tarafından yönetilen bir yapay zeka ordusuna dönüştürün. -
+
-[![][back-to-top]](#readme-top) +[![][back-to-top]](#readme-top) -
+
---- +--- -Telif Hakkı © 2025 [Mintplex Labs][profile-link].
+Telif Hakkı © 2025 [Mintplex Labs][profile-link].
Bu proje [MIT](../LICENSE) lisansı ile lisanslanmıştır. diff --git a/locales/README.zh-CN.md b/locales/README.zh-CN.md index aa328351..c5408dc6 100644 --- a/locales/README.zh-CN.md +++ b/locales/README.zh-CN.md @@ -98,6 +98,7 @@ AnythingLLM将您的文档划分为称为`workspaces` (工作区)的对象。工 - [Text Generation Web UI](https://github.com/oobabooga/text-generation-webui) - [Apipie](https://apipie.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) - [PPIO (聊天模型)](https://ppinfra.com?utm_source=github_anything-llm) - [CometAPI (聊天模型)](https://api.cometapi.com/) diff --git a/server/.env.example b/server/.env.example index 4b5779f5..8b5c2ea8 100644 --- a/server/.env.example +++ b/server/.env.example @@ -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_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' # NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000' # NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct' diff --git a/server/endpoints/utils.js b/server/endpoints/utils.js index 0bb51b83..136245dc 100644 --- a/server/endpoints/utils.js +++ b/server/endpoints/utils.js @@ -145,6 +145,9 @@ function getModelTag() { case "moonshotai": model = process.env.MOONSHOT_AI_MODEL_PREF; break; + case "zai": + model = process.env.ZAI_MODEL_PREF; + break; default: model = "--"; break; diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 935f4a3e..9015257c 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -625,6 +625,10 @@ const SystemSettings = { CometApiLLMApiKey: !!process.env.COMETAPI_LLM_API_KEY, CometApiLLMModelPref: process.env.COMETAPI_LLM_MODEL_PREF, CometApiLLMTimeout: process.env.COMETAPI_LLM_TIMEOUT_MS, + + // Z.AI Keys + ZAiApiKey: !!process.env.ZAI_API_KEY, + ZAiModelPref: process.env.ZAI_MODEL_PREF, }; }, diff --git a/server/utils/AiProviders/modelMap/index.js b/server/utils/AiProviders/modelMap/index.js index 8ff713ee..a8fac8bf 100644 --- a/server/utils/AiProviders/modelMap/index.js +++ b/server/utils/AiProviders/modelMap/index.js @@ -19,6 +19,7 @@ class ContextWindowFinder { xai: "xai", deepseek: "deepseek", 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 remoteUrl = @@ -116,8 +117,9 @@ You can fix this by restarting AnythingLLM so the model map is re-pulled. }); if (!remoteContexWindowMap) return null; - const modelMap = this.#formatModelMap(remoteContexWindowMap); - this.#validateModelMap(modelMap); + const modelMap = this.#validateModelMap( + this.#formatModelMap(remoteContexWindowMap) + ); fs.writeFileSync(this.cacheFilePath, JSON.stringify(modelMap, null, 2)); fs.writeFileSync(this.cacheFileExpiryPath, Date.now().toString()); return modelMap; @@ -139,12 +141,16 @@ You can fix this by restarting AnythingLLM so the model map is re-pulled. // Validate that the context window is a number for (const [model, contextWindow] of Object.entries(models)) { - if (isNaN(contextWindow) || contextWindow <= 0) - throw new Error( - `Invalid model map for ${provider} - context window is not a positive number for model ${model}` + if (isNaN(contextWindow) || contextWindow <= 0) { + this.log( + `${provider}:${model} - context window is not a positive number. Got ${contextWindow}.` ); + delete models[model]; + continue; + } } } + return modelMap; } /** diff --git a/server/utils/AiProviders/ollama/index.js b/server/utils/AiProviders/ollama/index.js index 842bf102..a2c74613 100644 --- a/server/utils/AiProviders/ollama/index.js +++ b/server/utils/AiProviders/ollama/index.js @@ -435,8 +435,9 @@ class OllamaAILLM { type: "textResponseChunk", textResponse: "", 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); stream?.endMeasurement(usage); diff --git a/server/utils/AiProviders/zai/index.js b/server/utils/AiProviders/zai/index.js new file mode 100644 index 00000000..ccbbc535 --- /dev/null +++ b/server/utils/AiProviders/zai/index.js @@ -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, +}; diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js index d4ebe9d6..f6cafb44 100644 --- a/server/utils/agents/aibitat/index.js +++ b/server/utils/agents/aibitat/index.js @@ -964,6 +964,8 @@ ${this.getHistory({ to: route.to }) return new Providers.ApiPieProvider({ model: config.model }); case "xai": return new Providers.XAIProvider({ model: config.model }); + case "zai": + return new Providers.ZAIProvider({ model: config.model }); case "novita": return new Providers.NovitaProvider({ model: config.model }); case "ppio": diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js index c1af28bc..ec9884a1 100644 --- a/server/utils/agents/aibitat/providers/ai-provider.js +++ b/server/utils/agents/aibitat/providers/ai-provider.js @@ -183,6 +183,14 @@ class Provider { apiKey: process.env.XAI_LLM_API_KEY ?? null, ...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": return new ChatOpenAI({ configuration: { diff --git a/server/utils/agents/aibitat/providers/index.js b/server/utils/agents/aibitat/providers/index.js index 8cf2e742..f927c82c 100644 --- a/server/utils/agents/aibitat/providers/index.js +++ b/server/utils/agents/aibitat/providers/index.js @@ -18,6 +18,7 @@ const DeepSeekProvider = require("./deepseek.js"); const LiteLLMProvider = require("./litellm.js"); const ApiPieProvider = require("./apipie.js"); const XAIProvider = require("./xai.js"); +const ZAIProvider = require("./zai.js"); const NovitaProvider = require("./novita.js"); const NvidiaNimProvider = require("./nvidiaNim.js"); const PPIOProvider = require("./ppio.js"); @@ -48,6 +49,7 @@ module.exports = { LiteLLMProvider, ApiPieProvider, XAIProvider, + ZAIProvider, NovitaProvider, CometApiProvider, NvidiaNimProvider, diff --git a/server/utils/agents/aibitat/providers/zai.js b/server/utils/agents/aibitat/providers/zai.js new file mode 100644 index 00000000..98630e27 --- /dev/null +++ b/server/utils/agents/aibitat/providers/zai.js @@ -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; diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index 4ed1ddea..10972594 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -172,6 +172,10 @@ class AgentHandler { if (!process.env.XAI_LLM_API_KEY) throw new Error("xAI API Key must be provided to use agents."); 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": if (!process.env.NOVITA_LLM_API_KEY) 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; case "xai": return process.env.XAI_LLM_MODEL_PREF ?? "grok-beta"; + case "zai": + return process.env.ZAI_MODEL_PREF ?? "glm-4.5"; case "novita": return process.env.NOVITA_LLM_MODEL_PREF ?? "deepseek/deepseek-r1"; case "nvidia-nim": diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index 7a62a67d..5c8d1962 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -38,6 +38,7 @@ const SUPPORT_CUSTOM_MODELS = [ "moonshotai", "foundry", "cohere", + "zai", // Embedding Engines "native-embedder", "cohere-embedder", @@ -100,6 +101,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return await getFoundryModels(basePath); case "cohere": return await getCohereModels(apiKey, "chat"); + case "zai": + return await getZAiModels(apiKey); case "native-embedder": return await getNativeEmbedderModels(); case "cohere-embedder": @@ -798,6 +801,29 @@ async function getCohereModels(_apiKey = null, type = "chat") { 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 = { getCustomModels, SUPPORT_CUSTOM_MODELS, diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index aa4ffc2f..765415c3 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -222,6 +222,9 @@ function getLLMProvider({ provider = null, model = null } = {}) { case "foundry": const { FoundryLLM } = require("../AiProviders/foundry"); return new FoundryLLM(embedder, model); + case "zai": + const { ZAiLLM } = require("../AiProviders/zai"); + return new ZAiLLM(embedder, model); default: throw new Error( `ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}` @@ -378,6 +381,9 @@ function getLLMProviderClass({ provider = null } = {}) { case "foundry": const { FoundryLLM } = require("../AiProviders/foundry"); return FoundryLLM; + case "zai": + const { ZAiLLM } = require("../AiProviders/zai"); + return ZAiLLM; default: return null; } @@ -450,6 +456,8 @@ function getBaseLLMProviderModel({ provider = null } = {}) { return process.env.COMETAPI_LLM_MODEL_PREF; case "foundry": return process.env.FOUNDRY_MODEL_PREF; + case "zai": + return process.env.ZAI_MODEL_PREF; default: return null; } diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index bb4f3a30..be92498a 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -752,6 +752,16 @@ const KEY_MAPPING = { envKey: "COMETAPI_LLM_TIMEOUT_MS", checks: [], }, + + // Z.AI Options + ZAiApiKey: { + envKey: "ZAI_API_KEY", + checks: [isNotEmpty], + }, + ZAiModelPref: { + envKey: "ZAI_MODEL_PREF", + checks: [isNotEmpty], + }, }; function isNotEmpty(input = "") { @@ -863,6 +873,7 @@ function supportedLLM(input = "") { "moonshotai", "cometapi", "foundry", + "zai", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; }