feat: RAG inline citations, source highlighting, and admin panel
- Increase RAG retrieval from 5 to 50 docs with relevance threshold (0.15) - LLM inline citations with [来源N] format and reference list - Clickable citation links scroll to source cards with highlight animation - Source previews with full chunk text and answer-matched highlighting - Message-scoped source IDs to fix cross-round citation targeting - Admin panel pages (knowledge, users, forum, course, settings) - Add rehype-raw dependency for HTML-in-markdown rendering Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
"""
|
||||
后台管理API
|
||||
"""
|
||||
from typing import List, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, desc
|
||||
|
||||
from ..core.database import get_db
|
||||
from ..core.security import get_current_user
|
||||
from ..models.user import User
|
||||
from ..models.chat import ChatSession, ChatMessage
|
||||
from ..models.document import Document, DocumentChunk
|
||||
from ..models.knowledge_base import KnowledgeBase
|
||||
from ..models.forum import ForumCategory, ForumPost, ForumReply
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["后台管理"])
|
||||
|
||||
|
||||
async def require_admin(
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
) -> User:
|
||||
"""验证当前用户是否为管理员"""
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user or not user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="需要管理员权限"
|
||||
)
|
||||
return user
|
||||
|
||||
|
||||
# ===== 数据概览 =====
|
||||
|
||||
class AdminDashboardStats(BaseModel):
|
||||
total_users: int
|
||||
active_users_7d: int
|
||||
total_sessions: int
|
||||
total_messages: int
|
||||
total_documents: int
|
||||
total_knowledge_bases: int
|
||||
total_forum_posts: int
|
||||
total_forum_replies: int
|
||||
total_generated_images: int
|
||||
|
||||
|
||||
class AdminTrendItem(BaseModel):
|
||||
date: str
|
||||
count: int
|
||||
|
||||
|
||||
@router.get("/dashboard", response_model=AdminDashboardStats)
|
||||
async def get_dashboard(
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取管理后台仪表盘数据"""
|
||||
total_users = db.query(func.count(User.id)).scalar() or 0
|
||||
|
||||
# 7天内活跃用户(有消息的用户)
|
||||
week_ago = datetime.utcnow() - timedelta(days=7)
|
||||
active_users_7d = db.query(func.count(func.distinct(ChatMessage.session_id))).join(
|
||||
ChatSession
|
||||
).filter(
|
||||
ChatMessage.created_at >= week_ago
|
||||
).scalar() or 0
|
||||
# 更准确的计算:有消息的独立用户数
|
||||
active_users_7d = db.query(func.count(func.distinct(ChatSession.user_id))).join(
|
||||
ChatMessage
|
||||
).filter(
|
||||
ChatMessage.created_at >= week_ago
|
||||
).scalar() or 0
|
||||
|
||||
total_sessions = db.query(func.count(ChatSession.id)).scalar() or 0
|
||||
total_messages = db.query(func.count(ChatMessage.id)).scalar() or 0
|
||||
total_documents = db.query(func.count(Document.id)).scalar() or 0
|
||||
total_knowledge_bases = db.query(func.count(KnowledgeBase.id)).scalar() or 0
|
||||
total_forum_posts = db.query(func.count(ForumPost.id)).scalar() or 0
|
||||
total_forum_replies = db.query(func.count(ForumReply.id)).scalar() or 0
|
||||
|
||||
# 生成图像数(从目录统计)
|
||||
from pathlib import Path
|
||||
from ..core.config import get_settings
|
||||
settings = get_settings()
|
||||
generated_images_dir = Path(settings.generated_images_dir)
|
||||
total_generated_images = 0
|
||||
if generated_images_dir.exists():
|
||||
image_extensions = {'.png', '.jpg', '.jpeg', '.webp', '.gif'}
|
||||
total_generated_images = sum(
|
||||
1 for f in generated_images_dir.iterdir()
|
||||
if f.is_file() and f.suffix.lower() in image_extensions
|
||||
)
|
||||
|
||||
return AdminDashboardStats(
|
||||
total_users=total_users,
|
||||
active_users_7d=active_users_7d,
|
||||
total_sessions=total_sessions,
|
||||
total_messages=total_messages,
|
||||
total_documents=total_documents,
|
||||
total_knowledge_bases=total_knowledge_bases,
|
||||
total_forum_posts=total_forum_posts,
|
||||
total_forum_replies=total_forum_replies,
|
||||
total_generated_images=total_generated_images,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/trends/users")
|
||||
async def get_user_trends(
|
||||
days: int = 30,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户注册趋势"""
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
results = db.query(
|
||||
func.date(User.created_at).label("date"),
|
||||
func.count(User.id).label("count")
|
||||
).filter(
|
||||
User.created_at >= start_date
|
||||
).group_by(
|
||||
func.date(User.created_at)
|
||||
).order_by("date").all()
|
||||
|
||||
return [{"date": str(r.date), "count": r.count} for r in results]
|
||||
|
||||
|
||||
@router.get("/trends/messages")
|
||||
async def get_message_trends(
|
||||
days: int = 30,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取消息趋势"""
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
results = db.query(
|
||||
func.date(ChatMessage.created_at).label("date"),
|
||||
func.count(ChatMessage.id).label("count")
|
||||
).filter(
|
||||
ChatMessage.created_at >= start_date
|
||||
).group_by(
|
||||
func.date(ChatMessage.created_at)
|
||||
).order_by("date").all()
|
||||
|
||||
return [{"date": str(r.date), "count": r.count} for r in results]
|
||||
|
||||
|
||||
# ===== 用户管理 =====
|
||||
|
||||
class AdminUserItem(BaseModel):
|
||||
id: int
|
||||
username: str
|
||||
email: str
|
||||
full_name: Optional[str]
|
||||
is_active: bool
|
||||
is_superuser: bool
|
||||
created_at: Optional[str]
|
||||
last_login: Optional[str]
|
||||
session_count: int
|
||||
message_count: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.get("/users", response_model=List[AdminUserItem])
|
||||
async def list_users(
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户列表"""
|
||||
users = db.query(User).order_by(desc(User.id)).offset(skip).limit(limit).all()
|
||||
|
||||
result = []
|
||||
for u in users:
|
||||
session_count = db.query(func.count(ChatSession.id)).filter(
|
||||
ChatSession.user_id == u.id
|
||||
).scalar() or 0
|
||||
message_count = db.query(func.count(ChatMessage.id)).join(
|
||||
ChatSession
|
||||
).filter(ChatSession.user_id == u.id).scalar() or 0
|
||||
|
||||
result.append(AdminUserItem(
|
||||
id=u.id,
|
||||
username=u.username,
|
||||
email=u.email,
|
||||
full_name=u.full_name,
|
||||
is_active=u.is_active,
|
||||
is_superuser=u.is_superuser,
|
||||
created_at=u.created_at.isoformat() if u.created_at else None,
|
||||
last_login=u.last_login.isoformat() if u.last_login else None,
|
||||
session_count=session_count,
|
||||
message_count=message_count,
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
@router.put("/users/{user_id}/toggle-active")
|
||||
async def toggle_user_active(
|
||||
user_id: int,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""启用/禁用用户"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if user.id == admin.id:
|
||||
raise HTTPException(status_code=400, detail="不能禁用自己")
|
||||
|
||||
user.is_active = not user.is_active
|
||||
db.commit()
|
||||
return {"success": True, "is_active": user.is_active}
|
||||
|
||||
|
||||
@router.put("/users/{user_id}/toggle-admin")
|
||||
async def toggle_user_admin(
|
||||
user_id: int,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""设为/取消管理员"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if user.id == admin.id:
|
||||
raise HTTPException(status_code=400, detail="不能修改自己的管理员状态")
|
||||
|
||||
user.is_superuser = not user.is_superuser
|
||||
db.commit()
|
||||
return {"success": True, "is_superuser": user.is_superuser}
|
||||
|
||||
|
||||
@router.delete("/users/{user_id}")
|
||||
async def delete_user(
|
||||
user_id: int,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除用户"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
if user.id == admin.id:
|
||||
raise HTTPException(status_code=400, detail="不能删除自己")
|
||||
|
||||
db.delete(user)
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
# ===== 论坛管理 =====
|
||||
|
||||
class AdminForumCategory(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
post_count: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class AdminForumCategoryCreate(BaseModel):
|
||||
name: str
|
||||
slug: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("/forum/categories", response_model=List[AdminForumCategory])
|
||||
async def list_forum_categories(
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取论坛分类列表(管理)"""
|
||||
categories = db.query(ForumCategory).order_by(ForumCategory.id).all()
|
||||
result = []
|
||||
for c in categories:
|
||||
post_count = db.query(func.count(ForumPost.id)).filter(
|
||||
ForumPost.category_id == c.id
|
||||
).scalar() or 0
|
||||
result.append(AdminForumCategory(
|
||||
id=c.id,
|
||||
slug=c.slug,
|
||||
name=c.name,
|
||||
description=c.description,
|
||||
post_count=post_count,
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/forum/categories", response_model=AdminForumCategory)
|
||||
async def create_forum_category(
|
||||
data: AdminForumCategoryCreate,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建论坛分类"""
|
||||
existing = db.query(ForumCategory).filter(ForumCategory.slug == data.slug).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="slug 已存在")
|
||||
category = ForumCategory(
|
||||
slug=data.slug,
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
)
|
||||
db.add(category)
|
||||
db.commit()
|
||||
db.refresh(category)
|
||||
return AdminForumCategory(
|
||||
id=category.id,
|
||||
slug=category.slug,
|
||||
name=category.name,
|
||||
description=category.description,
|
||||
post_count=0,
|
||||
)
|
||||
|
||||
|
||||
@router.put("/forum/categories/{category_id}")
|
||||
async def update_forum_category(
|
||||
category_id: int,
|
||||
data: AdminForumCategoryCreate,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""更新论坛分类"""
|
||||
category = db.query(ForumCategory).filter(ForumCategory.id == category_id).first()
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="分类不存在")
|
||||
category.name = data.name
|
||||
category.slug = data.slug
|
||||
category.description = data.description
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@router.delete("/forum/categories/{category_id}")
|
||||
async def delete_forum_category(
|
||||
category_id: int,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除论坛分类"""
|
||||
category = db.query(ForumCategory).filter(ForumCategory.id == category_id).first()
|
||||
if not category:
|
||||
raise HTTPException(status_code=404, detail="分类不存在")
|
||||
# 删除该分类下的所有帖子和回复
|
||||
posts = db.query(ForumPost).filter(ForumPost.category_id == category_id).all()
|
||||
for post in posts:
|
||||
db.query(ForumReply).filter(ForumReply.post_id == post.id).delete()
|
||||
db.query(ForumPost).filter(ForumPost.category_id == category_id).delete()
|
||||
db.delete(category)
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
class AdminForumPost(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
author_name: str
|
||||
category_name: str
|
||||
reply_count: int
|
||||
created_at: str
|
||||
|
||||
|
||||
@router.get("/forum/posts", response_model=List[AdminForumPost])
|
||||
async def list_forum_posts(
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取所有帖子列表(管理)"""
|
||||
posts = db.query(ForumPost).order_by(desc(ForumPost.created_at)).offset(skip).limit(limit).all()
|
||||
result = []
|
||||
for p in posts:
|
||||
author = db.query(User).filter(User.id == p.user_id).first()
|
||||
category = db.query(ForumCategory).filter(ForumCategory.id == p.category_id).first()
|
||||
reply_count = db.query(func.count(ForumReply.id)).filter(
|
||||
ForumReply.post_id == p.id
|
||||
).scalar() or 0
|
||||
result.append(AdminForumPost(
|
||||
id=p.id,
|
||||
title=p.title,
|
||||
author_name=author.username if author else "未知",
|
||||
category_name=category.name if category else "未知",
|
||||
reply_count=reply_count,
|
||||
created_at=p.created_at.isoformat() if p.created_at else "",
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
@router.delete("/forum/posts/{post_id}")
|
||||
async def delete_forum_post(
|
||||
post_id: int,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除帖子"""
|
||||
post = db.query(ForumPost).filter(ForumPost.id == post_id).first()
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="帖子不存在")
|
||||
db.query(ForumReply).filter(ForumReply.post_id == post_id).delete()
|
||||
db.delete(post)
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
# ===== 知识库管理 =====
|
||||
|
||||
class AdminKnowledgeBase(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str]
|
||||
owner_name: str
|
||||
is_system: bool
|
||||
document_count: int
|
||||
chunk_count: int
|
||||
created_at: str
|
||||
|
||||
|
||||
@router.get("/knowledge-bases", response_model=List[AdminKnowledgeBase])
|
||||
async def list_knowledge_bases(
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取所有知识库"""
|
||||
kbs = db.query(KnowledgeBase).order_by(desc(KnowledgeBase.id)).all()
|
||||
result = []
|
||||
for kb in kbs:
|
||||
owner = db.query(User).filter(User.id == kb.user_id).first()
|
||||
doc_count = db.query(func.count(Document.id)).filter(
|
||||
Document.knowledge_base_id == kb.id
|
||||
).scalar() or 0
|
||||
chunk_count = db.query(func.count(DocumentChunk.id)).join(
|
||||
Document
|
||||
).filter(Document.knowledge_base_id == kb.id).scalar() or 0
|
||||
result.append(AdminKnowledgeBase(
|
||||
id=kb.id,
|
||||
name=kb.name,
|
||||
description=kb.description,
|
||||
owner_name=owner.username if owner else "系统",
|
||||
is_system=kb.is_system,
|
||||
document_count=doc_count,
|
||||
chunk_count=chunk_count,
|
||||
created_at=kb.created_at.isoformat() if kb.created_at else "",
|
||||
))
|
||||
return result
|
||||
|
||||
|
||||
@router.delete("/knowledge-bases/{kb_id}")
|
||||
async def delete_knowledge_base(
|
||||
kb_id: int,
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""删除知识库"""
|
||||
kb = db.query(KnowledgeBase).filter(KnowledgeBase.id == kb_id).first()
|
||||
if not kb:
|
||||
raise HTTPException(status_code=404, detail="知识库不存在")
|
||||
# 删除关联文档和chunks
|
||||
docs = db.query(Document).filter(Document.knowledge_base_id == kb_id).all()
|
||||
for doc in docs:
|
||||
db.query(DocumentChunk).filter(DocumentChunk.document_id == doc.id).delete()
|
||||
db.query(Document).filter(Document.knowledge_base_id == kb_id).delete()
|
||||
db.delete(kb)
|
||||
db.commit()
|
||||
return {"success": True}
|
||||
|
||||
|
||||
# ===== 系统状态 =====
|
||||
|
||||
@router.get("/system/status")
|
||||
async def get_system_status(
|
||||
admin: User = Depends(require_admin),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取系统状态"""
|
||||
from ..core.config import get_settings
|
||||
settings = get_settings()
|
||||
|
||||
# 数据库状态
|
||||
db_ok = True
|
||||
try:
|
||||
db.execute(func.now())
|
||||
except Exception:
|
||||
db_ok = False
|
||||
|
||||
# 向量库状态
|
||||
vector_store_ok = True
|
||||
vector_count = 0
|
||||
try:
|
||||
from ..rag.vector_store import VectorStore
|
||||
vs = VectorStore()
|
||||
collection = vs.get_or_create_collection("knowledge_base")
|
||||
vector_count = collection.count()
|
||||
except Exception:
|
||||
vector_store_ok = False
|
||||
|
||||
return {
|
||||
"database": {"status": "ok" if db_ok else "error"},
|
||||
"vector_store": {"status": "ok" if vector_store_ok else "error", "vector_count": vector_count},
|
||||
"llm_model": settings.siliconflow_model,
|
||||
"embedding_model": "text2vec-base-chinese",
|
||||
}
|
||||
@@ -242,7 +242,7 @@ async def stream_message(
|
||||
rag_chain = create_rag_chain(
|
||||
knowledge_base_ids=request.knowledge_base_ids,
|
||||
search_type="similarity",
|
||||
k=5,
|
||||
k=50,
|
||||
model=request.model
|
||||
)
|
||||
|
||||
|
||||
+21
-10
@@ -18,8 +18,8 @@ class RAGChain:
|
||||
self,
|
||||
knowledge_base_ids: Optional[List[int]] = None,
|
||||
search_type: str = "similarity",
|
||||
k: int = 5,
|
||||
score_threshold: float = 0.1,
|
||||
k: int = 50,
|
||||
score_threshold: float = 0.15,
|
||||
model: Optional[str] = None
|
||||
):
|
||||
"""初始化RAG链
|
||||
@@ -58,8 +58,8 @@ class RAGChain:
|
||||
"""创建检索链(LangChain 1.0 Runnable API)"""
|
||||
# 使用LangChain 1.0的runnable API构建RAG链
|
||||
def format_docs(docs):
|
||||
"""格式化文档"""
|
||||
return "\n\n".join(doc.page_content for doc in docs)
|
||||
"""格式化文档(使用带编号的上下文格式)"""
|
||||
return self._format_docs_for_context(docs)
|
||||
|
||||
# 构建RAG链 - 只检索一次,返回完整结果
|
||||
def rag_with_sources(input_data):
|
||||
@@ -115,7 +115,7 @@ class RAGChain:
|
||||
"""流式调用(只流式输出答案)"""
|
||||
# 先获取文档(这是唯一一次检索)
|
||||
docs = await self.retriever.ainvoke(question)
|
||||
context = "\n\n".join(doc.page_content for doc in docs)
|
||||
context = self._format_docs_for_context(docs)
|
||||
|
||||
# 构建prompt
|
||||
prompt_value = await self.prompt.ainvoke({"context": context, "question": question})
|
||||
@@ -143,7 +143,7 @@ class RAGChain:
|
||||
# 发送检索结果(只在有文档时)
|
||||
if docs:
|
||||
doc_details = []
|
||||
for i, doc in enumerate(docs[:5]):
|
||||
for i, doc in enumerate(docs[:10]):
|
||||
metadata = doc.metadata if hasattr(doc, 'metadata') else {}
|
||||
title = metadata.get("title", metadata.get("filename", f"文档 {i+1}"))
|
||||
preview = doc.page_content[:100].replace('\n', ' ')
|
||||
@@ -158,7 +158,7 @@ class RAGChain:
|
||||
"details": doc_details
|
||||
}
|
||||
|
||||
context = "\n\n".join(doc.page_content for doc in docs)
|
||||
context = self._format_docs_for_context(docs)
|
||||
|
||||
# 2. 构建prompt并流式生成
|
||||
prompt_value = await self.prompt.ainvoke({"context": context, "question": question})
|
||||
@@ -190,20 +190,31 @@ class RAGChain:
|
||||
def _format_sources(self, documents: List) -> List[Dict]:
|
||||
"""格式化来源信息"""
|
||||
sources = []
|
||||
for doc in documents:
|
||||
for i, doc in enumerate(documents, start=1):
|
||||
metadata = doc.metadata if hasattr(doc, 'metadata') else {}
|
||||
sources.append({
|
||||
"id": i,
|
||||
"title": metadata.get("title", "未知标题"),
|
||||
"filename": metadata.get("filename", "未知文件"),
|
||||
"page": metadata.get("chunk_index", 0),
|
||||
"preview": doc.page_content[:200] + "..." if len(doc.page_content) > 200 else doc.page_content
|
||||
"score": metadata.get("score"),
|
||||
"preview": doc.page_content
|
||||
})
|
||||
return sources
|
||||
|
||||
@staticmethod
|
||||
def _format_docs_for_context(docs) -> str:
|
||||
"""将文档列表格式化为带编号的LLM上下文字符串"""
|
||||
parts = []
|
||||
for i, doc in enumerate(docs, start=1):
|
||||
source_title = doc.metadata.get("title", doc.metadata.get("filename", "未知"))
|
||||
parts.append(f"[来源{i}](来源:{source_title})\n{doc.page_content}")
|
||||
return "\n\n".join(parts)
|
||||
|
||||
def create_rag_chain(
|
||||
knowledge_base_ids: Optional[List[int]] = None,
|
||||
search_type: str = "similarity",
|
||||
k: int = 5,
|
||||
k: int = 50,
|
||||
model: Optional[str] = None
|
||||
) -> RAGChain:
|
||||
"""创建RAG链实例
|
||||
|
||||
@@ -7,15 +7,18 @@ from langchain_core.messages import SystemMessage, HumanMessage
|
||||
# RAG系统提示词
|
||||
RAG_SYSTEM_PROMPT = """你是一个专业的国土空间规划知识问答助手。请基于以下上下文信息回答用户的问题。
|
||||
|
||||
上下文信息:
|
||||
上下文信息(每个来源已标注编号,如 [来源1]、[来源2]):
|
||||
{context}
|
||||
|
||||
要求:
|
||||
1. 回答要准确、专业、详细
|
||||
2. 如果上下文中没有相关信息,请诚实说明
|
||||
3. 回答要结构清晰,逻辑性强
|
||||
4. 适当引用相关概念和术语
|
||||
5. 回答长度控制在500-1000字之间
|
||||
1. 综合评估上下文信息的相关性,优先采用与问题最相关的内容,不强行引用不相关的来源
|
||||
2. 回答中必须使用 [来源N] 格式(N为上下文中的来源编号)标注所引用的具体来源,例如"根据[来源3],国土空间规划..."
|
||||
3. 回答要准确、专业、详细,结构清晰,逻辑性强
|
||||
4. 如果上下文中没有相关信息,请诚实说明
|
||||
5. 在回答末尾,列出所有实际引用的参考来源,格式为:
|
||||
**参考来源:**
|
||||
- [来源N] 文档标题
|
||||
6. 回答长度控制在500-1000字之间
|
||||
|
||||
请基于上述上下文信息回答用户的问题。"""
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class KnowledgeBaseRetriever(BaseRetriever):
|
||||
knowledge_base_ids: Optional[List[int]] = None
|
||||
search_type: str = "similarity"
|
||||
search_kwargs: dict = {"k": 5}
|
||||
score_threshold: float = 0.1
|
||||
score_threshold: float = 0.15
|
||||
|
||||
def _get_relevant_documents(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user