Spaces:
Sleeping
Sleeping
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 +18 -0
- api/courseware/activity_designer.py +49 -0
- api/courseware/content_generator.py +48 -0
- api/courseware/prompts.py +96 -0
- api/courseware/qa_optimizer.py +46 -0
- api/courseware/rag.py +47 -0
- api/courseware/references.py +42 -0
- api/courseware/teaching_copilot.py +60 -0
- api/courseware/vision_builder.py +47 -0
- api/routes_courseware.py +170 -0
- api/routes_teacher.py +5 -1
- api/server.py +3 -0
- api/weaviate_retrieve.py +77 -1
- docs/COURSEWARE_AGENT.md +96 -0
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`,便于前端跳转或聚合展示。
|