Skip to content

Commit afe656a

Browse files
authored
Merge pull request #111 from solidSpoon/migrate-to-ai-sdk
migrate from LangChain to AI SDK
2 parents fc07a5c + 35af2c9 commit afe656a

22 files changed

+235
-352
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "dash-player",
33
"productName": "DashPlayer",
4-
"version": "5.1.2",
4+
"version": "5.1.3",
55
"description": "My Electron application description",
66
"main": ".vite/build/main.js",
77
"scripts": {
@@ -55,11 +55,11 @@
5555
"vitest": "^1.3.1"
5656
},
5757
"dependencies": {
58+
"@ai-sdk/openai": "^1.1.11",
5859
"@electron-forge/publisher-github": "^7.4.0",
5960
"@ffmpeg-installer/ffmpeg": "^1.1.0",
6061
"@floating-ui/react": "^0.26.9",
6162
"@hookform/resolvers": "^3.9.1",
62-
"@langchain/core": "^0.3.5",
6363
"@radix-ui/react-aspect-ratio": "^1.1.0",
6464
"@radix-ui/react-checkbox": "^1.0.4",
6565
"@radix-ui/react-context-menu": "^2.1.5",
@@ -82,6 +82,7 @@
8282
"@types/fluent-ffmpeg": "^2.1.24",
8383
"@uidotdev/usehooks": "^2.4.1",
8484
"@vitejs/plugin-react": "^4.2.1",
85+
"ai": "^4.1.41",
8586
"axios": "^1.6.8",
8687
"better-sqlite3": "^9.4.0",
8788
"class-variance-authority": "^0.7.0",
@@ -106,7 +107,6 @@
106107
"iconv-lite": "^0.6.3",
107108
"inversify": "^6.0.2",
108109
"jschardet": "^3.1.2",
109-
"langchain": "^0.3.2",
110110
"leven": "^3.1.0",
111111
"lucide-react": "^0.378.0",
112112
"moment": "^2.30.1",

src/backend/controllers/AiFuncController.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import TtsService from '@/backend/services/TtsService';
22
import registerRoute from '@/common/api/register';
33
import AiServiceImpl from '@/backend/services/AiServiceImpl';
44
import ChatServiceImpl from '@/backend/services/impl/ChatServiceImpl';
5-
import { MsgT, toLangChainMsg } from '@/common/types/msg/interfaces/MsgT';
65
import UrlUtil from '@/common/utils/UrlUtil';
76
import { inject, injectable } from 'inversify';
87
import Controller from '@/backend/interfaces/controller';
98
import TYPES from '@/backend/ioc/types';
109
import DpTaskService from '@/backend/services/DpTaskService';
1110
import WhisperService from '@/backend/services/WhisperService';
11+
import { CoreMessage } from 'ai';
1212

1313
@injectable()
1414
export default class AiFuncController implements Controller {
@@ -83,10 +83,9 @@ export default class AiFuncController implements Controller {
8383
return UrlUtil.dp(await TtsService.tts(string));
8484
}
8585

86-
public async chat({ msgs }: { msgs: MsgT[] }): Promise<number> {
86+
public async chat({ msgs }: { msgs: CoreMessage[] }): Promise<number> {
8787
const taskId = await this.dpTaskService.create();
88-
const ms = msgs.map((msg) => toLangChainMsg(msg));
89-
this.chatService.chat(taskId, ms).then();
88+
this.chatService.chat(taskId, msgs).then();
9089
return taskId;
9190
}
9291

src/backend/ioc/inversify.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import MediaServiceImpl from '@/backend/services/impl/MediaServiceImpl';
4949
import ClientProviderService from '@/backend/services/ClientProviderService';
5050
import YouDaoProvider from '@/backend/services/impl/clients/YouDaoProvider';
5151
import TencentProvider from '@/backend/services/impl/clients/TencentProvider';
52-
import { ChatOpenAI } from '@langchain/openai';
5352
import TranslateServiceImpl from '@/backend/services/impl/TranslateServiceImpl';
5453
import TranslateService from '@/backend/services/AiTransServiceImpl';
5554
import TagServiceImpl from '@/backend/services/impl/TagServiceImpl';
@@ -62,13 +61,14 @@ import WatchHistoryServiceImpl from '@/backend/services/impl/WatchHistoryService
6261
import WatchHistoryController from '@/backend/controllers/WatchHistoryController';
6362
import { OpenAIServiceImpl } from '@/backend/services/impl/OpenAIServiceImpl';
6463
import { OpenAiService } from '@/backend/services/OpenAiService';
64+
import AiProviderService from '@/backend/services/AiProviderService';
6565

6666

6767
const container = new Container();
6868
// Clients
6969
container.bind<ClientProviderService<YouDaoClient>>(TYPES.YouDaoClientProvider).to(YouDaoProvider).inSingletonScope();
7070
container.bind<ClientProviderService<TencentClient>>(TYPES.TencentClientProvider).to(TencentProvider).inSingletonScope();
71-
container.bind<ClientProviderService<ChatOpenAI>>(TYPES.OpenAiClientProvider).to(AiProviderServiceImpl).inSingletonScope();
71+
container.bind<AiProviderService>(TYPES.AiProviderService).to(AiProviderServiceImpl).inSingletonScope();
7272
// Controllers
7373
container.bind<Controller>(TYPES.Controller).to(FavoriteClipsController).inSingletonScope();
7474
container.bind<Controller>(TYPES.Controller).to(DownloadVideoController).inSingletonScope();

src/backend/ioc/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const TYPES = {
2525
// Clients
2626
YouDaoClientProvider: Symbol('YouDaoClientProvider'),
2727
TencentClientProvider: Symbol('TencentClientProvider'),
28-
OpenAiClientProvider: Symbol('OpenAiClientProvider'),
28+
AiProviderService: Symbol('AiProviderService'),
2929
};
3030

3131
export default TYPES;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { LanguageModelV1 } from 'ai';
2+
3+
export default interface AiProviderService {
4+
getModel(): LanguageModelV1 | null;
5+
}

src/backend/services/AiServiceImpl.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { getSubtitleContent, srtSlice } from '@/common/utils/srtSlice';
1515
import { inject, injectable } from 'inversify';
1616
import TYPES from '@/backend/ioc/types';
1717
import ChatService from '@/backend/services/ChatService';
18-
import { HumanMessage } from '@langchain/core/messages';
1918

2019
export interface AiService {
2120
polish(taskId: number, sentence: string): Promise<void>;
@@ -55,7 +54,10 @@ export default class AiServiceImpl implements AiService {
5554

5655
public async formatSplit(taskId: number, text: string) {
5756
// await AiFunc.run(taskId, null, AiFuncFormatSplitPrompt.promptFunc(text));
58-
await this.chatService.chat(taskId, [new HumanMessage(AiFuncFormatSplitPrompt.promptFunc(text))]);
57+
await this.chatService.chat(taskId, [{
58+
role: 'user',
59+
content: AiFuncFormatSplitPrompt.promptFunc(text)
60+
}]);
5961
}
6062

6163
public async analyzeWord(taskId: number, sentence: string) {

src/backend/services/ChatService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { BaseMessage } from '@langchain/core/messages';
21
import { ZodObject } from 'zod';
2+
import { CoreMessage } from 'ai';
33

44
export default interface ChatService {
5-
chat(taskId: number, msgs: BaseMessage[]): Promise<void>;
5+
chat(taskId: number, msgs: CoreMessage[]): Promise<void>;
66
run(taskId: number, resultSchema: ZodObject<any>, promptStr: string): Promise<void>;
77
}
88

src/backend/services/impl/ChatServiceImpl.ts

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,41 @@
11
import RateLimiter from '@/common/utils/RateLimiter';
2-
import { BaseMessage } from '@langchain/core/messages';
32
import { inject, injectable } from 'inversify';
43
import DpTaskService from '@/backend/services/DpTaskService';
54
import TYPES from '@/backend/ioc/types';
65
import ChatService from '@/backend/services/ChatService';
7-
import { ChatOpenAI } from '@langchain/openai';
8-
import ClientProviderService from '@/backend/services/ClientProviderService';
96
import { ZodObject } from 'zod';
10-
import { storeGet } from '@/backend/store';
11-
12-
7+
import { CoreMessage, streamObject, streamText } from 'ai';
8+
import AiProviderService from '@/backend/services/AiProviderService';
139
@injectable()
1410
export default class ChatServiceImpl implements ChatService {
1511

1612
@inject(TYPES.DpTaskService)
1713
private dpTaskService!: DpTaskService;
1814

19-
@inject(TYPES.OpenAiClientProvider)
20-
private aiProviderService!: ClientProviderService<ChatOpenAI>;
15+
@inject(TYPES.AiProviderService)
16+
private aiProviderService!: AiProviderService;
2117

2218

23-
public async chat(taskId: number, msgs: BaseMessage[]) {
19+
public async chat(taskId: number, msgs: CoreMessage[]) {
2420
await RateLimiter.wait('gpt');
25-
const chat = this.aiProviderService.getClient();
26-
if (chat) {
21+
const model = this.aiProviderService.getModel();
22+
if (!model) {
2723
this.dpTaskService.fail(taskId, {
2824
progress: 'OpenAI api key or endpoint is empty'
2925
});
26+
return;
3027
}
3128
this.dpTaskService.process(taskId, {
3229
progress: 'AI is thinking...'
3330
});
34-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
35-
// @ts-ignore
36-
const resStream = await chat.stream(msgs);
37-
const chunks = [];
31+
32+
const result = streamText({
33+
model: model,
34+
messages: msgs
35+
});
3836
let res = '';
39-
for await (const chunk of resStream) {
40-
res += chunk.content;
41-
chunks.push(chunk);
37+
for await (const chunk of result.textStream) {
38+
res += chunk;
4239
this.dpTaskService.process(taskId, {
4340
progress: `AI typing, ${res.length} characters`,
4441
result: res
@@ -52,38 +49,29 @@ export default class ChatServiceImpl implements ChatService {
5249

5350
public async run(taskId: number, resultSchema: ZodObject<any>, promptStr: string) {
5451
await RateLimiter.wait('gpt');
55-
const chat = this.aiProviderService.getClient();
56-
if (!chat) {
52+
const model = this.aiProviderService.getModel();
53+
if (!model) {
5754
this.dpTaskService.fail(taskId, {
5855
progress: 'OpenAI api key or endpoint is empty'
5956
});
6057
return;
6158
}
62-
const structuredLlm = chat.withStructuredOutput(resultSchema);
63-
59+
const { partialObjectStream } = streamObject({
60+
model: model,
61+
schema: resultSchema,
62+
prompt: promptStr,
63+
});
6464
this.dpTaskService.process(taskId, {
6565
progress: 'AI is analyzing...'
6666
});
67-
68-
const streaming = storeGet('apiKeys.openAi.stream') === 'on';
69-
70-
let resStr = null;
71-
if (streaming) {
72-
const resStream = await structuredLlm.stream(promptStr);
73-
for await (const chunk of resStream) {
74-
resStr = JSON.stringify(chunk);
75-
this.dpTaskService.process(taskId, {
76-
progress: 'AI is analyzing...',
77-
result: resStr
78-
});
79-
}
80-
} else {
81-
const res = await structuredLlm.invoke(promptStr);
82-
resStr = JSON.stringify(res);
67+
for await (const partialObject of partialObjectStream) {
68+
this.dpTaskService.process(taskId, {
69+
progress: 'AI is analyzing...',
70+
result: JSON.stringify(partialObject)
71+
});
8372
}
8473
this.dpTaskService.finish(taskId, {
85-
progress: 'AI has responded',
86-
result: resStr
74+
progress: 'AI has responded'
8775
});
8876
}
8977
}
Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { ChatOpenAI } from '@langchain/openai';
21
import { storeGet } from '@/backend/store';
32
import StrUtil from '@/common/utils/str-util';
43
import { joinUrl } from '@/common/utils/Util';
54
import { injectable } from 'inversify';
6-
import ClientProviderService from '@/backend/services/ClientProviderService';
5+
import AiProviderService from '@/backend/services/AiProviderService';
6+
import { createOpenAI } from '@ai-sdk/openai';
7+
import { LanguageModelV1 } from 'ai';
78

89

910
@injectable()
10-
export default class AiProviderServiceImpl implements ClientProviderService<ChatOpenAI> {
11-
public getClient(): ChatOpenAI | null {
11+
export default class AiProviderServiceImpl implements AiProviderService {
12+
13+
public getModel():LanguageModelV1 | null {
1214
const apiKey = storeGet('apiKeys.openAi.key');
1315
const endpoint = storeGet('apiKeys.openAi.endpoint');
1416
let model = storeGet('model.gpt.default');
@@ -18,14 +20,11 @@ export default class AiProviderServiceImpl implements ClientProviderService<Chat
1820
if (StrUtil.hasBlank(apiKey, endpoint)) {
1921
return null;
2022
}
21-
console.log(apiKey, endpoint);
22-
return new ChatOpenAI({
23-
modelName: model,
24-
temperature: 0.7,
25-
openAIApiKey: apiKey,
26-
configuration: {
27-
baseURL: joinUrl(endpoint, '/v1')
28-
},
23+
const openai = createOpenAI({
24+
compatibility: 'compatible',
25+
baseURL: joinUrl(endpoint, '/v1'),
26+
apiKey: apiKey
2927
});
28+
return openai(model);
3029
}
3130
}

src/common/api/api-def.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { MsgT } from '@/common/types/msg/interfaces/MsgT';
21
import { DpTask } from '@/backend/db/tables/dpTask';
32
import { YdRes } from '@/common/types/YdRes';
43
import { ChapterParseResult } from '@/common/types/chapter-result';
@@ -16,6 +15,7 @@ import { ClipQuery } from '@/common/api/dto';
1615
import { ClipMeta, OssBaseMeta } from '@/common/types/clipMeta';
1716
import WatchHistoryVO from '@/common/types/WatchHistoryVO';
1817
import { COOKIE } from '@/common/types/DlVideoType';
18+
import { CoreMessage } from 'ai';
1919

2020
interface ApiDefinition {
2121
'eg': { params: string, return: number },
@@ -32,7 +32,7 @@ interface AiFuncDef {
3232
'ai-func/analyze-grammars': { params: string, return: number };
3333
'ai-func/analyze-new-phrases': { params: string, return: number };
3434
'ai-func/analyze-new-words': { params: string, return: number };
35-
'ai-func/chat': { params: { msgs: MsgT[] }, return: number };
35+
'ai-func/chat': { params: { msgs: CoreMessage[] }, return: number };
3636
'ai-func/transcript': { params: { filePath: string }, return: number };
3737
'ai-func/explain-select-with-context': { params: { sentence: string, selectedWord: string }, return: number };
3838
'ai-func/explain-select': { params: { word: string }, return: number };

0 commit comments

Comments
 (0)