Skip to content

Commit

Permalink
feat(#36): 모듈화. 확장을 위한 enum 방식 도입.
Browse files Browse the repository at this point in the history
  • Loading branch information
kimhji committed Feb 6, 2025
1 parent 6771646 commit c4e5032
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 44 deletions.
107 changes: 65 additions & 42 deletions server/src/ai/ai.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,89 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { delay } from 'rxjs/operators';

export enum AIType {
Summary,
}

@Injectable()
export class AIService {
private API_KEY: String;
private CLOVASTUDIO_REQUEST_ID: String;
private URL: URL;
private headers;
private prompt;
static limitLength = 120;
private summaryConfig: AINeed;
static reReqCount = 5;
static summaryMaxLength = 120;

constructor(private readonly configService: ConfigService) {
this.API_KEY = this.configService.get<string>('API_KEY');
this.CLOVASTUDIO_REQUEST_ID = this.configService.get<string>(
'CLOVASTUDIO_REQUEST_ID',
);
this.headers = {
Authorization: `Bearer ${this.API_KEY}`,
'X-NCP-CLOVASTUDIO-REQUEST-ID': `${this.CLOVASTUDIO_REQUEST_ID}`,
this.initSummary();
}

initSummary() {
this.summaryConfig = {
API_KEY: this.configService.get<string>('API_KEY'),
CLOVASTUDIO_REQUEST_ID: this.configService.get<string>(
'CLOVASTUDIO_REQUEST_ID_SUMMARY',
),
URL: this.configService.get<URL>('CLOVASTUDIO_URL_SUMMARY'),
LIMITLENGTH: AIService.summaryMaxLength,
PROMPT: {
role: 'system',
content: `- 당신은 반드시 ${AIService.summaryMaxLength} 글자 미만의 요약을 제공하는 텍스트 요약 어시스턴트입니다.
- 주어진 XML 형태의 텍스트를 분석하고 핵심 주제를 추출합니다.
- 이 글에 대한 요약은 해당 글을 홍보하고자 하는 목적으로 사용되며, 내부 내용에 대한 상세 사항은 응답에 포함되면 안됩니다.
- 답변 형태 : ~~~한 주제에 대해 다루고 있는 포스트입니다.`,
},
};
}

getConfigByType(type: AIType) {
if (type == AIType.Summary) return this.summaryConfig;
else return null;
}

getHeader(type: AIType) {
const AIConfig = this.getConfigByType(type);
if (!AIConfig) return null;
return {
Authorization: `Bearer ${AIConfig.API_KEY}`,
'X-NCP-CLOVASTUDIO-REQUEST-ID': `${AIConfig.CLOVASTUDIO_REQUEST_ID}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
};
this.URL = this.configService.get<URL>('CLOVASTUDIO_URL');
this.prompt = {
role: 'system',
content: `- 당신은 반드시 ${AIService.limitLength} 글자 미만의 요약을 제공하는 텍스트 요약 어시스턴트입니다.
- 주어진 XML 형태의 텍스트를 분석하고 핵심 주제를 추출합니다.
- 이 글에 대한 요약은 해당 글을 홍보하고자 하는 목적으로 사용되며, 내부 내용에 대한 상세 사항은 응답에 포함되면 안됩니다.
- 답변 형태 : ~~~한 주제에 대해 다루고 있는 포스트입니다.`,
}

getBody(type: AIType, feedData: String) {
const AIConfig = this.getConfigByType(type);
if (!AIConfig) return null;
return {
messages: [
AIConfig.PROMPT,
{
role: 'assistant',
content: feedData,
},
],
topP: 0.6,
topK: 0,
maxTokens: 35,
temperature: 0.1,
repeatPenalty: 2.0,
stopBefore: [],
includeAiFilters: true,
};
}

async summaryFeed(feedData: String) {
async postAIReq(type: AIType, feedData: String) {
try {
const body = {
messages: [
this.prompt,
{
role: 'assistant',
content: feedData,
},
],
topP: 0.6,
topK: 0,
maxTokens: 35,
temperature: 0.1,
repeatPenalty: 2.0,
stopBefore: [],
includeAiFilters: true,
};
const AIConfig = this.getConfigByType(type);
const body = this.getBody(type, feedData);
let count = 0;
let resLength = -1;
let result = '';
while (
(resLength <= 0 || resLength > AIService.limitLength) &&
(resLength <= 0 || resLength > AIConfig.LIMITLENGTH) &&
count < AIService.reReqCount
) {
const response = await fetch(this.URL, {
const response = await fetch(AIConfig.URL, {
method: 'POST',
headers: this.headers,
headers: this.getHeader(AIType.Summary),
body: JSON.stringify(body),
});
if (response.status === 429) {
Expand All @@ -79,7 +103,7 @@ export class AIService {
resLength = result.length;
count++;
}
if (resLength > AIService.limitLength || resLength <= 0) {
if (resLength > AIConfig.LIMITLENGTH || resLength <= 0) {
result = '요약 데이터가 유효하지 않습니다.';
}
//console.log('응답 데이터:', result);
Expand All @@ -97,7 +121,6 @@ export class AIService {

while (true) {
const { done, value } = await reader.read();

if (done) break;

const chunk = decoder.decode(value, { stream: true });
Expand Down
12 changes: 12 additions & 0 deletions server/src/ai/ai.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type Prompt = {
role: String;
content: String;
};

type AINeed = {
API_KEY: String;
CLOVASTUDIO_REQUEST_ID: String;
PROMPT: Prompt;
URL: URL;
LIMITLENGTH?: number;
};
4 changes: 2 additions & 2 deletions server/src/rss/feed-crawler.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FeedRepository } from '../feed/feed.repository';
import { RssParserService } from './rss-parser.service';
import { RssAccept } from './rss.entity';
import { Feed } from '../feed/feed.entity';
import { AIService } from '../ai/ai.service';
import { AIService, AIType } from '../ai/ai.service';

@Injectable()
export class FeedCrawlerService {
Expand Down Expand Up @@ -43,7 +43,7 @@ export class FeedCrawlerService {

return await Promise.all(
objFromXml.rss.channel.item.map(async (feed) => {
this.feedAI.summaryFeed(feed.description);
this.feedAI.postAIReq(AIType.Summary, feed.description);
const date = new Date(feed.pubDate);
const formattedDate = date.toISOString().slice(0, 19).replace('T', ' ');
const thumbnail = await this.rssParser.getThumbnailUrl(feed.link);
Expand Down

0 comments on commit c4e5032

Please sign in to comment.