samlax12 commited on
Commit
4b8af40
·
verified ·
1 Parent(s): 3855701

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +517 -517
app.py CHANGED
@@ -1,517 +1,517 @@
1
- from flask import Flask, request, jsonify, send_from_directory, render_template, redirect, url_for
2
- from flask_cors import CORS
3
- import os
4
- import time
5
- import traceback
6
- import json
7
- import re
8
- import sys
9
- import io
10
- import threading
11
- import queue
12
- import contextlib
13
- import signal
14
- import psutil
15
- from dotenv import load_dotenv
16
-
17
- # 导入模块路由
18
- from modules.knowledge_base.routes import knowledge_bp
19
- from modules.code_executor.routes import code_executor_bp
20
- from modules.visualization.routes import visualization_bp
21
- from modules.agent_builder.routes import agent_builder_bp
22
-
23
- # 加载环境变量
24
- load_dotenv()
25
-
26
- app = Flask(__name__)
27
- CORS(app)
28
-
29
- # 注册蓝图
30
- app.register_blueprint(knowledge_bp, url_prefix='/api/knowledge')
31
- app.register_blueprint(code_executor_bp, url_prefix='/api/code')
32
- app.register_blueprint(visualization_bp, url_prefix='/api/visualization')
33
- app.register_blueprint(agent_builder_bp, url_prefix='/api/agent')
34
-
35
- # 确保目录存在
36
- os.makedirs('static', exist_ok=True)
37
- os.makedirs('uploads', exist_ok=True)
38
- os.makedirs('agents', exist_ok=True)
39
-
40
- # 用于代码执行的上下文
41
- execution_contexts = {}
42
-
43
- def get_memory_usage():
44
- """获取当前进程的内存使用情况"""
45
- process = psutil.Process(os.getpid())
46
- return f"{process.memory_info().rss / 1024 / 1024:.1f} MB"
47
-
48
- class CustomStdin:
49
- def __init__(self, input_queue):
50
- self.input_queue = input_queue
51
- self.buffer = ""
52
-
53
- def readline(self):
54
- if not self.buffer:
55
- self.buffer = self.input_queue.get() + "\n"
56
-
57
- result = self.buffer
58
- self.buffer = ""
59
- return result
60
-
61
- class InteractiveExecution:
62
- """管理Python代码的交互式执行"""
63
- def __init__(self, code):
64
- self.code = code
65
- self.context_id = str(time.time())
66
- self.is_complete = False
67
- self.is_waiting_for_input = False
68
- self.stdout_buffer = io.StringIO()
69
- self.last_read_position = 0
70
- self.input_queue = queue.Queue()
71
- self.error = None
72
- self.thread = None
73
- self.should_terminate = False
74
-
75
- def run(self):
76
- """在单独的线程中启动执行"""
77
- self.thread = threading.Thread(target=self._execute)
78
- self.thread.daemon = True
79
- self.thread.start()
80
-
81
- # 给执行一点时间开始
82
- time.sleep(0.1)
83
- return self.context_id
84
-
85
- def _execute(self):
86
- """执行代码,处理标准输入输出"""
87
- try:
88
- # 保存原始的stdin/stdout
89
- orig_stdin = sys.stdin
90
- orig_stdout = sys.stdout
91
-
92
- # 创建自定义stdin
93
- custom_stdin = CustomStdin(self.input_queue)
94
-
95
- # 重定向stdin和stdout
96
- sys.stdin = custom_stdin
97
- sys.stdout = self.stdout_buffer
98
-
99
- try:
100
- # 检查终止的函数
101
- self._last_check_time = 0
102
-
103
- def check_termination():
104
- if self.should_terminate:
105
- raise KeyboardInterrupt("Execution terminated by user")
106
-
107
- # 设置一个模拟__main__模块的命名空间
108
- shared_namespace = {
109
- "__builtins__": __builtins__,
110
- "_check_termination": check_termination,
111
- "time": time,
112
- "__name__": "__main__"
113
- }
114
-
115
- # 在这个命名空间中执行用户代码
116
- try:
117
- exec(self.code, shared_namespace)
118
- except KeyboardInterrupt:
119
- print("\nExecution terminated by user")
120
-
121
- except Exception as e:
122
- self.error = {
123
- "error": str(e),
124
- "traceback": traceback.format_exc()
125
- }
126
-
127
- finally:
128
- # 恢复原始stdin/stdout
129
- sys.stdin = orig_stdin
130
- sys.stdout = orig_stdout
131
-
132
- # 标记执行完成
133
- self.is_complete = True
134
-
135
- except Exception as e:
136
- self.error = {
137
- "error": str(e),
138
- "traceback": traceback.format_exc()
139
- }
140
- self.is_complete = True
141
-
142
- def terminate(self):
143
- """终止执行"""
144
- self.should_terminate = True
145
-
146
- # 如果在等待输入,放入一些内容以解除阻塞
147
- if self.is_waiting_for_input:
148
- self.input_queue.put("\n")
149
-
150
- # 给执行一点时间终止
151
- time.sleep(0.2)
152
-
153
- # 标记为完成
154
- self.is_complete = True
155
-
156
- return True
157
-
158
- def provide_input(self, user_input):
159
- """为运行的代码提供输入"""
160
- self.input_queue.put(user_input)
161
- self.is_waiting_for_input = False
162
- return True
163
-
164
- def get_output(self):
165
- """获取stdout缓冲区的当前内容"""
166
- output = self.stdout_buffer.getvalue()
167
- return output
168
-
169
- def get_new_output(self):
170
- """只获取自上次读取以来的新输出"""
171
- current_value = self.stdout_buffer.getvalue()
172
- if self.last_read_position < len(current_value):
173
- new_output = current_value[self.last_read_position:]
174
- self.last_read_position = len(current_value)
175
- return new_output
176
- return ""
177
-
178
- @app.route('/')
179
- def index():
180
- """主界面"""
181
- return render_template('index.html')
182
- @app.route('/code_execution.html')
183
- def index2():
184
- """主界面"""
185
- return render_template('code_execution.html')
186
- @app.route('/api/progress/<task_id>', methods=['GET'])
187
- def get_progress(task_id):
188
- """获取文档处理进度"""
189
- try:
190
- # 从知识库模块访问处理任务
191
- from modules.knowledge_base.routes import processing_tasks
192
-
193
- progress_data = processing_tasks.get(task_id, {
194
- 'progress': 0,
195
- 'status': '未找到任务',
196
- 'error': True
197
- })
198
-
199
- return jsonify({"success": True, "data": progress_data})
200
- except Exception as e:
201
- traceback.print_exc()
202
- return jsonify({"success": False, "message": str(e)}), 500
203
-
204
- @app.route('/student/<agent_id>')
205
- def student_view(agent_id):
206
- """学生访问Agent界面"""
207
- token = request.args.get('token', '')
208
-
209
- # 验证Agent存在
210
- agent_path = os.path.join('agents', f"{agent_id}.json")
211
- if not os.path.exists(agent_path):
212
- return render_template('error.html',
213
- message="找不到指定的Agent",
214
- error_code=404)
215
-
216
- # 加载Agent配置
217
- with open(agent_path, 'r', encoding='utf-8') as f:
218
- try:
219
- agent_config = json.load(f)
220
- except:
221
- return render_template('error.html',
222
- message="Agent配置无效",
223
- error_code=500)
224
-
225
- # 验证访问令牌
226
- if token:
227
- valid_token = False
228
- if "distributions" in agent_config:
229
- for dist in agent_config["distributions"]:
230
- if dist.get("token") == token:
231
- valid_token = True
232
- break
233
-
234
- if not valid_token:
235
- return render_template('error.html',
236
- message="访问令牌无效",
237
- error_code=403)
238
-
239
- # 渲染学生页面
240
- return render_template('student.html',
241
- agent_id=agent_id,
242
- agent_name=agent_config.get('name', 'AI学习助手'),
243
- agent_description=agent_config.get('description', ''),
244
- token=token)
245
-
246
- @app.route('/code_execution.html')
247
- def code_execution_page():
248
- """代码执行页面"""
249
- return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'code_execution.html')
250
-
251
- @app.route('/api/student/chat/<agent_id>', methods=['POST'])
252
- def student_chat(agent_id):
253
- """学生与Agent聊天的API"""
254
- try:
255
- data = request.json
256
- message = data.get('message', '')
257
- token = data.get('token', '')
258
-
259
- if not message:
260
- return jsonify({"success": False, "message": "消息不能为空"}), 400
261
-
262
- # 验证Agent和令牌
263
- agent_path = os.path.join('agents', f"{agent_id}.json")
264
- if not os.path.exists(agent_path):
265
- return jsonify({"success": False, "message": "Agent不存在"}), 404
266
-
267
- with open(agent_path, 'r', encoding='utf-8') as f:
268
- agent_config = json.load(f)
269
-
270
- # 验证令牌(如果提供)
271
- if token and "distributions" in agent_config:
272
- valid_token = False
273
- for dist in agent_config["distributions"]:
274
- if dist.get("token") == token:
275
- valid_token = True
276
-
277
- # 更新使用计数
278
- dist["usage_count"] = dist.get("usage_count", 0) + 1
279
- break
280
-
281
- if not valid_token:
282
- return jsonify({"success": False, "message": "访问令牌无效"}), 403
283
-
284
- # 更新Agent使用统计
285
- if "stats" not in agent_config:
286
- agent_config["stats"] = {}
287
-
288
- agent_config["stats"]["usage_count"] = agent_config["stats"].get("usage_count", 0) + 1
289
- agent_config["stats"]["last_used"] = int(time.time())
290
-
291
- # 保存更新��的Agent配置
292
- with open(agent_path, 'w', encoding='utf-8') as f:
293
- json.dump(agent_config, f, ensure_ascii=False, indent=2)
294
-
295
- # 获取Agent关联的知识库和插件
296
- knowledge_bases = agent_config.get('knowledge_bases', [])
297
- plugins = agent_config.get('plugins', [])
298
-
299
- # 获取学科和指导者信息
300
- subject = agent_config.get('subject', agent_config.get('name', '通用学科'))
301
- instructor = agent_config.get('instructor', '教师')
302
-
303
- # 创建Generator实例,传入学科和指导者信息
304
- from modules.knowledge_base.generator import Generator
305
- generator = Generator(subject=subject, instructor=instructor)
306
-
307
- # 检测需要使用的插件
308
- suggested_plugins = []
309
-
310
- # 检测是否需要代码执行插件
311
- if 'code' in plugins and ('代码' in message or 'python' in message.lower() or '编程' in message or 'code' in message.lower() or 'program' in message.lower()):
312
- suggested_plugins.append('code')
313
-
314
- # 检测是否需要3D可视化插件
315
- if 'visualization' in plugins and ('3d' in message.lower() or '可视化' in message or '图形' in message):
316
- suggested_plugins.append('visualization')
317
-
318
- # 检测是否需要思维导图插件
319
- if 'mindmap' in plugins and ('思维导图' in message or 'mindmap' in message.lower()):
320
- suggested_plugins.append('mindmap')
321
-
322
- # 检查是否有配置知识库
323
- if not knowledge_bases:
324
- # 没有知识库,直接使用模型进行回答
325
- print(f"\n=== 处理查询: {message} (无知识库) ===")
326
-
327
- # 使用空的文档列表调用生成器进行回答
328
- final_response = ""
329
- for chunk in generator.generate_stream(message, []):
330
- if isinstance(chunk, dict):
331
- continue # 跳过处理数据
332
- final_response += chunk
333
-
334
- # 返回生成的回答
335
- return jsonify({
336
- "success": True,
337
- "message": final_response,
338
- "tools": suggested_plugins
339
- })
340
-
341
- # 有知识库配置,执行知识库查询流程
342
- try:
343
- # 导入RAG系统组件
344
- from modules.knowledge_base.retriever import Retriever
345
- from modules.knowledge_base.reranker import Reranker
346
-
347
- retriever = Retriever()
348
- reranker = Reranker()
349
-
350
- # 构建工具定义 - 将所有知识库作为工具
351
- tools = []
352
-
353
- # 创建工具名称到索引的映射
354
- tool_to_index = {}
355
-
356
- for i, index in enumerate(knowledge_bases):
357
- display_name = index[4:] if index.startswith('rag_') else index
358
-
359
- # 判断是否是视频知识库
360
- is_video = "视频" in display_name or "video" in display_name.lower()
361
-
362
- # 根据内容类型生成适当的工具名称
363
- if is_video:
364
- tool_name = f"video_knowledge_base_{i+1}"
365
- description = f"在'{display_name}'视频知识库中搜索,返回带时间戳的视频链接。适用于需要视频讲解的问题。"
366
- else:
367
- tool_name = f"knowledge_base_{i+1}"
368
- description = f"在'{display_name}'知识库中搜索专业知识、概念和原理。适用于需要文本说明的问题。"
369
-
370
- # 添加工具名到索引的映射
371
- tool_to_index[tool_name] = index
372
-
373
- tools.append({
374
- "type": "function",
375
- "function": {
376
- "name": tool_name,
377
- "description": description,
378
- "parameters": {
379
- "type": "object",
380
- "properties": {
381
- "keywords": {
382
- "type": "array",
383
- "items": {"type": "string"},
384
- "description": "搜索的关键词列表"
385
- }
386
- },
387
- "required": ["keywords"],
388
- "additionalProperties": False
389
- },
390
- "strict": True
391
- }
392
- })
393
-
394
- # 第一阶段:工具选择决策
395
- print(f"\n=== 处理查询: {message} ===")
396
- tool_calls = generator.extract_keywords_with_tools(message, tools)
397
-
398
- # 如果不需要调用工具,直接回答
399
- if not tool_calls:
400
- print("未检测到需要使用知识库,直接回答")
401
- final_response = ""
402
- for chunk in generator.generate_stream(message, []):
403
- if isinstance(chunk, dict):
404
- continue # 跳过处理数据
405
- final_response += chunk
406
-
407
- return jsonify({
408
- "success": True,
409
- "message": final_response,
410
- "tools": suggested_plugins
411
- })
412
-
413
- # 收集来自工具执行的所有文档
414
- all_docs = []
415
-
416
- # 执行每个工具调用
417
- for tool_call in tool_calls:
418
- try:
419
- tool_name = tool_call["function"]["name"]
420
- actual_index = tool_to_index.get(tool_name)
421
-
422
- if not actual_index:
423
- print(f"找不到工具名称 '{tool_name}' 对应的索引")
424
- continue
425
-
426
- print(f"\n执行工具 '{tool_name}' -> 使用索引 '{actual_index}'")
427
-
428
- arguments = json.loads(tool_call["function"]["arguments"])
429
- keywords = " ".join(arguments.get("keywords", []))
430
-
431
- if not keywords:
432
- print("没有提供关键词,跳过检索")
433
- continue
434
-
435
- print(f"检索关键词: {keywords}")
436
-
437
- # 执行检索
438
- retrieved_docs, _ = retriever.retrieve(keywords, specific_index=actual_index)
439
- print(f"检索到 {len(retrieved_docs)} 个文档")
440
-
441
- # 重排序文档
442
- reranked_docs = reranker.rerank(message, retrieved_docs, actual_index)
443
- print(f"重排序完成,排序后有 {len(reranked_docs)} 个文档")
444
-
445
- # 添加结果
446
- all_docs.extend(reranked_docs)
447
-
448
- except Exception as e:
449
- print(f"执行工具 '{tool_call.get('function', {}).get('name', '未知')}' 调用时出错: {str(e)}")
450
- import traceback
451
- traceback.print_exc()
452
-
453
- # 如果没有检索到任何文档,直接回答
454
- if not all_docs:
455
- print("未检索到任何相关文档,直接回答")
456
- final_response = ""
457
- for chunk in generator.generate_stream(message, []):
458
- if isinstance(chunk, dict):
459
- continue # 跳过处理数据
460
- final_response += chunk
461
-
462
- return jsonify({
463
- "success": True,
464
- "message": final_response,
465
- "tools": suggested_plugins
466
- })
467
-
468
- # 按相关性排序
469
- all_docs.sort(key=lambda x: x.get('rerank_score', 0), reverse=True)
470
- print(f"\n最终收集到 {len(all_docs)} 个文档用于生成回答")
471
-
472
- # 提取参考信息
473
- references = []
474
- for i, doc in enumerate(all_docs[:3], 1): # 只展示前3个参考来源
475
- file_name = doc['metadata'].get('file_name', '未知文件')
476
- content = doc['content']
477
-
478
- # 提取大约前100字符作为摘要
479
- summary = content[:100] + ('...' if len(content) > 100 else '')
480
-
481
- references.append({
482
- 'index': i,
483
- 'file_name': file_name,
484
- 'content': content,
485
- 'summary': summary
486
- })
487
-
488
- # 第二阶段:生成最终答案
489
- final_response = ""
490
- for chunk in generator.generate_stream(message, all_docs):
491
- if isinstance(chunk, dict):
492
- continue # 跳过处理数据
493
- final_response += chunk
494
-
495
- # 构建回复
496
- return jsonify({
497
- "success": True,
498
- "message": final_response,
499
- "tools": suggested_plugins,
500
- "references": references
501
- })
502
-
503
- except Exception as e:
504
- import traceback
505
- traceback.print_exc()
506
- return jsonify({
507
- "success": False,
508
- "message": f"处理查询时出错: {str(e)}"
509
- }), 500
510
-
511
- except Exception as e:
512
- import traceback
513
- traceback.print_exc()
514
- return jsonify({"success": False, "message": str(e)}), 500
515
-
516
- if __name__ == '__main__':
517
- app.run(debug=True, host='0.0.0.0', port=5000)
 
1
+ from flask import Flask, request, jsonify, send_from_directory, render_template, redirect, url_for
2
+ from flask_cors import CORS
3
+ import os
4
+ import time
5
+ import traceback
6
+ import json
7
+ import re
8
+ import sys
9
+ import io
10
+ import threading
11
+ import queue
12
+ import contextlib
13
+ import signal
14
+ import psutil
15
+ from dotenv import load_dotenv
16
+
17
+ # 导入模块路由
18
+ from modules.knowledge_base.routes import knowledge_bp
19
+ from modules.code_executor.routes import code_executor_bp
20
+ from modules.visualization.routes import visualization_bp
21
+ from modules.agent_builder.routes import agent_builder_bp
22
+
23
+ # 加载环境变量
24
+ load_dotenv()
25
+
26
+ app = Flask(__name__)
27
+ CORS(app)
28
+
29
+ # 注册蓝图
30
+ app.register_blueprint(knowledge_bp, url_prefix='/api/knowledge')
31
+ app.register_blueprint(code_executor_bp, url_prefix='/api/code')
32
+ app.register_blueprint(visualization_bp, url_prefix='/api/visualization')
33
+ app.register_blueprint(agent_builder_bp, url_prefix='/api/agent')
34
+
35
+ # 确保目录存在
36
+ os.makedirs('static', exist_ok=True)
37
+ os.makedirs('uploads', exist_ok=True)
38
+ os.makedirs('agents', exist_ok=True)
39
+
40
+ # 用于代码执行的上下文
41
+ execution_contexts = {}
42
+
43
+ def get_memory_usage():
44
+ """获取当前进程的内存使用情况"""
45
+ process = psutil.Process(os.getpid())
46
+ return f"{process.memory_info().rss / 1024 / 1024:.1f} MB"
47
+
48
+ class CustomStdin:
49
+ def __init__(self, input_queue):
50
+ self.input_queue = input_queue
51
+ self.buffer = ""
52
+
53
+ def readline(self):
54
+ if not self.buffer:
55
+ self.buffer = self.input_queue.get() + "\n"
56
+
57
+ result = self.buffer
58
+ self.buffer = ""
59
+ return result
60
+
61
+ class InteractiveExecution:
62
+ """管理Python代码的交互式执行"""
63
+ def __init__(self, code):
64
+ self.code = code
65
+ self.context_id = str(time.time())
66
+ self.is_complete = False
67
+ self.is_waiting_for_input = False
68
+ self.stdout_buffer = io.StringIO()
69
+ self.last_read_position = 0
70
+ self.input_queue = queue.Queue()
71
+ self.error = None
72
+ self.thread = None
73
+ self.should_terminate = False
74
+
75
+ def run(self):
76
+ """在单独的线程中启动执行"""
77
+ self.thread = threading.Thread(target=self._execute)
78
+ self.thread.daemon = True
79
+ self.thread.start()
80
+
81
+ # 给执行一点时间开始
82
+ time.sleep(0.1)
83
+ return self.context_id
84
+
85
+ def _execute(self):
86
+ """执行代码,处理标准输入输出"""
87
+ try:
88
+ # 保存原始的stdin/stdout
89
+ orig_stdin = sys.stdin
90
+ orig_stdout = sys.stdout
91
+
92
+ # 创建自定义stdin
93
+ custom_stdin = CustomStdin(self.input_queue)
94
+
95
+ # 重定向stdin和stdout
96
+ sys.stdin = custom_stdin
97
+ sys.stdout = self.stdout_buffer
98
+
99
+ try:
100
+ # 检查终止的函数
101
+ self._last_check_time = 0
102
+
103
+ def check_termination():
104
+ if self.should_terminate:
105
+ raise KeyboardInterrupt("Execution terminated by user")
106
+
107
+ # 设置一个模拟__main__模块的命名空间
108
+ shared_namespace = {
109
+ "__builtins__": __builtins__,
110
+ "_check_termination": check_termination,
111
+ "time": time,
112
+ "__name__": "__main__"
113
+ }
114
+
115
+ # 在这个命名空间中执行用户代码
116
+ try:
117
+ exec(self.code, shared_namespace)
118
+ except KeyboardInterrupt:
119
+ print("\nExecution terminated by user")
120
+
121
+ except Exception as e:
122
+ self.error = {
123
+ "error": str(e),
124
+ "traceback": traceback.format_exc()
125
+ }
126
+
127
+ finally:
128
+ # 恢复原始stdin/stdout
129
+ sys.stdin = orig_stdin
130
+ sys.stdout = orig_stdout
131
+
132
+ # 标记执行完成
133
+ self.is_complete = True
134
+
135
+ except Exception as e:
136
+ self.error = {
137
+ "error": str(e),
138
+ "traceback": traceback.format_exc()
139
+ }
140
+ self.is_complete = True
141
+
142
+ def terminate(self):
143
+ """终止执行"""
144
+ self.should_terminate = True
145
+
146
+ # 如果在等待输入,放入一些内容以解除阻塞
147
+ if self.is_waiting_for_input:
148
+ self.input_queue.put("\n")
149
+
150
+ # 给执行一点时间终止
151
+ time.sleep(0.2)
152
+
153
+ # 标记为完成
154
+ self.is_complete = True
155
+
156
+ return True
157
+
158
+ def provide_input(self, user_input):
159
+ """为运行的代码提供���入"""
160
+ self.input_queue.put(user_input)
161
+ self.is_waiting_for_input = False
162
+ return True
163
+
164
+ def get_output(self):
165
+ """获取stdout缓冲区的当前内容"""
166
+ output = self.stdout_buffer.getvalue()
167
+ return output
168
+
169
+ def get_new_output(self):
170
+ """只获取自上次读取以来的新输出"""
171
+ current_value = self.stdout_buffer.getvalue()
172
+ if self.last_read_position < len(current_value):
173
+ new_output = current_value[self.last_read_position:]
174
+ self.last_read_position = len(current_value)
175
+ return new_output
176
+ return ""
177
+
178
+ @app.route('/')
179
+ def index():
180
+ """主界面"""
181
+ return render_template('index.html')
182
+ @app.route('/code_execution.html')
183
+ def index2():
184
+ """主界面"""
185
+ return render_template('code_execution.html')
186
+ @app.route('/api/progress/<task_id>', methods=['GET'])
187
+ def get_progress(task_id):
188
+ """获取文档处理进度"""
189
+ try:
190
+ # 从知识库模块访问处理任务
191
+ from modules.knowledge_base.routes import processing_tasks
192
+
193
+ progress_data = processing_tasks.get(task_id, {
194
+ 'progress': 0,
195
+ 'status': '未找到任务',
196
+ 'error': True
197
+ })
198
+
199
+ return jsonify({"success": True, "data": progress_data})
200
+ except Exception as e:
201
+ traceback.print_exc()
202
+ return jsonify({"success": False, "message": str(e)}), 500
203
+
204
+ @app.route('/student/<agent_id>')
205
+ def student_view(agent_id):
206
+ """学生访问Agent界面"""
207
+ token = request.args.get('token', '')
208
+
209
+ # 验证Agent存在
210
+ agent_path = os.path.join('agents', f"{agent_id}.json")
211
+ if not os.path.exists(agent_path):
212
+ return render_template('error.html',
213
+ message="找不到指定的Agent",
214
+ error_code=404)
215
+
216
+ # 加载Agent配置
217
+ with open(agent_path, 'r', encoding='utf-8') as f:
218
+ try:
219
+ agent_config = json.load(f)
220
+ except:
221
+ return render_template('error.html',
222
+ message="Agent配置无效",
223
+ error_code=500)
224
+
225
+ # 验证访问令牌
226
+ if token:
227
+ valid_token = False
228
+ if "distributions" in agent_config:
229
+ for dist in agent_config["distributions"]:
230
+ if dist.get("token") == token:
231
+ valid_token = True
232
+ break
233
+
234
+ if not valid_token:
235
+ return render_template('error.html',
236
+ message="访问令牌无效",
237
+ error_code=403)
238
+
239
+ # 渲染学生页面
240
+ return render_template('student.html',
241
+ agent_id=agent_id,
242
+ agent_name=agent_config.get('name', 'AI学习助手'),
243
+ agent_description=agent_config.get('description', ''),
244
+ token=token)
245
+
246
+ @app.route('/code_execution.html')
247
+ def code_execution_page():
248
+ """代码执行页面"""
249
+ return send_from_directory(os.path.dirname(os.path.abspath(__file__)), 'code_execution.html')
250
+
251
+ @app.route('/api/student/chat/<agent_id>', methods=['POST'])
252
+ def student_chat(agent_id):
253
+ """学生与Agent聊天的API"""
254
+ try:
255
+ data = request.json
256
+ message = data.get('message', '')
257
+ token = data.get('token', '')
258
+
259
+ if not message:
260
+ return jsonify({"success": False, "message": "消息不能为空"}), 400
261
+
262
+ # 验证Agent和令牌
263
+ agent_path = os.path.join('agents', f"{agent_id}.json")
264
+ if not os.path.exists(agent_path):
265
+ return jsonify({"success": False, "message": "Agent不存在"}), 404
266
+
267
+ with open(agent_path, 'r', encoding='utf-8') as f:
268
+ agent_config = json.load(f)
269
+
270
+ # 验证令牌(如果提供)
271
+ if token and "distributions" in agent_config:
272
+ valid_token = False
273
+ for dist in agent_config["distributions"]:
274
+ if dist.get("token") == token:
275
+ valid_token = True
276
+
277
+ # 更新使用计数
278
+ dist["usage_count"] = dist.get("usage_count", 0) + 1
279
+ break
280
+
281
+ if not valid_token:
282
+ return jsonify({"success": False, "message": "访问令牌无效"}), 403
283
+
284
+ # 更新Agent使用统计
285
+ if "stats" not in agent_config:
286
+ agent_config["stats"] = {}
287
+
288
+ agent_config["stats"]["usage_count"] = agent_config["stats"].get("usage_count", 0) + 1
289
+ agent_config["stats"]["last_used"] = int(time.time())
290
+
291
+ # 保存更新后的Agent配置
292
+ with open(agent_path, 'w', encoding='utf-8') as f:
293
+ json.dump(agent_config, f, ensure_ascii=False, indent=2)
294
+
295
+ # 获取Agent关联的知识库���插件
296
+ knowledge_bases = agent_config.get('knowledge_bases', [])
297
+ plugins = agent_config.get('plugins', [])
298
+
299
+ # 获取学科和指导者信息
300
+ subject = agent_config.get('subject', agent_config.get('name', '通用学科'))
301
+ instructor = agent_config.get('instructor', '教师')
302
+
303
+ # 创建Generator实例,传入学科和指导者信息
304
+ from modules.knowledge_base.generator import Generator
305
+ generator = Generator(subject=subject, instructor=instructor)
306
+
307
+ # 检测需要使用的插件
308
+ suggested_plugins = []
309
+
310
+ # 检测是否需要代码执行插件
311
+ if 'code' in plugins and ('代码' in message or 'python' in message.lower() or '编程' in message or 'code' in message.lower() or 'program' in message.lower()):
312
+ suggested_plugins.append('code')
313
+
314
+ # 检测是否需要3D可视化插件
315
+ if 'visualization' in plugins and ('3d' in message.lower() or '可视化' in message or '图形' in message):
316
+ suggested_plugins.append('visualization')
317
+
318
+ # 检测是否需要思维导图插件
319
+ if 'mindmap' in plugins and ('思维导图' in message or 'mindmap' in message.lower()):
320
+ suggested_plugins.append('mindmap')
321
+
322
+ # 检查是否有配置知识库
323
+ if not knowledge_bases:
324
+ # 没有知识库,直接使用模型进行回答
325
+ print(f"\n=== 处理查询: {message} (无知识库) ===")
326
+
327
+ # 使用空的文档列表调用生成器进行回答
328
+ final_response = ""
329
+ for chunk in generator.generate_stream(message, []):
330
+ if isinstance(chunk, dict):
331
+ continue # 跳过处理数据
332
+ final_response += chunk
333
+
334
+ # 返回生成的回答
335
+ return jsonify({
336
+ "success": True,
337
+ "message": final_response,
338
+ "tools": suggested_plugins
339
+ })
340
+
341
+ # 有知识库配置,执行知识库查询流程
342
+ try:
343
+ # 导入RAG系统组件
344
+ from modules.knowledge_base.retriever import Retriever
345
+ from modules.knowledge_base.reranker import Reranker
346
+
347
+ retriever = Retriever()
348
+ reranker = Reranker()
349
+
350
+ # 构建工具定义 - 将所有知识库作为工具
351
+ tools = []
352
+
353
+ # 创建工具名称到索引的映射
354
+ tool_to_index = {}
355
+
356
+ for i, index in enumerate(knowledge_bases):
357
+ display_name = index[4:] if index.startswith('rag_') else index
358
+
359
+ # 判断是否是视频知识库
360
+ is_video = "视频" in display_name or "video" in display_name.lower()
361
+
362
+ # 根据内容类型生成适当的工具名称
363
+ if is_video:
364
+ tool_name = f"video_knowledge_base_{i+1}"
365
+ description = f"在'{display_name}'视频知识库中搜索,返回带时间戳的视频链接。适用于需要视频讲解的问题。"
366
+ else:
367
+ tool_name = f"knowledge_base_{i+1}"
368
+ description = f"在'{display_name}'知识库中搜索专业知识、概念和原理。适用于需要文本说明的问题。"
369
+
370
+ # 添加工具名到索引的映射
371
+ tool_to_index[tool_name] = index
372
+
373
+ tools.append({
374
+ "type": "function",
375
+ "function": {
376
+ "name": tool_name,
377
+ "description": description,
378
+ "parameters": {
379
+ "type": "object",
380
+ "properties": {
381
+ "keywords": {
382
+ "type": "array",
383
+ "items": {"type": "string"},
384
+ "description": "搜索的关键词列表"
385
+ }
386
+ },
387
+ "required": ["keywords"],
388
+ "additionalProperties": False
389
+ },
390
+ "strict": True
391
+ }
392
+ })
393
+
394
+ # 第一阶段:工具选择决策
395
+ print(f"\n=== 处理查询: {message} ===")
396
+ tool_calls = generator.extract_keywords_with_tools(message, tools)
397
+
398
+ # 如果不需要调用工具,直接回答
399
+ if not tool_calls:
400
+ print("未检测到需要使用知识库,直接回答")
401
+ final_response = ""
402
+ for chunk in generator.generate_stream(message, []):
403
+ if isinstance(chunk, dict):
404
+ continue # 跳���处理数据
405
+ final_response += chunk
406
+
407
+ return jsonify({
408
+ "success": True,
409
+ "message": final_response,
410
+ "tools": suggested_plugins
411
+ })
412
+
413
+ # 收集来自工具执行的所有文档
414
+ all_docs = []
415
+
416
+ # 执行每个工具调用
417
+ for tool_call in tool_calls:
418
+ try:
419
+ tool_name = tool_call["function"]["name"]
420
+ actual_index = tool_to_index.get(tool_name)
421
+
422
+ if not actual_index:
423
+ print(f"找不到工具名称 '{tool_name}' 对应的索引")
424
+ continue
425
+
426
+ print(f"\n执行工具 '{tool_name}' -> 使用索引 '{actual_index}'")
427
+
428
+ arguments = json.loads(tool_call["function"]["arguments"])
429
+ keywords = " ".join(arguments.get("keywords", []))
430
+
431
+ if not keywords:
432
+ print("没有提供关键词,跳过检索")
433
+ continue
434
+
435
+ print(f"检索关键词: {keywords}")
436
+
437
+ # 执行检索
438
+ retrieved_docs, _ = retriever.retrieve(keywords, specific_index=actual_index)
439
+ print(f"检索到 {len(retrieved_docs)} 个文档")
440
+
441
+ # 重排序文档
442
+ reranked_docs = reranker.rerank(message, retrieved_docs, actual_index)
443
+ print(f"重排序完成,排序后有 {len(reranked_docs)} 个文档")
444
+
445
+ # 添加结果
446
+ all_docs.extend(reranked_docs)
447
+
448
+ except Exception as e:
449
+ print(f"执行工具 '{tool_call.get('function', {}).get('name', '未知')}' 调用时出错: {str(e)}")
450
+ import traceback
451
+ traceback.print_exc()
452
+
453
+ # 如果没有检索到任何文档,直接回答
454
+ if not all_docs:
455
+ print("未检索到任何相关文档,直接回答")
456
+ final_response = ""
457
+ for chunk in generator.generate_stream(message, []):
458
+ if isinstance(chunk, dict):
459
+ continue # 跳过处理数据
460
+ final_response += chunk
461
+
462
+ return jsonify({
463
+ "success": True,
464
+ "message": final_response,
465
+ "tools": suggested_plugins
466
+ })
467
+
468
+ # 按相关性排序
469
+ all_docs.sort(key=lambda x: x.get('rerank_score', 0), reverse=True)
470
+ print(f"\n最终收集到 {len(all_docs)} 个文档用于生成回答")
471
+
472
+ # 提取参考信息
473
+ references = []
474
+ for i, doc in enumerate(all_docs[:3], 1): # 只展示前3个参考来源
475
+ file_name = doc['metadata'].get('file_name', '未知文件')
476
+ content = doc['content']
477
+
478
+ # 提取大约前100字符作为摘要
479
+ summary = content[:100] + ('...' if len(content) > 100 else '')
480
+
481
+ references.append({
482
+ 'index': i,
483
+ 'file_name': file_name,
484
+ 'content': content,
485
+ 'summary': summary
486
+ })
487
+
488
+ # 第二阶段:生成最终答案
489
+ final_response = ""
490
+ for chunk in generator.generate_stream(message, all_docs):
491
+ if isinstance(chunk, dict):
492
+ continue # 跳过处理数据
493
+ final_response += chunk
494
+
495
+ # 构建回复
496
+ return jsonify({
497
+ "success": True,
498
+ "message": final_response,
499
+ "tools": suggested_plugins,
500
+ "references": references
501
+ })
502
+
503
+ except Exception as e:
504
+ import traceback
505
+ traceback.print_exc()
506
+ return jsonify({
507
+ "success": False,
508
+ "message": f"处理查询时出错: {str(e)}"
509
+ }), 500
510
+
511
+ except Exception as e:
512
+ import traceback
513
+ traceback.print_exc()
514
+ return jsonify({"success": False, "message": str(e)}), 500
515
+
516
+ if __name__ == '__main__':
517
+ app.run(debug=True, host='0.0.0.0', port=7860)