Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.ezcode.codetest.infrastructure.event.config;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -76,7 +78,7 @@ public void initConsumerGroup() {
public StreamMessageListenerContainer<String, MapRecord<String, String, String>> streamMessageListenerContainer(
RedisConnectionFactory factory,
RedisJudgeQueueConsumer consumer
) {
) throws UnknownHostException {
StreamMessageListenerContainer
.StreamMessageListenerContainerOptions<String, MapRecord<String, String, String>> options =
StreamMessageListenerContainer
Expand All @@ -90,7 +92,7 @@ public StreamMessageListenerContainer<String, MapRecord<String, String, String>>
StreamMessageListenerContainer.create(factory, options);

container.receive(
Consumer.from("judge-group", "consumer-1"),
Consumer.from("judge-group", "consumer-" + InetAddress.getLocalHost().getHostName()),
StreamOffset.create("judge-queue", ReadOffset.lastConsumed()),
consumer
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public void onMessage(MapRecord<String, String, String> message) {

try {
log.info("[컨슈머 수신] {}", msg.sessionKey());
Thread.sleep(6000);
submissionService.processSubmissionAsync(msg);

log.info("[컨슈머 ACK] messageId={}", message.getId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
@Component
class OpenAIMessageBuilder {

private static final String MODEL_NAME = "o4-mini";
private static final String MODEL_NAME = "gpt-4o";

private static final String PREFIX = """
당신은 코딩 테스트 사이트의 코드 리뷰어입니다.
아래 **정확히** 이 형식을 지켜 응답하세요:
'시간 복잡도:', '코드 총평:' 같은 제목은 **제목**과 같이 볼드체로 변환합니다.
'코드 총평' 같은 제목은 **제목** 과 같이 볼드체로 변환합니다.
**[중요] 오답일 경우 시간 복잡도 항목은 절대 금지입니다.**
만약 형식을 지켜 응답하지 않으면 시스템은 응답을 폐기합니다.
""".stripIndent();

private static final String SUFFIX = """
Expand Down Expand Up @@ -63,30 +65,38 @@ private String buildSystemPrompt(boolean isCorrect) {
String body;
if (isCorrect) {
body = """
- 시간 복잡도: Big-O 표기법으로만 답하세요. **단, N과 M을 같다고 가정하고 n으로 표기하세요.**
**시간 복잡도**: Big-O 표기법으로만 답하세요.
**단, N과 M을 같다고 가정하고 n으로 표기하세요.**
코드에 포함된 중첩 루프(depth)에 따라 O(N^k) 형태로 정확히 표기해주세요.
**for 루프뿐만 아니라 while 루프도 모두 중첩(depth)에 포함**하여, 코드에 실제로 있는 루프 개수만큼 exponent를 세십시오.
예) for-for-for ⇒ O(n³), for-for-while ⇒ O(n³), for-for-for-for-while ⇒ O(n⁵)
\n
- 코드 총평:
각 문장은 한 탭(\t) 들여쓰기 + '- '로 시작.
문장 끝에만 마침표를 붙이세요.
**코드 총평**:
각 문장은 '- '로 시작.
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
\n
- 조금 더 개선할 수 있는 방안:
각 문장은 한 탭(\t) 들여쓰기 + '- '로 시작.
문장 끝에만 마침표를 붙이세요.
**조금 더 개선할 수 있는 방안**:
각 문장은 '- '로 시작.
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
""".stripIndent();
} else {
body = """
- 코드 총평:
각 문장은 한 탭(\t) 들여쓰기 + '- '로 시작.
문장 끝에만 마침표를 붙이세요.
**코드 총평**:
각 문장은 '- '로 시작.
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.
\n
- 공부하면 좋은 키워드:
**공부하면 좋은 키워드**:
1. 첫 번째 키워드
2. 두 번째 키워드
3. 세 번째 키워드
… 필요한 만큼 번호를 늘려주세요.
\n
**조금 더 개선할 수 있는 방안**:
각 문장은 '- '로 시작.
문장 끝에만 마침표를 붙이고 줄바꿈 하세요.

절대 시간 복잡도라는 단어도 쓰지 마세요.
Big-O 표기법도 절대 포함하지 마세요.
""".stripIndent();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ protected boolean isValidFormat(String content, boolean isCorrect) {
if (content == null)
return false;

if (isCorrect) {
return content.contains("시간 복잡도:") &&
content.contains("코드 총평:") &&
content.contains("조금 더 개선할 수 있는 방안:");
if (!isCorrect) {

boolean containsRequired = content.contains("**코드 총평**:") &&
content.contains("**공부하면 좋은 키워드**:")&&
content.contains("**조금 더 개선할 수 있는 방안**:");

boolean containsForbidden = content.contains("**시간 복잡도**:") ||
content.toLowerCase().contains("**시간복잡도:**");
Comment on lines +17 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

금지 콘텐츠 검증 로직 개선 필요

toLowerCase() 처리 후 볼드 마크다운 패턴 **시간복잡도:**을 검사하는 것은 실제 응답에서 나타날 수 있는 다양한 형태를 놓칠 수 있습니다.

다음과 같이 개선하는 것을 권장합니다:

-            boolean containsForbidden = content.contains("**시간 복잡도**:") ||
-                content.toLowerCase().contains("**시간복잡도:**");
+            boolean containsForbidden = content.contains("**시간 복잡도**:") ||
+                content.toLowerCase().contains("시간복잡도") ||
+                content.toLowerCase().contains("시간 복잡도");
🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/infrastructure/openai/OpenAIResponseValidator.java
around lines 17 to 18, the current check for forbidden content uses
toLowerCase() combined with a fixed bold markdown pattern, which may miss
variations in the actual response. To fix this, replace the exact string match
with a case-insensitive regular expression that matches the phrase "시간 복잡도" or
"시간복잡도" with or without bold markdown, allowing for flexible spacing and
formatting variations.


return containsRequired && !containsForbidden;
}

return content.contains("코드 총평:") &&
content.contains("공부하면 좋은 키워드:");
return content.contains("**시간 복잡도**:") &&
content.contains("**코드 총평**:") &&
content.contains("**조금 더 개선할 수 있는 방안**:");
}
}
62 changes: 57 additions & 5 deletions src/main/resources/templates/test-submit.html
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,24 @@
background-color: #00b472;
}

button:disabled {
background-color: #555;
cursor: not-allowed;
}

.review-box {
margin-top: 16px;
background-color: #2a2d3d;
padding: 20px;
border-radius: 10px;
color: #f0f2f5;
}

.review-box strong,
.review-box b {
color: #00d084;
}

.result-box {
background-color: #1e212f;
padding: 16px;
Expand Down Expand Up @@ -288,13 +306,15 @@ <h2>코드 제출</h2>
<div id="editor" class="cm-theme" style="height: 300px;"></div>
</div>
<button onclick="submitCode()">코드 제출 (WebSocket)</button>
<button id="reviewBtn" onclick="requestReview()" disabled>리뷰 요청 (AI)</button>
</div>
<div class="section">
<h3>채점 결과</h3>
<div class="git-sidebar">
<div class="git-status-dot" id="gitDot"></div>
</div>
<div id="judgeResult" class="result-box">여기에 테스트 결과가 표시됩니다</div>
<div id="reviewBox" class="review-box" style="display: none"></div>
</div>
</div>
</div>
Expand All @@ -305,7 +325,11 @@ <h3>채점 결과</h3>
<script defer>
let stompClient;
let connected = false;
let finalIsCorrect = null;

const gitDot = document.getElementById('gitDot');
const reviewBtn = document.getElementById('reviewBtn');
const reviewBox = document.getElementById('reviewBox');
const probTitleEl = document.getElementById('prob-title');
const probDescEl = document.getElementById('prob-desc');
const probTimeEl = document.getElementById('prob-time');
Expand Down Expand Up @@ -354,6 +378,9 @@ <h3>채점 결과</h3>
const payload = {languageId, sourceCode};
const judgeEl = document.getElementById('judgeResult');
judgeEl.innerHTML = '';
reviewBox.style.display = 'none';
reviewBtn.disabled = true;
finalIsCorrect = null;

try {
const res = await fetch(`/api/problems/${pid}/submit-ws`, {
Expand Down Expand Up @@ -487,14 +514,39 @@ <h3>채점 결과</h3>
sum.className = 'final-summary';
sum.innerHTML = `<strong>최종:</strong> ${res.passedCount}/${res.totalCount} 통과 — ${res.message}`;
document.getElementById('judgeResult').appendChild(sum);
finalIsCorrect = res.passedCount === res.totalCount;
reviewBtn.disabled = false;
}

fetch('/html/header.html')
.then(res => res.text())
.then(data => {
document.getElementById('header-placeholder').innerHTML = data;
});
async function requestReview() {
if (!window.problemId || finalIsCorrect === null) return;
const languageId = +document.getElementById('language').value;
const sourceCode = window.getEditorCode();
try {
const res = await fetch(`/api/problems/${window.problemId}/review`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...tokenHeader()
},
body: JSON.stringify({ languageId, sourceCode, isCorrect: finalIsCorrect })
});
const json = await res.json();
const content = json.result?.reviewContent || json.reviewContent;
reviewBox.style.display = 'block';
const markdownSafe = content
.replace(/\n[\t ]*-/g, '\n-') // 리스트용 하이픈 앞 공백 제거
.replace(/\n/g, ' \n'); // 줄바꿈을 마크다운 줄바꿈으로 변환
reviewBox.innerHTML = marked.parse(markdownSafe || '리뷰 결과가 없습니다.');
console.log(JSON.stringify(content));
} catch (e) {
console.error('리뷰 요청 실패', e);
reviewBox.style.display = 'block';
reviewBox.textContent = '리뷰 요청 중 오류가 발생했습니다';
}
}
</script>

<script src="/js/headerUtils.js"></script>
<script src="/js/footerUtils.js"></script>
<script>
Expand Down