Skip to content
Merged
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
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ db_mode=json
########################################
# LLM Core Settings
########################################
LLM_PROVIDER=gemini # gemini | openai | claude | grok | ollama | openrouter | requesty | cheaperinference | minimax | apiroute
LLM_PROVIDER=gemini # gemini | openai | claude | grok | ollama | openrouter | requesty | cheaperinference | atlascloud | minimax | apiroute
EMB_PROVIDER=openai # openai | gemini | ollama etc.
LLM_TEMP=1
LLM_MAXTOK=16384
Expand Down Expand Up @@ -74,6 +74,16 @@ CHEAPER_INFERENCE_API_KEY=
CHEAPER_INFERENCE_MODEL=gpt-5.4-mini
CHEAPER_INFERENCE_BASE_URL="https://api.cheaperinference.com/v1"

########################################
# Atlas Cloud (https://atlascloud.ai)
########################################
# Get a key at https://atlascloud.ai -- the model catalog at
# https://api.atlascloud.ai/v1/models is public and needs no key.
# Embeddings use the OpenAI settings above
ATLASCLOUD_API_KEY=
ATLASCLOUD_MODEL=deepseek-ai/deepseek-v4-flash
ATLASCLOUD_BASE_URL="https://api.atlascloud.ai/v1"

########################################
# Grok (xAI)
########################################
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The platform provides a modern interface for students, educators, and researcher

### Supported AI Models

- Google Gemini • OpenAI GPT • Anthropic Claude • xAI Grok • [MiniMax](https://www.minimax.io/) • [API Route](https://www.api-route.com) • Ollama (local) • OpenRouter • [Requesty](https://requesty.ai) • [Cheaper Inference](https://cheaperinference.com)
- Google Gemini • OpenAI GPT • Anthropic Claude • xAI Grok • [MiniMax](https://www.minimax.io/) • [API Route](https://www.api-route.com) • Ollama (local) • OpenRouter • [Requesty](https://requesty.ai) • [Cheaper Inference](https://cheaperinference.com) • [Atlas Cloud](https://atlascloud.ai)

### Embedding Providers

Expand Down
3 changes: 3 additions & 0 deletions backend/src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const config = {
cheaperinference: process.env.CHEAPER_INFERENCE_API_KEY || '',
cheaperinference_model: process.env.CHEAPER_INFERENCE_MODEL || 'gpt-5.4-mini',
cheaperinference_base: process.env.CHEAPER_INFERENCE_BASE_URL || 'https://api.cheaperinference.com/v1',
atlascloud: process.env.ATLASCLOUD_API_KEY || '',
atlascloud_model: process.env.ATLASCLOUD_MODEL || 'deepseek-ai/deepseek-v4-flash',
atlascloud_base: process.env.ATLASCLOUD_BASE_URL || 'https://api.atlascloud.ai/v1',
gemini: process.env.gemini || process.env.GOOGLE_API_KEY || '',
gemini_model: process.env.gemini_model || 'gemini-1.5-pro',
gemini_embed_model: process.env.gemini_embed_model || 'text-embedding-004',
Expand Down
71 changes: 71 additions & 0 deletions backend/src/utils/llm/models/__tests__/atlascloud.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'

vi.mock('@langchain/openai', () => {
const MockChatOpenAI = vi.fn(function (this: any, opts: any) {
this.invoke = vi.fn().mockResolvedValue({ content: 'mock response' })
this._opts = opts
})
const MockOpenAIEmbeddings = vi.fn(function (this: any, opts: any) {
this.embedDocuments = vi.fn().mockResolvedValue([[0.1, 0.2]])
this.embedQuery = vi.fn().mockResolvedValue([0.1, 0.2])
this._opts = opts
})
return { ChatOpenAI: MockChatOpenAI, OpenAIEmbeddings: MockOpenAIEmbeddings }
})

import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai'
import { makeLLM, makeEmbeddings } from '../atlascloud'

describe('Atlas Cloud LLM provider', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('should create a ChatOpenAI instance with Atlas Cloud defaults', () => {
const llm = makeLLM({ atlascloud: 'test-key' })

expect(ChatOpenAI).toHaveBeenCalledWith(
expect.objectContaining({
model: 'deepseek-ai/deepseek-v4-flash',
apiKey: 'test-key',
configuration: { baseURL: 'https://api.atlascloud.ai/v1' },
}),
)
expect(typeof llm.invoke).toBe('function')
expect(typeof llm.call).toBe('function')
})

it('should use configured model and base URL', () => {
makeLLM({
atlascloud: 'key',
atlascloud_model: 'zai-org/glm-5.3-flash',
atlascloud_base: 'https://gateway.example.test/v1',
})

expect(ChatOpenAI).toHaveBeenCalledWith(
expect.objectContaining({
model: 'zai-org/glm-5.3-flash',
configuration: { baseURL: 'https://gateway.example.test/v1' },
}),
)
})

it('should pass temperature and max tokens through', () => {
makeLLM({ atlascloud: 'key', temp: 0.2, max_tokens: 1024 })

expect(ChatOpenAI).toHaveBeenCalledWith(
expect.objectContaining({ temperature: 0.2, maxTokens: 1024 }),
)
})

it('should create embeddings with the OpenAI settings', () => {
makeEmbeddings({ openai: 'openai-key' })

expect(OpenAIEmbeddings).toHaveBeenCalledWith(
expect.objectContaining({
model: 'text-embedding-3-large',
apiKey: 'openai-key',
}),
)
})
})
24 changes: 24 additions & 0 deletions backend/src/utils/llm/models/atlascloud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai'
import { wrapChat } from './util'
import type { MkLLM, MkEmb, EmbeddingsLike } from './types'

export const makeLLM: MkLLM = (cfg: any) => {
const m = new ChatOpenAI({
model: cfg.atlascloud_model || 'deepseek-ai/deepseek-v4-flash',
apiKey: cfg.atlascloud || '',
configuration: { baseURL: cfg.atlascloud_base || 'https://api.atlascloud.ai/v1' },
temperature: cfg.temp ?? 0.7,
maxTokens: cfg.max_tokens,
})
return wrapChat(m)
}

// Atlas Cloud serves chat completions only -- it has no /v1/embeddings
// endpoint -- so embeddings fall back to the OpenAI settings, the same way
// the other chat-only gateways here do.
export const makeEmbeddings: MkEmb = (cfg: any): EmbeddingsLike => {
return new OpenAIEmbeddings({
model: cfg.openai_embed_model || 'text-embedding-3-large',
apiKey: cfg.openai || process.env.OPENAI_API_KEY,
})
}
2 changes: 2 additions & 0 deletions backend/src/utils/llm/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as claude from './claude'
import * as openrouter from './openrouter'
import * as requesty from './requesty'
import * as cheaperinference from './cheaperinference'
import * as atlascloud from './atlascloud'
import * as minimax from './minimax'
import * as apiroute from './apiroute'
import { config } from '../../../config/env'
Expand All @@ -23,6 +24,7 @@ function pick(p: string) {
case 'openrouter': return openrouter
case 'requesty': return requesty
case 'cheaperinference': return cheaperinference
case 'atlascloud': return atlascloud
case 'minimax': return minimax
case 'apiroute':
case 'api_route':
Expand Down