package antigravity import ( "bytes" "context" "encoding/json" "fmt" "os/exec" "strings" ) // StudyElement represents the extracted study content from a question. type StudyElement struct { QuestionID int `json:"questionId"` Source string `json:"source"` Category string `json:"category"` Content string `json:"content"` // Markdown content containing formulas, charts, etc. } // 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"` } // CallAgy runs the agy CLI with the given prompt. func CallAgy(ctx context.Context, prompt string) (string, error) { cmd := exec.CommandContext(ctx, "agy", "--dangerously-skip-permissions", "--print", prompt) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { return "", fmt.Errorf("agy error: %v, stderr: %s", err, stderr.String()) } return strings.TrimSpace(stdout.String()), nil } // ExtractStudyElement requests the AI to generate a comprehensive study sheet/note for a specific quiz question. func ExtractStudyElement(ctx context.Context, questionText, choice1, choice2, choice3, choice4, correctAnswer, explanation string) (string, error) { prompt := fmt.Sprintf("아래 기출문제의 문제, 보기, 정답, 해설을 철저히 분석하여 핵심 개념을 담은 \"학습요소(개념 정리)\"를 작성해 주세요.\n"+ "이 학습요소는 사용자가 이 문제를 완벽하게 이해하고 관련된 이론적 배경까지 학습할 수 있도록 돕는 학습 노트입니다.\n\n"+ "[기출문제]\n"+ "문제: %s\n"+ "보기 1: %s\n"+ "보기 2: %s\n"+ "보기 3: %s\n"+ "보기 4: %s\n"+ "정답: %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. 다른 잡다한 설명(예: \"네, 작성해 드리겠습니다\" 등)은 모두 제외하고, 오직 마크다운으로 구성된 학습요소 내용만 반환해 주세요.", questionText, choice1, choice2, choice3, choice4, correctAnswer, explanation) return CallAgy(ctx, prompt) } // 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 }