File size: 16,121 Bytes
20f7a0a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 |
# modules/knowledge_base/generator.py
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=""):
# Set defaults if not provided
self.subject = subject or "通用学科"
self.instructor = instructor or "教师"
# 基础API配置
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 = []
# 检查是否有图片URL
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', '')
# 构建文档内容,如果有图片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()
# 流式API
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: " 前缀
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)}"
|