claudqunwang Cursor commited on
Commit
0cde401
·
1 Parent(s): 8f7c172

feat(courseware): AI Teacher Assistant Agent 模块化

Browse files

- Course Vision & Structure Builder / Activity Designer / Teaching Copilot / QA Optimizer / Content Generator
- RAG 引用规范: [Source: Filename/Page] | [Source: URL]
- api/courseware/*, routes_courseware, weaviate_retrieve refs, docs/COURSEWARE_AGENT.md

Co-authored-by: Cursor <cursoragent@cursor.com>

api/courseware/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware – AI Teacher Assistant Agent(模块化)
2
+ """
3
+ 基于「课程描述 + 辅助材料」与 RAG,生成全链路教学资源的 AI 教师助教。
4
+ 所有生成内容均带 Reference:本地 VDB [Source: Filename/Page],Web [Source: URL]。
5
+ """
6
+ from api.courseware.vision_builder import build_course_vision
7
+ from api.courseware.activity_designer import design_activities_and_assignments
8
+ from api.courseware.teaching_copilot import teaching_copilot
9
+ from api.courseware.qa_optimizer import optimize_from_quiz_data
10
+ from api.courseware.content_generator import generate_lesson_plan_and_ppt_data
11
+
12
+ __all__ = [
13
+ "build_course_vision",
14
+ "design_activities_and_assignments",
15
+ "teaching_copilot",
16
+ "optimize_from_quiz_data",
17
+ "generate_lesson_plan_and_ppt_data",
18
+ ]
api/courseware/activity_designer.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/activity_designer.py
2
+ """
3
+ Activity & Assignment Designer:课堂活动、作业及评分标准(Rubric);从上传资料提取核心知识点。
4
+ """
5
+ from typing import Optional
6
+
7
+ from api.config import client, DEFAULT_MODEL, USE_WEAVIATE
8
+ from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
9
+ from api.courseware.references import append_references_to_content
10
+ from api.courseware.prompts import ACTIVITY_DESIGNER_SYSTEM, ACTIVITY_DESIGNER_USER_TEMPLATE
11
+
12
+
13
+ def design_activities_and_assignments(
14
+ topic: str,
15
+ learning_objectives: Optional[str] = None,
16
+ rag_context_override: Optional[str] = None,
17
+ max_tokens: int = 2200,
18
+ ) -> str:
19
+ """
20
+ 设计课堂活动、作业及 Rubric;支持从上传资料提取核心知识点并与目标一致。
21
+ """
22
+ query = f"{topic}\n{learning_objectives or ''}"[:1500]
23
+ rag_context, refs = get_rag_context_with_refs(query, top_k=8, max_context_chars=5000)
24
+ if rag_context_override:
25
+ rag_context = rag_context_override
26
+ ref_instruction = inject_refs_instruction(refs)
27
+ user_content = ACTIVITY_DESIGNER_USER_TEMPLATE.format(
28
+ topic=topic.strip() or "(未提供主题)",
29
+ learning_objectives=(learning_objectives or "(未提供,请根据主题推断)").strip(),
30
+ rag_context=rag_context or "(无检索到知识库摘录,将基于通用教学设计回答。)",
31
+ ref_instruction=ref_instruction,
32
+ )
33
+ try:
34
+ resp = client.chat.completions.create(
35
+ model=DEFAULT_MODEL,
36
+ messages=[
37
+ {"role": "system", "content": ACTIVITY_DESIGNER_SYSTEM},
38
+ {"role": "user", "content": user_content},
39
+ ],
40
+ temperature=0.5,
41
+ max_tokens=max_tokens,
42
+ timeout=90,
43
+ )
44
+ out = (resp.choices[0].message.content or "").strip()
45
+ except Exception as e:
46
+ out = f"生成失败:{e}。请稍后重试。"
47
+ if refs and "## References" not in out and "[Source:" not in out:
48
+ out = append_references_to_content(out, refs)
49
+ return out
api/courseware/content_generator.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/content_generator.py
2
+ """
3
+ Content Generator:生成 Markdown 详细教案,并导出可用于生成 PPT 的结构化数据。
4
+ """
5
+ from typing import Optional
6
+
7
+ from api.config import client, DEFAULT_MODEL
8
+ from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
9
+ from api.courseware.references import append_references_to_content
10
+ from api.courseware.prompts import CONTENT_GENERATOR_SYSTEM, CONTENT_GENERATOR_LESSON_PLAN_TEMPLATE
11
+
12
+
13
+ def generate_lesson_plan_and_ppt_data(
14
+ topic: str,
15
+ duration: Optional[str] = None,
16
+ outline_points: Optional[str] = None,
17
+ max_tokens: int = 2800,
18
+ ) -> str:
19
+ """
20
+ 生成 Markdown 详细教案 + 可用于 PPT 的结构化数据(每页 title、bullets、speaker_notes)。
21
+ """
22
+ query = f"{topic}\n{outline_points or ''}"[:2000]
23
+ rag_context, refs = get_rag_context_with_refs(query, top_k=8, max_context_chars=5000)
24
+ ref_instruction = inject_refs_instruction(refs)
25
+ user_content = CONTENT_GENERATOR_LESSON_PLAN_TEMPLATE.format(
26
+ topic=topic.strip() or "(未提供主题)",
27
+ duration=(duration or "1 课时").strip(),
28
+ outline_points=(outline_points or "(未提供,请根据主题生成)").strip(),
29
+ rag_context=rag_context or "(无检索到知识库摘录。)",
30
+ ref_instruction=ref_instruction,
31
+ )
32
+ try:
33
+ resp = client.chat.completions.create(
34
+ model=DEFAULT_MODEL,
35
+ messages=[
36
+ {"role": "system", "content": CONTENT_GENERATOR_SYSTEM},
37
+ {"role": "user", "content": user_content},
38
+ ],
39
+ temperature=0.4,
40
+ max_tokens=max_tokens,
41
+ timeout=120,
42
+ )
43
+ out = (resp.choices[0].message.content or "").strip()
44
+ except Exception as e:
45
+ out = f"生成失败:{e}。请稍后重试。"
46
+ if refs and "## References" not in out and "[Source:" not in out:
47
+ out = append_references_to_content(out, refs)
48
+ return out
api/courseware/prompts.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/prompts.py
2
+ """
3
+ AI Teacher Assistant Agent 各模块独立 Prompt 模板。
4
+ """
5
+
6
+ # ------------------------- Course Vision & Structure Builder -------------------------
7
+ COURSE_VISION_SYSTEM = """你是一位课程设计专家,负责根据课程基本信息与教学大纲,产出课程定位、学习目标与层级化知识树(Knowledge Tree)。
8
+ 输出必须基于提供的「课程知识库摘录」与大纲;若存在引用,回答末尾必须包含 References,格式为 [Source: 文件名/页码] 或 [Source: URL]。"""
9
+
10
+ COURSE_VISION_USER_TEMPLATE = """请根据以下信息,生成:
11
+ 1. **课程定位**:一句话说明课程在培养方案中的角色与目标受众。
12
+ 2. **学习目标**:3–6 条可衡量的学习目标(Learning Objectives)。
13
+ 3. **知识树(Knowledge Tree)**:层级化结构(如 模块 → 单元 → 知识点),与大纲对应。
14
+
15
+ **课程基本信息:**
16
+ {course_info}
17
+
18
+ **教学大纲(或要点):**
19
+ {syllabus}
20
+
21
+ **参考知识库摘录(请据此保持与现有课程内容一致):**
22
+ {rag_context}
23
+
24
+ {ref_instruction}
25
+ 请直接输出以上三部分,并确保引用格式正确。"""
26
+
27
+
28
+ # ------------------------- Activity & Assignment Designer -------------------------
29
+ ACTIVITY_DESIGNER_SYSTEM = """你是课堂活动与作业设计专家。根据课程目标与上传资料中的核心知识点,设计课堂活动、作业及对应评分标准(Rubric)。
30
+ 输出必须基于提供的参考资料;回答末尾必须包含 References,格式为 [Source: 文件名/页码] 或 [Source: URL]。"""
31
+
32
+ ACTIVITY_DESIGNER_USER_TEMPLATE = """请针对以下主题与目标,设计:
33
+ 1. **课堂活动**:1–2 个可实施的课堂活动(时长、步骤、所需材料)。
34
+ 2. **作业**:1 个作业(题目描述、提交形式、截止建议)。
35
+ 3. **评分标准(Rubric)**:与该作业对应的评分维度与等级说明。
36
+
37
+ **主题/模块:** {topic}
38
+ **学习目标(可选):** {learning_objectives}
39
+ **参考知识库摘录(请提取核心知识点并与之对齐):**
40
+ {rag_context}
41
+
42
+ {ref_instruction}
43
+ 请直接输出以上三部分,并确保引用格式正确。"""
44
+
45
+
46
+ # ------------------------- Teaching Copilot & Student Adaptation -------------------------
47
+ TEACHING_COPILOT_SYSTEM = """你是课堂实时助教(Teaching Copilot),根据当前授课内容与学生画像(姓名、进度、行为),给出即时教学建议与个性化调整建议。
48
+ 回答末尾若依据了资料,必须包含 References:[Source: 文件名/页码] 或 [Source: URL]。"""
49
+
50
+ TEACHING_COPILOT_USER_TEMPLATE = """**当前授课内容/问题:**
51
+ {current_content}
52
+
53
+ **学生画像(可选):**
54
+ {student_profiles}
55
+
56
+ **参考知识库摘录:**
57
+ {rag_context}
58
+
59
+ {ref_instruction}
60
+ 请给出简洁的实时建议(如:强调某点、提问建议、对某位学生的关注建议),并确保引用格式正确。"""
61
+
62
+
63
+ # ------------------------- Course QA Optimizer -------------------------
64
+ QA_OPTIMIZER_SYSTEM = """你基于学生答题数据(Smart Quiz)分析薄弱点,并给出后续教学优化建议(如补充讲解、练习题、复习重点)。
65
+ 回答末尾若依据了资料,必须包含 References:[Source: 文件名/页码] 或 [Source: URL]。"""
66
+
67
+ QA_OPTIMIZER_USER_TEMPLATE = """**学生答题数据摘要(Smart Quiz):**
68
+ {quiz_summary}
69
+
70
+ **相关课程主题/章节:** {course_topic}
71
+
72
+ **参考知识库摘录(用于对齐课程目标与知识点):**
73
+ {rag_context}
74
+
75
+ {ref_instruction}
76
+ 请输出:1) 薄弱点分析;2) 后续教学建议(可操作);并确保引用格式正确。"""
77
+
78
+
79
+ # ------------------------- Content Generator (Lesson Plan & PPT) -------------------------
80
+ CONTENT_GENERATOR_SYSTEM = """你负责生成详细教案(Markdown)及可用于生成 PPT 的结构化数据(如每页标题、要点、备注)。
81
+ 输出必须基于提供的参考资料;回答末尾必须包含 References:[Source: 文件名/页码] 或 [Source: URL]。"""
82
+
83
+ CONTENT_GENERATOR_LESSON_PLAN_TEMPLATE = """请根据以下主题与大纲,生成:
84
+ 1. **详细教案(Markdown)**:含教学目标、重难点、教学步骤、时间分配、互动设计、作业/预习。
85
+ 2. **PPT 结构化数据**:JSON 或分条列出每页的 title、bullets(3–5 条)、speaker_notes(可选)。
86
+
87
+ **主题/章节:** {topic}
88
+ **课时/时长建议:** {duration}
89
+ **大纲要点:**
90
+ {outline_points}
91
+
92
+ **参考知识库摘录:**
93
+ {rag_context}
94
+
95
+ {ref_instruction}
96
+ 请先输出 Markdown 教案,再输出 PPT 结构化数据,最后附 References。"""
api/courseware/qa_optimizer.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/qa_optimizer.py
2
+ """
3
+ Course QA Optimizer:基于学生答题数据(Smart Quiz)分析弱点,自动优化后续教学建议。
4
+ """
5
+ from typing import Optional
6
+
7
+ from api.config import client, DEFAULT_MODEL
8
+ from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
9
+ from api.courseware.references import append_references_to_content
10
+ from api.courseware.prompts import QA_OPTIMIZER_SYSTEM, QA_OPTIMIZER_USER_TEMPLATE
11
+
12
+
13
+ def optimize_from_quiz_data(
14
+ quiz_summary: str,
15
+ course_topic: Optional[str] = None,
16
+ max_tokens: int = 1500,
17
+ ) -> str:
18
+ """
19
+ 基于 Smart Quiz 答题数据摘要,分析薄弱点并给出后续教学优化建议。
20
+ """
21
+ query = f"{quiz_summary}\n{course_topic or ''}"[:2000]
22
+ rag_context, refs = get_rag_context_with_refs(query, top_k=6, max_context_chars=4000)
23
+ ref_instruction = inject_refs_instruction(refs)
24
+ user_content = QA_OPTIMIZER_USER_TEMPLATE.format(
25
+ quiz_summary=quiz_summary.strip() or "(未提供答题数据)",
26
+ course_topic=(course_topic or "(未指定)").strip(),
27
+ rag_context=rag_context or "(无检索到知识库摘录。)",
28
+ ref_instruction=ref_instruction,
29
+ )
30
+ try:
31
+ resp = client.chat.completions.create(
32
+ model=DEFAULT_MODEL,
33
+ messages=[
34
+ {"role": "system", "content": QA_OPTIMIZER_SYSTEM},
35
+ {"role": "user", "content": user_content},
36
+ ],
37
+ temperature=0.4,
38
+ max_tokens=max_tokens,
39
+ timeout=90,
40
+ )
41
+ out = (resp.choices[0].message.content or "").strip()
42
+ except Exception as e:
43
+ out = f"生成失败:{e}。请稍后重试。"
44
+ if refs and "## References" not in out and "[Source:" not in out:
45
+ out = append_references_to_content(out, refs)
46
+ return out
api/courseware/rag.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/rag.py
2
+ """
3
+ Courseware RAG:统一检索并返回带引用的上下文。
4
+ - 本地 VDB(Weaviate / 上传文件 chunks):[Source: Filename/Page]
5
+ - Web Search(可选):[Source: URL]
6
+ """
7
+ from typing import List, Tuple, Optional
8
+
9
+ from api.config import USE_WEAVIATE
10
+ from api.weaviate_retrieve import retrieve_from_weaviate_with_refs, RefItem
11
+ from api.courseware.references import format_references, append_references_to_content
12
+
13
+
14
+ def get_rag_context_with_refs(
15
+ query: str,
16
+ top_k: int = 8,
17
+ web_search_urls: Optional[List[str]] = None,
18
+ max_context_chars: int = 6000,
19
+ ) -> Tuple[str, List[RefItem]]:
20
+ """
21
+ 获取 RAG 上下文与引用列表。优先使用 Weaviate;可选追加 web 来源。
22
+ context 已包含可放入 prompt 的参考摘录;refs 用于最终标注。
23
+ """
24
+ text = ""
25
+ refs: List[RefItem] = []
26
+ if USE_WEAVIATE and query and len(query.strip()) >= 3:
27
+ text, refs = retrieve_from_weaviate_with_refs(query, top_k=top_k)
28
+ if text and max_context_chars > 0 and len(text) > max_context_chars:
29
+ text = text[:max_context_chars] + "\n..."
30
+ if web_search_urls:
31
+ for url in web_search_urls[:10]:
32
+ url = (url or "").strip()
33
+ if url:
34
+ refs.append({"type": "web", "url": url})
35
+ return (text or "").strip(), refs
36
+
37
+
38
+ def inject_refs_instruction(refs: List[RefItem]) -> str:
39
+ """生成给 LLM 的引用说明:要求回答中必须标注引用。"""
40
+ if not refs:
41
+ return "(本次无检索到参考资料,回答中可注明「无引用」。)"
42
+ ref_block = format_references(refs)
43
+ return (
44
+ "回答末尾必须附「References」小节,按以下格式逐条列出所依据来源:\n"
45
+ "本地资料使用 [Source: 文件名/页码],网络来源使用 [Source: URL]。\n"
46
+ "本次参考来源:\n" + ref_block
47
+ )
api/courseware/references.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/references.py
2
+ """
3
+ Reference 规范:所有 RAG 生成内容必须附带引用。
4
+ - 本地 VDB: [Source: Filename/Page]
5
+ - Web Search: [Source: URL]
6
+ """
7
+ from typing import List, Dict, Any
8
+
9
+
10
+ def format_ref_item(item: Dict[str, Any]) -> str:
11
+ """单条引用格式化为 [Source: ...]。"""
12
+ t = (item.get("type") or "").strip().lower()
13
+ if t == "web" or item.get("url"):
14
+ url = (item.get("url") or "").strip()
15
+ if url:
16
+ return f"[Source: {url}]"
17
+ # 默认按本地 VDB
18
+ source = (item.get("source") or item.get("source_file") or "uploaded").strip()
19
+ page = (item.get("page") or "").strip()
20
+ if page:
21
+ return f"[Source: {source}/{page}]"
22
+ return f"[Source: {source}]"
23
+
24
+
25
+ def format_references(refs: List[Dict[str, Any]]) -> str:
26
+ """将引用列表格式化为多行 [Source: ...],去重保持顺序。"""
27
+ seen: set = set()
28
+ lines: List[str] = []
29
+ for r in refs:
30
+ s = format_ref_item(r)
31
+ if s and s not in seen:
32
+ seen.add(s)
33
+ lines.append(s)
34
+ return "\n".join(lines) if lines else ""
35
+
36
+
37
+ def append_references_to_content(content: str, refs: List[Dict[str, Any]]) -> str:
38
+ """在正文末尾追加 References 小节。"""
39
+ ref_block = format_references(refs)
40
+ if not ref_block:
41
+ return content.rstrip()
42
+ return content.rstrip() + "\n\n## References\n\n" + ref_block
api/courseware/teaching_copilot.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/teaching_copilot.py
2
+ """
3
+ Teaching Copilot & Student Adaptation:课堂实时辅助,按学生画像(Name, Progress, Behavior)动态调整建议。
4
+ """
5
+ from typing import Optional, List, Dict, Any
6
+
7
+ from api.config import client, DEFAULT_MODEL
8
+ from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
9
+ from api.courseware.references import append_references_to_content
10
+ from api.courseware.prompts import TEACHING_COPILOT_SYSTEM, TEACHING_COPILOT_USER_TEMPLATE
11
+
12
+
13
+ def _format_student_profiles(profiles: List[Dict[str, Any]]) -> str:
14
+ """将学生画像列表格式化为可读文本。"""
15
+ if not profiles:
16
+ return "(未提供)"
17
+ lines = []
18
+ for i, p in enumerate(profiles[:20], 1):
19
+ name = p.get("name") or p.get("Name") or "—"
20
+ progress = p.get("progress") or p.get("Progress") or "—"
21
+ behavior = p.get("behavior") or p.get("Behavior") or "—"
22
+ lines.append(f"- 学生{i}: Name={name}, Progress={progress}, Behavior={behavior}")
23
+ return "\n".join(lines)
24
+
25
+
26
+ def teaching_copilot(
27
+ current_content: str,
28
+ student_profiles: Optional[List[Dict[str, Any]]] = None,
29
+ max_tokens: int = 1200,
30
+ ) -> str:
31
+ """
32
+ 课堂实时辅助:根据当前授课内容与学生画像给出即时建议与个性化调整。
33
+ """
34
+ query = current_content[:1500]
35
+ rag_context, refs = get_rag_context_with_refs(query, top_k=6, max_context_chars=3500)
36
+ ref_instruction = inject_refs_instruction(refs)
37
+ profiles_text = _format_student_profiles(student_profiles or [])
38
+ user_content = TEACHING_COPILOT_USER_TEMPLATE.format(
39
+ current_content=current_content.strip() or "(未提供当前内容)",
40
+ student_profiles=profiles_text,
41
+ rag_context=rag_context or "(无检索到知识库摘录。)",
42
+ ref_instruction=ref_instruction,
43
+ )
44
+ try:
45
+ resp = client.chat.completions.create(
46
+ model=DEFAULT_MODEL,
47
+ messages=[
48
+ {"role": "system", "content": TEACHING_COPILOT_SYSTEM},
49
+ {"role": "user", "content": user_content},
50
+ ],
51
+ temperature=0.5,
52
+ max_tokens=max_tokens,
53
+ timeout=60,
54
+ )
55
+ out = (resp.choices[0].message.content or "").strip()
56
+ except Exception as e:
57
+ out = f"生成失败:{e}。请稍后重试。"
58
+ if refs and "## References" not in out and "[Source:" not in out:
59
+ out = append_references_to_content(out, refs)
60
+ return out
api/courseware/vision_builder.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/courseware/vision_builder.py
2
+ """
3
+ Course Vision & Structure Builder:课程定位、学习目标、层级化知识树。
4
+ """
5
+ from typing import Optional
6
+
7
+ from api.config import client, DEFAULT_MODEL, USE_WEAVIATE
8
+ from api.courseware.rag import get_rag_context_with_refs, inject_refs_instruction
9
+ from api.courseware.references import append_references_to_content
10
+ from api.courseware.prompts import COURSE_VISION_SYSTEM, COURSE_VISION_USER_TEMPLATE
11
+
12
+
13
+ def build_course_vision(
14
+ course_info: str,
15
+ syllabus: str,
16
+ max_tokens: int = 2000,
17
+ ) -> str:
18
+ """
19
+ 输入:课程基本信息 + 教学大纲。
20
+ 输出:课程定位、学习目标、层级化知识树;含 References。
21
+ """
22
+ query = f"{course_info}\n{syllabus}"[:2000]
23
+ rag_context, refs = get_rag_context_with_refs(query, top_k=8, max_context_chars=5000)
24
+ ref_instruction = inject_refs_instruction(refs)
25
+ user_content = COURSE_VISION_USER_TEMPLATE.format(
26
+ course_info=course_info.strip() or "(未提供)",
27
+ syllabus=syllabus.strip() or "(未提供)",
28
+ rag_context=rag_context or "(无检索到知识库摘录,将基于通用课程设计经验回答。)",
29
+ ref_instruction=ref_instruction,
30
+ )
31
+ try:
32
+ resp = client.chat.completions.create(
33
+ model=DEFAULT_MODEL,
34
+ messages=[
35
+ {"role": "system", "content": COURSE_VISION_SYSTEM},
36
+ {"role": "user", "content": user_content},
37
+ ],
38
+ temperature=0.4,
39
+ max_tokens=max_tokens,
40
+ timeout=90,
41
+ )
42
+ out = (resp.choices[0].message.content or "").strip()
43
+ except Exception as e:
44
+ out = f"生成失败:{e}。请稍后重试。"
45
+ if refs and "## References" not in out and "[Source:" not in out:
46
+ out = append_references_to_content(out, refs)
47
+ return out
api/routes_courseware.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # api/routes_courseware.py
2
+ """AI Teacher Assistant Agent(Courseware)API:课程愿景、活动设计、课堂助教、QA 优化、教案与 PPT 数据。"""
3
+ from typing import Optional, List, Any
4
+
5
+ from fastapi import APIRouter, HTTPException
6
+ from pydantic import BaseModel, Field
7
+
8
+ from api.config import USE_WEAVIATE
9
+ from api.courseware import (
10
+ build_course_vision,
11
+ design_activities_and_assignments,
12
+ teaching_copilot,
13
+ optimize_from_quiz_data,
14
+ generate_lesson_plan_and_ppt_data,
15
+ )
16
+
17
+ router = APIRouter(prefix="/api/courseware", tags=["courseware"])
18
+
19
+
20
+ # ------------------------- Course Vision & Structure Builder -------------------------
21
+ class VisionRequest(BaseModel):
22
+ course_info: str = Field(..., min_length=1, description="课程基本信息")
23
+ syllabus: str = Field(..., min_length=1, description="教学大纲或要点")
24
+
25
+
26
+ class VisionResponse(BaseModel):
27
+ ok: bool = True
28
+ content: str = Field(..., description="课程定位、学习目标、知识树(含 References)")
29
+ weaviate_used: bool = False
30
+
31
+
32
+ @router.post("/vision", response_model=VisionResponse)
33
+ def post_vision(req: VisionRequest):
34
+ """课程愿景与结构:输出课程定位、学习目标、层级化知识树。"""
35
+ try:
36
+ content = build_course_vision(
37
+ course_info=req.course_info.strip(),
38
+ syllabus=req.syllabus.strip(),
39
+ )
40
+ return VisionResponse(content=content, weaviate_used=USE_WEAVIATE)
41
+ except Exception as e:
42
+ raise HTTPException(status_code=500, detail=str(e))
43
+
44
+
45
+ # ------------------------- Activity & Assignment Designer -------------------------
46
+ class ActivitiesRequest(BaseModel):
47
+ topic: str = Field(..., min_length=1, description="主题/模块")
48
+ learning_objectives: Optional[str] = Field(None, description="学习目标(可选)")
49
+ rag_context_override: Optional[str] = Field(None, description="覆盖 RAG 上下文(如上传资料摘要)")
50
+
51
+
52
+ class ActivitiesResponse(BaseModel):
53
+ ok: bool = True
54
+ content: str = Field(..., description="课堂活动、作业、Rubric(含 References)")
55
+ weaviate_used: bool = False
56
+
57
+
58
+ @router.post("/activities", response_model=ActivitiesResponse)
59
+ def post_activities(req: ActivitiesRequest):
60
+ """活动与作业设计:课堂活动、作业、评分标准;支持从上传资料提取知识点。"""
61
+ try:
62
+ content = design_activities_and_assignments(
63
+ topic=req.topic.strip(),
64
+ learning_objectives=req.learning_objectives.strip() if req.learning_objectives else None,
65
+ rag_context_override=req.rag_context_override.strip() if req.rag_context_override else None,
66
+ )
67
+ return ActivitiesResponse(content=content, weaviate_used=USE_WEAVIATE)
68
+ except Exception as e:
69
+ raise HTTPException(status_code=500, detail=str(e))
70
+
71
+
72
+ # ------------------------- Teaching Copilot & Student Adaptation -------------------------
73
+ class StudentProfile(BaseModel):
74
+ name: Optional[str] = None
75
+ progress: Optional[str] = None
76
+ behavior: Optional[str] = None
77
+
78
+
79
+ class CopilotRequest(BaseModel):
80
+ current_content: str = Field(..., min_length=1, description="当前授课内容/问题")
81
+ student_profiles: Optional[List[StudentProfile]] = Field(None, description="学生画像 Name, Progress, Behavior")
82
+
83
+
84
+ class CopilotResponse(BaseModel):
85
+ ok: bool = True
86
+ content: str = Field(..., description="实时建议(含 References)")
87
+ weaviate_used: bool = False
88
+
89
+
90
+ @router.post("/copilot", response_model=CopilotResponse)
91
+ def post_copilot(req: CopilotRequest):
92
+ """课堂实时助教:根据当前内容与学生画像给出即时建议。"""
93
+ try:
94
+ profiles = [p.model_dump() for p in (req.student_profiles or [])]
95
+ content = teaching_copilot(
96
+ current_content=req.current_content.strip(),
97
+ student_profiles=profiles,
98
+ )
99
+ return CopilotResponse(content=content, weaviate_used=USE_WEAVIATE)
100
+ except Exception as e:
101
+ raise HTTPException(status_code=500, detail=str(e))
102
+
103
+
104
+ # ------------------------- Course QA Optimizer -------------------------
105
+ class QAOptimizeRequest(BaseModel):
106
+ quiz_summary: str = Field(..., min_length=1, description="学生答题数据摘要(Smart Quiz)")
107
+ course_topic: Optional[str] = Field(None, description="相关课程主题/章节")
108
+
109
+
110
+ class QAOptimizeResponse(BaseModel):
111
+ ok: bool = True
112
+ content: str = Field(..., description="薄弱点分析与教学建议(含 References)")
113
+ weaviate_used: bool = False
114
+
115
+
116
+ @router.post("/qa-optimize", response_model=QAOptimizeResponse)
117
+ def post_qa_optimize(req: QAOptimizeRequest):
118
+ """基于 Smart Quiz 答题数据分析弱点并给出教学优化建议。"""
119
+ try:
120
+ content = optimize_from_quiz_data(
121
+ quiz_summary=req.quiz_summary.strip(),
122
+ course_topic=req.course_topic.strip() if req.course_topic else None,
123
+ )
124
+ return QAOptimizeResponse(content=content, weaviate_used=USE_WEAVIATE)
125
+ except Exception as e:
126
+ raise HTTPException(status_code=500, detail=str(e))
127
+
128
+
129
+ # ------------------------- Content Generator (Lesson Plan & PPT) -------------------------
130
+ class ContentRequest(BaseModel):
131
+ topic: str = Field(..., min_length=1, description="主题/章节")
132
+ duration: Optional[str] = Field(None, description="课时/时长建议")
133
+ outline_points: Optional[str] = Field(None, description="大纲要点")
134
+
135
+
136
+ class ContentResponse(BaseModel):
137
+ ok: bool = True
138
+ content: str = Field(..., description="Markdown 教案 + PPT 结构化数据(含 References)")
139
+ weaviate_used: bool = False
140
+
141
+
142
+ @router.post("/content", response_model=ContentResponse)
143
+ def post_content(req: ContentRequest):
144
+ """生成详细教案(Markdown)与可用于 PPT 的结构化数据。"""
145
+ try:
146
+ content = generate_lesson_plan_and_ppt_data(
147
+ topic=req.topic.strip(),
148
+ duration=req.duration.strip() if req.duration else None,
149
+ outline_points=req.outline_points.strip() if req.outline_points else None,
150
+ )
151
+ return ContentResponse(content=content, weaviate_used=USE_WEAVIATE)
152
+ except Exception as e:
153
+ raise HTTPException(status_code=500, detail=str(e))
154
+
155
+
156
+ # ------------------------- Status -------------------------
157
+ @router.get("/status")
158
+ def get_courseware_status():
159
+ """Courseware Agent 状态与能力列表。"""
160
+ return {
161
+ "weaviate_configured": USE_WEAVIATE,
162
+ "reference_policy": "All outputs include References: [Source: Filename/Page] or [Source: URL]",
163
+ "modules": [
164
+ "vision", # Course Vision & Structure Builder
165
+ "activities", # Activity & Assignment Designer
166
+ "copilot", # Teaching Copilot & Student Adaptation
167
+ "qa-optimize", # Course QA Optimizer
168
+ "content", # Content Generator (Lesson Plan & PPT)
169
+ ],
170
+ }
api/routes_teacher.py CHANGED
@@ -128,7 +128,7 @@ def post_assessment_analysis(req: AssessmentAnalysisRequest):
128
 
129
  @router.get("/api/teacher/status")
130
  def get_teacher_status():
131
- """教师 Agent 状态(是否已配置 Weaviate)。"""
132
  return {
133
  "weaviate_configured": USE_WEAVIATE,
134
  "features": [
@@ -137,4 +137,8 @@ def get_teacher_status():
137
  "assignment-questions",
138
  "assessment-analysis",
139
  ],
 
 
 
 
140
  }
 
128
 
129
  @router.get("/api/teacher/status")
130
  def get_teacher_status():
131
+ """教师 Agent 状态(是否已配置 Weaviate);含 Courseware 模块入口。"""
132
  return {
133
  "weaviate_configured": USE_WEAVIATE,
134
  "features": [
 
137
  "assignment-questions",
138
  "assessment-analysis",
139
  ],
140
+ "courseware_modules": [
141
+ "vision", "activities", "copilot", "qa-optimize", "content",
142
+ ],
143
+ "courseware_base": "/api/courseware",
144
  }
api/server.py CHANGED
@@ -34,6 +34,8 @@ from api.tts_podcast import (
34
  from api.routes_directory import router as directory_router
35
  # ✅ 教师 Agent:课程描述、文档建议、作业题库、学习评估
36
  from api.routes_teacher import router as teacher_router
 
 
37
 
38
  # ✅ LangSmith (optional)
39
  try:
@@ -96,6 +98,7 @@ app.add_middleware(
96
  # ✅ NEW: include directory/workspace APIs BEFORE SPA fallback
97
  app.include_router(directory_router)
98
  app.include_router(teacher_router)
 
99
 
100
  # ----------------------------
101
  # Static hosting (Vite build)
 
34
  from api.routes_directory import router as directory_router
35
  # ✅ 教师 Agent:课程描述、文档建议、作业题库、学习评估
36
  from api.routes_teacher import router as teacher_router
37
+ # ✅ Courseware:课程愿景、活动设计、课堂助教、QA 优化、教案与 PPT
38
+ from api.routes_courseware import router as courseware_router
39
 
40
  # ✅ LangSmith (optional)
41
  try:
 
98
  # ✅ NEW: include directory/workspace APIs BEFORE SPA fallback
99
  app.include_router(directory_router)
100
  app.include_router(teacher_router)
101
+ app.include_router(courseware_router)
102
 
103
  # ----------------------------
104
  # Static hosting (Vite build)
api/weaviate_retrieve.py CHANGED
@@ -2,12 +2,16 @@
2
  """
3
  与 ClareVoice 共用同一 Weaviate 数据库(GenAICourses)的检索封装。
4
  教师 Agent 和 Clare 均可调用,需与 build_weaviate_index 使用相同 embedding(HF all-MiniLM-L6-v2)。
 
5
  """
6
  import os
7
- from typing import Optional
8
 
9
  from .config import USE_WEAVIATE, WEAVIATE_URL, WEAVIATE_API_KEY, WEAVIATE_COLLECTION
10
 
 
 
 
11
 
12
  def retrieve_from_weaviate(query: str, top_k: int = 8, timeout_sec: float = 45.0) -> str:
13
  """
@@ -59,3 +63,75 @@ def retrieve_from_weaviate(query: str, top_k: int = 8, timeout_sec: float = 45.0
59
  except concurrent.futures.TimeoutError:
60
  print(f"[weaviate_retrieve] timeout after {timeout_sec}s")
61
  return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  """
3
  与 ClareVoice 共用同一 Weaviate 数据库(GenAICourses)的检索封装。
4
  教师 Agent 和 Clare 均可调用,需与 build_weaviate_index 使用相同 embedding(HF all-MiniLM-L6-v2)。
5
+ 支持带引用的检索:返回 (text, refs),用于标注 [Source: Filename/Page]。
6
  """
7
  import os
8
+ from typing import List, Optional, Tuple
9
 
10
  from .config import USE_WEAVIATE, WEAVIATE_URL, WEAVIATE_API_KEY, WEAVIATE_COLLECTION
11
 
12
+ # 引用项:本地 VDB 为 {"type": "vdb", "source": "Filename", "page": "1"},Web 为 {"type": "web", "url": "..."}
13
+ RefItem = dict
14
+
15
 
16
  def retrieve_from_weaviate(query: str, top_k: int = 8, timeout_sec: float = 45.0) -> str:
17
  """
 
63
  except concurrent.futures.TimeoutError:
64
  print(f"[weaviate_retrieve] timeout after {timeout_sec}s")
65
  return ""
66
+
67
+
68
+ def retrieve_from_weaviate_with_refs(
69
+ query: str, top_k: int = 8, timeout_sec: float = 45.0
70
+ ) -> Tuple[str, List[RefItem]]:
71
+ """
72
+ 从 Weaviate 检索并返回正文与引用列表。引用用于标注 [Source: Filename/Page]。
73
+ 若 node 无 file_name/page 等元数据,则用 index_name 或 "GenAICourses" 作为 source。
74
+ """
75
+ if not USE_WEAVIATE or not query or len(query.strip()) < 3:
76
+ return "", []
77
+
78
+ def _call() -> Tuple[str, List[RefItem]]:
79
+ try:
80
+ import weaviate
81
+ from weaviate.classes.init import Auth
82
+ from llama_index.core import Settings, VectorStoreIndex
83
+ from llama_index.vector_stores.weaviate import WeaviateVectorStore
84
+ from llama_index.embeddings.huggingface import HuggingFaceEmbedding
85
+
86
+ Settings.embed_model = HuggingFaceEmbedding(
87
+ model_name=os.getenv("HF_EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
88
+ )
89
+ client = weaviate.connect_to_weaviate_cloud(
90
+ cluster_url=WEAVIATE_URL,
91
+ auth_credentials=Auth.api_key(WEAVIATE_API_KEY),
92
+ )
93
+ try:
94
+ if not client.is_ready():
95
+ return "", []
96
+ vs = WeaviateVectorStore(
97
+ weaviate_client=client,
98
+ index_name=WEAVIATE_COLLECTION,
99
+ )
100
+ index = VectorStoreIndex.from_vector_store(vs)
101
+ nodes = index.as_retriever(similarity_top_k=top_k).retrieve(query)
102
+ if not nodes:
103
+ return "", []
104
+ texts = []
105
+ refs: List[RefItem] = []
106
+ seen = set()
107
+ for n in nodes:
108
+ content = n.get_content()
109
+ if isinstance(content, str) and content.strip():
110
+ texts.append(content.strip())
111
+ # NodeWithScore: n.node 或 n 上可能有 metadata
112
+ node = getattr(n, "node", n)
113
+ meta = getattr(node, "metadata", None) or {}
114
+ fname = (meta.get("file_name") or meta.get("source_file") or meta.get("filename") or WEAVIATE_COLLECTION or "GenAICourses").strip()
115
+ page = (meta.get("page_label") or meta.get("page_number") or meta.get("page") or "")
116
+ page_str = str(page).strip() if page else ""
117
+ key = (fname, page_str)
118
+ if key not in seen:
119
+ seen.add(key)
120
+ refs.append({"type": "vdb", "source": fname, "page": page_str})
121
+ return "\n\n---\n\n".join(texts), refs
122
+ finally:
123
+ client.close()
124
+ except ImportError as e:
125
+ print(f"[weaviate_retrieve] 未安装 weaviate/llama_index,跳过 RAG: {e}")
126
+ return "", []
127
+ except Exception as e:
128
+ print(f"[weaviate_retrieve] {repr(e)}")
129
+ return "", []
130
+
131
+ try:
132
+ import concurrent.futures
133
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
134
+ return ex.submit(_call).result(timeout=timeout_sec)
135
+ except concurrent.futures.TimeoutError:
136
+ print(f"[weaviate_retrieve] timeout after {timeout_sec}s")
137
+ return "", []
docs/COURSEWARE_AGENT.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI Teacher Assistant Agent(Courseware)API
2
+
3
+ 将原有教师端能力重构为**模块化 AI Teacher Assistant Agent**,基于「课程描述 + 辅助材料」与 RAG,生成全链路教学资源。所有生成内容均带 **Reference**。
4
+
5
+ ## Reference 规范(技术约束)
6
+
7
+ - **本地 VDB(Weaviate / 上传文件)**:`[Source: Filename/Page]`
8
+ - **Web Search**:`[Source: URL]`
9
+
10
+ 回答末尾包含 References 小节,或正文中按上述格式标注来源。
11
+
12
+ ## 核心模块与 API
13
+
14
+ | 模块 | 路径 | 说明 |
15
+ |------|------|------|
16
+ | Course Vision & Structure Builder | `POST /api/courseware/vision` | 输入课程基本信息 + 教学大纲 → 课程定位、学习目标、层级化知识树 |
17
+ | Activity & Assignment Designer | `POST /api/courseware/activities` | 设计课堂活动、作业及 Rubric;支持从上传资料提取核心知识点(`rag_context_override`) |
18
+ | Teaching Copilot & Student Adaptation | `POST /api/courseware/copilot` | 课堂实时辅助;根据学生画像(Name, Progress, Behavior)动态调整建议 |
19
+ | Course QA Optimizer | `POST /api/courseware/qa-optimize` | 基于 Smart Quiz 答题数据分析弱点,输出教学优化建议 |
20
+ | Content Generator | `POST /api/courseware/content` | 生成 Markdown 详细教案 + 可用于 PPT 的结构化数据 |
21
+
22
+ - 状态:`GET /api/courseware/status`
23
+
24
+ ## 请求/响应示例
25
+
26
+ ### 1. Course Vision
27
+
28
+ ```json
29
+ POST /api/courseware/vision
30
+ {
31
+ "course_info": "IST345 生成式人工智能与负责任创新",
32
+ "syllabus": "Week 1–5 基础;Week 6–10 应用与伦理。"
33
+ }
34
+
35
+ → { "ok": true, "content": "...课程定位、学习目标、知识树...\n\n## References\n[Source: GenAICourses]", "weaviate_used": true }
36
+ ```
37
+
38
+ ### 2. Activities(可传入上传资料摘要)
39
+
40
+ ```json
41
+ POST /api/courseware/activities
42
+ {
43
+ "topic": "RAG 与检索增强",
44
+ "learning_objectives": "理解 embedding、检索流程",
45
+ "rag_context_override": "(可选)从上传文件 RAG 得到的摘要,覆盖 Weaviate 检索"
46
+ }
47
+ ```
48
+
49
+ ### 3. Teaching Copilot
50
+
51
+ ```json
52
+ POST /api/courseware/copilot
53
+ {
54
+ "current_content": "正在讲 Transformer 自注意力",
55
+ "student_profiles": [
56
+ { "name": "张三", "progress": "80%", "behavior": "积极提问" },
57
+ { "name": "李四", "progress": "60%", "behavior": "沉默" }
58
+ ]
59
+ }
60
+ ```
61
+
62
+ ### 4. QA Optimizer
63
+
64
+ ```json
65
+ POST /api/courseware/qa-optimize
66
+ {
67
+ "quiz_summary": "第 3 题正确率 45%,多数混淆了 encoder/decoder",
68
+ "course_topic": "Transformer 与 Seq2Seq"
69
+ }
70
+ ```
71
+
72
+ ### 5. Content(教案 + PPT 数据)
73
+
74
+ ```json
75
+ POST /api/courseware/content
76
+ {
77
+ "topic": "Prompt Engineering",
78
+ "duration": "1 课时",
79
+ "outline_points": "Zero-shot, Few-shot, CoT"
80
+ }
81
+ ```
82
+
83
+ ## 代码结构
84
+
85
+ - `api/courseware/`:模块化实现
86
+ - `prompts.py`:各模块独立 Prompt 模板
87
+ - `references.py`:Reference 格式化(Filename/Page、URL)
88
+ - `rag.py`:RAG 上下文 + 引用(Weaviate + 可选 Web)
89
+ - `vision_builder.py`、`activity_designer.py`、`teaching_copilot.py`、`qa_optimizer.py`、`content_generator.py`
90
+ - `api/weaviate_retrieve.py`:新增 `retrieve_from_weaviate_with_refs()` 返回 `(text, refs)`
91
+ - `api/routes_courseware.py`:Courseware API 路由
92
+
93
+ ## 与教师端原有接口的关系
94
+
95
+ - 原有 `api/teacher/*`(课程描述、文档建议、作业建议、评估分析)保留不变。
96
+ - `GET /api/teacher/status` 增加 `courseware_modules` 与 `courseware_base`,便于前端跳转或聚合展示。