Skip to content

Commit 1622e08

Browse files
committed
feat(providers): add lm studio
1 parent 55a4b9d commit 1622e08

4 files changed

Lines changed: 155 additions & 0 deletions

File tree

‎src/lib/models/providers/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import TransformersProvider from './transformers';
77
import GroqProvider from './groq';
88
import LemonadeProvider from './lemonade';
99
import AnthropicProvider from './anthropic';
10+
import LMStudioProvider from './lmstudio';
1011

1112
export const providers: Record<string, ProviderConstructor<any>> = {
1213
openai: OpenAIProvider,
@@ -16,6 +17,7 @@ export const providers: Record<string, ProviderConstructor<any>> = {
1617
groq: GroqProvider,
1718
lemonade: LemonadeProvider,
1819
anthropic: AnthropicProvider,
20+
lmstudio: LMStudioProvider,
1921
};
2022

2123
export const getModelProvidersUIConfigSection =
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { UIConfigField } from '@/lib/config/types';
2+
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
3+
import BaseModelProvider from '../../base/provider';
4+
import { Model, ModelList, ProviderMetadata } from '../../types';
5+
import LMStudioLLM from './lmstudioLLM';
6+
import BaseLLM from '../../base/llm';
7+
import BaseEmbedding from '../../base/embedding';
8+
import LMStudioEmbedding from './lmstudioEmbedding';
9+
10+
interface LMStudioConfig {
11+
baseURL: string;
12+
}
13+
14+
const providerConfigFields: UIConfigField[] = [
15+
{
16+
type: 'string',
17+
name: 'Base URL',
18+
key: 'baseURL',
19+
description: 'The base URL for LM Studio server',
20+
required: true,
21+
placeholder: 'http://localhost:1234',
22+
env: 'LM_STUDIO_BASE_URL',
23+
scope: 'server',
24+
},
25+
];
26+
27+
class LMStudioProvider extends BaseModelProvider<LMStudioConfig> {
28+
constructor(id: string, name: string, config: LMStudioConfig) {
29+
super(id, name, config);
30+
}
31+
32+
private normalizeBaseURL(url: string): string {
33+
const trimmed = url.trim().replace(/\/+$/, '');
34+
return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1`;
35+
}
36+
37+
async getDefaultModels(): Promise<ModelList> {
38+
try {
39+
const baseURL = this.normalizeBaseURL(this.config.baseURL);
40+
41+
const res = await fetch(`${baseURL}/models`, {
42+
method: 'GET',
43+
headers: {
44+
'Content-Type': 'application/json',
45+
},
46+
});
47+
48+
const data = await res.json();
49+
50+
const models: Model[] = data.data.map((m: any) => {
51+
return {
52+
name: m.id,
53+
key: m.id,
54+
};
55+
});
56+
57+
return {
58+
embedding: models,
59+
chat: models,
60+
};
61+
} catch (err) {
62+
if (err instanceof TypeError) {
63+
throw new Error(
64+
'Error connecting to LM Studio. Please ensure the base URL is correct and the LM Studio server is running.',
65+
);
66+
}
67+
68+
throw err;
69+
}
70+
}
71+
72+
async getModelList(): Promise<ModelList> {
73+
const defaultModels = await this.getDefaultModels();
74+
const configProvider = getConfiguredModelProviderById(this.id)!;
75+
76+
return {
77+
embedding: [
78+
...defaultModels.embedding,
79+
...configProvider.embeddingModels,
80+
],
81+
chat: [...defaultModels.chat, ...configProvider.chatModels],
82+
};
83+
}
84+
85+
async loadChatModel(key: string): Promise<BaseLLM<any>> {
86+
const modelList = await this.getModelList();
87+
88+
const exists = modelList.chat.find((m) => m.key === key);
89+
90+
if (!exists) {
91+
throw new Error(
92+
'Error Loading LM Studio Chat Model. Invalid Model Selected',
93+
);
94+
}
95+
96+
return new LMStudioLLM({
97+
apiKey: 'lm-studio',
98+
model: key,
99+
baseURL: this.normalizeBaseURL(this.config.baseURL),
100+
});
101+
}
102+
103+
async loadEmbeddingModel(key: string): Promise<BaseEmbedding<any>> {
104+
const modelList = await this.getModelList();
105+
const exists = modelList.embedding.find((m) => m.key === key);
106+
107+
if (!exists) {
108+
throw new Error(
109+
'Error Loading LM Studio Embedding Model. Invalid Model Selected.',
110+
);
111+
}
112+
113+
return new LMStudioEmbedding({
114+
apiKey: 'lm-studio',
115+
model: key,
116+
baseURL: this.normalizeBaseURL(this.config.baseURL),
117+
});
118+
}
119+
120+
static parseAndValidate(raw: any): LMStudioConfig {
121+
if (!raw || typeof raw !== 'object')
122+
throw new Error('Invalid config provided. Expected object');
123+
if (!raw.baseURL)
124+
throw new Error('Invalid config provided. Base URL must be provided');
125+
126+
return {
127+
baseURL: String(raw.baseURL),
128+
};
129+
}
130+
131+
static getProviderConfigFields(): UIConfigField[] {
132+
return providerConfigFields;
133+
}
134+
135+
static getProviderMetadata(): ProviderMetadata {
136+
return {
137+
key: 'lmstudio',
138+
name: 'LM Studio',
139+
};
140+
}
141+
}
142+
143+
export default LMStudioProvider;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import OpenAIEmbedding from '../openai/openaiEmbedding';
2+
3+
class LMStudioEmbedding extends OpenAIEmbedding {}
4+
5+
export default LMStudioEmbedding;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import OpenAILLM from '../openai/openaiLLM';
2+
3+
class LMStudioLLM extends OpenAILLM {}
4+
5+
export default LMStudioLLM;

0 commit comments

Comments
 (0)