docs: update README and CLAUDE.md to v2.2.0
- Added documentation for audit tracking (IP address, invocation method). - Updated database model descriptions for enhanced WorkOrder and Conversation fields. - Documented the new UnifiedConfig system. - Reflected enhanced logging transparency for knowledge base parsing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,8 +14,8 @@ from typing import Dict, Any
|
||||
from flask import Flask, render_template, request, jsonify, send_from_directory, make_response
|
||||
from flask_cors import CORS
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from src.config.unified_config import get_config
|
||||
|
||||
|
||||
# 导入核心模块
|
||||
from src.core.database import db_manager
|
||||
@@ -37,6 +37,8 @@ from src.web.blueprints.agent import agent_bp
|
||||
from src.web.blueprints.vehicle import vehicle_bp
|
||||
from src.web.blueprints.analytics import analytics_bp
|
||||
from src.web.blueprints.test import test_bp
|
||||
from src.web.blueprints.feishu_bot import feishu_bot_bp
|
||||
|
||||
|
||||
# 配置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -79,6 +81,8 @@ app.register_blueprint(agent_bp)
|
||||
app.register_blueprint(vehicle_bp)
|
||||
app.register_blueprint(analytics_bp)
|
||||
app.register_blueprint(test_bp)
|
||||
app.register_blueprint(feishu_bot_bp)
|
||||
|
||||
|
||||
# 页面路由
|
||||
@app.route('/')
|
||||
@@ -235,8 +239,9 @@ def get_active_sessions():
|
||||
# 飞书同步功能已合并到主页面,不再需要单独的路由
|
||||
|
||||
if __name__ == '__main__':
|
||||
import time
|
||||
app.config['START_TIME'] = time.time()
|
||||
app.config['SERVER_PORT'] = 5000
|
||||
app.config['WEBSOCKET_PORT'] = 8765
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
config = get_config()
|
||||
app.run(
|
||||
debug=config.server.debug,
|
||||
host=config.server.host,
|
||||
port=config.server.port
|
||||
)
|
||||
|
||||
@@ -94,7 +94,6 @@ src/web/
|
||||
├── app.py # 主应用文件 (674行)
|
||||
├── app_backup.py # 原文件备份
|
||||
├── blueprints/ # 蓝图目录
|
||||
│ ├── __init__.py
|
||||
│ ├── alerts.py # 预警管理
|
||||
│ ├── workorders.py # 工单管理
|
||||
│ ├── conversations.py # 对话管理
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/agent.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/agent.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/analytics.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/analytics.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/auth.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/auth.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/feishu_bot.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/feishu_bot.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/test.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/test.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/web/blueprints/__pycache__/vehicle.cpython-310.pyc
Normal file
BIN
src/web/blueprints/__pycache__/vehicle.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
123
src/web/blueprints/feishu_bot.py
Normal file
123
src/web/blueprints/feishu_bot.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
飞书机器人蓝图
|
||||
处理来自飞书机器人的事件回调
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import threading
|
||||
from flask import Blueprint, request, jsonify
|
||||
from src.integrations.feishu_service import FeishuService
|
||||
from src.web.service_manager import service_manager
|
||||
|
||||
# 初始化日志
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 创建蓝图
|
||||
feishu_bot_bp = Blueprint('feishu_bot', __name__, url_prefix='/api/feishu/bot')
|
||||
|
||||
# 在模块级别实例化飞书服务,以便复用
|
||||
# 注意:这假设配置在启动时是固定的。如果配置可热更新,则需要调整。
|
||||
feishu_service = FeishuService()
|
||||
|
||||
def _process_message_in_background(app_context, event_data: dict):
|
||||
"""
|
||||
在后台线程中处理消息,避免阻塞飞书的回调请求。
|
||||
"""
|
||||
with app_context:
|
||||
try:
|
||||
# 1. 解析事件数据
|
||||
message_id = event_data['event']['message']['message_id']
|
||||
chat_id = event_data['event']['message']['chat_id']
|
||||
# 内容是一个JSON字符串,需要再次解析
|
||||
content_json = json.loads(event_data['event']['message']['content'])
|
||||
text_content = content_json.get('text', '').strip()
|
||||
|
||||
logger.info(f"[Feishu Bot] 后台开始处理消息ID: {message_id}, 内容: '{text_content}'")
|
||||
|
||||
# 2. 移除@机器人的部分
|
||||
# 飞书的@消息格式通常是 "@机器人名 实际内容"
|
||||
if event_data['event']['message'].get('mentions'):
|
||||
for mention in event_data['event']['message']['mentions']:
|
||||
# mention['key']是@内容,例如"@_user_1"
|
||||
# mention['name']是显示的名字
|
||||
bot_mention_text = f"@{mention['name']}"
|
||||
if text_content.startswith(bot_mention_text):
|
||||
text_content = text_content[len(bot_mention_text):].strip()
|
||||
break
|
||||
|
||||
if not text_content:
|
||||
logger.warning(f"[Feishu Bot] 移除@后内容为空,不处理。消息ID: {message_id}")
|
||||
return
|
||||
|
||||
logger.info(f"[Feishu Bot] 清理后的消息内容: '{text_content}'")
|
||||
|
||||
# 3. 调用核心服务获取回复
|
||||
assistant = service_manager.get_assistant()
|
||||
# 注意:process_message_agent 是一个异步方法,需要处理
|
||||
# 在同步线程中运行异步方法
|
||||
import asyncio
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError: # 'RuntimeError: There is no current event loop...'
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# 调用对话服务
|
||||
logger.info(f"[Feishu Bot] 调用Agent服务处理消息...")
|
||||
response_data = loop.run_until_complete(
|
||||
assistant.process_message_agent(message=text_content, user_id=f"feishu_{chat_id}")
|
||||
)
|
||||
logger.info(f"[Feishu Bot] Agent服务返回结果: {response_data}")
|
||||
|
||||
# 4. 提取回复并发送
|
||||
reply_text = response_data.get("message", "抱歉,我暂时无法回答这个问题。")
|
||||
if isinstance(reply_text, dict): # 有时候返回的可能是字典
|
||||
reply_text = reply_text.get('content', str(reply_text))
|
||||
|
||||
logger.info(f"[Feishu Bot] 准备发送回复到飞书: '{reply_text}'")
|
||||
success = feishu_service.reply_to_message(message_id, reply_text)
|
||||
|
||||
if success:
|
||||
logger.info(f"[Feishu Bot] 成功回复消息到飞书。消息ID: {message_id}")
|
||||
else:
|
||||
logger.error(f"[Feishu Bot] 回复消息到飞书失败。消息ID: {message_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Feishu Bot] 后台处理消息时发生严重错误: {e}", exc_info=True)
|
||||
|
||||
|
||||
@feishu_bot_bp.route('/event', methods=['POST'])
|
||||
def handle_feishu_event():
|
||||
"""
|
||||
接收并处理飞书事件回调
|
||||
"""
|
||||
# 1. 解析请求
|
||||
data = request.json
|
||||
logger.info(f"[Feishu Bot] 收到飞书事件回调:\n{json.dumps(data, indent=2)}")
|
||||
|
||||
# 2. 安全校验 (如果配置了)
|
||||
# 此处可以添加Verification Token的校验逻辑
|
||||
# headers = request.headers
|
||||
# ...
|
||||
|
||||
# 3. 处理URL验证挑战
|
||||
if data and data.get("type") == "url_verification":
|
||||
challenge = data.get("challenge", "")
|
||||
logger.info(f"[Feishu Bot] 收到URL验证请求,返回challenge: {challenge}")
|
||||
return jsonify({"challenge": challenge})
|
||||
|
||||
# 4. 处理事件回调
|
||||
if data and data.get("header", {}).get("event_type") == "im.message.receive_v1":
|
||||
# 立即响应飞书,防止超时重试
|
||||
threading.Thread(
|
||||
target=_process_message_in_background,
|
||||
args=(request.environ['werkzeug.request'].environ['flask.app'].app_context(), data)
|
||||
).start()
|
||||
|
||||
logger.info("[Feishu Bot] 已将消息处理任务推送到后台线程,并立即响应200 OK")
|
||||
return jsonify({"status": "processing"})
|
||||
|
||||
# 5. 对于其他未知事件,也返回成功,避免飞书重试
|
||||
logger.warning(f"[Feishu Bot] 收到未知类型的事件: {data.get('header', {}).get('event_type')}")
|
||||
return jsonify({"status": "ignored"})
|
||||
@@ -7,203 +7,135 @@
|
||||
import os
|
||||
import tempfile
|
||||
import uuid
|
||||
import logging
|
||||
from flask import Blueprint, request, jsonify
|
||||
from src.agent_assistant import TSPAgentAssistant
|
||||
from src.web.service_manager import service_manager
|
||||
from src.web.error_handlers import handle_api_errors, create_error_response, create_success_response
|
||||
|
||||
knowledge_bp = Blueprint('knowledge', __name__, url_prefix='/api/knowledge')
|
||||
|
||||
_agent_assistant = None
|
||||
|
||||
def get_agent_assistant():
|
||||
"""获取Agent助手实例(懒加载)"""
|
||||
global _agent_assistant
|
||||
if '_agent_assistant' not in globals():
|
||||
if _agent_assistant is None:
|
||||
_agent_assistant = TSPAgentAssistant()
|
||||
return _agent_assistant
|
||||
|
||||
@knowledge_bp.route('')
|
||||
@handle_api_errors
|
||||
def get_knowledge():
|
||||
"""获取知识库列表(分页)"""
|
||||
try:
|
||||
# 获取分页参数
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
category_filter = request.args.get('category', '')
|
||||
verified_filter = request.args.get('verified', '')
|
||||
|
||||
# 从数据库获取知识库数据
|
||||
from src.core.database import db_manager
|
||||
from src.core.models import KnowledgeEntry
|
||||
|
||||
with db_manager.get_session() as session:
|
||||
# 构建查询
|
||||
query = session.query(KnowledgeEntry).filter(KnowledgeEntry.is_active == True)
|
||||
|
||||
# 应用过滤器
|
||||
if category_filter:
|
||||
query = query.filter(KnowledgeEntry.category == category_filter)
|
||||
if verified_filter:
|
||||
if verified_filter == 'true':
|
||||
query = query.filter(KnowledgeEntry.is_verified == True)
|
||||
elif verified_filter == 'false':
|
||||
query = query.filter(KnowledgeEntry.is_verified == False)
|
||||
|
||||
# 按创建时间倒序排列
|
||||
query = query.order_by(KnowledgeEntry.created_at.desc())
|
||||
|
||||
# 计算总数
|
||||
total = query.count()
|
||||
|
||||
# 分页查询
|
||||
knowledge_entries = query.offset((page - 1) * per_page).limit(per_page).all()
|
||||
|
||||
# 转换为字典
|
||||
knowledge_data = []
|
||||
for entry in knowledge_entries:
|
||||
knowledge_data.append({
|
||||
'id': entry.id,
|
||||
'question': entry.question,
|
||||
'answer': entry.answer,
|
||||
'category': entry.category,
|
||||
'confidence_score': entry.confidence_score,
|
||||
'usage_count': entry.usage_count,
|
||||
'is_verified': entry.is_verified,
|
||||
'is_active': entry.is_active,
|
||||
'created_at': entry.created_at.isoformat() if entry.created_at else None,
|
||||
'updated_at': entry.updated_at.isoformat() if entry.updated_at else None
|
||||
})
|
||||
|
||||
# 计算分页信息
|
||||
total_pages = (total + per_page - 1) // per_page
|
||||
|
||||
return jsonify({
|
||||
'knowledge': knowledge_data,
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'total': total,
|
||||
'total_pages': total_pages
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
category_filter = request.args.get('category', '')
|
||||
verified_filter = request.args.get('verified', '')
|
||||
|
||||
result = service_manager.get_assistant().knowledge_manager.get_knowledge_paginated(
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
category_filter=category_filter,
|
||||
verified_filter=verified_filter
|
||||
)
|
||||
return jsonify(result)
|
||||
|
||||
@knowledge_bp.route('/search')
|
||||
@handle_api_errors
|
||||
def search_knowledge():
|
||||
"""搜索知识库"""
|
||||
try:
|
||||
query = request.args.get('q', '')
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info(f"搜索查询: '{query}'")
|
||||
|
||||
if not query.strip():
|
||||
logger.info("查询为空,返回空结果")
|
||||
return jsonify([])
|
||||
|
||||
# 直接调用知识库管理器的搜索方法
|
||||
assistant = service_manager.get_assistant()
|
||||
results = assistant.knowledge_manager.search_knowledge(query, top_k=5)
|
||||
logger.info(f"搜索结果数量: {len(results)}")
|
||||
return jsonify(results)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f"搜索知识库失败: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
query = request.args.get('q', '')
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.info(f"搜索查询: '{query}'")
|
||||
|
||||
if not query.strip():
|
||||
logger.info("查询为空,返回空结果")
|
||||
return jsonify([])
|
||||
|
||||
assistant = service_manager.get_assistant()
|
||||
results = assistant.knowledge_manager.search_knowledge(query, top_k=5)
|
||||
logger.info(f"搜索结果数量: {len(results)}")
|
||||
return jsonify(results)
|
||||
|
||||
@knowledge_bp.route('', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def add_knowledge():
|
||||
"""添加知识库条目"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
success = service_manager.get_assistant().knowledge_manager.add_knowledge_entry(
|
||||
question=data['question'],
|
||||
answer=data['answer'],
|
||||
category=data['category'],
|
||||
confidence_score=data['confidence_score']
|
||||
)
|
||||
return jsonify({"success": success, "message": "知识添加成功" if success else "添加失败"})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
data = request.get_json()
|
||||
success = service_manager.get_assistant().knowledge_manager.add_knowledge_entry(
|
||||
question=data['question'],
|
||||
answer=data['answer'],
|
||||
category=data['category'],
|
||||
confidence_score=data.get('confidence_score', 0.8)
|
||||
)
|
||||
if success:
|
||||
return create_success_response("知识添加成功")
|
||||
else:
|
||||
return create_error_response("添加失败", 500)
|
||||
|
||||
@knowledge_bp.route('/stats')
|
||||
@handle_api_errors
|
||||
def get_knowledge_stats():
|
||||
"""获取知识库统计"""
|
||||
try:
|
||||
stats = service_manager.get_assistant().knowledge_manager.get_knowledge_stats()
|
||||
return jsonify(stats)
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
stats = service_manager.get_assistant().knowledge_manager.get_knowledge_stats()
|
||||
return jsonify(stats)
|
||||
|
||||
@knowledge_bp.route('/upload', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def upload_knowledge_file():
|
||||
"""上传文件并生成知识库"""
|
||||
if 'file' not in request.files:
|
||||
return create_error_response("没有上传文件", 400)
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return create_error_response("没有选择文件", 400)
|
||||
|
||||
temp_filename = f"upload_{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
|
||||
temp_path = os.path.join(tempfile.gettempdir(), temp_filename)
|
||||
|
||||
try:
|
||||
if 'file' not in request.files:
|
||||
return jsonify({"error": "没有上传文件"}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({"error": "没有选择文件"}), 400
|
||||
|
||||
# 保存文件到临时目录
|
||||
import tempfile
|
||||
import os
|
||||
import uuid
|
||||
|
||||
# 创建唯一的临时文件名
|
||||
temp_filename = f"upload_{uuid.uuid4()}{os.path.splitext(file.filename)[1]}"
|
||||
temp_path = os.path.join(tempfile.gettempdir(), temp_filename)
|
||||
|
||||
file.save(temp_path)
|
||||
assistant = get_agent_assistant()
|
||||
result = assistant.process_file_to_knowledge(temp_path, file.filename)
|
||||
return jsonify(result)
|
||||
finally:
|
||||
try:
|
||||
# 保存文件
|
||||
file.save(temp_path)
|
||||
|
||||
# 使用Agent助手处理文件
|
||||
result = get_agent_assistant().process_file_to_knowledge(temp_path, file.filename)
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
finally:
|
||||
# 确保删除临时文件
|
||||
try:
|
||||
if os.path.exists(temp_path):
|
||||
os.unlink(temp_path)
|
||||
except Exception as cleanup_error:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f"清理临时文件失败: {cleanup_error}")
|
||||
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f"文件上传处理失败: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
if os.path.exists(temp_path):
|
||||
os.unlink(temp_path)
|
||||
except Exception as cleanup_error:
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.warning(f"清理临时文件失败: {cleanup_error}")
|
||||
|
||||
@knowledge_bp.route('/delete/<int:knowledge_id>', methods=['DELETE'])
|
||||
@handle_api_errors
|
||||
def delete_knowledge(knowledge_id):
|
||||
"""删除知识库条目"""
|
||||
try:
|
||||
success = service_manager.get_assistant().knowledge_manager.delete_knowledge_entry(knowledge_id)
|
||||
return jsonify({"success": success, "message": "删除成功" if success else "删除失败"})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
success = service_manager.get_assistant().knowledge_manager.delete_knowledge_entry(knowledge_id)
|
||||
if success:
|
||||
return create_success_response("删除成功")
|
||||
else:
|
||||
return create_error_response("删除失败", 404)
|
||||
|
||||
@knowledge_bp.route('/verify/<int:knowledge_id>', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def verify_knowledge(knowledge_id):
|
||||
"""验证知识库条目"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
verified_by = data.get('verified_by', 'admin')
|
||||
success = service_manager.get_assistant().knowledge_manager.verify_knowledge_entry(knowledge_id, verified_by)
|
||||
return jsonify({"success": success, "message": "验证成功" if success else "验证失败"})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
data = request.get_json() or {}
|
||||
verified_by = data.get('verified_by', 'admin')
|
||||
success = service_manager.get_assistant().knowledge_manager.verify_knowledge_entry(knowledge_id, verified_by)
|
||||
if success:
|
||||
return create_success_response("验证成功")
|
||||
else:
|
||||
return create_error_response("验证失败", 404)
|
||||
|
||||
@knowledge_bp.route('/unverify/<int:knowledge_id>', methods=['POST'])
|
||||
@handle_api_errors
|
||||
def unverify_knowledge(knowledge_id):
|
||||
"""取消验证知识库条目"""
|
||||
try:
|
||||
success = service_manager.get_assistant().knowledge_manager.unverify_knowledge_entry(knowledge_id)
|
||||
return jsonify({"success": success, "message": "取消验证成功" if success else "取消验证失败"})
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
success = service_manager.get_assistant().knowledge_manager.unverify_knowledge_entry(knowledge_id)
|
||||
if success:
|
||||
return create_success_response("取消验证成功")
|
||||
else:
|
||||
return create_error_response("取消验证失败", 404)
|
||||
|
||||
@@ -16,27 +16,8 @@ from sqlalchemy import text
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 简化的AI准确率配置类
|
||||
class SimpleAIAccuracyConfig:
|
||||
"""简化的AI准确率配置"""
|
||||
def __init__(self):
|
||||
self.auto_approve_threshold = 0.95
|
||||
self.use_human_resolution_threshold = 0.90
|
||||
self.manual_review_threshold = 0.80
|
||||
self.ai_suggestion_confidence = 0.95
|
||||
self.human_resolution_confidence = 0.90
|
||||
|
||||
def should_auto_approve(self, similarity: float) -> bool:
|
||||
return similarity >= self.auto_approve_threshold
|
||||
|
||||
def should_use_human_resolution(self, similarity: float) -> bool:
|
||||
return similarity < self.use_human_resolution_threshold
|
||||
|
||||
def get_confidence_score(self, similarity: float, use_human: bool = False) -> float:
|
||||
if use_human:
|
||||
return self.human_resolution_confidence
|
||||
else:
|
||||
return max(similarity, self.ai_suggestion_confidence)
|
||||
# 移除SimpleAIAccuracyConfig,直接从统一配置获取
|
||||
from src.config.unified_config import get_config
|
||||
|
||||
from src.main import TSPAssistant
|
||||
from src.core.database import db_manager
|
||||
@@ -397,14 +378,14 @@ def save_workorder_human_resolution(workorder_id):
|
||||
rec.ai_similarity = sim
|
||||
|
||||
# 使用简化的配置
|
||||
config = SimpleAIAccuracyConfig()
|
||||
|
||||
config = get_config().ai_accuracy
|
||||
|
||||
# 自动审批条件
|
||||
approved = config.should_auto_approve(sim)
|
||||
approved = sim >= config.auto_approve_threshold
|
||||
rec.approved = approved
|
||||
|
||||
|
||||
# 记录使用人工描述入库的标记(当AI准确率低于阈值时)
|
||||
use_human_resolution = config.should_use_human_resolution(sim)
|
||||
use_human_resolution = sim < config.use_human_resolution_threshold
|
||||
rec.use_human_resolution = use_human_resolution
|
||||
|
||||
session.commit()
|
||||
@@ -431,19 +412,19 @@ def approve_workorder_to_knowledge(workorder_id):
|
||||
return jsonify({"error": "未找到工单建议记录"}), 400
|
||||
|
||||
# 使用简化的配置
|
||||
config = SimpleAIAccuracyConfig()
|
||||
|
||||
config = get_config().ai_accuracy
|
||||
|
||||
# 确定使用哪个内容入库
|
||||
if rec.use_human_resolution and rec.human_resolution:
|
||||
# AI准确率低于阈值,使用人工描述入库
|
||||
answer_content = rec.human_resolution
|
||||
confidence_score = config.get_confidence_score(rec.ai_similarity or 0, use_human=True)
|
||||
confidence_score = config.human_resolution_confidence
|
||||
verified_by = 'human_resolution'
|
||||
logger.info(f"工单 {workorder_id} 使用人工描述入库,AI相似度: {rec.ai_similarity:.4f}")
|
||||
elif rec.approved and rec.ai_suggestion:
|
||||
# AI准确率≥阈值,使用AI建议入库
|
||||
answer_content = rec.ai_suggestion
|
||||
confidence_score = config.get_confidence_score(rec.ai_similarity or 0, use_human=False)
|
||||
confidence_score = max(rec.ai_similarity or 0, config.ai_suggestion_confidence)
|
||||
verified_by = 'auto_approve'
|
||||
logger.info(f"工单 {workorder_id} 使用AI建议入库,相似度: {rec.ai_similarity:.4f}")
|
||||
else:
|
||||
|
||||
@@ -21,7 +21,7 @@ class ServiceManager:
|
||||
if service_name not in self._services:
|
||||
try:
|
||||
self._services[service_name] = factory_func()
|
||||
logger.info(f"服务 {service_name} 已初始化")
|
||||
logger.debug(f"服务 {service_name} 已初始化")
|
||||
except Exception as e:
|
||||
logger.error(f"初始化服务 {service_name} 失败: {e}")
|
||||
raise
|
||||
|
||||
@@ -90,8 +90,21 @@ class WebSocketServer:
|
||||
await self._send_error(websocket, "缺少必要参数", message_id)
|
||||
return
|
||||
|
||||
# 获取客户端IP
|
||||
ip_address = None
|
||||
try:
|
||||
# websockets 15.x 获取 remote_address 的方式
|
||||
ip_address = websocket.remote_address[0] if websocket.remote_address else None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 处理消息
|
||||
result = self.chat_manager.process_message(session_id, message)
|
||||
result = self.chat_manager.process_message(
|
||||
session_id,
|
||||
message,
|
||||
ip_address=ip_address,
|
||||
invocation_method="websocket"
|
||||
)
|
||||
|
||||
response = {
|
||||
"type": "message_response",
|
||||
@@ -210,21 +223,9 @@ class WebSocketServer:
|
||||
|
||||
await websocket.send(json.dumps(response, ensure_ascii=False))
|
||||
|
||||
async def handle_client(self, websocket: WebSocketServerProtocol, path: str):
|
||||
"""处理客户端连接"""
|
||||
# 检查连接头
|
||||
headers = websocket.request_headers
|
||||
connection = headers.get("Connection", "").lower()
|
||||
|
||||
# 处理不同的连接头格式
|
||||
if "upgrade" not in connection and "keep-alive" in connection:
|
||||
logger.warning(f"收到非标准连接头: {connection}")
|
||||
# 对于keep-alive连接头,我们仍然接受连接
|
||||
elif "upgrade" not in connection:
|
||||
logger.warning(f"连接头不包含upgrade: {connection}")
|
||||
await websocket.close(code=1002, reason="Invalid connection header")
|
||||
return
|
||||
|
||||
async def handle_client(self, websocket):
|
||||
"""处理客户端连接(兼容 websockets 15.x)"""
|
||||
# websockets 15.x 版本中,handler 只接收 websocket 参数,不再有 path 参数
|
||||
await self.register_client(websocket)
|
||||
|
||||
try:
|
||||
@@ -238,61 +239,17 @@ class WebSocketServer:
|
||||
await self.unregister_client(websocket)
|
||||
|
||||
async def start_server(self):
|
||||
"""启动WebSocket服务器"""
|
||||
"""启动WebSocket服务器(兼容 websockets 15.x)"""
|
||||
logger.info(f"启动WebSocket服务器: ws://{self.host}:{self.port}")
|
||||
|
||||
# 添加CORS支持
|
||||
async def handle_client_with_cors(websocket: WebSocketServerProtocol):
|
||||
# 获取path,websockets在提供process_request时,不会将path传递给handler
|
||||
path = websocket.path
|
||||
# 设置CORS头
|
||||
if websocket.request_headers.get("Origin"):
|
||||
# 允许跨域连接
|
||||
pass
|
||||
await self.handle_client(websocket, path)
|
||||
|
||||
# websockets 15.x 简化版本:直接传递处理函数
|
||||
async with websockets.serve(
|
||||
handle_client_with_cors,
|
||||
self.handle_client,
|
||||
self.host,
|
||||
self.port,
|
||||
# 添加额外的服务器选项
|
||||
process_request=self._process_request
|
||||
self.port
|
||||
):
|
||||
await asyncio.Future() # 保持服务器运行
|
||||
|
||||
def _process_request(self, path, request_headers):
|
||||
"""处理HTTP请求,支持CORS"""
|
||||
# 检查是否是WebSocket升级请求
|
||||
# request_headers 可能是 Headers 对象或 Request 对象
|
||||
if hasattr(request_headers, 'get'):
|
||||
upgrade_header = request_headers.get("Upgrade", "").lower()
|
||||
elif hasattr(request_headers, 'headers'):
|
||||
upgrade_header = request_headers.headers.get("Upgrade", "").lower()
|
||||
else:
|
||||
upgrade_header = ""
|
||||
|
||||
if upgrade_header == "websocket":
|
||||
return None # 允许WebSocket连接
|
||||
|
||||
# 对于非WebSocket请求,返回简单的HTML页面
|
||||
return (
|
||||
200,
|
||||
[("Content-Type", "text/html; charset=utf-8")],
|
||||
b"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSocket Server</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Server is running</h1>
|
||||
<p>This is a WebSocket server. Please use a WebSocket client to connect.</p>
|
||||
<p>WebSocket URL: ws://localhost:8765</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""运行服务器"""
|
||||
asyncio.run(self.start_server())
|
||||
|
||||
Reference in New Issue
Block a user