This commit is contained in:
root
2026-07-05 08:45:54 +00:00
parent c3596c12f3
commit 073d1dd383
8 changed files with 655 additions and 158 deletions
+26 -54
View File
@@ -3,7 +3,6 @@ package antigravity
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"
@@ -19,15 +18,16 @@ type StudyElement struct {
// GeneratedQuestion matches the structure to be saved in QuizQuestions DB.
type GeneratedQuestion struct {
QuestionText string `json:"questionText"`
Choice1 string `json:"choice1"`
Choice2 string `json:"choice2"`
Choice3 string `json:"choice3"`
Choice4 string `json:"choice4"`
CorrectAnswer int `json:"correctAnswer"`
Explanation string `json:"explanation"`
Difficulty string `json:"difficulty"`
Subject string `json:"subject"`
QuestionText string `json:"questionText"`
Choice1 string `json:"choice1"`
Choice2 string `json:"choice2"`
Choice3 string `json:"choice3"`
Choice4 string `json:"choice4"`
CorrectAnswer int `json:"correctAnswer"`
CorrectAnswerStr string `json:"correctAnswerStr"`
Explanation string `json:"explanation"`
Difficulty string `json:"difficulty"`
Subject string `json:"subject"`
}
// CallAgy runs the agy CLI with the given prompt.
@@ -58,54 +58,26 @@ func ExtractStudyElement(ctx context.Context, questionText, choice1, choice2, ch
"정답: %s\n"+
"해설: %s\n\n"+
"[작성 지침]\n"+
"1. 단순 정답 해설을 넘어, 문제에 포함된 핵심 개념(예: 특정 암호화 알고리즘, 시스템 보안 기술, 네트워크 프로토콜 등)의 정의, 특징, 작동 방식, 장단점을 일목요연하게 정리해 주세요.\n"+
"2. 가독성을 위해 마크다운 포맷을 사용해 주세요. (예: 표, 리스트)\n"+
"3. 수식이나 암호학적 기호가 필요한 경우 KaTeX 문법(예: $E_k(M) = C$)을 적극적으로 사용해 주세요.\n"+
"4. 아키텍처나 흐름을 시각화할 수 있도록 Mermaid 다이어그램(예: ```mermaid ... ```) 또는 간단한 아스키 차트(ASCII Chart/Plot)를 본문에 반드시 하나 이상 포함해 주세요.\n"+
"5. 다른 잡다한 설명(예: \"네, 작성해 드리겠습니다\" 등)은 모두 제외하고, 오직 마크다운으로 구성된 학습요소 내용만 반환해 주세요.",
"1. **매우 중요**: 문서의 가장 첫 번째 줄은 반드시 핵심 학습개념/키워드를 설명하는 제목으로 시작해 주세요. (예: `# AES 암호화 알고리즘` 또는 `# OSI 7계층과 캡슐화`)\n"+
" - 첫 줄 외에 다른 서두(예: \"네, 아래는 개념 정리입니다\" 등)는 일절 포함하지 마십시오.\n"+
"2. 단순 정답 해설을 넘어, 문제에 포함된 핵심 개념의 정의, 특징, 작동 방식, 장단점을 일목요연하게 정리해 주세요. (표, 리스트 적극 활용)\n"+
"3. **KaTeX 문법 규칙**: 수식 작성 시 파싱 오류를 철저히 방지하세요. 괄호 매칭을 완벽히 지키고, 수식 내부에 한글을 포함해야 하는 경우 반드시 `\\text{한글}` 형식을 사용하세요. 인라인 수식은 `$수식$`, 디스플레이 수식은 `$$수식$$`로만 작성하세요.\n"+
"4. **Mermaid 문법 규칙**: Mermaid 버전 10.9.6 문법을 엄격히 지켜 `syntax error in text mermaid` 에러를 예방하세요.\n"+
" - `subgraph` 이름(타이틀)에는 공백이나 한글, 괄호가 들어가므로 **반드시** 쌍따옴표로 감싸서 선언하세요. (예: `subgraph \"정상 스택 (Normal)\"`)\n"+
" - 화살표 연결선의 텍스트 라벨 `|설명|` 안에는 괄호 `()`, 슬래시 `/`, 백슬래시 `\\` 등의 특수문자를 절대 쓰지 마세요. (예: `-->|SMTP 프로토콜 포트 25|` 로 표현)\n"+
" - 노드 내부에 한글, 공백, 기호, 괄호가 포함되는 경우 **반드시** 쌍따옴표로 감싸서 선언하세요 (예: `A[\"AES 암호화\"] --> B[\"키 생성\"]`).\n"+
" - 연결 화살표 기호는 오직 `-->` 또는 `-.->` 만 사용해야 하며, `->` 나 `- >` 처럼 깨진 형식을 쓰지 마세요.\n"+
" - 서브그래프(subgraph)를 열었다면 반드시 `end`로 닫아 마감하세요.\n"+
" - 백슬래시(`\\`)는 이스케이프 오류 방지를 위해 두 번(`\\\\`) 겹쳐 적으세요.\n"+
"5. 다른 불필요한 설명은 제외하고, 오직 첫 줄 개념명(# ...)으로 시작하는 마크다운 본문만 반환해 주세요.",
questionText, choice1, choice2, choice3, choice4, correctAnswer, explanation)
return CallAgy(ctx, prompt)
return prompt, nil
}
// GenerateDescriptiveQuestion generates a new descriptive/practical exam question based on given reference questions.
func GenerateDescriptiveQuestion(ctx context.Context, referenceExams []string) (*GeneratedQuestion, error) {
refs := strings.Join(referenceExams, "\n---\n")
prompt := fmt.Sprintf("기존 정보보호기사 실기 문제들을 분석하여, 이와 유사한 출제 패턴과 난이도를 가진 \"새로운 정보보호기사 실기 문제\"를 만들어 주세요.\n\n"+
"[기존 실기 문제 예시]\n"+
"%s\n\n"+
"[출제 지침]\n"+
"1. 문제 유형은 정보보호기사 실기 시험의 단답형 또는 서술형 형태여야 합니다. (지문을 제시하고 빈칸 채우기, 또는 특정 시나리오를 주고 대처 방안 서술하기 등)\n"+
"2. 문제의 설명, 예시, 지문이 풍부하고 실무적이어야 합니다.\n"+
"3. 생성된 문제는 반드시 아래의 JSON 포맷으로만 출력되어야 합니다. 마크다운 코드 블록(```json ... ```)을 사용하지 말고, 원시 JSON 텍스트만 출력하세요.\n\n"+
"JSON 구조:\n"+
"{\n"+
" \"questionText\": \"문제 지문 및 지시 사항 (예: 다음 지문을 읽고 빈칸 A, B에 들어갈 단어를 기술하시오...)\",\n"+
" \"explanation\": \"이 문제에 대한 모범 답안 및 상세한 해설\",\n"+
" \"difficulty\": \"실기(Go)\",\n"+
" \"subject\": \"만든 자료\"\n"+
"}\n\n"+
"오직 JSON만 출력해야 합니다. 다른 텍스트는 절대 포함하지 마십시오.", refs)
resp, err := CallAgy(ctx, prompt)
if err != nil {
return nil, err
}
// Clean JSON response (sometimes LLM might return markdown code blocks despite instructions)
cleaned := resp
if idx := strings.Index(cleaned, "{"); idx != -1 {
cleaned = cleaned[idx:]
}
if idx := strings.LastIndex(cleaned, "}"); idx != -1 {
cleaned = cleaned[:idx+1]
}
var gq GeneratedQuestion
err = json.Unmarshal([]byte(cleaned), &gq)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON from AI: %v, raw output: %s", err, resp)
}
return &gq, nil
// We will create the prompt text in quiz_service.go using callAIEngine.
// This function is kept for backwards compatibility but we will format the prompt inside quiz_service to leverage both API and agy engines.
return nil, fmt.Errorf("use the prompt constructor and callAIEngine helper instead")
}