add Article.is_platform_knowledge_base
This commit is contained in:
parent
bd2d2eb358
commit
5d93ac5a32
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -138,8 +138,9 @@ class Article(models.Model):
|
|||||||
title = models.CharField(max_length=128, null=True, blank=True, verbose_name="标题")
|
title = models.CharField(max_length=128, null=True, blank=True, verbose_name="标题")
|
||||||
body = models.TextField()
|
body = models.TextField()
|
||||||
tenant = models.ForeignKey(Tenant, related_name="articles", on_delete=models.CASCADE, null=True, blank=True)
|
tenant = models.ForeignKey(Tenant, related_name="articles", on_delete=models.CASCADE, null=True, blank=True)
|
||||||
options = models.TextField(default='')
|
options = models.TextField(default='', blank=True)
|
||||||
url = models.TextField(null=True)
|
url = models.TextField(null=True, blank=True)
|
||||||
|
is_platform_knowledge_base = models.BooleanField(default=False, verbose_name="是否为平台知识库")
|
||||||
|
|
||||||
class AssetFile(models.Model):
|
class AssetFile(models.Model):
|
||||||
filename = models.TextField(verbose_name="文件名")
|
filename = models.TextField(verbose_name="文件名")
|
||||||
|
|||||||
786
doc/ai-chat.md
786
doc/ai-chat.md
@ -47,36 +47,6 @@
|
|||||||
|
|
||||||
### 4.1 AI模型集成
|
### 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 智能能力调度系统
|
#### 4.1.4 智能能力调度系统
|
||||||
- **架构选择**: 直接以Tool形式接入Kimi K2,无需复杂路由
|
- **架构选择**: 直接以Tool形式接入Kimi K2,无需复杂路由
|
||||||
- **能力分类**: 知识库检索工具、二维码扫描工具、一般问答
|
- **能力分类**: 知识库检索工具、二维码扫描工具、一般问答
|
||||||
@ -91,371 +61,13 @@ class AIChatService:
|
|||||||
- **更新机制**: 支持实时更新和版本控制
|
- **更新机制**: 支持实时更新和版本控制
|
||||||
- **成本优化**: 本地处理,减少API调用
|
- **成本优化**: 本地处理,减少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流程**:
|
**RAG流程**:
|
||||||
- **检索(Retrieval)**:从本地知识库找到相关信息
|
- **检索(Retrieval)**:从本地知识库找到相关信息
|
||||||
- **增强(Augmentation)**:将检索到的信息作为上下文
|
- **增强(Augmentation)**:将检索到的信息作为上下文
|
||||||
- **生成(Generation)**:Kimi K2基于增强的上下文生成回答
|
- **生成(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 数据模型扩展
|
||||||
|
|
||||||
#### 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 后端API设计
|
||||||
|
|
||||||
#### 4.3.1 AI聊天API
|
#### 4.3.1 AI聊天API
|
||||||
@ -507,397 +119,6 @@ class AIChatView(BaseView):
|
|||||||
'messages': [self._serialize_message(m) for m in messages]
|
'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
|
|
||||||
<!-- web/src/views/ai-config.vue -->
|
|
||||||
<template>
|
|
||||||
<div class="ai-config">
|
|
||||||
<CCard>
|
|
||||||
<CCardHeader>
|
|
||||||
<h4>AI聊天配置</h4>
|
|
||||||
</CCardHeader>
|
|
||||||
<CCardBody>
|
|
||||||
<CForm>
|
|
||||||
<CFormGroup label="AI模型提供商">
|
|
||||||
<CSelect v-model="config.model_provider">
|
|
||||||
<option value="kimi">Kimi K2</option>
|
|
||||||
<option value="zhipu">智谱AI</option>
|
|
||||||
<option value="baidu">百度文心一言</option>
|
|
||||||
</CSelect>
|
|
||||||
</CFormGroup>
|
|
||||||
|
|
||||||
<CFormGroup label="API密钥">
|
|
||||||
<CInput v-model="config.api_key" type="password" />
|
|
||||||
</CFormGroup>
|
|
||||||
|
|
||||||
<CFormGroup label="模型名称">
|
|
||||||
<CInput v-model="config.model_name" />
|
|
||||||
</CFormGroup>
|
|
||||||
|
|
||||||
<CFormGroup label="温度">
|
|
||||||
<CInput v-model="config.temperature" type="number" step="0.1" min="0" max="2" />
|
|
||||||
</CFormGroup>
|
|
||||||
|
|
||||||
<CFormGroup label="最大Token数">
|
|
||||||
<CInput v-model="config.max_tokens" type="number" />
|
|
||||||
</CFormGroup>
|
|
||||||
|
|
||||||
<CButton color="primary" @click="saveConfig">保存配置</CButton>
|
|
||||||
</CForm>
|
|
||||||
</CCardBody>
|
|
||||||
</CCard>
|
|
||||||
|
|
||||||
<CCard class="mt-4">
|
|
||||||
<CCardHeader>
|
|
||||||
<h4>品牌知识库</h4>
|
|
||||||
<CButton color="success" @click="showAddKnowledge">添加知识</CButton>
|
|
||||||
</CCardHeader>
|
|
||||||
<CCardBody>
|
|
||||||
<CTable :items="knowledgeList" :fields="knowledgeFields">
|
|
||||||
<template #actions="{ item }">
|
|
||||||
<CButton color="danger" size="sm" @click="deleteKnowledge(item.id)">删除</CButton>
|
|
||||||
</template>
|
|
||||||
</CTable>
|
|
||||||
</CCardBody>
|
|
||||||
</CCard>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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 <Text>{content}</Text>;
|
|
||||||
case 'image':
|
|
||||||
return <Image src={content} mode="widthFix" />;
|
|
||||||
case 'video':
|
|
||||||
return <Video src={content} />;
|
|
||||||
case 'link':
|
|
||||||
return <Link href={content.url} text={content.text} />;
|
|
||||||
case 'rich_text':
|
|
||||||
return <RichText content={content} />;
|
|
||||||
default:
|
|
||||||
return <Text>{content}</Text>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 会话管理
|
|
||||||
|
|
||||||
#### 5.3.1 会话状态
|
|
||||||
- 会话创建和销毁
|
|
||||||
- 消息历史记录
|
|
||||||
- 上下文保持
|
|
||||||
- 会话超时处理
|
|
||||||
|
|
||||||
#### 5.3.2 用户身份识别
|
|
||||||
- 微信OpenID绑定
|
|
||||||
- 匿名会话支持
|
|
||||||
- 用户偏好记录
|
|
||||||
|
|
||||||
### 5.4 智能调度系统(简化版)
|
|
||||||
|
|
||||||
#### 5.4.1 能力处理器架构
|
|
||||||
- 基础能力处理器注册机制
|
|
||||||
- 简单的能力降级策略
|
|
||||||
- 固定路由规则
|
|
||||||
|
|
||||||
#### 5.4.2 调度策略
|
|
||||||
- 基于关键词的能力选择
|
|
||||||
- 基础异常处理和回退
|
|
||||||
- 简单性能监控
|
|
||||||
- 后续版本支持高级功能
|
|
||||||
|
|
||||||
### 5.5 上下文长度管理
|
|
||||||
|
|
||||||
#### 5.5.1 当前问题
|
|
||||||
- **无限累积**:对话历史无限制增长
|
|
||||||
- **Token超限**:可能超出API的token限制
|
|
||||||
- **内存占用**:长时间对话占用大量内存
|
|
||||||
- **API失败**:超长历史可能导致API调用失败
|
|
||||||
|
|
||||||
#### 5.5.2 优化策略
|
|
||||||
```python
|
|
||||||
class AIChatService:
|
|
||||||
def __init__(self, api_key: str = None, base_url: str = None):
|
|
||||||
# ... 现有代码 ...
|
|
||||||
self.max_history_length = 20 # 最大历史记录数
|
|
||||||
self.max_tokens_per_message = 1000 # 每条消息最大token数
|
|
||||||
|
|
||||||
def _truncate_history(self):
|
|
||||||
"""截断对话历史,保持上下文长度"""
|
|
||||||
if len(self.conversation_history) > self.max_history_length:
|
|
||||||
# 保留系统提示词和最近的对话
|
|
||||||
system_message = self.conversation_history[0] # 系统提示词
|
|
||||||
recent_messages = self.conversation_history[-self.max_history_length+1:]
|
|
||||||
self.conversation_history = [system_message] + recent_messages
|
|
||||||
|
|
||||||
def _estimate_tokens(self, text: str) -> int:
|
|
||||||
"""估算文本的token数量(简化版)"""
|
|
||||||
# 中文约1.5字符=1token,英文约4字符=1token
|
|
||||||
chinese_chars = len([c for c in text if '\u4e00' <= c <= '\u9fff'])
|
|
||||||
english_chars = len(text) - chinese_chars
|
|
||||||
return int(chinese_chars / 1.5 + english_chars / 4)
|
|
||||||
|
|
||||||
def _smart_truncate(self):
|
|
||||||
"""智能截断:基于token数量而非消息数量"""
|
|
||||||
total_tokens = sum(self._estimate_tokens(msg['content']) for msg in self.conversation_history)
|
|
||||||
max_total_tokens = 4000 # 预留1000token给AI回复
|
|
||||||
|
|
||||||
if total_tokens > max_total_tokens:
|
|
||||||
# 保留系统提示词和最近的对话,直到token数合适
|
|
||||||
system_message = self.conversation_history[0]
|
|
||||||
truncated_history = [system_message]
|
|
||||||
|
|
||||||
for msg in reversed(self.conversation_history[1:]):
|
|
||||||
truncated_history.insert(1, msg)
|
|
||||||
current_tokens = sum(self._estimate_tokens(m['content']) for m in truncated_history)
|
|
||||||
if current_tokens > max_total_tokens:
|
|
||||||
truncated_history.pop(1) # 移除刚添加的消息
|
|
||||||
break
|
|
||||||
|
|
||||||
self.conversation_history = truncated_history
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.5.3 分层历史策略
|
|
||||||
```python
|
|
||||||
def _build_context_aware_history(self, user_message: str) -> List[Dict]:
|
|
||||||
"""构建上下文感知的对话历史"""
|
|
||||||
# 策略1:保留最近的N轮对话
|
|
||||||
recent_messages = self.conversation_history[-10:] # 最近10轮
|
|
||||||
|
|
||||||
# 策略2:保留关键信息(如工具调用结果)
|
|
||||||
important_messages = [msg for msg in self.conversation_history
|
|
||||||
if msg.get('role') == 'tool' or '重要' in msg.get('content', '')]
|
|
||||||
|
|
||||||
# 策略3:动态调整:根据用户问题复杂度决定保留多少历史
|
|
||||||
if '刚才' in user_message or '之前' in user_message:
|
|
||||||
# 用户引用之前内容,保留更多历史
|
|
||||||
context_messages = self.conversation_history[-15:]
|
|
||||||
else:
|
|
||||||
# 新话题,保留较少历史
|
|
||||||
context_messages = self.conversation_history[-5:]
|
|
||||||
|
|
||||||
return [self.conversation_history[0]] + context_messages # 系统提示词 + 上下文
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5.5.4 错误处理和降级
|
|
||||||
```python
|
|
||||||
def chat(self, user_message: str) -> str:
|
|
||||||
try:
|
|
||||||
# 智能截断历史
|
|
||||||
self._smart_truncate()
|
|
||||||
|
|
||||||
# 调用API
|
|
||||||
response = self.client.chat.completions.create(
|
|
||||||
model=self.model,
|
|
||||||
messages=self.conversation_history,
|
|
||||||
tools=self.tools,
|
|
||||||
tool_choice="auto",
|
|
||||||
temperature=0.7,
|
|
||||||
max_tokens=2000
|
|
||||||
)
|
|
||||||
|
|
||||||
# ... 处理响应 ...
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
if "context_length_exceeded" in str(e) or "token_limit" in str(e):
|
|
||||||
# Token超限,截断历史重试
|
|
||||||
self._truncate_history()
|
|
||||||
return self.chat(user_message) # 递归重试
|
|
||||||
else:
|
|
||||||
return f"AI服务调用失败: {str(e)}"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. 安全考虑
|
## 7. 安全考虑
|
||||||
|
|
||||||
### 7.1 数据安全
|
### 7.1 数据安全
|
||||||
@ -938,9 +159,6 @@ def chat(self, user_message: str) -> str:
|
|||||||
- Week 3: 完成小程序AI聊天集成和内容管理
|
- Week 3: 完成小程序AI聊天集成和内容管理
|
||||||
- Week 4: 完成测试、优化和上线
|
- Week 4: 完成测试、优化和上线
|
||||||
|
|
||||||
### 10.3 Backup
|
|
||||||
- 多AI服务商备选方案
|
|
||||||
|
|
||||||
### 11.2 开发周期
|
### 11.2 开发周期
|
||||||
- **总周期**: 4周(压缩版)
|
- **总周期**: 4周(压缩版)
|
||||||
- **核心功能**: AI聊天、RAG知识检索、工具调用、小程序集成、基础内容管理
|
- **核心功能**: AI聊天、RAG知识检索、工具调用、小程序集成、基础内容管理
|
||||||
@ -967,3 +185,7 @@ def chat(self, user_message: str) -> str:
|
|||||||
- 降低维护成本
|
- 降低维护成本
|
||||||
- 提高系统稳定性
|
- 提高系统稳定性
|
||||||
- 保持架构简洁性
|
- 保持架构简洁性
|
||||||
|
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- context length management
|
||||||
Loading…
x
Reference in New Issue
Block a user