diff --git a/backend/src/intelligence/dto/run-feature.dto.ts b/backend/src/intelligence/dto/run-feature.dto.ts new file mode 100644 index 00000000..d8bd7828 --- /dev/null +++ b/backend/src/intelligence/dto/run-feature.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from "@nestjs/swagger"; + +export class RunFeatureDto { + @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 0c645652..bbbcd2f1 100644 --- a/backend/src/intelligence/intelligence.controller.ts +++ b/backend/src/intelligence/intelligence.controller.ts @@ -1,4 +1,44 @@ -import { Controller } from "@nestjs/common"; +import { Body, Controller, Param, Post, Req, Res } from "@nestjs/common"; +import { ApiBearerAuth, 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"; +@ApiTags("Intelligences") +@ApiBearerAuth() @Controller("intelligence") -export class IntelligenceController {} +export class IntelligenceController { + constructor(private intelligenceService: IntelligenceService) {} + + @Post(":feature") + @ApiOperation({ + summary: "Run the Yorkie Intelligence Feature", + description: "Run the selected yorkie intelligence", + }) + @ApiParam({ + name: "feature", + description: "Feature of intelligence to run", + enum: Feature, + }) + @ApiOkResponse({ type: String }) + async runFeature( + @Req() req: AuthroizedRequest, + @Res() res: Response, + @Param("feature") feature: Feature, + @Body() runFeatureDto: RunFeatureDto + ): Promise { + const stream = await this.intelligenceService.runFeature( + req.user.id, + feature, + runFeatureDto.content + ); + + for await (const chunk of stream) { + res.write(chunk); + } + + res.end(); + } +} diff --git a/backend/src/intelligence/intelligence.service.ts b/backend/src/intelligence/intelligence.service.ts index c9142d88..8a564b94 100644 --- a/backend/src/intelligence/intelligence.service.ts +++ b/backend/src/intelligence/intelligence.service.ts @@ -1,7 +1,33 @@ -import { Inject, Injectable } from "@nestjs/common"; +import { Inject, Injectable, NotFoundException } from "@nestjs/common"; 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"; @Injectable() export class IntelligenceService { - constructor(@Inject("BaseChatModel") private chatModel: BaseChatModel) {} + constructor(@Inject("ChatModel") private chatModel: BaseChatModel) {} + + private selectPromptTemplate(feature: Feature) { + const promptTemplates = { + [Feature.GITHUB_ISSUE]: githubIssuePromptTemplate, + }; + const selectedPrompt = promptTemplates[feature]; + + if (!selectedPrompt) throw new NotFoundException(); + + return selectedPrompt; + } + + async runFeature(userId: string, feature: Feature, content: string) { + const promptTemplate = this.selectPromptTemplate(feature); + const prompt = await promptTemplate.format({ + content, + }); + const parser = new StringOutputParser(); + + return this.chatModel.pipe(parser).stream(prompt, { + tags: [feature, userId], + }); + } } diff --git a/backend/src/intelligence/prompt/github-issue.ts b/backend/src/intelligence/prompt/github-issue.ts new file mode 100644 index 00000000..76f59827 --- /dev/null +++ b/backend/src/intelligence/prompt/github-issue.ts @@ -0,0 +1,82 @@ +import { ChatPromptTemplate, FewShotChatMessagePromptTemplate } from "langchain/prompts"; + +const examplePrompt = ChatPromptTemplate.fromTemplate( + "## Title\n{title}\n## Issue Type\n{issueType}\n## Content\n{content}" +); + +const examples = [ + { + title: "Error with `qemu` when launching `yorkie` project using `yorkieteam/yorkie` image on Apple M1", + issueType: "bug 🐞", + content: ` + +**What happened**: +When launch \`yorkie\` project using \`yorkieteam/yorkie\` image. got error about \`qemu\` like below. + +\`\`\`sh +yorkie | qemu: uncaught target signal 11 (Segmentation fault) - core dumped +\`\`\` + +this is known issue on \`QEMU\` and I can not find how to resolve. but I attached related urls. + +**What you expected to happen**: +\`yorkie\` work properly. + +**How to reproduce it (as minimally and precisely as possible)**: +referenced [this issue](https://gitlab.com/qemu-project/qemu/-/issues/340) which gitlab, \`QEMU\`'s original repository. +**Rechardson Dx** said try to edit \`dockerfile\` and \`docker-compose\`. I do not tested. + +**Anything else we need to know?**: +I attached related urls below + + - [QEMU issue](https://gitlab.com/qemu-project/qemu/-/issues/340) + - [stackoverflow answer about qemu](https://stackoverflow.com/questions/68862313/qemu-uncaught-target-signal-11-segmentation-fault-core-dumped-in-docker-con) + +**Environment**: +- Operating system: OSX Big Sur 11.5.2 apple m1 +- Browser and version: chrome, safari +- Yorkie version (use \`yorkie version\`): 0.1.6 +- Yorkie JS SDK version: 0.1.6 + `, + }, + { + title: "Introduce broadcast API for event sharing", + issueType: "enhancement 🌟", + content: ` +**What would you like to be added**: +Yorkie presently relies on the Publish-Subscribe model for sharing document and presence events (refer to: [pub-sub.md](https://github.com/yorkie-team/yorkie/blob/main/design/pub-sub.md)). +However, this lacks the capability to extend its scope to encompass additional event types, notably notifications for end users concerning new document updates or comments. +To address this limitation, the introduction of a "broadcast" feature is recommended. +This feature would enable users to define and share a wider range of general events beyond the existing document and presence events. +It's also related to #442, which extracts \`Room\` from \`Document\` and moves \`Presence\` from \`Client\` to \`Room\`. +**Why is this needed**: +Provide a more comprehensive event-sharing mechanism that satisfies various use cases.`, + }, + { + title: "Enhance Tree.Edit to manage Merge and Split scenarios", + issueType: "common issue 🐾", + content: ` + +**Description**: + +Move \`Client.Watch\` inside \`Client.Attach\` and hide it from the external interface. + +Go SDK is just used in integration tests of servers without other SDK installations. So it was OK to expose \`Client.Watch\` to the external interface. But by adding more and more features to the SDK, it is quite difficult to keep simple tests. + +Let's move Client.Watch inside Client.Attach and hide it from the external interface to maintain consistency with other SDKs and simplify testing. + +**Why**: + +Keep the product simple`, + }, +]; + +export const githubIssuePromptTemplate = new FewShotChatMessagePromptTemplate({ + prefix: `I want you to act as a GitHub Issue writer. I will provide brief information about the GitHub issue I want to create, and you should write the GitHub issue based on the examples I provide. + The types of issues you can write are bug 🐞 or enhancement 🌟. Please ensure that you follow the template used in each type of issue example provided. Please write your responses in English.`, + suffix: "Brief information about the GitHub issue: {content}", + examplePrompt, + examples, + inputVariables: ["content"], +}); diff --git a/backend/src/intelligence/types/feature.type.ts b/backend/src/intelligence/types/feature.type.ts new file mode 100644 index 00000000..f04a7678 --- /dev/null +++ b/backend/src/intelligence/types/feature.type.ts @@ -0,0 +1,4 @@ +export enum Feature { + GITHUB_ISSUE = "github-issue", + GITHUB_PR = "github-pr", +} diff --git a/backend/src/langchain/langchain.module.ts b/backend/src/langchain/langchain.module.ts index a88441c1..a0e18936 100644 --- a/backend/src/langchain/langchain.module.ts +++ b/backend/src/langchain/langchain.module.ts @@ -3,7 +3,7 @@ import { ChatOpenAI } from "@langchain/openai"; import { BaseChatModel } from "@langchain/core/language_models/chat_models"; const chatModelFactory = { - provide: "BaseChatModel", + provide: "ChatModel", useFactory: () => new ChatOpenAI({}) as BaseChatModel, };