This commit is contained in:
liqiang-fit2cloud 2024-12-11 16:40:09 +08:00
commit 0f65670f99
20 changed files with 110 additions and 102 deletions

View File

@ -1,16 +1,14 @@
[English](README_EN.md) | [中文](README.md)
<p align="center"><img src= "https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf" alt="MaxKB" width="300" /></p>
<h3 align="center">基于大语言模型和 RAG 的知识库问答系统</h3>
<h3 align="center">基于大模型和 RAG 的知识库问答系统</h3>
<p align="center">
<a href="https://trendshift.io/repositories/9113" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9113" alt="1Panel-dev%2FMaxKB | Trendshift" style="width: 250px; height: auto;" /></a>
<a href="https://market.aliyun.com/products/53690006/cmjj00067609.html?userCode=kmemb8jp" target="_blank"><img src="https://img.alicdn.com/imgextra/i2/O1CN01H5JIwY1rZ0OobDjnJ_!!6000000005644-2-tps-1000-216.png" alt="1Panel-dev%2FMaxKB | Aliyun" style="width: 250px; height: auto;" /></a>
</p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text"><img src="https://img.shields.io/github/license/1Panel-dev/maxkb?color=%231890FF" alt="License: GPL v3"></a>
<a href="https://app.codacy.com/gh/1Panel-dev/maxkb?utm_source=github.com&utm_medium=referral&utm_content=1Panel-dev/maxkb&utm_campaign=Badge_Grade_Dashboard"><img src="https://app.codacy.com/project/badge/Grade/da67574fd82b473992781d1386b937ef" alt="Codacy"></a>
<a href="README_EN.md"><img src="https://img.shields.io/badge/English_README-blue" alt="English README"></a>
<a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text"><img src="https://img.shields.io/github/license/1Panel-dev/maxkb" alt="License: GPL v3"></a>
<a href="https://github.com/1Panel-dev/maxkb/releases/latest"><img src="https://img.shields.io/github/v/release/1Panel-dev/maxkb" alt="Latest release"></a>
<a href="https://github.com/1Panel-dev/maxkb"><img src="https://img.shields.io/github/stars/1Panel-dev/maxkb?color=%231890FF&style=flat-square" alt="Stars"></a>
<a href="https://github.com/1Panel-dev/maxkb"><img src="https://img.shields.io/github/stars/1Panel-dev/maxkb?style=flat-square" alt="Stars"></a>
<a href="https://hub.docker.com/r/1panel/maxkb"><img src="https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads" alt="Download"></a>
</p>
<hr/>
@ -22,7 +20,7 @@ MaxKB = Max Knowledge Base是一款基于大语言模型和 RAG 的开源知
- **灵活编排**:内置强大的工作流引擎和函数库,支持编排 AI 工作过程,满足复杂业务场景下的需求;
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度。
三分钟视频介绍https://www.bilibili.com/video/BV18JypYeEkj/
MaxKB 三分钟视频介绍https://www.bilibili.com/video/BV18JypYeEkj/
## 快速开始
@ -52,7 +50,7 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/po
## 案例展示
MaxKB 自发布以来,日均安装下载超过 1000 次,被广泛应用于智能客服、企业内部知识库、学术研究与教育等场景。
MaxKB 自发布以来,日均安装下载超过 1000 次,被广泛应用于智能客服、企业内部知识库、学术教育研究等场景。
- [华莱士智能客服](https://ai.cnhls.com/ui/chat/1fc0f6a9b5a6fb27)
- [JumpServer 小助手](https://maxkb.fit2cloud.com/ui/chat/b4e27a6e72d349a3)
@ -78,7 +76,6 @@ MaxKB 自发布以来,日均安装下载超过 1000 次,被广泛应用于
- 后端:[Python / Django](https://www.djangoproject.com/)
- LangChain[LangChain](https://www.langchain.com/)
- 向量数据库:[PostgreSQL / pgvector](https://www.postgresql.org/)
- 大模型:各种本地私有或者公共大模型
## 飞致云的其他明星项目

View File

@ -1,5 +1,5 @@
<p align="center"><img src= "https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf" alt="MaxKB" width="300" /></p>
<h3 align="center">Knowledge base, question answering system, based on LLM large language models</h3>
<h3 align="center">Top-Rated Retrieval-Augmented Generation (RAG) Chatbot.</h3>
<p align="center"><a href="https://trendshift.io/repositories/9113" target="_blank"><img src="https://trendshift.io/api/badge/repositories/9113" alt="1Panel-dev%2FMaxKB | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a></p>
<p align="center">
<a href="https://www.gnu.org/licenses/gpl-3.0.html#license-text"><img src="https://img.shields.io/github/license/1Panel-dev/maxkb?color=%231890FF" alt="License: GPL v3"></a>
@ -10,12 +10,13 @@
</p>
<hr/>
MaxKB = Max Knowledge BaseIt is an open source knowledge base question and answer system based on the LLM large language model. It is widely used in enterprise internal knowledge bases, customer services, academic research and education and other scenarios.
MaxKB = Max Knowledge Base, it is a Chatbot based on Large Language Models (LLM) and Retrieval-Augmented Generation (RAG). MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education.
- **Ready-to-Use**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization, and RAG (Retrieval-Augmented Generation). This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience.
- **Model-Agnostic**: Supports various large models, including private models (such as Llama 3, Qwen 2, etc.) and public models (like OpenAI, Claude, Gemini, etc.).
- **Flexible Orchestration**: Equipped with a powerful workflow engine and function library, enabling the orchestration of AI processes to meet the needs of complex business scenarios.
- **Seamless Integration**: Facilitates zero-coding rapid integration into third-party business systems, quickly equipping existing systems with intelligent Q&A capabilities to enhance user satisfaction.
- **Out-of-the-box**: Supports direct uploading of documents, automatic crawling of online documents, automatic text splitting, vectorization, RAG (retrieval enhancement generation), and a good interactive experience in intelligent question and answer;
- **Model neutral**: Supports docking with various large language models, including local private large models (Llama 3/Qwen 2, etc.), domestic public large models (Tongyi Qianwen/Zhipu AI/Baidu Qianfan/Kimi/DeepSeek, etc.) and foreign public models Large models (OpenAI / Azure OpenAI / Gemini, etc.);
- **Flexible Orchestration**: Built-in powerful workflow engine supports the orchestration of AI work processes to meet the needs of complex business scenarios;
- **Seamless Embedding**: Supports rapid embedding into third-party business systems with zero coding, allowing existing systems to quickly have intelligent question and answer capabilities and improve user satisfaction
## Quick start
```
@ -25,20 +26,7 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/po
# pass: MaxKB@123..
```
- You can also quickly deploy MaxKB + Ollama + Llama 3 through [1Panel App Store](https://apps.fit2cloud.com/1panel). A knowledge base question and answer system based on a local large model can be launched within 30 minutes and embedded into In third-party business systems;
- If it is an intranet environment, it is recommended to use [offline installation package](https://community.fit2cloud.com/#/products/maxkb/downloads) for installation and deployment;
- You can also experience it online: [DataEase Assistant](https://dataease.io/docs/v2/), which is an intelligent question and answer system based on MaxKB and has been embedded in DataEase products and online documents.
- MaxKB's product version is divided into community version and professional version. For details, please see: [MaxKB product version comparison](https://maxkb.cn/pricing.html).
If you have more questions, you can check the user manual or communicate with us through the forum. If you need to build a technical blog or knowledge base, it is recommended to use [Halo open source website building tool](https://github.com/halo-dev/halo/). You can experience Feizhiyuns official [Technical Blog](https://blog.fit2cloud.com/) and [Knowledge Base](https://kb.fit2cloud.com) cases.
- [Docs](https://maxkb.cn/docs/)
- [Demo Vid](https://www.bilibili.com/video/BV1BE421M7YM/)
- [Forum](https://bbs.fit2cloud.com/c/mk/11)
- Technical exchange group
<image height="150px" width="150px" src="https://github.com/1Panel-dev/MaxKB/assets/52996290/a083d214-02be-4178-a1db-4f428124153a"/>
## UI Screenshots
## Screenshots
<table style="border-collapse: collapse; border: 1px solid black;">
<tr>
@ -51,26 +39,15 @@ If you have more questions, you can check the user manual or communicate with us
</tr>
</table>
## Stack Used
## Technical Stack
- Frontend[Vue.js](https://cn.vuejs.org/)
- Frontend[Vue.js](https://vuejs.org/)
- Backend[Python / Django](https://www.djangoproject.com/)
- LangChain[LangChain](https://www.langchain.com/)
- Vector DB[PostgreSQL / pgvector](https://www.postgresql.org/)
- Large models: various local private or public large models
## Other Projects From Feizhiyun
- [1Panel](https://github.com/1panel-dev/1panel/) - Modern, open source Linux server operation and maintenance management panel
- [JumpServer](https://github.com/jumpserver/jumpserver/) - Popular open source bastion host
- [DataEase](https://github.com/dataease/dataease/) - Open source data visualization analysis tools available to everyone
- [MeterSphere](https://github.com/metersphere/metersphere/) - New generation of open-source test tools
- [Halo](https://github.com/halo-dev/halo/) - Powerful and easy-to-use open source website building tool
## License
Copyright (c) 2014-2024 Feizhiyun FIT2CLOUD, All rights reserved.
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
<https://www.gnu.org/licenses/gpl-3.0.html>

View File

@ -52,6 +52,7 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
runtime_node_id = response_content.get('runtime_node_id', '')
chat_record_id = response_content.get('chat_record_id', '')
child_node = response_content.get('child_node')
view_type = response_content.get('view_type')
node_type = response_content.get('node_type')
real_node_id = response_content.get('real_node_id')
node_is_end = response_content.get('node_is_end', False)
@ -65,7 +66,8 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
'child_node': child_node,
'real_node_id': real_node_id,
'node_is_end': node_is_end}
'node_is_end': node_is_end,
'view_type': view_type}
usage = response_content.get('usage', {})
node_variable['result'] = {'usage': usage}
node_variable['is_interrupt_exec'] = is_interrupt_exec

View File

@ -29,7 +29,7 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable:
result = str(step_variable['result']) + '\n'
yield result
workflow.answer += result
node.answer_text = result
node.context['run_time'] = time.time() - node.context['start_time']
@ -94,6 +94,7 @@ class BaseFunctionLibNodeNode(IFunctionLibNode):
def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result')
self.answer_text = details.get('result')
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
if not function_lib.is_active:

View File

@ -27,7 +27,7 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable:
result = str(step_variable['result']) + '\n'
yield result
workflow.answer += result
node.answer_text = result
node.context['run_time'] = time.time() - node.context['start_time']

View File

@ -454,6 +454,7 @@ class WorkflowManage:
content = r
child_node = {}
node_is_end = False
view_type = current_node.view_type
if isinstance(r, dict):
content = r.get('content')
child_node = {'runtime_node_id': r.get('runtime_node_id'),
@ -461,6 +462,7 @@ class WorkflowManage:
, 'child_node': r.get('child_node')}
real_node_id = r.get('real_node_id')
node_is_end = r.get('node_is_end')
view_type = r.get('view_type')
chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
self.params['chat_record_id'],
current_node.id,
@ -468,7 +470,7 @@ class WorkflowManage:
content, False, 0, 0,
{'node_type': current_node.type,
'runtime_node_id': current_node.runtime_node_id,
'view_type': current_node.view_type,
'view_type': view_type,
'child_node': child_node,
'node_is_end': node_is_end,
'real_node_id': real_node_id})

View File

@ -341,10 +341,12 @@ class ChatMessageSerializer(serializers.Serializer):
user_id = chat_info.application.user_id
chat_record_id = self.data.get('chat_record_id')
chat_record = None
history_chat_record = chat_info.chat_record_list
if chat_record_id is not None:
chat_record = self.get_chat_record(chat_info, chat_record_id)
history_chat_record = [r for r in chat_info.chat_record_list if str(r.id) != chat_record_id]
work_flow_manage = WorkflowManage(Flow.new_instance(chat_info.work_flow_version.work_flow),
{'history_chat_record': chat_info.chat_record_list, 'question': message,
{'history_chat_record': history_chat_record, 'question': message,
'chat_id': chat_info.chat_id, 'chat_record_id': str(
uuid.uuid1()) if chat_record is None else chat_record.id,
'stream': stream,

View File

@ -272,6 +272,7 @@ function initMaxkbStyle(root){
position: absolute;
display: flex;
align-items: center;
line-height: 18px;
}
#maxkb #maxkb-chat-container .maxkb-operate .maxkb-chat-close{
margin-left:15px;

View File

@ -143,6 +143,18 @@ class DocumentWebInstanceSerializer(ApiMixin, serializers.Serializer):
required=True,
description='知识库id'),
]
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['source_url_list'],
properties={
'source_url_list': openapi.Schema(type=openapi.TYPE_ARRAY, title="文档地址列表", description="文档地址列表",
items=openapi.Schema(type=openapi.TYPE_STRING)),
'selector': openapi.Schema(type=openapi.TYPE_STRING, title="选择器", description="选择器")
}
)
class DocumentInstanceSerializer(ApiMixin, serializers.Serializer):

View File

@ -236,7 +236,7 @@ class Document(APIView):
return result.success(
DocumentSerializers.Operate(data={'document_id': document_id, 'dataset_id': dataset_id}).cancel(
request.data
))
))
class Refresh(APIView):
authentication_classes = [TokenAuth]
@ -309,7 +309,7 @@ class Document(APIView):
manual_parameters=DocumentSerializers.Operate.get_request_params_api(),
tags=["知识库/文档"])
@has_permissions(
lambda r, k: Permission(group=Group.DATASET, operate=Operate.USE,
lambda r, k: Permission(group=Group.DATASET, operate=Operate.MANAGE,
dynamic_tag=k.get('dataset_id')))
def get(self, request: Request, dataset_id: str, document_id: str):
return DocumentSerializers.Operate(data={'document_id': document_id, 'dataset_id': dataset_id}).export()

View File

@ -251,9 +251,9 @@ export class ChatRecordManage {
(node_info.divider_content ? node_info.divider_content.splice(0).join('') : '') +
node_info.current_node.buffer.splice(0).join(''),
node_info.answer_text_list_index,
current_node.chat_record_id,
current_node.runtime_node_id,
current_node.child_node
node_info.current_node.chat_record_id,
node_info.current_node.runtime_node_id,
node_info.current_node.child_node
)
if (node_info.current_node.buffer.length == 0) {
node_info.current_node.is_end = true

View File

@ -154,9 +154,7 @@ function markdownToPlainText(md: string) {
function removeFormRander(text: string) {
return text
.replace('你好,请先填写下面表单内容:', '')
.replace(/<formrander>[\s\S]*?<\/formrander>/, '')
.replace('填写后请点击【提交】按钮进行提交。', '')
.replace(/<form_rander>[\s\S]*?<\/form_rander>/g, '')
.trim()
}
@ -164,10 +162,11 @@ const playAnswerText = (text: string) => {
if (!text) {
text = '抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。'
}
// text
text = markdownToPlainText(text)
//
text = removeFormRander(text)
// text
text = markdownToPlainText(text)
// console.log(text)
audioPlayerStatus.value = true
if (props.tts_type === 'BROWSER') {
if (text !== utterance.value?.text) {

View File

@ -133,9 +133,7 @@ function markdownToPlainText(md: string) {
function removeFormRander(text: string) {
return text
.replace('你好,请先填写下面表单内容:', '')
.replace(/<formrander>[\s\S]*?<\/formrander>/, '')
.replace('填写后请点击【提交】按钮进行提交。', '')
.replace(/<form_rander>[\s\S]*?<\/form_rander>/g, '')
.trim()
}
@ -144,10 +142,11 @@ const playAnswerText = (text: string) => {
if (!text) {
text = '抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。'
}
// text
text = markdownToPlainText(text)
//
text = removeFormRander(text)
// text
text = markdownToPlainText(text)
// console.log(text)
audioPlayerStatus.value = true
if (props.tts_type === 'BROWSER') {
if (text !== utterance.value?.text) {

View File

@ -225,8 +225,9 @@ export const exportExcel: (
params: any,
loading?: NProgress | Ref<boolean>
) => {
return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading)
.then((res: any) => {
return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading).then(
(res: any) => {
console.log(res)
if (res) {
const blob = new Blob([res], {
type: 'application/vnd.ms-excel'
@ -239,8 +240,8 @@ export const exportExcel: (
window.URL.revokeObjectURL(link.href)
}
return true
})
.catch((e) => {})
}
)
}
export const exportExcelPost: (
@ -265,22 +266,20 @@ export const exportExcelPost: (
responseType: 'blob'
}),
loading
)
.then((res: any) => {
if (res) {
const blob = new Blob([res], {
type: 'application/vnd.ms-excel'
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
// 释放内存
window.URL.revokeObjectURL(link.href)
}
return true
})
.catch((e) => {})
).then((res: any) => {
if (res) {
const blob = new Blob([res], {
type: 'application/vnd.ms-excel'
})
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
// 释放内存
window.URL.revokeObjectURL(link.href)
}
return true
})
}
export const download: (

View File

@ -48,7 +48,7 @@ import { cloneDeep } from 'lodash'
import { ref, computed } from 'vue'
import EditParagraphDialog from './EditParagraphDialog.vue'
import { MsgConfirm } from '@/utils/message'
const page_size = ref<number>(20)
const page_size = ref<number>(30)
const current_page = ref<number>(1)
const currentCIndex = ref<number>(0)
const EditParagraphDialogRef = ref()

View File

@ -160,7 +160,7 @@ const loading = ref(false)
const datasetList = ref<any[]>([])
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
page_size: 30,
total: 0
})

View File

@ -187,9 +187,10 @@
<template #default="{ row }">
<div @click.stop>
<el-switch
:loading="loading"
size="small"
v-model="row.is_active"
@change="changeState($event, row)"
:before-change="() => changeState(row)"
/>
</div>
</template>
@ -678,18 +679,24 @@ function deleteDocument(row: any) {
更新名称或状态
*/
function updateData(documentId: string, data: any, msg: string) {
documentApi.putDocument(id, documentId, data, loading).then((res) => {
const index = documentData.value.findIndex((v) => v.id === documentId)
documentData.value.splice(index, 1, res.data)
MsgSuccess(msg)
})
documentApi
.putDocument(id, documentId, data, loading)
.then((res) => {
const index = documentData.value.findIndex((v) => v.id === documentId)
documentData.value.splice(index, 1, res.data)
MsgSuccess(msg)
return true
})
.catch(() => {
return false
})
}
function changeState(bool: Boolean, row: any) {
function changeState(row: any) {
const obj = {
is_active: bool
is_active: !row.is_active
}
const str = bool ? '启用成功' : '禁用成功'
const str = !row.is_active ? '启用成功' : '禁用成功'
currentMouseId.value && updateData(row.id, obj, str)
}

View File

@ -142,7 +142,7 @@ const functionLibList = ref<any[]>([])
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
page_size: 30,
total: 0
})

View File

@ -104,8 +104,9 @@
>
<div class="active-button" @click.stop>
<el-switch
:loading="loading"
v-model="item.is_active"
@change="changeState($event, item)"
:before-change="() => changeState(item)"
size="small"
/>
</div>
@ -162,7 +163,6 @@
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
<SelectDocumentDialog ref="SelectDocumentDialogRef" @refresh="refreshMigrateParagraph" />
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="refresh" />
</LayoutContainer>
</template>
<script setup lang="ts">
@ -198,7 +198,7 @@ const multipleSelection = ref<any[]>([])
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
page_size: 30,
total: 0
})
@ -258,11 +258,20 @@ function searchHandle() {
getParagraphList()
}
function changeState(bool: Boolean, row: any) {
function changeState(row: any) {
const obj = {
is_active: bool
is_active: !row.is_active
}
paragraph.asyncPutParagraph(id, documentId, row.id, obj, changeStateloading).then((res) => {})
paragraph
.asyncPutParagraph(id, documentId, row.id, obj, changeStateloading)
.then((res) => {
const index = paragraphDetail.value.findIndex((v) => v.id === row.id)
paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active
return true
})
.catch(() => {
return false
})
}
function deleteParagraph(row: any) {
@ -328,7 +337,6 @@ function refresh(data: any) {
}
}
const GenerateRelatedDialogRef = ref()
function openGenerateDialog(row?: any) {
const arr: string[] = []

View File

@ -331,8 +331,10 @@ onMounted(() => {
set(props.nodeModel.properties.node_data, 'is_result', true)
}
}
set(props.nodeModel, 'validate', validate)
if (!chat_data.value.dialogue_type) {
chat_data.value.dialogue_type = 'WORKFLOW'
}
})
</script>
<style lang="scss" scoped></style>