Commit 66813230 authored by nanahira's avatar nanahira

add davinci fallback

parent cc44f8dc
......@@ -7,4 +7,5 @@ accounts:
password: 'wwww'
pro: false
loginType: 'default'
proxies: []
\ No newline at end of file
proxies: []
apiKeys: []
\ No newline at end of file
......@@ -14,9 +14,10 @@
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/swagger": "^6.2.1",
"aragami": "^1.1.2",
"aragami": "^1.1.3",
"better-lock": "^2.0.3",
"chatgpt3": "npm:chatgpt@^3.5.2",
"chatgpt4": "npm:chatgpt@^4.3.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"nestjs-aragami": "^1.0.0",
......@@ -2747,9 +2748,9 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
},
"node_modules/aragami": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aragami/-/aragami-1.1.2.tgz",
"integrity": "sha512-asrmtKkLOgtXMYt3TeM/lWsmbKdJwjUPDV9yb+h62FBBMFx0h+lrEQN1XL6ZIkfyggtyC0G5gv0f9G+Ul0cTUg==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/aragami/-/aragami-1.1.3.tgz",
"integrity": "sha512-k/VdRFoNTgpCmFkuJL1P0NohIai7b+EPj1JzykrmTj1az71PSgNofkXxZ2M38FWD/+iRC7XJGAHhaDpzlSAWHw==",
"dependencies": {
"@nanahira/redlock": "^1.0.0",
"better-lock": "^2.0.3",
......@@ -3273,6 +3274,34 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chatgpt4": {
"name": "chatgpt",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/chatgpt/-/chatgpt-4.3.2.tgz",
"integrity": "sha512-5+Kh0mdP/pDJTL3kA6C6Dp43EO8T8sILPJbbQwOu3J4dK+n/tYpSXsmG6VZkmitAVOffeTonXTGDJwslc66orw==",
"dependencies": {
"eventsource-parser": "^0.0.5",
"gpt-3-encoder": "^1.1.4",
"keyv": "^4.5.2",
"p-timeout": "^6.0.0",
"quick-lru": "^6.1.1",
"uuid": "^9.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/chatgpt4/node_modules/p-timeout": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.1.tgz",
"integrity": "sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
......@@ -4992,6 +5021,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gpt-3-encoder": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/gpt-3-encoder/-/gpt-3-encoder-1.1.4.tgz",
"integrity": "sha512-fSQRePV+HUAhCn7+7HL7lNIXNm6eaFWFbNLOOGtmSJ0qJycyQvj60OvRlH7mee8xAMjBDNRdMXlMwjAbMTDjkg=="
},
"node_modules/graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
......@@ -6366,6 +6400,11 @@
"node": ">=4"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
......@@ -6412,6 +6451,14 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/keyv": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
"integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
......@@ -8232,6 +8279,17 @@
}
]
},
"node_modules/quick-lru": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
"integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/random": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/random/-/random-4.1.0.tgz",
......@@ -12425,9 +12483,9 @@
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
},
"aragami": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aragami/-/aragami-1.1.2.tgz",
"integrity": "sha512-asrmtKkLOgtXMYt3TeM/lWsmbKdJwjUPDV9yb+h62FBBMFx0h+lrEQN1XL6ZIkfyggtyC0G5gv0f9G+Ul0cTUg==",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/aragami/-/aragami-1.1.3.tgz",
"integrity": "sha512-k/VdRFoNTgpCmFkuJL1P0NohIai7b+EPj1JzykrmTj1az71PSgNofkXxZ2M38FWD/+iRC7XJGAHhaDpzlSAWHw==",
"requires": {
"@nanahira/redlock": "^1.0.0",
"better-lock": "^2.0.3",
......@@ -12810,6 +12868,26 @@
}
}
},
"chatgpt4": {
"version": "npm:chatgpt@4.3.2",
"resolved": "https://registry.npmjs.org/chatgpt/-/chatgpt-4.3.2.tgz",
"integrity": "sha512-5+Kh0mdP/pDJTL3kA6C6Dp43EO8T8sILPJbbQwOu3J4dK+n/tYpSXsmG6VZkmitAVOffeTonXTGDJwslc66orw==",
"requires": {
"eventsource-parser": "^0.0.5",
"gpt-3-encoder": "^1.1.4",
"keyv": "^4.5.2",
"p-timeout": "^6.0.0",
"quick-lru": "^6.1.1",
"uuid": "^9.0.0"
},
"dependencies": {
"p-timeout": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.1.tgz",
"integrity": "sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w=="
}
}
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
......@@ -14103,6 +14181,11 @@
"slash": "^3.0.0"
}
},
"gpt-3-encoder": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/gpt-3-encoder/-/gpt-3-encoder-1.1.4.tgz",
"integrity": "sha512-fSQRePV+HUAhCn7+7HL7lNIXNm6eaFWFbNLOOGtmSJ0qJycyQvj60OvRlH7mee8xAMjBDNRdMXlMwjAbMTDjkg=="
},
"graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
......@@ -15122,6 +15205,11 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
......@@ -15160,6 +15248,14 @@
"universalify": "^2.0.0"
}
},
"keyv": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
"integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
"requires": {
"json-buffer": "3.0.1"
}
},
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
......@@ -16344,6 +16440,11 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"quick-lru": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
"integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q=="
},
"random": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/random/-/random-4.1.0.tgz",
......
......@@ -30,7 +30,7 @@ export class AccountPoolService
account: OpenAIAccount,
retry = true,
): Promise<boolean> {
const proxy = this.proxyPoolService.getProxy();
const proxy = await this.proxyPoolService.getProxy();
const state = new AccountState(
account,
new this.ChatGPTAPIBrowserConstructor({
......@@ -64,7 +64,6 @@ export class AccountPoolService
this.ChatGPTAPIBrowserConstructor = (
await eval("import('chatgpt3')")
).ChatGPTAPIBrowser;
await this.proxyPoolService.init();
this.accountInfos = await this.accountProvider.getAccounts();
this.accountInfos.forEach((a) => this.initAccount(a));
}
......@@ -98,6 +97,10 @@ export class AccountPoolService
return useAccount;
}
hasAccount(email: string) {
return this.accounts.has(email);
}
getAccount(email: string, exclude: string[] = []) {
if (!email || exclude.includes(email)) {
return this.randomAccount(exclude);
......
......@@ -6,7 +6,7 @@ import { OpenAIAccount } from '../utility/config';
export class AccountProviderService {
constructor(private config: ConfigService) {}
async getAccounts() {
return this.config.get<OpenAIAccount[]>('accounts');
return this.config.get<OpenAIAccount[]>('accounts') || [];
}
async writeBack(
......
......@@ -19,7 +19,7 @@ import {
ApiOperation,
} from '@nestjs/swagger';
import { TalkDto } from './chatgpt/talk.dto';
import { ResetConversationDto } from './conversation/reset-conversation.dto';
import { SessionDto } from './conversation/session.dto';
import { ConversationService } from './conversation/conversation.service';
import { AuthService } from './auth/auth.service';
import { AccountPoolStatusDto } from './account-pool/account-pool-status.dto';
......@@ -57,7 +57,7 @@ export class AppController {
@Post('reset-conversation')
@ApiOperation({ summary: 'Reset conversation' })
@ApiBody({ type: ResetConversationDto })
@ApiBody({ type: SessionDto })
@ApiCreatedResponse({ type: BlankReturnMessageDto })
async resetConversation(
@Body(
......@@ -66,7 +66,7 @@ export class AppController {
transformOptions: { enableImplicitConversion: true },
}),
)
dto: ResetConversationDto,
dto: SessionDto,
@Headers('Authorization') header: string,
) {
await this.authService.auth(header);
......
......@@ -10,6 +10,8 @@ import { ProxyPoolService } from './proxy-pool/proxy-pool.service';
import { AccountPoolService } from './account-pool/account-pool.service';
import { AccountProviderService } from './account-provider/account-provider.service';
import { ProxyProviderService } from './proxy-provider/proxy-provider.service';
import { KeyProviderService } from './key-provider/key-provider.service';
import { DavinciService } from './davinci/davinci.service';
@Module({
imports: [
......@@ -43,6 +45,8 @@ import { ProxyProviderService } from './proxy-provider/proxy-provider.service';
AccountPoolService,
AccountProviderService,
ProxyProviderService,
KeyProviderService,
DavinciService,
],
})
export class AppModule {}
......@@ -6,12 +6,14 @@ import { ConversationDto } from '../conversation/conversation.dto';
import { BetterLock } from 'better-lock/dist/better_lock';
import { BlankReturnMessageDto } from '../dto/ReturnMessage.dto';
import { AccountPoolService } from '../account-pool/account-pool.service';
import { DavinciService } from '../davinci/davinci.service';
@Injectable()
export class ChatgptService extends ConsoleLogger {
constructor(
private conversationService: ConversationService,
private accountPoolService: AccountPoolService,
private davinciService: DavinciService,
) {
super('ChatGPT');
}
......@@ -32,10 +34,17 @@ export class ChatgptService extends ConsoleLogger {
!previousConversation ||
account.getEmail() !== previousConversation.account;
if (!account) {
throw new BlankReturnMessageDto(
/*throw new BlankReturnMessageDto(
500,
'No available accounts',
).toException();
).toException();*/
// fallback to davinci
const davinciResponse = await this.davinciService.chat(question.text);
const dto = new ConversationDto();
dto.session = '';
dto.isNewConversation = true;
dto.text = davinciResponse.text.replace(/^<!--(.*)-->$/gm, '');
return dto;
}
this.log(`Processing chat for ${session} with ${account.getEmail()}`);
const result = await account.sendMessage(question.text, {
......@@ -72,4 +81,13 @@ export class ChatgptService extends ConsoleLogger {
? this.sessionLock.acquire(dto.session, () => this.chatProcess(dto))
: this.chatProcess(dto);
}
async hasConversation(session: string) {
const conversation = await this.conversationService.getConversation(
session,
);
return (
conversation && this.accountPoolService.hasAccount(conversation.account)
);
}
}
......@@ -8,6 +8,10 @@ import type { ChatResponse } from 'chatgpt3';
export class ConversationService {
constructor(@InjectAragami() private readonly aragami: Aragami) {}
async hasConversation(session: string) {
return this.aragami.has(ConversationStorage, session);
}
async getConversation(session: string) {
return this.aragami.get(ConversationStorage, session);
}
......
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class ResetConversationDto {
export class SessionDto {
@IsString()
@IsNotEmpty()
@ApiProperty({ description: 'Session identifier.' })
......
import { Test, TestingModule } from '@nestjs/testing';
import { DavinciService } from './davinci.service';
describe('DavinciService', () => {
let service: DavinciService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DavinciService],
}).compile();
service = module.get<DavinciService>(DavinciService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { ConsoleLogger, Injectable, OnModuleInit } from '@nestjs/common';
import { KeyProviderService } from '../key-provider/key-provider.service';
import { ChatGPTAPI, ChatMessage } from 'chatgpt4';
import { BlankReturnMessageDto } from '../dto/ReturnMessage.dto';
@Injectable()
export class DavinciService extends ConsoleLogger implements OnModuleInit {
constructor(private keyProvider: KeyProviderService) {
super('Davinci');
}
private ChatGPTApiConstructor: typeof ChatGPTAPI;
async onModuleInit() {
const { ChatGPTAPI } = await eval("import('chatgpt4')");
this.ChatGPTApiConstructor = ChatGPTAPI;
}
private pointer = 0;
private async getKey(exclude: string[] = []) {
const excludeSet = new Set(exclude);
const keys = (await this.keyProvider.getKeys()).filter(
(key) => !excludeSet.has(key),
);
if (!keys.length) {
throw new BlankReturnMessageDto(
500,
'No available accounts.',
).toException();
}
const index = this.pointer++ % keys.length;
return keys[index];
}
getInstance(apiKey: string) {
return new this.ChatGPTApiConstructor({
apiKey,
completionParams: {
model: 'text-davinci-003',
},
getMessageById: async () => undefined,
upsertMessage: async () => undefined,
});
}
async chat(text: string, excludeKeys: string[] = []): Promise<ChatMessage> {
const key = await this.getKey(excludeKeys);
const api = this.getInstance(key);
try {
return await api.sendMessage(text);
} catch (e) {
this.error(`Error with key ${key}}`);
this.error(e);
return this.chat(text, [...excludeKeys, key]);
} finally {
// clean off the message store
await api['_messageStore'].disconnect();
delete api['_messageStore'];
}
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { KeyProviderService } from './key-provider.service';
describe('KeyProviderService', () => {
let service: KeyProviderService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [KeyProviderService],
}).compile();
service = module.get<KeyProviderService>(KeyProviderService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class KeyProviderService {
constructor(private config: ConfigService) {}
async getKeys() {
return this.config.get<string[]>('apiKeys') || [];
}
}
......@@ -4,20 +4,16 @@ import { ProxyProviderService } from '../proxy-provider/proxy-provider.service';
@Injectable()
export class ProxyPoolService {
private pointer = 0;
private proxies: string[] = [];
constructor(private proxyProvider: ProxyProviderService) {}
async init() {
this.proxies = await this.proxyProvider.getProxies();
}
getProxy() {
if (!this.proxies?.length) {
async getProxy() {
const proxies = await this.proxyProvider.getProxies();
if (!proxies?.length) {
return;
}
if (this.pointer >= this.proxies.length) {
if (this.pointer >= proxies.length) {
this.pointer = 0;
}
return this.proxies[this.pointer++];
return proxies[this.pointer++];
}
}
......@@ -5,6 +5,6 @@ import { ConfigService } from '@nestjs/config';
export class ProxyProviderService {
constructor(private config: ConfigService) {}
async getProxies() {
return this.config.get<string[]>('proxies');
return this.config.get<string[]>('proxies') || [];
}
}
......@@ -15,6 +15,7 @@ const defaultConfig = {
redisUrl: '',
token: '',
proxies: [] as string[],
apiKeys: [] as string[],
};
export type Config = typeof defaultConfig;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment