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"") 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()