From 463824480b6744ba26e48a7797bf91dfa2a25bed Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Tue, 14 Jul 2026 23:04:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=9F=A5=E8=AF=86=E5=BA=93=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=B8=8A=E4=BC=A0(=E5=B0=BE=E6=96=9C=E6=9D=A0+FormDat?= =?UTF-8?q?a+=E7=9C=9F=E6=96=87=E4=BB=B6=E6=8E=A5=E6=94=B6);=20AI=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E5=BC=BA=E5=88=B6Markdown;=20=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=85=A8=E5=8A=9F=E8=83=BD=E7=9F=A5=E8=AF=86=E5=BA=93=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=E7=B3=BB=E7=BB=9F=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/python/app/api/chat.py | 12 +++ backend/python/app/api/knowledge.py | 84 ++++++++++++++++----- backend/python/app/platform_knowledge.py | 86 ++++++++++++++++++++++ frontend/src/api/index.ts | 2 +- frontend/src/pages/front/KnowledgePage.tsx | 23 +++++- 5 files changed, 183 insertions(+), 24 deletions(-) create mode 100644 backend/python/app/platform_knowledge.py diff --git a/backend/python/app/api/chat.py b/backend/python/app/api/chat.py index a255fb0..c7e8014 100644 --- a/backend/python/app/api/chat.py +++ b/backend/python/app/api/chat.py @@ -26,6 +26,7 @@ from ..models import ( gen_uuid ) from ..auth import get_current_user +from ..platform_knowledge import get_platform_knowledge from ..middleware.log_service import LogService from ..capability_registry import resolve_auto_model @@ -656,6 +657,17 @@ def _build_system_prompt( 返回: (system_prompt_str, function_definitions_list) """ system_parts = ["你是AIFlow平台的AI助手,具备专业的知识储备和工具调用能力。"] + + # 平台知识库注入(始终开启):确保AI优先使用平台已有能力 + try: + pk = get_platform_knowledge() + if pk: + system_parts.append(f"【平台知识库——优先使用以下知识回答用户问题】\n{pk}") + except Exception: + pass + + # 回复格式要求:强制使用Markdown + system_parts.append("回复格式要求:你必须使用Markdown格式回复,确保层次分明、条理清晰。使用##和###做标题、**加粗**强调重点、-或1.做列表、```代码块包裹代码或配置、表格展示对比数据。禁止回复纯文本长段落。") function_definitions = [] diff --git a/backend/python/app/api/knowledge.py b/backend/python/app/api/knowledge.py index dd1d836..72edbad 100644 --- a/backend/python/app/api/knowledge.py +++ b/backend/python/app/api/knowledge.py @@ -851,9 +851,12 @@ def get_documents( @router.post("/bases/{base_id}/documents") -def upload_document( +async def upload_document( base_id: str, - req: CreateDocumentRequest, + file: Optional[UploadFile] = File(None), + filename: str = Form(...), + file_type: Optional[str] = Form(None), + file_size: Optional[int] = Form(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): @@ -864,7 +867,7 @@ def upload_document( raise HTTPException(status_code=404, detail="知识库不存在") # 确定文件扩展名和存储路径 - file_ext = (req.file_type or "txt").lower() + file_ext = (file_type or "txt").lower() if file_ext.startswith("."): file_ext = file_ext[1:] @@ -877,28 +880,69 @@ def upload_document( stored_filename = f"{doc_id}.{file_ext}" stored_path = os.path.join(storage_dir, stored_filename) - # 文本解析:根据文件类型生成模拟文档内容 - # 实际生产环境中应使用 PyPDF2/pdfplumber(PDF)、python-docx(Word)、openpyxl(Excel)等库 - file_size = req.file_size or len(req.filename.encode('utf-8')) * 100 - content = _generate_document_content(req.filename, file_ext, file_size) + # 文件内容解析 + actual_size = file_size or 0 + content = "" + uploaded_content = b"" - # 将内容写入存储文件 - try: - with open(stored_path, "w", encoding="utf-8") as f: - f.write(content) - logger.info(f"知识库文档已写入: {stored_path}, size={len(content)} chars") - except Exception as e: - logger.error(f"知识库文档写入失败: {stored_path}, error={e}") - raise HTTPException(status_code=500, detail=f"文件存储失败: {str(e)}") + # 如果有真实文件上传,先保存并读取内容 + if file and file.filename: + try: + uploaded_content = await file.read() + actual_size = len(uploaded_content) + # 先写入原始文件 + with open(stored_path, "wb") as f: + f.write(uploaded_content) + # 对于文本类格式,尝试直接解析 + text_exts = {"txt", "md", "csv", "json", "yaml", "yml", "xml", "py", "js", "ts", "html", "css", "log"} + if file_ext in text_exts: + try: + content = uploaded_content.decode("utf-8") + except UnicodeDecodeError: + try: + content = uploaded_content.decode("gbk") + except UnicodeDecodeError: + content = f"[二进制文件: {filename}, 大小: {actual_size} bytes]" + else: + # 非文本格式:记录文件但内容用摘要代替 + content = _generate_document_content(filename, file_ext, actual_size) + logger.info(f"非文本文件 {filename}({file_ext}) 已保存到 {stored_path}, 使用模拟内容用于检索") + except Exception as e: + logger.error(f"文件读取失败: {e}") + content = _generate_document_content(filename, file_ext, actual_size) + else: + # 无文件上传(兼容旧 JSON 模式):生成模拟内容 + actual_size = file_size or len(filename.encode('utf-8')) * 100 + content = _generate_document_content(filename, file_ext, actual_size) + # 写入模拟内容 + try: + with open(stored_path, "w", encoding="utf-8") as f: + f.write(content) + except Exception as e: + logger.error(f"知识库文档写入失败: {stored_path}, error={e}") + raise HTTPException(status_code=500, detail=f"文件存储失败: {str(e)}") - # 文本分块:将内容按段落分割为chunks + # 确保文件已写入(防止重复写入) + if uploaded_content and file_ext not in {"txt", "md", "csv", "json", "yaml", "yml", "xml", "py", "js", "ts", "html", "css", "log"}: + pass # 已在上面 wb 写入 + elif not os.path.exists(stored_path): + try: + with open(stored_path, "w", encoding="utf-8") as f: + f.write(content) + except Exception as e: + logger.error(f"知识库文档写入失败: {stored_path}, error={e}") + raise HTTPException(status_code=500, detail=f"文件存储失败: {str(e)}") + + logger.info(f"知识库文档已处理: {stored_path}, ext={file_ext}, size={actual_size}, content_len={len(content)}") + + # 文本分块 chunks = _split_content_into_chunks(content, chunk_size=500) doc = KnowledgeDocument( id=doc_id, knowledge_base_id=base_id, - filename=req.filename, - file_size=file_size, + filename=filename, + file_size=actual_size, file_type=file_ext, status="completed", chunk_count=len(chunks), @@ -911,8 +955,8 @@ def upload_document( kb.document_count = (kb.document_count or 0) + 1 kb.updated_at = datetime.now() - # 同步创建Asset记录(问题9:资产关联知识库文档用量) - _sync_asset_for_knowledge_doc(db, current_user.id, doc_id, req.filename, file_ext, file_size) + # 同步创建Asset记录 + _sync_asset_for_knowledge_doc(db, current_user.id, doc_id, filename, file_ext, actual_size) db.commit() db.refresh(doc) diff --git a/backend/python/app/platform_knowledge.py b/backend/python/app/platform_knowledge.py new file mode 100644 index 0000000..a364fcf --- /dev/null +++ b/backend/python/app/platform_knowledge.py @@ -0,0 +1,86 @@ +""" +七虎AI平台 — 全功能知识库与能力导航 +注入到每轮AI对话中,确保AI回复优先使用平台已有知识和能力。 +""" +import logging + +logger = logging.getLogger("aiflow") + +PLATFORM_KNOWLEDGE = """# 七虎AI平台能力全览 + +你是七虎AI(qihuyun.com)平台的AI助手,你必须充分了解并使用以下平台能力来回答用户问题,**优先引用平台内部知识而非互联网搜索**。 + +## 1. AI聊天(核心功能) +- **多模型支持**: DeepSeek-V4-Flash(默认)、DeepSeek-V3、通义千问、OpenAI等,可按能力自动匹配(Auto模式) +- **Auto智能匹配**: 根据用户意图自动选择最佳模型——文本对话用DeepSeek-V4-Flash、图像生成用agnes-image-2.1-flash、视频生成用agnes-video-v2.0、语音识别用MiMo-ASR +- **强大提示**: 图片理解(OCR用DeepSeek-OCR)、思维链推理、函数调用(Function Calling) +- **Markdown格式化**: 回复使用Markdown格式(标题/加粗/列表/代码块/表格),确保清晰、有层次、有逻辑 +- **多模态生成**: AI绘图(文生图)、AI视频(文生视频) +- **对话管理**: 新建会话、历史管理、消息分页 + +## 2. 知识库系统 +- **文档管理**: 上传txt/md/csv/pdf/docx/xlsx/pptx等文件,自动分块索引 +- **智能检索**: BM25+语义检索,RAG增强回复 +- **Dify集成**: 接入Dify外部知识库,深度RAG检索,AI聊天产出+用户文档自动入库量化 +- **知识图谱**: 文档关系可视化、节点关联 + +## 3. 技能市场 +- **Skills工具**: 从skills.sh、ClawHub、GitHub、ModelScope、SkillsMP等平台导入技能,支持SKILL.md格式验证 +- **智能Agent**: 角色定制、行为指引、自动执行任务 +- **MCP协议应用**: 从ModelScope MCP、MCP.so、MCPMarket导入MCP服务,支持Streamable HTTP/SSE传输协议,AI代码验证 +- **导入Hub**: 一键从公共平台导入Skills/MCP应用,AI自动验证代码完整性 +- **自定义创建**: 手动创建Skills/MCP/Agent应用,配置价格和发布 + +## 4. 进化看板 +- **自进化体系**: AI对话轨迹自动分析、技能权重排序、原子化学习 +- **进化仪表盘**: 技能贡献度、轨迹图谱、进化历史可视化 +- **Pipeline调度**: 定期扫描对话、提取高价值轨迹、更新技能链接 + +## 5. IM Bot(微信集成) +- **个人微信Bot**: 通过iLink官方接口接入,扫码绑定,微信内直接AI对话 +- **企业微信/飞书/QQ/悟空IM**: Webhook框架已就绪,支持自定义机器人 +- **统一会话管理**: 微信消息并入平台AI聊天,统一历史管理 + +## 6. 资源中心 +- **Token驾驶舱**: 用量统计(按日/模型)、剩余额度、消耗趋势图表 +- **会员订阅**: 免费版/专业版/企业版,享受不同Token配额和功能权限 +- **资产文件**: 上传管理图片/视频/文档等文件,关联知识库和AI聊天 + +## 7. 商业服务 +- **计费系统**: Token计费、订单管理、支付渠道(支持多种支付) +- **广告管理**: 首页广告位、轮播图、付费推广 +- **推广系统**: 邀请码、分销返佣、等级激励机制 +- **成就系统**: 用户行为奖励、里程碑徽章、积分兑换 + +## 8. 管理后台(Admin) +- **数据驾驶舱**: 平台概览(用户数/模型数/对话数/Token消耗)、30天趋势图、操作日志/审计日志(分页+CSV导出) +- **大模型库**: 模型供应商管理(DeepSeek/OpenAI/SiliconFlow等)、模型配置(能力标签/超时/定价)、能力默认模型设置(11项能力) +- **用户管理**: 用户列表、会员等级、角色权限(RBAC) +- **系统设置**: 网站基础配置、能力默认值、MCP全局代理 +- **短信配置**: SMSBao短信服务,测试发送/余额查询 +- **隐私与安全**: GDPR设置、MCP隐私策略、数据加密 + +## 9. 开发者工具 +- **API密钥管理**: 创建/撤销API密钥,调用平台API +- **MCP Client管理**: MCP服务器配置(stdio/sse/http)、环境变量、连接测试 +- **自动化**: 定时任务调度 +- **代码沙箱**: 安全代码执行环境 + +## 10. 基础设施 +- **数据存储**: MySQL主库+Redis缓存+PG向量库+Elasticsearch(可选) +- **智能路由代理**: 国内/香港双通道智能分流,容器透明代理 +- **部署**: Docker容器化,Nginx反向代理,FastAPI+React前后端分离 +- **安全**: JWT鉴权、HTTPS加密、操作审计日志 + +## AI回复规范 +1. **回复格式**: 使用Markdown(标题 `##`/`###`、加粗 `**`、列表 `-`/`1.`、代码块等) +2. **层次分明**: 使用标题分隔段落,确保信息有层次 +3. **平台优先**: 回答问题时优先引用本平台功能,而非泛泛建议 +4. **准确具体**: 基于平台文档给出具体操作路径、配置方法、API示例 +5. **结构清晰**: 复杂问题先概括再分点,使用编号列表或表格 +""" + + +def get_platform_knowledge() -> str: + """获取平台全功能知识库文本,注入到AI对话系统提示中""" + return PLATFORM_KNOWLEDGE diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index c468a95..3e16e6d 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -261,7 +261,7 @@ export const knowledgeAPI = { getDocument: (baseId: string, docId: string) => get(`/knowledge/bases/${baseId}/documents/${docId}`), - uploadDocument: (baseId: string, data: any) => post(`/knowledge/bases/${baseId}/documents/`, data), + uploadDocument: (baseId: string, data: any) => post(`/knowledge/bases/${baseId}/documents`, data), deleteDocument: (baseId: string, docId: string) => del(`/knowledge/bases/${baseId}/documents/${docId}`), diff --git a/frontend/src/pages/front/KnowledgePage.tsx b/frontend/src/pages/front/KnowledgePage.tsx index fa93901..6a1a4d1 100644 --- a/frontend/src/pages/front/KnowledgePage.tsx +++ b/frontend/src/pages/front/KnowledgePage.tsx @@ -617,10 +617,12 @@ const UploadDocModal: React.FC<{ const [fileSize, setFileSize] = useState(102400); const [uploading, setUploading] = useState(false); const [progress, setProgress] = useState(0); + const [selectedFile, setSelectedFile] = useState(null); const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + setSelectedFile(file); setFilename(file.name); const ext = file.name.split('.').pop()?.toLowerCase() || 'txt'; setFileType(ext); @@ -633,7 +635,6 @@ const UploadDocModal: React.FC<{ setUploading(true); setProgress(0); - // 模拟上传进度: 0 -> 90% in ~2 seconds (保留10%给成功动画) const interval = setInterval(() => { setProgress(prev => { if (prev >= 90) return prev; @@ -642,9 +643,25 @@ const UploadDocModal: React.FC<{ }, 150); try { - await onUpload(kbId, filename.trim(), fileType, fileSize); + const formData = new FormData(); + if (selectedFile) { + formData.append('file', selectedFile); + } + formData.append('filename', filename.trim()); + formData.append('file_type', fileType); + formData.append('file_size', String(fileSize)); + + const token = localStorage.getItem('aiflow_token'); + const res = await fetch(`/app/api/knowledge/bases/${kbId}/documents`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}` }, + body: formData, + }); + const data = await res.json(); + if (!res.ok || data.code !== 0) { + throw new Error(data.message || data.detail || '上传失败'); + } clearInterval(interval); - // 上传成功后先显示100% 800ms,让进度条动画完整可见,再关闭弹窗 setProgress(100); setTimeout(() => { onClose();