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

(AI) LLM Yorkie Intelligence Development (GitHub Issue Writing, PR Writing, Additional Question Function) #120

Merged
merged 7 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 26 additions & 8 deletions backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ datasource db {
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
socialProvider String @map("social_provider")
socialUid String @unique @map("social_uid")
nickname String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
userWorkspaceList UserWorkspace[]
id String @id @default(auto()) @map("_id") @db.ObjectId
socialProvider String @map("social_provider")
socialUid String @unique @map("social_uid")
nickname String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
userWorkspaceList UserWorkspace[]
intelligenceLogList IntelligenceLog[]

@@map("users")
}
Expand Down Expand Up @@ -58,6 +59,7 @@ model Document {
workspace Workspace @relation(fields: [workspaceId], references: [id])
workspaceId String @map("workspace_id") @db.ObjectId
documentSharingTokenList DocumentSharingToken[]
intelligenceLogList IntelligenceLog[]

@@map("documents")
}
Expand All @@ -84,5 +86,21 @@ model DocumentSharingToken {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

@@map("document_sharing_token")
@@map("document_sharing_tokens")
}

model IntelligenceLog {
id String @id @default(auto()) @map("_id") @db.ObjectId
document Document @relation(fields: [documentId], references: [id])
documentId String @map("document_id") @db.ObjectId
user User @relation(fields: [userId], references: [id])
userId String @map("user_id") @db.ObjectId
memoryKey String
question String
answer String
expiredAt DateTime? @map("expired_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

@@map("intelligence_logs")
}
3 changes: 3 additions & 0 deletions backend/src/intelligence/dto/run-feature.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ApiProperty } from "@nestjs/swagger";

export class RunFeatureDto {
@ApiProperty({ type: String, description: "ID of document" })
documentId: string;

@ApiProperty({ type: String, description: "Content to run feature" })
content: string;
}
12 changes: 12 additions & 0 deletions backend/src/intelligence/dto/run-followup.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiProperty } from "@nestjs/swagger";

export class RunFollowUpDto {
@ApiProperty({ type: String, description: "ID of document" })
documentId: string;

@ApiProperty({ type: String, description: "Key of chat history" })
memoryKey: string;

@ApiProperty({ type: String, description: "Content to run feature" })
content: string;
}
41 changes: 32 additions & 9 deletions backend/src/intelligence/intelligence.controller.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,46 @@
import { Body, Controller, Param, Post, Req, Res } from "@nestjs/common";
import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger";
import {
ApiBearerAuth,
ApiBody,
ApiOkResponse,
ApiOperation,
ApiParam,
ApiTags,
} from "@nestjs/swagger";
import { IntelligenceService } from "./intelligence.service";
import { AuthroizedRequest } from "src/utils/types/req.type";
import { Feature } from "./types/feature.type";
import { RunFeatureDto } from "./dto/run-feature.dto";
import { Response } from "express";
import { RunFollowUpDto } from "./dto/run-followup.dto";

@ApiTags("Intelligences")
@ApiBearerAuth()
@Controller("intelligence")
export class IntelligenceController {
constructor(private intelligenceService: IntelligenceService) {}

@Post("")
@ApiOperation({
summary: "Run the Follow Up Yorkie Intelligence after Feature Running",
description: "Run the follow up requests",
})
@ApiBody({
type: RunFollowUpDto,
})
@ApiOkResponse({ type: String })
async runFollowUp(
@Req() req: AuthroizedRequest,
@Res() res: Response,
@Body() runFollowUpDto: RunFollowUpDto
): Promise<void> {
await this.intelligenceService.runFollowUp(req.user.id, runFollowUpDto, (chunk) =>
res.write(chunk)
);

res.end();
}

@Post(":feature")
@ApiOperation({
summary: "Run the Yorkie Intelligence Feature",
Expand All @@ -29,16 +58,10 @@ export class IntelligenceController {
@Param("feature") feature: Feature,
@Body() runFeatureDto: RunFeatureDto
): Promise<void> {
const stream = await this.intelligenceService.runFeature(
req.user.id,
feature,
runFeatureDto.content
await this.intelligenceService.runFeature(req.user.id, feature, runFeatureDto, (chunk) =>
res.write(chunk)
);

for await (const chunk of stream) {
res.write(chunk);
}

res.end();
}
}
3 changes: 2 additions & 1 deletion backend/src/intelligence/intelligence.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Module } from "@nestjs/common";
import { IntelligenceController } from "./intelligence.controller";
import { IntelligenceService } from "./intelligence.service";
import { LangchainModule } from "src/langchain/langchain.module";
import { PrismaService } from "src/db/prisma.service";

@Module({
imports: [LangchainModule],
controllers: [IntelligenceController],
providers: [IntelligenceService],
providers: [IntelligenceService, PrismaService],
})
export class IntelligenceModule {}
101 changes: 95 additions & 6 deletions backend/src/intelligence/intelligence.service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { Inject, Injectable, NotFoundException } from "@nestjs/common";
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
import { BufferMemory } from "langchain/memory";
import { Feature } from "./types/feature.type";
import { githubIssuePromptTemplate } from "./prompt/github-issue";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { githubPrPromptTemplate } from "./prompt/github-pr";
import { generateRandomKey } from "src/utils/functions/random-string";
import { PrismaService } from "src/db/prisma.service";
import { RunFeatureDto } from "./dto/run-feature.dto";
import { RunFollowUpDto } from "./dto/run-followup.dto";
import { followUpPromptTemplate } from "./prompt/followup";

@Injectable()
export class IntelligenceService {
constructor(@Inject("ChatModel") private chatModel: BaseChatModel) {}
constructor(
@Inject("ChatModel") private chatModel: BaseChatModel,
private prismaService: PrismaService
) {}

private selectPromptTemplate(feature: Feature) {
const promptTemplates = {
[Feature.GITHUB_ISSUE]: githubIssuePromptTemplate,
[Feature.GITHUB_PR]: githubPrPromptTemplate,
};
const selectedPrompt = promptTemplates[feature];

Expand All @@ -19,15 +30,93 @@ export class IntelligenceService {
return selectedPrompt;
}

async runFeature(userId: string, feature: Feature, content: string) {
async runFeature(
userId: string,
feature: Feature,
runFeatureDto: RunFeatureDto,
handleChunk: (token: string) => void
) {
const promptTemplate = this.selectPromptTemplate(feature);
const prompt = await promptTemplate.format({
content,
content: runFeatureDto.content,
});
const parser = new StringOutputParser();
const memoryKey = generateRandomKey();
const stream = await this.chatModel.pipe(new StringOutputParser()).stream(prompt, {
tags: [
feature,
`user_id: ${userId}`,
`document_id: ${runFeatureDto.documentId}`,
`memory_key: ${memoryKey}`,
],
});

let result = "";
handleChunk(`${memoryKey}\n`);
for await (const chunk of stream) {
result += chunk;
handleChunk(chunk);
}

await this.prismaService.intelligenceLog.create({
data: {
memoryKey,
question: prompt,
answer: result,
userId,
documentId: runFeatureDto.documentId,
},
});
}

async runFollowUp(
userId: string,
runFollowUpDto: RunFollowUpDto,
handleChunk: (token: string) => void
) {
const chatPromptMemory = new BufferMemory({
memoryKey: "chat_history",
returnMessages: true,
});
const intelligenceLogList = await this.prismaService.intelligenceLog.findMany({
where: {
memoryKey: runFollowUpDto.memoryKey,
documentId: runFollowUpDto.documentId,
},
});

for (const intelligenceLog of intelligenceLogList) {
await chatPromptMemory.chatHistory.addUserMessage(intelligenceLog.question);
await chatPromptMemory.chatHistory.addAIChatMessage(intelligenceLog.answer);
}

const prompt = await followUpPromptTemplate.format({
content: runFollowUpDto.content,
chat_history: (await chatPromptMemory.loadMemoryVariables({})).chat_history,
});

const stream = await this.chatModel.pipe(new StringOutputParser()).stream(prompt, {
tags: [
"follow-up",
`user_id: ${userId}`,
`document_id: ${runFollowUpDto.documentId}`,
`memory_key: ${runFollowUpDto.memoryKey}`,
],
});

let result = "";
for await (const chunk of stream) {
result += chunk;
handleChunk(chunk);
}

return this.chatModel.pipe(parser).stream(prompt, {
tags: [feature, userId],
await this.prismaService.intelligenceLog.create({
data: {
userId,
documentId: runFollowUpDto.documentId,
memoryKey: runFollowUpDto.memoryKey,
question: runFollowUpDto.content,
answer: result,
},
});
}
}
10 changes: 10 additions & 0 deletions backend/src/intelligence/prompt/followup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts";

export const followUpPromptTemplate = ChatPromptTemplate.fromMessages([
[
"system",
"I would like you to function like an AI intelligence for a Markdown editor similar to Notion. I will provide you with a conversation log between the user and the AI intelligence, and you just need to respond to the user's latest question.",
],
new MessagesPlaceholder("chat_history"),
["human", "{content}\nAI: "],
]);
Loading
Loading