Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OV-52: add chat controller #63

Merged
merged 55 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d377c33
OV-52: - generatedText type
sergiy4 Aug 23, 2024
c369e99
OV-52: + enums
sergiy4 Aug 23, 2024
0a9de7a
OV-52: + HTTPMethod enum
sergiy4 Aug 23, 2024
23bbba9
OV-52: * ApiPath enum
sergiy4 Aug 23, 2024
60bb05c
OV-52: + HTTPMethod enum
sergiy4 Aug 23, 2024
16b9285
OV-52: + GenerateTextRequestDto type
sergiy4 Aug 23, 2024
fd75b00
OV-52: + validation schema
sergiy4 Aug 23, 2024
a9b7198
OV-52: * rename file
sergiy4 Aug 23, 2024
9dc16e6
OV-52: * rename endpoint
sergiy4 Aug 23, 2024
23fd9b3
OV-52: + shared exports
sergiy4 Aug 23, 2024
2cdc718
OV-52: + session key
sergiy4 Aug 23, 2024
da455ab
OV-52: + fastify session library
sergiy4 Aug 23, 2024
0dcbe1c
OV-52: + types
sergiy4 Aug 23, 2024
31e7bdc
OV-52: + libs
sergiy4 Aug 23, 2024
fbd2dff
OV-52: + libs
sergiy4 Aug 23, 2024
dbe2dfb
OV-52: + plugin
sergiy4 Aug 23, 2024
e69ce2a
OV-52: + open ai service
sergiy4 Aug 23, 2024
61e42d4
OV-52: + chat controller
sergiy4 Aug 23, 2024
f4a1e58
OV-52: + fastify module
sergiy4 Aug 23, 2024
0314364
Merge branch 'next' into task/OV-52-add-chat-history-saving
sergiy4 Aug 23, 2024
7b3c962
OV-52: * HttpMethod type
sergiy4 Aug 23, 2024
3a1ebc3
OV-52: * OpenAISerrvice type
sergiy4 Aug 23, 2024
4e95492
OV-52: + clear chat logic
sergiy4 Aug 23, 2024
6d3f7e0
OV-52: - sessionChatHistory type
sergiy4 Aug 24, 2024
331a987
OV-52: + tiktoken package
sergiy4 Aug 24, 2024
d5438bf
OV-52: * HttpMethod type
sergiy4 Aug 24, 2024
37c3dd9
OV-52: - sessionChatHistory type
sergiy4 Aug 24, 2024
d76157a
OV-52: * MAX_TOKEN
sergiy4 Aug 24, 2024
33b4c70
OV-52: + delete old messages logic
sergiy4 Aug 24, 2024
6b646bb
OV-52: + delete session handler
sergiy4 Aug 24, 2024
1c3f5ef
OV-52: * openAi service
sergiy4 Aug 24, 2024
ff6f18c
OV-52: * openAi service
sergiy4 Aug 24, 2024
d400b39
OV-52: + chat service
sergiy4 Aug 26, 2024
3ef2954
OV-52: * openAI service
sergiy4 Aug 26, 2024
e0f6a47
OV-52: * chat controller
sergiy4 Aug 26, 2024
1029592
OV-52: * chat service
sergiy4 Aug 26, 2024
dec2880
OV-52: * chat controller
sergiy4 Aug 26, 2024
053a0dd
OV-52: * move OpenAi service
sergiy4 Aug 26, 2024
6f50b86
OV-52: * chat controller
sergiy4 Aug 26, 2024
1dbd383
OV-52: * rename
sergiy4 Aug 27, 2024
222d36e
OV-52: + swagger docs
sergiy4 Aug 27, 2024
14cb9f3
OV-52: * .env.example
sergiy4 Aug 27, 2024
bd51240
OV-52: + merge
sergiy4 Aug 27, 2024
623fee7
OV-52: - as const
sergiy4 Aug 27, 2024
567bd8b
OV-52: * chat enum
sergiy4 Aug 27, 2024
37664fb
OV-52: * chat service
sergiy4 Aug 27, 2024
08d1e70
OV-52: * chat controller
sergiy4 Aug 27, 2024
0f66887
OV-52: * openai service
sergiy4 Aug 27, 2024
a1eeb57
OV-52: * .env.example
sergiy4 Aug 28, 2024
a31d874
OV-52: + merge
sergiy4 Aug 28, 2024
ded87cc
OV-52: * exports
sergiy4 Aug 28, 2024
70f9652
Merge branch 'next' into task/OV-52-add-chat-history-saving
sergiy4 Aug 28, 2024
9c5488e
OV-52: + merge
sergiy4 Aug 28, 2024
1c21628
OV-52: + merge
sergiy4 Aug 29, 2024
dcb791f
Merge branch 'next' into task/OV-52-add-chat-history-saving
sergiy4 Aug 29, 2024
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
5 changes: 4 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"@aws-sdk/client-s3": "3.635.0",
"@aws-sdk/s3-request-presigner": "3.635.0",
"@fastify/multipart": "8.3.0",
"@fastify/cookie": "9.4.0",
"@fastify/session": "10.9.0",
"@fastify/static": "7.0.4",
"@fastify/swagger": "8.15.0",
"@fastify/swagger-ui": "4.0.1",
Expand All @@ -48,6 +50,7 @@
"pino": "9.3.2",
"pino-pretty": "10.3.1",
"shared": "*",
"swagger-jsdoc": "6.2.8"
"swagger-jsdoc": "6.2.8",
"tiktoken": "1.0.16"
}
}
176 changes: 176 additions & 0 deletions backend/src/bundles/chat/chat.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { type FastifySessionObject } from '@fastify/session';

import {
type ApiHandlerOptions,
type ApiHandlerResponse,
BaseController,
} from '~/common/controller/controller.js';
import { ApiPath } from '~/common/enums/enums.js';
import { HttpCode, HTTPMethod } from '~/common/http/http.js';
import { type Logger } from '~/common/logger/logger.js';
import { MAX_TOKEN } from '~/common/services/open-ai/libs/constants/constants.js';
import {
ChatPath,
OpenAIRole,
} from '~/common/services/open-ai/libs/enums/enums.js';
import { type OpenAIService } from '~/common/services/open-ai/open-ai.service.js';

import { type ChatService } from './chat.service.js';
import { type GenerateTextRequestDto } from './libs/types/types.js';
import { textGenerationValidationSchema } from './libs/validation-schemas/validation-schemas.js';

class ChatController extends BaseController {
private openAIService: OpenAIService;
private chatService: ChatService;

public constructor(
logger: Logger,
openAIService: OpenAIService,
chatService: ChatService,
) {
super(logger, ApiPath.CHAT);

this.openAIService = openAIService;
this.chatService = chatService;

this.addRoute({
path: ChatPath.ROOT,
method: HTTPMethod.POST,
validation: {
body: textGenerationValidationSchema,
},
handler: (options) =>
this.generateChatAnswer(
options as ApiHandlerOptions<{
body: GenerateTextRequestDto;
session: FastifySessionObject;
}>,
),
});

this.addRoute({
path: ChatPath.ROOT,
method: HTTPMethod.DELETE,
handler: (options) =>
this.deleteSession(
options as ApiHandlerOptions<{
session: FastifySessionObject;
}>,
),
});
}

/**
* @swagger
* /chat/:
* post:
* description: Returns generated text by Open AI
* requestBody:
* description: User message
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* responses:
* 200:
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* generatedText:
* type: string
*/

private async generateChatAnswer(
sergiy4 marked this conversation as resolved.
Show resolved Hide resolved
options: ApiHandlerOptions<{
body: GenerateTextRequestDto;
session: FastifySessionObject;
}>,
): Promise<ApiHandlerResponse> {
const { body, session } = options;

session.chatHistory = this.chatService.addMessageToHistory(
session.chatHistory,
body.message,
OpenAIRole.USER,
);

session.chatHistory = this.chatService.deleteOldMessages(
session.chatHistory,
MAX_TOKEN,
);

const generatedText = await this.openAIService.generateText(
session.chatHistory,
);

session.chatHistory = this.chatService.addMessageToHistory(
session.chatHistory,
generatedText,
OpenAIRole.ASSISTANT,
);

return {
payload: { generatedText },
status: HttpCode.OK,
};
}

/**
* @swagger
* /chat/:
* delete:
* description: Clears chat history
* requestBody:
* description: User message
* required: false
* responses:
* 200:
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* isDeleted:
* type: boolean
* 500:
* description: Failed operation
* content:
* application/json:
* schema:
* type: object
* properties:
* isDeleted:
* type: boolean
*/
private deleteSession(
sergiy4 marked this conversation as resolved.
Show resolved Hide resolved
options: ApiHandlerOptions<{
session: FastifySessionObject;
}>,
): ApiHandlerResponse {
const { session } = options;

session.destroy((error) => {
if (error) {
return {
payload: { isDeleted: false },
status: HttpCode.INTERNAL_SERVER_ERROR,
};
}
});

return {
payload: { isDeleted: true },
status: HttpCode.OK,
};
}
}

export { ChatController };
64 changes: 64 additions & 0 deletions backend/src/bundles/chat/chat.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { type ValueOf } from 'shared';
import { type Tiktoken, encoding_for_model } from 'tiktoken';

import { CHAT_MODEL } from '~/common/services/open-ai/libs/constants/constants.js';
import { type OpenAIRole } from '~/common/services/open-ai/libs/enums/enums.js';

import {
type ChatService as ChatServiceT,
type Message,
} from './libs/types/types.js';

class ChatService implements ChatServiceT {
private modelEncoding: Tiktoken;

public constructor() {
this.modelEncoding = encoding_for_model(CHAT_MODEL);
}

public addMessageToHistory(
chatHistory: Message[],
userMessage: string,
role: ValueOf<typeof OpenAIRole>,
): Message[] {
const newUserMessage = {
content: userMessage,
role,
};

return [...chatHistory, newUserMessage];
}

private countTokens(messages: Message[]): number {
return messages.reduce(
(sum, message) =>
sum + this.modelEncoding.encode(message.content).length,
0,
);
}

public deleteOldMessages(
messages: Message[],
maxTokens: number,
): Message[] {
let totalTokens = this.countTokens(messages);
let updatedMessages = [...messages];

while (totalTokens > maxTokens && updatedMessages.length > 0) {
const [removedMessage, ...rest] = updatedMessages;
updatedMessages = rest;

if (!removedMessage) {
break;
}

totalTokens -= this.modelEncoding.encode(
removedMessage.content,
).length;
}

return updatedMessages;
}
}

export { ChatService };
10 changes: 10 additions & 0 deletions backend/src/bundles/chat/chat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { logger } from '~/common/logger/logger.js';
import { openAIService } from '~/common/services/services.js';

import { ChatController } from './chat.controller.js';
import { ChatService } from './chat.service.js';

const chatService = new ChatService();
const chatController = new ChatController(logger, openAIService, chatService);

export { chatController };
16 changes: 16 additions & 0 deletions backend/src/bundles/chat/libs/types/chat-service.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type ValueOf } from 'shared';

import { type OpenAIRole } from '~/common/services/open-ai/libs/enums/enums.js';

import { type Message } from './message.type.js';

type ChatService = {
addMessageToHistory(
chatHistory: Message[],
userMessage: string,
role: ValueOf<typeof OpenAIRole>,
): Message[];
deleteOldMessages(messages: Message[], maxTokens: number): void;
};

export { type ChatService };
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type ValueOf } from 'shared';

import { type OpenAIRole } from '../enums/enums.js';
import { type OpenAIRole } from '~/common/services/open-ai/libs/enums/enums.js';

type Message = {
role: ValueOf<typeof OpenAIRole>;
Expand Down
3 changes: 3 additions & 0 deletions backend/src/bundles/chat/libs/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { type ChatService } from './chat-service.type.js';
export { type Message } from './message.type.js';
export { type GenerateTextRequestDto } from 'shared';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { textGenerationValidationSchema } from 'shared';
6 changes: 6 additions & 0 deletions backend/src/common/config/base-config.package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class BaseConfig implements Config {
env: 'OPEN_AI_KEY',
default: null,
},
SESSION_KEY: {
doc: 'Key for sessions',
format: String,
env: 'SESSION_KEY',
default: null,
},
},
DB: {
CONNECTION_STRING: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type EnvironmentSchema = {
PORT: number;
ENVIRONMENT: ValueOf<typeof AppEnvironment>;
OPEN_AI_KEY: string;
SESSION_KEY: string;
};
DB: {
CONNECTION_STRING: string;
Expand Down
3 changes: 2 additions & 1 deletion backend/src/common/controller/base-controller.package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ class BaseController implements Controller {
private mapRequest(
request: Parameters<ServerAppRouteParameters['handler']>[0],
): ApiHandlerOptions {
const { body, query, params } = request;
const { body, query, params, session } = request;

return {
body,
query,
params,
session,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ type DefaultApiHandlerOptions = {
body?: unknown;
query?: unknown;
params?: unknown;
session?: unknown;
};

type ApiHandlerOptions<
Expand All @@ -10,6 +11,7 @@ type ApiHandlerOptions<
body: T['body'];
query: T['query'];
params: T['params'];
session: T['session'];
};

export { type ApiHandlerOptions };
2 changes: 1 addition & 1 deletion backend/src/common/http/enums/enums.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { HttpCode } from 'shared';
export { HttpCode, HTTPMethod } from 'shared';
2 changes: 1 addition & 1 deletion backend/src/common/http/http.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { HttpCode } from './enums/enums.js';
export { HttpCode, HTTPMethod } from './enums/enums.js';
export { HttpError } from './exceptions/exceptions.js';
export { type HttpMethod } from './types/types.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const ControllerHook = {
ON_REQUEST: 'onRequest',
} as const;

export { ControllerHook };
1 change: 1 addition & 0 deletions backend/src/common/plugins/libs/enums/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ControllerHook } from './controller-hook.enum.js';
1 change: 1 addition & 0 deletions backend/src/common/plugins/plugins.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { authenticateJWT } from './auth/auth-jwt.plugin.js';
export { session } from './session/session.plugin.js';
Loading
Loading