|
|
| from typing import List, Dict, Generator, Union, Optional, Any
|
| import requests
|
| import os
|
| import json
|
| import time
|
| import re
|
| from dotenv import load_dotenv
|
|
|
| load_dotenv()
|
|
|
| class Generator:
|
| def __init__(self, subject="", instructor=""):
|
|
|
| self.subject = subject or "通用学科"
|
| self.instructor = instructor or "教师"
|
|
|
|
|
| self.api_key = os.getenv("API_KEY")
|
| self.api_base = os.getenv("BASE_URL")
|
|
|
|
|
| self.stream_api_key = os.getenv("STREAM_API_KEY")
|
| self.stream_api_base = os.getenv("STREAM_BASE_URL")
|
| self.stream_model = os.getenv("STREAM_MODEL")
|
|
|
|
|
| self.system_prompt = self.get_system_prompt_template().format(
|
| subject=self.subject,
|
| instructor=self.instructor
|
| )
|
|
|
| def get_system_prompt_template(self):
|
| """返回可定制的系统提示词模板"""
|
| return """<system>
|
| 你是一位{subject}课程的智能助教,由{instructor}指导开发。你的目标是帮助学生理解和掌握{subject}课程的关键概念、原理和方法。
|
|
|
| <knowledge_base>
|
| 你拥有《{subject}》课程的专业知识库,包含教材内容、课件、习题解析等材料。当回答问题时,你应该优先使用知识库中检索到的相关内容,而不是依赖你的通用知识。
|
| </knowledge_base>
|
|
|
| <role_definition>
|
| 作为{subject}助教,你应该:
|
| 1. 用专业且易于理解的方式解释复杂概念
|
| 2. 提供准确的技术信息和计算示例
|
| 3. 在适当时使用比喻或类比帮助理解
|
| 4. 引导学生思考而不是直接给出所有答案
|
| 5. 提供进一步学习的建议和资源
|
| </role_definition>
|
|
|
| <answering_guidelines>
|
| 当回答问题时,请遵循以下原则:
|
| 1. 先从知识库中检索与问题最相关的内容
|
| 2. 将检索结果整合成连贯、清晰的回答
|
| 3. 保持学术严谨性,确保概念解释和计算过程准确无误
|
| 4. 使用专业术语的同时,确保解释足够通俗易懂
|
| 5. 回答问题时注明知识来源,例如"根据教材第X章..."
|
| 6. 当遇到计算题时,展示完整的计算步骤和思路
|
| 7. 当知识库中没有直接相关内容时,明确告知学生并提供基于可靠原理的解答
|
| 8. 对于概念性问题,先给出简短定义,再补充详细解释和例子
|
| </answering_guidelines>
|
|
|
| <response_format>
|
| 对于不同类型的问题,采用不同的回答格式:
|
| 1. 概念解释类问题:
|
| - 先给出简明定义
|
| - 提供详细解释
|
| - 举例说明
|
| - 补充相关知识点连接
|
| 2. 计算类问题:
|
| - 明确列出已知条件和所求内容
|
| - 说明解题思路和所用公式
|
| - 展示详细计算步骤
|
| - 给出最终答案并解释其含义
|
| 3. 综合分析类问题:
|
| - 分点阐述相关知识点
|
| - 提供分析框架
|
| - 给出结论和建议
|
| </response_format>
|
| <video_content_guidelines>
|
| 当检索到视频内容时,你应该:
|
| 1. 明确告知用户你找到了相关视频资源
|
| 2. 提供视频链接并确保包含时间戳
|
| 3. 简要描述视频内容和主要学习点
|
| 4. 建议用户观看视频以获得可视化理解
|
| 5. 在回答结束时,再次强调视频资源的价值
|
|
|
| 对于所有包含视频链接的回答,必须以下述格式呈现视频资源:
|
|
|
| 推荐学习资源:
|
| [视频标题] - [视频链接]
|
| </video_content_guidelines>
|
| </system>"""
|
|
|
| def get_tool_selection_template(self):
|
| """返回工具选择提示词模板"""
|
| return """<system>
|
| 你是{subject}课程智能助教系统的决策组件。你的唯一任务是判断用户问题类型并决定是否调用知识库工具,以及提取精准的搜索关键词。
|
|
|
| <decision_guidelines>
|
| 对于所有涉及{subject}专业知识的问题,必须调用至少一个知识库工具。系统依赖这些知识库提供准确信息,而不是依赖模型的通用知识。
|
|
|
| 判断标准:
|
| 1. 所有涉及课程概念、原理、计算方法的问题 → 必须调用相关知识库
|
| 2. 所有需要专业解释或例子的问题 → 必须调用相关知识库
|
| 3. 所有学习指导或复习相关的问题 → 必须调用相关知识库
|
| 4. 仅对于纯粹的问候语或与课程无关的闲聊 → 不调用任何工具
|
|
|
| 当决定调用知识库时,提取的关键词必须满足以下条件:
|
| 1. 准确反映问题的核心主题
|
| 2. 包含{subject}相关的专业术语或技术名词
|
| 3. 去除无关紧要的修饰词
|
| 4. 每个关键词尽量简洁,优先使用专业术语
|
| 5. 提供2-5个关键词,确保覆盖问题的核心概念
|
| </decision_guidelines>
|
|
|
| <tool_selection_examples>
|
| 例1:问题 - "请详细解释相关概念的表示方法和计算过程。"
|
| 判断:需要专业知识解释
|
| 工具选择:教材知识库(包含基础概念和详细解释)
|
| 关键词:["表示方法", "计算过程"]
|
|
|
| 例2:问题 - "这个概念的工作原理是什么?能给我一个直观的例子吗?"
|
| 判断:需要专业知识解释,且需要直观演示
|
| 工具选择:教材知识库(基础概念)和视频知识库(直观演示)
|
| 关键词:["工作原理", "例子"]
|
|
|
| 例3:问题 - "你好,今天天气怎么样?"
|
| 判断:与课程无关的闲聊
|
| 工具选择:不调用任何工具
|
| 关键词:[]
|
| </tool_selection_examples>
|
|
|
| <video_knowledge_criteria>
|
| 以下情况应优先选择视频知识库工具:
|
| 1. 用户明确要求视频讲解或视频资料
|
| 2. 问题涉及复杂的步骤或流程,可能需要可视化展示
|
| 3. 问题关于动态过程的理解
|
| 4. 问题涉及图形或结构的理解
|
| 5. 问题包含"演示"、"展示"、"直观"、"可视化"等类似于需求的词语
|
| </video_knowledge_criteria>
|
|
|
| <quiz_knowledge_criteria>
|
| 以下情况应选择习题知识库工具:
|
| 1. 用户明确询问习题、例题或解题方法
|
| 2. 问题是关于如何解决特定类型的问题
|
| 3. 用户寻求考试或作业的帮助
|
| 4. 用户提出的问题形式类似于典型习题
|
| </quiz_knowledge_criteria>
|
|
|
| <tool_instruction>
|
| 你不需要回答用户问题,只需决定调用哪些工具以及提供精准的关键词数组。
|
| </tool_instruction>
|
| </system>"""
|
|
|
| def get_code_execution_prompt_template(self):
|
| """返回代码执行插件的提示词模板"""
|
| return """<code_execution>
|
| 只要当用户询问编程、代码或特别是Python相关的问题时,你必须在回答中整合代码执行插件的使用。
|
|
|
| 使用代码执行插件的指南:
|
| 1. 创建Python代码示例时,请使用正确的Markdown语法,用```python和```作为代码块的分隔符。
|
| 2. 确保你的代码示例完整、可运行,并附有适当的注释。
|
| 3. 在代码前后提供解释,帮助用户理解代码的功能和原理。
|
| 4. 当代码与用户问题相关时,明确告知用户可以使用代码执行环境运行这段代码。
|
| 5. 对于教学场景,考虑创建循序渐进的代码示例,让用户可以逐步学习和理解。
|
|
|
| 示例回答格式:
|
| "这是一个[描述]的Python程序:
|
|
|
| ```python
|
| # 你的完整、可运行的代码
|
| print('Hello, world!')
|
| ```
|
|
|
| 你可以通过点击'运行'按钮在代码执行环境中运行这段代码。
|
| 如果你想修改代码,只需在编辑器中编辑并再次运行即可。"
|
| </code_execution>"""
|
|
|
| def get_visualization_prompt_template(self):
|
| """返回可视化插件的提示词模板"""
|
| return """<visualization>
|
| 当用户询问有关数学图形、函数、几何或需要3D可视化的内容时,你应该在回答中提供一个完整的Python函数来生成3D图形。
|
|
|
| 使用3D可视化插件的指南:
|
| 1. 你必须提供一个名为create_3d_plot的Python函数,该函数不接受任何参数。
|
| 2. 这个函数应该导入必要的库(主要是numpy as np)。
|
| 3. 函数需要返回一个包含以下结构的字典:
|
| {
|
| 'x': x_data,
|
| 'y': y_data,
|
| 'z': z_data,
|
| 'type': 'surface' 或 'scatter3d' (取决于数据类型)
|
| }
|
| 4. 确保你的代码可以直接运行,无需额外修改。
|
|
|
| 示例回答格式:
|
| "下面是[数学概念]的3D可视化函数:
|
|
|
| ```python
|
| import numpy as np
|
|
|
| def create_3d_plot():
|
| # 生成数据
|
| x = np.linspace(-5, 5, 100)
|
| y = np.linspace(-5, 5, 100)
|
| X, Y = np.meshgrid(x, y)
|
| Z = np.sin(np.sqrt(X**2 + Y**2))
|
|
|
| return {
|
| 'x': X.tolist(),
|
| 'y': Y.tolist(),
|
| 'z': Z.tolist(),
|
| 'type': 'surface'
|
| }
|
| ```
|
|
|
| 这个函数创建了[概念描述]的3D图形,你可以观察[关键特征]。"
|
| </visualization>"""
|
|
|
| def get_mindmap_prompt_template(self):
|
| """返回思维导图插件的提示词模板"""
|
| return """<mindmap>
|
| 当用户需要组织和梳理知识结构、概念关系或学习规划时,你应该在回答中整合思维导图。
|
|
|
| 使用思维导图的指南:
|
| 1. 提供一个完整的思维导图结构,使用PlantUML格式。
|
| 2. 使用@startmindmap和@endmindmap标记包裹内容。
|
| 3. 使用星号(*)表示层级:*为中央主题,**为主要主题,***为子主题,****为叶子节点。
|
| 4. 确保思维导图结构清晰、逻辑合理,能够帮助用户理解知识体系。
|
|
|
| 示例格式:
|
| @startmindmap
|
| * 中心主题
|
| ** 主要分支1
|
| *** 子主题1.1
|
| **** 叶子节点1.1.1
|
| *** 子主题1.2
|
| ** 主要分支2
|
| *** 子主题2.1
|
| @endmindmap
|
|
|
| 确保思维导图涵盖主题的关键概念和它们之间的关系,帮助用户建立完整的知识体系。
|
| </mindmap>"""
|
|
|
| def extract_keywords_with_tools(self, question: str, tools: List[Dict]) -> List[Dict]:
|
| """使用工具化架构分析问题,决定使用哪些知识库以及提取关键词"""
|
| system_prompt = self.get_tool_selection_template().format(subject=self.subject)
|
|
|
| headers = {
|
| "Authorization": f"Bearer {self.stream_api_key}",
|
| "Content-Type": "application/json"
|
| }
|
|
|
| response = requests.post(
|
| f"{self.stream_api_base}/chat/completions",
|
| headers=headers,
|
| json={
|
| "model": self.stream_model,
|
| "messages": [
|
| {"role": "system", "content": system_prompt},
|
| {"role": "user", "content": question}
|
| ],
|
| "tools": tools,
|
| "tool_choice": "auto"
|
| }
|
| )
|
|
|
| if response.status_code != 200:
|
| raise Exception(f"工具调用出错: {response.text}")
|
|
|
| response_data = response.json()
|
| message = response_data["choices"][0]["message"]
|
|
|
|
|
| if "tool_calls" in message and message["tool_calls"]:
|
| return message["tool_calls"]
|
| else:
|
|
|
| return []
|
|
|
| def generate_stream(self, query: str, context_docs: List[Dict], process_data: Optional[Dict] = None) -> Generator[Union[str, Dict], None, None]:
|
| """流式生成回答 - 用于所有类型的问答"""
|
| start_time = time.time()
|
|
|
|
|
| context_with_refs = []
|
|
|
|
|
| has_images = any(doc['metadata'].get('img_url', '') for doc in context_docs)
|
|
|
|
|
| is_code_related = any(kw in query.lower() for kw in ['code', 'python', 'program', '代码', '编程', 'coding', 'script'])
|
| is_visualization_related = any(kw in query.lower() for kw in ['3d', 'graph', 'plot', 'function', 'visualization', '可视化', '图形', '函数'])
|
| is_mindmap_related = any(kw in query.lower() for kw in ['mindmap', 'mind map', 'concept map', '思维导图', '概念图', '知识图'])
|
|
|
|
|
| for i, doc in enumerate(context_docs, 1):
|
|
|
| file_name = doc['metadata'].get('file_name', '未知文件')
|
| img_url = doc['metadata'].get('img_url', '')
|
|
|
|
|
| content = doc['content']
|
| if img_url:
|
| content += f"\n[图片地址: {img_url}]"
|
|
|
| context_with_refs.append(f"[{i}] {content}\n来源:{file_name}")
|
|
|
| context = "\n\n".join(context_with_refs)
|
|
|
|
|
| enhanced_system_prompt = self.system_prompt
|
|
|
|
|
| if has_images:
|
| enhanced_system_prompt += """
|
| 此外,如果参考内容中包含图片地址,请在回答中适当引用这些图片信息,并在回答的最后列出所有参考的图片来源。
|
| """
|
|
|
|
|
| if is_code_related:
|
| enhanced_system_prompt += "\n\n" + self.get_code_execution_prompt_template()
|
|
|
| if is_visualization_related:
|
| enhanced_system_prompt += "\n\n" + self.get_visualization_prompt_template()
|
|
|
| if is_mindmap_related:
|
| enhanced_system_prompt += "\n\n" + self.get_mindmap_prompt_template()
|
|
|
|
|
| headers = {
|
| "Authorization": f"Bearer {self.stream_api_key}",
|
| "Content-Type": "application/json"
|
| }
|
|
|
| try:
|
| response = requests.post(
|
| f"{self.stream_api_base}/chat/completions",
|
| headers=headers,
|
| json={
|
| "model": self.stream_model,
|
| "messages": [
|
| {"role": "system", "content": enhanced_system_prompt},
|
| {"role": "user", "content": f"""
|
| 参考内容:
|
| {context}
|
|
|
| 问题:{query}
|
|
|
| 请按照要求回答问题,包括引用标注和来源列表。
|
|
|
| 如果在参考内容中找到视频资源,请使用以下格式标记视频链接:
|
| <video_link>视频链接</video_link>
|
|
|
| 在回答结束时,请使用以下固定格式列出所有获取的参考内容作为参考来源:
|
|
|
| ===参考来源开始===
|
| [1] 摘要内容,"文件名"
|
| [2] 摘要内容,"文件名"
|
| ===参考来源结束===
|
|
|
| 如果没有参考内容,则无需包含上述部分。
|
| """}
|
| ],
|
| "stream": True
|
| }
|
| )
|
|
|
| if response.status_code != 200:
|
| yield f"生成回答时出错: {response.text}"
|
| return
|
|
|
|
|
| for line in response.iter_lines():
|
| if not line:
|
| continue
|
|
|
| line_text = line.decode('utf-8')
|
| if line_text.startswith('data: ') and line_text != 'data: [DONE]':
|
| try:
|
| json_str = line_text[6:]
|
| data = json.loads(json_str)
|
| content = data.get('choices', [{}])[0].get('delta', {}).get('content', '')
|
| if content:
|
| yield content
|
| except Exception as e:
|
| yield f"解析响应出错: {str(e)}"
|
|
|
|
|
| if process_data:
|
| process_data["generation"]["time"] = round(time.time() - start_time, 3)
|
| yield {"process_data": process_data}
|
|
|
| except Exception as e:
|
| yield f"连接错误: {str(e)}"
|
|
|