@lobehub/chat 1.36.31 → 1.36.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/self-hosting/environment-variables/model-provider.mdx +7 -0
  4. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +7 -0
  5. package/docs/self-hosting/server-database/dokploy.zh-CN.mdx +12 -12
  6. package/package.json +1 -1
  7. package/src/config/modelProviders/github.ts +19 -10
  8. package/src/database/repositories/dataImporter/__tests__/index.test.ts +11 -18
  9. package/src/database/repositories/dataImporter/index.ts +31 -46
  10. package/src/database/server/models/__tests__/_test_template.ts +1 -1
  11. package/src/database/server/models/__tests__/agent.test.ts +1 -1
  12. package/src/database/server/models/__tests__/asyncTask.test.ts +1 -1
  13. package/src/database/server/models/__tests__/chunk.test.ts +1 -1
  14. package/src/database/server/models/__tests__/file.test.ts +1 -1
  15. package/src/database/server/models/__tests__/knowledgeBase.test.ts +1 -2
  16. package/src/database/server/models/__tests__/message.test.ts +35 -72
  17. package/src/database/server/models/__tests__/nextauth.test.ts +1 -1
  18. package/src/database/server/models/__tests__/session.test.ts +1 -1
  19. package/src/database/server/models/__tests__/sessionGroup.test.ts +1 -2
  20. package/src/database/server/models/__tests__/topic.test.ts +1 -1
  21. package/src/database/server/models/__tests__/user.test.ts +1 -1
  22. package/src/database/server/models/_template.ts +2 -2
  23. package/src/database/server/models/agent.ts +17 -25
  24. package/src/database/server/models/asyncTask.ts +2 -2
  25. package/src/database/server/models/chunk.ts +14 -14
  26. package/src/database/server/models/embedding.ts +1 -1
  27. package/src/database/server/models/file.ts +8 -10
  28. package/src/database/server/models/knowledgeBase.ts +4 -6
  29. package/src/database/server/models/message.ts +54 -65
  30. package/src/database/server/models/plugin.ts +2 -2
  31. package/src/database/server/models/ragEval/dataset.ts +2 -2
  32. package/src/database/server/models/ragEval/datasetRecord.ts +3 -8
  33. package/src/database/server/models/ragEval/evaluation.ts +3 -2
  34. package/src/database/server/models/ragEval/evaluationRecord.ts +2 -2
  35. package/src/database/server/models/session.ts +38 -35
  36. package/src/database/server/models/sessionGroup.ts +4 -4
  37. package/src/database/server/models/thread.ts +2 -2
  38. package/src/database/server/models/topic.ts +48 -53
  39. package/src/database/server/models/user.ts +12 -12
  40. package/src/libs/agent-runtime/github/index.test.ts +68 -41
  41. package/src/libs/agent-runtime/github/index.ts +51 -1
  42. package/src/libs/agent-runtime/togetherai/index.ts +2 -1
  43. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +10 -9
  44. package/src/libs/agent-runtime/utils/streams/azureOpenai.test.ts +0 -1
  45. package/src/libs/next-auth/adapter/index.ts +1 -1
  46. package/src/server/routers/lambda/chunk.ts +2 -2
  47. package/vercel.json +1 -1
@@ -29,46 +29,42 @@ export class TopicModel {
29
29
  }
30
30
  // **************** Query *************** //
31
31
 
32
- async query({ current = 0, pageSize = 9999, sessionId }: QueryTopicParams = {}) {
32
+ query = async ({ current = 0, pageSize = 9999, sessionId }: QueryTopicParams = {}) => {
33
33
  const offset = current * pageSize;
34
-
35
- return (
36
- this.db
37
- .select({
38
- createdAt: topics.createdAt,
39
- favorite: topics.favorite,
40
- historySummary: topics.historySummary,
41
- id: topics.id,
42
- metadata: topics.metadata,
43
- title: topics.title,
44
- updatedAt: topics.updatedAt,
45
- })
46
- .from(topics)
47
- .where(and(eq(topics.userId, this.userId), this.matchSession(sessionId)))
48
- // In boolean sorting, false is considered "smaller" than true.
49
- // So here we use desc to ensure that topics with favorite as true are in front.
50
- .orderBy(desc(topics.favorite), desc(topics.updatedAt))
51
- .limit(pageSize)
52
- .offset(offset)
53
- );
54
- }
55
-
56
- async findById(id: string) {
34
+ return this.db
35
+ .select({
36
+ createdAt: topics.createdAt,
37
+ favorite: topics.favorite,
38
+ historySummary: topics.historySummary,
39
+ id: topics.id,
40
+ metadata: topics.metadata,
41
+ title: topics.title,
42
+ updatedAt: topics.updatedAt,
43
+ })
44
+ .from(topics)
45
+ .where(and(eq(topics.userId, this.userId), this.matchSession(sessionId)))
46
+ // In boolean sorting, false is considered "smaller" than true.
47
+ // So here we use desc to ensure that topics with favorite as true are in front.
48
+ .orderBy(desc(topics.favorite), desc(topics.updatedAt))
49
+ .limit(pageSize)
50
+ .offset(offset);
51
+ };
52
+
53
+ findById = async (id: string) => {
57
54
  return this.db.query.topics.findFirst({
58
55
  where: and(eq(topics.id, id), eq(topics.userId, this.userId)),
59
56
  });
60
- }
57
+ };
61
58
 
62
- async queryAll(): Promise<TopicItem[]> {
59
+ queryAll = async (): Promise<TopicItem[]> => {
63
60
  return this.db
64
61
  .select()
65
62
  .from(topics)
66
63
  .orderBy(topics.updatedAt)
67
- .where(eq(topics.userId, this.userId))
68
- .execute();
69
- }
64
+ .where(eq(topics.userId, this.userId));
65
+ };
70
66
 
71
- async queryByKeyword(keyword: string, sessionId?: string | null): Promise<TopicItem[]> {
67
+ queryByKeyword = async (keyword: string, sessionId?: string | null): Promise<TopicItem[]> => {
72
68
  if (!keyword) return [];
73
69
 
74
70
  const keywordLowerCase = keyword.toLowerCase();
@@ -92,26 +88,25 @@ export class TopicModel {
92
88
  ),
93
89
  ),
94
90
  });
95
- }
91
+ };
96
92
 
97
- async count() {
93
+ count = async (): Promise<number> => {
98
94
  const result = await this.db
99
95
  .select({
100
- count: count(),
96
+ count: count(topics.id),
101
97
  })
102
98
  .from(topics)
103
- .where(eq(topics.userId, this.userId))
104
- .execute();
99
+ .where(eq(topics.userId, this.userId));
105
100
 
106
101
  return result[0].count;
107
- }
102
+ };
108
103
 
109
104
  // **************** Create *************** //
110
105
 
111
- async create(
106
+ create = async (
112
107
  { messages: messageIds, ...params }: CreateTopicParams,
113
108
  id: string = this.genId(),
114
- ): Promise<TopicItem> {
109
+ ): Promise<TopicItem> => {
115
110
  return this.db.transaction(async (tx) => {
116
111
  // 在 topics 表中插入新的 topic
117
112
  const [topic] = await tx
@@ -133,9 +128,9 @@ export class TopicModel {
133
128
 
134
129
  return topic;
135
130
  });
136
- }
131
+ };
137
132
 
138
- async batchCreate(topicParams: (CreateTopicParams & { id?: string })[]) {
133
+ batchCreate = async (topicParams: (CreateTopicParams & { id?: string })[]) => {
139
134
  // 开始一个事务
140
135
  return this.db.transaction(async (tx) => {
141
136
  // 在 topics 表中批量插入新的 topics
@@ -167,9 +162,9 @@ export class TopicModel {
167
162
 
168
163
  return createdTopics;
169
164
  });
170
- }
165
+ };
171
166
 
172
- async duplicate(topicId: string, newTitle?: string) {
167
+ duplicate = async (topicId: string, newTitle?: string) => {
173
168
  return this.db.transaction(async (tx) => {
174
169
  // find original topic
175
170
  const originalTopic = await tx.query.topics.findFirst({
@@ -217,48 +212,48 @@ export class TopicModel {
217
212
  topic: duplicatedTopic,
218
213
  };
219
214
  });
220
- }
215
+ };
221
216
 
222
217
  // **************** Delete *************** //
223
218
 
224
219
  /**
225
220
  * Delete a session, also delete all messages and topics associated with it.
226
221
  */
227
- async delete(id: string) {
222
+ delete = async (id: string) => {
228
223
  return this.db.delete(topics).where(and(eq(topics.id, id), eq(topics.userId, this.userId)));
229
- }
224
+ };
230
225
 
231
226
  /**
232
227
  * Deletes multiple topics based on the sessionId.
233
228
  */
234
- async batchDeleteBySessionId(sessionId?: string | null) {
229
+ batchDeleteBySessionId = async (sessionId?: string | null) => {
235
230
  return this.db
236
231
  .delete(topics)
237
232
  .where(and(this.matchSession(sessionId), eq(topics.userId, this.userId)));
238
- }
233
+ };
239
234
 
240
235
  /**
241
236
  * Deletes multiple topics and all messages associated with them in a transaction.
242
237
  */
243
- async batchDelete(ids: string[]) {
238
+ batchDelete = async (ids: string[]) => {
244
239
  return this.db
245
240
  .delete(topics)
246
241
  .where(and(inArray(topics.id, ids), eq(topics.userId, this.userId)));
247
- }
242
+ };
248
243
 
249
- async deleteAll() {
244
+ deleteAll = async () => {
250
245
  return this.db.delete(topics).where(eq(topics.userId, this.userId));
251
- }
246
+ };
252
247
 
253
248
  // **************** Update *************** //
254
249
 
255
- async update(id: string, data: Partial<TopicItem>) {
250
+ update = async (id: string, data: Partial<TopicItem>) => {
256
251
  return this.db
257
252
  .update(topics)
258
253
  .set({ ...data, updatedAt: new Date() })
259
254
  .where(and(eq(topics.id, id), eq(topics.userId, this.userId)))
260
255
  .returning();
261
- }
256
+ };
262
257
 
263
258
  // **************** Helper *************** //
264
259
 
@@ -26,7 +26,7 @@ export class UserModel {
26
26
  this.db = db;
27
27
  }
28
28
 
29
- async getUserState() {
29
+ getUserState = async () => {
30
30
  const result = await this.db
31
31
  .select({
32
32
  isOnboarded: users.isOnboarded,
@@ -81,20 +81,20 @@ export class UserModel {
81
81
  settings,
82
82
  userId: this.userId,
83
83
  };
84
- }
84
+ };
85
85
 
86
- async updateUser(value: Partial<UserItem>) {
86
+ updateUser = async (value: Partial<UserItem>) => {
87
87
  return this.db
88
88
  .update(users)
89
89
  .set({ ...value, updatedAt: new Date() })
90
90
  .where(eq(users.id, this.userId));
91
- }
91
+ };
92
92
 
93
- async deleteSetting() {
93
+ deleteSetting = async () => {
94
94
  return this.db.delete(userSettings).where(eq(userSettings.id, this.userId));
95
- }
95
+ };
96
96
 
97
- async updateSetting(value: Partial<UserSettings>) {
97
+ updateSetting = async (value: Partial<UserSettings>) => {
98
98
  const { keyVaults, ...res } = value;
99
99
 
100
100
  // Encrypt keyVaults
@@ -120,9 +120,9 @@ export class UserModel {
120
120
  }
121
121
 
122
122
  return this.db.update(userSettings).set(newValue).where(eq(userSettings.id, this.userId));
123
- }
123
+ };
124
124
 
125
- async updatePreference(value: Partial<UserPreference>) {
125
+ updatePreference = async (value: Partial<UserPreference>) => {
126
126
  const user = await this.db.query.users.findFirst({ where: eq(users.id, this.userId) });
127
127
  if (!user) return;
128
128
 
@@ -130,9 +130,9 @@ export class UserModel {
130
130
  .update(users)
131
131
  .set({ preference: merge(user.preference, value) })
132
132
  .where(eq(users.id, this.userId));
133
- }
133
+ };
134
134
 
135
- async updateGuide(value: Partial<UserGuide>) {
135
+ updateGuide = async (value: Partial<UserGuide>) => {
136
136
  const user = await this.db.query.users.findFirst({ where: eq(users.id, this.userId) });
137
137
  if (!user) return;
138
138
 
@@ -141,7 +141,7 @@ export class UserModel {
141
141
  .update(users)
142
142
  .set({ preference: { ...prevPreference, guide: merge(prevPreference.guide || {}, value) } })
143
143
  .where(eq(users.id, this.userId));
144
- }
144
+ };
145
145
 
146
146
  // Static method
147
147
 
@@ -21,15 +21,10 @@ let instance: LobeOpenAICompatibleRuntime;
21
21
 
22
22
  beforeEach(() => {
23
23
  instance = new LobeGithubAI({ apiKey: 'test' });
24
-
25
- // Use vi.spyOn to mock the chat.completions.create method
26
- vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
27
- new ReadableStream() as any,
28
- );
29
24
  });
30
25
 
31
26
  afterEach(() => {
32
- vi.clearAllMocks();
27
+ vi.restoreAllMocks();
33
28
  });
34
29
 
35
30
  describe('LobeGithubAI', () => {
@@ -42,6 +37,13 @@ describe('LobeGithubAI', () => {
42
37
  });
43
38
 
44
39
  describe('chat', () => {
40
+ beforeEach(() => {
41
+ // Use vi.spyOn to mock the chat.completions.create method
42
+ vi.spyOn(instance['client'].chat.completions, 'create').mockResolvedValue(
43
+ new ReadableStream() as any,
44
+ );
45
+ });
46
+
45
47
  describe('Error', () => {
46
48
  it('should return GithubBizError with an openai error response when OpenAI.APIError is thrown', async () => {
47
49
  // Arrange
@@ -119,41 +121,6 @@ describe('LobeGithubAI', () => {
119
121
  }
120
122
  });
121
123
 
122
- it('should return GithubBizError with an cause response with desensitize Url', async () => {
123
- // Arrange
124
- const errorInfo = {
125
- stack: 'abc',
126
- cause: { message: 'api is undefined' },
127
- };
128
- const apiError = new OpenAI.APIError(400, errorInfo, 'module error', {});
129
-
130
- instance = new LobeGithubAI({
131
- apiKey: 'test',
132
- baseURL: 'https://api.abc.com/v1',
133
- });
134
-
135
- vi.spyOn(instance['client'].chat.completions, 'create').mockRejectedValue(apiError);
136
-
137
- // Act
138
- try {
139
- await instance.chat({
140
- messages: [{ content: 'Hello', role: 'user' }],
141
- model: 'meta-llama-3-70b-instruct',
142
- temperature: 0.7,
143
- });
144
- } catch (e) {
145
- expect(e).toEqual({
146
- endpoint: 'https://api.***.com/v1',
147
- error: {
148
- cause: { message: 'api is undefined' },
149
- stack: 'abc',
150
- },
151
- errorType: bizErrorType,
152
- provider,
153
- });
154
- }
155
- });
156
-
157
124
  it('should throw an InvalidGithubToken error type on 401 status code', async () => {
158
125
  // Mock the API call to simulate a 401 error
159
126
  const error = new Error('InvalidApiKey') as any;
@@ -243,4 +210,64 @@ describe('LobeGithubAI', () => {
243
210
  });
244
211
  });
245
212
  });
213
+
214
+ describe('models', () => {
215
+ beforeEach(() => {});
216
+
217
+ it('should return a list of models', async () => {
218
+ // Arrange
219
+ const arr = [
220
+ {
221
+ id: 'azureml://registries/azureml-ai21/models/AI21-Jamba-Instruct/versions/2',
222
+ name: 'AI21-Jamba-Instruct',
223
+ friendly_name: 'AI21-Jamba-Instruct',
224
+ model_version: 2,
225
+ publisher: 'AI21 Labs',
226
+ model_family: 'AI21 Labs',
227
+ model_registry: 'azureml-ai21',
228
+ license: 'custom',
229
+ task: 'chat-completion',
230
+ description:
231
+ "Jamba-Instruct is the world's first production-grade Mamba-based LLM model and leverages its hybrid Mamba-Transformer architecture to achieve best-in-class performance, quality, and cost efficiency.\n\n**Model Developer Name**: _AI21 Labs_\n\n## Model Architecture\n\nJamba-Instruct leverages a hybrid Mamba-Transformer architecture to achieve best-in-class performance, quality, and cost efficiency.\nAI21's Jamba architecture features a blocks-and-layers approach that allows Jamba to successfully integrate the two architectures. Each Jamba block contains either an attention or a Mamba layer, followed by a multi-layer perceptron (MLP), producing an overall ratio of one Transformer layer out of every eight total layers.\n",
232
+ summary:
233
+ "Jamba-Instruct is the world's first production-grade Mamba-based LLM model and leverages its hybrid Mamba-Transformer architecture to achieve best-in-class performance, quality, and cost efficiency.",
234
+ tags: ['chat', 'rag'],
235
+ },
236
+ {
237
+ id: 'azureml://registries/azureml-cohere/models/Cohere-command-r/versions/3',
238
+ name: 'Cohere-command-r',
239
+ friendly_name: 'Cohere Command R',
240
+ model_version: 3,
241
+ publisher: 'cohere',
242
+ model_family: 'cohere',
243
+ model_registry: 'azureml-cohere',
244
+ license: 'custom',
245
+ task: 'chat-completion',
246
+ description:
247
+ "Command R is a highly performant generative large language model, optimized for a variety of use cases including reasoning, summarization, and question answering. \n\nThe model is optimized to perform well in the following languages: English, French, Spanish, Italian, German, Brazilian Portuguese, Japanese, Korean, Simplified Chinese, and Arabic.\n\nPre-training data additionally included the following 13 languages: Russian, Polish, Turkish, Vietnamese, Dutch, Czech, Indonesian, Ukrainian, Romanian, Greek, Hindi, Hebrew, Persian.\n\n## Resources\n\nFor full details of this model, [release blog post](https://aka.ms/cohere-blog).\n\n## Model Architecture\n\nThis is an auto-regressive language model that uses an optimized transformer architecture. After pretraining, this model uses supervised fine-tuning (SFT) and preference training to align model behavior to human preferences for helpfulness and safety.\n\n### Tool use capabilities\n\nCommand R has been specifically trained with conversational tool use capabilities. These have been trained into the model via a mixture of supervised fine-tuning and preference fine-tuning, using a specific prompt template. Deviating from this prompt template will likely reduce performance, but we encourage experimentation.\n\nCommand R's tool use functionality takes a conversation as input (with an optional user-system preamble), along with a list of available tools. The model will then generate a json-formatted list of actions to execute on a subset of those tools. Command R may use one of its supplied tools more than once.\n\nThe model has been trained to recognise a special directly_answer tool, which it uses to indicate that it doesn't want to use any of its other tools. The ability to abstain from calling a specific tool can be useful in a range of situations, such as greeting a user, or asking clarifying questions. We recommend including the directly_answer tool, but it can be removed or renamed if required.\n\n### Grounded Generation and RAG Capabilities\n\nCommand R has been specifically trained with grounded generation capabilities. This means that it can generate responses based on a list of supplied document snippets, and it will include grounding spans (citations) in its response indicating the source of the information. This can be used to enable behaviors such as grounded summarization and the final step of Retrieval Augmented Generation (RAG).This behavior has been trained into the model via a mixture of supervised fine-tuning and preference fine-tuning, using a specific prompt template. Deviating from this prompt template may reduce performance, but we encourage experimentation.\n\nCommand R's grounded generation behavior takes a conversation as input (with an optional user-supplied system preamble, indicating task, context and desired output style), along with a list of retrieved document snippets. The document snippets should be chunks, rather than long documents, typically around 100-400 words per chunk. Document snippets consist of key-value pairs. The keys should be short descriptive strings, the values can be text or semi-structured.\n\nBy default, Command R will generate grounded responses by first predicting which documents are relevant, then predicting which ones it will cite, then generating an answer. Finally, it will then insert grounding spans into the answer. See below for an example. This is referred to as accurate grounded generation.\n\nThe model is trained with a number of other answering modes, which can be selected by prompt changes . A fast citation mode is supported in the tokenizer, which will directly generate an answer with grounding spans in it, without first writing the answer out in full. This sacrifices some grounding accuracy in favor of generating fewer tokens.\n\n### Code Capabilities\n\nCommand R has been optimized to interact with your code, by requesting code snippets, code explanations, or code rewrites. It might not perform well out-of-the-box for pure code completion. For better performance, we also recommend using a low temperature (and even greedy decoding) for code-generation related instructions.\n",
248
+ summary:
249
+ 'Command R is a scalable generative model targeting RAG and Tool Use to enable production-scale AI for enterprise.',
250
+ tags: ['rag', 'multilingual'],
251
+ },
252
+ ];
253
+ vi.spyOn(instance['client'].models, 'list').mockResolvedValue({
254
+ body: arr,
255
+ } as any);
256
+
257
+ // Act & Assert
258
+ const models = await instance.models();
259
+
260
+ const modelsCount = models.length;
261
+ expect(modelsCount).toBe(arr.length);
262
+
263
+ for (let i = 0; i < arr.length; i++) {
264
+ const model = models[i];
265
+ expect(model).toEqual({
266
+ description: arr[i].description,
267
+ displayName: arr[i].friendly_name,
268
+ id: arr[i].name,
269
+ });
270
+ }
271
+ });
272
+ });
246
273
  });
@@ -1,7 +1,35 @@
1
+ import { LOBE_DEFAULT_MODEL_LIST } from '@/config/modelProviders';
2
+ import type { ChatModelCard } from '@/types/llm';
3
+
1
4
  import { AgentRuntimeErrorType } from '../error';
2
5
  import { o1Models, pruneO1Payload } from '../openai';
3
6
  import { ModelProvider } from '../types';
4
- import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
7
+ import {
8
+ CHAT_MODELS_BLOCK_LIST,
9
+ LobeOpenAICompatibleFactory,
10
+ } from '../utils/openaiCompatibleFactory';
11
+
12
+ enum Task {
13
+ 'chat-completion',
14
+ 'embeddings',
15
+ }
16
+
17
+ /* eslint-disable typescript-sort-keys/interface */
18
+ type Model = {
19
+ id: string;
20
+ name: string;
21
+ friendly_name: string;
22
+ model_version: number;
23
+ publisher: string;
24
+ model_family: string;
25
+ model_registry: string;
26
+ license: string;
27
+ task: Task;
28
+ description: string;
29
+ summary: string;
30
+ tags: string[];
31
+ };
32
+ /* eslint-enable typescript-sort-keys/interface */
5
33
 
6
34
  export const LobeGithubAI = LobeOpenAICompatibleFactory({
7
35
  baseURL: 'https://models.inference.ai.azure.com',
@@ -23,5 +51,27 @@ export const LobeGithubAI = LobeOpenAICompatibleFactory({
23
51
  bizError: AgentRuntimeErrorType.ProviderBizError,
24
52
  invalidAPIKey: AgentRuntimeErrorType.InvalidGithubToken,
25
53
  },
54
+ models: async ({ client }) => {
55
+ const modelsPage = (await client.models.list()) as any;
56
+ const modelList: Model[] = modelsPage.body;
57
+ return modelList
58
+ .filter((model) => {
59
+ return CHAT_MODELS_BLOCK_LIST.every(
60
+ (keyword) => !model.name.toLowerCase().includes(keyword),
61
+ );
62
+ })
63
+ .map((model) => {
64
+ const knownModel = LOBE_DEFAULT_MODEL_LIST.find((m) => m.id === model.name);
65
+
66
+ if (knownModel) return knownModel;
67
+
68
+ return {
69
+ description: model.description,
70
+ displayName: model.friendly_name,
71
+ id: model.name,
72
+ };
73
+ })
74
+ .filter(Boolean) as ChatModelCard[];
75
+ },
26
76
  provider: ModelProvider.Github,
27
77
  });
@@ -16,7 +16,8 @@ export const LobeTogetherAI = LobeOpenAICompatibleFactory({
16
16
  debug: {
17
17
  chatCompletion: () => process.env.DEBUG_TOGETHERAI_CHAT_COMPLETION === '1',
18
18
  },
19
- models: async ({ apiKey }) => {
19
+ models: async ({ client }) => {
20
+ const apiKey = client.apiKey;
20
21
  const data = await fetch(`${baseURL}/api/models`, {
21
22
  headers: {
22
23
  Authorization: `Bearer ${apiKey}`,
@@ -2,17 +2,18 @@ import OpenAI, { ClientOptions } from 'openai';
2
2
  import { Stream } from 'openai/streaming';
3
3
 
4
4
  import { LOBE_DEFAULT_MODEL_LIST } from '@/config/modelProviders';
5
- import { ChatModelCard } from '@/types/llm';
5
+ import type { ChatModelCard } from '@/types/llm';
6
6
 
7
7
  import { LobeRuntimeAI } from '../../BaseAI';
8
8
  import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '../../error';
9
- import {
9
+ import type {
10
10
  ChatCompetitionOptions,
11
11
  ChatCompletionErrorPayload,
12
12
  ChatStreamPayload,
13
13
  Embeddings,
14
14
  EmbeddingsOptions,
15
15
  EmbeddingsPayload,
16
+ ModelProvider,
16
17
  TextToImagePayload,
17
18
  TextToSpeechOptions,
18
19
  TextToSpeechPayload,
@@ -26,7 +27,7 @@ import { StreamingResponse } from '../response';
26
27
  import { OpenAIStream, OpenAIStreamOptions } from '../streams';
27
28
 
28
29
  // the model contains the following keywords is not a chat model, so we should filter them out
29
- const CHAT_MODELS_BLOCK_LIST = [
30
+ export const CHAT_MODELS_BLOCK_LIST = [
30
31
  'embedding',
31
32
  'davinci',
32
33
  'curie',
@@ -77,7 +78,7 @@ interface OpenAICompatibleFactoryOptions<T extends Record<string, any> = any> {
77
78
  invalidAPIKey: ILobeAgentRuntimeErrorType;
78
79
  };
79
80
  models?:
80
- | ((params: { apiKey: string }) => Promise<ChatModelCard[]>)
81
+ | ((params: { client: OpenAI }) => Promise<ChatModelCard[]>)
81
82
  | {
82
83
  transformModel?: (model: OpenAI.Model) => ChatModelCard;
83
84
  };
@@ -157,7 +158,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
157
158
  client!: OpenAI;
158
159
 
159
160
  baseURL!: string;
160
- private _options: ConstructorOptions<T>;
161
+ protected _options: ConstructorOptions<T>;
161
162
 
162
163
  constructor(options: ClientOptions & Record<string, any> = {}) {
163
164
  const _options = {
@@ -249,7 +250,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
249
250
  }
250
251
 
251
252
  async models() {
252
- if (typeof models === 'function') return models({ apiKey: this.client.apiKey });
253
+ if (typeof models === 'function') return models({ client: this.client });
253
254
 
254
255
  const list = await this.client.models.list();
255
256
 
@@ -312,7 +313,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
312
313
  }
313
314
  }
314
315
 
315
- private handleError(error: any): ChatCompletionErrorPayload {
316
+ protected handleError(error: any): ChatCompletionErrorPayload {
316
317
  let desensitizedEndpoint = this.baseURL;
317
318
 
318
319
  // refs: https://github.com/lobehub/lobe-chat/issues/842
@@ -337,7 +338,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
337
338
  endpoint: desensitizedEndpoint,
338
339
  error: error as any,
339
340
  errorType: ErrorType.invalidAPIKey,
340
- provider: provider as any,
341
+ provider: provider as ModelProvider,
341
342
  });
342
343
  }
343
344
 
@@ -353,7 +354,7 @@ export const LobeOpenAICompatibleFactory = <T extends Record<string, any> = any>
353
354
  endpoint: desensitizedEndpoint,
354
355
  error: errorResult,
355
356
  errorType: RuntimeError || ErrorType.bizError,
356
- provider: provider as any,
357
+ provider: provider as ModelProvider,
357
358
  });
358
359
  }
359
360
  };
@@ -1,4 +1,3 @@
1
- import { desc } from 'drizzle-orm/expressions';
2
1
  import { describe, expect, it, vi } from 'vitest';
3
2
 
4
3
  import { AzureOpenAIStream } from './azureOpenai';
@@ -4,7 +4,7 @@ import type {
4
4
  AdapterUser,
5
5
  VerificationToken,
6
6
  } from '@auth/core/adapters';
7
- import { and, eq } from 'drizzle-orm';
7
+ import { and, eq } from 'drizzle-orm/expressions';
8
8
  import type { NeonDatabase } from 'drizzle-orm/neon-serverless';
9
9
  import { Adapter, AdapterAccount } from 'next-auth/adapters';
10
10
 
@@ -1,14 +1,14 @@
1
- import { inArray } from 'drizzle-orm';
1
+ import { inArray } from 'drizzle-orm/expressions';
2
2
  import { z } from 'zod';
3
3
 
4
4
  import { DEFAULT_EMBEDDING_MODEL } from '@/const/settings';
5
+ import { knowledgeBaseFiles } from '@/database/schemas';
5
6
  import { serverDB } from '@/database/server';
6
7
  import { AsyncTaskModel } from '@/database/server/models/asyncTask';
7
8
  import { ChunkModel } from '@/database/server/models/chunk';
8
9
  import { EmbeddingModel } from '@/database/server/models/embedding';
9
10
  import { FileModel } from '@/database/server/models/file';
10
11
  import { MessageModel } from '@/database/server/models/message';
11
- import { knowledgeBaseFiles } from '@/database/schemas';
12
12
  import { ModelProvider } from '@/libs/agent-runtime';
13
13
  import { authedProcedure, router } from '@/libs/trpc';
14
14
  import { keyVaults } from '@/libs/trpc/middleware/keyVaults';
package/vercel.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "installCommand": "bun install"
2
+ "installCommand": "npx bun@1.1.38 install"
3
3
  }