θΏ™ζ˜―indexlocζδΎ›ηš„ζœεŠ‘οΌŒδΈθ¦θΎ“ε…₯任何密码
Skip to content

Jan AI LLM/agent provider #4183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace
- [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/)
- [Jan AI](https://jan.ai/)

**Embedder models:**

Expand Down
6 changes: 6 additions & 0 deletions docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ GID='1000'
# MOONSHOT_AI_API_KEY='your-moonshot-api-key-here'
# MOONSHOT_AI_MODEL_PREF='moonshot-v1-32k'

# LLM_PROVIDER='janai'
# JAN_AI_API_KEY='your-jan-ai-api-key-here'
# JAN_AI_BASE_PATH='http://127.0.0.1:1337/v1'
# JAN_AI_MODEL_PREF='gemma3:1b'
# JAN_AI_MODEL_TOKEN_LIMIT=4096

###########################################
######## Embedding API SElECTION ##########
###########################################
Expand Down
193 changes: 193 additions & 0 deletions frontend/src/components/LLMSelection/JanAiOptions/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { useState, useEffect } from "react";
import System from "@/models/system";
import { CaretDown, CaretUp } from "@phosphor-icons/react";
import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery";
import { JAN_AI_COMMON_URLS } from "@/utils/constants";

export default function JanAiOptions({ settings }) {
const [inputValue, setInputValue] = useState(settings?.JanAiApiKey);
const [apiKey, setApiKey] = useState(settings?.JanAiApiKey);

const {
basePath,
basePathValue,
showAdvancedControls,
setShowAdvancedControls,
} = useProviderEndpointAutoDiscovery({
provider: "janai",
// Falls back to first common URL if no base path is set to prevent auto-detect
// from running with no API key (Jan AI always requires an API key)
initialBasePath: settings?.JanAiBasePath || JAN_AI_COMMON_URLS[0],
initialAuthToken: apiKey,
ENDPOINTS: [JAN_AI_COMMON_URLS],
});

return (
<div className="w-full flex flex-col gap-y-7">
<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">
API Key
</label>
<input
type="password"
name="JanAiApiKey"
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="Jan AI API Key"
defaultValue={settings?.JanAiApiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
onChange={(e) => setInputValue(e.target.value)}
onBlur={() => setApiKey(inputValue)}
/>
</div>
{!settings?.credentialsOnly && (
<JanAiModelSelection
settings={settings}
apiKey={apiKey}
basePath={basePath.value}
/>
)}
</div>

<div className="flex justify-start mt-4">
<button
onClick={(e) => {
e.preventDefault();
setShowAdvancedControls(!showAdvancedControls);
}}
className="border-none text-theme-text-primary hover:text-theme-text-secondary flex items-center text-sm"
>
{showAdvancedControls ? "Hide" : "Show"} Manual Endpoint Input
{showAdvancedControls ? (
<CaretUp size={14} className="ml-1" />
) : (
<CaretDown size={14} className="ml-1" />
)}
</button>
</div>

<div hidden={!showAdvancedControls}>
<div className="w-full flex items-start gap-4">
<div className="flex flex-col w-60">
<div className="flex justify-between items-center mb-2">
<label className="text-white text-sm font-semibold">
Jan AI Base URL
</label>
</div>
<input
type="url"
name="JanAiBasePath"
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="http://127.0.0.1:1337/v1"
value={basePathValue.value}
required={true}
autoComplete="off"
spellCheck={false}
onChange={basePath.onChange}
onBlur={basePath.onBlur}
/>
<p className="text-xs leading-[18px] font-base text-white text-opacity-60 mt-2">
Enter the URL where Jan AI is running.
</p>
</div>
</div>
</div>
</div>
);
}

function JanAiModelSelection({ apiKey, settings, basePath }) {
const [models, setModels] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedModel, setSelectedModel] = useState(
settings?.JanAiModelPref || ""
);

useEffect(() => {
async function findCustomModels() {
if (!apiKey || !basePath) {
setModels([]);
setLoading(false);
return;
}

try {
setLoading(true);
const { models } = await System.customModels("janai", apiKey, basePath);
setModels(models || []);

// If no model is selected and we have models, select the first one
if (!selectedModel && models?.length > 0) {
setSelectedModel(models[0].id);
}
} catch (error) {
console.error("Failed to fetch custom models:", error);
setModels([]);
} finally {
setLoading(false);
}
}
findCustomModels();
}, [apiKey, basePath]);

if (!apiKey || !basePath) {
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="JanAiModelPref"
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}>
-- Enter API key and Base URL --
</option>
</select>
</div>
);
}

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="JanAiModelPref"
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>
</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="JanAiModelPref"
required={true}
value={selectedModel}
onChange={(e) => setSelectedModel(e.target.value)}
className="border-none bg-theme-settings-input-bg border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
{models.map((model) => (
<option key={model.id} value={model.id}>
{model.id}
</option>
))}
</select>
</div>
);
}
Binary file added frontend/src/media/llmprovider/janai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions frontend/src/pages/GeneralSettings/LLMPreference/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import XAILogo from "@/media/llmprovider/xai.png";
import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
import PPIOLogo from "@/media/llmprovider/ppio.png";
import DellProAiStudioLogo from "@/media/llmprovider/dpais.png";
import JanAiLogo from "@/media/llmprovider/janai.png";
import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";

import PreLoader from "@/components/Preloader";
Expand Down Expand Up @@ -62,6 +63,7 @@ import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions";
import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions";
import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions";
import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions";
import JanAiOptions from "@/components/LLMSelection/JanAiOptions";
import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions";

import LLMItem from "@/components/LLMSelection/LLMItem";
Expand Down Expand Up @@ -295,6 +297,14 @@ export const AVAILABLE_LLM_PROVIDERS = [
description: "A unified API of AI services from leading providers",
requiredConfig: ["ApipieLLMApiKey", "ApipieLLMModelPref"],
},
{
name: "Jan AI",
value: "janai",
logo: JanAiLogo,
options: (settings) => <JanAiOptions settings={settings} />,
description: "Run Jan AI's local LLMs.",
requiredConfig: ["JanAiApiKey"],
},
{
name: "Moonshot AI",
value: "moonshotai",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png";
import PPIOLogo from "@/media/llmprovider/ppio.png";
import PGVectorLogo from "@/media/vectordbs/pgvector.png";
import DPAISLogo from "@/media/llmprovider/dpais.png";
import JanAiLogo from "@/media/llmprovider/janai.png";
import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";

import React, { useState, useEffect } from "react";
Expand Down Expand Up @@ -244,6 +245,14 @@ export const LLM_SELECTION_PRIVACY = {
],
logo: DPAISLogo,
},
janai: {
name: "Jan AI",
description: [
"Your chats stay local and are not used for training",
"Your prompts and document text stay on your local machine",
],
logo: JanAiLogo,
},
moonshotai: {
name: "Moonshot AI",
description: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png";
import CohereLogo from "@/media/llmprovider/cohere.png";
import PPIOLogo from "@/media/llmprovider/ppio.png";
import DellProAiStudioLogo from "@/media/llmprovider/dpais.png";
import JanAiLogo from "@/media/llmprovider/janai.png";
import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png";

import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
Expand Down Expand Up @@ -56,6 +57,7 @@ import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions";
import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions";
import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions";
import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions";
import JanAiOptions from "@/components/LLMSelection/JanAiOptions";
import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions";

import LLMItem from "@/components/LLMSelection/LLMItem";
Expand Down Expand Up @@ -265,6 +267,13 @@ const LLMS = [
options: (settings) => <XAILLMOptions settings={settings} />,
description: "Run xAI's powerful LLMs like Grok-2 and more.",
},
{
name: "Jan AI",
value: "janai",
logo: JanAiLogo,
options: (settings) => <JanAiOptions settings={settings} />,
description: "Run models from local Jan AI server.",
},
{
name: "Moonshot AI",
value: "moonshotai",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const ENABLED_PROVIDERS = [
"xai",
"nvidia-nim",
"gemini",
"janai",
"moonshotai",
// TODO: More agent support.
// "cohere", // Has tool calling and will need to build explicit support
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export const NVIDIA_NIM_COMMON_URLS = [
"http://172.17.0.1:8000/v1/version",
];

export const JAN_AI_COMMON_URLS = [
"http://127.0.0.1:1337/v1",
"http://localhost:1337/v1",
"http://host.docker.internal:1337/v1",
"http://172.17.0.1:1337/v1",
];

export function fullApiUrl() {
if (API_BASE !== "/api") return API_BASE;
return `${window.location.origin}/api`;
Expand Down
6 changes: 6 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long.
# MOONSHOT_AI_API_KEY='your-moonshot-api-key-here'
# MOONSHOT_AI_MODEL_PREF='moonshot-v1-32k'

# LLM_PROVIDER='janai'
# JAN_AI_API_KEY='your-jan-ai-api-key-here'
# JAN_AI_BASE_PATH='http://127.0.0.1:1337/v1'
# JAN_AI_MODEL_PREF='gemma3:1b'
# JAN_AI_MODEL_TOKEN_LIMIT=4096

###########################################
######## Embedding API SElECTION ##########
###########################################
Expand Down
3 changes: 3 additions & 0 deletions server/endpoints/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ function getModelTag() {
case "gemini":
model = process.env.GEMINI_LLM_MODEL_PREF;
break;
case "janai":
model = process.env.JAN_AI_MODEL_PREF;
break;
case "moonshotai":
model = process.env.MOONSHOT_AI_MODEL_PREF;
break;
Expand Down
6 changes: 6 additions & 0 deletions server/models/systemSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,12 @@ const SystemSettings = {
NvidiaNimLLMModelPref: process.env.NVIDIA_NIM_LLM_MODEL_PREF,
NvidiaNimLLMTokenLimit: process.env.NVIDIA_NIM_LLM_MODEL_TOKEN_LIMIT,

// Jan AI Keys
JanAiApiKey: !!process.env.JAN_AI_API_KEY,
JanAiBasePath: process.env.JAN_AI_BASE_PATH,
JanAiModelPref: process.env.JAN_AI_MODEL_PREF,
JanAiModelTokenLimit: process.env.JAN_AI_MODEL_TOKEN_LIMIT,

// PPIO API keys
PPIOApiKey: !!process.env.PPIO_API_KEY,
PPIOModelPref: process.env.PPIO_MODEL_PREF,
Expand Down
Loading