From 5d93ac5a3247168df02eced5699ae895c2279c92 Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Sat, 23 Aug 2025 21:49:45 +0100 Subject: [PATCH] add Article.is_platform_knowledge_base --- ...cle_is_platform_knowledge_base_and_more.py | 28 + api/products/models.py | 5 +- doc/ai-chat.md | 786 +----------------- 3 files changed, 35 insertions(+), 784 deletions(-) create mode 100644 api/products/migrations/0106_article_is_platform_knowledge_base_and_more.py diff --git a/api/products/migrations/0106_article_is_platform_knowledge_base_and_more.py b/api/products/migrations/0106_article_is_platform_knowledge_base_and_more.py new file mode 100644 index 0000000..178de73 --- /dev/null +++ b/api/products/migrations/0106_article_is_platform_knowledge_base_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.23 on 2025-08-23 20:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0105_chatsession_chatmessage'), + ] + + operations = [ + migrations.AddField( + model_name='article', + name='is_platform_knowledge_base', + field=models.BooleanField(default=False, verbose_name='是否为平台知识库'), + ), + migrations.AlterField( + model_name='article', + name='options', + field=models.TextField(blank=True, default=''), + ), + migrations.AlterField( + model_name='article', + name='url', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/api/products/models.py b/api/products/models.py index dd9b3a0..66440b5 100644 --- a/api/products/models.py +++ b/api/products/models.py @@ -138,8 +138,9 @@ class Article(models.Model): title = models.CharField(max_length=128, null=True, blank=True, verbose_name="标题") body = models.TextField() tenant = models.ForeignKey(Tenant, related_name="articles", on_delete=models.CASCADE, null=True, blank=True) - options = models.TextField(default='') - url = models.TextField(null=True) + options = models.TextField(default='', blank=True) + url = models.TextField(null=True, blank=True) + is_platform_knowledge_base = models.BooleanField(default=False, verbose_name="是否为平台知识库") class AssetFile(models.Model): filename = models.TextField(verbose_name="文件名") diff --git a/doc/ai-chat.md b/doc/ai-chat.md index 25c2d6d..4918926 100644 --- a/doc/ai-chat.md +++ b/doc/ai-chat.md @@ -47,36 +47,6 @@ ### 4.1 AI模型集成 -#### 4.1.1 模型选择 -- **主要模型**: Kimi K2 API ✅ **已实现** -- **备选模型**: 智谱AI (ChatGLM)、百度文心一言 -- **理由**: Kimi K2在中文理解、多模态处理和联网搜索方面表现优秀,API稳定可靠,特别适合品牌客服场景 - -#### 4.1.2 API集成架构 ✅ **已实现** -```python -# api/products/aichat.py -class AIChatService: - def __init__(self, api_key: str = None, base_url: str = None): - self.api_key = api_key or getattr(settings, 'KIMI_API_KEY', None) - self.base_url = base_url or getattr(settings, 'KIMI_API_URL', None) - self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) - - def chat(self, user_message: str) -> str: - # 调用Moonshot API,支持工具调用 - response = self.client.chat.completions.create( - model=self.model, - messages=self.conversation_history, - tools=self.tools, - tool_choice="auto" - ) - return self._process_response(response) -``` - -#### 4.1.3 工具调用系统 ✅ **已实现** -- **工具定义**: `start_qr_scan` 二维码扫描工具 -- **工具执行**: 模拟5秒扫描过程,返回验证结果 -- **工具集成**: 完整的工具调用流程,支持多轮对话 - #### 4.1.4 智能能力调度系统 - **架构选择**: 直接以Tool形式接入Kimi K2,无需复杂路由 - **能力分类**: 知识库检索工具、二维码扫描工具、一般问答 @@ -91,371 +61,13 @@ class AIChatService: - **更新机制**: 支持实时更新和版本控制 - **成本优化**: 本地处理,减少API调用 -#### 4.1.6 RAG架构设计 -```python -class AIChatService: - def __init__(self): - self.client = OpenAI(api_key=KIMI_API_KEY, base_url=KIMI_API_URL) - self.knowledge_base = LocalKnowledgeBase() # 本地知识库 - self.tools = self._define_tools() - - def chat(self, message: str) -> str: - # 步骤1:从本地知识库检索相关信息 - relevant_docs = self.knowledge_base.search(message, k=3) - - # 步骤2:构建增强的上下文 - enhanced_context = self._build_enhanced_context(message, relevant_docs) - - # 步骤3:使用Kimi K2生成回答(基于检索到的信息) - response = self.client.chat.completions.create( - model=self.model, - messages=[ - {"role": "system", "content": self.system_prompt + enhanced_context}, - {"role": "user", "content": message} - ], - tools=self.tools, - tool_choice="auto" - ) - - # 步骤4:处理工具调用(如果需要) - if response.choices[0].message.tool_calls: - # 执行工具调用逻辑... - pass - - # 步骤5:将问答对保存到知识库(可选) - self.knowledge_base.add_qa_pair(message, response.choices[0].message.content) - - return response.choices[0].message.content - - def _build_enhanced_context(self, query: str, docs: List[Document]) -> str: - """构建增强的上下文信息""" - if not docs: - return "" - - context = "\n\n相关参考信息:\n" - for i, doc in enumerate(docs, 1): - context += f"{i}. {doc.page_content}\n" - - context += f"\n请基于以上信息回答用户问题:{query}" - return context -``` - **RAG流程**: - **检索(Retrieval)**:从本地知识库找到相关信息 - **增强(Augmentation)**:将检索到的信息作为上下文 - **生成(Generation)**:Kimi K2基于增强的上下文生成回答 -#### 4.1.7 本地知识库实现 -```python -class LocalKnowledgeBase: - def __init__(self): - # 使用轻量级向量数据库 - self.vector_store = Chroma( - embedding_function=OpenAIEmbeddings(api_key=KIMI_API_KEY), - persist_directory="./knowledge_base" - ) - self.file_storage = FileStorage() # 文件存储 - - def search(self, query: str, k: int = 3, threshold: float = 0.5) -> List[Document]: - """搜索本地知识库,返回相关文档列表""" - # 语义搜索 - results = self.vector_store.similarity_search_with_score(query, k=k) - - # 过滤低置信度结果,但保留更多候选 - filtered_results = [r for r in results if r[1] > threshold] - - # 转换为Document对象列表 - documents = [] - for doc, score in filtered_results: - documents.append(Document( - page_content=doc.page_content, - metadata={ - **doc.metadata, - 'similarity_score': score - } - )) - - return documents - - def add_content(self, content: str, content_type: str, metadata: dict): - """添加内容到知识库""" - # 文本内容直接向量化 - if content_type == 'text': - self.vector_store.add_texts([content], [metadata]) - - # 图片内容使用OCR提取文本 - elif content_type == 'image': - text = self._extract_text_from_image(content) - self.vector_store.add_texts([text], [metadata]) - - # 视频内容提取关键帧和音频 - elif content_type == 'video': - frames = self._extract_key_frames(content) - audio_text = self._extract_audio_text(content) - self.vector_store.add_texts([audio_text], [metadata]) - - def add_qa_pair(self, question: str, answer: str): - """添加问答对到知识库""" - qa_text = f"问题:{question}\n答案:{answer}" - metadata = { - 'type': 'qa_pair', - 'question': question, - 'answer': answer, - 'created_at': datetime.now().isoformat() - } - self.vector_store.add_texts([qa_text], [metadata]) -``` - -**技术特点**: -- **轻量级**:使用Chroma向量数据库,无需复杂部署 -- **多模态**:支持文字、图片、视频内容 -- **实时更新**:支持动态添加和更新内容 -- **成本优化**:本地处理,减少API调用 - -#### 4.1.8 完整RAG实现示例 -```python -from langchain.text_splitter import RecursiveCharacterTextSplitter -from langchain.embeddings import OpenAIEmbeddings -from langchain.vectorstores import Chroma -from langchain.document_loaders import TextLoader, ImageLoader, VideoLoader - -class RAGService: - def __init__(self): - self.embeddings = OpenAIEmbeddings(api_key=KIMI_API_KEY) - self.text_splitter = RecursiveCharacterTextSplitter( - chunk_size=1000, - chunk_overlap=200 - ) - self.vector_store = Chroma( - embedding_function=self.embeddings, - persist_directory="./knowledge_base" - ) - - def add_document(self, file_path: str, content_type: str): - """添加文档到知识库""" - if content_type == 'text': - loader = TextLoader(file_path) - elif content_type == 'image': - loader = ImageLoader(file_path) - elif content_type == 'video': - loader = VideoLoader(file_path) - else: - raise ValueError(f"不支持的内容类型: {content_type}") - - documents = loader.load() - # 分块处理 - chunks = self.text_splitter.split_documents(documents) - # 添加到向量数据库 - self.vector_store.add_documents(chunks) - - def retrieve_context(self, query: str, k: int = 3) -> str: - """检索相关上下文""" - docs = self.vector_store.similarity_search(query, k=k) - context = "\n\n".join([doc.page_content for doc in docs]) - return context - - def generate_answer(self, query: str, context: str) -> str: - """使用Kimi K2生成回答""" - enhanced_prompt = f""" -基于以下参考信息回答用户问题: - -参考信息: -{context} - -用户问题:{query} - -请基于参考信息提供准确、详细的回答。如果参考信息不足以回答问题,请明确说明。 -""" - - response = self.client.chat.completions.create( - model="kimi-k2-0711-preview", - messages=[ - {"role": "system", "content": "你是徵象防伪验证平台的AI助手,请基于提供的参考信息回答问题。"}, - {"role": "user", "content": enhanced_prompt} - ], - temperature=0.7, - max_tokens=2000 - ) - - return response.choices[0].message.content - -# 使用示例 -rag_service = RAGService() - -# 添加文档 -rag_service.add_document("产品说明.txt", "text") -rag_service.add_document("防伪标识.jpg", "image") -rag_service.add_document("操作演示.mp4", "video") - -# 问答流程 -query = "如何验证产品真伪?" -context = rag_service.retrieve_context(query) -answer = rag_service.generate_answer(query, context) -``` - -**RAG优势**: -- **信息可控**:检索到的信息来自平台知识库 -- **回答质量**:Kimi K2基于准确信息生成高质量回答 -- **成本优化**:避免重复的API调用,知识库一次构建多次使用 -- **专业性强**:平台专业知识 + AI生成能力 - -#### 4.1.9 架构选择说明 - -**为什么选择直接以Tool形式接入Kimi K2?** - -1. **符合Kimi K2设计理念** - - Kimi K2本身就是为工具调用设计的 - - 原生支持Function Calling,无需额外路由层 - - AI能根据上下文智能选择合适的工具 - -2. **实现简单,维护成本低** - - 代码逻辑清晰,易于理解和维护 - - 避免复杂的if-else路由规则 - - 减少系统复杂度,降低出错概率 - -3. **足够灵活,扩展性好** - - 可以轻松添加新的工具 - - 工具优先级通过描述和系统提示词控制 - - 支持复杂的工具组合调用 - -4. **性能优势** - - 减少额外的路由判断逻辑 - - 直接利用Kimi K2的智能判断能力 - - 避免不必要的中间层处理 - -**具体实现方式**: -```python -def _define_tools(self): - return [ - { - "type": "function", - "function": { - "name": "search_knowledge_base", - "description": "搜索徵象平台知识库,获取产品信息、防伪验证方法、技术文档等相关内容。当用户询问平台相关问题时,优先使用此工具。", - "parameters": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "搜索查询,支持中文关键词" - }, - "content_type": { - "type": "string", - "enum": ["all", "text", "image", "video"], - "description": "内容类型过滤" - } - }, - "required": ["query"] - } - } - }, - { - "type": "function", - "function": { - "name": "start_qr_scan", - "description": "启动二维码扫描功能,用于产品防伪验证。当用户需要验证产品真伪时使用。", - "parameters": { - "type": "object", - "properties": {}, - "required": [] - } - } - } - ] -``` - -**系统提示词优化**: -```python -system_prompt = """你是一个专业的徵象防伪验证平台AI客服助手。 - -使用策略: -1. 当用户询问平台相关问题时,优先使用search_knowledge_base工具搜索知识库 -2. 当用户需要验证产品真伪时,使用start_qr_scan工具启动扫描 -3. 基于检索到的信息,结合你的知识,提供准确、详细的回答 -4. 如果知识库信息不足,可以结合你的通用知识回答,但要明确说明信息来源 - -工具优先级:知识库搜索 > 二维码扫描 > 一般问答 -""" -``` - ### 4.2 数据模型扩展 -#### 4.2.1 新增数据表 -```python -# api/products/models.py - -class AIConfig(models.Model): - """AI配置表""" - tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) - model_provider = models.CharField(max_length=50, default='kimi') - api_key = models.CharField(max_length=255) - model_name = models.CharField(max_length=100, default='kimi-k2') - -class BrandKnowledge(models.Model): - """品牌知识库""" - tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) - title = models.CharField(max_length=200) - content = models.TextField() - content_type = models.CharField(max_length=20) # text, image, video, link - file_url = models.URLField(null=True, blank=True) - priority = models.IntegerField(default=0) - created_at = models.DateTimeField(auto_now_add=True) - -class PlatformKnowledge(models.Model): - """平台知识库""" - title = models.CharField(max_length=200) - content = models.TextField() - content_type = models.CharField(max_length=20, choices=[ - ('platform_intro', '平台介绍'), - ('service_guide', '服务指南'), - ('faq', '常见问题') - ]) - ai_summary = models.TextField(null=True, blank=True, verbose_name="AI摘要版本") - ai_segments = models.JSONField(default=dict, verbose_name="AI分段内容") - ai_keywords = models.JSONField(default=list, verbose_name="AI关键词") - is_platform_content = models.BooleanField(default=True) - ai_priority = models.IntegerField(default=0) - created_at = models.DateTimeField(auto_now_add=True) - -class ChatSession(models.Model): - """聊天会话""" - session_id = models.CharField(max_length=100, unique=True) - tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) - user_openid = models.CharField(max_length=100, null=True) - created_at = models.DateTimeField(auto_now_add=True) - last_active = models.DateTimeField(auto_now=True) - -class ChatMessage(models.Model): - """聊天消息""" - session = models.ForeignKey(ChatSession, on_delete=models.CASCADE) - role = models.CharField(max_length=20) # user, assistant, system - content = models.TextField() - content_type = models.CharField(max_length=20, default='text') - metadata = models.JSONField(default=dict) - created_at = models.DateTimeField(auto_now_add=True) -``` - -#### 4.2.2 现有模型扩展 -```python -# 扩展CodeBatch模型 -class CodeBatch(models.Model): - # ... 现有字段 ... - enable_ai_chat = models.BooleanField(default=False, verbose_name="启用AI聊天") - ai_chat_config = models.JSONField(null=True, blank=True, verbose_name="AI聊天配置") - -# 扩展Tenant模型 -class Tenant(models.Model): - # ... 现有字段 ... - ai_brand_name = models.CharField(max_length=100, null=True, blank=True, verbose_name="AI品牌名称") - ai_welcome_message = models.TextField(null=True, blank=True, verbose_name="AI欢迎语") - ai_theme_colors = models.JSONField(null=True, blank=True, verbose_name="AI主题色彩") - -# TODO: 在MiniProgramContent模型中添加enable_ai_chat字段 -# class MiniProgramContent(models.Model): -# # ... 现有字段 ... -# enable_ai_chat = models.BooleanField(default=False, verbose_name="启用AI客服") -``` - ### 4.3 后端API设计 #### 4.3.1 AI聊天API @@ -507,397 +119,6 @@ class AIChatView(BaseView): 'messages': [self._serialize_message(m) for m in messages] }) -class BrandKnowledgeView(BaseView): - name = 'brand-knowledge' - auth_check = 'tenant' - - def post(self, request): - """上传品牌知识""" - title = request.data.get('title') - content = request.data.get('content') - content_type = request.data.get('content_type', 'text') - file_url = request.data.get('file_url') - - knowledge = BrandKnowledge.objects.create( - tenant=request.tenant, - title=title, - content=content, - content_type=content_type, - file_url=file_url - ) - - return JsonResponse({'id': knowledge.id}) -``` - -#### 4.3.2 配置管理API -```python -class AIConfigView(BaseView): - name = 'ai-config' - auth_check = 'tenant' - - def get(self, request): - """获取AI配置""" - config = AIConfig.objects.filter(tenant=request.tenant).first() - if not config: - config = AIConfig.objects.create(tenant=request.tenant) - - return JsonResponse({ - 'model_provider': config.model_provider, - 'model_name': config.model_name, - 'temperature': config.temperature, - 'max_tokens': config.max_tokens, - 'is_active': config.is_active - }) - - def patch(self, request): - """更新AI配置""" - config = AIConfig.objects.filter(tenant=request.tenant).first() - if not config: - config = AIConfig.objects.create(tenant=request.tenant) - - for field in ['model_provider', 'model_name', 'temperature', 'max_tokens', 'is_active']: - if field in request.data: - setattr(config, field, request.data[field]) - - config.save() - return JsonResponse({'status': 'success'}) -``` - -### 4.4 前端界面设计 - -#### 4.4.1 小程序AI聊天界面 -```javascript -// scanner/pages/chat/chat.js -Page({ - data: { - messages: [], - inputValue: '', - sessionId: null, - tenant: null, - loading: false - }, - - onLoad(options) { - this.initChat(options); - }, - - async initChat(options) { - // 获取租户信息 - const tenant = await this.getTenantInfo(options.tenant); - this.setData({ tenant }); - - // 创建或获取会话 - const sessionId = await this.createChatSession(tenant.id); - this.setData({ sessionId }); - - // 显示欢迎消息 - this.showWelcomeMessage(tenant); - - // 如果有验证结果,显示相关产品信息 - if (options.serial_code) { - await this.showProductInfo(options.serial_code); - } - }, - - async sendMessage() { - if (!this.data.inputValue.trim()) return; - - const message = this.data.inputValue; - this.addMessage('user', message); - this.setData({ inputValue: '', loading: true }); - - try { - const response = await this.callAIChat(message); - this.addMessage('assistant', response); - } catch (error) { - this.addMessage('system', '抱歉,AI服务暂时不可用,请稍后再试。'); - } finally { - this.setData({ loading: false }); - } - }, - - async callAIChat(message) { - const response = await wx.request({ - url: `${this.data.tenant.server_url}/api/v1/ai-chat/`, - method: 'POST', - data: { - session_id: this.data.sessionId, - message: message, - context: this.getContext() - }, - header: { - 'Authorization': `token ${this.data.tenant.token}` - } - }); - - return response.data.response; - } -}); -``` - -#### 4.4.2 Web管理端界面 -```vue - - -``` - -### 4.5 内容管理系统 - -#### 4.5.1 知识库管理 -- 支持文本、图片、视频、链接等多种内容类型 -- 内容优先级设置 -- 内容分类标签 -- 版本控制 - -#### 4.5.2 品牌定制 -- 品牌色调配置 -- 欢迎语定制 -- 头像和图标上传 -- 回答风格调整 - -#### 4.5.3 首页AI客服开关 -- TODO: 在MiniProgramContent中添加enable_ai_chat字段 -- 后台管理:简单的on/off开关 -- 小程序端:判断bool值,如果为true则跳转到chat页面 -- 实现方式:几行代码即可完成 - -#### 4.5.3 平台知识库管理(简化版) -- 平台介绍内容(4000字)的基础分块 -- 分层内容策略:摘要、完整两个层次 -- 基于关键词的简单内容选择 -- 基础内容压缩,避免信息过载 - -#### 4.5.4 智能内容处理(简化版) -- 基于LangChain的文档基础预处理 -- 简单分块策略:按段落和句子分割 -- 基础内容压缩:摘要和完整内容 -- 固定上下文长度,简化Token管理 - -## 5. 技术实现细节 - -#### 5.1.3 智能能力路由(简化版) -```python -def route_capability(self, query, context): - # 基于规则的快速路由 - capability = self._rule_based_routing(query) - - # 根据能力类型构建相应的Prompt - if capability == "rag_knowledge": - return self._build_rag_prompt(query, context) - elif capability == "platform_service": - return self._build_platform_prompt(query, context) - else: - return self._build_general_prompt(query, context) -``` - -### 5.2 多模态内容处理 - -#### 5.2.1 内容类型支持 -- **文本**: 直接返回 -- **图片**: 上传到OSS,返回URL -- **视频**: 支持微信视频号链接 -- **链接**: 小程序码、商城链接等 -- **富文本**: Markdown格式支持 - -#### 5.2.2 内容渲染 -```javascript -// 内容渲染组件 -function renderContent(content, type) { - switch (type) { - case 'text': - return {content}; - case 'image': - return ; - case 'video': - return