112 lines
5.1 KiB
Go
112 lines
5.1 KiB
Go
|
|
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
|
||
|
|
}
|