From d3e58d8be2cd4fe70d05d544c772233206c02d01 Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 13:38:17 +0900 Subject: [PATCH 1/7] Add GitHub PR Prompt Template --- .../src/intelligence/intelligence.service.ts | 2 + backend/src/intelligence/prompt/github-pr.ts | 205 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 backend/src/intelligence/prompt/github-pr.ts diff --git a/backend/src/intelligence/intelligence.service.ts b/backend/src/intelligence/intelligence.service.ts index 8a564b94..d548fc67 100644 --- a/backend/src/intelligence/intelligence.service.ts +++ b/backend/src/intelligence/intelligence.service.ts @@ -3,6 +3,7 @@ import { BaseChatModel } from "@langchain/core/language_models/chat_models"; 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"; @Injectable() export class IntelligenceService { @@ -11,6 +12,7 @@ export class IntelligenceService { private selectPromptTemplate(feature: Feature) { const promptTemplates = { [Feature.GITHUB_ISSUE]: githubIssuePromptTemplate, + [Feature.GITHUB_PR]: githubPrPromptTemplate, }; const selectedPrompt = promptTemplates[feature]; diff --git a/backend/src/intelligence/prompt/github-pr.ts b/backend/src/intelligence/prompt/github-pr.ts new file mode 100644 index 00000000..b457fed9 --- /dev/null +++ b/backend/src/intelligence/prompt/github-pr.ts @@ -0,0 +1,205 @@ +import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "langchain/prompts"; + +const examplePrompt = ChatPromptTemplate.fromTemplate("## Title\n{title}\n## Content\n{content}"); + +const examples = [ + { + title: "Introduce MongoDB sharding rules to Project-wide and Document-wide collections", + content: ` + +**What this PR does / why we need it**: +This PR introduces MongoDB sharding rules to Project-wide and Document-wide collections. + +There are three types of collections: +1. Cluster-wide: \`users\`, \`projects\` (expected data count: less than \`10,000\`) +4. Project-wide: \`documents\`, \`clients\` (expected data count: more than \`1\` million) +7. Document-wide: \`changes\`, \`snapshots\`, \`syncedseqs\` (expected data count: more than \`100\` million) + +We determine whether a collection is required to be sharded based on the expected data count of its types. +1. Cluster-wide: not sharded +2. Project-wide, Document-wide: sharded + +Project-wide collections contain range queries using a \`project_id\` filter, and document-wide collections usually contain them using a \`doc_id\` filter. +We choose the shard key based on the query pattern of each collection: +1. Project-wide: \`project_id\` +2. Document-wide: \`doc_id\` + +This involves changes in reference keys of collections: +1. \`documents\`: \`_id\` -> \`(project_id, _id)\` +2. \`clients\`: \`_id\` -> \`(project_id, _id)\` +4. \`changes\`: \`_id\` -> \`(project_id, doc_id, server_seq)\` +5. \`snapshots\`: \`_id\` -> \`(project_id, doc_id, server_seq)\` +6. \`syncedseqs\`: \`_id\` -> \`(project_id, doc_id, client_id)\` + +Therefore, if we have an existing MongoDB instance, data migration is mandatory before using the MongoDB sharded cluster. +After connecting to the MongoDB through \`mongosh\`, declare the following function and execute it. + +\`\`\`ts +function addProjectIDFrom(id = 0, limit = 20000, dbname='yorkie-meta-prev') {{ + const previousID = ObjectId(id); + const docs = db.getSiblingDB(dbname).documents.find({{_id:{{$gte:previousID}}}}).sort({{_id: 1}}).limit(limit); + while (docs.hasNext()) {{ + const doc = docs.next(); + const docID = doc._id; + const projectID = doc.project_id; + + console.log("doc", docID.toString(), "start"); + db.getSiblingDB(dbname).changes.updateMany( + {{doc_id:{{$eq:docID}}}}, + {{$set: {{project_id: projectID}}}}); + db.getSiblingDB(dbname).snapshots.updateMany( + {{doc_id:{{$eq:docID}}}}, + {{$set: {{project_id: projectID}}}}); + db.getSiblingDB(dbname).syncedseqs.updateMany( + {{doc_id:{{$eq:docID}}}}, + {{$set: {{project_id: projectID}}}}); + console.log("doc", docID.toString(), "end"); + }} +}} + +addProjectIDFrom(0, 20000, ) +\`\`\` + + +**Which issue(s) this PR fixes**: + +Fixes #673 + +**Special notes for your reviewer**: + +**Does this PR introduce a user-facing change?**: + +\`\`\`release-note + +\`\`\` + +**Additional documentation**: + + +\`\`\`docs + +\`\`\` + +**Checklist**: +- [x] Added relevant tests or not required +- [x] Didn't break anything + `, + }, + { + title: "Change 'Documents' from plural to singular in DocEvent", + content: ` + +#### What this PR does / why we need it? + +Change "documents" to "document" in DocEvent + +#### Any background context you want to provide? + +There were previous proto changes in the js-sdk, so I have set the target branch to "concurrent-case-handling." + +#### What are the relevant tickets? + +Related https://github.com/yorkie-team/yorkie/issues/612 + +### Checklist +- [x] Added relevant tests or not required +- [x] Didn't break anything + `, + }, + { + title: "Add `presence.get()` to get presence value in `doc.update()`", + content: ` + +#### What this PR does / why we need it? + +The presence value can be obtained using the \`presence.get()\` function within the \`doc.update\` function + +\`\`\`js +client.attach(doc, {{ + initialPresence: {{ counter: 0 }}, +}}); + +// as-is +doc.update((root, p) => {{ + const counter = doc.getMyPresence().counter; + p.set({{ counter: counter + 1 }}); +}}); + +// to-be +doc.update((root, p) => {{ + const counter = p.get('counter'); + p.set({{ counter: counter + 1 }}); +}}); +\`\`\` + + +#### Any background context you want to provide? + + +#### What are the relevant tickets? + +Fixes # + +### Checklist +- [x] Added relevant tests or not required +- [x] Didn't break anything + `, + }, + { + title: "Support concurrent insertion and splitting in Tree", + content: ` + +#### What this PR does / why we need it? +This PR introduces support for concurrent insertion and splitting in the Tree by utilizing \`leftNode.parent\` as the \`parent\` node. + +#### Any background context you want to provide? +Currently, the \`parentID\` and \`leftSiblingID\` in \`CRDTTreePos\` represent positions in the Tree. In other words, they help derive the \`parentNode\` and \`leftSiblingNode\` in the Tree. + +When splitting an element node, the split node receives a new nodeID (#707). This complicates concurrent editing, particularly when a remote operation, unaware of the node's split, refers to child nodes that were previously in the original node but are now in the split node. In such cases, the local cannot locate the target node because the original node no longer contains those child nodes. + +Fortunately, the \`leftNode.parent\` represents the exact parent node in the current tree. Therefore, using this as a \`parent\` effectively addresses the above problem. + +In summary, the \`parentNodeID\` in \`CRDTTreePos\` is now solely used to determine whether the given position is the leftmost. Instead, substantial tree operations utilize \`leftNode.parent\` as the \`parent\`. + +#### What are the relevant tickets? + +Fixes # + +### Checklist +- [x] Added relevant tests or not required +- [x] Didn't break anything + `, + }, +]; + +export const githubPrPromptTemplate = new FewShotChatMessagePromptTemplate({ + prefix: `I want you to act as a GitHub PR Writer for me. I'll provide you with brief notes about GitHub PR, and you just need to write the PR using the examples I've provided. +Make sure to adhere to the template that we commonly follow in Example. +If the information is not provided by the user, please refrain from attaching document links found elsewhere. Please respond in English.`, + suffix: "Brief information about the GitHub PR: {content}", + examplePrompt, + examples, + inputVariables: ["content"], +}); From d145da1a572384dee4ff8e4f69b18fd0f1b4b1a8 Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 13:43:08 +0900 Subject: [PATCH 2/7] Add intelligence log schema --- backend/prisma/schema.prisma | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 406ec66e..c81eccf0 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -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") } @@ -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") } @@ -84,5 +86,20 @@ 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 + 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") } From edc5905c40da1efb97ea2be9c4cf58a9d7d6634f Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 13:51:20 +0900 Subject: [PATCH 3/7] Update schema.prisma --- backend/prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index c81eccf0..2ef1fa4b 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -95,6 +95,7 @@ model IntelligenceLog { 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") From 0078b801053aac8ec14d8ffa6110fecb627f3e03 Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 13:51:33 +0900 Subject: [PATCH 4/7] Change GitHub PR Prompt example --- backend/src/intelligence/prompt/github-pr.ts | 95 -------------------- 1 file changed, 95 deletions(-) diff --git a/backend/src/intelligence/prompt/github-pr.ts b/backend/src/intelligence/prompt/github-pr.ts index b457fed9..02d2ce8a 100644 --- a/backend/src/intelligence/prompt/github-pr.ts +++ b/backend/src/intelligence/prompt/github-pr.ts @@ -3,101 +3,6 @@ import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "langchain/ const examplePrompt = ChatPromptTemplate.fromTemplate("## Title\n{title}\n## Content\n{content}"); const examples = [ - { - title: "Introduce MongoDB sharding rules to Project-wide and Document-wide collections", - content: ` - -**What this PR does / why we need it**: -This PR introduces MongoDB sharding rules to Project-wide and Document-wide collections. - -There are three types of collections: -1. Cluster-wide: \`users\`, \`projects\` (expected data count: less than \`10,000\`) -4. Project-wide: \`documents\`, \`clients\` (expected data count: more than \`1\` million) -7. Document-wide: \`changes\`, \`snapshots\`, \`syncedseqs\` (expected data count: more than \`100\` million) - -We determine whether a collection is required to be sharded based on the expected data count of its types. -1. Cluster-wide: not sharded -2. Project-wide, Document-wide: sharded - -Project-wide collections contain range queries using a \`project_id\` filter, and document-wide collections usually contain them using a \`doc_id\` filter. -We choose the shard key based on the query pattern of each collection: -1. Project-wide: \`project_id\` -2. Document-wide: \`doc_id\` - -This involves changes in reference keys of collections: -1. \`documents\`: \`_id\` -> \`(project_id, _id)\` -2. \`clients\`: \`_id\` -> \`(project_id, _id)\` -4. \`changes\`: \`_id\` -> \`(project_id, doc_id, server_seq)\` -5. \`snapshots\`: \`_id\` -> \`(project_id, doc_id, server_seq)\` -6. \`syncedseqs\`: \`_id\` -> \`(project_id, doc_id, client_id)\` - -Therefore, if we have an existing MongoDB instance, data migration is mandatory before using the MongoDB sharded cluster. -After connecting to the MongoDB through \`mongosh\`, declare the following function and execute it. - -\`\`\`ts -function addProjectIDFrom(id = 0, limit = 20000, dbname='yorkie-meta-prev') {{ - const previousID = ObjectId(id); - const docs = db.getSiblingDB(dbname).documents.find({{_id:{{$gte:previousID}}}}).sort({{_id: 1}}).limit(limit); - while (docs.hasNext()) {{ - const doc = docs.next(); - const docID = doc._id; - const projectID = doc.project_id; - - console.log("doc", docID.toString(), "start"); - db.getSiblingDB(dbname).changes.updateMany( - {{doc_id:{{$eq:docID}}}}, - {{$set: {{project_id: projectID}}}}); - db.getSiblingDB(dbname).snapshots.updateMany( - {{doc_id:{{$eq:docID}}}}, - {{$set: {{project_id: projectID}}}}); - db.getSiblingDB(dbname).syncedseqs.updateMany( - {{doc_id:{{$eq:docID}}}}, - {{$set: {{project_id: projectID}}}}); - console.log("doc", docID.toString(), "end"); - }} -}} - -addProjectIDFrom(0, 20000, ) -\`\`\` - - -**Which issue(s) this PR fixes**: - -Fixes #673 - -**Special notes for your reviewer**: - -**Does this PR introduce a user-facing change?**: - -\`\`\`release-note - -\`\`\` - -**Additional documentation**: - - -\`\`\`docs - -\`\`\` - -**Checklist**: -- [x] Added relevant tests or not required -- [x] Didn't break anything - `, - }, { title: "Change 'Documents' from plural to singular in DocEvent", content: ` From 4daea198b26b6946fe6dc3d823d3348d712f6d2e Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 13:59:08 +0900 Subject: [PATCH 5/7] Add intelligenceLog in database --- .../src/intelligence/dto/run-feature.dto.ts | 3 ++ .../intelligence/intelligence.controller.ts | 10 +---- .../src/intelligence/intelligence.module.ts | 3 +- .../src/intelligence/intelligence.service.ts | 39 ++++++++++++++++--- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/backend/src/intelligence/dto/run-feature.dto.ts b/backend/src/intelligence/dto/run-feature.dto.ts index d8bd7828..4de1b280 100644 --- a/backend/src/intelligence/dto/run-feature.dto.ts +++ b/backend/src/intelligence/dto/run-feature.dto.ts @@ -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; } diff --git a/backend/src/intelligence/intelligence.controller.ts b/backend/src/intelligence/intelligence.controller.ts index bbbcd2f1..c5c92381 100644 --- a/backend/src/intelligence/intelligence.controller.ts +++ b/backend/src/intelligence/intelligence.controller.ts @@ -29,16 +29,10 @@ export class IntelligenceController { @Param("feature") feature: Feature, @Body() runFeatureDto: RunFeatureDto ): Promise { - 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(); } } diff --git a/backend/src/intelligence/intelligence.module.ts b/backend/src/intelligence/intelligence.module.ts index 471ff922..99a56fae 100644 --- a/backend/src/intelligence/intelligence.module.ts +++ b/backend/src/intelligence/intelligence.module.ts @@ -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 {} diff --git a/backend/src/intelligence/intelligence.service.ts b/backend/src/intelligence/intelligence.service.ts index d548fc67..968479db 100644 --- a/backend/src/intelligence/intelligence.service.ts +++ b/backend/src/intelligence/intelligence.service.ts @@ -4,10 +4,16 @@ 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"; @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 = { @@ -21,15 +27,36 @@ 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 stream = await this.chatModel.pipe(new StringOutputParser()).stream(prompt, { + tags: [feature, `user_id: ${userId}`, `document_id: ${runFeatureDto.documentId}`], + }); + const memoryKey = generateRandomKey(); + let result = ""; + + handleChunk(`${memoryKey}\n`); + 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: { + memoryKey, + question: prompt, + answer: result, + userId, + documentId: runFeatureDto.documentId, + }, }); } } From 514632d2399ee9dc356e3fe00e90692cda1768b2 Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 15:01:44 +0900 Subject: [PATCH 6/7] Implement follow up request --- .../src/intelligence/dto/run-feature.dto.ts | 2 +- .../src/intelligence/dto/run-followup.dto.ts | 12 ++++ .../intelligence/intelligence.controller.ts | 31 ++++++++- .../src/intelligence/intelligence.service.ts | 66 ++++++++++++++++++- backend/src/intelligence/prompt/followup.ts | 10 +++ 5 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 backend/src/intelligence/dto/run-followup.dto.ts create mode 100644 backend/src/intelligence/prompt/followup.ts diff --git a/backend/src/intelligence/dto/run-feature.dto.ts b/backend/src/intelligence/dto/run-feature.dto.ts index 4de1b280..f28ed4a2 100644 --- a/backend/src/intelligence/dto/run-feature.dto.ts +++ b/backend/src/intelligence/dto/run-feature.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from "@nestjs/swagger"; export class RunFeatureDto { - @ApiProperty({ type: String, description: "ID of Document" }) + @ApiProperty({ type: String, description: "ID of document" }) documentId: string; @ApiProperty({ type: String, description: "Content to run feature" }) diff --git a/backend/src/intelligence/dto/run-followup.dto.ts b/backend/src/intelligence/dto/run-followup.dto.ts new file mode 100644 index 00000000..d9309555 --- /dev/null +++ b/backend/src/intelligence/dto/run-followup.dto.ts @@ -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; +} diff --git a/backend/src/intelligence/intelligence.controller.ts b/backend/src/intelligence/intelligence.controller.ts index c5c92381..88d239fb 100644 --- a/backend/src/intelligence/intelligence.controller.ts +++ b/backend/src/intelligence/intelligence.controller.ts @@ -1,10 +1,18 @@ 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() @@ -12,6 +20,27 @@ import { Response } from "express"; 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 { + await this.intelligenceService.runFollowUp(req.user.id, runFollowUpDto, (chunk) => + res.write(chunk) + ); + + res.end(); + } + @Post(":feature") @ApiOperation({ summary: "Run the Yorkie Intelligence Feature", diff --git a/backend/src/intelligence/intelligence.service.ts b/backend/src/intelligence/intelligence.service.ts index 968479db..44101862 100644 --- a/backend/src/intelligence/intelligence.service.ts +++ b/backend/src/intelligence/intelligence.service.ts @@ -1,5 +1,6 @@ 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"; @@ -7,6 +8,8 @@ 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 { @@ -37,12 +40,17 @@ export class IntelligenceService { const prompt = await promptTemplate.format({ content: runFeatureDto.content, }); + const memoryKey = generateRandomKey(); const stream = await this.chatModel.pipe(new StringOutputParser()).stream(prompt, { - tags: [feature, `user_id: ${userId}`, `document_id: ${runFeatureDto.documentId}`], + tags: [ + feature, + `user_id: ${userId}`, + `document_id: ${runFeatureDto.documentId}`, + `memory_key: ${memoryKey}`, + ], }); - const memoryKey = generateRandomKey(); - let result = ""; + let result = ""; handleChunk(`${memoryKey}\n`); for await (const chunk of stream) { result += chunk; @@ -59,4 +67,56 @@ export class IntelligenceService { }, }); } + + 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); + } + + await this.prismaService.intelligenceLog.create({ + data: { + userId, + documentId: runFollowUpDto.documentId, + memoryKey: runFollowUpDto.memoryKey, + question: runFollowUpDto.content, + answer: result, + }, + }); + } } diff --git a/backend/src/intelligence/prompt/followup.ts b/backend/src/intelligence/prompt/followup.ts new file mode 100644 index 00000000..b03cdcff --- /dev/null +++ b/backend/src/intelligence/prompt/followup.ts @@ -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: "], +]); From b8967f8b25cca6b305eeb5809dd5d5c80c643386 Mon Sep 17 00:00:00 2001 From: devleejb Date: Tue, 30 Jan 2024 15:07:44 +0900 Subject: [PATCH 7/7] Update github-pr.ts --- backend/src/intelligence/prompt/github-pr.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/intelligence/prompt/github-pr.ts b/backend/src/intelligence/prompt/github-pr.ts index 02d2ce8a..66443aed 100644 --- a/backend/src/intelligence/prompt/github-pr.ts +++ b/backend/src/intelligence/prompt/github-pr.ts @@ -102,7 +102,8 @@ Fixes # export const githubPrPromptTemplate = new FewShotChatMessagePromptTemplate({ prefix: `I want you to act as a GitHub PR Writer for me. I'll provide you with brief notes about GitHub PR, and you just need to write the PR using the examples I've provided. Make sure to adhere to the template that we commonly follow in Example. -If the information is not provided by the user, please refrain from attaching document links found elsewhere. Please respond in English.`, +If the information is not provided by the user, please refrain from attaching document links found elsewhere. Please respond in English. +Please refer to the example for guidance, but generate results based on the information provided in the Brief Information section.`, suffix: "Brief information about the GitHub PR: {content}", examplePrompt, examples,