-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] AI요금 사용량에 따른 모델 변경 로직 작성 #80
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
Merged
+829
−229
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
0ae5cde
chore: mcp client 로직을 코드 기반으로 수정
coli-geonwoo 510fedc
chore: chatclient config 추가
coli-geonwoo f7bdda1
chore: schema 생성을 위한 required 추가
coli-geonwoo 25891a2
chore: 프롬프트 수정
coli-geonwoo 6f2d036
chore: 프롬프트 수정
coli-geonwoo 612632c
chore: AI PR 분석로직 수정
coli-geonwoo b455cc6
feat: Aiconfig로 옵션 설정
coli-geonwoo 4ec1670
feat: PromptBuilder 구현
coli-geonwoo 1804e2d
feat: PR 분석 토큰을 반환하도록 수정
coli-geonwoo e29dd6f
feat: event 발행 시 AiModel를 포함하도록 수정
coli-geonwoo fa9bd51
feat: PrAnalysisService에서 분석하도록 수정
coli-geonwoo 0f3bec9
feat: model 로드벨런싱 기준 설정
coli-geonwoo 984a8fa
feat: 환율계산 유틸 구현
coli-geonwoo a118668
feat: 비용 계산 로직 구현
coli-geonwoo 784aa28
feat: 비용 관련 도메인 로직 작성
coli-geonwoo c889cf4
feat: 요금에 따른 model 선택 정책 도입
coli-geonwoo eb88a59
feat: 요금에 기준점 변경
coli-geonwoo e8d5d70
feat: 요금 엔티티 구현
coli-geonwoo f5e495b
refactor: 요금 업데이트 로직 변경
coli-geonwoo 2112956
test: 모델 selection 테스트
coli-geonwoo 61bd043
refactor: zoneId 추가
coli-geonwoo 702594e
refactor: unique 제약조건 제거
coli-geonwoo 934adb8
fix: 중복 저장 문제 로직 변경
coli-geonwoo b7265e0
chore: 프롬프트 변경
coli-geonwoo 82415d0
test: ai비용 업데이트 테스트 작성
coli-geonwoo cc9c21f
test: ai호출 테스트 disabled
coli-geonwoo fa7db45
fix: 중복 웹훅 등록 문제 해결
coli-geonwoo 10cd9b0
fix: 동시성 이슈에 대응하기 위해 optionsBuilder를 인스턴스 객체로 선언
coli-geonwoo 5a96598
refactor: 매핑로직 분리
coli-geonwoo 0a2afed
refactor: 복합 unique 제약조건 추가
coli-geonwoo afdbdc9
refactor: year과 month를 모두 파라미터로 받도록 수정
coli-geonwoo 46841b7
refactor: 부동소수점 에러에 대응하기 위한 타입변경
coli-geonwoo 9f8c1f2
Merge branch 'develop' into refactor/#72
coli-geonwoo 73afb58
refactor: 부동소수점 에러에 대응하기 위한 타입변경
coli-geonwoo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
gss-api-app/src/test/java/com/devoops/BaseRepositoryTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.devoops; | ||
|
|
||
| import com.devoops.domain.repository.analysis.AiChargeRepository; | ||
| import com.devoops.generator.AiChargeGenerator; | ||
| import com.devoops.jpa.repository.analysis.AiChargeJpaRepository; | ||
| import com.devoops.jpa.repository.analysis.AiChargeRepositoryImpl; | ||
| import com.devoops.jpa.repository.github.repo.GithubRepoJpaRepository; | ||
| import com.devoops.jpa.repository.github.pr.PullRequestJpaRepository; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||
| import org.springframework.context.annotation.Import; | ||
|
|
||
| @DataJpaTest | ||
| @Import({ | ||
| AiChargeRepository.class, | ||
| AiChargeRepositoryImpl.class, | ||
| AiChargeJpaRepository.class, | ||
| AiChargeGenerator.class, | ||
| }) | ||
| public abstract class BaseRepositoryTest { | ||
|
|
||
|
|
||
| @Autowired | ||
| protected AiChargeGenerator aiChargeGenerator; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
gss-api-app/src/test/java/com/devoops/repository/analysis/AiChargeRepositoryTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.devoops.repository.analysis; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import com.devoops.BaseServiceTest; | ||
| import com.devoops.domain.entity.analysis.AiCharge; | ||
| import com.devoops.domain.repository.analysis.AiChargeRepository; | ||
| import java.time.LocalDate; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
|
|
||
| class AiChargeRepositoryTest extends BaseServiceTest { | ||
|
|
||
| @Autowired | ||
| private AiChargeRepository chargeRepository; | ||
|
|
||
| @Nested | ||
| class GetByMonth { | ||
|
|
||
| @Test | ||
| void 월에_해당하는_요금을_가져온다() { | ||
| double charge = 1500.0; | ||
| LocalDate localDate = LocalDate.now(); | ||
| aiChargeGenerator.generate(localDate.getYear(), localDate.getMonthValue(), charge); | ||
|
|
||
| AiCharge actual = chargeRepository.getByYearAndMonth(localDate.getYear(), localDate.getMonthValue()); | ||
|
|
||
| assertThat(actual.getCharge().doubleValue()).isEqualTo(charge); | ||
| } | ||
|
|
||
| @Test | ||
| void 가져올_요금이_없다면_초기화한다() { | ||
| LocalDate localDate = LocalDate.now(); | ||
|
|
||
| AiCharge actual = chargeRepository.getByYearAndMonth(localDate.getYear(), localDate.getMonthValue()); | ||
|
|
||
| assertThat(actual.getCharge().doubleValue()).isEqualTo(0.0); | ||
| } | ||
| } | ||
|
|
||
| @Nested | ||
| class Update { | ||
|
|
||
| @Test | ||
| void 요금을_업데이트_할_수_있다() { | ||
| double charge = 1500.0; | ||
| LocalDate localDate = LocalDate.now(); | ||
| aiChargeGenerator.generate(localDate.getYear(), localDate.getMonthValue(), charge); | ||
|
|
||
| chargeRepository.addCharge(localDate.getYear(), localDate.getMonthValue(), charge); | ||
|
|
||
| AiCharge updatedcharge = chargeRepository.getByYearAndMonth(localDate.getYear(), localDate.getMonthValue()); | ||
| assertThat(updatedcharge.getCharge().doubleValue()).isEqualTo(charge * 2); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 3 additions & 1 deletion
4
gss-client/gss-mcp-client/src/main/java/com/devoops/client/PrAnalysisClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| package com.devoops.client; | ||
|
|
||
| import com.devoops.dto.request.AnalyzePrRequest; | ||
| import com.devoops.dto.response.AnalyzePrResponse; | ||
|
|
||
| public interface PrAnalysisClient { | ||
|
|
||
| AnalyzePrResponse analyze(String title, String description, String diff); | ||
| AnalyzePrResponse analyze(AnalyzePrRequest request); | ||
|
|
||
| } |
76 changes: 52 additions & 24 deletions
76
gss-client/gss-mcp-client/src/main/java/com/devoops/client/PrAnalysisClientImpl.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,44 +1,72 @@ | ||
| package com.devoops.client; | ||
|
|
||
| import com.devoops.dto.request.AnalyzePrRequest; | ||
| import com.devoops.dto.response.AnalyzePrResponse; | ||
| import com.fasterxml.jackson.core.type.TypeReference; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.devoops.dto.response.PrAnalysis; | ||
| import com.devoops.serdes.PrAnalysisMapper; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.ai.chat.model.ChatModel; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.ai.chat.client.ChatClient; | ||
| import org.springframework.ai.chat.metadata.Usage; | ||
| import org.springframework.ai.chat.model.ChatResponse; | ||
| import org.springframework.ai.chat.prompt.ChatOptions; | ||
| import org.springframework.ai.converter.BeanOutputConverter; | ||
| import org.springframework.ai.openai.OpenAiChatOptions; | ||
| import org.springframework.ai.openai.api.ResponseFormat; | ||
| import org.springframework.ai.openai.api.ResponseFormat.Type; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| @Slf4j | ||
| public class PrAnalysisClientImpl implements PrAnalysisClient { | ||
|
|
||
| @Value("${dev-oops.github-pr-analysis.prompt}") | ||
| private String promptTemplate; | ||
|
|
||
| private final ChatModel chatModel; | ||
| private final ObjectMapper objectMapper; | ||
| private final ChatClient chatClient; | ||
| private final PromptBuilder promptBuilder; | ||
| private final PrAnalysisMapper prAnalysisMapper; | ||
|
|
||
| @Override | ||
| public AnalyzePrResponse analyze(String title, String description, String diff) { | ||
| String prompt = buildPrompt(title, description, diff); | ||
| // log.info("prompt = {}", prompt); | ||
| String content = chatModel.call(prompt); | ||
| return parseResponse(content); | ||
| public AnalyzePrResponse analyze(AnalyzePrRequest request) { | ||
| //option 설정 | ||
| OpenAiChatOptions openAiChatOptions = openAiChatBuilder() | ||
| .model(request.model()) | ||
| .build(); | ||
|
|
||
| ChatResponse chatresponse = callChatResponse( | ||
| request.title(), | ||
| request.description(), | ||
| request.codeDifference(), | ||
| openAiChatOptions | ||
| ); | ||
|
|
||
| Usage usage = chatresponse.getMetadata().getUsage(); | ||
| String analysisResult = chatresponse.getResult().getOutput().getText(); | ||
| PrAnalysis prAnalysis = prAnalysisMapper.mapToPrAnalysis(analysisResult); | ||
| return new AnalyzePrResponse(usage, prAnalysis); | ||
| } | ||
|
|
||
| private OpenAiChatOptions.Builder openAiChatBuilder() { | ||
| return OpenAiChatOptions.builder() | ||
| .responseFormat(new ResponseFormat(Type.JSON_SCHEMA, outputJsonSchema())) | ||
| .reasoningEffort("medium") | ||
| .temperature(1.0); | ||
|
|
||
| } | ||
|
|
||
| private String buildPrompt(String title, String description, String diff) { | ||
| return String.format(promptTemplate, title, description, diff); | ||
| private String outputJsonSchema() { | ||
| BeanOutputConverter<PrAnalysis> outputConverter = new BeanOutputConverter<>(PrAnalysis.class); | ||
| return outputConverter.getJsonSchema(); | ||
| } | ||
|
|
||
| private AnalyzePrResponse parseResponse(String content) { | ||
| try { | ||
| return objectMapper.readValue(content, new TypeReference<>() { | ||
| }); | ||
| } catch (Exception e) { | ||
| log.error("AI 응답 파싱 실패. 응답 내용: {}", content, e); | ||
| throw new IllegalArgumentException("AI 응답 파싱 중 오류 발생", e); | ||
| } | ||
| private ChatResponse callChatResponse(String title, String description, String codeDifference, | ||
| ChatOptions options) { | ||
| String userPrompt = promptBuilder.buildUserPrompt(title, description, codeDifference); | ||
| String systemPrompt = promptBuilder.buildSystemPrompt(); | ||
| return chatClient.prompt() | ||
| .options(options) | ||
| .system(systemPrompt) | ||
| .user(userPrompt) | ||
| .call() | ||
| .chatResponse(); | ||
| } | ||
| } |
31 changes: 31 additions & 0 deletions
31
gss-client/gss-mcp-client/src/main/java/com/devoops/client/PromptBuilder.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.devoops.client; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.Base64; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class PromptBuilder { | ||
|
|
||
| @Value("${dev-oops.github-pr-analysis.prompt}") | ||
| private String promptTemplate; | ||
|
|
||
| @Value("${dev-oops.github-pr-analysis.system}") | ||
| private String systemPrompt; | ||
|
|
||
| public String buildUserPrompt(String title, String description, String diff) { | ||
| return promptTemplate | ||
| .replace("{title}", title) | ||
| .replace("{description}", description) | ||
| .replace("{diff}", encodeDiff(diff)); | ||
| } | ||
|
|
||
| public String buildSystemPrompt() { | ||
| return systemPrompt; | ||
| } | ||
|
|
||
| private String encodeDiff(String diff) { | ||
| return Base64.getEncoder().encodeToString(diff.getBytes(StandardCharsets.UTF_8)); | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
gss-client/gss-mcp-client/src/main/java/com/devoops/config/AiConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.devoops.config; | ||
|
|
||
| import org.springframework.ai.chat.client.ChatClient; | ||
| import org.springframework.ai.openai.OpenAiChatModel; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| public class AiConfig { | ||
|
|
||
| @Bean | ||
| public ChatClient chatClient(OpenAiChatModel chatModel) { | ||
| return ChatClient.create(chatModel); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
implementationvsapi재검토 — gss-common 타입이 공개 API에 노출된다면api가 맞습니다java-library를 사용하는 경우, gss-common의 타입(GssException,ErrorCode, DTO 등)이 이 모듈의 public 메서드 시그니처/예외에 등장하면api project(":gss-common")로 노출해야 합니다. 그렇지 않으면 소비자 모듈에서 클래스패스 누락 문제가 날 수 있습니다.다음 스크립트로 공개 API 노출 여부를 확인해 주세요(리포지토리 루트에서 실행).
🏁 Script executed:
Length of output: 441
gss-mcp-client의 public 메서드 시그니처에
AnalyzePrResponse가 사용되는지 확인하기 위해 파일 상단을 출력합니다.🏁 Script executed:
Length of output: 323
gss-mcp-client: gss-common 의존성
implementation→api로 변경 필요PrAnalysisClient 인터페이스가
AnalyzePrRequest/AnalyzePrResponse(com.devoops.dto.*) 타입을 public API 시그니처에 노출하고 있으므로, gss-common 모듈을api로 선언해야 소비자 모듈에서 클래스패스 누락 없이 정상 컴파일/실행이 가능합니다.– 파일:
gss-client/gss-mcp-client/build.gradle– 변경 위치: 6행
제안하는 수정:
📝 Committable suggestion
🤖 Prompt for AI Agents