spadezli's picture
Update app.py
9129fdb verified
import os
import gradio as gr
import dashscope
from dashscope import Generation
from http import HTTPStatus
# ====== 1. 配置区域 ======
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY", "")
MODEL_TUTOR = "qwen-turbo"
MODEL_PROFESSOR = "qwen-plus"
TEXTBOOK_PATH = "textbook.txt"
# ====== 2. 教材加载 ======
def load_textbook(path=TEXTBOOK_PATH, max_chars=8000):
if not os.path.exists(path):
return "(未找到教材文件,系统将基于通用知识运行,严谨性可能会下降)"
try:
with open(path, "r", encoding="utf-8") as f:
text = f.read()
except UnicodeDecodeError:
with open(path, "r", encoding="gbk") as f:
text = f.read()
if len(text) > max_chars:
text = text[:max_chars] + "\n...(教材内容过长,已截断)"
return text
TEXTBOOK_CONTENT = load_textbook()
# ====== 3. 模型调用 ======
def call_llm(messages, model_name):
resp = Generation.call(model=model_name, messages=messages, result_format="message")
if resp.status_code == HTTPStatus.OK:
return resp.output["choices"][0]["message"]["content"].strip()
return f"API Error: {resp.code} - {resp.message}"
# ====== 4. Agent 逻辑(完全保留) ======
def answer_agent(question):
"""
生成第一版答案:直接回答 + 详细讲解 + 教材例子
"""
system_prompt = f"""你是一个专业、耐心、循循善诱的教学助手,专门帮助学生理解教材内容。
## 你的角色和任务:
你是问题回答者智能体,负责生成第一版答案,是整个系统的主要输出来源。
你必须严格基于提供的教材内容进行回答,不得编造教材外的知识。
## 教材内容参考:
{TEXTBOOK_CONTENT}
## 核心能力要求:
1. **生成解答**:首先直接、清晰地回答学生的问题
2. **讲解内容**:分点展开,详细解释相关概念和原理
3. **举例说明**:必须基于上述教材内容进行举例说明
4. **追问学生**:最后提出一个相关的思考问题引导学生进一步思考
## 回答结构建议:
### 直接解答
先用简洁明了的语言回答问题的核心
### 详细讲解
- 将复杂概念分解成易于理解的要点
- 使用分点方式展开说明
- 确保逻辑清晰,层次分明
### 教材示例
明确引用教材中的具体内容,可以使用"如教材中所述..."或"教材中提到..."等表述
### 启发思考
提出一个与问题相关的思考题,帮助学生深化理解
## 语言风格:
- 面向初学者,语言亲切易懂
- 温柔指导式,避免过于学术化
- 使用鼓励性语言,激发学习兴趣
- 保持耐心和教学友好度
## 重要提醒:
- 所有回答必须严格基于提供的教材内容
- 如果教材中没有相关内容,请如实告知学生
- 确保回答的准确性和教学价值
- 你的目标是帮助学生真正理解概念,而不仅仅是给出答案"""
user_prompt = f"""请基于教材内容回答以下问题:
问题:{question}
请按照以下要求组织回答:
1. 首先给出直接答案
2. 然后进行分点详细讲解
3. 必须基于教材内容举例说明
4. 最后提出一个启发性的思考问题
5. 使用面向初学者的友好教学语言
请开始你的回答:"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
return call_llm(messages, MODEL_TUTOR)
def checker_agent(question, draft):
"""
审查助教的回答:检查概念准确性、完整性、严谨性
"""
# === 分支 A:初稿为空或过短 ===
if not draft or len(draft.strip()) < 50:
system_prompt = f"""
你是一名严谨负责的AI课程助教,核心任务是对照教材审查回答者的初稿质量。
当前初稿为空或内容过短,无法满足回答要求,请按以下框架反馈:
### 检查依据(唯一参考,不得脱离)
{TEXTBOOK_CONTENT}
### 输出要求(结构化呈现)
1. 【学生问题复盘】:简要重复学生问题(确保理解核心诉求)
2. 【初稿质量评估】:
- 优点:无(初稿未满足基础长度要求)
- 不足:初稿为空或内容过短(少于50字),未覆盖“直接回答+详细讲解+教材例子”的基础结构,无法判断知识点准确性
3. 【针对性修改建议】:
- 第一步:先给出“直接回答”(用1-2句话概括问题结论,基于教材)
- 第二步:补充“详细讲解”(分点解释核心概念,如定义、分类等,对应教材内容)
- 第三步:添加“教材例子”(引用教材中的具体案例)
4. 【修订后的参考答案】:按上述三步,基于教材完整撰写回答,格式清晰
5. 【学生水平预判】:未形成有效回答,暂无法判断,建议先掌握“基础回答结构+教材核心概念”
### 注意事项
- 修订答案需严格基于教材,不添加额外扩展内容
- 语气友好,体现助教的指导属性
"""
# === 分支 B:正常审查 ===
else:
system_prompt = f"""
你是一名严谨负责的AI课程教授,核心任务是对照教材,全面审查回答者的初稿质量,确保内容准确、完整、贴合教材。
### 检查依据(唯一参考,不得脱离)
{TEXTBOOK_CONTENT}
### 检查维度(必须逐一覆盖,不遗漏,每个维度需对应教材依据)
1. 概念准确性:是否存在与教材冲突的错误?需引用教材原文对比。
2. 内容完整性:是否遗漏教材中与问题相关的核心知识点?
3. 表达严谨性:是否有模糊/歧义表述?
4. 扩展合理性:若初稿有教材外内容,是否标注“扩展补充”且不超过总内容的20%?
5. 格式规范性:是否符合“直接回答 + 详细讲解 + 教材相关例子”三部分?
### 输出要求(结构化呈现,清晰易懂,语言专业且友好)
请严格按以下框架组织反馈,每个部分需具体、可落地:
1. 【学生问题复盘】:1-2句话重复学生问题。
2. 【初稿质量评估】:
- 优点:分点列出初稿的亮点。
- 不足:分点列出具体问题,每个问题需包含「初稿原文」+「教材依据」。
3. 【针对性修改建议】:对每个“不足”给出唯一对应的修改方案。
4. 【修订后的参考答案】:
- 保留初稿中正确的部分,仅修正错误、补充遗漏。
- 严格按“直接回答+详细讲解+教材例子”结构撰写。
- 格式清晰:可用小标题分点。
5. 【学生水平预判】:基于初稿质量判断(如“基础概念掌握准确,但细节记忆偏差”)。
### 注意事项
- 若初稿完全符合教材且无任何问题:“不足”写“无”,“修改建议”写“无需修改”。
- 修订答案不得添加教材外知识(除非是通俗化解释)。
- 反馈语气像助教指导学生,避免生硬批评。
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"学生问题:{question}\n\n回答者初稿:\n{draft}"}
]
return call_llm(messages, MODEL_PROFESSOR)
# ====== 5. 对话式交互逻辑(核心修改点) ======
def run_chat(question, history):
if not question:
return history, history
history += f"\n\n---\n\n👤 **学生:** {question}\n"
tutor_answer = answer_agent(question)
history += f"\n🤖 **实习助教:**\n{tutor_answer}\n"
professor_answer = checker_agent(question, tutor_answer)
history += f"\n👨‍🏫 **严谨教授:**\n{professor_answer}\n"
return history, history
# ====== 6. UI ======
APPLE_UI_CSS = """
body { background: #F5F5F7; font-family: -apple-system; }
.card { background: white; border-radius: 16px; padding: 24px; margin-bottom: 20px; }
.output-card { height: 600px; overflow-y: auto; }
"""
with gr.Blocks(title="双师课堂 AI") as demo:
gr.Markdown(f"<style>{APPLE_UI_CSS}</style>")
gr.Markdown("# 🎓 双师课堂:对话式教学问答")
chat_history = gr.State("")
with gr.Column(elem_classes=["card"]):
inp = gr.Textbox(
placeholder="请输入你的问题,例如:什么是加速度?",
show_label=False
)
btn = gr.Button("发送")
with gr.Column(elem_classes=["card", "output-card"]):
chat_display = gr.Markdown("等待提问...")
btn.click(
fn=run_chat,
inputs=[inp, chat_history],
outputs=[chat_display, chat_history]
)
inp.submit(
fn=run_chat,
inputs=[inp, chat_history],
outputs=[chat_display, chat_history]
)
if __name__ == "__main__":
demo.launch()