chore: 移除 Chat 、Retriever 相关代码,知识库与知识图谱默认开启且无法修改

- 删除 retriever.py 文件,移除不再使用的检索器逻辑。
- 更新 base_router.py,简化重启逻辑,移除 retriever.restart() 调用。
- 修改 config.py,移除知识库和知识图谱的配置项。
- 更新前端组件,调整与知识库相关的显示逻辑,确保界面一致性。
- 修改路由配置,移除与聊天相关的路由,优化用户导航体验。
This commit is contained in:
Wenjie Zhang 2025-06-27 01:52:59 +08:00
parent a9b5e84618
commit 9bc5b086f9
14 changed files with 30 additions and 621 deletions

View File

@ -93,7 +93,6 @@ async def update_config_item(
async def restart(current_user: User = Depends(get_superadmin_user)):
knowledge_base.restart()
graph_base.start()
retriever.restart()
return {"message": "Restarted!"}
@base.get("/log")

View File

@ -10,7 +10,7 @@ from langchain_core.messages import AIMessageChunk, HumanMessage
from sqlalchemy.orm import Session
from pydantic import BaseModel
from src import executor, config, retriever
from src import executor, config
from src.core import HistoryManager
from src.agents import agent_manager
from src.models import select_model
@ -67,82 +67,6 @@ async def chat_get(current_user: User = Depends(get_required_user)):
"""聊天服务健康检查(需要登录)"""
return "Chat Get!"
@chat.post("/")
async def chat_post(
query: str = Body(...),
meta: dict = Body(None),
history: list[dict] | None = Body(None),
thread_id: str | None = Body(None),
current_user: User = Depends(get_required_user)):
"""处理聊天请求的主要端点(需要登录)"""
model = select_model()
meta["server_model_name"] = model.model_name
history_manager = HistoryManager(history, system_prompt=meta.get("system_prompt"))
logger.debug(f"Received query: {query} with meta: {meta}")
def make_chunk(content=None, **kwargs):
return json.dumps({
"response": content,
"meta": meta,
**kwargs
}, ensure_ascii=False).encode('utf-8') + b"\n"
def need_retrieve(meta):
return meta.get("use_web") or meta.get("use_graph") or meta.get("db_id")
def generate_response():
modified_query = query
refs = None
# 处理知识库检索
if meta and need_retrieve(meta):
chunk = make_chunk(status="searching")
yield chunk
try:
modified_query, refs = retriever(modified_query, history_manager.messages, meta)
except Exception as e:
logger.error(f"Retriever error: {e}, {traceback.format_exc()}")
yield make_chunk(message=f"Retriever error: {e}", status="error")
return
yield make_chunk(status="generating")
messages = history_manager.get_history_with_msg(modified_query, max_rounds=meta.get('history_round'))
history_manager.add_user(query) # 注意这里使用原始查询
content = ""
reasoning_content = ""
try:
for delta in model.predict(messages, stream=True):
if not delta.content and hasattr(delta, 'reasoning_content'):
reasoning_content += delta.reasoning_content or ""
chunk = make_chunk(reasoning_content=reasoning_content, status="reasoning")
yield chunk
continue
# 文心一言
if hasattr(delta, 'is_full') and delta.is_full:
content = delta.content
else:
content += delta.content or ""
chunk = make_chunk(content=delta.content, status="loading")
yield chunk
logger.debug(f"Final response: {content}")
logger.debug(f"Final reasoning response: {reasoning_content}")
yield make_chunk(status="finished",
history=history_manager.update_ai(content),
refs=refs)
except Exception as e:
logger.error(f"Model error: {e}, {traceback.format_exc()}")
yield make_chunk(message=f"Model error: {e}", status="error")
return
return StreamingResponse(generate_response(), media_type='application/json')
@chat.post("/call")
async def call(query: str = Body(...), meta: dict = Body(None), current_user: User = Depends(get_required_user)):
"""调用模型进行简单问答(需要登录)"""

View File

@ -13,6 +13,3 @@ knowledge_base = KnowledgeBase()
from src.core import GraphDatabase # noqa: E402
graph_base = GraphDatabase()
from src.core.retriever import Retriever # noqa: E402
retriever = Retriever()

View File

@ -48,8 +48,6 @@ class Config(SimpleConfig):
### >>> 默认配置
# 功能选项
self.add_item("enable_reranker", default=False, des="是否开启重排序")
self.add_item("enable_knowledge_base", default=False, des="是否开启知识库")
self.add_item("enable_knowledge_graph", default=False, des="是否开启知识图谱")
self.add_item("enable_web_search", default=False, des="是否开启网页搜索(注:现阶段会根据 TAVILY_API_KEY 自动开启,无法手动配置,将会在下个版本移除此配置项)") # noqa: E501
# 默认智能体配置
self.add_item("default_agent_id", default="", des="默认智能体ID")
@ -57,7 +55,7 @@ class Config(SimpleConfig):
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
## 如果需要自定义本地模型路径,则在 src/.env 中配置 MODEL_DIR
self.add_item("model_provider", default="siliconflow", des="模型提供商", choices=list(self.model_names.keys()))
self.add_item("model_name", default="Qwen/Qwen2.5-7B-Instruct", des="模型名称")
self.add_item("model_name", default="Qwen/Qwen3-32B", des="模型名称")
self.add_item("embed_model", default="siliconflow/BAAI/bge-m3", des="Embedding 模型", choices=list(self.embed_model_names.keys()))
self.add_item("reranker", default="siliconflow/BAAI/bge-reranker-v2-m3", des="Re-Ranker 模型", choices=list(self.reranker_names.keys())) # noqa: E501

View File

@ -31,9 +31,6 @@ class GraphDatabase:
self.start()
def start(self):
if not config.enable_knowledge_graph or not config.enable_knowledge_base:
return
uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
username = os.environ.get("NEO4J_USERNAME", "neo4j")
password = os.environ.get("NEO4J_PASSWORD", "0123456789")
@ -46,7 +43,6 @@ class GraphDatabase:
self.save_graph_info(self.kgdb_name)
except Exception as e:
logger.error(f"Failed to connect to Neo4j: {e}, {uri}, {self.kgdb_name}, {username}, {password}")
self.config.enable_knowledge_graph = False
def close(self):
"""关闭数据库连接"""
@ -54,10 +50,7 @@ class GraphDatabase:
def is_running(self):
"""检查图数据库是否正在运行"""
if not config.enable_knowledge_graph or not config.enable_knowledge_base:
return False
else:
return self.status == "open"
return self.status == "open"
def get_sample_nodes(self, kgdb_name='neo4j', num=50):
"""获取指定数据库的 num 个节点信息"""

View File

@ -1,182 +0,0 @@
import traceback
from src import config, knowledge_base, graph_base
from src.models.rerank_model import get_reranker
from src.utils.logging_config import logger
from src.models import select_model
from src.core.operators import HyDEOperator
class Retriever:
def __init__(self):
self._load_models()
def _load_models(self):
if config.enable_reranker:
self.reranker = get_reranker()
if config.enable_web_search:
from src.utils.web_search import WebSearcher
self.web_searcher = WebSearcher()
def retrieval(self, query, history, meta):
refs = {"query": query, "history": history, "meta": meta}
refs["model_name"] = config.model_name
refs["entities"] = self.reco_entities(query, history, refs)
refs["knowledge_base"] = self.query_knowledgebase(query, history, refs)
refs["graph_base"] = self.query_graph(query, history, refs)
refs["web_search"] = self.query_web(query, history, refs)
return refs
def restart(self):
"""所有需要重启的模型"""
self._load_models()
def construct_query(self, query, refs, meta):
logger.debug(f"{refs=}")
if not refs or len(refs) == 0:
return query
external_parts = []
# 解析知识库的结果
kb_res = refs.get("knowledge_base", {}).get("results", [])
if kb_res:
kb_text = "\n".join(f"{r['id']}: {r['entity']['text']}" for r in kb_res)
external_parts.extend(["知识库信息:", kb_text])
# 解析图数据库的结果
db_res = refs.get("graph_base", {}).get("results", {})
if db_res.get("nodes") and len(db_res["nodes"]) > 0:
db_text = "\n".join(
[f"{edge['source_name']}{edge['target_name']}的关系是{edge['type']}" for edge in db_res.get("edges", [])]
)
external_parts.extend(["图数据库信息:", db_text])
# 解析网络搜索的结果
web_res = refs.get("web_search", {}).get("results", [])
if web_res:
web_text = "\n".join(f"{r['title']}: {r['content']}" for r in web_res)
external_parts.extend(["网络搜索信息:", web_text])
# 构造查询
from src.utils.prompts import knowbase_qa_template
if external_parts and len(external_parts) > 0:
external = "\n\n".join(external_parts)
query = knowbase_qa_template.format(external=external, query=query)
return query
def query_classification(self, query):
"""判断是否需要查询
- 对于完全基于用户给定信息的任务称之为"足够""sufficient"不需要检索
- 否则称之为"不足""insufficient"可能需要检索
"""
raise NotImplementedError
def query_graph(self, query, history, refs):
results = []
if refs["meta"].get("use_graph") and config.enable_knowledge_base:
for entity in refs["entities"]:
if entity == "":
continue
result = graph_base.query_node(entity)
if result != []:
results.extend(result)
return {"results": graph_base.format_query_result_to_graph(results)}
def query_knowledgebase(self, query, history, refs):
"""查询知识库"""
response = {
"results": [],
"all_results": [],
"rw_query": query,
"message": "",
}
meta = refs["meta"]
db_id = meta.get("db_id")
if not db_id or not config.enable_knowledge_base:
response["message"] = "知识库未启用、或未指定知识库、或知识库不存在"
return response
rw_query = self.rewrite_query(query, history, refs)
logger.debug(f"{meta=}")
query_result = knowledge_base.query(query_text=rw_query,
db_id=db_id,
distance_threshold=meta.get("distanceThreshold", 0.5),
rerank_threshold=meta.get("rerankThreshold", 0.1),
max_query_count=meta.get("maxQueryCount", 20),
top_k=meta.get("topK", 10))
response["results"] = query_result["results"]
response["all_results"] = query_result["all_results"]
response["rw_query"] = rw_query
return response
def query_web(self, query, history, refs):
"""查询网络"""
if not (refs["meta"].get("use_web") or not config.enable_web_search):
return {"results": [], "message": "Web search is disabled"}
try:
search_results = self.web_searcher.search(query, max_results=5)
except Exception as e:
logger.error(f"Web search error: {str(e)}")
return {"results": [], "message": "Web search error"}
return {"results": search_results}
def rewrite_query(self, query, history, refs):
"""重写查询"""
model_provider = config.model_provider
model_name = config.model_name
model = select_model(model_provider=model_provider, model_name=model_name)
if refs["meta"].get("mode") == "search": # 如果是搜索模式,就使用 meta 的配置,否则就使用全局的配置
rewrite_query_span = refs["meta"].get("use_rewrite_query", "off")
else:
rewrite_query_span = config.use_rewrite_query
if rewrite_query_span == "off":
return query
from src.utils.prompts import rewritten_query_prompt_template2 as rw_template
history_query = [entry["content"] for entry in history if entry["role"] == "user"] if history else ""
rewritten_query_prompt = rw_template.format(history=history_query, query=query)
rewritten_query = model.predict(rewritten_query_prompt).content
if rewrite_query_span == "hyde":
res = HyDEOperator.call(model_callable=model.predict, query=query, context_str=history_query)
rewritten_query = res.content
return rewritten_query
def reco_entities(self, query, history, refs):
"""识别句子中的实体"""
query = refs.get("rewritten_query", query)
model_provider = config.model_provider
model_name = config.model_name
model = select_model(model_provider=model_provider, model_name=model_name)
entities = []
if refs["meta"].get("use_graph"):
from src.utils.prompts import entity_extraction_prompt_template as entity_template
# from src.utils.prompts import keywords_prompt_template as entity_templat|e
entity_extraction_prompt = entity_template.format(text=query)
entities = model.predict(entity_extraction_prompt).content.split("<->")
# entities = [entity for entity in entities if all(char.isalnum() or char in "汉字" for char in entity)]
return entities
def __call__(self, query, history, meta):
refs = self.retrieval(query, history, meta)
query = self.construct_query(query, refs, meta)
return query, refs

View File

@ -182,9 +182,6 @@ class OtherEmbedding(BaseEmbeddingModel):
}
def get_embedding_model():
if not config.enable_knowledge_base:
return None
provider, model_name = config.embed_model.split('/', 1)
support_embed_models = config.embed_model_names.keys()
assert config.embed_model in support_embed_models, f"Unsupported embed model: {config.embed_model}, only support {support_embed_models}"

View File

@ -98,14 +98,13 @@
</div>
<div
:class="{'switch': true, 'opt-item': true, 'active': meta.use_graph}"
v-if="configStore.config.enable_knowledge_graph"
@click="meta.use_graph=!meta.use_graph"
>
<Waypoints style="margin-right: 3px;" size="14"/>
知识图谱
</div>
<a-dropdown
v-if="configStore.config.enable_knowledge_base && opts.databases.length > 0"
v-if="opts.databases.length > 0"
:class="{'opt-item': true, 'active': meta.selectedKB !== null}"
>
<a class="ant-dropdown-link" @click.prevent>

View File

@ -27,14 +27,35 @@ const layoutSettings = reactive({
const githubStars = ref(0)
const isLoadingStars = ref(false)
// Add state for debug modal
const showDebugModal = ref(false)
const htmlRefHook = useTemplateRef('htmlRefHook')
// Setup long press for debug modal
onLongPress(
htmlRefHook,
() => {
console.log('long press')
showDebugModal.value = true
},
{
delay: 1000, // 1
modifiers: {
prevent: true
}
}
)
// Handle debug modal close
const handleDebugModalClose = () => {
showDebugModal.value = false
}
const getRemoteConfig = () => {
configStore.refreshConfig()
}
const getRemoteDatabase = () => {
if (!configStore.config.enable_knowledge_base) {
return
}
databaseStore.refreshDatabase()
}
@ -68,11 +89,6 @@ console.log(route)
//
const mainList = [{
name: '对话',
path: '/chat',
icon: MessageSquareMore,
activeIcon: MessageSquareMore,
}, {
name: '智能体',
path: '/agent',
icon: Bot,
@ -82,13 +98,11 @@ const mainList = [{
path: '/graph',
icon: Waypoints,
activeIcon: Waypoints,
// hidden: !configStore.config.enable_knowledge_graph,
}, {
name: '知识库',
path: '/database',
icon: LibraryBig,
activeIcon: LibraryBig,
// hidden: !configStore.config.enable_knowledge_base,
}
]
</script>

View File

@ -25,19 +25,6 @@ const router = createRouter({
component: () => import('../views/LoginView.vue'),
meta: { requiresAuth: false }
},
{
path: '/chat',
name: 'chat',
component: AppLayout,
children: [
{
path: '',
name: 'ChatComp',
component: () => import('../views/ChatView.vue'),
meta: { keepAlive: true, requiresAuth: true, requiresAdmin: true }
}
]
},
{
path: '/agent',
name: 'AgentMain',

View File

@ -1,288 +0,0 @@
<template>
<div class="chat-container">
<div class="conversations" :class="{ 'is-open': state.isSidebarOpen }">
<div class="actions">
<!-- <div class="action new" @click="addNewConv"><FormOutlined /></div> -->
<span class="header-title">Yuxi-Know</span>
<div class="action close" @click="state.isSidebarOpen = false">
<PanelLeftClose size="20" color="var(--gray-800)"/>
</div>
</div>
<div class="conversation-list">
<div class="conversation"
v-for="(state, index) in convs"
:key="index"
:class="{ active: curConvId === index }"
@click="goToConversation(index)">
<div class="conversation__title">{{ state.title }}</div>
<div class="conversation__delete" @click.stop="delConv(index)"><DeleteOutlined /></div>
</div>
</div>
</div>
<ChatComponent
:conv="convs[curConvId]"
:state="state"
@rename-title="renameTitle"
@newconv="addNewConv"/>
</div>
</template>
<script setup>
import { reactive, ref, watch, onMounted } from 'vue'
import { DeleteOutlined } from '@ant-design/icons-vue'
import ChatComponent from '@/components/ChatComponent.vue'
import { MessageSquareMore, PanelLeftClose } from 'lucide-vue-next'
const convs = reactive(JSON.parse(localStorage.getItem('chat-convs')) || [
{
id: 0,
title: '新对话',
history: [],
messages: [],
inputText: ''
},
])
const state = reactive({
isSidebarOpen: JSON.parse(localStorage.getItem('chat-sidebar-open') || 'true'),
})
// Watch isSidebarOpen and save to localStorage
watch(
() => state.isSidebarOpen,
(newValue) => {
localStorage.setItem('chat-sidebar-open', JSON.stringify(newValue))
}
)
const curConvId = ref(0)
const generateRandomHash = (length) => {
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let hash = '';
for (let i = 0; i < length; i++) {
hash += chars.charAt(Math.floor(Math.random() * chars.length));
}
return hash;
}
const renameTitle = (newTitle) => {
convs[curConvId.value].title = newTitle
}
const goToConversation = (index) => {
curConvId.value = index
console.log(convs[curConvId.value])
}
const addNewConv = () => {
curConvId.value = 0
if (convs.length > 0 && convs[0].messages.length === 0) {
return
}
convs.unshift({
id: generateRandomHash(8),
title: `新对话`,
history: [],
messages: [],
inputText: ''
})
}
const delConv = (index) => {
convs.splice(index, 1)
if (index < curConvId.value) {
curConvId.value -= 1
} else if (index === curConvId.value) {
curConvId.value = 0
}
if (convs.length === 0) {
addNewConv()
}
}
// Watch convs and save to localStorage
watch(
() => convs,
(newStates) => {
localStorage.setItem('chat-convs', JSON.stringify(newStates))
},
{ deep: true }
)
// Load convs from localStorage on mount
onMounted(() => {
const savedSonvs = JSON.parse(localStorage.getItem('chat-convs'))
if (savedSonvs) {
for (let i = 0; i < savedSonvs.length; i++) {
convs[i] = savedSonvs[i]
}
}
})
</script>
<style lang="less" scoped>
@import '@/assets/main.css';
.chat-container {
display: flex;
width: 100%;
height: 100%;
position: relative;
}
.conversations {
width: 230px;
max-width: 230px;
border-right: 1px solid var(--main-light-3);
background-color: var(--bg-sider);
transition: all 0.3s ease;
white-space: nowrap; /* 防止文本换行 */
overflow: hidden; /* 确保内容不溢出 */
&.is-open {
width: 230px;
}
&:not(.is-open) {
width: 0;
padding: 0;
overflow: hidden;
}
& .actions {
height: var(--header-height);
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
z-index: 9;
border-bottom: 1px solid var(--main-light-3);
.header-title {
font-weight: bold;
user-select: none;
white-space: nowrap;
overflow: hidden;
}
.action {
font-size: 1.2rem;
width: 2.5rem;
height: 2.5rem;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
color: var(--gray-800);
cursor: pointer;
&:hover {
background-color: var(--main-light-3);
}
.nav-btn-icon {
width: 1.2rem;
height: 1.2rem;
}
}
}
.conversation-list {
display: flex;
flex-direction: column;
overflow-y: auto;
max-height: 100%;
}
.conversation-list .conversation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
cursor: pointer;
width: 100%;
user-select: none;
transition: background-color 0.2s ease-in-out;
&__title {
color: var(--gray-700);
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 显示省略号 */
}
&__delete {
display: none;
color: var(--gray-500);
transition: all 0.2s ease-in-out;
&:hover {
color: #F93A37;
background-color: #EEE;
}
}
&.active {
border-right: 3px solid var(--main-500);
padding-right: 13px;
background-color: var(--gray-200);
& .conversation__title {
color: var(--gray-1000);
}
}
&:not(.active):hover {
background-color: var(--main-light-3);
& .conversation__delete {
display: block;
}
}
}
}
.conversation-list::-webkit-scrollbar {
position: absolute;
width: 4px;
}
.conversation-list::-webkit-scrollbar-track {
background: transparent;
border-radius: 4px;
}
.conversation-list::-webkit-scrollbar-thumb {
background: var(--gray-400);
border-radius: 4px;
}
.conversation-list::-webkit-scrollbar-thumb:hover {
background: rgb(100, 100, 100);
border-radius: 4px;
}
.conversation-list::-webkit-scrollbar-thumb:active {
background: rgb(68, 68, 68);
border-radius: 4px;
}
@media (max-width: 520px) {
.conversations {
position: absolute;
z-index: 101;
width: 300px;
height: 100%;
border-radius: 0 16px 16px 0;
box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.05);
&:not(.is-open) {
width: 0;
padding: 0;
overflow: hidden;
}
}
}
</style>

View File

@ -71,7 +71,7 @@ const goToChat = async () => {
//
if (userStore.isAdmin) {
//
router.push('/chat');
router.push('/agent');
return;
}

View File

@ -221,7 +221,7 @@ const handleLogin = async () => {
if (redirectPath === '/') {
// /chat
if (userStore.isAdmin) {
router.push('/chat');
router.push('/agent');
return;
}

View File

@ -13,7 +13,6 @@
<div class="sider" v-if="state.windowWidth > 520">
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'base'}" @click="state.section='base'" :icon="h(SettingOutlined)"> 基本设置 </a-button>
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'model'}" @click="state.section='model'" :icon="h(CodeOutlined)"> 模型配置 </a-button>
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'path'}" @click="state.section='path'" :icon="h(FolderOutlined)"> 路径配置 </a-button>
<a-button type="text" :class="{ activesec: state.section === 'user'}" @click="state.section='user'" :icon="h(UserOutlined)" v-if="userStore.isAdmin"> 用户管理 </a-button>
</div>
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'base') && userStore.isSuperAdmin">
@ -72,23 +71,6 @@
</a-select>
</div>
</div>
<h3>功能配置</h3>
<div class="section">
<div class="card">
<span class="label">{{ items?.enable_knowledge_base.des }}</span>
<a-switch
:checked="configStore.config.enable_knowledge_base"
@change="handleChange('enable_knowledge_base', !configStore.config.enable_knowledge_base)"
/>
</div>
<div class="card">
<span class="label">{{ items?.enable_knowledge_graph.des }}</span>
<a-switch
:checked="configStore.config.enable_knowledge_graph"
@change="handleChange('enable_knowledge_graph', !configStore.config.enable_knowledge_graph)"
/>
</div>
</div>
</div>
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'model') && userStore.isSuperAdmin">
<h3>模型配置</h3>
@ -138,19 +120,8 @@ const handleModelLocalPathsUpdate = (config) => {
}
const preHandleChange = (key, e) => {
if (key == 'enable_knowledge_graph' && e && !configStore.config.enable_knowledge_base) {
message.error('启动知识图谱必须请先启用知识库功能')
return
}
if (key == 'enable_knowledge_base' && !e && configStore.config.enable_knowledge_graph) {
message.error('关闭知识库功能必须请先关闭知识图谱功能')
return
}
if (key == 'enable_reranker'
|| key == 'enable_knowledge_graph'
|| key == 'enable_knowledge_base'
|| key == 'embed_model'
|| key == 'reranker'
|| key == 'model_local_paths') {