작년에 Upstage에서 주최한 2025 AI Workflow Hackathon에 참여해 대상을 수상하고, 컨소시엄 세미나를 포함해 업스테이지에서 주최하는 행사에 많은 관심을 가지고 있었는데 마침 이번에 Upstage에서 AI Ambassador 2기를 뽑는다는 소식을 듣고 지원을 결심하게 되었다.
1차 선발 과제는 Upstage의 Solar, Document Parse, Information Extract API 등을 활용한 AI Agent 서비스로 기획부터 개발까지 그 과정을 담아내보고자 한다.
먼저 Upstage Document AI, Solar LLM, Information Extract에 대해 모르는 분들도 있을거 같아 간략히 설명하자면
- Upstage Document AI :PDF·이미지 문서를 실제 텍스트 데이터로 변환하는 API
- Solar LLM : 업스테이지가 선보이는 차세대 프런티어 언어 모델로 광범위한 다국어 처리, 고도화된 추론 성능 가능
- Information Extract : 문서를 업로드하는 즉시 AI가 자동으로 정보를 분석하고 추출 (CRM, ERP, 클라우드, 자동화 도구와 연결)
구체적인 사용방법에 대해 궁금한 분들은 아래 사이트에서 무료로 강의도 찾아보는걸 추천한다. 이전에 업스테이지 해커톤 때 수강했던 강의인데 꽤나 도움이 많이 되었던 기억이 있다.
Home
본 강의는 자연어 처리(NLP) 분야에 관심이 있는 학습자를 대상으로 한 심화 과정입니다. 참가자들은 자연어 처리 문제를 이해하고, 분석하여 구현 가능한 적절한 형태로 정의하는 방법에 대해
edu.upstage.ai

본격적으로 기획한 AI Agent 서비스에 대해 소개를 하자면,
바로 비어있는 개념을 찾아 연결하는 AI 수학 학습도구이다.
이전에 수학 강사로 학원에서 일해본 경험에서 시작하여 도출한 문제인 만큼 이전부터 한 번쯤 실현해보고 싶다는 생각이 들었는데 이번 기회를 삼아 직접 개발까지 해보게 되었다.
1. 문제 정의
통계에 따르면 실제로 학생들이 공부를 시작했을 때 가장 어렵게 느끼는 과목은 바로 '수학'이라고 한다.
그리고 그 이유는 모두가 잘 알듯이 수학은 '연속성'이 강한 과목이기 때문인데, 앞에서 배웠던 내용이 이후 단원에서 반복해서 나오고 대표적으로 수능은 무려 12년 교육과정의 모든 수학개념을 평가하는 것만 보더라도 알 수 있다.
결국 문제의 핵심은 다음과 같다.
- 선수지식 불충분하면 → 새로운 개념 학습자체가 실패
- 그러나 외부개입 없이는 그 개념이 왜 필요한 지 조차 알 수 없다
실제로 학원에서 가르쳤던 학생 중 한 명은 중3때 이차함수배우는 딱 그 시기에 잠시 놀았을 뿐인데, 고등학교 내내 문제에 이차함수가 연계되어 나오면 잘 안 읽히고 어렵게 느껴진다며 힘들어 하는 경우를 봤었다.

결국 학생들이 지금 배우는 과정을 아무리 열심히 공부하고, 완벽히 이해했다 하더라도 앞서 배운 내용을 놓쳤거나 까먹었다면 문제를 풀기가 어렵다는 것이다.

2. 솔루션
만약 프로그램이 학생의 공백개념을 파악을 할 수 있고 그래서 학생 한 명 한 명에게 가장 필요한 학습들을 그때그때 채워줄 수 있다면, 본질적으로 학생들의 수학 성적을 올릴 수 있을 거라는 생각으로 이어지게 되었다.
강사의 입장으로 학생뿐만 아니라 선생님을 위한 학습관리 서비스로도 확장 가능성이 있을거라 생각하여 적어봤는데, 우선적으로 학생들을 위한 기능 먼저 고도화하고자 한다.

3. 핵심 기능 및 기술
주요 워크플로우는 이제 학생이 문제와 풀이과정을 업로드하면, 그 동안 학습해온 기록을 바탕으로 공백개념을 탐지한 후 공백개념을 학습할 수 있는 단계별 학습 로드맵과 콘텐츠를 제공하는 것이다.

1. 대시보드
서비스의 첫 화면으로 학생의 개념별 진행 상태와 전체 학습 흐름을 확인할 수 있다.

2. 문제 업로드 (Upstage Document Parse API)
학습하기 버튼을 누르면 수학 문제와 정답, 풀이과정을 업로드하는 화면이 나오게 된다.
이 단계의 핵심은 사람이 찍은 문제 이미지를 AI가 이해할 수 있는 텍스트로 바꾸는 것이다.

AI 모델(Solar LLM)은 이미지 자체를 직접 분석하지 않고 텍스트 형태의 입력을 가장 잘 이해하기 때문에
문제 이미지 -> 텍스트로 변환하는 역할을 하기 위해 Document Parse API를 사용했다.
쉽게 말해
- 학생이 문제 사진을 업로드하면
- Document Parse가 그 이미지를 OCR + 문서 구조 분석을 통해
- “사람이 읽는 문장 형태의 텍스트” 로 변환한다.
전체 흐름은 문제 업로드 → Document Parse(텍스트 추출) → Solar(분석/추천)으로
이 단계에서 얻은 problemText가 이후 개념 추출(Information Extract)과 학습 피드백 생성(Solar)의 입력으로 사용된다.
async function documentParse(apiKey, file) {
// Upstage Document Parse API
// 업로드한 PDF/이미지 문서를 사람이 읽을 수 있는 텍스트로 변환해주는 API
const url = "https://api.upstage.ai/v1/document-ai/document-parse";
// Document Parse는 JSON이 아니라 FormData(파일 업로드 형식)를 사용
const form = new FormData();
// 문제 이미지/PDF 파일을 document 필드로 전달
// file.buffer: multer로 받은 실제 파일 데이터
form.append("document", new Blob([file.buffer]), file.originalname);
// 결과를 텍스트 형태로 받고 싶어서 output_format을 text로 지정
form.append("output_format", "text");
// API 호출 (Bearer 인증 방식)
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
},
body: form,
});
// Document Parse의 결과는 보통 JSON 형태로 오며,
// 인식된 문서 내용이 text 또는 content 필드에 들어 있음
const text = await resp.text();
const data = JSON.parse(text);
// 최종적으로 "문서에서 추출된 텍스트"만 반환
return data.text || data.content;
}
3. 정보 추출 및 문제 분석(Information Extract + Solar LLM)
문제 이미지를 Document Parse로 텍스트로 바꿨다면, 이제부터는 “문제를 이해하고, 학습 관점에서 분석 결과를 만들어주는 단계”이다.
이 단계는 크게 두 번의 AI 호출로 나뉘게 된다.

(1) Information Extract: 문제를 ‘구조화된 정보’로 정리하기
문제 텍스트를 그대로 LLM에게 던지면, 결과가 매번 조금씩 달라질 수 있기 때문에 정해진 형태(JSON)로 뽑아둬야 한다.
문제 텍스트 → 문제 유형/개념/필요 스킬을 JSON으로 추출
JSON 구조는 다음과 같이 항목화해보았다.
- curriculum_unit : 단원(예: 지수함수와 로그함수)
- problem_type : 문제 유형(예: 일차방정식, 그래프 해석)
- core_concepts : 핵심 개념
- related_concepts : 연계 개념
- required_skills : 필요한 풀이 능력
- common_mistakes : 흔한 실수 포인트
async function informationExtract(apiKey, solarModel, problemText) {
// Solar Chat Completions 엔드포인트(LLM 호출)
// 여기서는 LLM을 "정보 추출기(Information Extract)"처럼 사용함
const url = "https://api.upstage.ai/v1/solar/chat/completions";
// 문제 텍스트에서 뽑아낼 항목을 JSON 스키마 형태로 명시
// LLM이 자유롭게 서술하지 않고, 구조화된 결과(JSON)만 반환하도록 유도
const prompt = `아래 수학 문항 텍스트를 분석해 JSON만 출력해라.
JSON:
{
"grade_band": "string",
"curriculum_unit": "string",
"problem_type": "string",
"core_concepts": ["string"],
"related_concepts": ["string"],
"required_skills": ["string"],
"common_mistakes": ["string"]
}`;
// model: 사용할 Solar 모델명
// messages: system(역할 고정) + user(실제 문항 텍스트 입력)
// temperature=0.0: 추출 결과를 최대한 일관되게 만들기 위한 설정
const body = {
model: solarModel,
messages: [
{
role: "system",
content: "너는 수학 문항을 분석해 구조화 JSON으로 추출한다. JSON만 출력한다.",
},
{ role: "user", content: `${prompt}\n\n[문항]\n${problemText}` },
],
temperature: 0.0,
};
// Bearer 인증으로 API Key 전달 후 요청 전송
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
// Solar 응답(JSON) 안의 choices[0].message.content에 LLM 결과가 들어 있음
// 여기서는 그 content가 "JSON 문자열"이라고 가정하고 다시 JSON.parse 해서 객체로 반환
const text = await resp.text();
const data = JSON.parse(text);
const content = data?.choices?.[0]?.message?.content ?? "{}";
return JSON.parse(content);
}
(2) Solar LLM: 학생 답안을 바탕으로 ‘학습 피드백’을 생성하기
Information Extract가 문제를 구조화했다면, 이제 Solar LLM이 이 데이터를 바탕으로 피드백을 작성하게 된다.
즉, Solar는 “문제를 그냥 읽고 답변”하는 게 아니라, 정답/오답 + 개념 정보 + 풀이 단서까지 함께 보고 분석하는 것이다.
최종 분석 결과를 구조화한 뒤 다음과 같이 프롬프트 요구사항을 작성해보았다. 생각보다 Solar AI가 원하는대로 결과를 잘 분석해줘서 정확도 면에서 만족도가 꽤 높았다.
async function solarAnalyze(apiKey, solarModel, payload) {
// Solar(Chat Completions) 엔드포인트: 분석/추론/피드백 생성에 사용
const url = "https://api.upstage.ai/v1/solar/chat/completions";
// 분석에 필요한 입력: 문제 텍스트 + (선택) 풀이 텍스트 + 구조화 정보 + 정답/학생답
const { problemText, solutionText, extracted, correctAnswer, studentAnswer } = payload;
// 정답/오답 판정은 서버에서 먼저 계산해서 프롬프트에 함께 전달(모델 판단 흔들림 방지)
const isCorrect = normalize(studentAnswer) === normalize(correctAnswer);
// 모델 출력이 길게 흘러가지 않도록 "반드시 JSON만 출력" + 고정 스키마를 강제
// 결과를 화면(요약/연계개념/로드맵)에 바로 꽂기 쉽게 만들기 위한 설계
const prompt = `
너는 고등학교 수학 학습 코치다. 반드시 유효한 JSON만 출력해라.
[문제 텍스트]
${problemText}
[풀이과정 텍스트(있으면)]
${solutionText || ""}
[추출 JSON]
${JSON.stringify(extracted, null, 2)}
[정답] ${correctAnswer}
[학생 답] ${studentAnswer}
[채점 결과] ${isCorrect ? "정답" : "오답"}
출력 JSON 스키마(키 이름 그대로):
{
"one_line_summary": string,
"problem_brief": string,
"what_it_asks": string,
"what_you_did_well": string,
"likely_gap": string,
"linked_concepts": [string, string, string],
"roadmap_20min": [string, string, string, string]
}
`;
// model: 사용할 Solar 모델
// messages: user 프롬프트로 분석 결과 생성 요청
const body = {
model: solarModel,
messages: [{ role: "user", content: prompt }],
temperature: 0.2,
};
// Bearer 인증으로 API Key 전달 후 요청 전송
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
// Solar 응답(JSON) 안의 choices[0].message.content에 "JSON 문자열"이 들어오도록 설계
// 그 content를 다시 JSON.parse 해서 프론트에서 바로 쓸 수 있는 객체로 반환
const text = await resp.text();
const data = JSON.parse(text);
const content = data?.choices?.[0]?.message?.content ?? "{}";
return JSON.parse(content);
}
3. 연관 문제 풀이 (Upstage Solar)
부족한 개념을 바탕으로 AI가 생성한 연관 문제를 풀이하는 화면으로 개념 이해 → 적용 → 반복 학습을 이어준다.
분석 결과에서 추천된 개념을 클릭하면, Solar가 즉시 연습문제를 생성하고, 사용자가 풀어본 결과를 학습 기록에 반영해 개념별 진도 상태를 관리하는 기능이다.

(1) 연관 문제 출제(Upstage Solar)
부족한 수학 개념을 전달받아 Solar는 다음과 같은 결과를 JSON 형식으로 반환하게끔 설계했다.
- 개념 요약(concept_summary)
- 체크포인트 3개(checkpoints)
- 풀이 팁 2개(tips)
- 연습문제 2개(problems)
- 단답형 1개 + 선다형 1개
- 정답 포함
app.post("/api/related", async (req, res) => {
try {
// 1️⃣ Upstage API 키와 사용할 Solar 모델 준비
const apiKey = process.env.UPSTAGE_API_KEY;
const solarModel = process.env.SOLAR_MODEL || "solar-pro3-260126";
// 2️⃣ 프론트에서 전달한 연계 개념(concept)
const { concept } = req.body || {};
// 3️⃣ 필수 값 체크
if (!apiKey)
return res.status(500).json({ ok: false, error: "UPSTAGE_API_KEY missing" });
if (!concept)
return res.status(400).json({ ok: false, error: "concept required" });
// 4️⃣ Solar LLM Chat API 엔드포인트
const url = "https://api.upstage.ai/v1/solar/chat/completions";
// 5️⃣ Solar에게 줄 프롬프트
// - 반드시 JSON만 출력
// - 고1 수준 연관 문제 + 요약 + 체크포인트 생성
const prompt = `
반드시 유효한 JSON만 출력해라. 다른 글/마크다운/번호/별표/백틱 금지.
스키마:
{
"concept": "string",
"concept_summary": "string",
"checkpoints": ["string","string","string"],
"tips": ["string","string"],
"problems": [
{"id":"p1","type":"단답형","question":"string","answer":"string"},
{"id":"p2","type":"선다형","question":"string","choices":["string","string","string","string"],"answer":"string"}
]
}
규칙:
- 고1 수준, 기초~중간 난이도
- 문제는 반드시 2개 (단답형 1 + 선다형 1)
- 모든 텍스트는 한국어
- 영어 사용 금지
개념: ${concept}
`;
// 6️⃣ Solar API 요청 바디
const body = {
model: solarModel,
messages: [{ role: "user", content: prompt }],
temperature: 0.4, // 약간의 다양성 허용
};
// 7️⃣ Solar LLM 호출
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const text = await resp.text();
// 8️⃣ Solar 응답에서 실제 메시지(JSON 문자열)만 추출
const outer = JSON.parse(text);
const content = outer?.choices?.[0]?.message?.content || "{}";
// 9️⃣ JSON 파싱
const json = JSON.parse(content);
// 🔟 프론트에서 쓰기 좋게 최소한의 정규화
const normalized = {
concept: String(json.concept || concept),
concept_summary: String(json.concept_summary || ""),
checkpoints: Array.isArray(json.checkpoints) ? json.checkpoints.slice(0, 3) : [],
tips: Array.isArray(json.tips) ? json.tips.slice(0, 2) : [],
problems: Array.isArray(json.problems) ? json.problems : [],
};
// 1️⃣1️⃣ 프론트로 연관 문제 데이터 전달
res.json({ ok: true, ...normalized });
} catch (e) {
res.status(500).json({ ok: false, error: e.message });
}
});
4. 학습 기록
지금까지 학습한 개념과 문제 풀이 기록을 누적 관리하는 화면으로 개념별 진도 상태와 학습 이력을 시간 순으로 확인할 수 있다.

(1) 새로운 개념 자동 분류: classifyConcept (Solar 호출)
새로운 개념이 처음 등장하면 서버는 그 개념을 그냥 저장하지 않고,
Solar에게 “이 개념은 어떤 단원에 속하는지, 현행/연계/예정 중 무엇인지”를 물어본다.
현실적으로 모든 현행 교육과정 데이터를 수기로 넣고 매칭시키는게 문제였는데, 이 부분을 Solar 덕분에 쉽게 해결할 수 있었다.
(2) 학습 로드맵 생성: /api/roadmap (Solar 호출)
학습 로드맵은 현재 개념의 진행현황을 Solar가 보고
- 단원별로 묶고
- 다음에 할 개념을 우선순위로 정리해
- “완료/진행중/예정” 상태까지 계산한 로드맵을 만들어준다.
app.post("/api/roadmap", async (req, res) => {
// 1) Upstage Solar 모델(LLM) 준비
const apiKey = process.env.UPSTAGE_API_KEY;
const solarModel = process.env.SOLAR_MODEL || "solar-pro3-260126";
// 2) 서버에 저장된 학습 상태(data.json)에서 개념 목록을 읽어옴
// (각 개념의 단원(unit) / 유형(kind) / 진행률(progress)을 Solar에게 전달)
const db = await readDB();
const concepts = Object.values(db.concepts || {}).map((c) => ({
name: c.name,
unit: c.unit,
kind: c.kind,
progress: c.progress || 0,
}));
// 3) Solar Chat Completions 엔드포인트 호출
const url = "https://api.upstage.ai/v1/solar/chat/completions";
// 4) Solar에게 "로드맵 JSON"을 만들어 달라고 요청
// - concepts를 입력으로 주고
// - 단원별로 묶고(unit)
// - 다음에 할 것부터(children 우선순위)
// - 단원 상태(status: 완료/진행중/예정)를 progress 기반으로 결정하게 함
const prompt = `
반드시 유효한 JSON만 출력.
{
"roadmap": [
{"unit":"string","status":"완료|진행중|예정","open":boolean,"children":["string","string","string"]}
]
}
입력 concepts:
${JSON.stringify(concepts, null, 2)}
규칙:
- 고1 현행 기준 단원 중심으로 묶기
- children은 우선순위 순(다음에 할 것 먼저)
- status는 children의 progress를 종합해 결정
`;
const body = {
model: solarModel,
messages: [{ role: "user", content: prompt }],
temperature: 0.2,
};
// 5) Solar 호출 → 응답(JSON) 안의 message.content를 다시 JSON으로 파싱
const resp = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const outer = JSON.parse(await resp.text());
const content = outer?.choices?.[0]?.message?.content || "{}";
const json = JSON.parse(content);
// 6) 프론트(History)에서 바로 렌더링할 수 있도록 roadmap 배열만 반환
res.json({ ok: true, roadmap: json.roadmap || [] });
});
4. 기대 효과
이 서비스는 문제를 하나 더 맞히는 데서 끝나지 않고, 문제 속에 숨어 있는 개념을 이해하는 것을 학습의 출발점으로 삼는다.
보통 수학 공부는 “이 문제를 어떻게 풀까?”에 집중되지만, 이 서비스는 “이 문제에 어떤 개념이 쓰였을까?”를 먼저 생각하게 만드는 것이다. 그래서 학생은 한 문제를 풀면서도, 그 문제에 포함된 핵심 개념과 연관된 개념들을 함께 살펴보게 된다.
이 과정을 거치면 비슷한 문제를 외워서 푸는 대신, 다른 형태로 나온 문제에서도 같은 개념을 스스로 꺼내 쓸 수 있는 힘이 자연스럽게 길러지면서 수학의 본질에 가까운 학습 경험을 제공한다는 점에서 실제로 학생들에게 도움을 줄 수 있는 서비스라고 생각한다.
5. 회고
이번 프로젝트를 돌아보면, 모든 시작은 Upstage 해커톤에 참여했을 때였다. 그때는 AI 기술 자체가 익숙하지 않았고, 용어 하나하나가 낯설어서 “내가 이걸 정말 이해할 수 있을까?”라는 막막함과 걱정이 더 컸던 기억이 난다.
그러나 해커톤을 계기로 Solar를 시작으로 하나씩 기술을 공부해 나가면서, AI가 막연한 대상이 아니라 이해하고 활용할 수 있는 기술이라는 감각을 얻게 된거 같다. 아직 완벽하다고 말할 수는 없지만, 적어도 이제는 새로운 기술을 마주했을 때 겁내기보다는 조금이라도 시간을 들이면 따라갈 수 있는 영역이라는 자신감이 생기게 되었다.
최근에는 Upstage에서 주최하는 컨소시엄 세미나에도 참여하며 현장에서 AI 기술이 어떻게 활용되고 있는지 직접 들을 수 있었고, AI Ambassador 라는 귀중한 기회를 잡고자 이렇게 직접 공부하고 개발까지 이어나간 시간 자체로도 배우는 점이 너무 많았다.
아직은 지원 과정일 뿐이지만 그럼에도 꼭 선발되어서 AI Ambassador로서 다른 사람들과 함께 배우면서 성장하는 경험을 만들어 나가고 싶다.