Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 6097606

Browse files
authored
Merge pull request #599 from janhq/feat/store-messages-cli
feat: store messages when chat via cli
2 parents c588048 + c8e3e84 commit 6097606

File tree

15 files changed

+272
-66
lines changed

15 files changed

+272
-66
lines changed

cortex-js/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"sqlite3": "^5.1.7",
5050
"typeorm": "^0.3.20",
5151
"ulid": "^2.3.0",
52-
"yaml": "^2.4.2"
52+
"yaml": "^2.4.2",
53+
"uuid": "^9.0.1"
5354
},
5455
"devDependencies": {
5556
"@nestjs/cli": "^10.0.0",
@@ -62,6 +63,7 @@
6263
"@types/jest": "^29.5.2",
6364
"@types/node": "^20.12.9",
6465
"@types/supertest": "^6.0.0",
66+
"@types/uuid": "^9.0.8",
6567
"@typescript-eslint/eslint-plugin": "^6.0.0",
6668
"@typescript-eslint/parser": "^6.0.0",
6769
"eslint": "^8.42.0",

cortex-js/src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ExtensionModule } from './infrastructure/repositories/extensions/extens
1010
import { CortexModule } from './usecases/cortex/cortex.module';
1111
import { ConfigModule } from '@nestjs/config';
1212
import { env } from 'node:process';
13+
import { SeedService } from './usecases/seed/seed.service';
1314

1415
@Module({
1516
imports: [
@@ -29,5 +30,6 @@ import { env } from 'node:process';
2930
CortexModule,
3031
ExtensionModule,
3132
],
33+
providers: [SeedService],
3234
})
3335
export class AppModule {}

cortex-js/src/command.module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { CortexModule } from './usecases/cortex/cortex.module';
66
import { ServeCommand } from './infrastructure/commanders/serve.command';
77
import { ModelsCommand } from './infrastructure/commanders/models.command';
88
import { ExtensionModule } from './infrastructure/repositories/extensions/extension.module';
9-
import { ChatModule } from './usecases/chat/chat.module';
109
import { InitCommand } from './infrastructure/commanders/init.command';
1110
import { HttpModule } from '@nestjs/axios';
1211
import { InitRunModeQuestions } from './infrastructure/commanders/questions/init.questions';
@@ -20,8 +19,10 @@ import { ModelGetCommand } from './infrastructure/commanders/models/model-get.co
2019
import { ModelRemoveCommand } from './infrastructure/commanders/models/model-remove.command';
2120
import { RunCommand } from './infrastructure/commanders/shortcuts/run.command';
2221
import { InitCudaQuestions } from './infrastructure/commanders/questions/cuda.questions';
23-
import { CliUsecasesModule } from './infrastructure/commanders/usecases/cli.usecases.module';
2422
import { ModelUpdateCommand } from './infrastructure/commanders/models/model-update.command';
23+
import { AssistantsModule } from './usecases/assistants/assistants.module';
24+
import { CliUsecasesModule } from './infrastructure/commanders/usecases/cli.usecases.module';
25+
import { MessagesModule } from './usecases/messages/messages.module';
2526

2627
@Module({
2728
imports: [
@@ -36,6 +37,8 @@ import { ModelUpdateCommand } from './infrastructure/commanders/models/model-upd
3637
ExtensionModule,
3738
HttpModule,
3839
CliUsecasesModule,
40+
AssistantsModule,
41+
MessagesModule,
3942
],
4043
providers: [
4144
CortexCommand,

cortex-js/src/infrastructure/commanders/chat.command.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { exit } from 'node:process';
44

55
type ChatOptions = {
66
model?: string;
7+
threadId?: string;
78
};
89

910
@SubCommand({ name: 'chat', description: 'Start a chat with a model' })
@@ -19,14 +20,23 @@ export class ChatCommand extends CommandRunner {
1920
exit(1);
2021
}
2122

22-
return this.chatCliUsecases.chat(modelId);
23+
return this.chatCliUsecases.chat(modelId, option.threadId);
2324
}
2425

2526
@Option({
2627
flags: '-m, --model <model_id>',
2728
description: 'Model Id to start chat with',
29+
required: true,
2830
})
2931
parseModelId(value: string) {
3032
return value;
3133
}
34+
35+
@Option({
36+
flags: '-t, --thread <thread_id>',
37+
description: 'Thread Id. If not provided, will create new thread',
38+
})
39+
parseThreadId(value: string) {
40+
return value;
41+
}
3242
}

cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { exit } from 'node:process';
55
import { ChatCliUsecases } from '../usecases/chat.cli.usecases';
66
import { defaultCortexCppHost, defaultCortexCppPort } from 'constant';
77

8+
type RunOptions = {
9+
threadId?: string;
10+
};
11+
812
@SubCommand({
913
name: 'run',
1014
description: 'EXPERIMENTAL: Shortcut to start a model and chat',
@@ -18,7 +22,7 @@ export class RunCommand extends CommandRunner {
1822
super();
1923
}
2024

21-
async run(input: string[]): Promise<void> {
25+
async run(input: string[], option?: RunOptions): Promise<void> {
2226
if (input.length === 0) {
2327
console.error('Model Id is required');
2428
exit(1);
@@ -31,14 +35,14 @@ export class RunCommand extends CommandRunner {
3135
false,
3236
);
3337
await this.modelsUsecases.startModel(modelId);
34-
await this.chatCliUsecases.chat(modelId);
38+
await this.chatCliUsecases.chat(modelId, option?.threadId);
3539
}
3640

3741
@Option({
38-
flags: '-m, --model <model_id>',
39-
description: 'Model Id to start chat with',
42+
flags: '-t, --thread <thread_id>',
43+
description: 'Thread Id. If not provided, will create new thread',
4044
})
41-
parseModelId(value: string) {
45+
parseThreadId(value: string) {
4246
return value;
4347
}
4448
}
Lines changed: 138 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
import { ChatUsecases } from '@/usecases/chat/chat.usecases';
2-
import { ChatCompletionRole } from '@/domain/models/message.interface';
2+
import {
3+
ChatCompletionRole,
4+
ContentType,
5+
MessageStatus,
6+
} from '@/domain/models/message.interface';
37
import { exit, stdin, stdout } from 'node:process';
48
import * as readline from 'node:readline/promises';
59
import { ChatCompletionMessage } from '@/infrastructure/dtos/chat/chat-completion-message.dto';
610
import { CreateChatCompletionDto } from '@/infrastructure/dtos/chat/create-chat-completion.dto';
711
import { CortexUsecases } from '@/usecases/cortex/cortex.usecases';
812
import { Injectable } from '@nestjs/common';
13+
import { ThreadsUsecases } from '@/usecases/threads/threads.usecases';
14+
import { Thread } from '@/domain/models/thread.interface';
15+
import { CreateThreadDto } from '@/infrastructure/dtos/threads/create-thread.dto';
16+
import { AssistantsUsecases } from '@/usecases/assistants/assistants.usecases';
17+
import { CreateThreadAssistantDto } from '@/infrastructure/dtos/threads/create-thread-assistant.dto';
18+
import { CreateThreadModelInfoDto } from '@/infrastructure/dtos/threads/create-thread-model-info.dto';
19+
import { ModelsUsecases } from '@/usecases/models/models.usecases';
20+
import stream from 'stream';
21+
import { CreateMessageDto } from '@/infrastructure/dtos/messages/create-message.dto';
22+
import { MessagesUsecases } from '@/usecases/messages/messages.usecases';
923

1024
@Injectable()
1125
export class ChatCliUsecases {
@@ -14,13 +28,59 @@ export class ChatCliUsecases {
1428
private exitMessage = 'Bye!';
1529

1630
constructor(
31+
private readonly assistantUsecases: AssistantsUsecases,
32+
private readonly threadUsecases: ThreadsUsecases,
1733
private readonly chatUsecases: ChatUsecases,
1834
private readonly cortexUsecases: CortexUsecases,
35+
private readonly modelsUsecases: ModelsUsecases,
36+
private readonly messagesUsecases: MessagesUsecases,
1937
) {}
2038

21-
async chat(modelId: string): Promise<void> {
39+
private async getOrCreateNewThread(
40+
modelId: string,
41+
threadId?: string,
42+
): Promise<Thread> {
43+
if (threadId) {
44+
const thread = await this.threadUsecases.findOne(threadId);
45+
if (!thread) throw new Error(`Cannot find thread with id: ${threadId}`);
46+
return thread;
47+
}
48+
49+
const model = await this.modelsUsecases.findOne(modelId);
50+
if (!model) throw new Error(`Cannot find model with id: ${modelId}`);
51+
52+
const assistant = await this.assistantUsecases.findOne('jan');
53+
if (!assistant) throw new Error('No assistant available');
54+
55+
const createThreadModel: CreateThreadModelInfoDto = {
56+
id: modelId,
57+
settings: model.settings,
58+
parameters: model.parameters,
59+
};
60+
61+
const assistantDto: CreateThreadAssistantDto = {
62+
assistant_id: assistant.id,
63+
assistant_name: assistant.name,
64+
model: createThreadModel,
65+
};
66+
67+
const createThreadDto: CreateThreadDto = {
68+
title: 'New Thread',
69+
assistants: [assistantDto],
70+
};
71+
72+
return this.threadUsecases.create(createThreadDto);
73+
}
74+
75+
async chat(modelId: string, threadId?: string): Promise<void> {
2276
console.log(`Inorder to exit, type '${this.exitClause}'.`);
23-
const messages: ChatCompletionMessage[] = [];
77+
const thread = await this.getOrCreateNewThread(modelId, threadId);
78+
const messages: ChatCompletionMessage[] = (
79+
await this.messagesUsecases.getLastMessagesByThread(thread.id, 10)
80+
).map((message) => ({
81+
content: message.content[0].text.value,
82+
role: message.role,
83+
}));
2484

2585
const rl = readline.createInterface({
2686
input: stdin,
@@ -36,6 +96,8 @@ export class ChatCliUsecases {
3696
});
3797
});
3898

99+
const decoder = new TextDecoder('utf-8');
100+
39101
rl.on('line', (userInput: string) => {
40102
if (userInput.trim() === this.exitClause) {
41103
rl.close();
@@ -47,6 +109,22 @@ export class ChatCliUsecases {
47109
role: ChatCompletionRole.User,
48110
});
49111

112+
const createMessageDto: CreateMessageDto = {
113+
thread_id: thread.id,
114+
role: ChatCompletionRole.User,
115+
content: [
116+
{
117+
type: ContentType.Text,
118+
text: {
119+
value: userInput,
120+
annotations: [],
121+
},
122+
},
123+
],
124+
status: MessageStatus.Ready,
125+
};
126+
this.messagesUsecases.create(createMessageDto);
127+
50128
const chatDto: CreateChatCompletionDto = {
51129
messages,
52130
model: modelId,
@@ -59,44 +137,70 @@ export class ChatCliUsecases {
59137
top_p: 0.7,
60138
};
61139

62-
const decoder = new TextDecoder('utf-8');
63-
this.chatUsecases.inference(chatDto, {}).then((response) => {
64-
response.on('error', (error: any) => {
65-
console.error(error);
66-
rl.prompt();
67-
});
140+
this.chatUsecases
141+
.inference(chatDto, {})
142+
.then((response: stream.Readable) => {
143+
let assistantResponse: string = '';
68144

69-
response.on('end', () => {
70-
console.log('\n');
71-
rl.prompt();
72-
});
145+
response.on('error', (error: any) => {
146+
console.error(error);
147+
rl.prompt();
148+
});
73149

74-
response.on('data', (chunk: any) => {
75-
let content = '';
76-
const text = decoder.decode(chunk);
77-
const lines = text.trim().split('\n');
78-
let cachedLines = '';
79-
for (const line of lines) {
80-
try {
81-
const toParse = cachedLines + line;
82-
if (!line.includes('data: [DONE]')) {
83-
const data = JSON.parse(toParse.replace('data: ', ''));
84-
content += data.choices[0]?.delta?.content ?? '';
85-
86-
if (content.startsWith('assistant: ')) {
87-
content = content.replace('assistant: ', '');
88-
}
150+
response.on('end', () => {
151+
messages.push({
152+
content: assistantResponse,
153+
role: ChatCompletionRole.Assistant,
154+
});
155+
const createMessageDto: CreateMessageDto = {
156+
thread_id: thread.id,
157+
role: ChatCompletionRole.Assistant,
158+
content: [
159+
{
160+
type: ContentType.Text,
161+
text: {
162+
value: assistantResponse,
163+
annotations: [],
164+
},
165+
},
166+
],
167+
status: MessageStatus.Ready,
168+
};
89169

90-
if (content.trim().length > 0) {
91-
stdout.write(content);
170+
this.messagesUsecases.create(createMessageDto).then(() => {
171+
assistantResponse = '';
172+
console.log('\n');
173+
rl.prompt();
174+
});
175+
});
176+
177+
response.on('data', (chunk: any) => {
178+
let content = '';
179+
const text = decoder.decode(chunk);
180+
const lines = text.trim().split('\n');
181+
let cachedLines = '';
182+
for (const line of lines) {
183+
try {
184+
const toParse = cachedLines + line;
185+
if (!line.includes('data: [DONE]')) {
186+
const data = JSON.parse(toParse.replace('data: ', ''));
187+
content += data.choices[0]?.delta?.content ?? '';
188+
189+
if (content.startsWith('assistant: ')) {
190+
content = content.replace('assistant: ', '');
191+
}
192+
193+
if (content.trim().length > 0) {
194+
assistantResponse += content;
195+
stdout.write(content);
196+
}
92197
}
198+
} catch {
199+
cachedLines = line;
93200
}
94-
} catch {
95-
cachedLines = line;
96201
}
97-
}
202+
});
98203
});
99-
});
100204
});
101205
}
102206
}

cortex-js/src/infrastructure/commanders/usecases/cli.usecases.module.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,20 @@ import { ModelsModule } from '@/usecases/models/models.module';
66
import { ChatCliUsecases } from './chat.cli.usecases';
77
import { ChatModule } from '@/usecases/chat/chat.module';
88
import { CortexModule } from '@/usecases/cortex/cortex.module';
9+
import { ThreadsModule } from '@/usecases/threads/threads.module';
10+
import { AssistantsModule } from '@/usecases/assistants/assistants.module';
11+
import { MessagesModule } from '@/usecases/messages/messages.module';
912

1013
@Module({
11-
imports: [HttpModule, ModelsModule, ChatModule, CortexModule],
14+
imports: [
15+
HttpModule,
16+
ModelsModule,
17+
ChatModule,
18+
CortexModule,
19+
ThreadsModule,
20+
AssistantsModule,
21+
MessagesModule,
22+
],
1223
providers: [InitCliUsecases, ModelsCliUsecases, ChatCliUsecases],
1324
exports: [InitCliUsecases, ModelsCliUsecases, ChatCliUsecases],
1425
})

0 commit comments

Comments
 (0)