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