Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
072964c
Add web S0-S6 screens, SSE hook, KaTeX renderer, login/samples
May 20, 2026
eec053b
Drop italic on hero accent text, keep primary color
May 20, 2026
d4808b1
Apply PR #7 last review fixes (P1-P3)
smilebank7 May 21, 2026
0518949
Implement agent runtime wiring
smilebank7 May 22, 2026
92b2b54
Add seed RAG data and backend contracts
smilebank7 May 22, 2026
3051be3
Wire source-grounded generation workflow
smilebank7 May 22, 2026
34bdad0
Connect web verification results to generated output
smilebank7 May 22, 2026
e2dd003
Add source problem selection to intent flow
smilebank7 May 22, 2026
8960048
Polish demo navigation and branding
smilebank7 May 22, 2026
f29fc75
Merge origin/main into feat/product-docs
smilebank7 May 27, 2026
92f25b8
Add critique, solve-attempt, and objective-mapping-nuance schemas
smilebank7 Jun 2, 2026
dc0106d
Add LaTeX formatter tool
smilebank7 Jun 2, 2026
c7eee52
Update objective-mapper and problem-generator prompts
smilebank7 Jun 2, 2026
21399b5
Refresh agent .env example and RAG corpus seed
smilebank7 Jun 2, 2026
ee4bf96
Wire specialist agents to schemas and prompt loader
smilebank7 Jun 2, 2026
4b162a8
Implement 6 pipeline steps with new step contracts
smilebank7 Jun 2, 2026
eccacc6
Rewire verification workflow and entry point
smilebank7 Jun 2, 2026
65f7160
Forward RunOptions through generate route and SSE adapter
smilebank7 Jun 2, 2026
6b39c5f
Refine landing book-stage layout and global styles
smilebank7 Jun 2, 2026
c307268
Update README, architecture, domain, and product specs
smilebank7 Jun 2, 2026
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
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,94 @@ pnpm build # production build (agent + web)
- Math Engine: `http://localhost:8000`
- Web: `http://localhost:3001` (landing)

### LLM 환경 설정 (필수)

**⚠ LLM 환경 없이는 생성·검증이 100% 실패합니다** — seed fallback은 RAG 원본을 그대로 후보로 반환하므로 `objective_map`의 `not_transformed` 가드에 걸립니다.

다음 3가지 중 하나를 설정하세요. 모두 `packages/agent/.env` (또는 환경변수)로 주입.

#### 옵션 1 — CLIProxyAPI (Claude/GPT/Gemini 통합 라우터, 권장)

[CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)를 로컬에서 실행하면 Claude·GPT·Gemini를 OpenAI 호환 API 하나로 라우팅 가능.

```bash
LLM_PROVIDER=cliproxy
LLM_BASE_URL=http://localhost:8317/v1
LLM_API_KEY=dummy-key
LLM_MODEL=gpt-4o # 또는 claude-3-5-sonnet, gemini-2.0-flash
```

**Trade-off**: 로컬 라우터 별도 실행 필요. 캐시·모델 비교에 유리.

#### 옵션 2 — OpenAI 직접

```bash
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o
```

**Trade-off**: 가장 빠른 setup. 비용은 사용량 기반.

#### 옵션 3 — Anthropic via OpenAI-compatible 호환 레이어

```bash
LLM_PROVIDER=anthropic-via-compatible
LLM_BASE_URL=<your-anthropic-compatible-endpoint>
LLM_API_KEY=...
LLM_MODEL=claude-3-5-sonnet-20241022
```

**Trade-off**: 별도 호환 layer 필요 (예: LiteLLM proxy). Claude의 수학 추론 품질 활용.

### 포트 충돌 회피

다른 프로세스(Mantis, 별도 Next.js 서버 등)가 3000번을 점유하면 `pnpm dev:all` 시 agent가 `EADDRINUSE`로 실패. 다음 단계로 회피:

```bash
# 1. 충돌 확인
lsof -i :3000

# 2. agent 포트 변경
PORT=3002 pnpm dev

# 3. web도 다른 포트의 agent를 보도록 변경
# packages/web/.env.local 생성:
echo "NEXT_PUBLIC_AGENT_URL=http://localhost:3002" > packages/web/.env.local

# 4. web 재기동
pnpm -F @openmath/web dev
```

`NEXT_PUBLIC_AGENT_URL`이 미설정이면 web은 `http://localhost:3000`을 호출하므로 포트 변경 시 반드시 `.env.local` 갱신.

### 데모 실행 절차 (캡스톤)

기본 데모 시나리오: **중3 / 이차방정식 (9수02-09) / 구조 동형 / 3문항** (OM-104 결정).

```bash
# 1. (한 번) deps + LLM 환경 + corpus 확인
pnpm install
cp packages/agent/.env.example packages/agent/.env
# .env 의 LLM_* 채우기

# 2. (매 시연) 세 서비스 기동
pnpm dev:all

# 3. (브라우저) http://localhost:3001 → S0 → 학년(중3) → 단원(이차방정식)
# → 평가 차원(인수분해 또는 근의 공식 사용 + 판별식) → 생성

# 4. (검증) S4 6단계 ✓ → S5 결과 3문항 → S6 PDF
```

또는 직접 SSE:

```bash
curl -sN -X POST http://localhost:3000/api/generate \
-H "Content-Type: application/json" \
-d '{"grade":3,"topic":"9수02-09","mode":"structural","dims":["인수분해 또는 근의 공식 사용","판별식으로 해의 종류 해석"]}'
```

```
packages/
├── agent/ # Node 22 — verification pipeline + HTTP/SSE
Expand Down
2 changes: 1 addition & 1 deletion docs/product/SCREENS.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@
|---|---|---|
| Sub nav | `{component.sub-nav}` | "← 검증 진행 / 결과" · 우측 progress 카운터 없음 |
| Page title | `{typography.heading-xl}` | "3개 문항이 준비되었습니다" — 동적 카운트 |
| Filter row | `{component.filter-chip}` × M | "전체" / "구조동형" / "개념동형" / "주의" / "실패" |
| Filter row | `{component.filter-chip}` × 4 | "전체" / "구조동형" / "개념동형" / "주의" — 실패 카드는 색·아이콘으로 충분히 구분되므로 별도 필터 없음 (OM-101 결정) |
| Result grid | `{component.result-card}` × N (3-up → 1024px↓ 2-up → 600px↓ 1-up) | |
| Card head | `{typography.body-strong}` 문항 번호 + `{component.badge-pass}` / `{component.badge-concept}` / `{component.badge-warn}` + 아이콘 클러스터 (`{component.button-icon-circular}` ★ 즐겨찾기 / ↻ 재생성 / ✎ 수정 / × 폐기) | padding `16px 20px`, border-bottom `{colors.hairline-soft}` |
| Card body | `{component.formula-stage}` | LaTeX 문제 본문 (KaTeX), padding `28px 24px`, min-height 120px |
Expand Down
14 changes: 14 additions & 0 deletions docs/specs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@
- **대안**: (a) Vite + React (SSR 없음 → 첫 페인트 늦음, SEO 약함, AI SDK 통합 약함), (b) SvelteKit (팀 친숙도 낮음, 학습 비용), (c) `apps/` + `packages/` 분리 (현재 평면 3 패키지 구조에서 분리 비용 > 가치), (d) Pretendard 단일 시스템 유지 (랜딩의 editorial 권위 표현 못함).
- **채택 사유**: D-4 Vercel AI SDK 와 1:1 호환 (`ai/react`, `useChat`, SSE consumption hook 기본). Server Components 로 KaTeX SSR (수식이 첫 페인트부터 보임). Tailwind v4 의 CSS-first config 가 DESIGN.md 토큰을 그대로 `@theme` 블록에 매핑 가능. 듀얼-서피스로 랜딩 (D-3 학원 강사 첫 인상 — 신뢰감) 과 앱 내부 (정확/결정론적 출제 도구) 의 voice 둘 다 충족. 기존 `docs/product/DESIGN.md` (Nike fork productivity-only) 는 본 결정으로 superseded → historical reference 로 보존.

### D-11. I-G4 정책 — 검증 거부 후보 *투명* 노출

- **결정**: `Verification.overall == "rejected"` 후보를 SSE `result` 이벤트에 **포함**한다. FE는 fail 카드 (좌측 4px `{colors.fail}` border + `inline-notice-fail`) 로 명시 + "채택" 액션 비활성. 데이터로는 노출하되 *사용 가능한 문항* 으로 보이지 않게 시각적 가드.
- **대안**: (A) 본 결정 (투명 노출), (B) 워크플로 result emit 시 rejected 필터링 (error 이벤트로 별도 알림), (C) rejected는 별도 SSE 이벤트 `rejection` 으로 분리.
- **채택 사유**: D-3 1차 사용자 (학원 강사) 에게 *왜 검증이 실패했는지* 신호가 가장 중요. (B) 는 "AI가 뭔가 만들었는데 보여주지 않음" 으로 신뢰 하락. (A) 의 시각적 가드 (fail 카드 + 채택 비활성) 가 I-G4 ("검증되지 않은 문제는 *사용자에게 제공* 되어서는 안 된다") 의 의도를 충족 — *제공* 의 의미는 "강사가 학생 시험지에 넣을 수 있는 상태" 이지 "화면 표시 자체" 가 아니다. 자세한 시각 규약은 `packages/web/DESIGN.md` `{component.result-card-failed}` 참조.
- **Closes**: OM-92.

### D-12. S0-B (OCR 입력) — v2 로 명확히 보류

- **결정**: S0-B "이 문제처럼" (OCR 이미지 입력 → LaTeX 자동 추출) 흐름은 1차 MVP 범위 외. 캡스톤 데모 후 v2 트리거. S0 카드는 현 상태 (disabled + "준비 중" 표기) 유지.
- **대안**: (A) 본 결정 (v2 보류), (B) MVP 직후 추가 (Mathpix or Tesseract+SymPy 라이브러리 선정 + LaTeX 후처리 epic 신설), (C) v3 이후 보류.
- **채택 사유**: 캡스톤 5분 데모 (OM-104: 중3 / 이차방정식 / 구조 동형 / 3문항) 흐름은 S0-A → S1 → S6 만으로 완결. OCR 도입 시 *Q-U4* (S0-B 도입 시 S1 존재 의의) 가 함께 풀려야 하고, 모델 선정 + 비용 + 인식률 평가가 별도 epic 단위. v2 트리거 시 (B) 가 정답이 될 수도 있으나, *현 시점에서 명시적 보류* 가 최선.
- **Closes**: OM-103, OM-58 §(4).

### D-10. 인증·세션 layer 는 Better Auth — v2 도입 (1차 MVP 비활성)
- **결정**: 사용자 가입·로그인·세션·계정 관리 기능 도입 시 [Better Auth](https://www.better-auth.com) 를 채택. TypeScript-first, framework-agnostic auth library. 1차 MVP (D-3 학원 강사 단일 사용자 PoC) 에서는 *비활성* — 랜딩의 "무료로 시작하기" CTA 는 placeholder, 실제 가입 흐름 없음. 캡스톤 데모 발표 후 v2 트리거.
- **대안**: (a) NextAuth/Auth.js (Next.js 14 App Router 1급, 그러나 type-safety/customization 약함, OAuth 외 흐름 어색), (b) Clerk (호스티드 SaaS — 한국 컴플라이언스·비용·vendor 락인), (c) Supabase Auth (전체 BaaS 락인 — DB·storage·realtime 함께 채택해야 효율), (d) Lucia Auth (2024 maintenance mode 진입), (e) 자체 구현 (보안 위험 + 일정 부담).
Expand Down
2 changes: 1 addition & 1 deletion docs/specs/domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ type SurfaceConstraints = {
- (I-G1) `inferred_intent.objective_code == request.intent.objective_code` — 학습 목표는 항상 동일.
- (I-G2) `mode == "structural"`이면 `inferred_intent.required_techniques`가 원본과 동일 (구조 동형 정의).
- (I-G3) `mode == "conceptual"`이면 `inferred_intent.evaluation_dimensions` 중 `must_preserve` 차원이 원본과 동일 (개념 동형 정의).
- (I-G4) 검증되지 않은 `GeneratedProblem`은 사용자에게 노출되지 않는다 (D-1 원칙).
- (I-G4) 검증되지 않은 `GeneratedProblem`은 *사용 가능한 문항* 으로 노출되지 않는다 — verdict `"rejected"` 후보는 `result` 이벤트에 포함되되 FE의 `{component.result-card-failed}` (좌측 4px `{colors.fail}` border + `inline-notice-fail` + "채택" 비활성) 로 *시각 가드* 되어 학생 시험지 등 외부로 흘러가지 못함. *데이터 노출* 은 허용 (강사 신뢰 확보), *사용 노출* 은 차단 (D-1 원칙). 자세한 정책 결정은 architecture.md D-11.

---

Expand Down
32 changes: 24 additions & 8 deletions packages/agent/.env.example
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
# OpenMath agent — environment variables.
# 모두 optional. 미설정 시 src/config/env.ts 기본값 또는 src/index.ts fallback 사용.

# --- 서버 ---
# 다른 프로세스(예: Mantis 등)가 3000 점유 시 PORT 변경 + 동시에
# packages/web/.env.local 의 NEXT_PUBLIC_AGENT_URL 도 같이 갱신해야 web↔agent 연결됨.
PORT=3000
MATH_ENGINE_URL=http://localhost:8000
CORPUS_JSONL=/Users/tw81512/dev/AI_HUB_data/rag_problem_generation_dataset/openmath_rag_records.jsonl

# LLM Provider: "openai" or "cliproxy"
LLM_PROVIDER=openai
# --- RAG ---
# 미설정 시 ./data/corpus/math-sample-unified-v1.jsonl (repo 동봉) 사용.
# 별도 corpus 경로 쓰려면 절대경로 또는 packages/agent/ 기준 상대경로로 지정.
# CORPUS_JSONL=./data/corpus/math-sample-unified-v1.jsonl

# OpenAI Direct
OPENAI_API_KEY=your-api-key-here
OPENAI_MODEL=gpt-4o
# --- LLM 환경 (필수 — 미설정 시 seed fallback 으로 생성/검증 100% 실패) ---
# LLM_PROVIDER: "openai" | "openai-compatible" | "anthropic-via-compatible" | "cliproxy"
LLM_PROVIDER=cliproxy

# CLIProxyAPI (alternative)
CLIPROXY_BASE_URL=http://localhost:8080/v1
# (옵션 1) CLIProxyAPI 로컬 라우터 — Claude/GPT/Gemini 통합 (https://github.com/router-for-me/CLIProxyAPI)
CLIPROXY_BASE_URL=http://localhost:8317/v1
CLIPROXY_API_KEY=dummy-key
CLIPROXY_MODEL=gpt-4o

# (옵션 2) OpenAI 직접 — 비용/속도 안정. LLM_PROVIDER=openai 와 함께.
# OPENAI_API_KEY=sk-...
# OPENAI_MODEL=gpt-4o

# Canonical aliases — 위 3가지 옵션 중 어느 것도 매핑되지 않으면 이 3개로 직접 지정 가능

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Canonical alias priority is described backwards. The comment says to use LLM_BASE_URL/LLM_API_KEY/LLM_MODEL "when none of the above 3 options map" (fallback), but in src/index.ts they are the highest-priority values checked first via ??. If a user follows 옵션 2 (OpenAI direct) by uncommenting OPENAI_API_KEY/OPENAI_MODEL and switching LLM_PROVIDER=openai without also disabling the canonical aliases, LLM_BASE_URL=http://localhost:8317/v1 and LLM_API_KEY=dummy-key would silently override the OpenAI settings — routing to CLIProxy with dummy key instead of OpenAI.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/.env.example, line 28:

<comment>Canonical alias priority is described backwards. The comment says to use `LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL` "when none of the above 3 options map" (fallback), but in `src/index.ts` they are the **highest-priority** values checked first via `??`. If a user follows 옵션 2 (OpenAI direct) by uncommenting `OPENAI_API_KEY`/`OPENAI_MODEL` and switching `LLM_PROVIDER=openai` without also disabling the canonical aliases, `LLM_BASE_URL=http://localhost:8317/v1` and `LLM_API_KEY=dummy-key` would silently override the OpenAI settings — routing to CLIProxy with dummy key instead of OpenAI.</comment>

<file context>
@@ -1,20 +1,31 @@
+# OPENAI_API_KEY=sk-...
+# OPENAI_MODEL=gpt-4o
+
+# Canonical aliases — 위 3가지 옵션 중 어느 것도 매핑되지 않으면 이 3개로 직접 지정 가능
 LLM_BASE_URL=http://localhost:8317/v1
 LLM_API_KEY=dummy-key
</file context>
Suggested change
# Canonical aliases — 위 3가지 옵션 중 어느 것도 매핑되지 않으면 이 3개로 직접 지정 가능
# Canonical aliases — CLIProxy/OpenAI 를 통틀어 최우선 적용. 아래 LLM_BASE_URL/LLM_API_KEY/LLM_MODEL 이 설정되면 CLIPROXY_*, OPENAI_* 보다 먼저 사용됨. (옵션 2 로 전환 시 다른 값과 충돌하지 않도록 비활성화)

LLM_BASE_URL=http://localhost:8317/v1
LLM_API_KEY=dummy-key
LLM_MODEL=gpt-4o
38 changes: 38 additions & 0 deletions packages/agent/data/achievement-standards/9수01-05.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
code: 9수01-05
title: 제곱근의 뜻과 실수의 분류를 이해한다
school_level: middle
grade: 3
techniques:
required_at_least_one_of:
- square_root_definition
- real_number_classification
forbidden:
- calculus
evaluation_dimensions:
- id: A
description: 제곱근의 뜻을 식으로 해석한다
must_preserve: true
- id: B
description: 양수의 두 제곱근을 모두 구한다
must_preserve: true
- id: C
description: 근호를 사용해 정확한 값을 표현한다
must_preserve: true
- id: D
description: 실수 범위에서 해를 판단한다
must_preserve: true
difficulty_range:
- easy
- medium
problem_types_supported:
- short_answer
- objective
structural_transforms:
- kind: coefficient_swap
range: [2, 20]
exclude_zero: true
- kind: variable_rename
allowed: [x, y]
conceptual_transforms:
- kind: inverse_question
hint: 제곱하면 같은 수가 되는 두 실수를 묻는다
32 changes: 32 additions & 0 deletions packages/agent/data/achievement-standards/9수02-03.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
code: 9수02-03
title: 일차방정식을 풀 수 있다
school_level: middle
grade: 1
techniques:
required_at_least_one_of:
- inverse_operation
- equation_simplification
forbidden:
- quadratic_formula
evaluation_dimensions:
- id: A
description: 등식의 성질을 이용해 미지수를 고립시킨다
must_preserve: true
- id: B
description: 구한 값을 원래 식에 대입해 확인한다
must_preserve: true
difficulty_range:
- easy
- medium
problem_types_supported:
- short_answer
- objective
structural_transforms:
- kind: coefficient_swap
range: [-20, 20]
exclude_zero: true
conceptual_transforms:
- kind: rephrase_as_word_problem
context:
- 수량 관계
- 비율
31 changes: 31 additions & 0 deletions packages/agent/data/achievement-standards/9수02-09.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
code: 9수02-09
title: 이차방정식을 풀 수 있다
school_level: middle
grade: 3
techniques:
required_at_least_one_of:
- factorization
- root_relation
forbidden:
- calculus
evaluation_dimensions:
- id: A
description: 이차식을 인수분해하여 해를 구한다
must_preserve: true
- id: B
description: 해를 원래 식에 대입해 검산한다
must_preserve: true
difficulty_range:
- easy
- medium
problem_types_supported:
- short_answer
- objective
structural_transforms:
- kind: coefficient_swap
range: [-12, 12]
exclude_zero: true
- kind: sign_flip
conceptual_transforms:
- kind: present_via_root_relations
hint: 두 근의 합과 곱 조건으로 다시 제시
7 changes: 7 additions & 0 deletions packages/agent/data/corpus/math-sample-unified-v1.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"id":{"problem_id":"seed-9수02-09-001","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":3,"semester":1,"topic_code":"9수02-09","topic_name":"이차방정식","achievement_standard":"9수02-09","achievement_confidence":1.0},"problem":{"question_text":"x**2 - 5*x + 6 = 0","answer_text":"2, 3","explanation_text":"(x - 2)(x - 3) = 0 이므로 x = 2 또는 x = 3","choice_blocks":null,"problem_type":"short_answer","difficulty":"easy"}}
{"id":{"problem_id":"seed-9수02-09-002","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":3,"semester":1,"topic_code":"9수02-09","topic_name":"이차방정식","achievement_standard":"9수02-09","achievement_confidence":1.0},"problem":{"question_text":"x**2 + 4*x - 12 = 0","answer_text":"2, -6","explanation_text":"(x - 2)(x + 6) = 0 이므로 x = 2 또는 x = -6","choice_blocks":null,"problem_type":"short_answer","difficulty":"medium"}}
{"id":{"problem_id":"seed-9수02-09-003","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":3,"semester":1,"topic_code":"9수02-09","topic_name":"이차방정식","achievement_standard":"9수02-09","achievement_confidence":1.0},"problem":{"question_text":"x**2 - 4*x + 4 = 0","answer_text":"2","explanation_text":"(x - 2)**2 = 0 이므로 x = 2","choice_blocks":null,"problem_type":"short_answer","difficulty":"easy"}}
{"id":{"problem_id":"seed-9수02-03-001","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":1,"semester":1,"topic_code":"9수02-03","topic_name":"일차방정식","achievement_standard":"9수02-03","achievement_confidence":1.0},"problem":{"question_text":"3*x + 5 = 14","answer_text":"3","explanation_text":"3x = 9 이므로 x = 3","choice_blocks":null,"problem_type":"short_answer","difficulty":"easy"}}
{"id":{"problem_id":"seed-9수02-03-002","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":1,"semester":1,"topic_code":"9수02-03","topic_name":"일차방정식","achievement_standard":"9수02-03","achievement_confidence":1.0},"problem":{"question_text":"4*(x - 1) = 8","answer_text":"3","explanation_text":"4x - 4 = 8, 4x = 12 이므로 x = 3","choice_blocks":null,"problem_type":"short_answer","difficulty":"easy"}}
{"id":{"problem_id":"seed-9수02-07-001","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":2,"semester":1,"topic_code":"9수02-07","topic_name":"연립일차방정식","achievement_standard":"9수02-07","achievement_confidence":1.0},"problem":{"question_text":"x + y = 5; x - y = 1","answer_text":"x = 3, y = 2","explanation_text":"두 식을 더하면 2x = 6, x = 3이고 y = 2","choice_blocks":null,"problem_type":"short_answer","difficulty":"medium"}}
{"id":{"problem_id":"seed-9수01-05-001","source_dataset":"111","split":"train","source_label_type":"seed"},"curriculum":{"school_level":"middle","grade":3,"semester":1,"topic_code":"9수01-05","topic_name":"제곱근과 실수","achievement_standard":"9수01-05","achievement_confidence":1.0},"problem":{"question_text":"x**2 - 5 = 0","answer_text":"sqrt(5), -sqrt(5)","explanation_text":"x**2 = 5 이므로 x = sqrt(5) 또는 x = -sqrt(5)","choice_blocks":null,"problem_type":"short_answer","difficulty":"medium"}}
4 changes: 0 additions & 4 deletions packages/agent/prompts/objective-mapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,3 @@ LLM 보조 판정은 *nuance*만 제공:

- 통과/실패 *판정*은 하지 않는다. 결정론 매칭 결과를 *bolster*만 함 (D-1)
- 의심스러우면 `lost_dimensions`에 표기

# TODO

- ObjectiveMappingNuanceSchema 정의 후 schema 필드 채우기
23 changes: 18 additions & 5 deletions packages/agent/prompts/problem-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
id: problem-generator
version: 0.1.0
model: gpt-4o
temperature: 0.7
temperature: 0.35
max_tokens: 2000
schema: GeneratedProblemSchema
variables:
Expand All @@ -23,6 +23,15 @@ updated: 2026-05-18

`{{request.mode}}` — `structural` 이면 같은 풀이법, 다른 숫자/표현. `conceptual` 이면 같은 학습 목표·평가 차원, 다른 풀이 경로.

# Selected Source Problem

이 문제를 반드시 변형의 기준으로 삼아라: `{{request.source_problem_text}}`

- 원문을 그대로 복사하지 말 것.
- structural: 식의 골격과 풀이 단계는 유지하되 계수·상수·근을 바꾼다.
- conceptual: 평가 차원은 보존하되 식의 형태를 바꾼다. 예: `x**2 - a = 0` 기준이면 `(x - h)**2 - a = 0` 또는 전개형 `x**2 + b*x + c = 0`처럼 다른 표현 경로를 사용한다.
- `{{request.difficulty}}` 난이도에 맞춘다. medium 이상에서는 `x**2 - a = 0`처럼 너무 직접적인 기본형만 내지 말고 한 단계 변형된 식을 낸다.

# Intent

학습 목표: {{intent.objective_description}} (`{{intent.objective_code}}`)
Expand All @@ -47,10 +56,14 @@ updated: 2026-05-18

# Output

`GeneratedProblem` 스키마(`docs/specs/domain.md` §2.3)에 맞춰 JSON으로 응답.
- `question_text`, `expected_answer`는 LaTeX (`\frac` 통일, `\dfrac` 금지)
- `proposed_solution_trace`에 풀이 단계 명시
- `inferred_intent.objective_code == intent.objective_code` (I-G1)
JSON으로만 응답.
- `question_text`는 KaTeX가 바로 렌더링할 수 있는 LaTeX `x` 방정식 문자열이어야 한다. `**`, `*`를 쓰지 말고 지수는 `x^{2}`, 곱셈은 `5x`, 제곱근은 `\\sqrt{7}`처럼 쓴다.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: Prompt instructs LLM to output question_text and expected_answer in KaTeX LaTeX format, but the downstream SymPy verification pipeline expects standard SymPy expressions. The math engine (packages/math-engine/src/routers/math.py) uses sympy.parsing.sympy_parser.parse_expr with standard_transformations + implicit_multiplication_application + convert_xor — this can parse x**2, sqrt(7), 2*x + 3 but CANNOT parse LaTeX syntax like x^{2}, \sqrt{7}, 5x. No LaTeX-to-SymPy conversion exists in the pipeline. The formatLatex function (packages/agent/src/tools/latex-formatter.ts) converts from SymPy → KaTeX, not the reverse. Result: when the LLM follows the prompt and outputs LaTeX, both the generation step's normalizeExpectedAnswer (in problem-generation.ts:137) and the verification step's verifyWithSympy (in sympy-verification.ts:68,73) will send unparseable expressions to the math engine, causing HTTP 400 parse errors and cascading step failures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/agent/prompts/problem-generator.md, line 60:

<comment>Prompt instructs LLM to output `question_text` and `expected_answer` in KaTeX LaTeX format, but the downstream SymPy verification pipeline expects standard SymPy expressions. The math engine (`packages/math-engine/src/routers/math.py`) uses `sympy.parsing.sympy_parser.parse_expr` with `standard_transformations + implicit_multiplication_application + convert_xor` — this can parse `x**2`, `sqrt(7)`, `2*x + 3` but CANNOT parse LaTeX syntax like `x^{2}`, `\sqrt{7}`, `5x`. No LaTeX-to-SymPy conversion exists in the pipeline. The `formatLatex` function (`packages/agent/src/tools/latex-formatter.ts`) converts from SymPy → KaTeX, not the reverse. Result: when the LLM follows the prompt and outputs LaTeX, both the generation step's `normalizeExpectedAnswer` (in `problem-generation.ts:137`) and the verification step's `verifyWithSympy` (in `sympy-verification.ts:68,73`) will send unparseable expressions to the math engine, causing HTTP 400 parse errors and cascading step failures.</comment>

<file context>
@@ -57,8 +57,10 @@ updated: 2026-05-18
 JSON으로만 응답.
-- `question_text`는 SymPy가 풀 수 있는 `x` 방정식 문자열이어야 한다. 예: `x**2 - 7 = 0`, `2*x + 3 = 11`.
-- `expected_answer`는 `sqrt(7), -sqrt(7)`처럼 쉼표로 구분한다. 서버가 SymPy로 다시 계산해 최종 정답을 검증한다.
+- `question_text`는 KaTeX가 바로 렌더링할 수 있는 LaTeX `x` 방정식 문자열이어야 한다. `**`, `*`를 쓰지 말고 지수는 `x^{2}`, 곱셈은 `5x`, 제곱근은 `\\sqrt{7}`처럼 쓴다.
+- `expected_answer`도 LaTeX로 쓴다. 예: `2, 5`, `\\sqrt{7}, -\\sqrt{7}`.
+- 예시 출력: `{ "question_text": "x^{2} - 7x + 10 = 0", "expected_answer": "2, 5", "proposed_solution_trace": "인수분해하면 (x - 2)(x - 5) = 0이므로 x = 2 또는 x = 5이다." }`
</file context>

- `expected_answer`도 LaTeX로 쓴다. 예: `2, 5`, `\\sqrt{7}, -\\sqrt{7}`.
- 예시 출력: `{ "question_text": "x^{2} - 7x + 10 = 0", "expected_answer": "2, 5", "proposed_solution_trace": "인수분해하면 (x - 2)(x - 5) = 0이므로 x = 2 또는 x = 5이다." }`
- 서버가 SymPy로 다시 계산해 최종 정답을 검증한다.
- `proposed_solution_trace`에 풀이 단계와 출제 의도를 한국어로 명시한다.
- 풀이 가능하고 답이 실수인 중학교 수준 방정식만 생성한다.
- 결과 문제는 source problem과 달라야 하며, structural/conceptual 모드 차이가 풀이 설명에 드러나야 한다.

# TODO

Expand Down
Loading
Loading