Compare commits

..

26 Commits
main ... v1.9.1

Author SHA1 Message Date
wxg0103 a1d5c83f2f fix: error
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
(cherry picked from commit 2a63cd6bea)
2025-01-08 18:06:22 +08:00
shaohuzhang1 e73414d9a9 perf: Optimize workflow logic (#1996)
(cherry picked from commit 3e327d52d6)
2025-01-08 16:16:14 +08:00
wxg0103 c6e9a99cad fix: Front-end error
(cherry picked from commit b07641cf66)
2025-01-08 16:16:14 +08:00
CaptainB e6e4b68100 refactor: image model get_num_tokens override
(cherry picked from commit c46b7ab094)
2025-01-08 12:49:03 +08:00
wxg0103 9acc11786c fix: fix github#1988
--bug=1051321 --user=王孝刚 【github#1988】【应用】应用编排接入带用户输入参数的应用,选择后,再次进入应用,选择的参数消失了 https://www.tapd.cn/57709429/s/1643562
--bug=1051323 --user=王孝刚 【应用】创建应用报错 https://www.tapd.cn/57709429/s/1643571

(cherry picked from commit bf279898b9)
2025-01-08 12:49:03 +08:00
shaohuzhang1 3b529ab386 fix: Document upload prompt: missing status list field (#1991)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
(cherry picked from commit 8db35c45c1)
2025-01-07 18:24:19 +08:00
shaohuzhang1 c6abb6d77d build: Specify the poetry version (#1989)
Some checks are pending
sync2gitee / repo-sync (push) Waiting to run
(cherry picked from commit 923c75512b)
2025-01-07 15:18:51 +08:00
shaohuzhang1 8450a5b06d fix: Guide package (#1986)
(cherry picked from commit d744fb4106)
2025-01-07 15:18:50 +08:00
shaohuzhang1 b4ffe656ad fix: tencent and xf embedding model set default model (#1985)
(cherry picked from commit 4bddd1aab1)
2025-01-07 15:18:48 +08:00
shaohuzhang1 1bfaf2f024 feat: Document vectorization supports processing based on status (#1984)
(cherry picked from commit 54381ffaf3)
2025-01-07 15:18:47 +08:00
CaptainB ae56590f3f fix: bailian tti and image model set default model
--bug=1051223 --user=刘瑞斌 【模型管理】阿里云百炼自定义添加图片生成模型报错 https://www.tapd.cn/57709429/s/1642319

--bug=1051218 --user=刘瑞斌 【模型管理】阿里云百炼自定义添加图片理解模型报错 https://www.tapd.cn/57709429/s/1642323

(cherry picked from commit 9a310bfb98)
2025-01-07 15:18:46 +08:00
CaptainB 1605fb5388 fix: openai tti, tts, stt and image model set default model
--bug=1051227 --user=刘瑞斌 【模型管理】OpenAI添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642314

(cherry picked from commit 56b243c0ce)
2025-01-07 15:18:45 +08:00
CaptainB fc54a845f4 fix: qwen tti and image model set default model
--bug=1051228 --user=刘瑞斌 【模型管理】通义千问添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642313

(cherry picked from commit b7bcca6562)
2025-01-07 15:18:44 +08:00
CaptainB e59b531754 fix: tencent tti and image model set default model
--bug=1051229 --user=刘瑞斌 【模型管理】腾讯混元添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642310

(cherry picked from commit 3f063ae791)
2025-01-07 15:18:43 +08:00
CaptainB c2962e9ecb fix: volcanic tts and stt model set default model
--bug=1051231 --user=刘瑞斌 【模型管理】火山引擎添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642307

(cherry picked from commit ff7f9f7876)
2025-01-07 15:18:42 +08:00
CaptainB 6861f95cdb fix: xunfei tts and stt model set default model
--bug=1051233 --user=刘瑞斌 【模型管理】讯飞星火添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642301

(cherry picked from commit ee6afffc6a)
2025-01-07 15:18:41 +08:00
CaptainB ea28f67696 fix: zhipu tti and image model set default model
--bug=1051236 --user=刘瑞斌 【模型管理】智谱AI添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642279

(cherry picked from commit e175864699)
2025-01-07 15:18:40 +08:00
CaptainB 7eaac94649 fix: xinference tti and image model set default model
--bug=1051237 --user=刘瑞斌 【模型管理】Xorbits Inference添加自定义模型报错问题汇总 https://www.tapd.cn/57709429/s/1642278

(cherry picked from commit 9b57655255)
2025-01-07 15:18:39 +08:00
shaohuzhang1 18581c9622 fix: Part of the docx document is parsed incorrectly (#1981)
(cherry picked from commit d9df013e33)
2025-01-07 15:18:37 +08:00
shaohuzhang1 a2eef10234 fix: Workflow The condition setting 'any' did not take effect when the discriminator was executed (#1979)
(cherry picked from commit 00591a5b25)
2025-01-07 15:18:36 +08:00
CaptainB e254951ea8 fix: Unable to enter custom model name
--bug=1051178 --user=刘瑞斌 【模型】添加Ollama的图片理解模型,自定义输入基础模型报错 https://www.tapd.cn/57709429/s/1641511

(cherry picked from commit 301c60ee92)
2025-01-07 15:18:35 +08:00
CatpainB 96763fe140 fix: support zhipu cogview-3-flash model
--bug=1051161 --user=刘瑞斌 【模型】添加智普免费的图片生成模型(Cogview-3-Flash)报错 https://www.tapd.cn/57709429/s/1641187

(cherry picked from commit f9b1f2d927)
2025-01-07 15:18:34 +08:00
wangdan-fit2cloud bd8aebdbb2 fix: tip image 404
(cherry picked from commit 892878396d)
2025-01-07 15:18:33 +08:00
wangdan-fit2cloud 7eedf1da28 perf: application overview update Text
(cherry picked from commit 4db8c9c397)
2025-01-07 15:18:32 +08:00
wangdan-fit2cloud 8d6dc492d8 perf: KnowledgeSource filename too long
(cherry picked from commit b6c676f4dc)
2025-01-07 15:18:31 +08:00
wangdan-fit2cloud 30975698de fix: paragraph-card style
(cherry picked from commit d4c605f671)
2025-01-07 15:18:31 +08:00
789 changed files with 10603 additions and 55213 deletions

View File

@ -1,17 +0,0 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
timezone: "Asia/Shanghai"
day: "friday"
target-branch: "v2"
groups:
python-dependencies:
patterns:
- "*"
# ignore:
# - dependency-name: "pymupdf"
# versions: ["*"]

View File

@ -14,7 +14,7 @@ on:
- linux/amd64,linux/arm64 - linux/amd64,linux/arm64
jobs: jobs:
build-and-push-python-pg-to-ghcr: build-and-push-python-pg-to-ghcr:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- name: Check Disk Space - name: Check Disk Space
run: df -h run: df -h
@ -50,9 +50,6 @@ jobs:
${DOCKER_IMAGE_TAGS} . ${DOCKER_IMAGE_TAGS} .
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with:
# Until https://github.com/tonistiigi/binfmt/issues/215
image: tonistiigi/binfmt:qemu-v7.0.0-28
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry

View File

@ -19,7 +19,7 @@ on:
jobs: jobs:
build-and-push-vector-model-to-ghcr: build-and-push-vector-model-to-ghcr:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- name: Check Disk Space - name: Check Disk Space
run: df -h run: df -h
@ -55,9 +55,6 @@ jobs:
${DOCKER_IMAGE_TAGS} . ${DOCKER_IMAGE_TAGS} .
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with:
# Until https://github.com/tonistiigi/binfmt/issues/215
image: tonistiigi/binfmt:qemu-v7.0.0-28
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry

View File

@ -6,14 +6,9 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
dockerImageTag: dockerImageTag:
description: 'Image Tag' description: 'Docker Image Tag'
default: 'v1.10.7-dev' default: 'v1.9.0-dev'
required: true required: true
dockerImageTagWithLatest:
description: '是否发布latest tag正式发版时选择测试版本切勿选择'
default: false
required: true
type: boolean
architecture: architecture:
description: 'Architecture' description: 'Architecture'
required: true required: true
@ -62,14 +57,13 @@ jobs:
DOCKER_IMAGE=${{ secrets.FIT2CLOUD_REGISTRY_HOST }}/maxkb/maxkb DOCKER_IMAGE=${{ secrets.FIT2CLOUD_REGISTRY_HOST }}/maxkb/maxkb
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }} DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
TAG_NAME=${{ github.event.inputs.dockerImageTag }} TAG_NAME=${{ github.event.inputs.dockerImageTag }}
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }} if [[ ${TAG_NAME} == *dev* ]]; then
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
else
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}" DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
else
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
fi fi
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \ echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \ --build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=${GITHUB_SHA::8} --no-cache \
${DOCKER_IMAGE_TAGS} . ${DOCKER_IMAGE_TAGS} .
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@ -89,7 +83,6 @@ jobs:
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }} password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
- name: Docker Buildx (build-and-push) - name: Docker Buildx (build-and-push)
run: | run: |
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile
build-and-push-to-dockerhub: build-and-push-to-dockerhub:
@ -119,15 +112,14 @@ jobs:
run: | run: |
DOCKER_IMAGE=1panel/maxkb DOCKER_IMAGE=1panel/maxkb
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }} DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
TAG_NAME=${{ github.event.inputs.dockerImageTag }} TAG_NAME=${{ github.event.inputs.dockerImageTag }}
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }} if [[ ${TAG_NAME} == *dev* ]]; then
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
else
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}" DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
else
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
fi fi
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \ echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} \
--build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \ --build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=${GITHUB_SHA::8} --no-cache \
${DOCKER_IMAGE_TAGS} . ${DOCKER_IMAGE_TAGS} .
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@ -146,5 +138,4 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker Buildx (build-and-push) - name: Docker Buildx (build-and-push)
run: | run: |
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile

4
.gitignore vendored
View File

@ -178,10 +178,8 @@ ui/node_modules
ui/dist ui/dist
apps/static apps/static
models/ models/
apps/xpack
!apps/**/models/ !apps/**/models/
data data
.dev .dev
poetry.lock poetry.lock
apps/setting/models_provider/impl/*/icon/ apps/setting/models_provider/impl/*/icon/
tmp/

View File

@ -1,4 +1,4 @@
[files] [files]
extend-exclude = [ extend-exclude = [
'apps/setting/models_provider/impl/*/icon/*' 'apps/setting/models_provider/impl/*/icon/*'
] ]

157
README.md
View File

@ -1,126 +1,95 @@
<p align="center"><img src= "https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf" alt="MaxKB" width="300" /></p> <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">Open-source platform for building enterprise-grade agents</h3> <h3 align="center">基于大模型和 RAG 的知识库问答系统</h3>
<h3 align="center">强大易用的企业级智能体平台</h3> <h4 align="center">Ready-to-use, flexible RAG Chatbot</h4>
<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"> <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://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="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/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><br/> <a href="https://hub.docker.com/r/1panel/maxkb"><img src="https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads" alt="Download"></a>
[<a href="/README_CN.md">中文(简体)</a>] | [<a href="/README.md">English</a>]
</p> </p>
<hr/> <hr/>
MaxKB = Max Knowledge Brain, it is an open-source platform for building enterprise-grade agents. MaxKB integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities. MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education. MaxKB = Max Knowledge Base是一款基于大语言模型和 RAG 的开源知识库问答系统,广泛应用于智能客服、企业内部知识库、学术研究与教育等场景。
- **RAG Pipeline**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization. This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience. - **开箱即用**:支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化和 RAG检索增强生成有效减少大模型幻觉智能问答交互体验好
- **Agentic Workflow**: Equipped with a powerful workflow engine, function library and MCP tool-use, enabling the orchestration of AI processes to meet the needs of complex business scenarios. - **模型中立**支持对接各种大模型包括本地私有大模型Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等和国外公共大模型OpenAI / Claude / Gemini 等);
- **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. - **灵活编排**:内置强大的工作流引擎和函数库,支持编排 AI 工作过程,满足复杂业务场景下的需求;
- **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.). - **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度。
- **Multi Modal**: Native support for input and output text, image, audio and video.
## Quick start MaxKB 三分钟视频介绍https://www.bilibili.com/video/BV18JypYeEkj/
Execute the script below to start a MaxKB container using Docker: ## 快速开始
```bash ```
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages 1panel/maxkb # Linux 机器
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages cr2.fit2cloud.com/1panel/maxkb
# Windows 机器
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/postgresql/data -v C:/python-packages:/opt/maxkb/app/sandbox/python-packages cr2.fit2cloud.com/1panel/maxkb
# 用户名: admin
# 密码: MaxKB@123..
``` ```
Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials: - 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html)
- 如果您需要向团队介绍 MaxKB可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202501.pdf)。
- username: admin 如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
- password: MaxKB@123..
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/installation/offline_installtion/) 进行安装。 - [使用手册](https://maxkb.cn/docs/)
- [论坛求助](https://bbs.fit2cloud.com/c/mk/11)
- 技术交流群
## Screenshots <image height="150px" width="150px" src="https://github.com/1Panel-dev/MaxKB/assets/52996290/a083d214-02be-4178-a1db-4f428124153a"/>
## 案例展示
MaxKB 自发布以来,日均安装下载超过 1000 次,被广泛应用于智能客服、企业内部知识库、学术教育研究等场景。
- [华莱士智能客服](https://ai.cnhls.com/ui/chat/1fc0f6a9b5a6fb27)
- [JumpServer 小助手](https://maxkb.fit2cloud.com/ui/chat/b4e27a6e72d349a3)
- [信用深圳](https://www.szcredit.org.cn/#/index)
- [重庆交通大学教务在线](http://jwc.anyquestion.cn/ui/chat/b75496390f7d935d)
## UI 展示
<table style="border-collapse: collapse; border: 1px solid black;"> <table style="border-collapse: collapse; border: 1px solid black;">
<tr> <tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/overview.png" alt="MaxKB Demo1" /></td> <td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/d87395fa-a8d7-401c-82bf-c6e475d10ae9" alt="MaxKB Demo1" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-models.png" alt="MaxKB Demo2" /></td> <td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/47c35ee4-3a3b-4bd4-9f4f-ee20788b2b9a" alt="MaxKB Demo2" /></td>
</tr> </tr>
<tr> <tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-knowledge.png" alt="MaxKB Demo3" /></td> <td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/9a1043cb-fa62-4f71-b9a3-0b46fa59a70e" alt="MaxKB Demo3" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-function.png" alt="MaxKB Demo4" /></td> <td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/3407ce9a-779c-4eb4-858e-9441a2ddc664" alt="MaxKB Demo4" /></td>
</tr> </tr>
</table> </table>
## Technical stack ## 技术栈
- Frontend[Vue.js](https://vuejs.org/) - 前端:[Vue.js](https://cn.vuejs.org/)
- Backend[Python / Django](https://www.djangoproject.com/) - 后端[Python / Django](https://www.djangoproject.com/)
- LLM Framework[LangChain](https://www.langchain.com/) - LangChain[LangChain](https://www.langchain.com/)
- Database[PostgreSQL + pgvector](https://www.postgresql.org/) - 向量数据库:[PostgreSQL / pgvector](https://www.postgresql.org/)
## Feature Comparison ## 飞致云的其他明星项目
<table style="width: 100%;"> - [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板
<tr> - [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
<th align="center">Feature</th> - [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
<th align="center">LangChain</th> - [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具
<th align="center">Dify.AI</th> - [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
<th align="center">Flowise</th>
<th align="center">MaxKB <br>Built upon LangChain</th>
</tr>
<tr>
<td align="center">Supported LLMs</td>
<td align="center">Rich Variety</td>
<td align="center">Rich Variety</td>
<td align="center">Rich Variety</td>
<td align="center">Rich Variety</td>
</tr>
<tr>
<td align="center">RAG Engine</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">Agent</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">Workflow</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">Observability</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">SSO/Access control</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">✅ (Pro)</td>
</tr>
<tr>
<td align="center">On-premise Deployment</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</table>
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/MaxKB&type=Date)](https://star-history.com/#1Panel-dev/MaxKB&Date)
## License ## License
Copyright (c) 2014-2025 飞致云 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 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> <https://www.gnu.org/licenses/gpl-3.0.html>

View File

@ -1,89 +0,0 @@
<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">强大易用的企业级智能体平台</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>
</p>
<p align="center">
<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?color=%231890FF" 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?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>
<a href="https://gitee.com/fit2cloud-feizhiyun/MaxKB"><img src="https://gitee.com/fit2cloud-feizhiyun/MaxKB/badge/star.svg?theme=gvp" alt="Gitee Stars"></a>
<a href="https://gitcode.com/feizhiyun/MaxKB"><img src="https://gitcode.com/feizhiyun/MaxKB/star/badge.svg" alt="GitCode Stars"></a>
</p>
<hr/>
MaxKB = Max Knowledge Brain是一款强大易用的企业级智能体平台支持 RAG 检索增强生成、工作流编排、MCP 工具调用能力。MaxKB 支持对接各种主流大语言模型,广泛应用于智能客服、企业内部知识库问答、员工助手、学术研究与教育等场景。
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果;
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求;
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度;
- **模型中立**支持对接各种大模型包括本地私有大模型DeepSeek R1 / Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等和国外公共大模型OpenAI / Claude / Gemini 等)。
MaxKB 三分钟视频介绍https://www.bilibili.com/video/BV18JypYeEkj/
## 快速开始
```
# Linux 机器
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
# Windows 机器
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/var/lib/postgresql/data -v C:/python-packages:/opt/maxkb/app/sandbox/python-packages registry.fit2cloud.com/maxkb/maxkb
# 用户名: admin
# 密码: MaxKB@123..
```
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html)
- 如果您需要向团队介绍 MaxKB可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202503.pdf)。
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
- [案例展示](USE-CASES.md)
- [使用手册](https://maxkb.cn/docs/)
- [论坛求助](https://bbs.fit2cloud.com/c/mk/11)
- 技术交流群
<image height="150px" width="150px" src="https://github.com/1Panel-dev/MaxKB/assets/52996290/a083d214-02be-4178-a1db-4f428124153a"/>
## UI 展示
<table style="border-collapse: collapse; border: 1px solid black;">
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/d87395fa-a8d7-401c-82bf-c6e475d10ae9" alt="MaxKB Demo1" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/47c35ee4-3a3b-4bd4-9f4f-ee20788b2b9a" alt="MaxKB Demo2" /></td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/9a1043cb-fa62-4f71-b9a3-0b46fa59a70e" alt="MaxKB Demo3" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/3407ce9a-779c-4eb4-858e-9441a2ddc664" alt="MaxKB Demo4" /></td>
</tr>
</table>
## 技术栈
- 前端:[Vue.js](https://cn.vuejs.org/)
- 后端:[Python / Django](https://www.djangoproject.com/)
- LangChain[LangChain](https://www.langchain.com/)
- 向量数据库:[PostgreSQL / pgvector](https://www.postgresql.org/)
## 飞致云的其他明星项目
- [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具
- [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
## License
Copyright (c) 2014-2025 飞致云 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>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

54
README_EN.md Normal file
View File

@ -0,0 +1,54 @@
<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">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>
<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://hub.docker.com/r/1panel/maxkb"><img src="https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads" alt="Download"></a>
</p>
<hr/>
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.
## Quick start
```
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data -v ~/.python-packages:/opt/maxkb/app/sandbox/python-packages cr2.fit2cloud.com/1panel/maxkb
# username: admin
# pass: MaxKB@123..
```
## Screenshots
<table style="border-collapse: collapse; border: 1px solid black;">
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/d87395fa-a8d7-401c-82bf-c6e475d10ae9" alt="MaxKB Demo1" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/47c35ee4-3a3b-4bd4-9f4f-ee20788b2b9a" alt="MaxKB Demo2" /></td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/1c0c5e32-6194-47f9-bc32-487996349d9c" alt="MaxKB Demo3" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/1Panel-dev/MaxKB/assets/52996290/f32f5fe9-a769-488c-ae0e-783bc2b89b3e" alt="MaxKB Demo4" /></td>
</tr>
</table>
## Technical stack
- 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/)
## License
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>
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View File

@ -1,39 +0,0 @@
<h3 align="center">MaxKB 应用案例,持续更新中...</h3>
------------------------------
- [MaxKB 应用案例:中国农业大学-小鹉哥](https://mp.weixin.qq.com/s/4g_gySMBQZCJ9OZ-yBkmvw)
- [MaxKB 应用案例:东北财经大学-小银杏](https://mp.weixin.qq.com/s/3BoxkY7EMomMmmvFYxvDIA)
- [MaxKB 应用案例:中铁水务](https://mp.weixin.qq.com/s/voNAddbK2CJOrJJs1ewZ8g)
- [MaxKB 应用案例:解放军总医院](https://mp.weixin.qq.com/s/ETrZC-vrA4Aap0eF-15EeQ)
- [MaxKB 应用案例:无锡市数据局](https://mp.weixin.qq.com/s/enfUFLevvL_La74PQ0kIXw)
- [MaxKB 应用案例:中核西仪研究院-西仪睿答](https://mp.weixin.qq.com/s/CbKr4mev8qahKLAtV6Dxdg)
- [MaxKB 应用案例:南京中医药大学](https://mp.weixin.qq.com/s/WUmAKYbZjp3272HIecpRFA)
- [MaxKB 应用案例:西北电力设计院-AI数字助理Memex](https://mp.weixin.qq.com/s/ezHFdB7C7AVL9MTtDwYGSA)
- [MaxKB 应用案例:西安国际医院中心医院-国医小助](https://mp.weixin.qq.com/s/DSOUvwrQrxbqQxKBilTCFQ)
- [MaxKB 应用案例华莱士智能AI客服助手上线啦](https://www.bilibili.com/video/BV1hQtVeXEBL)
- [MaxKB 应用案例:把医疗行业知识转化为知识库问答助手!](https://www.bilibili.com/video/BV157wme9EgB)
- [MaxKB 应用案例会展AI智能客服体验](https://www.bilibili.com/video/BV1J7BqY6EKA)
- [MaxKB 应用案例孩子要上幼儿园了AI 智能助手择校好帮手](https://www.bilibili.com/video/BV1wKrhYvEer)
- [MaxKB 应用案例产品使用指南AI助手新手小白也能轻松搞定](https://www.bilibili.com/video/BV1Yz6gYtEqX)
- [MaxKB 应用案例生物医药AI客服智能体验!](https://www.bilibili.com/video/BV13JzvYsE3e)
- [MaxKB 应用案例高校行政管理AI小助手](https://www.bilibili.com/video/BV1yvBMYvEdy)
- [MaxKB 应用案例:岳阳市人民医院-OA小助手](https://mp.weixin.qq.com/s/O94Qo3UH-MiUtDdWCVg8sQ)
- [MaxKB 应用案例:常熟市第一人民医院](https://mp.weixin.qq.com/s/s5XXGTR3_MUo41NbJ8WzZQ)
- [MaxKB 应用案例:华北水利水电大学](https://mp.weixin.qq.com/s/PoOFAcMCr9qJdvSj8c08qg)
- [MaxKB 应用案例:唐山海事局-“小海”AI语音助手](https://news.qq.com/rain/a/20250223A030BE00)
- [MaxKB 应用案例:湖南汉寿政务](http://hsds.hsdj.gov.cn:19999/ui/chat/a2c976736739aadc)
- [MaxKB 应用案例:广州市妇女儿童医疗中心-AI医疗数据分类分级小助手](https://mp.weixin.qq.com/s/YHUMkUOAaUomBV8bswpK3g)
- [MaxKB 应用案例:苏州热工研究院有限公司-维修大纲评估质量自查AI小助手](https://mp.weixin.qq.com/s/Ts5FQdnv7Tu9Jp7bvofCVA)
- [MaxKB 应用案例:国核自仪系统工程有限公司-NuCON AI帮](https://mp.weixin.qq.com/s/HNPc7u5xVfGLJr8IQz3vjQ)
- [MaxKB 应用案例深圳通开启Deep Seek智能应用新篇章](https://mp.weixin.qq.com/s/SILN0GSescH9LyeQqYP0VQ)
- [MaxKB 应用案例南通智慧出行领跑长三角首款接入DeepSeek的"畅行南通"APP上线AI新场景](https://mp.weixin.qq.com/s/WEC9UQ6msY0VS8LhTZh-Ew)
- [MaxKB 应用案例:中船动力人工智能"智慧动力云助手"及首批数字员工正式上线](https://mp.weixin.qq.com/s/OGcEkjh9DzGO1Tkc9nr7qg)
- [MaxKB 应用案例AI+矿山DeepSeek助力绿色智慧矿山智慧“升级”](https://mp.weixin.qq.com/s/SZstxTvVoLZg0ECbZbfpIA)
- [MaxKB 应用案例DeepSeek落地弘盛铜业国产大模型点亮"黑灯工厂"新引擎](https://mp.weixin.qq.com/s/Eczdx574MS5RMF7WfHN7_A)
- [MaxKB 应用案例:拥抱智能时代!中国五矿以 “AI+”赋能企业发展](https://mp.weixin.qq.com/s/D5vBtlX2E81pWE3_2OgWSw)
- [MaxKB 应用案例DeepSeek赋能中冶武勘AI智能体](https://mp.weixin.qq.com/s/8m0vxGcWXNdZazziQrLyxg)
- [MaxKB 应用案例重磅陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA)
- [MaxKB 应用案例粤海集团完成DeepSeek私有化部署助力集团智能化管理](https://mp.weixin.qq.com/s/2JbVp0-kr9Hfp-0whH4cvg)
- [MaxKB 应用案例建筑材料工业信息中心完成DeepSeek本地化部署推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)
- [MaxKB 应用案例一起DeepSeek福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)

View File

@ -9,7 +9,6 @@
from abc import abstractmethod from abc import abstractmethod
from typing import Type, List from typing import Type, List
from django.utils.translation import gettext_lazy as _
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.schema import BaseMessage from langchain.schema import BaseMessage
from rest_framework import serializers from rest_framework import serializers
@ -24,7 +23,7 @@ from common.util.field_message import ErrMessage
class ModelField(serializers.Field): class ModelField(serializers.Field):
def to_internal_value(self, data): def to_internal_value(self, data):
if not isinstance(data, BaseChatModel): if not isinstance(data, BaseChatModel):
self.fail(_('Model type error'), value=data) self.fail('模型类型错误', value=data)
return data return data
def to_representation(self, value): def to_representation(self, value):
@ -34,7 +33,7 @@ class ModelField(serializers.Field):
class MessageField(serializers.Field): class MessageField(serializers.Field):
def to_internal_value(self, data): def to_internal_value(self, data):
if not isinstance(data, BaseMessage): if not isinstance(data, BaseMessage):
self.fail(_('Message type error'), value=data) self.fail('message类型错误', value=data)
return data return data
def to_representation(self, value): def to_representation(self, value):
@ -53,42 +52,37 @@ class IChatStep(IBaseChatPipelineStep):
class InstanceSerializer(serializers.Serializer): class InstanceSerializer(serializers.Serializer):
# 对话列表 # 对话列表
message_list = serializers.ListField(required=True, child=MessageField(required=True), message_list = serializers.ListField(required=True, child=MessageField(required=True),
error_messages=ErrMessage.list(_("Conversation list"))) error_messages=ErrMessage.list("对话列表"))
model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid(_("Model id"))) model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid("模型id"))
# 段落列表 # 段落列表
paragraph_list = serializers.ListField(error_messages=ErrMessage.list(_("Paragraph List"))) paragraph_list = serializers.ListField(error_messages=ErrMessage.list("段落列表"))
# 对话id # 对话id
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
# 用户问题 # 用户问题
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.uuid(_("User Questions"))) problem_text = serializers.CharField(required=True, error_messages=ErrMessage.uuid("用户问题"))
# 后置处理器 # 后置处理器
post_response_handler = InstanceField(model_type=PostResponseHandler, post_response_handler = InstanceField(model_type=PostResponseHandler,
error_messages=ErrMessage.base(_("Post-processor"))) error_messages=ErrMessage.base("用户问题"))
# 补全问题 # 补全问题
padding_problem_text = serializers.CharField(required=False, padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.base("补全问题"))
error_messages=ErrMessage.base(_("Completion Question")))
# 是否使用流的形式输出 # 是否使用流的形式输出
stream = serializers.BooleanField(required=False, error_messages=ErrMessage.base(_("Streaming Output"))) stream = serializers.BooleanField(required=False, error_messages=ErrMessage.base("流式输出"))
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client id"))) client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client Type"))) client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
# 未查询到引用分段 # 未查询到引用分段
no_references_setting = NoReferencesSetting(required=True, no_references_setting = NoReferencesSetting(required=True, error_messages=ErrMessage.base("无引用分段设置"))
error_messages=ErrMessage.base(_("No reference segment settings")))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
model_setting = serializers.DictField(required=True, allow_null=True,
error_messages=ErrMessage.dict(_("Model settings")))
model_params_setting = serializers.DictField(required=False, allow_null=True, model_params_setting = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Model parameter settings"))) error_messages=ErrMessage.dict("模型参数设置"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
message_list: List = self.initial_data.get('message_list') message_list: List = self.initial_data.get('message_list')
for message in message_list: for message in message_list:
if not isinstance(message, BaseMessage): if not isinstance(message, BaseMessage):
raise Exception(_("message type error")) raise Exception("message 类型错误")
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
return self.InstanceSerializer return self.InstanceSerializer
@ -106,5 +100,5 @@ class IChatStep(IBaseChatPipelineStep):
paragraph_list=None, paragraph_list=None,
manage: PipelineManage = None, manage: PipelineManage = None,
padding_problem_text: str = None, stream: bool = True, client_id=None, client_type=None, padding_problem_text: str = None, stream: bool = True, client_id=None, client_type=None,
no_references_setting=None, model_params_setting=None, model_setting=None, **kwargs): no_references_setting=None, model_params_setting=None, **kwargs):
pass pass

View File

@ -14,7 +14,6 @@ from typing import List
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
from django.utils.translation import gettext as _
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.schema import BaseMessage from langchain.schema import BaseMessage
from langchain.schema.messages import HumanMessage, AIMessage from langchain.schema.messages import HumanMessage, AIMessage
@ -24,7 +23,6 @@ from rest_framework import status
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
from application.chat_pipeline.pipeline_manage import PipelineManage from application.chat_pipeline.pipeline_manage import PipelineManage
from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler
from application.flow.tools import Reasoning
from application.models.api_key_model import ApplicationPublicAccessClient from application.models.api_key_model import ApplicationPublicAccessClient
from common.constants.authentication_type import AuthenticationType from common.constants.authentication_type import AuthenticationType
from setting.models_provider.tools import get_model_instance_by_model_user_id from setting.models_provider.tools import get_model_instance_by_model_user_id
@ -64,54 +62,17 @@ def event_content(response,
problem_text: str, problem_text: str,
padding_problem_text: str = None, padding_problem_text: str = None,
client_id=None, client_type=None, client_id=None, client_type=None,
is_ai_chat: bool = None, is_ai_chat: bool = None):
model_setting=None):
if model_setting is None:
model_setting = {}
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')
reasoning = Reasoning(reasoning_content_start,
reasoning_content_end)
all_text = '' all_text = ''
reasoning_content = ''
try: try:
response_reasoning_content = False
for chunk in response: for chunk in response:
reasoning_chunk = reasoning.get_reasoning_content(chunk) all_text += chunk.content
content_chunk = reasoning_chunk.get('content')
if 'reasoning_content' in chunk.additional_kwargs:
response_reasoning_content = True
reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '')
else:
reasoning_content_chunk = reasoning_chunk.get('reasoning_content')
all_text += content_chunk
if reasoning_content_chunk is None:
reasoning_content_chunk = ''
reasoning_content += reasoning_content_chunk
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
[], content_chunk, [], chunk.content,
False, False,
0, 0, {'node_is_end': False, 0, 0, {'node_is_end': False,
'view_type': 'many_view', 'view_type': 'many_view',
'node_type': 'ai-chat-node', 'node_type': 'ai-chat-node'})
'real_node_id': 'ai-chat-node',
'reasoning_content': reasoning_content_chunk if reasoning_content_enable else ''})
reasoning_chunk = reasoning.get_end_reasoning_content()
all_text += reasoning_chunk.get('content')
reasoning_content_chunk = ""
if not response_reasoning_content:
reasoning_content_chunk = reasoning_chunk.get(
'reasoning_content')
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
[], reasoning_chunk.get('content'),
False,
0, 0, {'node_is_end': False,
'view_type': 'many_view',
'node_type': 'ai-chat-node',
'real_node_id': 'ai-chat-node',
'reasoning_content'
: reasoning_content_chunk if reasoning_content_enable else ''})
# 获取token # 获取token
if is_ai_chat: if is_ai_chat:
try: try:
@ -124,11 +85,8 @@ def event_content(response,
request_token = 0 request_token = 0
response_token = 0 response_token = 0
write_context(step, manage, request_token, response_token, all_text) write_context(step, manage, request_token, response_token, all_text)
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
all_text, manage, step, padding_problem_text, client_id, all_text, manage, step, padding_problem_text, client_id)
reasoning_content=reasoning_content if reasoning_content_enable else ''
, asker=asker)
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',
[], '', True, [], '', True,
request_token, response_token, request_token, response_token,
@ -137,21 +95,16 @@ def event_content(response,
add_access_num(client_id, client_type, manage.context.get('application_id')) add_access_num(client_id, client_type, manage.context.get('application_id'))
except Exception as e: except Exception as e:
logging.getLogger("max_kb_error").error(f'{str(e)}:{traceback.format_exc()}') logging.getLogger("max_kb_error").error(f'{str(e)}:{traceback.format_exc()}')
all_text = 'Exception:' + str(e) all_text = '异常' + str(e)
write_context(step, manage, 0, 0, all_text) write_context(step, manage, 0, 0, all_text)
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
all_text, manage, step, padding_problem_text, client_id, reasoning_content='', all_text, manage, step, padding_problem_text, client_id)
asker=asker)
add_access_num(client_id, client_type, manage.context.get('application_id')) add_access_num(client_id, client_type, manage.context.get('application_id'))
yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node', yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), all_text,
[], all_text, 'ai-chat-node',
False, [], True, 0, 0,
0, 0, {'node_is_end': False, {'node_is_end': True, 'view_type': 'many_view',
'view_type': 'many_view', 'node_type': 'ai-chat-node'})
'node_type': 'ai-chat-node',
'real_node_id': 'ai-chat-node',
'reasoning_content': ''})
class BaseChatStep(IChatStep): class BaseChatStep(IChatStep):
@ -168,20 +121,17 @@ class BaseChatStep(IChatStep):
client_id=None, client_type=None, client_id=None, client_type=None,
no_references_setting=None, no_references_setting=None,
model_params_setting=None, model_params_setting=None,
model_setting=None,
**kwargs): **kwargs):
chat_model = get_model_instance_by_model_user_id(model_id, user_id, chat_model = get_model_instance_by_model_user_id(model_id, user_id,
**model_params_setting) if model_id is not None else None **model_params_setting) if model_id is not None else None
if stream: if stream:
return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model, return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model,
paragraph_list, paragraph_list,
manage, padding_problem_text, client_id, client_type, no_references_setting, manage, padding_problem_text, client_id, client_type, no_references_setting)
model_setting)
else: else:
return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model, return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model,
paragraph_list, paragraph_list,
manage, padding_problem_text, client_id, client_type, no_references_setting, manage, padding_problem_text, client_id, client_type, no_references_setting)
model_setting)
def get_details(self, manage, **kwargs): def get_details(self, manage, **kwargs):
return { return {
@ -222,8 +172,7 @@ class BaseChatStep(IChatStep):
return iter( return iter(
[AIMessageChunk(content=no_references_setting.get('value').replace('{question}', problem_text))]), False [AIMessageChunk(content=no_references_setting.get('value').replace('{question}', problem_text))]), False
if chat_model is None: if chat_model is None:
return iter([AIMessageChunk( return iter([AIMessageChunk('抱歉,没有配置 AI 模型,请先去应用中设置 AI 模型。')]), False
_('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.'))]), False
else: else:
return chat_model.stream(message_list), True return chat_model.stream(message_list), True
@ -236,15 +185,14 @@ class BaseChatStep(IChatStep):
manage: PipelineManage = None, manage: PipelineManage = None,
padding_problem_text: str = None, padding_problem_text: str = None,
client_id=None, client_type=None, client_id=None, client_type=None,
no_references_setting=None, no_references_setting=None):
model_setting=None):
chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list, chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list,
no_references_setting, problem_text) no_references_setting, problem_text)
chat_record_id = uuid.uuid1() chat_record_id = uuid.uuid1()
r = StreamingHttpResponse( r = StreamingHttpResponse(
streaming_content=event_content(chat_result, chat_id, chat_record_id, paragraph_list, streaming_content=event_content(chat_result, chat_id, chat_record_id, paragraph_list,
post_response_handler, manage, self, chat_model, message_list, problem_text, post_response_handler, manage, self, chat_model, message_list, problem_text,
padding_problem_text, client_id, client_type, is_ai_chat, model_setting), padding_problem_text, client_id, client_type, is_ai_chat),
content_type='text/event-stream;charset=utf-8') content_type='text/event-stream;charset=utf-8')
r['Cache-Control'] = 'no-cache' r['Cache-Control'] = 'no-cache'
@ -258,17 +206,17 @@ class BaseChatStep(IChatStep):
problem_text=None): problem_text=None):
if paragraph_list is None: if paragraph_list is None:
paragraph_list = [] paragraph_list = []
directly_return_chunk_list = [AIMessageChunk(content=paragraph.content)
for paragraph in paragraph_list if ( directly_return_chunk_list = [AIMessage(content=paragraph.content)
paragraph.hit_handling_method == 'directly_return' and paragraph.similarity >= paragraph.directly_return_similarity)] for paragraph in paragraph_list if
paragraph.hit_handling_method == 'directly_return']
if directly_return_chunk_list is not None and len(directly_return_chunk_list) > 0: if directly_return_chunk_list is not None and len(directly_return_chunk_list) > 0:
return directly_return_chunk_list[0], False return directly_return_chunk_list[0], False
elif len(paragraph_list) == 0 and no_references_setting.get( elif len(paragraph_list) == 0 and no_references_setting.get(
'status') == 'designated_answer': 'status') == 'designated_answer':
return AIMessage(no_references_setting.get('value').replace('{question}', problem_text)), False return AIMessage(no_references_setting.get('value').replace('{question}', problem_text)), False
if chat_model is None: if chat_model is None:
return AIMessage( return AIMessage('抱歉,没有配置 AI 模型,请先去应用中设置 AI 模型。'), False
_('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.')), False
else: else:
return chat_model.invoke(message_list), True return chat_model.invoke(message_list), True
@ -280,13 +228,7 @@ class BaseChatStep(IChatStep):
paragraph_list=None, paragraph_list=None,
manage: PipelineManage = None, manage: PipelineManage = None,
padding_problem_text: str = None, padding_problem_text: str = None,
client_id=None, client_type=None, no_references_setting=None, client_id=None, client_type=None, no_references_setting=None):
model_setting=None):
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')
reasoning = Reasoning(reasoning_content_start,
reasoning_content_end)
chat_record_id = uuid.uuid1() chat_record_id = uuid.uuid1()
# 调用模型 # 调用模型
try: try:
@ -299,36 +241,17 @@ class BaseChatStep(IChatStep):
request_token = 0 request_token = 0
response_token = 0 response_token = 0
write_context(self, manage, request_token, response_token, chat_result.content) write_context(self, manage, request_token, response_token, chat_result.content)
reasoning_result = reasoning.get_reasoning_content(chat_result)
reasoning_result_end = reasoning.get_end_reasoning_content()
content = reasoning_result.get('content') + reasoning_result_end.get('content')
if 'reasoning_content' in chat_result.response_metadata:
reasoning_content = chat_result.response_metadata.get('reasoning_content', '')
else:
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get(
'reasoning_content')
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
content, manage, self, padding_problem_text, client_id, chat_result.content, manage, self, padding_problem_text, client_id)
reasoning_content=reasoning_content if reasoning_content_enable else '',
asker=asker)
add_access_num(client_id, client_type, manage.context.get('application_id')) add_access_num(client_id, client_type, manage.context.get('application_id'))
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id),
content, True, chat_result.content, True,
request_token, response_token, request_token, response_token)
{
'reasoning_content': reasoning_content if reasoning_content_enable else '',
'answer_list': [{
'content': content,
'reasoning_content': reasoning_content if reasoning_content_enable else ''
}]})
except Exception as e: except Exception as e:
all_text = 'Exception:' + str(e) all_text = '异常' + str(e)
write_context(self, manage, 0, 0, all_text) write_context(self, manage, 0, 0, all_text)
asker = manage.context.get('form_data', {}).get('asker', None)
post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,
all_text, manage, self, padding_problem_text, client_id, reasoning_content='', all_text, manage, self, padding_problem_text, client_id)
asker=asker)
add_access_num(client_id, client_type, manage.context.get('application_id')) add_access_num(client_id, client_type, manage.context.get('application_id'))
return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0, return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0,
0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR) 0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR)

View File

@ -9,7 +9,6 @@
from abc import abstractmethod from abc import abstractmethod
from typing import Type, List from typing import Type, List
from django.utils.translation import gettext_lazy as _
from langchain.schema import BaseMessage from langchain.schema import BaseMessage
from rest_framework import serializers from rest_framework import serializers
@ -24,26 +23,26 @@ from common.util.field_message import ErrMessage
class IGenerateHumanMessageStep(IBaseChatPipelineStep): class IGenerateHumanMessageStep(IBaseChatPipelineStep):
class InstanceSerializer(serializers.Serializer): class InstanceSerializer(serializers.Serializer):
# 问题 # 问题
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("question"))) problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char("问题"))
# 段落列表 # 段落列表
paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True), paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True),
error_messages=ErrMessage.list(_("Paragraph List"))) error_messages=ErrMessage.list("段落列表"))
# 历史对答 # 历史对答
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
error_messages=ErrMessage.list(_("History Questions"))) error_messages=ErrMessage.list("历史对答"))
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
# 最大携带知识库段落长度 # 最大携带知识库段落长度
max_paragraph_char_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer( max_paragraph_char_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(
_("Maximum length of the knowledge base paragraph"))) "最大携带知识库段落长度"))
# 模板 # 模板
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词"))
system = serializers.CharField(required=False, allow_null=True, allow_blank=True, system = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("System prompt words (role)"))) error_messages=ErrMessage.char("系统提示词(角色)"))
# 补齐问题 # 补齐问题
padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Completion problem"))) padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char("补齐问题"))
# 未查询到引用分段 # 未查询到引用分段
no_references_setting = NoReferencesSetting(required=True, error_messages=ErrMessage.base(_("No reference segment settings"))) no_references_setting = NoReferencesSetting(required=True, error_messages=ErrMessage.base("无引用分段设置"))
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
return self.InstanceSerializer return self.InstanceSerializer

View File

@ -9,11 +9,12 @@
from abc import abstractmethod from abc import abstractmethod
from typing import Type, List from typing import Type, List
from django.utils.translation import gettext_lazy as _ from langchain.chat_models.base import BaseChatModel
from rest_framework import serializers from rest_framework import serializers
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep
from application.chat_pipeline.pipeline_manage import PipelineManage from application.chat_pipeline.pipeline_manage import PipelineManage
from application.chat_pipeline.step.chat_step.i_chat_step import ModelField
from application.models import ChatRecord from application.models import ChatRecord
from common.field.common import InstanceField from common.field.common import InstanceField
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
@ -22,16 +23,15 @@ from common.util.field_message import ErrMessage
class IResetProblemStep(IBaseChatPipelineStep): class IResetProblemStep(IBaseChatPipelineStep):
class InstanceSerializer(serializers.Serializer): class InstanceSerializer(serializers.Serializer):
# 问题文本 # 问题文本
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.float(_("question"))) problem_text = serializers.CharField(required=True, error_messages=ErrMessage.float("问题文本"))
# 历史对答 # 历史对答
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
error_messages=ErrMessage.list(_("History Questions"))) error_messages=ErrMessage.list("历史对答"))
# 大语言模型 # 大语言模型
model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid(_("Model id"))) model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid("模型id"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400, problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
error_messages=ErrMessage.char( error_messages=ErrMessage.char("问题补全提示词"))
_("Question completion prompt")))
def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]: def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:
return self.InstanceSerializer return self.InstanceSerializer

View File

@ -8,7 +8,6 @@
""" """
from typing import List from typing import List
from django.utils.translation import gettext as _
from langchain.schema import HumanMessage from langchain.schema import HumanMessage
from application.chat_pipeline.step.reset_problem_step.i_reset_problem_step import IResetProblemStep from application.chat_pipeline.step.reset_problem_step.i_reset_problem_step import IResetProblemStep
@ -16,8 +15,8 @@ from application.models import ChatRecord
from common.util.split_model import flat_map from common.util.split_model import flat_map
from setting.models_provider.tools import get_model_instance_by_model_user_id from setting.models_provider.tools import get_model_instance_by_model_user_id
prompt = _( prompt = (
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag") '()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中')
class BaseResetProblemStep(IResetProblemStep): class BaseResetProblemStep(IResetProblemStep):

View File

@ -11,7 +11,6 @@ from abc import abstractmethod
from typing import List, Type from typing import List, Type
from django.core import validators from django.core import validators
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel from application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel
@ -22,30 +21,29 @@ from common.util.field_message import ErrMessage
class ISearchDatasetStep(IBaseChatPipelineStep): class ISearchDatasetStep(IBaseChatPipelineStep):
class InstanceSerializer(serializers.Serializer): class InstanceSerializer(serializers.Serializer):
# 原始问题文本 # 原始问题文本
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("question"))) problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char("问题"))
# 系统补全问题文本 # 系统补全问题文本
padding_problem_text = serializers.CharField(required=False, padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char("系统补全问题文本"))
error_messages=ErrMessage.char(_("System completes question text")))
# 需要查询的数据集id列表 # 需要查询的数据集id列表
dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("Dataset id list"))) error_messages=ErrMessage.list("数据集id列表"))
# 需要排除的文档id # 需要排除的文档id
exclude_document_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), exclude_document_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("List of document ids to exclude"))) error_messages=ErrMessage.list("排除的文档id列表"))
# 需要排除向量id # 需要排除向量id
exclude_paragraph_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), exclude_paragraph_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("List of exclusion vector ids"))) error_messages=ErrMessage.list("排除向量id列表"))
# 需要查询的条数 # 需要查询的条数
top_n = serializers.IntegerField(required=True, top_n = serializers.IntegerField(required=True,
error_messages=ErrMessage.integer(_("Reference segment number"))) error_messages=ErrMessage.integer("引用分段数"))
# 相似度 0-1之间 # 相似度 0-1之间
similarity = serializers.FloatField(required=True, max_value=1, min_value=0, similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
error_messages=ErrMessage.float(_("Similarity"))) error_messages=ErrMessage.float("引用分段数"))
search_mode = serializers.CharField(required=True, validators=[ search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports embedding|keywords|blend"), code=500) message="类型只支持register|reset_password", code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode"))) ], error_messages=ErrMessage.char("检索模式"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
def get_step_serializer(self, manage: PipelineManage) -> Type[InstanceSerializer]: def get_step_serializer(self, manage: PipelineManage) -> Type[InstanceSerializer]:
return self.InstanceSerializer return self.InstanceSerializer

View File

@ -10,8 +10,6 @@ import os
from typing import List, Dict from typing import List, Dict
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from rest_framework.utils.formatting import lazy_format
from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel
from application.chat_pipeline.step.search_dataset_step.i_search_dataset_step import ISearchDatasetStep from application.chat_pipeline.step.search_dataset_step.i_search_dataset_step import ISearchDatasetStep
@ -28,19 +26,18 @@ from smartdoc.conf import PROJECT_DIR
def get_model_by_id(_id, user_id): def get_model_by_id(_id, user_id):
model = QuerySet(Model).filter(id=_id).first() model = QuerySet(Model).filter(id=_id).first()
if model is None: if model is None:
raise Exception(_("Model does not exist")) raise Exception("模型不存在")
if model.permission_type == 'PRIVATE' and str(model.user_id) != str(user_id): if model.permission_type == 'PRIVATE' and str(model.user_id) != str(user_id):
message = lazy_format(_('No permission to use this model {model_name}'), model_name=model.name) raise Exception(f"无权限使用此模型:{model.name}")
raise Exception(message)
return model return model
def get_embedding_id(dataset_id_list): def get_embedding_id(dataset_id_list):
dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list) dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list)
if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1: if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1:
raise Exception(_("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled.")) raise Exception("关联知识库的向量模型不一致,无法召回分段。")
if len(dataset_list) == 0: if len(dataset_list) == 0:
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base")) raise Exception("知识库设置错误,请重新设置知识库")
return dataset_list[0].embedding_mode_id return dataset_list[0].embedding_mode_id

View File

@ -9,22 +9,16 @@
class Answer: class Answer:
def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node, real_node_id, def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node):
reasoning_content):
self.view_type = view_type self.view_type = view_type
self.content = content self.content = content
self.reasoning_content = reasoning_content
self.runtime_node_id = runtime_node_id self.runtime_node_id = runtime_node_id
self.chat_record_id = chat_record_id self.chat_record_id = chat_record_id
self.child_node = child_node self.child_node = child_node
self.real_node_id = real_node_id
def to_dict(self): def to_dict(self):
return {'view_type': self.view_type, 'content': self.content, 'runtime_node_id': self.runtime_node_id, return {'view_type': self.view_type, 'content': self.content, 'runtime_node_id': self.runtime_node_id,
'chat_record_id': self.chat_record_id, 'chat_record_id': self.chat_record_id, 'child_node': self.child_node}
'child_node': self.child_node,
'reasoning_content': self.reasoning_content,
'real_node_id': self.real_node_id}
class NodeChunk: class NodeChunk:

View File

@ -1,451 +0,0 @@
{
"nodes": [
{
"id": "base-node",
"type": "base-node",
"x": 360,
"y": 2810,
"properties": {
"config": {
},
"height": 825.6,
"stepName": "Base",
"node_data": {
"desc": "",
"name": "maxkbapplication",
"prologue": "Hello, I am the MaxKB assistant. You can ask me about MaxKB usage issues.\n-What are the main functions of MaxKB?\n-What major language models does MaxKB support?\n-What document types does MaxKB support?"
},
"input_field_list": [
]
}
},
{
"id": "start-node",
"type": "start-node",
"x": 430,
"y": 3660,
"properties": {
"config": {
"fields": [
{
"label": "用户问题",
"value": "question"
}
],
"globalFields": [
{
"label": "当前时间",
"value": "time"
}
]
},
"fields": [
{
"label": "用户问题",
"value": "question"
}
],
"height": 276,
"stepName": "Start",
"globalFields": [
{
"label": "当前时间",
"value": "time"
}
]
}
},
{
"id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"type": "search-dataset-node",
"x": 840,
"y": 3210,
"properties": {
"config": {
"fields": [
{
"label": "检索结果的分段列表",
"value": "paragraph_list"
},
{
"label": "满足直接回答的分段列表",
"value": "is_hit_handling_method_list"
},
{
"label": "检索结果",
"value": "data"
},
{
"label": "满足直接回答的分段内容",
"value": "directly_return"
}
]
},
"height": 794,
"stepName": "Knowledge Search",
"node_data": {
"dataset_id_list": [
],
"dataset_setting": {
"top_n": 3,
"similarity": 0.6,
"search_mode": "embedding",
"max_paragraph_char_number": 5000
},
"question_reference_address": [
"start-node",
"question"
],
"source_dataset_id_list": [
]
}
}
},
{
"id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"type": "condition-node",
"x": 1490,
"y": 3210,
"properties": {
"width": 600,
"config": {
"fields": [
{
"label": "分支名称",
"value": "branch_name"
}
]
},
"height": 543.675,
"stepName": "Conditional Branch",
"node_data": {
"branch": [
{
"id": "1009",
"type": "IF",
"condition": "and",
"conditions": [
{
"field": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"is_hit_handling_method_list"
],
"value": "1",
"compare": "len_ge"
}
]
},
{
"id": "4908",
"type": "ELSE IF 1",
"condition": "and",
"conditions": [
{
"field": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"paragraph_list"
],
"value": "1",
"compare": "len_ge"
}
]
},
{
"id": "161",
"type": "ELSE",
"condition": "and",
"conditions": [
]
}
]
},
"branch_condition_list": [
{
"index": 0,
"height": 121.225,
"id": "1009"
},
{
"index": 1,
"height": 121.225,
"id": "4908"
},
{
"index": 2,
"height": 44,
"id": "161"
}
]
}
},
{
"id": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"type": "reply-node",
"x": 2170,
"y": 2480,
"properties": {
"config": {
"fields": [
{
"label": "内容",
"value": "answer"
}
]
},
"height": 378,
"stepName": "Specified Reply",
"node_data": {
"fields": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"directly_return"
],
"content": "",
"reply_type": "referencing",
"is_result": true
}
}
},
{
"id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"type": "ai-chat-node",
"x": 2160,
"y": 3200,
"properties": {
"config": {
"fields": [
{
"label": "AI 回答内容",
"value": "answer"
}
]
},
"height": 763,
"stepName": "AI Chat",
"node_data": {
"prompt": "Known information:\n{{Knowledge Search.data}}\nQuestion:\n{{Start.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0,
"is_result": true
}
}
},
{
"id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"type": "ai-chat-node",
"x": 2160,
"y": 3970,
"properties": {
"config": {
"fields": [
{
"label": "AI 回答内容",
"value": "answer"
}
]
},
"height": 763,
"stepName": "AI Chat1",
"node_data": {
"prompt": "{{Start.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0,
"is_result": true
}
}
}
],
"edges": [
{
"id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73",
"type": "app-edge",
"sourceNodeId": "start-node",
"targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"startPoint": {
"x": 590,
"y": 3660
},
"endPoint": {
"x": 680,
"y": 3210
},
"properties": {
},
"pointsList": [
{
"x": 590,
"y": 3660
},
{
"x": 700,
"y": 3660
},
{
"x": 570,
"y": 3210
},
{
"x": 680,
"y": 3210
}
],
"sourceAnchorId": "start-node_right",
"targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left"
},
{
"id": "35cb86dd-f328-429e-a973-12fd7218b696",
"type": "app-edge",
"sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"startPoint": {
"x": 1000,
"y": 3210
},
"endPoint": {
"x": 1200,
"y": 3210
},
"properties": {
},
"pointsList": [
{
"x": 1000,
"y": 3210
},
{
"x": 1110,
"y": 3210
},
{
"x": 1090,
"y": 3210
},
{
"x": 1200,
"y": 3210
}
],
"sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right",
"targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left"
},
{
"id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"startPoint": {
"x": 1780,
"y": 3073.775
},
"endPoint": {
"x": 2010,
"y": 2480
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3073.775
},
{
"x": 1890,
"y": 3073.775
},
{
"x": 1900,
"y": 2480
},
{
"x": 2010,
"y": 2480
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right",
"targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left"
},
{
"id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"startPoint": {
"x": 1780,
"y": 3203
},
"endPoint": {
"x": 2000,
"y": 3200
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3203
},
{
"x": 1890,
"y": 3203
},
{
"x": 1890,
"y": 3200
},
{
"x": 2000,
"y": 3200
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right",
"targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left"
},
{
"id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"startPoint": {
"x": 1780,
"y": 3293.6124999999997
},
"endPoint": {
"x": 2000,
"y": 3970
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3293.6124999999997
},
{
"x": 1890,
"y": 3293.6124999999997
},
{
"x": 1890,
"y": 3970
},
{
"x": 2000,
"y": 3970
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right",
"targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left"
}
]
}

View File

@ -1,451 +0,0 @@
{
"nodes": [
{
"id": "base-node",
"type": "base-node",
"x": 360,
"y": 2810,
"properties": {
"config": {
},
"height": 825.6,
"stepName": "基本信息",
"node_data": {
"desc": "",
"name": "maxkbapplication",
"prologue": "您好,我是 MaxKB 小助手,您可以向我提出 MaxKB 使用问题。\n- MaxKB 主要功能有什么?\n- MaxKB 支持哪些大语言模型?\n- MaxKB 支持哪些文档类型?"
},
"input_field_list": [
]
}
},
{
"id": "start-node",
"type": "start-node",
"x": 430,
"y": 3660,
"properties": {
"config": {
"fields": [
{
"label": "用户问题",
"value": "question"
}
],
"globalFields": [
{
"label": "当前时间",
"value": "time"
}
]
},
"fields": [
{
"label": "用户问题",
"value": "question"
}
],
"height": 276,
"stepName": "开始",
"globalFields": [
{
"label": "当前时间",
"value": "time"
}
]
}
},
{
"id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"type": "search-dataset-node",
"x": 840,
"y": 3210,
"properties": {
"config": {
"fields": [
{
"label": "检索结果的分段列表",
"value": "paragraph_list"
},
{
"label": "满足直接回答的分段列表",
"value": "is_hit_handling_method_list"
},
{
"label": "检索结果",
"value": "data"
},
{
"label": "满足直接回答的分段内容",
"value": "directly_return"
}
]
},
"height": 794,
"stepName": "知识库检索",
"node_data": {
"dataset_id_list": [
],
"dataset_setting": {
"top_n": 3,
"similarity": 0.6,
"search_mode": "embedding",
"max_paragraph_char_number": 5000
},
"question_reference_address": [
"start-node",
"question"
],
"source_dataset_id_list": [
]
}
}
},
{
"id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"type": "condition-node",
"x": 1490,
"y": 3210,
"properties": {
"width": 600,
"config": {
"fields": [
{
"label": "分支名称",
"value": "branch_name"
}
]
},
"height": 543.675,
"stepName": "判断器",
"node_data": {
"branch": [
{
"id": "1009",
"type": "IF",
"condition": "and",
"conditions": [
{
"field": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"is_hit_handling_method_list"
],
"value": "1",
"compare": "len_ge"
}
]
},
{
"id": "4908",
"type": "ELSE IF 1",
"condition": "and",
"conditions": [
{
"field": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"paragraph_list"
],
"value": "1",
"compare": "len_ge"
}
]
},
{
"id": "161",
"type": "ELSE",
"condition": "and",
"conditions": [
]
}
]
},
"branch_condition_list": [
{
"index": 0,
"height": 121.225,
"id": "1009"
},
{
"index": 1,
"height": 121.225,
"id": "4908"
},
{
"index": 2,
"height": 44,
"id": "161"
}
]
}
},
{
"id": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"type": "reply-node",
"x": 2170,
"y": 2480,
"properties": {
"config": {
"fields": [
{
"label": "内容",
"value": "answer"
}
]
},
"height": 378,
"stepName": "指定回复",
"node_data": {
"fields": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"directly_return"
],
"content": "",
"reply_type": "referencing",
"is_result": true
}
}
},
{
"id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"type": "ai-chat-node",
"x": 2160,
"y": 3200,
"properties": {
"config": {
"fields": [
{
"label": "AI 回答内容",
"value": "answer"
}
]
},
"height": 763,
"stepName": "AI 对话",
"node_data": {
"prompt": "已知信息:\n{{知识库检索.data}}\n问题\n{{开始.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0,
"is_result": true
}
}
},
{
"id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"type": "ai-chat-node",
"x": 2160,
"y": 3970,
"properties": {
"config": {
"fields": [
{
"label": "AI 回答内容",
"value": "answer"
}
]
},
"height": 763,
"stepName": "AI 对话1",
"node_data": {
"prompt": "{{开始.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0,
"is_result": true
}
}
}
],
"edges": [
{
"id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73",
"type": "app-edge",
"sourceNodeId": "start-node",
"targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"startPoint": {
"x": 590,
"y": 3660
},
"endPoint": {
"x": 680,
"y": 3210
},
"properties": {
},
"pointsList": [
{
"x": 590,
"y": 3660
},
{
"x": 700,
"y": 3660
},
{
"x": 570,
"y": 3210
},
{
"x": 680,
"y": 3210
}
],
"sourceAnchorId": "start-node_right",
"targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left"
},
{
"id": "35cb86dd-f328-429e-a973-12fd7218b696",
"type": "app-edge",
"sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"startPoint": {
"x": 1000,
"y": 3210
},
"endPoint": {
"x": 1200,
"y": 3210
},
"properties": {
},
"pointsList": [
{
"x": 1000,
"y": 3210
},
{
"x": 1110,
"y": 3210
},
{
"x": 1090,
"y": 3210
},
{
"x": 1200,
"y": 3210
}
],
"sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right",
"targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left"
},
{
"id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"startPoint": {
"x": 1780,
"y": 3073.775
},
"endPoint": {
"x": 2010,
"y": 2480
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3073.775
},
{
"x": 1890,
"y": 3073.775
},
{
"x": 1900,
"y": 2480
},
{
"x": 2010,
"y": 2480
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right",
"targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left"
},
{
"id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"startPoint": {
"x": 1780,
"y": 3203
},
"endPoint": {
"x": 2000,
"y": 3200
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3203
},
{
"x": 1890,
"y": 3203
},
{
"x": 1890,
"y": 3200
},
{
"x": 2000,
"y": 3200
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right",
"targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left"
},
{
"id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"startPoint": {
"x": 1780,
"y": 3293.6124999999997
},
"endPoint": {
"x": 2000,
"y": 3970
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3293.6124999999997
},
{
"x": 1890,
"y": 3293.6124999999997
},
{
"x": 1890,
"y": 3970
},
{
"x": 2000,
"y": 3970
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right",
"targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left"
}
]
}

View File

@ -1,451 +0,0 @@
{
"nodes": [
{
"id": "base-node",
"type": "base-node",
"x": 360,
"y": 2810,
"properties": {
"config": {
},
"height": 825.6,
"stepName": "基本資訊",
"node_data": {
"desc": "",
"name": "maxkbapplication",
"prologue": "您好我是MaxKB小助手您可以向我提出MaxKB使用問題。\n- MaxKB主要功能有什麼\n- MaxKB支持哪些大語言模型\n- MaxKB支持哪些文檔類型"
},
"input_field_list": [
]
}
},
{
"id": "start-node",
"type": "start-node",
"x": 430,
"y": 3660,
"properties": {
"config": {
"fields": [
{
"label": "用户问题",
"value": "question"
}
],
"globalFields": [
{
"label": "当前时间",
"value": "time"
}
]
},
"fields": [
{
"label": "用户问题",
"value": "question"
}
],
"height": 276,
"stepName": "開始",
"globalFields": [
{
"label": "当前时间",
"value": "time"
}
]
}
},
{
"id": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"type": "search-dataset-node",
"x": 840,
"y": 3210,
"properties": {
"config": {
"fields": [
{
"label": "检索结果的分段列表",
"value": "paragraph_list"
},
{
"label": "满足直接回答的分段列表",
"value": "is_hit_handling_method_list"
},
{
"label": "检索结果",
"value": "data"
},
{
"label": "满足直接回答的分段内容",
"value": "directly_return"
}
]
},
"height": 794,
"stepName": "知識庫檢索",
"node_data": {
"dataset_id_list": [
],
"dataset_setting": {
"top_n": 3,
"similarity": 0.6,
"search_mode": "embedding",
"max_paragraph_char_number": 5000
},
"question_reference_address": [
"start-node",
"question"
],
"source_dataset_id_list": [
]
}
}
},
{
"id": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"type": "condition-node",
"x": 1490,
"y": 3210,
"properties": {
"width": 600,
"config": {
"fields": [
{
"label": "分支名称",
"value": "branch_name"
}
]
},
"height": 543.675,
"stepName": "判斷器",
"node_data": {
"branch": [
{
"id": "1009",
"type": "IF",
"condition": "and",
"conditions": [
{
"field": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"is_hit_handling_method_list"
],
"value": "1",
"compare": "len_ge"
}
]
},
{
"id": "4908",
"type": "ELSE IF 1",
"condition": "and",
"conditions": [
{
"field": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"paragraph_list"
],
"value": "1",
"compare": "len_ge"
}
]
},
{
"id": "161",
"type": "ELSE",
"condition": "and",
"conditions": [
]
}
]
},
"branch_condition_list": [
{
"index": 0,
"height": 121.225,
"id": "1009"
},
{
"index": 1,
"height": 121.225,
"id": "4908"
},
{
"index": 2,
"height": 44,
"id": "161"
}
]
}
},
{
"id": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"type": "reply-node",
"x": 2170,
"y": 2480,
"properties": {
"config": {
"fields": [
{
"label": "内容",
"value": "answer"
}
]
},
"height": 378,
"stepName": "指定回覆",
"node_data": {
"fields": [
"b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"directly_return"
],
"content": "",
"reply_type": "referencing",
"is_result": true
}
}
},
{
"id": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"type": "ai-chat-node",
"x": 2160,
"y": 3200,
"properties": {
"config": {
"fields": [
{
"label": "AI 回答内容",
"value": "answer"
}
]
},
"height": 763,
"stepName": "AI 對話",
"node_data": {
"prompt": "已知資訊:\n{{知識庫檢索.data}}\n問題\n{{開始.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0,
"is_result": true
}
}
},
{
"id": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"type": "ai-chat-node",
"x": 2160,
"y": 3970,
"properties": {
"config": {
"fields": [
{
"label": "AI 回答内容",
"value": "answer"
}
]
},
"height": 763,
"stepName": "AI 對話1",
"node_data": {
"prompt": "{{開始.question}}",
"system": "",
"model_id": "",
"dialogue_number": 0,
"is_result": true
}
}
}
],
"edges": [
{
"id": "7d0f166f-c472-41b2-b9a2-c294f4c83d73",
"type": "app-edge",
"sourceNodeId": "start-node",
"targetNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"startPoint": {
"x": 590,
"y": 3660
},
"endPoint": {
"x": 680,
"y": 3210
},
"properties": {
},
"pointsList": [
{
"x": 590,
"y": 3660
},
{
"x": 700,
"y": 3660
},
{
"x": 570,
"y": 3210
},
{
"x": 680,
"y": 3210
}
],
"sourceAnchorId": "start-node_right",
"targetAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left"
},
{
"id": "35cb86dd-f328-429e-a973-12fd7218b696",
"type": "app-edge",
"sourceNodeId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5",
"targetNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"startPoint": {
"x": 1000,
"y": 3210
},
"endPoint": {
"x": 1200,
"y": 3210
},
"properties": {
},
"pointsList": [
{
"x": 1000,
"y": 3210
},
{
"x": 1110,
"y": 3210
},
{
"x": 1090,
"y": 3210
},
{
"x": 1200,
"y": 3210
}
],
"sourceAnchorId": "b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right",
"targetAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_left"
},
{
"id": "e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "4ffe1086-25df-4c85-b168-979b5bbf0a26",
"startPoint": {
"x": 1780,
"y": 3073.775
},
"endPoint": {
"x": 2010,
"y": 2480
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3073.775
},
{
"x": 1890,
"y": 3073.775
},
{
"x": 1900,
"y": 2480
},
{
"x": 2010,
"y": 2480
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right",
"targetAnchorId": "4ffe1086-25df-4c85-b168-979b5bbf0a26_left"
},
{
"id": "994ff325-6f7a-4ebc-b61b-10e15519d6d2",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb",
"startPoint": {
"x": 1780,
"y": 3203
},
"endPoint": {
"x": 2000,
"y": 3200
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3203
},
{
"x": 1890,
"y": 3203
},
{
"x": 1890,
"y": 3200
},
{
"x": 2000,
"y": 3200
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right",
"targetAnchorId": "f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left"
},
{
"id": "19270caf-bb9f-4ba7-9bf8-200aa70fecd5",
"type": "app-edge",
"sourceNodeId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b",
"targetNodeId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7",
"startPoint": {
"x": 1780,
"y": 3293.6124999999997
},
"endPoint": {
"x": 2000,
"y": 3970
},
"properties": {
},
"pointsList": [
{
"x": 1780,
"y": 3293.6124999999997
},
{
"x": 1890,
"y": 3293.6124999999997
},
{
"x": 1890,
"y": 3970
},
{
"x": 2000,
"y": 3970
}
],
"sourceAnchorId": "fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right",
"targetAnchorId": "309d0eef-c597-46b5-8d51-b9a28aaef4c7_left"
}
]
}

View File

@ -62,9 +62,7 @@ class WorkFlowPostHandler:
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
'answer_tokens' in row and row.get('answer_tokens') is not None]) 'answer_tokens' in row and row.get('answer_tokens') is not None])
answer_text_list = workflow.get_answer_text_list() answer_text_list = workflow.get_answer_text_list()
answer_text = '\n\n'.join( answer_text = '\n\n'.join(answer['content'] for answer in answer_text_list)
'\n\n'.join([a.get('content') for a in answer]) for answer in
answer_text_list)
if workflow.chat_record is not None: if workflow.chat_record is not None:
chat_record = workflow.chat_record chat_record = workflow.chat_record
chat_record.answer_text = answer_text chat_record.answer_text = answer_text
@ -84,8 +82,7 @@ class WorkFlowPostHandler:
answer_text_list=answer_text_list, answer_text_list=answer_text_list,
run_time=time.time() - workflow.context['start_time'], run_time=time.time() - workflow.context['start_time'],
index=0) index=0)
asker = workflow.context.get('asker', None) self.chat_info.append_chat_record(chat_record, self.client_id)
self.chat_info.append_chat_record(chat_record, self.client_id, asker)
# 重新设置缓存 # 重新设置缓存
chat_cache.set(chat_id, chat_cache.set(chat_id,
self.chat_info, timeout=60 * 30) self.chat_info, timeout=60 * 30)
@ -160,10 +157,8 @@ class INode:
def get_answer_list(self) -> List[Answer] | None: def get_answer_list(self) -> List[Answer] | None:
if self.answer_text is None: if self.answer_text is None:
return None return None
reasoning_content_enable = self.context.get('model_setting', {}).get('reasoning_content_enable', False)
return [ return [
Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], {}, Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], {})]
self.runtime_node_id, self.context.get('reasoning_content', '') if reasoning_content_enable else '')]
def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None, def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None,
get_node_params=lambda node: node.properties.get('node_data')): get_node_params=lambda node: node.properties.get('node_data')):

View File

@ -24,15 +24,11 @@ from .search_dataset_node import *
from .speech_to_text_step_node import BaseSpeechToTextNode from .speech_to_text_step_node import BaseSpeechToTextNode
from .start_node import * from .start_node import *
from .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode from .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode
from .variable_assign_node import BaseVariableAssignNode
from .mcp_node import BaseMcpNode
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode, BaseConditionNode, BaseReplyNode,
BaseConditionNode, BaseReplyNode,
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode, BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode,
BaseDocumentExtractNode, BaseDocumentExtractNode,
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode, BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,BaseImageGenerateNode]
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode]
def get_node(node_type): def get_node(node_type):

View File

@ -8,7 +8,6 @@
""" """
from typing import Type from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
@ -16,26 +15,19 @@ from common.util.field_message import ErrMessage
class ChatNodeSerializer(serializers.Serializer): class ChatNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, error_messages=ErrMessage.char("模型id"))
system = serializers.CharField(required=False, allow_blank=True, allow_null=True, system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Role Setting"))) error_messages=ErrMessage.char("角色设定"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词"))
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer( dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
_("Number of multi-round conversations")))
is_result = serializers.BooleanField(required=False, is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
error_messages=ErrMessage.boolean(_('Whether to return content')))
model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.integer("模型参数相关设置"))
model_params_setting = serializers.DictField(required=False,
error_messages=ErrMessage.dict(_("Model parameter settings")))
model_setting = serializers.DictField(required=False,
error_messages=ErrMessage.dict('Model settings'))
dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True, dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Context Type"))) error_messages=ErrMessage.char("上下文类型"))
mcp_enable = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean(_("Whether to enable MCP")))
mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server")))
class IChatNode(INode): class IChatNode(INode):
@ -51,8 +43,5 @@ class IChatNode(INode):
chat_record_id, chat_record_id,
model_params_setting=None, model_params_setting=None,
dialogue_type=None, dialogue_type=None,
model_setting=None,
mcp_enable=False,
mcp_servers=None,
**kwargs) -> NodeResult: **kwargs) -> NodeResult:
pass pass

View File

@ -6,43 +6,22 @@
@date2024/6/4 14:30 @date2024/6/4 14:30
@desc: @desc:
""" """
import asyncio
import json
import re
import time import time
from functools import reduce from functools import reduce
from types import AsyncGeneratorType
from typing import List, Dict from typing import List, Dict
from django.db.models import QuerySet from django.db.models import QuerySet
from langchain.schema import HumanMessage, SystemMessage from langchain.schema import HumanMessage, SystemMessage
from langchain_core.messages import BaseMessage, AIMessage, AIMessageChunk, ToolMessage from langchain_core.messages import BaseMessage, AIMessage
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from application.flow.i_step_node import NodeResult, INode from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
from application.flow.tools import Reasoning
from setting.models import Model from setting.models import Model
from setting.models_provider import get_model_credential from setting.models_provider import get_model_credential
from setting.models_provider.tools import get_model_instance_by_model_user_id from setting.models_provider.tools import get_model_instance_by_model_user_id
tool_message_template = """
<details>
<summary>
<strong>Called MCP Tool: <em>%s</em></strong>
</summary>
```json def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
%s
```
</details>
"""
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,
reasoning_content: str):
chat_model = node_variable.get('chat_model') chat_model = node_variable.get('chat_model')
message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list')) message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))
answer_tokens = chat_model.get_num_tokens(answer) answer_tokens = chat_model.get_num_tokens(answer)
@ -52,7 +31,6 @@ def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wo
node.context['history_message'] = node_variable['history_message'] node.context['history_message'] = node_variable['history_message']
node.context['question'] = node_variable['question'] node.context['question'] = node_variable['question']
node.context['run_time'] = time.time() - node.context['start_time'] node.context['run_time'] = time.time() - node.context['start_time']
node.context['reasoning_content'] = reasoning_content
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
node.answer_text = answer node.answer_text = answer
@ -67,73 +45,10 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
""" """
response = node_variable.get('result') response = node_variable.get('result')
answer = '' answer = ''
reasoning_content = ''
model_setting = node.context.get('model_setting',
{'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
'reasoning_content_start': '<think>'})
reasoning = Reasoning(model_setting.get('reasoning_content_start', '<think>'),
model_setting.get('reasoning_content_end', '</think>'))
response_reasoning_content = False
for chunk in response: for chunk in response:
reasoning_chunk = reasoning.get_reasoning_content(chunk) answer += chunk.content
content_chunk = reasoning_chunk.get('content') yield chunk.content
if 'reasoning_content' in chunk.additional_kwargs: _write_context(node_variable, workflow_variable, node, workflow, answer)
response_reasoning_content = True
reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '')
else:
reasoning_content_chunk = reasoning_chunk.get('reasoning_content')
answer += content_chunk
if reasoning_content_chunk is None:
reasoning_content_chunk = ''
reasoning_content += reasoning_content_chunk
yield {'content': content_chunk,
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
False) else ''}
reasoning_chunk = reasoning.get_end_reasoning_content()
answer += reasoning_chunk.get('content')
reasoning_content_chunk = ""
if not response_reasoning_content:
reasoning_content_chunk = reasoning_chunk.get(
'reasoning_content')
yield {'content': reasoning_chunk.get('content'),
'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',
False) else ''}
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)
async def _yield_mcp_response(chat_model, message_list, mcp_servers):
async with MultiServerMCPClient(json.loads(mcp_servers)) as client:
agent = create_react_agent(chat_model, client.get_tools())
response = agent.astream({"messages": message_list}, stream_mode='messages')
async for chunk in response:
if isinstance(chunk[0], ToolMessage):
content = tool_message_template % (chunk[0].name, chunk[0].content)
chunk[0].content = content
yield chunk[0]
if isinstance(chunk[0], AIMessageChunk):
yield chunk[0]
def mcp_response_generator(chat_model, message_list, mcp_servers):
loop = asyncio.new_event_loop()
try:
async_gen = _yield_mcp_response(chat_model, message_list, mcp_servers)
while True:
try:
chunk = loop.run_until_complete(anext_async(async_gen))
yield chunk
except StopAsyncIteration:
break
except Exception as e:
print(f'exception: {e}')
finally:
loop.close()
async def anext_async(agen):
return await agen.__anext__()
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
@ -145,18 +60,8 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
@param workflow: 工作流管理器 @param workflow: 工作流管理器
""" """
response = node_variable.get('result') response = node_variable.get('result')
model_setting = node.context.get('model_setting', answer = response.content
{'reasoning_content_enable': False, 'reasoning_content_end': '</think>', _write_context(node_variable, workflow_variable, node, workflow, answer)
'reasoning_content_start': '<think>'})
reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end'))
reasoning_result = reasoning.get_reasoning_content(response)
reasoning_result_end = reasoning.get_end_reasoning_content()
content = reasoning_result.get('content') + reasoning_result_end.get('content')
if 'reasoning_content' in response.response_metadata:
reasoning_content = response.response_metadata.get('reasoning_content', '')
else:
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content')
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
def get_default_model_params_setting(model_id): def get_default_model_params_setting(model_id):
@ -187,26 +92,17 @@ class BaseChatNode(IChatNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
self.context['question'] = details.get('question') self.context['question'] = details.get('question')
self.context['reasoning_content'] = details.get('reasoning_content') self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,
model_params_setting=None, model_params_setting=None,
dialogue_type=None, dialogue_type=None,
model_setting=None,
mcp_enable=False,
mcp_servers=None,
**kwargs) -> NodeResult: **kwargs) -> NodeResult:
if dialogue_type is None: if dialogue_type is None:
dialogue_type = 'WORKFLOW' dialogue_type = 'WORKFLOW'
if model_params_setting is None: if model_params_setting is None:
model_params_setting = get_default_model_params_setting(model_id) model_params_setting = get_default_model_params_setting(model_id)
if model_setting is None:
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
'reasoning_content_start': '<think>'}
self.context['model_setting'] = model_setting
chat_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), chat_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'),
**model_params_setting) **model_params_setting)
history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type, history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type,
@ -218,14 +114,6 @@ class BaseChatNode(IChatNode):
self.context['system'] = system self.context['system'] = system
message_list = self.generate_message_list(system, prompt, history_message) message_list = self.generate_message_list(system, prompt, history_message)
self.context['message_list'] = message_list self.context['message_list'] = message_list
if mcp_enable and mcp_servers is not None and '"stdio"' not in mcp_servers:
r = mcp_response_generator(chat_model, message_list, mcp_servers)
return NodeResult(
{'result': r, 'chat_model': chat_model, 'message_list': message_list,
'history_message': history_message, 'question': question.content}, {},
_write_context=write_context_stream)
if stream: if stream:
r = chat_model.stream(message_list) r = chat_model.stream(message_list)
return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list, return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,
@ -244,9 +132,6 @@ class BaseChatNode(IChatNode):
get_message(history_chat_record[index], dialogue_type, runtime_node_id) get_message(history_chat_record[index], dialogue_type, runtime_node_id)
for index in for index in
range(start_index if start_index > 0 else 0, len(history_chat_record))], []) range(start_index if start_index > 0 else 0, len(history_chat_record))], [])
for message in history_message:
if isinstance(message.content, str):
message.content = re.sub('<form_rander>[\d\D]*?<\/form_rander>', '', message.content)
return history_message return history_message
def generate_prompt_question(self, prompt): def generate_prompt_question(self, prompt):
@ -279,7 +164,6 @@ class BaseChatNode(IChatNode):
'history_message') is not None else [])], 'history_message') is not None else [])],
'question': self.context.get('question'), 'question': self.context.get('question'),
'answer': self.context.get('answer'), 'answer': self.context.get('answer'),
'reasoning_content': self.context.get('reasoning_content'),
'type': self.node.type, 'type': self.node.type,
'message_tokens': self.context.get('message_tokens'), 'message_tokens': self.context.get('message_tokens'),
'answer_tokens': self.context.get('answer_tokens'), 'answer_tokens': self.context.get('answer_tokens'),

View File

@ -6,22 +6,17 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class ApplicationNodeSerializer(serializers.Serializer): class ApplicationNodeSerializer(serializers.Serializer):
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application ID"))) application_id = serializers.CharField(required=True, error_messages=ErrMessage.char("应用id"))
question_reference_address = serializers.ListField(required=True, question_reference_address = serializers.ListField(required=True, error_messages=ErrMessage.list("用户问题"))
error_messages=ErrMessage.list(_("User Questions"))) api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list("api输入字段"))
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("API Input Fields"))) user_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.uuid("用户输入字段"))
user_input_field_list = serializers.ListField(required=False, image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
error_messages=ErrMessage.uuid(_("User Input Fields"))) document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list("音频"))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) child_node = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("子节点"))
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio"))) node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("表单数据"))
child_node = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Child Nodes")))
node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data")))
class IApplicationNode(INode): class IApplicationNode(INode):
@ -36,16 +31,11 @@ class IApplicationNode(INode):
self.node_params_serializer.data.get('question_reference_address')[1:]) self.node_params_serializer.data.get('question_reference_address')[1:])
kwargs = {} kwargs = {}
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []): for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else '' kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(api_input_field['value'][0],
kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(value, api_input_field['value'][1:])
api_input_field['value'][
1:]) if value != '' else ''
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []): for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else '' kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(user_input_field['value'][0],
kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(value, user_input_field['value'][1:])
user_input_field['value'][
1:]) if value != '' else ''
# 判断是否包含这个属性 # 判断是否包含这个属性
app_document_list = self.node_params_serializer.data.get('document_list', []) app_document_list = self.node_params_serializer.data.get('document_list', [])
if app_document_list and len(app_document_list) > 0: if app_document_list and len(app_document_list) > 0:
@ -54,8 +44,7 @@ class IApplicationNode(INode):
app_document_list[1:]) app_document_list[1:])
for document in app_document_list: for document in app_document_list:
if 'file_id' not in document: if 'file_id' not in document:
raise ValueError( raise ValueError("参数值错误: 上传的文档中缺少file_id文档上传失败")
_("Parameter value error: The uploaded document lacks file_id, and the document upload fails"))
app_image_list = self.node_params_serializer.data.get('image_list', []) app_image_list = self.node_params_serializer.data.get('image_list', [])
if app_image_list and len(app_image_list) > 0: if app_image_list and len(app_image_list) > 0:
app_image_list = self.workflow_manage.get_reference_field( app_image_list = self.workflow_manage.get_reference_field(
@ -63,8 +52,7 @@ class IApplicationNode(INode):
app_image_list[1:]) app_image_list[1:])
for image in app_image_list: for image in app_image_list:
if 'file_id' not in image: if 'file_id' not in image:
raise ValueError( raise ValueError("参数值错误: 上传的图片中缺少file_id图片上传失败")
_("Parameter value error: The uploaded image lacks file_id, and the image upload fails"))
app_audio_list = self.node_params_serializer.data.get('audio_list', []) app_audio_list = self.node_params_serializer.data.get('audio_list', [])
if app_audio_list and len(app_audio_list) > 0: if app_audio_list and len(app_audio_list) > 0:
@ -73,8 +61,7 @@ class IApplicationNode(INode):
app_audio_list[1:]) app_audio_list[1:])
for audio in app_audio_list: for audio in app_audio_list:
if 'file_id' not in audio: if 'file_id' not in audio:
raise ValueError( raise ValueError("参数值错误: 上传的图片中缺少file_id音频上传失败")
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,
app_document_list=app_document_list, app_image_list=app_image_list, app_document_list=app_document_list, app_image_list=app_image_list,
app_audio_list=app_audio_list, app_audio_list=app_audio_list,

View File

@ -19,8 +19,7 @@ def _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):
return node_variable.get('is_interrupt_exec', False) return node_variable.get('is_interrupt_exec', False)
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str, def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
reasoning_content: str):
result = node_variable.get('result') result = node_variable.get('result')
node.context['application_node_dict'] = node_variable.get('application_node_dict') node.context['application_node_dict'] = node_variable.get('application_node_dict')
node.context['node_dict'] = node_variable.get('node_dict', {}) node.context['node_dict'] = node_variable.get('node_dict', {})
@ -29,7 +28,6 @@ def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wo
node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0) node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)
node.context['answer'] = answer node.context['answer'] = answer
node.context['result'] = answer node.context['result'] = answer
node.context['reasoning_content'] = reasoning_content
node.context['question'] = node_variable['question'] node.context['question'] = node_variable['question']
node.context['run_time'] = time.time() - node.context['start_time'] node.context['run_time'] = time.time() - node.context['start_time']
if workflow.is_result(node, NodeResult(node_variable, workflow_variable)): if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):
@ -46,7 +44,6 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
""" """
response = node_variable.get('result') response = node_variable.get('result')
answer = '' answer = ''
reasoning_content = ''
usage = {} usage = {}
node_child_node = {} node_child_node = {}
application_node_dict = node.context.get('application_node_dict', {}) application_node_dict = node.context.get('application_node_dict', {})
@ -63,11 +60,9 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
node_type = response_content.get('node_type') node_type = response_content.get('node_type')
real_node_id = response_content.get('real_node_id') real_node_id = response_content.get('real_node_id')
node_is_end = response_content.get('node_is_end', False) node_is_end = response_content.get('node_is_end', False)
_reasoning_content = response_content.get('reasoning_content', '')
if node_type == 'form-node': if node_type == 'form-node':
is_interrupt_exec = True is_interrupt_exec = True
answer += content answer += content
reasoning_content += _reasoning_content
node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
'child_node': child_node} 'child_node': child_node}
@ -80,16 +75,13 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
'chat_record_id': chat_record_id, 'chat_record_id': chat_record_id,
'child_node': child_node, 'child_node': child_node,
'index': len(application_node_dict), 'index': len(application_node_dict),
'view_type': view_type, 'view_type': view_type}
'reasoning_content': _reasoning_content}
else: else:
application_node['content'] += content application_node['content'] += content
application_node['reasoning_content'] += _reasoning_content
yield {'content': content, yield {'content': content,
'node_type': node_type, 'node_type': node_type,
'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id, 'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,
'reasoning_content': _reasoning_content,
'child_node': child_node, 'child_node': child_node,
'real_node_id': real_node_id, 'real_node_id': real_node_id,
'node_is_end': node_is_end, 'node_is_end': node_is_end,
@ -99,7 +91,7 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
node_variable['is_interrupt_exec'] = is_interrupt_exec node_variable['is_interrupt_exec'] = is_interrupt_exec
node_variable['child_node'] = node_child_node node_variable['child_node'] = node_child_node
node_variable['application_node_dict'] = application_node_dict node_variable['application_node_dict'] = application_node_dict
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content) _write_context(node_variable, workflow_variable, node, workflow, answer)
def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow): def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):
@ -114,12 +106,7 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
node_variable['result'] = {'usage': {'completion_tokens': response.get('completion_tokens'), node_variable['result'] = {'usage': {'completion_tokens': response.get('completion_tokens'),
'prompt_tokens': response.get('prompt_tokens')}} 'prompt_tokens': response.get('prompt_tokens')}}
answer = response.get('content', '') or "抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。" answer = response.get('content', '') or "抱歉,没有查找到相关内容,请重新描述您的问题或提供更多信息。"
reasoning_content = response.get('reasoning_content', '') _write_context(node_variable, workflow_variable, node, workflow, answer)
answer_list = response.get('answer_list', [])
node_variable['application_node_dict'] = {answer.get('real_node_id'): {**answer, 'index': index} for answer, index
in
zip(answer_list, range(len(answer_list)))}
_write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)
def reset_application_node_dict(application_node_dict, runtime_node_id, node_data): def reset_application_node_dict(application_node_dict, runtime_node_id, node_data):
@ -152,24 +139,19 @@ class BaseApplicationNode(IApplicationNode):
if application_node_dict is None or len(application_node_dict) == 0: if application_node_dict is None or len(application_node_dict) == 0:
return [ return [
Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'],
self.context.get('child_node'), self.runtime_node_id, '')] self.context.get('child_node'))]
else: else:
return [Answer(n.get('content'), n.get('view_type'), self.runtime_node_id, return [Answer(n.get('content'), n.get('view_type'), self.runtime_node_id,
self.workflow_params['chat_record_id'], {'runtime_node_id': n.get('runtime_node_id'), self.workflow_params['chat_record_id'], {'runtime_node_id': n.get('runtime_node_id'),
'chat_record_id': n.get('chat_record_id') 'chat_record_id': n.get('chat_record_id')
, 'child_node': n.get('child_node')}, n.get('real_node_id'), , 'child_node': n.get('child_node')}) for n in
n.get('reasoning_content', ''))
for n in
sorted(application_node_dict.values(), key=lambda item: item.get('index'))] sorted(application_node_dict.values(), key=lambda item: item.get('index'))]
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
self.context['result'] = details.get('answer')
self.context['question'] = details.get('question') self.context['question'] = details.get('question')
self.context['type'] = details.get('type') self.context['type'] = details.get('type')
self.context['reasoning_content'] = details.get('reasoning_content') self.answer_text = details.get('answer')
if self.node_params.get('is_result', False):
self.answer_text = details.get('answer')
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type, def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
app_document_list=None, app_image_list=None, app_audio_list=None, child_node=None, node_data=None, app_document_list=None, app_image_list=None, app_audio_list=None, child_node=None, node_data=None,
@ -179,8 +161,7 @@ class BaseApplicationNode(IApplicationNode):
current_chat_id = string_to_uuid(chat_id + application_id) current_chat_id = string_to_uuid(chat_id + application_id)
Chat.objects.get_or_create(id=current_chat_id, defaults={ Chat.objects.get_or_create(id=current_chat_id, defaults={
'application_id': application_id, 'application_id': application_id,
'abstract': message[0:1024], 'abstract': message
'client_id': client_id,
}) })
if app_document_list is None: if app_document_list is None:
app_document_list = [] app_document_list = []
@ -226,25 +207,20 @@ class BaseApplicationNode(IApplicationNode):
def get_details(self, index: int, **kwargs): def get_details(self, index: int, **kwargs):
global_fields = [] global_fields = []
for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []): for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):
value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else ''
global_fields.append({ global_fields.append({
'label': api_input_field['variable'], 'label': api_input_field['variable'],
'key': api_input_field['variable'], 'key': api_input_field['variable'],
'value': self.workflow_manage.get_reference_field( 'value': self.workflow_manage.get_reference_field(
value, api_input_field['value'][0],
api_input_field['value'][1:] api_input_field['value'][1:])
) if value != '' else ''
}) })
for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []): for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):
value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else ''
global_fields.append({ global_fields.append({
'label': user_input_field['label'], 'label': user_input_field['label'],
'key': user_input_field['field'], 'key': user_input_field['field'],
'value': self.workflow_manage.get_reference_field( 'value': self.workflow_manage.get_reference_field(
value, user_input_field['value'][0],
user_input_field['value'][1:] user_input_field['value'][1:])
) if value != '' else ''
}) })
return { return {
'name': self.node.properties.get('stepName'), 'name': self.node.properties.get('stepName'),
@ -253,7 +229,6 @@ class BaseApplicationNode(IApplicationNode):
'run_time': self.context.get('run_time'), 'run_time': self.context.get('run_time'),
'question': self.context.get('question'), 'question': self.context.get('question'),
'answer': self.context.get('answer'), 'answer': self.context.get('answer'),
'reasoning_content': self.context.get('reasoning_content'),
'type': self.node.type, 'type': self.node.type,
'message_tokens': self.context.get('message_tokens'), 'message_tokens': self.context.get('message_tokens'),
'answer_tokens': self.context.get('answer_tokens'), 'answer_tokens': self.context.get('answer_tokens'),

View File

@ -9,22 +9,20 @@
from .contain_compare import * from .contain_compare import *
from .equal_compare import * from .equal_compare import *
from .ge_compare import *
from .gt_compare import * from .gt_compare import *
from .is_not_null_compare import * from .ge_compare import *
from .is_not_true import IsNotTrueCompare
from .is_null_compare import *
from .is_true import IsTrueCompare
from .le_compare import * from .le_compare import *
from .len_equal_compare import * from .lt_compare import *
from .len_ge_compare import * from .len_ge_compare import *
from .len_gt_compare import * from .len_gt_compare import *
from .len_le_compare import * from .len_le_compare import *
from .len_lt_compare import * from .len_lt_compare import *
from .lt_compare import * from .len_equal_compare import *
from .is_not_null_compare import *
from .is_null_compare import *
from .not_contain_compare import * from .not_contain_compare import *
compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(), compare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(),
LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(), LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(),
IsNullCompare(), IsNullCompare(),
IsNotNullCompare(), NotContainCompare(), IsTrueCompare(), IsNotTrueCompare()] IsNotNullCompare(), NotContainCompare()]

View File

@ -1,24 +0,0 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file is_not_true.py
@date2025/4/7 13:44
@desc:
"""
from typing import List
from application.flow.step_node.condition_node.compare import Compare
class IsNotTrueCompare(Compare):
def support(self, node_id, fields: List[str], source_value, compare, target_value):
if compare == 'is_not_true':
return True
def compare(self, source_value, compare, target_value):
try:
return source_value is False
except Exception as e:
return False

View File

@ -1,24 +0,0 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file IsTrue.py
@date2025/4/7 13:38
@desc:
"""
from typing import List
from application.flow.step_node.condition_node.compare import Compare
class IsTrueCompare(Compare):
def support(self, node_id, fields: List[str], source_value, compare, target_value):
if compare == 'is_true':
return True
def compare(self, source_value, compare, target_value):
try:
return source_value is True
except Exception as e:
return False

View File

@ -6,9 +6,9 @@
@date2024/6/7 9:54 @date2024/6/7 9:54
@desc: @desc:
""" """
import json
from typing import Type from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode from application.flow.i_step_node import INode
@ -16,15 +16,15 @@ from common.util.field_message import ErrMessage
class ConditionSerializer(serializers.Serializer): class ConditionSerializer(serializers.Serializer):
compare = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Comparator"))) compare = serializers.CharField(required=True, error_messages=ErrMessage.char("比较器"))
value = serializers.CharField(required=True, error_messages=ErrMessage.char(_("value"))) value = serializers.CharField(required=True, error_messages=ErrMessage.char(""))
field = serializers.ListField(required=True, error_messages=ErrMessage.char(_("Fields"))) field = serializers.ListField(required=True, error_messages=ErrMessage.char("字段"))
class ConditionBranchSerializer(serializers.Serializer): class ConditionBranchSerializer(serializers.Serializer):
id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch id"))) id = serializers.CharField(required=True, error_messages=ErrMessage.char("分支id"))
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch Type"))) type = serializers.CharField(required=True, error_messages=ErrMessage.char("分支类型"))
condition = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Condition or|and"))) condition = serializers.CharField(required=True, error_messages=ErrMessage.char("条件or|and"))
conditions = ConditionSerializer(many=True) conditions = ConditionSerializer(many=True)

View File

@ -36,15 +36,7 @@ class BaseConditionNode(IConditionNode):
return all(condition_list) if condition == 'and' else any(condition_list) return all(condition_list) if condition == 'and' else any(condition_list)
def assertion(self, field_list: List[str], compare: str, value): def assertion(self, field_list: List[str], compare: str, value):
try: field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])
value = self.workflow_manage.generate_prompt(value)
except Exception as e:
pass
field_value = None
try:
field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])
except Exception as e:
pass
for compare_handler in compare_handle_list: for compare_handler in compare_handle_list:
if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value): if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value):
return compare_handler.compare(field_value, compare, value) return compare_handler.compare(field_value, compare, value)

View File

@ -13,26 +13,25 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class ReplyNodeParamsSerializer(serializers.Serializer): class ReplyNodeParamsSerializer(serializers.Serializer):
reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Response Type"))) reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char("回复类型"))
fields = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Reference Field"))) fields = serializers.ListField(required=False, error_messages=ErrMessage.list("引用字段"))
content = serializers.CharField(required=False, allow_blank=True, allow_null=True, content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Direct answer content"))) error_messages=ErrMessage.char("直接回答内容"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
if self.data.get('reply_type') == 'referencing': if self.data.get('reply_type') == 'referencing':
if 'fields' not in self.data: if 'fields' not in self.data:
raise AppApiException(500, _("Reference field cannot be empty")) raise AppApiException(500, "引用字段不能为空")
if len(self.data.get('fields')) < 2: if len(self.data.get('fields')) < 2:
raise AppApiException(500, _("Reference field error")) raise AppApiException(500, "引用字段错误")
else: else:
if 'content' not in self.data or self.data.get('content') is None: if 'content' not in self.data or self.data.get('content') is None:
raise AppApiException(500, _("Content cannot be empty")) raise AppApiException(500, "内容不能为空")
class IReplyNode(INode): class IReplyNode(INode):

View File

@ -15,9 +15,7 @@ from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode
class BaseReplyNode(IReplyNode): class BaseReplyNode(IReplyNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
if self.node_params.get('is_result', False): self.answer_text = details.get('answer')
self.answer_text = details.get('answer')
def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult: def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:
if reply_type == 'referencing': if reply_type == 'referencing':
result = self.get_reference_content(fields) result = self.get_reference_content(fields)

View File

@ -2,7 +2,6 @@
from typing import Type from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
@ -10,7 +9,7 @@ from common.util.field_message import ErrMessage
class DocumentExtractNodeSerializer(serializers.Serializer): class DocumentExtractNodeSerializer(serializers.Serializer):
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
class IDocumentExtractNode(INode): class IDocumentExtractNode(INode):

View File

@ -38,10 +38,6 @@ def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
splitter = '\n`-----------------------------------`\n' splitter = '\n`-----------------------------------`\n'
class BaseDocumentExtractNode(IDocumentExtractNode): class BaseDocumentExtractNode(IDocumentExtractNode):
def save_context(self, details, workflow_manage):
self.context['content'] = details.get('content')
def execute(self, document, chat_id, **kwargs): def execute(self, document, chat_id, **kwargs):
get_buffer = FileBufferHandle().get_buffer get_buffer = FileBufferHandle().get_buffer

View File

@ -12,13 +12,12 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class FormNodeParamsSerializer(serializers.Serializer): class FormNodeParamsSerializer(serializers.Serializer):
form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Form Configuration"))) form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list("表单配置"))
form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Form output content'))) form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char('表单输出内容'))
form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data"))) form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("表单数据"))
class IFormNode(INode): class IFormNode(INode):

View File

@ -38,8 +38,7 @@ class BaseFormNode(IFormNode):
self.context['start_time'] = details.get('start_time') self.context['start_time'] = details.get('start_time')
self.context['form_data'] = form_data self.context['form_data'] = form_data
self.context['is_submit'] = details.get('is_submit') self.context['is_submit'] = details.get('is_submit')
if self.node_params.get('is_result', False): self.answer_text = details.get('result')
self.answer_text = details.get('result')
if form_data is not None: if form_data is not None:
for key in form_data: for key in form_data:
self.context[key] = form_data[key] self.context[key] = form_data[key]
@ -55,7 +54,7 @@ class BaseFormNode(IFormNode):
form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id, form_setting = {"form_field_list": form_field_list, "runtime_node_id": self.runtime_node_id,
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
"is_submit": self.context.get("is_submit", False)} "is_submit": self.context.get("is_submit", False)}
form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>' form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
context = self.workflow_manage.get_workflow_content() context = self.workflow_manage.get_workflow_content()
form_content_format = self.workflow_manage.reset_prompt(form_content_format) form_content_format = self.workflow_manage.reset_prompt(form_content_format)
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
@ -71,13 +70,12 @@ class BaseFormNode(IFormNode):
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
'form_data': self.context.get('form_data', {}), 'form_data': self.context.get('form_data', {}),
"is_submit": self.context.get("is_submit", False)} "is_submit": self.context.get("is_submit", False)}
form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>' form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
context = self.workflow_manage.get_workflow_content() context = self.workflow_manage.get_workflow_content()
form_content_format = self.workflow_manage.reset_prompt(form_content_format) form_content_format = self.workflow_manage.reset_prompt(form_content_format)
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')
value = prompt_template.format(form=form, context=context) value = prompt_template.format(form=form, context=context)
return [Answer(value, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], None, return [Answer(value, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'], None)]
self.runtime_node_id, '')]
def get_details(self, index: int, **kwargs): def get_details(self, index: int, **kwargs):
form_content_format = self.context.get('form_content_format') form_content_format = self.context.get('form_content_format')
@ -86,7 +84,7 @@ class BaseFormNode(IFormNode):
"chat_record_id": self.flow_params_serializer.data.get("chat_record_id"), "chat_record_id": self.flow_params_serializer.data.get("chat_record_id"),
'form_data': self.context.get('form_data', {}), 'form_data': self.context.get('form_data', {}),
"is_submit": self.context.get("is_submit", False)} "is_submit": self.context.get("is_submit", False)}
form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>' form = f'<form_rander>{json.dumps(form_setting)}</form_rander>'
context = self.workflow_manage.get_workflow_content() context = self.workflow_manage.get_workflow_content()
form_content_format = self.workflow_manage.reset_prompt(form_content_format) form_content_format = self.workflow_manage.reset_prompt(form_content_format)
prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2') prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')

View File

@ -15,24 +15,23 @@ from application.flow.i_step_node import INode, NodeResult
from common.field.common import ObjectField from common.field.common import ObjectField
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from function_lib.models.function import FunctionLib from function_lib.models.function import FunctionLib
from django.utils.translation import gettext_lazy as _
class InputField(serializers.Serializer): class InputField(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name'))) name = serializers.CharField(required=True, error_messages=ErrMessage.char('变量名'))
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list]) value = ObjectField(required=True, error_messages=ErrMessage.char("变量值"), model_type_list=[str, list])
class FunctionLibNodeParamsSerializer(serializers.Serializer): class FunctionLibNodeParamsSerializer(serializers.Serializer):
function_lib_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('Library ID'))) function_lib_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid('函数库id'))
input_field_list = InputField(required=True, many=True) input_field_list = InputField(required=True, many=True)
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
f_lib = QuerySet(FunctionLib).filter(id=self.data.get('function_lib_id')).first() f_lib = QuerySet(FunctionLib).filter(id=self.data.get('function_lib_id')).first()
if f_lib is None: if f_lib is None:
raise Exception(_('The function has been deleted')) raise Exception('函数库已被删除')
class IFunctionLibNode(INode): class IFunctionLibNode(INode):

View File

@ -11,13 +11,11 @@ import time
from typing import Dict from typing import Dict
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext as _
from application.flow.i_step_node import NodeResult from application.flow.i_step_node import NodeResult
from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.util.function_code import FunctionExecutor from common.util.function_code import FunctionExecutor
from common.util.rsa_util import rsa_long_decrypt
from function_lib.models.function import FunctionLib from function_lib.models.function import FunctionLib
from smartdoc.const import CONFIG from smartdoc.const import CONFIG
@ -40,15 +38,15 @@ def get_field_value(debug_field_list, name, is_required):
if len(result) > 0: if len(result) > 0:
return result[-1]['value'] return result[-1]['value']
if is_required: if is_required:
raise AppApiException(500, _('Field: {name} No value set').format(name=name)) raise AppApiException(500, f"{name}字段未设置值")
return None return None
def valid_reference_value(_type, value, name): def valid_reference_value(_type, value, name):
if _type == 'int': if _type == 'int':
instance_type = int | float instance_type = int
elif _type == 'float': elif _type == 'float':
instance_type = float | int instance_type = float
elif _type == 'dict': elif _type == 'dict':
instance_type = dict instance_type = dict
elif _type == 'array': elif _type == 'array':
@ -56,16 +54,13 @@ def valid_reference_value(_type, value, name):
elif _type == 'string': elif _type == 'string':
instance_type = str instance_type = str
else: else:
raise Exception(_('Field: {name} Type: {_type} Value: {value} Unsupported types').format(name=name, raise Exception(500, f'字段:{name}类型:{_type} 不支持的类型')
_type=_type))
if not isinstance(value, instance_type): if not isinstance(value, instance_type):
raise Exception( raise Exception(f'字段:{name}类型:{_type}值:{value}类型错误')
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
value=value))
def convert_value(name: str, value, _type, is_required, source, node): def convert_value(name: str, value, _type, is_required, source, node):
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)): if not is_required and value is None:
return None return None
if not is_required and source == 'reference' and (value is None or len(value) == 0): if not is_required and source == 'reference' and (value is None or len(value) == 0):
return None return None
@ -74,10 +69,6 @@ def convert_value(name: str, value, _type, is_required, source, node):
value[0], value[0],
value[1:]) value[1:])
valid_reference_value(_type, value, name) valid_reference_value(_type, value, name)
if _type == 'int':
return int(value)
if _type == 'float':
return float(value)
return value return value
try: try:
if _type == 'int': if _type == 'int':
@ -88,37 +79,26 @@ def convert_value(name: str, value, _type, is_required, source, node):
v = json.loads(value) v = json.loads(value)
if isinstance(v, dict): if isinstance(v, dict):
return v return v
raise Exception(_('type error')) raise Exception("类型错误")
if _type == 'array': if _type == 'array':
v = json.loads(value) v = json.loads(value)
if isinstance(v, list): if isinstance(v, list):
return v return v
raise Exception(_('type error')) raise Exception("类型错误")
return value return value
except Exception as e: except Exception as e:
raise Exception( raise Exception(f'字段:{name}类型:{_type}值:{value}类型错误')
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
value=value))
def valid_function(function_lib, user_id):
if function_lib is None:
raise Exception(_('Function does not exist'))
if function_lib.permission_type == 'PRIVATE' and str(function_lib.user_id) != str(user_id):
raise Exception(_('No permission to use this function {name}').format(name=function_lib.name))
if not function_lib.is_active:
raise Exception(_('Function {name} is unavailable').format(name=function_lib.name))
class BaseFunctionLibNodeNode(IFunctionLibNode): class BaseFunctionLibNodeNode(IFunctionLibNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result') self.context['result'] = details.get('result')
if self.node_params.get('is_result'): self.answer_text = details.get('result')
self.answer_text = str(details.get('result'))
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult: def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first() function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
valid_function(function_lib, self.flow_params_serializer.data.get('user_id')) if not function_lib.is_active:
raise Exception(f'函数:{function_lib.name} 不可用')
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'), params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),
field.get('is_required'), field.get('is_required'),
field.get('source'), self) field.get('source'), self)
@ -127,14 +107,8 @@ class BaseFunctionLibNodeNode(IFunctionLibNode):
), **field} ), **field}
for field in for field in
function_lib.input_field_list]} function_lib.input_field_list]}
self.context['params'] = params self.context['params'] = params
# 合并初始化参数 result = function_executor.exec_code(function_lib.code, params)
if function_lib.init_params is not None:
all_params = json.loads(rsa_long_decrypt(function_lib.init_params)) | params
else:
all_params = params
result = function_executor.exec_code(function_lib.code, all_params)
return NodeResult({'result': result}, {}, _write_context=write_context) return NodeResult({'result': result}, {}, _write_context=write_context)
def get_details(self, index: int, **kwargs): def get_details(self, index: int, **kwargs):

View File

@ -16,35 +16,32 @@ from application.flow.i_step_node import INode, NodeResult
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.field.common import ObjectField from common.field.common import ObjectField
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
from rest_framework.utils.formatting import lazy_format
class InputField(serializers.Serializer): class InputField(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name'))) name = serializers.CharField(required=True, error_messages=ErrMessage.char('变量名'))
is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean(_("Is this field required"))) is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("是否必填"))
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("type")), validators=[ type = serializers.CharField(required=True, error_messages=ErrMessage.char("类型"), validators=[
validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"), validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"),
message=_("The field only supports string|int|dict|array|float"), code=500) message="字段只支持string|int|dict|array|float", code=500)
]) ])
source = serializers.CharField(required=True, error_messages=ErrMessage.char(_("source")), validators=[ source = serializers.CharField(required=True, error_messages=ErrMessage.char("来源"), validators=[
validators.RegexValidator(regex=re.compile("^custom|reference$"), validators.RegexValidator(regex=re.compile("^custom|reference$"),
message=_("The field only supports custom|reference"), code=500) message="字段只支持custom|reference", code=500)
]) ])
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list]) value = ObjectField(required=True, error_messages=ErrMessage.char("变量值"), model_type_list=[str, list])
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
is_required = self.data.get('is_required') is_required = self.data.get('is_required')
if is_required and self.data.get('value') is None: if is_required and self.data.get('value') is None:
message = lazy_format(_('{field}, this field is required.'), field=self.data.get("name")) raise AppApiException(500, f'{self.data.get("name")}必填')
raise AppApiException(500, message)
class FunctionNodeParamsSerializer(serializers.Serializer): class FunctionNodeParamsSerializer(serializers.Serializer):
input_field_list = InputField(required=True, many=True) input_field_list = InputField(required=True, many=True)
code = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function"))) code = serializers.CharField(required=True, error_messages=ErrMessage.char("函数"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)

View File

@ -33,9 +33,9 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
def valid_reference_value(_type, value, name): def valid_reference_value(_type, value, name):
if _type == 'int': if _type == 'int':
instance_type = int | float instance_type = int
elif _type == 'float': elif _type == 'float':
instance_type = float | int instance_type = float
elif _type == 'dict': elif _type == 'dict':
instance_type = dict instance_type = dict
elif _type == 'array': elif _type == 'array':
@ -49,17 +49,13 @@ def valid_reference_value(_type, value, name):
def convert_value(name: str, value, _type, is_required, source, node): def convert_value(name: str, value, _type, is_required, source, node):
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)): if not is_required and value is None:
return None return None
if source == 'reference': if source == 'reference':
value = node.workflow_manage.get_reference_field( value = node.workflow_manage.get_reference_field(
value[0], value[0],
value[1:]) value[1:])
valid_reference_value(_type, value, name) valid_reference_value(_type, value, name)
if _type == 'int':
return int(value)
if _type == 'float':
return float(value)
return value return value
try: try:
if _type == 'int': if _type == 'int':
@ -84,8 +80,7 @@ def convert_value(name: str, value, _type, is_required, source, node):
class BaseFunctionNodeNode(IFunctionNode): class BaseFunctionNodeNode(IFunctionNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result') self.context['result'] = details.get('result')
if self.node_params.get('is_result', False): self.answer_text = details.get('result')
self.answer_text = str(details.get('result'))
def execute(self, input_field_list, code, **kwargs) -> NodeResult: def execute(self, input_field_list, code, **kwargs) -> NodeResult:
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'), params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),

View File

@ -6,27 +6,22 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class ImageGenerateNodeSerializer(serializers.Serializer): class ImageGenerateNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, error_messages=ErrMessage.char("模型id"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word (positive)"))) prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词(正向)"))
negative_prompt = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Prompt word (negative)")), negative_prompt = serializers.CharField(required=False, error_messages=ErrMessage.char("提示词(负向)"), allow_null=True, allow_blank=True,)
allow_null=True, allow_blank=True, )
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=False, default=0, dialogue_number = serializers.IntegerField(required=False, default=0, error_messages=ErrMessage.integer("多轮对话数量"))
error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
dialogue_type = serializers.CharField(required=False, default='NODE', dialogue_type = serializers.CharField(required=False, default='NODE', error_messages=ErrMessage.char("对话存储类型"))
error_messages=ErrMessage.char(_("Conversation storage type")))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
model_params_setting = serializers.JSONField(required=False, default=dict, model_params_setting = serializers.JSONField(required=False, default=dict, error_messages=ErrMessage.json("模型参数设置"))
error_messages=ErrMessage.json(_("Model parameter settings")))
class IImageGenerateNode(INode): class IImageGenerateNode(INode):

View File

@ -16,8 +16,7 @@ class BaseImageGenerateNode(IImageGenerateNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
self.context['question'] = details.get('question') self.context['question'] = details.get('question')
if self.node_params.get('is_result', False): self.answer_text = details.get('answer')
self.answer_text = details.get('answer')
def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, chat_id, def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record, chat_id,
model_params_setting, model_params_setting,
@ -25,8 +24,7 @@ class BaseImageGenerateNode(IImageGenerateNode):
**kwargs) -> NodeResult: **kwargs) -> NodeResult:
print(model_params_setting) print(model_params_setting)
application = self.workflow_manage.work_flow_post_handler.chat_info.application application = self.workflow_manage.work_flow_post_handler.chat_info.application
tti_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), tti_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), **model_params_setting)
**model_params_setting)
history_message = self.get_history_message(history_chat_record, dialogue_number) history_message = self.get_history_message(history_chat_record, dialogue_number)
self.context['history_message'] = history_message self.context['history_message'] = history_message
question = self.generate_prompt_question(prompt) question = self.generate_prompt_question(prompt)
@ -49,7 +47,7 @@ class BaseImageGenerateNode(IImageGenerateNode):
file_url = FileSerializer(data={'file': file, 'meta': meta}).upload() file_url = FileSerializer(data={'file': file, 'meta': meta}).upload()
file_urls.append(file_url) file_urls.append(file_url)
self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls] self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls]
answer = ' '.join([f"![Image]({path})" for path in file_urls]) answer = '\n'.join([f"![Image]({path})" for path in file_urls])
return NodeResult({'answer': answer, 'chat_model': tti_model, 'message_list': message_list, return NodeResult({'answer': answer, 'chat_model': tti_model, 'message_list': message_list,
'image': [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls], 'image': [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls],
'history_message': history_message, 'question': question}, {}) 'history_message': history_message, 'question': question}, {})

View File

@ -6,25 +6,24 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class ImageUnderstandNodeSerializer(serializers.Serializer): class ImageUnderstandNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, error_messages=ErrMessage.char("模型id"))
system = serializers.CharField(required=False, allow_blank=True, allow_null=True, system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Role Setting"))) error_messages=ErrMessage.char("角色设定"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词"))
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
dialogue_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Conversation storage type"))) dialogue_type = serializers.CharField(required=True, error_messages=ErrMessage.char("对话存储类型"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
model_params_setting = serializers.JSONField(required=False, default=dict, error_messages=ErrMessage.json("模型参数设置"))
model_params_setting = serializers.JSONField(required=False, default=dict,
error_messages=ErrMessage.json(_("Model parameter settings")))
class IImageUnderstandNode(INode): class IImageUnderstandNode(INode):

View File

@ -69,8 +69,7 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
self.context['question'] = details.get('question') self.context['question'] = details.get('question')
if self.node_params.get('is_result', False): self.answer_text = details.get('answer')
self.answer_text = details.get('answer')
def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, chat_id, def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream, chat_id,
model_params_setting, model_params_setting,

View File

@ -1,3 +0,0 @@
# coding=utf-8
from .impl import *

View File

@ -1,35 +0,0 @@
# coding=utf-8
from typing import Type
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class McpNodeSerializer(serializers.Serializer):
mcp_servers = serializers.JSONField(required=True,
error_messages=ErrMessage.char(_("Mcp servers")))
mcp_server = serializers.CharField(required=True,
error_messages=ErrMessage.char(_("Mcp server")))
mcp_tool = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Mcp tool")))
tool_params = serializers.DictField(required=True,
error_messages=ErrMessage.char(_("Tool parameters")))
class IMcpNode(INode):
type = 'mcp-node'
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
return McpNodeSerializer
def _run(self):
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
pass

View File

@ -1,3 +0,0 @@
# coding=utf-8
from .base_mcp_node import BaseMcpNode

View File

@ -1,61 +0,0 @@
# coding=utf-8
import asyncio
import json
from typing import List
from langchain_mcp_adapters.client import MultiServerMCPClient
from application.flow.i_step_node import NodeResult
from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode
class BaseMcpNode(IMcpNode):
def save_context(self, details, workflow_manage):
self.context['result'] = details.get('result')
self.context['tool_params'] = details.get('tool_params')
self.context['mcp_tool'] = details.get('mcp_tool')
if self.node_params.get('is_result', False):
self.answer_text = details.get('result')
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
servers = json.loads(mcp_servers)
params = json.loads(json.dumps(tool_params))
params = self.handle_variables(params)
async def call_tool(s, session, t, a):
async with MultiServerMCPClient(s) as client:
s = await client.sessions[session].call_tool(t, a)
return s
res = asyncio.run(call_tool(servers, mcp_server, mcp_tool, params))
return NodeResult(
{'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})
def handle_variables(self, tool_params):
# 处理参数中的变量
for k, v in tool_params.items():
if type(v) == str:
tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k])
if type(v) == dict:
self.handle_variables(v)
if (type(v) == list) and (type(v[0]) == str):
tool_params[k] = self.get_reference_content(v)
return tool_params
def get_reference_content(self, fields: List[str]):
return str(self.workflow_manage.get_reference_field(
fields[0],
fields[1:]))
def get_details(self, index: int, **kwargs):
return {
'name': self.node.properties.get('stepName'),
"index": index,
'run_time': self.context.get('run_time'),
'status': self.status,
'err_message': self.err_message,
'type': self.node.type,
'mcp_tool': self.context.get('mcp_tool'),
'tool_params': self.context.get('tool_params'),
'result': self.context.get('result'),
}

View File

@ -12,19 +12,18 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class QuestionNodeSerializer(serializers.Serializer): class QuestionNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, error_messages=ErrMessage.char("模型id"))
system = serializers.CharField(required=False, allow_blank=True, allow_null=True, system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Role Setting"))) error_messages=ErrMessage.char("角色设定"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) prompt = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词"))
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("多轮对话数量"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.integer(_("Model parameter settings"))) model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.integer("模型参数相关设置"))
class IQuestionNode(INode): class IQuestionNode(INode):

View File

@ -6,7 +6,6 @@
@date2024/6/4 14:30 @date2024/6/4 14:30
@desc: @desc:
""" """
import re
import time import time
from functools import reduce from functools import reduce
from typing import List, Dict from typing import List, Dict
@ -80,8 +79,7 @@ class BaseQuestionNode(IQuestionNode):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
self.context['message_tokens'] = details.get('message_tokens') self.context['message_tokens'] = details.get('message_tokens')
self.context['answer_tokens'] = details.get('answer_tokens') self.context['answer_tokens'] = details.get('answer_tokens')
if self.node_params.get('is_result', False): self.answer_text = details.get('answer')
self.answer_text = details.get('answer')
def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id, def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,
model_params_setting=None, model_params_setting=None,
@ -116,9 +114,6 @@ class BaseQuestionNode(IQuestionNode):
[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()] [history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]
for index in for index in
range(start_index if start_index > 0 else 0, len(history_chat_record))], []) range(start_index if start_index > 0 else 0, len(history_chat_record))], [])
for message in history_message:
if isinstance(message.content, str):
message.content = re.sub('<form_rander>[\d\D]*?<\/form_rander>', '', message.content)
return history_message return history_message
def generate_prompt_question(self, prompt): def generate_prompt_question(self, prompt):

View File

@ -12,18 +12,17 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class RerankerSettingSerializer(serializers.Serializer): class RerankerSettingSerializer(serializers.Serializer):
# 需要查询的条数 # 需要查询的条数
top_n = serializers.IntegerField(required=True, top_n = serializers.IntegerField(required=True,
error_messages=ErrMessage.integer(_("Reference segment number"))) error_messages=ErrMessage.integer("引用分段数"))
# 相似度 0-1之间 # 相似度 0-1之间
similarity = serializers.FloatField(required=True, max_value=2, min_value=0, similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
error_messages=ErrMessage.float(_("Reference segment number"))) error_messages=ErrMessage.float("引用分段数"))
max_paragraph_char_number = serializers.IntegerField(required=True, max_paragraph_char_number = serializers.IntegerField(required=True,
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment"))) error_messages=ErrMessage.float("最大引用分段字数"))
class RerankerStepNodeSerializer(serializers.Serializer): class RerankerStepNodeSerializer(serializers.Serializer):

View File

@ -23,14 +23,9 @@ def merge_reranker_list(reranker_list, result=None):
merge_reranker_list(document, result) merge_reranker_list(document, result)
elif isinstance(document, dict): elif isinstance(document, dict):
content = document.get('title', '') + document.get('content', '') content = document.get('title', '') + document.get('content', '')
title = document.get("title") result.append(str(document) if len(content) == 0 else content)
dataset_name = document.get("dataset_name")
document_name = document.get('document_name')
result.append(
Document(page_content=str(document) if len(content) == 0 else content,
metadata={'title': title, 'dataset_name': dataset_name, 'document_name': document_name}))
else: else:
result.append(Document(page_content=str(document), metadata={})) result.append(str(document))
return result return result
@ -48,21 +43,6 @@ def filter_result(document_list: List[Document], max_paragraph_char_number, top_
return result return result
def reset_result_list(result_list: List[Document], document_list: List[Document]):
r = []
document_list = document_list.copy()
for result in result_list:
filter_result_list = [document for document in document_list if document.page_content == result.page_content]
if len(filter_result_list) > 0:
item = filter_result_list[0]
document_list.remove(item)
r.append(Document(page_content=item.page_content,
metadata={**item.metadata, 'relevance_score': result.metadata.get('relevance_score')}))
else:
r.append(result)
return r
class BaseRerankerNode(IRerankerNode): class BaseRerankerNode(IRerankerNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['document_list'] = details.get('document_list', []) self.context['document_list'] = details.get('document_list', [])
@ -75,18 +55,16 @@ class BaseRerankerNode(IRerankerNode):
**kwargs) -> NodeResult: **kwargs) -> NodeResult:
documents = merge_reranker_list(reranker_list) documents = merge_reranker_list(reranker_list)
top_n = reranker_setting.get('top_n', 3) top_n = reranker_setting.get('top_n', 3)
self.context['document_list'] = [{'page_content': document.page_content, 'metadata': document.metadata} for self.context['document_list'] = documents
document in documents]
self.context['question'] = question self.context['question'] = question
reranker_model = get_model_instance_by_model_user_id(reranker_model_id, reranker_model = get_model_instance_by_model_user_id(reranker_model_id,
self.flow_params_serializer.data.get('user_id'), self.flow_params_serializer.data.get('user_id'),
top_n=top_n) top_n=top_n)
result = reranker_model.compress_documents( result = reranker_model.compress_documents(
documents, [Document(page_content=document) for document in documents if document is not None and len(document) > 0],
question) question)
similarity = reranker_setting.get('similarity', 0.6) similarity = reranker_setting.get('similarity', 0.6)
max_paragraph_char_number = reranker_setting.get('max_paragraph_char_number', 5000) max_paragraph_char_number = reranker_setting.get('max_paragraph_char_number', 5000)
result = reset_result_list(result, documents)
r = filter_result(result, max_paragraph_char_number, top_n, similarity) r = filter_result(result, max_paragraph_char_number, top_n, similarity)
return NodeResult({'result_list': r, 'result': ''.join([item.get('page_content') for item in r])}, {}) return NodeResult({'result_list': r, 'result': ''.join([item.get('page_content') for item in r])}, {})

View File

@ -15,31 +15,30 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.common import flat_map from common.util.common import flat_map
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class DatasetSettingSerializer(serializers.Serializer): class DatasetSettingSerializer(serializers.Serializer):
# 需要查询的条数 # 需要查询的条数
top_n = serializers.IntegerField(required=True, top_n = serializers.IntegerField(required=True,
error_messages=ErrMessage.integer(_("Reference segment number"))) error_messages=ErrMessage.integer("引用分段数"))
# 相似度 0-1之间 # 相似度 0-1之间
similarity = serializers.FloatField(required=True, max_value=2, min_value=0, similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
error_messages=ErrMessage.float(_('similarity'))) error_messages=ErrMessage.float("引用分段数"))
search_mode = serializers.CharField(required=True, validators=[ search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports embedding|keywords|blend"), code=500) message="类型只支持register|reset_password", code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode"))) ], error_messages=ErrMessage.char("检索模式"))
max_paragraph_char_number = serializers.IntegerField(required=True, max_paragraph_char_number = serializers.IntegerField(required=True,
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment"))) error_messages=ErrMessage.float("最大引用分段字数"))
class SearchDatasetStepNodeSerializer(serializers.Serializer): class SearchDatasetStepNodeSerializer(serializers.Serializer):
# 需要查询的数据集id列表 # 需要查询的数据集id列表
dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("Dataset id list"))) error_messages=ErrMessage.list("数据集id列表"))
dataset_setting = DatasetSettingSerializer(required=True) dataset_setting = DatasetSettingSerializer(required=True)
question_reference_address = serializers.ListField(required=True) question_reference_address = serializers.ListField(required=True, )
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
@ -66,7 +65,7 @@ class ISearchDatasetStepNode(INode):
if self.flow_params_serializer.data.get('re_chat', False): if self.flow_params_serializer.data.get('re_chat', False):
history_chat_record = self.flow_params_serializer.data.get('history_chat_record', []) history_chat_record = self.flow_params_serializer.data.get('history_chat_record', [])
paragraph_id_list = [p.get('id') for p in flat_map( paragraph_id_list = [p.get('id') for p in flat_map(
[get_paragraph_list(chat_record, self.runtime_node_id) for chat_record in history_chat_record if [get_paragraph_list(chat_record, self.node.id) for chat_record in history_chat_record if
chat_record.problem_text == question])] chat_record.problem_text == question])]
exclude_paragraph_id_list = list(set(paragraph_id_list)) exclude_paragraph_id_list = list(set(paragraph_id_list))

View File

@ -88,7 +88,7 @@ class BaseSearchDatasetNode(ISearchDatasetStepNode):
'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')], 'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')],
'data': '\n'.join( 'data': '\n'.join(
[f"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}" for paragraph in [f"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}" for paragraph in
result])[0:dataset_setting.get('max_paragraph_char_number', 5000)], paragraph_list])[0:dataset_setting.get('max_paragraph_char_number', 5000)],
'directly_return': '\n'.join( 'directly_return': '\n'.join(
[paragraph.get('content') for paragraph in [paragraph.get('content') for paragraph in
result if result if

View File

@ -6,15 +6,14 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class SpeechToTextNodeSerializer(serializers.Serializer): class SpeechToTextNodeSerializer(serializers.Serializer):
stt_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) stt_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char("模型id"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
audio_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("The audio file cannot be empty"))) audio_list = serializers.ListField(required=True, error_messages=ErrMessage.list("音频文件不能为空", ))
class ISpeechToTextNode(INode): class ISpeechToTextNode(INode):
@ -28,7 +27,7 @@ class ISpeechToTextNode(INode):
self.node_params_serializer.data.get('audio_list')[1:]) self.node_params_serializer.data.get('audio_list')[1:])
for audio in res: for audio in res:
if 'file_id' not in audio: if 'file_id' not in audio:
raise ValueError(_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails")) raise ValueError("参数值错误: 上传的音频中缺少file_id音频上传失败")
return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data) return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data)

View File

@ -18,8 +18,7 @@ class BaseSpeechToTextNode(ISpeechToTextNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
if self.node_params.get('is_result', False): self.answer_text = details.get('answer')
self.answer_text = details.get('answer')
def execute(self, stt_model_id, chat_id, audio, **kwargs) -> NodeResult: def execute(self, stt_model_id, chat_id, audio, **kwargs) -> NodeResult:
stt_model = get_model_instance_by_model_user_id(stt_model_id, self.flow_params_serializer.data.get('user_id')) stt_model = get_model_instance_by_model_user_id(stt_model_id, self.flow_params_serializer.data.get('user_id'))

View File

@ -40,13 +40,10 @@ class BaseStartStepNode(IStarNode):
self.context['document'] = details.get('document_list') self.context['document'] = details.get('document_list')
self.context['image'] = details.get('image_list') self.context['image'] = details.get('image_list')
self.context['audio'] = details.get('audio_list') self.context['audio'] = details.get('audio_list')
self.context['other'] = details.get('other_list')
self.status = details.get('status') self.status = details.get('status')
self.err_message = details.get('err_message') self.err_message = details.get('err_message')
for key, value in workflow_variable.items(): for key, value in workflow_variable.items():
workflow_manage.context[key] = value workflow_manage.context[key] = value
for item in details.get('global_fields', []):
workflow_manage.context[item.get('key')] = item.get('value')
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]: def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
pass pass
@ -62,8 +59,7 @@ class BaseStartStepNode(IStarNode):
'question': question, 'question': question,
'image': self.workflow_manage.image_list, 'image': self.workflow_manage.image_list,
'document': self.workflow_manage.document_list, 'document': self.workflow_manage.document_list,
'audio': self.workflow_manage.audio_list, 'audio': self.workflow_manage.audio_list
'other': self.workflow_manage.other_list,
} }
return NodeResult(node_variable, workflow_variable) return NodeResult(node_variable, workflow_variable)
@ -87,6 +83,5 @@ class BaseStartStepNode(IStarNode):
'image_list': self.context.get('image'), 'image_list': self.context.get('image'),
'document_list': self.context.get('document'), 'document_list': self.context.get('document'),
'audio_list': self.context.get('audio'), 'audio_list': self.context.get('audio'),
'other_list': self.context.get('other'),
'global_fields': global_fields 'global_fields': global_fields
} }

View File

@ -6,17 +6,16 @@ from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class TextToSpeechNodeSerializer(serializers.Serializer): class TextToSpeechNodeSerializer(serializers.Serializer):
tts_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) tts_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char("模型id"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean('是否返回内容'))
content_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Text content"))) content_list = serializers.ListField(required=True, error_messages=ErrMessage.list("文本内容"))
model_params_setting = serializers.DictField(required=False, model_params_setting = serializers.DictField(required=False,
error_messages=ErrMessage.integer(_("Model parameter settings"))) error_messages=ErrMessage.integer("模型参数相关设置"))
class ITextToSpeechNode(INode): class ITextToSpeechNode(INode):

View File

@ -37,8 +37,7 @@ def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
class BaseTextToSpeechNode(ITextToSpeechNode): class BaseTextToSpeechNode(ITextToSpeechNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['answer'] = details.get('answer') self.context['answer'] = details.get('answer')
if self.node_params.get('is_result', False): self.answer_text = details.get('answer')
self.answer_text = details.get('answer')
def execute(self, tts_model_id, chat_id, def execute(self, tts_model_id, chat_id,
content, model_params_setting=None, content, model_params_setting=None,

View File

@ -1,3 +0,0 @@
# coding=utf-8
from .impl import *

View File

@ -1,27 +0,0 @@
# coding=utf-8
from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
class VariableAssignNodeParamsSerializer(serializers.Serializer):
variable_list = serializers.ListField(required=True,
error_messages=ErrMessage.list(_("Reference Field")))
class IVariableAssignNode(INode):
type = 'variable-assign-node'
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
return VariableAssignNodeParamsSerializer
def _run(self):
return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
pass

View File

@ -1,9 +0,0 @@
# coding=utf-8
"""
@project: maxkb
@Author
@file __init__.py
@date2024/6/11 17:49
@desc:
"""
from .base_variable_assign_node import *

View File

@ -1,65 +0,0 @@
# coding=utf-8
import json
from typing import List
from application.flow.i_step_node import NodeResult
from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode
class BaseVariableAssignNode(IVariableAssignNode):
def save_context(self, details, workflow_manage):
self.context['variable_list'] = details.get('variable_list')
self.context['result_list'] = details.get('result_list')
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
#
result_list = []
for variable in variable_list:
if 'fields' not in variable:
continue
if 'global' == variable['fields'][0]:
result = {
'name': variable['name'],
'input_value': self.get_reference_content(variable['fields']),
}
if variable['source'] == 'custom':
if variable['type'] == 'json':
if isinstance(variable['value'], dict) or isinstance(variable['value'], list):
val = variable['value']
else:
val = json.loads(variable['value'])
self.workflow_manage.context[variable['fields'][1]] = val
result['output_value'] = variable['value'] = val
elif variable['type'] == 'string':
# 变量解析 例如:{{global.xxx}}
val = self.workflow_manage.generate_prompt(variable['value'])
self.workflow_manage.context[variable['fields'][1]] = val
result['output_value'] = val
else:
val = variable['value']
self.workflow_manage.context[variable['fields'][1]] = val
result['output_value'] = val
else:
reference = self.get_reference_content(variable['reference'])
self.workflow_manage.context[variable['fields'][1]] = reference
result['output_value'] = reference
result_list.append(result)
return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})
def get_reference_content(self, fields: List[str]):
return str(self.workflow_manage.get_reference_field(
fields[0],
fields[1:]))
def get_details(self, index: int, **kwargs):
return {
'name': self.node.properties.get('stepName'),
"index": index,
'run_time': self.context.get('run_time'),
'type': self.node.type,
'variable_list': self.context.get('variable_list'),
'result_list': self.context.get('result_list'),
'status': self.status,
'err_message': self.err_message
}

View File

@ -16,92 +16,6 @@ from application.flow.i_step_node import WorkFlowPostHandler
from common.response import result from common.response import result
class Reasoning:
def __init__(self, reasoning_content_start, reasoning_content_end):
self.content = ""
self.reasoning_content = ""
self.all_content = ""
self.reasoning_content_start_tag = reasoning_content_start
self.reasoning_content_end_tag = reasoning_content_end
self.reasoning_content_start_tag_len = len(
reasoning_content_start) if reasoning_content_start is not None else 0
self.reasoning_content_end_tag_len = len(reasoning_content_end) if reasoning_content_end is not None else 0
self.reasoning_content_end_tag_prefix = reasoning_content_end[
0] if self.reasoning_content_end_tag_len > 0 else ''
self.reasoning_content_is_start = False
self.reasoning_content_is_end = False
self.reasoning_content_chunk = ""
def get_end_reasoning_content(self):
if not self.reasoning_content_is_start and not self.reasoning_content_is_end:
r = {'content': self.all_content, 'reasoning_content': ''}
self.reasoning_content_chunk = ""
return r
if self.reasoning_content_is_start and not self.reasoning_content_is_end:
r = {'content': '', 'reasoning_content': self.reasoning_content_chunk}
self.reasoning_content_chunk = ""
return r
return {'content': '', 'reasoning_content': ''}
def get_reasoning_content(self, chunk):
# 如果没有开始思考过程标签那么就全是结果
if self.reasoning_content_start_tag is None or len(self.reasoning_content_start_tag) == 0:
self.content += chunk.content
return {'content': chunk.content, 'reasoning_content': ''}
# 如果没有结束思考过程标签那么就全部是思考过程
if self.reasoning_content_end_tag is None or len(self.reasoning_content_end_tag) == 0:
return {'content': '', 'reasoning_content': chunk.content}
self.all_content += chunk.content
if not self.reasoning_content_is_start and len(self.all_content) >= self.reasoning_content_start_tag_len:
if self.all_content.startswith(self.reasoning_content_start_tag):
self.reasoning_content_is_start = True
self.reasoning_content_chunk = self.all_content[self.reasoning_content_start_tag_len:]
else:
if not self.reasoning_content_is_end:
self.reasoning_content_is_end = True
self.content += self.all_content
return {'content': self.all_content, 'reasoning_content': ''}
else:
if self.reasoning_content_is_start:
self.reasoning_content_chunk += chunk.content
reasoning_content_end_tag_prefix_index = self.reasoning_content_chunk.find(
self.reasoning_content_end_tag_prefix)
if self.reasoning_content_is_end:
self.content += chunk.content
return {'content': chunk.content, 'reasoning_content': ''}
# 是否包含结束
if reasoning_content_end_tag_prefix_index > -1:
if len(self.reasoning_content_chunk) - reasoning_content_end_tag_prefix_index >= self.reasoning_content_end_tag_len:
reasoning_content_end_tag_index = self.reasoning_content_chunk.find(self.reasoning_content_end_tag)
if reasoning_content_end_tag_index > -1:
reasoning_content_chunk = self.reasoning_content_chunk[0:reasoning_content_end_tag_index]
content_chunk = self.reasoning_content_chunk[
reasoning_content_end_tag_index + self.reasoning_content_end_tag_len:]
self.reasoning_content += reasoning_content_chunk
self.content += content_chunk
self.reasoning_content_chunk = ""
self.reasoning_content_is_end = True
return {'content': content_chunk, 'reasoning_content': reasoning_content_chunk}
else:
reasoning_content_chunk = self.reasoning_content_chunk[0:reasoning_content_end_tag_prefix_index + 1]
self.reasoning_content_chunk = self.reasoning_content_chunk.replace(reasoning_content_chunk, '')
self.reasoning_content += reasoning_content_chunk
return {'content': '', 'reasoning_content': reasoning_content_chunk}
else:
return {'content': '', 'reasoning_content': ''}
else:
if self.reasoning_content_is_end:
self.content += chunk.content
return {'content': chunk.content, 'reasoning_content': ''}
else:
# aaa
result = {'content': '', 'reasoning_content': self.reasoning_content_chunk}
self.reasoning_content += self.reasoning_content_chunk
self.reasoning_content_chunk = ""
return result
def event_content(chat_id, chat_record_id, response, workflow, def event_content(chat_id, chat_record_id, response, workflow,
write_context, write_context,
post_handler: WorkFlowPostHandler): post_handler: WorkFlowPostHandler):

View File

@ -10,20 +10,19 @@ import concurrent
import json import json
import threading import threading
import traceback import traceback
import uuid
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from functools import reduce from functools import reduce
from typing import List, Dict from typing import List, Dict
from django.db import close_old_connections from django.db import close_old_connections
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils import translation
from django.utils.translation import get_language
from django.utils.translation import gettext as _
from langchain_core.prompts import PromptTemplate from langchain_core.prompts import PromptTemplate
from rest_framework import status from rest_framework import status
from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.exceptions import ErrorDetail, ValidationError
from application.flow import tools from application.flow import tools
from application.flow.common import Answer
from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult from application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult
from application.flow.step_node import get_node from application.flow.step_node import get_node
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
@ -104,14 +103,12 @@ class Flow:
edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id] edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id]
if len(edge_list) == 0: if len(edge_list) == 0:
raise AppApiException(500, raise AppApiException(500,
_('The branch {branch} of the {node} node needs to be connected').format( f'{node.properties.get("stepName")} 节点的{branch.get("type")}分支需要连接')
node=node.properties.get("stepName"), branch=branch.get("type")))
else: else:
edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id] edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]
if len(edge_list) == 0 and not end_nodes.__contains__(node.type): if len(edge_list) == 0 and not end_nodes.__contains__(node.type):
raise AppApiException(500, _("{node} Nodes cannot be considered as end nodes").format( raise AppApiException(500, f'{node.properties.get("stepName")} 节点不能当做结束节点')
node=node.properties.get("stepName")))
def get_next_nodes(self, node: Node): def get_next_nodes(self, node: Node):
edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id] edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]
@ -120,7 +117,7 @@ class Flow:
[]) [])
if len(node_list) == 0 and not end_nodes.__contains__(node.type): if len(node_list) == 0 and not end_nodes.__contains__(node.type):
raise AppApiException(500, raise AppApiException(500,
_("The next node that does not exist")) f'不存在的下一个节点')
return node_list return node_list
def is_valid_work_flow(self, up_node=None): def is_valid_work_flow(self, up_node=None):
@ -134,17 +131,16 @@ class Flow:
def is_valid_start_node(self): def is_valid_start_node(self):
start_node_list = [node for node in self.nodes if node.id == 'start-node'] start_node_list = [node for node in self.nodes if node.id == 'start-node']
if len(start_node_list) == 0: if len(start_node_list) == 0:
raise AppApiException(500, _('The starting node is required')) raise AppApiException(500, '开始节点必填')
if len(start_node_list) > 1: if len(start_node_list) > 1:
raise AppApiException(500, _('There can only be one starting node')) raise AppApiException(500, '开始节点只能有一个')
def is_valid_model_params(self): def is_valid_model_params(self):
node_list = [node for node in self.nodes if (node.type == 'ai-chat-node' or node.type == 'question-node')] node_list = [node for node in self.nodes if (node.type == 'ai-chat-node' or node.type == 'question-node')]
for node in node_list: for node in node_list:
model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first() model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first()
if model is None: if model is None:
raise ValidationError(ErrorDetail( raise ValidationError(ErrorDetail(f'节点{node.properties.get("stepName")} 模型不存在'))
_('The node {node} model does not exist').format(node=node.properties.get("stepName"))))
credential = get_model_credential(model.provider, model.model_type, model.model_name) credential = get_model_credential(model.provider, model.model_type, model.model_name)
model_params_setting = node.properties.get('node_data', {}).get('model_params_setting') model_params_setting = node.properties.get('node_data', {}).get('model_params_setting')
model_params_setting_form = credential.get_model_params_setting_form( model_params_setting_form = credential.get_model_params_setting_form(
@ -153,25 +149,22 @@ class Flow:
model_params_setting = model_params_setting_form.get_default_form_data() model_params_setting = model_params_setting_form.get_default_form_data()
node.properties.get('node_data', {})['model_params_setting'] = model_params_setting node.properties.get('node_data', {})['model_params_setting'] = model_params_setting
if node.properties.get('status', 200) != 200: if node.properties.get('status', 200) != 200:
raise ValidationError( raise ValidationError(ErrorDetail(f'节点{node.properties.get("stepName")} 不可用'))
ErrorDetail(_("Node {node} is unavailable").format(node.properties.get("stepName"))))
node_list = [node for node in self.nodes if (node.type == 'function-lib-node')] node_list = [node for node in self.nodes if (node.type == 'function-lib-node')]
for node in node_list: for node in node_list:
function_lib_id = node.properties.get('node_data', {}).get('function_lib_id') function_lib_id = node.properties.get('node_data', {}).get('function_lib_id')
if function_lib_id is None: if function_lib_id is None:
raise ValidationError(ErrorDetail( raise ValidationError(ErrorDetail(f'节点{node.properties.get("stepName")} 函数库id不能为空'))
_('The library ID of node {node} cannot be empty').format(node=node.properties.get("stepName"))))
f_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first() f_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
if f_lib is None: if f_lib is None:
raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format( raise ValidationError(ErrorDetail(f'节点{node.properties.get("stepName")} 函数库不可用'))
node=node.properties.get("stepName"))))
def is_valid_base_node(self): def is_valid_base_node(self):
base_node_list = [node for node in self.nodes if node.id == 'base-node'] base_node_list = [node for node in self.nodes if node.id == 'base-node']
if len(base_node_list) == 0: if len(base_node_list) == 0:
raise AppApiException(500, _('Basic information node is required')) raise AppApiException(500, '基本信息节点必填')
if len(base_node_list) > 1: if len(base_node_list) > 1:
raise AppApiException(500, _('There can only be one basic information node')) raise AppApiException(500, '基本信息节点只能有一个')
class NodeResultFuture: class NodeResultFuture:
@ -238,7 +231,6 @@ class WorkflowManage:
base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None, base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,
document_list=None, document_list=None,
audio_list=None, audio_list=None,
other_list=None,
start_node_id=None, start_node_id=None,
start_node_data=None, chat_record=None, child_node=None): start_node_data=None, chat_record=None, child_node=None):
if form_data is None: if form_data is None:
@ -249,15 +241,12 @@ class WorkflowManage:
document_list = [] document_list = []
if audio_list is None: if audio_list is None:
audio_list = [] audio_list = []
if other_list is None:
other_list = []
self.start_node_id = start_node_id self.start_node_id = start_node_id
self.start_node = None self.start_node = None
self.form_data = form_data self.form_data = form_data
self.image_list = image_list self.image_list = image_list
self.document_list = document_list self.document_list = document_list
self.audio_list = audio_list self.audio_list = audio_list
self.other_list = other_list
self.params = params self.params = params
self.flow = flow self.flow = flow
self.context = {} self.context = {}
@ -273,36 +262,11 @@ class WorkflowManage:
self.child_node = child_node self.child_node = child_node
self.future_list = [] self.future_list = []
self.lock = threading.Lock() self.lock = threading.Lock()
self.field_list = []
self.global_field_list = []
self.init_fields()
if start_node_id is not None: if start_node_id is not None:
self.load_node(chat_record, start_node_id, start_node_data) self.load_node(chat_record, start_node_id, start_node_data)
else: else:
self.node_context = [] self.node_context = []
def init_fields(self):
field_list = []
global_field_list = []
for node in self.flow.nodes:
properties = node.properties
node_name = properties.get('stepName')
node_id = node.id
node_config = properties.get('config')
if node_config is not None:
fields = node_config.get('fields')
if fields is not None:
for field in fields:
field_list.append({**field, 'node_id': node_id, 'node_name': node_name})
global_fields = node_config.get('globalFields')
if global_fields is not None:
for global_field in global_fields:
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
global_field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
self.field_list = field_list
self.global_field_list = global_field_list
def append_answer(self, content): def append_answer(self, content):
self.answer += content self.answer += content
self.answer_list[-1] += content self.answer_list[-1] += content
@ -339,22 +303,20 @@ class WorkflowManage:
node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list')) node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list'))
node.valid_args(node.node_params, node.workflow_params) node.valid_args(node.node_params, node.workflow_params)
node.save_context(node_details, self) node.save_context(node_details, self)
node.node_chunk.end()
self.node_context.append(node) self.node_context.append(node)
def run(self): def run(self):
close_old_connections() close_old_connections()
language = get_language()
if self.params.get('stream'): if self.params.get('stream'):
return self.run_stream(self.start_node, None, language) return self.run_stream(self.start_node, None)
return self.run_block(language) return self.run_block()
def run_block(self, language='zh'): def run_block(self):
""" """
非流式响应 非流式响应
@return: 结果 @return: 结果
""" """
self.run_chain_async(None, None, language) self.run_chain_async(None, None)
while self.is_run(): while self.is_run():
pass pass
details = self.get_runtime_details() details = self.get_runtime_details()
@ -363,25 +325,21 @@ class WorkflowManage:
answer_tokens = sum([row.get('answer_tokens') for row in details.values() if answer_tokens = sum([row.get('answer_tokens') for row in details.values() if
'answer_tokens' in row and row.get('answer_tokens') is not None]) 'answer_tokens' in row and row.get('answer_tokens') is not None])
answer_text_list = self.get_answer_text_list() answer_text_list = self.get_answer_text_list()
answer_text = '\n\n'.join( answer_text = '\n\n'.join(answer['content'] for answer in answer_text_list)
'\n\n'.join([a.get('content') for a in answer]) for answer in
answer_text_list)
answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'], self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
answer_text, answer_text,
self) self)
return self.base_to_response.to_block_response(self.params['chat_id'], return self.base_to_response.to_block_response(self.params['chat_id'],
self.params['chat_record_id'], answer_text, True self.params['chat_record_id'], answer_text, True
, message_tokens, answer_tokens, , message_tokens, answer_tokens,
_status=status.HTTP_200_OK if self.status == 200 else status.HTTP_500_INTERNAL_SERVER_ERROR, _status=status.HTTP_200_OK if self.status == 200 else status.HTTP_500_INTERNAL_SERVER_ERROR)
other_params={'answer_list': answer_list})
def run_stream(self, current_node, node_result_future, language='zh'): def run_stream(self, current_node, node_result_future):
""" """
流式响应 流式响应
@return: @return:
""" """
self.run_chain_async(current_node, node_result_future, language) self.run_chain_async(current_node, node_result_future)
return tools.to_stream_response_simple(self.await_result()) return tools.to_stream_response_simple(self.await_result())
def is_run(self, timeout=0.5): def is_run(self, timeout=0.5):
@ -413,8 +371,6 @@ class WorkflowManage:
break break
yield chunk yield chunk
finally: finally:
while self.is_run():
pass
details = self.get_runtime_details() details = self.get_runtime_details()
message_tokens = sum([row.get('message_tokens') for row in details.values() if message_tokens = sum([row.get('message_tokens') for row in details.values() if
'message_tokens' in row and row.get('message_tokens') is not None]) 'message_tokens' in row and row.get('message_tokens') is not None])
@ -429,12 +385,11 @@ class WorkflowManage:
[], [],
'', True, message_tokens, answer_tokens, {}) '', True, message_tokens, answer_tokens, {})
def run_chain_async(self, current_node, node_result_future, language='zh'): def run_chain_async(self, current_node, node_result_future):
future = executor.submit(self.run_chain_manage, current_node, node_result_future, language) future = executor.submit(self.run_chain_manage, current_node, node_result_future)
self.future_list.append(future) self.future_list.append(future)
def run_chain_manage(self, current_node, node_result_future, language='zh'): def run_chain_manage(self, current_node, node_result_future):
translation.activate(language)
if current_node is None: if current_node is None:
start_node = self.get_start_node() start_node = self.get_start_node()
current_node = get_node(start_node.type)(start_node, self.params, self) current_node = get_node(start_node.type)(start_node, self.params, self)
@ -446,12 +401,11 @@ class WorkflowManage:
return return
node_list = self.get_next_node_list(current_node, result) node_list = self.get_next_node_list(current_node, result)
if len(node_list) == 1: if len(node_list) == 1:
self.run_chain_manage(node_list[0], None, language) self.run_chain_manage(node_list[0], None)
elif len(node_list) > 1: elif len(node_list) > 1:
sorted_node_run_list = sorted(node_list, key=lambda n: n.node.y) sorted_node_run_list = sorted(node_list, key=lambda n: n.node.y)
# 获取到可执行的子节点 # 获取到可执行的子节点
result_list = [{'node': node, 'future': executor.submit(self.run_chain_manage, node, None, language)} for result_list = [{'node': node, 'future': executor.submit(self.run_chain_manage, node, None)} for node in
node in
sorted_node_run_list] sorted_node_run_list]
for r in result_list: for r in result_list:
self.future_list.append(r.get('future')) self.future_list.append(r.get('future'))
@ -494,7 +448,6 @@ class WorkflowManage:
self.node_context.append(current_node) self.node_context.append(current_node)
def hand_event_node_result(self, current_node, node_result_future): def hand_event_node_result(self, current_node, node_result_future):
runtime_node_id = current_node.runtime_node_id
real_node_id = current_node.runtime_node_id real_node_id = current_node.runtime_node_id
child_node = {} child_node = {}
view_type = current_node.view_type view_type = current_node.view_type
@ -504,7 +457,6 @@ class WorkflowManage:
if result is not None: if result is not None:
if self.is_result(current_node, current_result): if self.is_result(current_node, current_result):
for r in result: for r in result:
reasoning_content = ''
content = r content = r
child_node = {} child_node = {}
node_is_end = False node_is_end = False
@ -514,24 +466,20 @@ class WorkflowManage:
child_node = {'runtime_node_id': r.get('runtime_node_id'), child_node = {'runtime_node_id': r.get('runtime_node_id'),
'chat_record_id': r.get('chat_record_id') 'chat_record_id': r.get('chat_record_id')
, 'child_node': r.get('child_node')} , 'child_node': r.get('child_node')}
if r.__contains__('real_node_id'): real_node_id = r.get('real_node_id')
real_node_id = r.get('real_node_id') node_is_end = r.get('node_is_end')
if r.__contains__('node_is_end'):
node_is_end = r.get('node_is_end')
view_type = r.get('view_type') view_type = r.get('view_type')
reasoning_content = r.get('reasoning_content')
chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'], chunk = self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
self.params['chat_record_id'], self.params['chat_record_id'],
current_node.id, current_node.id,
current_node.up_node_id_list, current_node.up_node_id_list,
content, False, 0, 0, content, False, 0, 0,
{'node_type': current_node.type, {'node_type': current_node.type,
'runtime_node_id': runtime_node_id, 'runtime_node_id': current_node.runtime_node_id,
'view_type': view_type, 'view_type': view_type,
'child_node': child_node, 'child_node': child_node,
'node_is_end': node_is_end, 'node_is_end': node_is_end,
'real_node_id': real_node_id, 'real_node_id': real_node_id})
'reasoning_content': reasoning_content})
current_node.node_chunk.add_chunk(chunk) current_node.node_chunk.add_chunk(chunk)
chunk = (self.base_to_response chunk = (self.base_to_response
.to_stream_chunk_response(self.params['chat_id'], .to_stream_chunk_response(self.params['chat_id'],
@ -539,12 +487,11 @@ class WorkflowManage:
current_node.id, current_node.id,
current_node.up_node_id_list, current_node.up_node_id_list,
'', False, 0, 0, {'node_is_end': True, '', False, 0, 0, {'node_is_end': True,
'runtime_node_id': runtime_node_id, 'runtime_node_id': current_node.runtime_node_id,
'node_type': current_node.type, 'node_type': current_node.type,
'view_type': view_type, 'view_type': view_type,
'child_node': child_node, 'child_node': child_node,
'real_node_id': real_node_id, 'real_node_id': real_node_id}))
'reasoning_content': ''}))
current_node.node_chunk.add_chunk(chunk) current_node.node_chunk.add_chunk(chunk)
else: else:
list(result) list(result)
@ -556,7 +503,7 @@ class WorkflowManage:
self.params['chat_record_id'], self.params['chat_record_id'],
current_node.id, current_node.id,
current_node.up_node_id_list, current_node.up_node_id_list,
'Exception:' + str(e), False, 0, 0, str(e), False, 0, 0,
{'node_is_end': True, {'node_is_end': True,
'runtime_node_id': current_node.runtime_node_id, 'runtime_node_id': current_node.runtime_node_id,
'node_type': current_node.type, 'node_type': current_node.type,
@ -629,7 +576,7 @@ class WorkflowManage:
details['node_id'] = node.id details['node_id'] = node.id
details['up_node_id_list'] = node.up_node_id_list details['up_node_id_list'] = node.up_node_id_list
details['runtime_node_id'] = node.runtime_node_id details['runtime_node_id'] = node.runtime_node_id
details_result[node.runtime_node_id] = details details_result[str(uuid.uuid1())] = details
return details_result return details_result
def get_answer_text_list(self): def get_answer_text_list(self):
@ -643,19 +590,20 @@ class WorkflowManage:
if len(current_answer.content) > 0: if len(current_answer.content) > 0:
if up_node is None or current_answer.view_type == 'single_view' or ( if up_node is None or current_answer.view_type == 'single_view' or (
current_answer.view_type == 'many_view' and up_node.view_type == 'single_view'): current_answer.view_type == 'many_view' and up_node.view_type == 'single_view'):
result.append([current_answer]) result.append(current_answer)
else: else:
if len(result) > 0: if len(result) > 0:
exec_index = len(result) - 1 exec_index = len(result) - 1
if isinstance(result[exec_index], list): content = result[exec_index].content
result[exec_index].append(current_answer) result[exec_index].content += current_answer.content if len(
content) == 0 else ('\n\n' + current_answer.content)
else: else:
result.insert(0, [current_answer]) result.insert(0, current_answer)
up_node = current_answer up_node = current_answer
if len(result) == 0: if len(result) == 0:
# 如果没有响应 就响应一个空数据 # 如果没有响应 就响应一个空数据
return [[]] return [Answer('', '', '', '', {}).to_dict()]
return [[item.to_dict() for item in r] for r in result] return [r.to_dict() for r in result]
def get_next_node(self): def get_next_node(self):
""" """
@ -768,15 +716,22 @@ class WorkflowManage:
def reset_prompt(self, prompt: str): def reset_prompt(self, prompt: str):
placeholder = "{}" placeholder = "{}"
for field in self.field_list: for node in self.flow.nodes:
globeLabel = f"{field.get('node_name')}.{field.get('value')}" properties = node.properties
globeValue = f"context.get('{field.get('node_id')}',{placeholder}).get('{field.get('value', '')}','')" node_config = properties.get('config')
prompt = prompt.replace(globeLabel, globeValue) if node_config is not None:
for field in self.global_field_list: fields = node_config.get('fields')
globeLabel = f"全局变量.{field.get('value')}" if fields is not None:
globeLabelNew = f"global.{field.get('value')}" for field in fields:
globeValue = f"context.get('global').get('{field.get('value', '')}','')" globeLabel = f"{properties.get('stepName')}.{field.get('value')}"
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue) globeValue = f"context.get('{node.id}',{placeholder}).get('{field.get('value', '')}','')"
prompt = prompt.replace(globeLabel, globeValue)
global_fields = node_config.get('globalFields')
if global_fields is not None:
for field in global_fields:
globeLabel = f"全局变量.{field.get('value')}"
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
prompt = prompt.replace(globeLabel, globeValue)
return prompt return prompt
def generate_prompt(self, prompt: str): def generate_prompt(self, prompt: str):

View File

@ -1,11 +1,11 @@
# Generated by Django 4.2.13 on 2024-07-15 15:52 # Generated by Django 4.2.13 on 2024-07-15 15:52
import application.models.application
from django.db import migrations, models from django.db import migrations, models
import common.encoder.encoder
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('application', '0009_application_type_application_work_flow_and_more'), ('application', '0009_application_type_application_work_flow_and_more'),
] ]
@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='chatrecord', model_name='chatrecord',
name='details', name='details',
field=models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情'), field=models.JSONField(default=dict, encoder=application.models.application.DateEncoder, verbose_name='对话详情'),
), ),
] ]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.15 on 2025-01-03 14:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0021_applicationpublicaccessclient_client_id_and_more'),
]
operations = [
migrations.AddField(
model_name='application',
name='tts_autoplay',
field=models.BooleanField(default=False, verbose_name='自动播放'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.15 on 2025-01-06 10:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0022_application_tts_autoplay'),
]
operations = [
migrations.AddField(
model_name='application',
name='stt_autosend',
field=models.BooleanField(default=False, verbose_name='自动发送'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 4.2.15 on 2025-01-20 06:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0023_application_stt_autosend'),
]
operations = [
migrations.AddField(
model_name='applicationaccesstoken',
name='language',
field=models.CharField(default=None, max_length=10, null=True, verbose_name='语言')
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.18 on 2025-01-22 09:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0024_applicationaccesstoken_language'),
]
operations = [
migrations.AlterField(
model_name='application',
name='prologue',
field=models.CharField(default='', max_length=40960, verbose_name='开场白'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 4.2.18 on 2025-03-18 06:05
import application.models.application
import common.encoder.encoder
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('application', '0025_alter_application_prologue'),
]
operations = [
migrations.AddField(
model_name='chat',
name='asker',
field=models.JSONField(default=application.models.application.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者'),
),
]

View File

@ -13,14 +13,9 @@ from django.db import models
from application.models import Application from application.models import Application
from common.mixins.app_model_mixin import AppModelMixin from common.mixins.app_model_mixin import AppModelMixin
from smartdoc.const import CONFIG
from users.models import User from users.models import User
def get_language():
return CONFIG.get_language_code()
class ApplicationApiKey(AppModelMixin): class ApplicationApiKey(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
secret_key = models.CharField(max_length=1024, verbose_name="秘钥", unique=True) secret_key = models.CharField(max_length=1024, verbose_name="秘钥", unique=True)
@ -50,8 +45,6 @@ class ApplicationAccessToken(AppModelMixin):
, default=list) , default=list)
show_source = models.BooleanField(default=False, verbose_name="是否显示知识来源") show_source = models.BooleanField(default=False, verbose_name="是否显示知识来源")
language = models.CharField(max_length=10, verbose_name="语言", default=None, null=True)
class Meta: class Meta:
db_table = "application_access_token" db_table = "application_access_token"

View File

@ -6,13 +6,14 @@
@date2023/9/25 14:24 @date2023/9/25 14:24
@desc: @desc:
""" """
import datetime
import json
import uuid import uuid
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from langchain.schema import HumanMessage, AIMessage from langchain.schema import HumanMessage, AIMessage
from django.utils.translation import gettext as _
from common.encoder.encoder import SystemEncoder
from common.mixins.app_model_mixin import AppModelMixin from common.mixins.app_model_mixin import AppModelMixin
from dataset.models.data_set import DataSet from dataset.models.data_set import DataSet
from setting.models.model_management import Model from setting.models.model_management import Model
@ -34,20 +35,14 @@ def get_dataset_setting_dict():
def get_model_setting_dict(): def get_model_setting_dict():
return { return {'prompt': Application.get_default_model_prompt(), 'no_references_prompt': '{question}'}
'prompt': Application.get_default_model_prompt(),
'no_references_prompt': '{question}',
'reasoning_content_start': '<think>',
'reasoning_content_end': '</think>',
'reasoning_content_enable': False,
}
class Application(AppModelMixin): class Application(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
name = models.CharField(max_length=128, verbose_name="应用名称") name = models.CharField(max_length=128, verbose_name="应用名称")
desc = models.CharField(max_length=512, verbose_name="引用描述", default="") desc = models.CharField(max_length=512, verbose_name="引用描述", default="")
prologue = models.CharField(max_length=40960, verbose_name="开场白", default="") prologue = models.CharField(max_length=4096, verbose_name="开场白", default="")
dialogue_number = models.IntegerField(default=0, verbose_name="会话数量") dialogue_number = models.IntegerField(default=0, verbose_name="会话数量")
user = models.ForeignKey(User, on_delete=models.DO_NOTHING) user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True) model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)
@ -70,8 +65,6 @@ class Application(AppModelMixin):
tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False) tts_model_enable = models.BooleanField(verbose_name="语音合成模型是否启用", default=False)
stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False) stt_model_enable = models.BooleanField(verbose_name="语音识别模型是否启用", default=False)
tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER") tts_type = models.CharField(verbose_name="语音播放类型", max_length=20, default="BROWSER")
tts_autoplay = models.BooleanField(verbose_name="自动播放", default=False)
stt_autosend = models.BooleanField(verbose_name="自动发送", default=False)
clean_time = models.IntegerField(verbose_name="清理时间", default=180) clean_time = models.IntegerField(verbose_name="清理时间", default=180)
file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False) file_upload_enable = models.BooleanField(verbose_name="文件上传是否启用", default=False)
file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict) file_upload_setting = models.JSONField(verbose_name="文件上传相关设置", default=dict)
@ -115,15 +108,10 @@ class ApplicationDatasetMapping(AppModelMixin):
db_table = "application_dataset_mapping" db_table = "application_dataset_mapping"
def default_asker():
return {'user_name': '游客'}
class Chat(AppModelMixin): class Chat(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
application = models.ForeignKey(Application, on_delete=models.CASCADE) application = models.ForeignKey(Application, on_delete=models.CASCADE)
abstract = models.CharField(max_length=1024, verbose_name="摘要") abstract = models.CharField(max_length=1024, verbose_name="摘要")
asker = models.JSONField(verbose_name="访问者", default=default_asker, encoder=SystemEncoder)
client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True) client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True)
is_deleted = models.BooleanField(verbose_name="", default=False) is_deleted = models.BooleanField(verbose_name="", default=False)
@ -138,6 +126,16 @@ class VoteChoices(models.TextChoices):
TRAMPLE = 1, '反对' TRAMPLE = 1, '反对'
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, uuid.UUID):
return str(obj)
if isinstance(obj, datetime.datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
else:
return json.JSONEncoder.default(self, obj)
class ChatRecord(AppModelMixin): class ChatRecord(AppModelMixin):
""" """
对话日志 详情 对话日志 详情
@ -154,7 +152,7 @@ class ChatRecord(AppModelMixin):
message_tokens = models.IntegerField(verbose_name="请求token数量", default=0) message_tokens = models.IntegerField(verbose_name="请求token数量", default=0)
answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0) answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0)
const = models.IntegerField(verbose_name="总费用", default=0) const = models.IntegerField(verbose_name="总费用", default=0)
details = models.JSONField(verbose_name="对话详情", default=dict, encoder=SystemEncoder) details = models.JSONField(verbose_name="对话详情", default=dict, encoder=DateEncoder)
improve_paragraph_id_list = ArrayField(verbose_name="改进标注列表", improve_paragraph_id_list = ArrayField(verbose_name="改进标注列表",
base_field=models.UUIDField(max_length=128, blank=True) base_field=models.UUIDField(max_length=128, blank=True)
, default=list) , default=list)
@ -167,11 +165,7 @@ class ChatRecord(AppModelMixin):
return HumanMessage(content=self.problem_text) return HumanMessage(content=self.problem_text)
def get_ai_message(self): def get_ai_message(self):
answer_text = self.answer_text return AIMessage(content=self.answer_text)
if answer_text is None or len(str(answer_text).strip()) == 0:
answer_text = _(
'Sorry, no relevant content was found. Please re-describe your problem or provide more information. ')
return AIMessage(content=answer_text)
def get_node_details_runtime_node_id(self, runtime_node_id): def get_node_details_runtime_node_id(self, runtime_node_id):
return self.details.get(runtime_node_id, None) return self.details.get(runtime_node_id, None)

View File

@ -6,7 +6,6 @@
@date2023/11/7 10:02 @date2023/11/7 10:02
@desc: @desc:
""" """
import asyncio
import datetime import datetime
import hashlib import hashlib
import json import json
@ -16,6 +15,7 @@ import re
import uuid import uuid
from functools import reduce from functools import reduce
from typing import Dict, List from typing import Dict, List
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.core import cache, validators from django.core import cache, validators
from django.core import signing from django.core import signing
@ -24,10 +24,7 @@ from django.db.models import QuerySet
from django.db.models.expressions import RawSQL from django.db.models.expressions import RawSQL
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Template, Context from django.template import Template, Context
from langchain_mcp_adapters.client import MultiServerMCPClient
from mcp.client.sse import sse_client
from rest_framework import serializers, status from rest_framework import serializers, status
from rest_framework.utils.formatting import lazy_format
from application.flow.workflow_manage import Flow from application.flow.workflow_manage import Flow
from application.models import Application, ApplicationDatasetMapping, ApplicationTypeChoices, WorkFlowVersion from application.models import Application, ApplicationDatasetMapping, ApplicationTypeChoices, WorkFlowVersion
@ -42,13 +39,13 @@ from common.exception.app_exception import AppApiException, NotFound404, AppUnau
from common.field.common import UploadedImageField, UploadedFileField from common.field.common import UploadedImageField, UploadedFileField
from common.models.db_model_manage import DBModelManage from common.models.db_model_manage import DBModelManage
from common.response import result from common.response import result
from common.util.common import valid_license, password_encrypt, restricted_loads from common.util.common import valid_license, password_encrypt
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from common.util.file_util import get_file_content from common.util.file_util import get_file_content
from dataset.models import DataSet, Document, Image from dataset.models import DataSet, Document, Image
from dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list from dataset.serializers.common_serializers import list_paragraph, get_embedding_model_by_dataset_id_list
from embedding.models import SearchMode from embedding.models import SearchMode
from function_lib.models.function import FunctionLib, PermissionType, FunctionType from function_lib.models.function import FunctionLib, PermissionType
from function_lib.serializers.function_lib_serializer import FunctionLibSerializer, FunctionLibModelSerializer from function_lib.serializers.function_lib_serializer import FunctionLibSerializer, FunctionLibModelSerializer
from setting.models import AuthOperate, TeamMemberPermission from setting.models import AuthOperate, TeamMemberPermission
from setting.models.model_management import Model from setting.models.model_management import Model
@ -57,13 +54,13 @@ from setting.models_provider.tools import get_model_instance_by_model_user_id
from setting.serializers.provider_serializers import ModelSerializer from setting.serializers.provider_serializers import ModelSerializer
from smartdoc.conf import PROJECT_DIR from smartdoc.conf import PROJECT_DIR
from users.models import User from users.models import User
from django.utils.translation import gettext_lazy as _, get_language, to_locale from django.db.models import Value
from django.db.models.fields.json import KeyTextTransform
chat_cache = cache.caches['chat_cache'] chat_cache = cache.caches['chat_cache']
class MKInstance: class MKInstance:
def __init__(self, application: dict, function_lib_list: List[dict], version: str): def __init__(self, application: dict, function_lib_list: List[dict], version: str):
self.application = application self.application = application
self.function_lib_list = function_lib_list self.function_lib_list = function_lib_list
@ -71,13 +68,13 @@ class MKInstance:
class ModelDatasetAssociation(serializers.Serializer): class ModelDatasetAssociation(serializers.Serializer):
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Model id"))) error_messages=ErrMessage.char("模型id"))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True, dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True,
error_messages=ErrMessage.uuid( error_messages=ErrMessage.uuid(
_("Knowledge base id"))), "知识库id")),
error_messages=ErrMessage.list(_("Knowledge Base List"))) error_messages=ErrMessage.list("知识库列表"))
def is_valid(self, *, raise_exception=True): def is_valid(self, *, raise_exception=True):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
@ -85,13 +82,13 @@ class ModelDatasetAssociation(serializers.Serializer):
user_id = self.data.get('user_id') user_id = self.data.get('user_id')
if model_id is not None and len(model_id) > 0: if model_id is not None and len(model_id) > 0:
if not QuerySet(Model).filter(id=model_id).exists(): if not QuerySet(Model).filter(id=model_id).exists():
raise AppApiException(500, f'{_("Model does not exist")}{model_id}') raise AppApiException(500, f'模型不存在{model_id}')
dataset_id_list = list(set(self.data.get('dataset_id_list'))) dataset_id_list = list(set(self.data.get('dataset_id_list')))
exist_dataset_id_list = [str(dataset.id) for dataset in exist_dataset_id_list = [str(dataset.id) for dataset in
QuerySet(DataSet).filter(id__in=dataset_id_list, user_id=user_id)] QuerySet(DataSet).filter(id__in=dataset_id_list, user_id=user_id)]
for dataset_id in dataset_id_list: for dataset_id in dataset_id_list:
if not exist_dataset_id_list.__contains__(dataset_id): if not exist_dataset_id_list.__contains__(dataset_id):
raise AppApiException(500, f'{_("The knowledge base id does not exist")}{dataset_id}') raise AppApiException(500, f'知识库id不存在{dataset_id}')
class ApplicationSerializerModel(serializers.ModelSerializer): class ApplicationSerializerModel(serializers.ModelSerializer):
@ -108,8 +105,8 @@ class NoReferencesChoices(models.TextChoices):
class NoReferencesSetting(serializers.Serializer): class NoReferencesSetting(serializers.Serializer):
status = serializers.ChoiceField(required=True, choices=NoReferencesChoices.choices, status = serializers.ChoiceField(required=True, choices=NoReferencesChoices.choices,
error_messages=ErrMessage.char(_("No reference status"))) error_messages=ErrMessage.char("无引用状态"))
value = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) value = serializers.CharField(required=True, error_messages=ErrMessage.char("提示词"))
def valid_model_params_setting(model_id, model_params_setting): def valid_model_params_setting(model_id, model_params_setting):
@ -121,64 +118,45 @@ def valid_model_params_setting(model_id, model_params_setting):
class DatasetSettingSerializer(serializers.Serializer): class DatasetSettingSerializer(serializers.Serializer):
top_n = serializers.FloatField(required=True, max_value=10000, min_value=1, top_n = serializers.FloatField(required=True, max_value=100, min_value=1,
error_messages=ErrMessage.float(_("Reference segment number"))) error_messages=ErrMessage.float("引用分段数"))
similarity = serializers.FloatField(required=True, max_value=1, min_value=0, similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
error_messages=ErrMessage.float(_("Acquaintance"))) error_messages=ErrMessage.float("相识度"))
max_paragraph_char_number = serializers.IntegerField(required=True, min_value=500, max_value=100000, max_paragraph_char_number = serializers.IntegerField(required=True, min_value=500, max_value=100000,
error_messages=ErrMessage.integer( error_messages=ErrMessage.integer("最多引用字符数"))
_("Maximum number of quoted characters")))
search_mode = serializers.CharField(required=True, validators=[ search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports embedding|keywords|blend"), code=500) message="类型只支持register|reset_password", code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode"))) ], error_messages=ErrMessage.char("检索模式"))
no_references_setting = NoReferencesSetting(required=True, no_references_setting = NoReferencesSetting(required=True, error_messages=ErrMessage.base("未引用分段设置"))
error_messages=ErrMessage.base(_("Segment settings not referenced")))
class ModelSettingSerializer(serializers.Serializer): class ModelSettingSerializer(serializers.Serializer):
prompt = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, prompt = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char(_("Prompt word"))) error_messages=ErrMessage.char("提示词"))
system = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, system = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
error_messages=ErrMessage.char(_("Role prompts"))) error_messages=ErrMessage.char("角色提示词"))
no_references_prompt = serializers.CharField(required=True, max_length=102400, allow_null=True, allow_blank=True, no_references_prompt = serializers.CharField(required=True, max_length=102400, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("No citation segmentation prompt"))) error_messages=ErrMessage.char("无引用分段提示词"))
reasoning_content_enable = serializers.BooleanField(required=False,
error_messages=ErrMessage.char(_("Thinking process switch")))
reasoning_content_start = serializers.CharField(required=False, allow_null=True, default="<think>",
allow_blank=True, max_length=256,
trim_whitespace=False,
error_messages=ErrMessage.char(
_("The thinking process begins to mark")))
reasoning_content_end = serializers.CharField(required=False, allow_null=True, allow_blank=True, default="</think>",
max_length=256,
trim_whitespace=False,
error_messages=ErrMessage.char(_("End of thinking process marker")))
class ApplicationWorkflowSerializer(serializers.Serializer): class ApplicationWorkflowSerializer(serializers.Serializer):
name = serializers.CharField(required=True, max_length=64, min_length=1, name = serializers.CharField(required=True, max_length=64, min_length=1, error_messages=ErrMessage.char("应用名称"))
error_messages=ErrMessage.char(_("Application Name")))
desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
max_length=256, min_length=1, max_length=256, min_length=1,
error_messages=ErrMessage.char(_("Application Description"))) error_messages=ErrMessage.char("应用描述"))
work_flow = serializers.DictField(required=False, error_messages=ErrMessage.dict(_("Workflow Objects"))) work_flow = serializers.DictField(required=False, error_messages=ErrMessage.dict("工作流对象"))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
error_messages=ErrMessage.char(_("Opening remarks"))) error_messages=ErrMessage.char("开场白"))
@staticmethod @staticmethod
def to_application_model(user_id: str, application: Dict): def to_application_model(user_id: str, application: Dict):
language = get_language()
if application.get('work_flow') is not None: if application.get('work_flow') is not None:
default_workflow = application.get('work_flow') default_workflow = application.get('work_flow')
else: else:
workflow_file_path = os.path.join(PROJECT_DIR, "apps", "application", 'flow', default_workflow_json = get_file_content(
f'default_workflow_{to_locale(language)}.json') os.path.join(PROJECT_DIR, "apps", "application", 'flow', 'default_workflow.json'))
if not os.path.exists(workflow_file_path):
workflow_file_path = os.path.join(PROJECT_DIR, "apps", "application", 'flow',
f'default_workflow_zh.json')
default_workflow_json = get_file_content(workflow_file_path)
default_workflow = json.loads(default_workflow_json) default_workflow = json.loads(default_workflow_json)
for node in default_workflow.get('nodes'): for node in default_workflow.get('nodes'):
if node.get('id') == 'base-node': if node.get('id') == 'base-node':
@ -216,41 +194,36 @@ def get_base_node_work_flow(work_flow):
class ApplicationSerializer(serializers.Serializer): class ApplicationSerializer(serializers.Serializer):
name = serializers.CharField(required=True, max_length=64, min_length=1, name = serializers.CharField(required=True, max_length=64, min_length=1, error_messages=ErrMessage.char("应用名称"))
error_messages=ErrMessage.char(_("application name")))
desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
max_length=256, min_length=1, max_length=256, min_length=1,
error_messages=ErrMessage.char(_("application describe"))) error_messages=ErrMessage.char("应用描述"))
model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Model"))) error_messages=ErrMessage.char("模型"))
dialogue_number = serializers.IntegerField(required=True, dialogue_number = serializers.IntegerField(required=True,
min_value=0, min_value=0,
max_value=1024, max_value=1024,
error_messages=ErrMessage.integer(_("Historical chat records"))) error_messages=ErrMessage.integer("历史聊天记录"))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
error_messages=ErrMessage.char(_("Opening remarks"))) error_messages=ErrMessage.char("开场白"))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
allow_null=True, allow_null=True, error_messages=ErrMessage.list("关联知识库"))
error_messages=ErrMessage.list(_("Related Knowledge Base")))
# 数据集相关设置 # 数据集相关设置
dataset_setting = DatasetSettingSerializer(required=True) dataset_setting = DatasetSettingSerializer(required=True)
# 模型相关设置 # 模型相关设置
model_setting = ModelSettingSerializer(required=True) model_setting = ModelSettingSerializer(required=True)
# 问题补全 # 问题补全
problem_optimization = serializers.BooleanField(required=True, problem_optimization = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("问题补全"))
error_messages=ErrMessage.boolean(_("Question completion")))
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400, problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
error_messages=ErrMessage.char(_("Question completion prompt"))) error_messages=ErrMessage.char("问题补全提示词"))
# 应用类型 # 应用类型
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application Type")), type = serializers.CharField(required=True, error_messages=ErrMessage.char("应用类型"),
validators=[ validators=[
validators.RegexValidator(regex=re.compile("^SIMPLE|WORK_FLOW$"), validators.RegexValidator(regex=re.compile("^SIMPLE|WORK_FLOW$"),
message=_( message="应用类型只支持SIMPLE|WORK_FLOW", code=500)
"Application type only supports SIMPLE|WORK_FLOW"),
code=500)
] ]
) )
model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.dict(_('Model parameters'))) model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.dict('模型参数'))
def is_valid(self, *, user_id=None, raise_exception=False): def is_valid(self, *, user_id=None, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
@ -258,9 +231,9 @@ class ApplicationSerializer(serializers.Serializer):
'dataset_id_list': self.data.get('dataset_id_list')}).is_valid() 'dataset_id_list': self.data.get('dataset_id_list')}).is_valid()
class Embed(serializers.Serializer): class Embed(serializers.Serializer):
host = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Host"))) host = serializers.CharField(required=True, error_messages=ErrMessage.char("主机"))
protocol = serializers.CharField(required=True, error_messages=ErrMessage.char(_("protocol"))) protocol = serializers.CharField(required=True, error_messages=ErrMessage.char("协议"))
token = serializers.CharField(required=True, error_messages=ErrMessage.char(_("token"))) token = serializers.CharField(required=True, error_messages=ErrMessage.char("token"))
def get_embed(self, with_valid=True, params=None): def get_embed(self, with_valid=True, params=None):
if params is None: if params is None:
@ -334,31 +307,26 @@ class ApplicationSerializer(serializers.Serializer):
for field in input_field_list: for field in input_field_list:
if field['assignment_method'] == 'api_input' and field['variable'] in params: if field['assignment_method'] == 'api_input' and field['variable'] in params:
query += f"&{field['variable']}={params[field['variable']]}" query += f"&{field['variable']}={params[field['variable']]}"
if 'asker' in params:
query += f"&asker={params.get('asker')}"
return query return query
class AccessTokenSerializer(serializers.Serializer): class AccessTokenSerializer(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.boolean(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.boolean("应用id"))
class AccessTokenEditSerializer(serializers.Serializer): class AccessTokenEditSerializer(serializers.Serializer):
access_token_reset = serializers.BooleanField(required=False, access_token_reset = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean(_("Reset Token"))) error_messages=ErrMessage.boolean("重置Token"))
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_("Is it enabled"))) is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("是否开启"))
access_num = serializers.IntegerField(required=False, max_value=10000, access_num = serializers.IntegerField(required=False, max_value=10000,
min_value=0, min_value=0,
error_messages=ErrMessage.integer(_("Number of visits"))) error_messages=ErrMessage.integer("访问次数"))
white_active = serializers.BooleanField(required=False, white_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("是否开启白名单"))
error_messages=ErrMessage.boolean(_("Whether to enable whitelist")))
white_list = serializers.ListSerializer(required=False, child=serializers.CharField(required=True, white_list = serializers.ListSerializer(required=False, child=serializers.CharField(required=True,
error_messages=ErrMessage.char( error_messages=ErrMessage.char(
_("Whitelist"))), "白名单")),
error_messages=ErrMessage.list(_("Whitelist"))), error_messages=ErrMessage.list("白名单列表")),
show_source = serializers.BooleanField(required=False, show_source = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean( error_messages=ErrMessage.boolean("是否显示知识来源"))
_("Whether to display knowledge sources")))
language = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("language")))
def edit(self, instance: Dict, with_valid=True): def edit(self, instance: Dict, with_valid=True):
if with_valid: if with_valid:
@ -381,10 +349,6 @@ class ApplicationSerializer(serializers.Serializer):
application_access_token.white_list = instance.get('white_list') application_access_token.white_list = instance.get('white_list')
if 'show_source' in instance and instance.get('show_source') is not None: if 'show_source' in instance and instance.get('show_source') is not None:
application_access_token.show_source = instance.get('show_source') application_access_token.show_source = instance.get('show_source')
if 'language' in instance and instance.get('language') is not None:
application_access_token.language = instance.get('language')
if 'language' not in instance or instance.get('language') is None:
application_access_token.language = None
application_access_token.save() application_access_token.save()
application_setting_model = DBModelManage.get_model('application_setting') application_setting_model = DBModelManage.get_model('application_setting')
xpack_cache = DBModelManage.get_model('xpack_cache') xpack_cache = DBModelManage.get_model('xpack_cache')
@ -422,14 +386,13 @@ class ApplicationSerializer(serializers.Serializer):
'access_num': application_access_token.access_num, 'access_num': application_access_token.access_num,
'white_active': application_access_token.white_active, 'white_active': application_access_token.white_active,
'white_list': application_access_token.white_list, 'white_list': application_access_token.white_list,
'show_source': application_access_token.show_source, 'show_source': application_access_token.show_source
'language': application_access_token.language
} }
class Authentication(serializers.Serializer): class Authentication(serializers.Serializer):
access_token = serializers.CharField(required=True, error_messages=ErrMessage.char(_("access_token"))) access_token = serializers.CharField(required=True, error_messages=ErrMessage.char("access_token"))
authentication_value = serializers.JSONField(required=False, allow_null=True, authentication_value = serializers.JSONField(required=False, allow_null=True,
error_messages=ErrMessage.char(_("Certification Information"))) error_messages=ErrMessage.char("认证信息"))
def auth(self, request, with_valid=True): def auth(self, request, with_valid=True):
token = request.META.get('HTTP_AUTHORIZATION') token = request.META.get('HTTP_AUTHORIZATION')
@ -466,7 +429,7 @@ class ApplicationSerializer(serializers.Serializer):
'authentication': authentication}) 'authentication': authentication})
return token return token
else: else:
raise NotFound404(404, _("Invalid access_token")) raise NotFound404(404, "无效的access_token")
def auth_authentication_value(self, authentication_value, application_id): def auth_authentication_value(self, authentication_value, application_id):
application_setting_model = DBModelManage.get_model('application_setting') application_setting_model = DBModelManage.get_model('application_setting')
@ -477,7 +440,7 @@ class ApplicationSerializer(serializers.Serializer):
if application_setting.authentication and authentication_value is not None: if application_setting.authentication and authentication_value is not None:
if authentication_value.get('type') == 'password': if authentication_value.get('type') == 'password':
if not self.auth_password(authentication_value, application_setting.authentication_value): if not self.auth_password(authentication_value, application_setting.authentication_value):
raise AppApiException(1005, _("Wrong password")) raise AppApiException(1005, "密码错误")
return True return True
@staticmethod @staticmethod
@ -486,40 +449,38 @@ class ApplicationSerializer(serializers.Serializer):
class Edit(serializers.Serializer): class Edit(serializers.Serializer):
name = serializers.CharField(required=False, max_length=64, min_length=1, name = serializers.CharField(required=False, max_length=64, min_length=1,
error_messages=ErrMessage.char(_("Application Name"))) error_messages=ErrMessage.char("应用名称"))
desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True, desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Application Description"))) error_messages=ErrMessage.char("应用描述"))
model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Model"))) error_messages=ErrMessage.char("模型"))
dialogue_number = serializers.IntegerField(required=False, dialogue_number = serializers.IntegerField(required=False,
min_value=0, min_value=0,
max_value=1024, max_value=1024,
error_messages=ErrMessage.integer(_("Historical chat records"))) error_messages=ErrMessage.integer("历史聊天记录"))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400, prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=4096,
error_messages=ErrMessage.char(_("Opening remarks"))) error_messages=ErrMessage.char("开场白"))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("Related Knowledge Base")) error_messages=ErrMessage.list("关联知识库")
) )
# 数据集相关设置 # 数据集相关设置
dataset_setting = DatasetSettingSerializer(required=False, allow_null=True, dataset_setting = DatasetSettingSerializer(required=False, allow_null=True,
error_messages=ErrMessage.json(_("Dataset settings"))) error_messages=ErrMessage.json("数据集设置"))
# 模型相关设置 # 模型相关设置
model_setting = ModelSettingSerializer(required=False, allow_null=True, model_setting = ModelSettingSerializer(required=False, allow_null=True,
error_messages=ErrMessage.json(_("Model setup"))) error_messages=ErrMessage.json("模型设置"))
# 问题补全 # 问题补全
problem_optimization = serializers.BooleanField(required=False, allow_null=True, problem_optimization = serializers.BooleanField(required=False, allow_null=True,
error_messages=ErrMessage.boolean(_("Question completion"))) error_messages=ErrMessage.boolean("问题补全"))
icon = serializers.CharField(required=False, allow_null=True, error_messages=ErrMessage.char(_("Icon"))) icon = serializers.CharField(required=False, allow_null=True, error_messages=ErrMessage.char("icon图标"))
model_params_setting = serializers.DictField(required=False, model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.dict('模型参数'))
error_messages=ErrMessage.dict(_('Model parameters')))
class Create(serializers.Serializer): class Create(serializers.Serializer):
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
@valid_license(model=Application, count=5, @valid_license(model=Application, count=5,
message=_( message='社区版最多支持 5 个应用如需拥有更多应用请联系我们https://fit2cloud.com/)。')
'The community version supports up to 5 applications. If you need more applications, please contact us (https://fit2cloud.com/).'))
@transaction.atomic @transaction.atomic
def insert(self, application: Dict): def insert(self, application: Dict):
application_type = application.get('type') application_type = application.get('type')
@ -585,22 +546,22 @@ class ApplicationSerializer(serializers.Serializer):
return ApplicationDatasetMapping(id=uuid.uuid1(), application_id=application_id, dataset_id=dataset_id) return ApplicationDatasetMapping(id=uuid.uuid1(), application_id=application_id, dataset_id=dataset_id)
class HitTest(serializers.Serializer): class HitTest(serializers.Serializer):
id = serializers.CharField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) id = serializers.CharField(required=True, error_messages=ErrMessage.uuid("应用id"))
user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid("用户id"))
query_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Query text"))) query_text = serializers.CharField(required=True, error_messages=ErrMessage.char("查询文本"))
top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1, top_number = serializers.IntegerField(required=True, max_value=100, min_value=1,
error_messages=ErrMessage.integer(_("topN"))) error_messages=ErrMessage.integer("topN"))
similarity = serializers.FloatField(required=True, max_value=2, min_value=0, similarity = serializers.FloatField(required=True, max_value=2, min_value=0,
error_messages=ErrMessage.float(_("Relevance"))) error_messages=ErrMessage.float("相关度"))
search_mode = serializers.CharField(required=True, validators=[ search_mode = serializers.CharField(required=True, validators=[
validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"), validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
message=_("The type only supports embedding|keywords|blend"), code=500) message="类型只支持register|reset_password", code=500)
], error_messages=ErrMessage.char(_("Retrieval Mode"))) ], error_messages=ErrMessage.char("检索模式"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
if not QuerySet(Application).filter(id=self.data.get('id')).exists(): if not QuerySet(Application).filter(id=self.data.get('id')).exists():
raise AppApiException(500, _('Application id does not exist')) raise AppApiException(500, '不存在的应用id')
def hit_test(self): def hit_test(self):
self.is_valid() self.is_valid()
@ -627,12 +588,12 @@ class ApplicationSerializer(serializers.Serializer):
'comprehensive_score': hit_dict.get(p.get('id')).get('comprehensive_score')} for p in p_list] 'comprehensive_score': hit_dict.get(p.get('id')).get('comprehensive_score')} for p in p_list]
class Query(serializers.Serializer): class Query(serializers.Serializer):
name = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Application Name"))) name = serializers.CharField(required=False, error_messages=ErrMessage.char("应用名称"))
desc = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Application Description"))) desc = serializers.CharField(required=False, error_messages=ErrMessage.char("应用描述"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
select_user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid(_("Select User ID"))) select_user_id = serializers.UUIDField(required=False, error_messages=ErrMessage.uuid("选择用户id"))
def get_query_set(self): def get_query_set(self):
user_id = self.data.get("user_id") user_id = self.data.get("user_id")
@ -660,7 +621,8 @@ class ApplicationSerializer(serializers.Serializer):
query_set_dict['team_member_permission_custom_sql'] = QuerySet(model=get_dynamics_model( query_set_dict['team_member_permission_custom_sql'] = QuerySet(model=get_dynamics_model(
{'user_id': models.CharField(), {'user_id': models.CharField(),
'team_member_permission.auth_target_type': models.CharField(), 'team_member_permission.auth_target_type': models.CharField(),
'team_member_permission.operate': ArrayField(base_field=models.CharField(max_length=256, 'team_member_permission.operate': ArrayField(verbose_name="权限操作列表",
base_field=models.CharField(max_length=256,
blank=True, blank=True,
choices=AuthOperate.choices, choices=AuthOperate.choices,
default=AuthOperate.USE) default=AuthOperate.USE)
@ -700,16 +662,16 @@ class ApplicationSerializer(serializers.Serializer):
fields = ['id', 'name', 'desc', 'prologue', 'dialogue_number', 'icon', 'type'] fields = ['id', 'name', 'desc', 'prologue', 'dialogue_number', 'icon', 'type']
class IconOperate(serializers.Serializer): class IconOperate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
image = UploadedImageField(required=True, error_messages=ErrMessage.image(_("picture"))) image = UploadedImageField(required=True, error_messages=ErrMessage.image("图片"))
def edit(self, with_valid=True): def edit(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
application = QuerySet(Application).filter(id=self.data.get('application_id')).first() application = QuerySet(Application).filter(id=self.data.get('application_id')).first()
if application is None: if application is None:
raise AppApiException(500, _('Application id does not exist')) raise AppApiException(500, '不存在的应用id')
image_id = uuid.uuid1() image_id = uuid.uuid1()
image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name) image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name)
image.save() image.save()
@ -721,12 +683,11 @@ class ApplicationSerializer(serializers.Serializer):
return {**ApplicationSerializer.Query.reset_application(ApplicationSerializerModel(application).data)} return {**ApplicationSerializer.Query.reset_application(ApplicationSerializerModel(application).data)}
class Import(serializers.Serializer): class Import(serializers.Serializer):
file = UploadedFileField(required=True, error_messages=ErrMessage.image(_("file"))) file = UploadedFileField(required=True, error_messages=ErrMessage.image("文件"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
@valid_license(model=Application, count=5, @valid_license(model=Application, count=5,
message=_( message='社区版最多支持 5 个应用如需拥有更多应用请联系我们https://fit2cloud.com/)。')
'The community version supports up to 5 applications. If you need more applications, please contact us (https://fit2cloud.com/).'))
@transaction.atomic @transaction.atomic
def import_(self, with_valid=True): def import_(self, with_valid=True):
if with_valid: if with_valid:
@ -734,9 +695,9 @@ class ApplicationSerializer(serializers.Serializer):
user_id = self.data.get('user_id') user_id = self.data.get('user_id')
mk_instance_bytes = self.data.get('file').read() mk_instance_bytes = self.data.get('file').read()
try: try:
mk_instance = restricted_loads(mk_instance_bytes) mk_instance = pickle.loads(mk_instance_bytes)
except Exception as e: except Exception as e:
raise AppApiException(1001, _("Unsupported file format")) raise AppApiException(1001, "不支持的文件格式")
application = mk_instance.application application = mk_instance.application
function_lib_list = mk_instance.function_lib_list function_lib_list = mk_instance.function_lib_list
if len(function_lib_list) > 0: if len(function_lib_list) > 0:
@ -795,13 +756,13 @@ class ApplicationSerializer(serializers.Serializer):
permission_type=PermissionType.PRIVATE) permission_type=PermissionType.PRIVATE)
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
if not QuerySet(Application).filter(id=self.data.get('application_id')).exists(): if not QuerySet(Application).filter(id=self.data.get('application_id')).exists():
raise AppApiException(500, _('Application id does not exist')) raise AppApiException(500, '不存在的应用id')
def list_model(self, model_type=None, with_valid=True): def list_model(self, model_type=None, with_valid=True):
if with_valid: if with_valid:
@ -817,10 +778,8 @@ class ApplicationSerializer(serializers.Serializer):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
application = QuerySet(Application).filter(id=self.data.get("application_id")).first() application = QuerySet(Application).filter(id=self.data.get("application_id")).first()
return FunctionLibSerializer.Query( return FunctionLibSerializer.Query(data={'user_id': application.user_id, 'is_active': True}).list(
data={'user_id': application.user_id, 'is_active': True, with_valid=True)
'function_type': FunctionType.PUBLIC}
).list(with_valid=True)
def get_function_lib(self, function_lib_id, with_valid=True): def get_function_lib(self, function_lib_id, with_valid=True):
if with_valid: if with_valid:
@ -876,7 +835,7 @@ class ApplicationSerializer(serializers.Serializer):
application = QuerySet(Application).filter(id=self.data.get("application_id")).first() application = QuerySet(Application).filter(id=self.data.get("application_id")).first()
work_flow = instance.get('work_flow') work_flow = instance.get('work_flow')
if work_flow is None: if work_flow is None:
raise AppApiException(500, _("work_flow is a required field")) raise AppApiException(500, "work_flow是必填字段")
Flow.new_instance(work_flow).is_valid() Flow.new_instance(work_flow).is_valid()
base_node = get_base_node_work_flow(work_flow) base_node = get_base_node_work_flow(work_flow)
if base_node is not None: if base_node is not None:
@ -938,9 +897,7 @@ class ApplicationSerializer(serializers.Serializer):
dataset_id_list = node_data.get('dataset_id_list', []) dataset_id_list = node_data.get('dataset_id_list', [])
for dataset_id in dataset_id_list: for dataset_id in dataset_id_list:
if not user_dataset_id_list.__contains__(dataset_id): if not user_dataset_id_list.__contains__(dataset_id):
message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'), raise AppApiException(500, f"未知的知识库id${dataset_id},无法关联")
dataset_id=dataset_id)
raise AppApiException(500, message)
source_dataset_id_list = node_data.get('source_dataset_id_list', []) source_dataset_id_list = node_data.get('source_dataset_id_list', [])
source_dataset_id_list = [source_dataset_id for source_dataset_id in source_dataset_id_list if source_dataset_id_list = [source_dataset_id for source_dataset_id in source_dataset_id_list if
@ -958,7 +915,7 @@ class ApplicationSerializer(serializers.Serializer):
application = QuerySet(Application).get(id=application_id) application = QuerySet(Application).get(id=application_id)
application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application.id).first() application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application.id).first()
if application_access_token is None: if application_access_token is None:
raise AppUnauthorizedFailed(500, _("Illegal User")) raise AppUnauthorizedFailed(500, "非法用户")
application_setting_model = DBModelManage.get_model('application_setting') application_setting_model = DBModelManage.get_model('application_setting')
if application.type == ApplicationTypeChoices.WORK_FLOW: if application.type == ApplicationTypeChoices.WORK_FLOW:
work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application.id).order_by( work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application.id).order_by(
@ -990,7 +947,6 @@ class ApplicationSerializer(serializers.Serializer):
'draggable': application_setting.draggable, 'draggable': application_setting.draggable,
'show_guide': application_setting.show_guide, 'show_guide': application_setting.show_guide,
'avatar': application_setting.avatar, 'avatar': application_setting.avatar,
'show_avatar': application_setting.show_avatar,
'float_icon': application_setting.float_icon, 'float_icon': application_setting.float_icon,
'authentication': application_setting.authentication, 'authentication': application_setting.authentication,
'authentication_type': application_setting.authentication_value.get( 'authentication_type': application_setting.authentication_value.get(
@ -999,7 +955,6 @@ class ApplicationSerializer(serializers.Serializer):
'disclaimer_value': application_setting.disclaimer_value, 'disclaimer_value': application_setting.disclaimer_value,
'custom_theme': application_setting.custom_theme, 'custom_theme': application_setting.custom_theme,
'user_avatar': application_setting.user_avatar, 'user_avatar': application_setting.user_avatar,
'show_user_avatar': application_setting.show_user_avatar,
'float_location': application_setting.float_location} 'float_location': application_setting.float_location}
return ApplicationSerializer.Query.reset_application( return ApplicationSerializer.Query.reset_application(
{**ApplicationSerializer.ApplicationModel(application).data, {**ApplicationSerializer.ApplicationModel(application).data,
@ -1008,14 +963,10 @@ class ApplicationSerializer(serializers.Serializer):
'stt_model_enable': application.stt_model_enable, 'stt_model_enable': application.stt_model_enable,
'tts_model_enable': application.tts_model_enable, 'tts_model_enable': application.tts_model_enable,
'tts_type': application.tts_type, 'tts_type': application.tts_type,
'tts_autoplay': application.tts_autoplay,
'stt_autosend': application.stt_autosend,
'file_upload_enable': application.file_upload_enable, 'file_upload_enable': application.file_upload_enable,
'file_upload_setting': application.file_upload_setting, 'file_upload_setting': application.file_upload_setting,
'work_flow': {'nodes': [node for node in ((application.work_flow or {}).get('nodes', []) or []) if 'work_flow': application.work_flow,
node.get('id') == 'base-node']},
'show_source': application_access_token.show_source, 'show_source': application_access_token.show_source,
'language': application_access_token.language,
**application_setting_dict}) **application_setting_dict})
@transaction.atomic @transaction.atomic
@ -1033,30 +984,27 @@ class ApplicationSerializer(serializers.Serializer):
model = QuerySet(Model).filter( model = QuerySet(Model).filter(
id=instance.get('model_id')).first() id=instance.get('model_id')).first()
if model is None: if model is None:
raise AppApiException(500, _("Model does not exist")) raise AppApiException(500, "模型不存在")
if not model.is_permission(application.user_id): if not model.is_permission(application.user_id):
message = lazy_format(_('No permission to use this model:{model_name}'), model_name=model.name) raise AppApiException(500, f"沒有权限使用该模型:{model.name}")
raise AppApiException(500, message)
if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0: if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0:
application.stt_model_id = None application.stt_model_id = None
else: else:
model = QuerySet(Model).filter( model = QuerySet(Model).filter(
id=instance.get('stt_model_id')).first() id=instance.get('stt_model_id')).first()
if model is None: if model is None:
raise AppApiException(500, _("Model does not exist")) raise AppApiException(500, "模型不存在")
if not model.is_permission(application.user_id): if not model.is_permission(application.user_id):
message = lazy_format(_('No permission to use this model:{model_name}'), model_name=model.name) raise AppApiException(500, f"沒有权限使用该模型:{model.name}")
raise AppApiException(500, message)
if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0: if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0:
application.tts_model_id = None application.tts_model_id = None
else: else:
model = QuerySet(Model).filter( model = QuerySet(Model).filter(
id=instance.get('tts_model_id')).first() id=instance.get('tts_model_id')).first()
if model is None: if model is None:
raise AppApiException(500, _("Model does not exist")) raise AppApiException(500, "模型不存在")
if not model.is_permission(application.user_id): if not model.is_permission(application.user_id):
message = lazy_format(_('No permission to use this model:{model_name}'), model_name=model.name) raise AppApiException(500, f"沒有权限使用该模型:{model.name}")
raise AppApiException(500, message)
if 'work_flow' in instance: if 'work_flow' in instance:
# 当前用户可修改关联的知识库列表 # 当前用户可修改关联的知识库列表
application_dataset_id_list = [str(dataset_dict.get('id')) for dataset_dict in application_dataset_id_list = [str(dataset_dict.get('id')) for dataset_dict in
@ -1068,13 +1016,12 @@ class ApplicationSerializer(serializers.Serializer):
update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status', update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',
'dataset_setting', 'model_setting', 'problem_optimization', 'dialogue_number', 'dataset_setting', 'model_setting', 'problem_optimization', 'dialogue_number',
'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type', 'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type',
'tts_autoplay', 'stt_autosend', 'file_upload_enable', 'file_upload_setting', 'file_upload_enable', 'file_upload_setting',
'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting', 'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting',
'problem_optimization_prompt', 'clean_time'] 'problem_optimization_prompt', 'clean_time']
for update_key in update_keys: for update_key in update_keys:
if update_key in instance and instance.get(update_key) is not None: if update_key in instance and instance.get(update_key) is not None:
application.__setattr__(update_key, instance.get(update_key)) application.__setattr__(update_key, instance.get(update_key))
print(application.name)
application.save() application.save()
if 'dataset_id_list' in instance: if 'dataset_id_list' in instance:
@ -1084,16 +1031,13 @@ class ApplicationSerializer(serializers.Serializer):
self.list_dataset(with_valid=False)] self.list_dataset(with_valid=False)]
for dataset_id in dataset_id_list: for dataset_id in dataset_id_list:
if not application_dataset_id_list.__contains__(dataset_id): if not application_dataset_id_list.__contains__(dataset_id):
message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'), raise AppApiException(500, f"未知的知识库id${dataset_id},无法关联")
dataset_id=dataset_id)
raise AppApiException(500, message)
self.save_application_mapping(application_dataset_id_list, dataset_id_list, application_id) self.save_application_mapping(application_dataset_id_list, dataset_id_list, application_id)
if application.type == ApplicationTypeChoices.SIMPLE: if application.type == ApplicationTypeChoices.SIMPLE:
chat_cache.clear_by_application_id(application_id) chat_cache.clear_by_application_id(application_id)
application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application_id).first() application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application_id).first()
# 更新缓存数据 # 更新缓存数据
print(application.name)
get_application_access_token(application_access_token.access_token, False) get_application_access_token(application_access_token.access_token, False)
return self.one(with_valid=False) return self.one(with_valid=False)
@ -1136,18 +1080,12 @@ class ApplicationSerializer(serializers.Serializer):
instance['tts_model_enable'] = node_data['tts_model_enable'] instance['tts_model_enable'] = node_data['tts_model_enable']
if 'tts_type' in node_data: if 'tts_type' in node_data:
instance['tts_type'] = node_data['tts_type'] instance['tts_type'] = node_data['tts_type']
if 'tts_autoplay' in node_data:
instance['tts_autoplay'] = node_data['tts_autoplay']
if 'stt_autosend' in node_data:
instance['stt_autosend'] = node_data['stt_autosend']
if 'tts_model_params_setting' in node_data: if 'tts_model_params_setting' in node_data:
instance['tts_model_params_setting'] = node_data['tts_model_params_setting'] instance['tts_model_params_setting'] = node_data['tts_model_params_setting']
if 'file_upload_enable' in node_data: if 'file_upload_enable' in node_data:
instance['file_upload_enable'] = node_data['file_upload_enable'] instance['file_upload_enable'] = node_data['file_upload_enable']
if 'file_upload_setting' in node_data: if 'file_upload_setting' in node_data:
instance['file_upload_setting'] = node_data['file_upload_setting'] instance['file_upload_setting'] = node_data['file_upload_setting']
if 'name' in node_data:
instance['name'] = node_data['name']
break break
def speech_to_text(self, file, with_valid=True): def speech_to_text(self, file, with_valid=True):
@ -1217,9 +1155,7 @@ class ApplicationSerializer(serializers.Serializer):
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
if with_valid: if with_valid:
self.is_valid() self.is_valid()
embed_application = QuerySet(Application).filter(id=app_id).first() embed_application = QuerySet(Application).get(id=app_id)
if embed_application is None:
raise AppApiException(500, _('Application does not exist'))
if embed_application.type == ApplicationTypeChoices.WORK_FLOW: if embed_application.type == ApplicationTypeChoices.WORK_FLOW:
work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=embed_application.id).order_by( work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=embed_application.id).order_by(
'-create_time')[0:1].first() '-create_time')[0:1].first()
@ -1241,16 +1177,16 @@ class ApplicationSerializer(serializers.Serializer):
fields = "__all__" fields = "__all__"
class ApplicationKeySerializer(serializers.Serializer): class ApplicationKeySerializer(serializers.Serializer):
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
application_id = self.data.get("application_id") application_id = self.data.get("application_id")
application = QuerySet(Application).filter(id=application_id).first() application = QuerySet(Application).filter(id=application_id).first()
if application is None: if application is None:
raise AppApiException(1001, _("Application does not exist")) raise AppApiException(1001, "应用不存在")
def generate(self, with_valid=True): def generate(self, with_valid=True):
if with_valid: if with_valid:
@ -1272,22 +1208,21 @@ class ApplicationSerializer(serializers.Serializer):
QuerySet(ApplicationApiKey).filter(application_id=application_id)] QuerySet(ApplicationApiKey).filter(application_id=application_id)]
class Edit(serializers.Serializer): class Edit(serializers.Serializer):
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_("Availability"))) is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("是否可用"))
allow_cross_domain = serializers.BooleanField(required=False, allow_cross_domain = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean( error_messages=ErrMessage.boolean("是否允许跨域"))
_("Is cross-domain allowed")))
cross_domain_list = serializers.ListSerializer(required=False, cross_domain_list = serializers.ListSerializer(required=False,
child=serializers.CharField(required=True, child=serializers.CharField(required=True,
error_messages=ErrMessage.char( error_messages=ErrMessage.char(
_("Cross-domain address"))), "跨域列表")),
error_messages=ErrMessage.char(_("Cross-domain list"))) error_messages=ErrMessage.char("跨域地址"))
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
api_key_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("ApiKeyid"))) api_key_id = serializers.CharField(required=True, error_messages=ErrMessage.char("ApiKeyid"))
def delete(self, with_valid=True): def delete(self, with_valid=True):
if with_valid: if with_valid:
@ -1308,7 +1243,7 @@ class ApplicationSerializer(serializers.Serializer):
application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id, application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,
application_id=application_id).first() application_id=application_id).first()
if application_api_key is None: if application_api_key is None:
raise AppApiException(500, _('APIKey does not exist')) raise AppApiException(500, '不存在')
if 'is_active' in instance and instance.get('is_active') is not None: if 'is_active' in instance and instance.get('is_active') is not None:
application_api_key.is_active = instance.get('is_active') application_api_key.is_active = instance.get('is_active')
if 'allow_cross_domain' in instance and instance.get('allow_cross_domain') is not None: if 'allow_cross_domain' in instance and instance.get('allow_cross_domain') is not None:
@ -1318,29 +1253,3 @@ class ApplicationSerializer(serializers.Serializer):
application_api_key.save() application_api_key.save()
# 写入缓存 # 写入缓存
get_application_api_key(application_api_key.secret_key, False) get_application_api_key(application_api_key.secret_key, False)
class McpServers(serializers.Serializer):
mcp_servers = serializers.JSONField(required=True)
def get_mcp_servers(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
if '"stdio"' in self.data.get('mcp_servers'):
raise AppApiException(500, _('stdio is not supported'))
servers = json.loads(self.data.get('mcp_servers'))
async def get_mcp_tools(servers):
async with MultiServerMCPClient(servers) as client:
return client.get_tools()
tools = []
for server in servers:
tools += [
{
'server': server,
'name': tool.name,
'description': tool.description,
'args_schema': tool.args_schema,
}
for tool in asyncio.run(get_mcp_tools({server: servers[server]}))]
return tools

View File

@ -19,13 +19,12 @@ from common.db.search import native_search, get_dynamics_model
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from common.util.file_util import get_file_content from common.util.file_util import get_file_content
from smartdoc.conf import PROJECT_DIR from smartdoc.conf import PROJECT_DIR
from django.utils.translation import gettext_lazy as _
class ApplicationStatisticsSerializer(serializers.Serializer): class ApplicationStatisticsSerializer(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char("应用id"))
start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date(_("Start time"))) start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("开始时间"))
end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date(_("End time"))) end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("结束时间"))
def get_end_time(self): def get_end_time(self):
return datetime.datetime.combine( return datetime.datetime.combine(

View File

@ -9,7 +9,6 @@
from typing import Dict from typing import Dict
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.models import WorkFlowVersion from application.models import WorkFlowVersion
@ -27,14 +26,14 @@ class ApplicationVersionModelSerializer(serializers.ModelSerializer):
class ApplicationVersionEditSerializer(serializers.Serializer): class ApplicationVersionEditSerializer(serializers.Serializer):
name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True, name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Version Name"))) error_messages=ErrMessage.char("版本名称"))
class ApplicationVersionSerializer(serializers.Serializer): class ApplicationVersionSerializer(serializers.Serializer):
class Query(serializers.Serializer): class Query(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char("应用id"))
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, name = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("summary"))) error_messages=ErrMessage.char("摘要"))
def get_query_set(self): def get_query_set(self):
query_set = QuerySet(WorkFlowVersion).filter(application_id=self.data.get('application_id')) query_set = QuerySet(WorkFlowVersion).filter(application_id=self.data.get('application_id'))
@ -56,9 +55,8 @@ class ApplicationVersionSerializer(serializers.Serializer):
post_records_handler=lambda v: ApplicationVersionModelSerializer(v).data) post_records_handler=lambda v: ApplicationVersionModelSerializer(v).data)
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char("应用id"))
work_flow_version_id = serializers.UUIDField(required=True, work_flow_version_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("工作流版本id"))
error_messages=ErrMessage.uuid(_("Workflow version id")))
def one(self, with_valid=True): def one(self, with_valid=True):
if with_valid: if with_valid:
@ -68,7 +66,7 @@ class ApplicationVersionSerializer(serializers.Serializer):
if work_flow_version is not None: if work_flow_version is not None:
return ApplicationVersionModelSerializer(work_flow_version).data return ApplicationVersionModelSerializer(work_flow_version).data
else: else:
raise AppApiException(500, _('Workflow version does not exist')) raise AppApiException(500, '不存在的工作流版本')
def edit(self, instance: Dict, with_valid=True): def edit(self, instance: Dict, with_valid=True):
if with_valid: if with_valid:
@ -83,4 +81,4 @@ class ApplicationVersionSerializer(serializers.Serializer):
work_flow_version.save() work_flow_version.save()
return ApplicationVersionModelSerializer(work_flow_version).data return ApplicationVersionModelSerializer(work_flow_version).data
else: else:
raise AppApiException(500, _('Workflow version does not exist')) raise AppApiException(500, '不存在的工作流版本')

View File

@ -22,7 +22,6 @@ from application.chat_pipeline.step.generate_human_message_step.impl.base_genera
BaseGenerateHumanMessageStep BaseGenerateHumanMessageStep
from application.chat_pipeline.step.reset_problem_step.impl.base_reset_problem_step import BaseResetProblemStep from application.chat_pipeline.step.reset_problem_step.impl.base_reset_problem_step import BaseResetProblemStep
from application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset_step import BaseSearchDatasetStep from application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset_step import BaseSearchDatasetStep
from application.flow.common import Answer
from application.flow.i_step_node import WorkFlowPostHandler from application.flow.i_step_node import WorkFlowPostHandler
from application.flow.workflow_manage import WorkflowManage, Flow from application.flow.workflow_manage import WorkflowManage, Flow
from application.models import ChatRecord, Chat, Application, ApplicationDatasetMapping, ApplicationTypeChoices, \ from application.models import ChatRecord, Chat, Application, ApplicationDatasetMapping, ApplicationTypeChoices, \
@ -38,7 +37,6 @@ from common.util.split_model import flat_map
from dataset.models import Paragraph, Document from dataset.models import Paragraph, Document
from setting.models import Model, Status from setting.models import Model, Status
from setting.models_provider import get_model_credential from setting.models_provider import get_model_credential
from django.utils.translation import gettext_lazy as _
chat_cache = caches['chat_cache'] chat_cache = caches['chat_cache']
@ -95,8 +93,7 @@ class ChatInfo:
'chat_id': self.chat_id, 'chat_id': self.chat_id,
'dialogue_number': self.application.dialogue_number, 'dialogue_number': self.application.dialogue_number,
'problem_optimization_prompt': self.application.problem_optimization_prompt if self.application.problem_optimization_prompt is not None and len( 'problem_optimization_prompt': self.application.problem_optimization_prompt if self.application.problem_optimization_prompt is not None and len(
self.application.problem_optimization_prompt) > 0 else _( self.application.problem_optimization_prompt) > 0 else '()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中',
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag"),
'prompt': model_setting.get( 'prompt': model_setting.get(
'prompt') if 'prompt' in model_setting and len(model_setting.get( 'prompt') if 'prompt' in model_setting and len(model_setting.get(
'prompt')) > 0 else Application.get_default_model_prompt(), 'prompt')) > 0 else Application.get_default_model_prompt(),
@ -105,7 +102,6 @@ class ChatInfo:
'model_id': model_id, 'model_id': model_id,
'problem_optimization': self.application.problem_optimization, 'problem_optimization': self.application.problem_optimization,
'stream': True, 'stream': True,
'model_setting': model_setting,
'model_params_setting': model_params_setting if self.application.model_params_setting is None or len( 'model_params_setting': model_params_setting if self.application.model_params_setting is None or len(
self.application.model_params_setting.keys()) == 0 else self.application.model_params_setting, self.application.model_params_setting.keys()) == 0 else self.application.model_params_setting,
'search_mode': self.application.dataset_setting.get( 'search_mode': self.application.dataset_setting.get(
@ -116,15 +112,13 @@ class ChatInfo:
} }
def to_pipeline_manage_params(self, problem_text: str, post_response_handler: PostResponseHandler, def to_pipeline_manage_params(self, problem_text: str, post_response_handler: PostResponseHandler,
exclude_paragraph_id_list, client_id: str, client_type, stream=True, form_data=None): exclude_paragraph_id_list, client_id: str, client_type, stream=True):
if form_data is None:
form_data = {}
params = self.to_base_pipeline_manage_params() params = self.to_base_pipeline_manage_params()
return {**params, 'problem_text': problem_text, 'post_response_handler': post_response_handler, return {**params, 'problem_text': problem_text, 'post_response_handler': post_response_handler,
'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'client_id': client_id, 'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'client_id': client_id,
'client_type': client_type, 'form_data': form_data} 'client_type': client_type}
def append_chat_record(self, chat_record: ChatRecord, client_id=None, asker=None): def append_chat_record(self, chat_record: ChatRecord, client_id=None):
chat_record.problem_text = chat_record.problem_text[0:10240] if chat_record.problem_text is not None else "" chat_record.problem_text = chat_record.problem_text[0:10240] if chat_record.problem_text is not None else ""
chat_record.answer_text = chat_record.answer_text[0:40960] if chat_record.problem_text is not None else "" chat_record.answer_text = chat_record.answer_text[0:40960] if chat_record.problem_text is not None else ""
is_save = True is_save = True
@ -139,17 +133,8 @@ class ChatInfo:
if self.application.id is not None: if self.application.id is not None:
# 插入数据库 # 插入数据库
if not QuerySet(Chat).filter(id=self.chat_id).exists(): if not QuerySet(Chat).filter(id=self.chat_id).exists():
asker_dict = {'user_name': '游客'}
if asker is not None:
if isinstance(asker, str):
asker_dict = {
'user_name': asker
}
elif isinstance(asker, dict):
asker_dict = asker
Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text[0:1024], Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text[0:1024],
client_id=client_id, asker=asker_dict, update_time=datetime.now()).save() client_id=client_id, update_time=datetime.now()).save()
else: else:
Chat.objects.filter(id=self.chat_id).update(update_time=datetime.now()) Chat.objects.filter(id=self.chat_id).update(update_time=datetime.now())
# 插入会话记录 # 插入会话记录
@ -170,8 +155,6 @@ def get_post_handler(chat_info: ChatInfo):
padding_problem_text: str = None, padding_problem_text: str = None,
client_id=None, client_id=None,
**kwargs): **kwargs):
answer_list = [[Answer(answer_text, 'ai-chat-node', 'ai-chat-node', 'ai-chat-node', {}, 'ai-chat-node',
kwargs.get('reasoning_content', '')).to_dict()]]
chat_record = ChatRecord(id=chat_record_id, chat_record = ChatRecord(id=chat_record_id,
chat_id=chat_id, chat_id=chat_id,
problem_text=problem_text, problem_text=problem_text,
@ -179,11 +162,10 @@ def get_post_handler(chat_info: ChatInfo):
details=manage.get_details(), details=manage.get_details(),
message_tokens=manage.context['message_tokens'], message_tokens=manage.context['message_tokens'],
answer_tokens=manage.context['answer_tokens'], answer_tokens=manage.context['answer_tokens'],
answer_text_list=answer_list, answer_text_list=[answer_text],
run_time=manage.context['run_time'], run_time=manage.context['run_time'],
index=len(chat_info.chat_record_list) + 1) index=len(chat_info.chat_record_list) + 1)
asker = kwargs.get("asker", None) chat_info.append_chat_record(chat_record, client_id)
chat_info.append_chat_record(chat_record, client_id, asker=asker)
# 重新设置缓存 # 重新设置缓存
chat_cache.set(chat_id, chat_cache.set(chat_id,
chat_info, timeout=60 * 30) chat_info, timeout=60 * 30)
@ -192,42 +174,33 @@ def get_post_handler(chat_info: ChatInfo):
class OpenAIMessage(serializers.Serializer): class OpenAIMessage(serializers.Serializer):
content = serializers.CharField(required=True, error_messages=ErrMessage.char(_('content'))) content = serializers.CharField(required=True, error_messages=ErrMessage.char('内容'))
role = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Role'))) role = serializers.CharField(required=True, error_messages=ErrMessage.char('角色'))
class OpenAIInstanceSerializer(serializers.Serializer): class OpenAIInstanceSerializer(serializers.Serializer):
messages = serializers.ListField(child=OpenAIMessage()) messages = serializers.ListField(child=OpenAIMessage())
chat_id = serializers.UUIDField(required=False, error_messages=ErrMessage.char(_("Conversation ID"))) chat_id = serializers.UUIDField(required=False, error_messages=ErrMessage.char("对话id"))
re_chat = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_("Regenerate"))) re_chat = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("重新生成"))
stream = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_("Streaming Output"))) stream = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean("流式输出"))
class OpenAIChatSerializer(serializers.Serializer): class OpenAIChatSerializer(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client id"))) client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client Type"))) client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
@staticmethod @staticmethod
def get_message(instance): def get_message(instance):
return instance.get('messages')[-1].get('content') return instance.get('messages')[-1].get('content')
@staticmethod @staticmethod
def generate_chat(chat_id, application_id, message, client_id, asker=None): def generate_chat(chat_id, application_id, message, client_id):
if chat_id is None: if chat_id is None:
chat_id = str(uuid.uuid1()) chat_id = str(uuid.uuid1())
chat = QuerySet(Chat).filter(id=chat_id).first() chat = QuerySet(Chat).filter(id=chat_id).first()
if chat is None: if chat is None:
asker_dict = {'user_name': '游客'} Chat(id=chat_id, application_id=application_id, abstract=message[0:1024], client_id=client_id).save()
if asker is not None:
if isinstance(asker, str):
asker_dict = {
'user_name': asker
}
elif isinstance(asker, dict):
asker_dict = asker
Chat(id=chat_id, application_id=application_id, abstract=message[0:1024], client_id=client_id,
asker=asker_dict).save()
return chat_id return chat_id
def chat(self, instance: Dict, with_valid=True): def chat(self, instance: Dict, with_valid=True):
@ -241,53 +214,40 @@ class OpenAIChatSerializer(serializers.Serializer):
application_id = self.data.get('application_id') application_id = self.data.get('application_id')
client_id = self.data.get('client_id') client_id = self.data.get('client_id')
client_type = self.data.get('client_type') client_type = self.data.get('client_type')
chat_id = self.generate_chat(chat_id, application_id, message, client_id, chat_id = self.generate_chat(chat_id, application_id, message, client_id)
asker=instance.get('form_data', {}).get("asker"))
return ChatMessageSerializer( return ChatMessageSerializer(
data={ data={'chat_id': chat_id, 'message': message,
'chat_id': chat_id, 'message': message, 're_chat': re_chat,
're_chat': re_chat, 'stream': stream,
'stream': stream, 'application_id': application_id,
'application_id': application_id, 'client_id': client_id,
'client_id': client_id, 'client_type': client_type, 'form_data': instance.get('form_data', {})}).chat(
'client_type': client_type, base_to_response=OpenaiToResponse())
'form_data': instance.get('form_data', {}),
'image_list': instance.get('image_list', []),
'document_list': instance.get('document_list', []),
'audio_list': instance.get('audio_list', []),
'other_list': instance.get('other_list', []),
}
).chat(base_to_response=OpenaiToResponse())
class ChatMessageSerializer(serializers.Serializer): class ChatMessageSerializer(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
message = serializers.CharField(required=True, error_messages=ErrMessage.char(_("User Questions"))) message = serializers.CharField(required=True, error_messages=ErrMessage.char("用户问题"))
stream = serializers.BooleanField(required=True, stream = serializers.BooleanField(required=True, error_messages=ErrMessage.char("是否流式回答"))
error_messages=ErrMessage.char(_("Is the answer in streaming mode"))) re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.char("是否重新回答"))
re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.char(_("Do you want to reply again")))
chat_record_id = serializers.UUIDField(required=False, allow_null=True, chat_record_id = serializers.UUIDField(required=False, allow_null=True,
error_messages=ErrMessage.uuid(_("Conversation record id"))) error_messages=ErrMessage.uuid("对话记录id"))
node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Node id"))) error_messages=ErrMessage.char("节点id"))
runtime_node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, runtime_node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Runtime node id"))) error_messages=ErrMessage.char("运行时节点id"))
node_data = serializers.DictField(required=False, allow_null=True, node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.char("节点参数"))
error_messages=ErrMessage.char(_("Node parameters"))) application_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid("应用id"))
application_id = serializers.UUIDField(required=False, allow_null=True, client_id = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端id"))
error_messages=ErrMessage.uuid(_("Application ID"))) client_type = serializers.CharField(required=True, error_messages=ErrMessage.char("客户端类型"))
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client id"))) form_data = serializers.DictField(required=False, error_messages=ErrMessage.char("全局变量"))
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client Type"))) image_list = serializers.ListField(required=False, error_messages=ErrMessage.list("图片"))
form_data = serializers.DictField(required=False, error_messages=ErrMessage.char(_("Global variables"))) document_list = serializers.ListField(required=False, error_messages=ErrMessage.list("文档"))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list("音频"))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) child_node = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict("子节点"))
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio")))
other_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Other")))
child_node = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Child Nodes")))
def is_valid_application_workflow(self, *, raise_exception=False): def is_valid_application_workflow(self, *, raise_exception=False):
self.is_valid_intraday_access_num() self.is_valid_intraday_access_num()
@ -295,7 +255,7 @@ class ChatMessageSerializer(serializers.Serializer):
def is_valid_chat_id(self, chat_info: ChatInfo): def is_valid_chat_id(self, chat_info: ChatInfo):
if self.data.get('application_id') is not None and self.data.get('application_id') != str( if self.data.get('application_id') is not None and self.data.get('application_id') != str(
chat_info.application.id): chat_info.application.id):
raise ChatException(500, _("Conversation does not exist")) raise ChatException(500, "会话不存在")
def is_valid_intraday_access_num(self): def is_valid_intraday_access_num(self):
if self.data.get('client_type') == AuthenticationType.APPLICATION_ACCESS_TOKEN.value: if self.data.get('client_type') == AuthenticationType.APPLICATION_ACCESS_TOKEN.value:
@ -312,7 +272,7 @@ class ChatMessageSerializer(serializers.Serializer):
application_access_token = QuerySet(ApplicationAccessToken).filter( application_access_token = QuerySet(ApplicationAccessToken).filter(
application_id=self.data.get('application_id')).first() application_id=self.data.get('application_id')).first()
if application_access_token.access_num <= access_client.intraday_access_num: if application_access_token.access_num <= access_client.intraday_access_num:
raise AppChatNumOutOfBoundsFailed(1002, _("The number of visits exceeds today's visits")) raise AppChatNumOutOfBoundsFailed(1002, "访问次数超过今日访问量")
def is_valid_application_simple(self, *, chat_info: ChatInfo, raise_exception=False): def is_valid_application_simple(self, *, chat_info: ChatInfo, raise_exception=False):
self.is_valid_intraday_access_num() self.is_valid_intraday_access_num()
@ -323,9 +283,9 @@ class ChatMessageSerializer(serializers.Serializer):
if model is None: if model is None:
return chat_info return chat_info
if model.status == Status.ERROR: if model.status == Status.ERROR:
raise ChatException(500, _("The current model is not available")) raise ChatException(500, "当前模型不可用")
if model.status == Status.DOWNLOAD: if model.status == Status.DOWNLOAD:
raise ChatException(500, _("The model is downloading, please try again later")) raise ChatException(500, "模型正在下载中,请稍后再发起对话")
return chat_info return chat_info
def chat_simple(self, chat_info: ChatInfo, base_to_response): def chat_simple(self, chat_info: ChatInfo, base_to_response):
@ -334,7 +294,6 @@ class ChatMessageSerializer(serializers.Serializer):
stream = self.data.get('stream') stream = self.data.get('stream')
client_id = self.data.get('client_id') client_id = self.data.get('client_id')
client_type = self.data.get('client_type') client_type = self.data.get('client_type')
form_data = self.data.get("form_data")
pipeline_manage_builder = PipelineManage.builder() pipeline_manage_builder = PipelineManage.builder()
# 如果开启了问题优化,则添加上问题优化步骤 # 如果开启了问题优化,则添加上问题优化步骤
if chat_info.application.problem_optimization: if chat_info.application.problem_optimization:
@ -356,7 +315,7 @@ class ChatMessageSerializer(serializers.Serializer):
exclude_paragraph_id_list = list(set(paragraph_id_list)) exclude_paragraph_id_list = list(set(paragraph_id_list))
# 构建运行参数 # 构建运行参数
params = chat_info.to_pipeline_manage_params(message, get_post_handler(chat_info), exclude_paragraph_id_list, params = chat_info.to_pipeline_manage_params(message, get_post_handler(chat_info), exclude_paragraph_id_list,
client_id, client_type, stream, form_data) client_id, client_type, stream)
# 运行流水线作业 # 运行流水线作业
pipeline_message.run(params) pipeline_message.run(params)
return pipeline_message.context['chat_result'] return pipeline_message.context['chat_result']
@ -370,7 +329,7 @@ class ChatMessageSerializer(serializers.Serializer):
return chat_record_list[-1] return chat_record_list[-1]
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first() chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first()
if chat_record is None: if chat_record is None:
raise ChatException(500, _("Conversation record does not exist")) raise ChatException(500, "对话纪要不存在")
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first() chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first()
return chat_record return chat_record
@ -384,7 +343,6 @@ class ChatMessageSerializer(serializers.Serializer):
image_list = self.data.get('image_list') image_list = self.data.get('image_list')
document_list = self.data.get('document_list') document_list = self.data.get('document_list')
audio_list = self.data.get('audio_list') audio_list = self.data.get('audio_list')
other_list = self.data.get('other_list')
user_id = chat_info.application.user_id user_id = chat_info.application.user_id
chat_record_id = self.data.get('chat_record_id') chat_record_id = self.data.get('chat_record_id')
chat_record = None chat_record = None
@ -401,7 +359,7 @@ class ChatMessageSerializer(serializers.Serializer):
'client_id': client_id, 'client_id': client_id,
'client_type': client_type, 'client_type': client_type,
'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type), 'user_id': user_id}, WorkFlowPostHandler(chat_info, client_id, client_type),
base_to_response, form_data, image_list, document_list, audio_list, other_list, base_to_response, form_data, image_list, document_list, audio_list,
self.data.get('runtime_node_id'), self.data.get('runtime_node_id'),
self.data.get('node_data'), chat_record, self.data.get('child_node')) self.data.get('node_data'), chat_record, self.data.get('child_node'))
r = work_flow_manage.run() r = work_flow_manage.run()
@ -431,10 +389,10 @@ class ChatMessageSerializer(serializers.Serializer):
def re_open_chat(self, chat_id: str): def re_open_chat(self, chat_id: str):
chat = QuerySet(Chat).filter(id=chat_id).first() chat = QuerySet(Chat).filter(id=chat_id).first()
if chat is None: if chat is None:
raise ChatException(500, _("Conversation does not exist")) raise ChatException(500, "会话不存在")
application = QuerySet(Application).filter(id=chat.application_id).first() application = QuerySet(Application).filter(id=chat.application_id).first()
if application is None: if application is None:
raise ChatException(500, _("Application does not exist")) raise ChatException(500, "应用不存在")
if application.type == ApplicationTypeChoices.SIMPLE: if application.type == ApplicationTypeChoices.SIMPLE:
return self.re_open_chat_simple(chat_id, application) return self.re_open_chat_simple(chat_id, application)
else: else:
@ -464,7 +422,7 @@ class ChatMessageSerializer(serializers.Serializer):
work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application.id).order_by( work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application.id).order_by(
'-create_time')[0:1].first() '-create_time')[0:1].first()
if work_flow_version is None: if work_flow_version is None:
raise ChatException(500, _("The application has not been published. Please use it after publishing.")) raise ChatException(500, "应用未发布,请发布后再使用")
chat_info = ChatInfo(chat_id, [], [], application, work_flow_version) chat_info = ChatInfo(chat_id, [], [], application, work_flow_version)
chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5]) chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5])

View File

@ -13,17 +13,15 @@ import uuid
from functools import reduce from functools import reduce
from io import BytesIO from io import BytesIO
from typing import Dict from typing import Dict
import pytz
import openpyxl import openpyxl
from django.core import validators from django.core import validators
from django.core.cache import caches from django.core.cache import caches
from django.db import transaction, models from django.db import transaction, models
from django.db.models import QuerySet, Q from django.db.models import QuerySet, Q
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
from django.utils.translation import gettext_lazy as _, gettext
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
from rest_framework import serializers from rest_framework import serializers
from rest_framework.utils.formatting import lazy_format
from application.flow.workflow_manage import Flow from application.flow.workflow_manage import Flow
from application.models import Chat, Application, ApplicationDatasetMapping, VoteChoices, ChatRecord, WorkFlowVersion, \ from application.models import Chat, Application, ApplicationDatasetMapping, VoteChoices, ChatRecord, WorkFlowVersion, \
@ -46,14 +44,13 @@ from embedding.task import embedding_by_paragraph, embedding_by_paragraph_list
from setting.models import Model from setting.models import Model
from setting.models_provider import get_model_credential from setting.models_provider import get_model_credential
from smartdoc.conf import PROJECT_DIR from smartdoc.conf import PROJECT_DIR
from smartdoc.settings import TIME_ZONE
chat_cache = caches['chat_cache'] chat_cache = caches['chat_cache']
class WorkFlowSerializers(serializers.Serializer): class WorkFlowSerializers(serializers.Serializer):
nodes = serializers.ListSerializer(child=serializers.DictField(), error_messages=ErrMessage.uuid(_("node"))) nodes = serializers.ListSerializer(child=serializers.DictField(), error_messages=ErrMessage.uuid("节点"))
edges = serializers.ListSerializer(child=serializers.DictField(), error_messages=ErrMessage.uuid(_("Connection"))) edges = serializers.ListSerializer(child=serializers.DictField(), error_messages=ErrMessage.uuid("连线"))
def valid_model_params_setting(model_id, model_params_setting): def valid_model_params_setting(model_id, model_params_setting):
@ -67,14 +64,10 @@ def valid_model_params_setting(model_id, model_params_setting):
credential.get_model_params_setting_form(model.model_name).valid_form(model_params_setting) credential.get_model_params_setting_form(model.model_name).valid_form(model_params_setting)
class ReAbstractInstanceSerializers(serializers.Serializer):
abstract = serializers.CharField(required=True, error_messages=ErrMessage.char(_("abstract")))
class ChatSerializers(serializers.Serializer): class ChatSerializers(serializers.Serializer):
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
def logic_delete(self, with_valid=True): def logic_delete(self, with_valid=True):
if with_valid: if with_valid:
@ -83,15 +76,6 @@ class ChatSerializers(serializers.Serializer):
is_deleted=True) is_deleted=True)
return True return True
def re_abstract(self, instance, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
ReAbstractInstanceSerializers(data=instance).is_valid(raise_exception=True)
QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id')).update(
abstract=instance.get('abstract'))
return True
def delete(self, with_valid=True): def delete(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
@ -99,8 +83,8 @@ class ChatSerializers(serializers.Serializer):
return True return True
class ClientChatHistory(serializers.Serializer): class ClientChatHistory(serializers.Serializer):
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
client_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Client id"))) client_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("客户端id"))
def page(self, current_page: int, page_size: int, with_valid=True): def page(self, current_page: int, page_size: int, with_valid=True):
if with_valid: if with_valid:
@ -112,18 +96,18 @@ class ChatSerializers(serializers.Serializer):
return page_search(current_page, page_size, queryset, lambda row: ChatSerializerModel(row).data) return page_search(current_page, page_size, queryset, lambda row: ChatSerializerModel(row).data)
class Query(serializers.Serializer): class Query(serializers.Serializer):
abstract = serializers.CharField(required=False, error_messages=ErrMessage.char(_("summary"))) abstract = serializers.CharField(required=False, error_messages=ErrMessage.char("摘要"))
start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date(_("Start time"))) start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("开始时间"))
end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date(_("End time"))) end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("结束时间"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
min_star = serializers.IntegerField(required=False, min_value=0, min_star = serializers.IntegerField(required=False, min_value=0,
error_messages=ErrMessage.integer(_("Minimum number of likes"))) error_messages=ErrMessage.integer("最小点赞数"))
min_trample = serializers.IntegerField(required=False, min_value=0, min_trample = serializers.IntegerField(required=False, min_value=0,
error_messages=ErrMessage.integer(_("Minimum number of clicks"))) error_messages=ErrMessage.integer("最小点踩数"))
comparer = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Comparator")), validators=[ comparer = serializers.CharField(required=False, error_messages=ErrMessage.char("比较器"), validators=[
validators.RegexValidator(regex=re.compile("^and|or$"), validators.RegexValidator(regex=re.compile("^and|or$"),
message=_("Only supports and|or"), code=500) message="只支持and|or", code=500)
]) ])
def get_end_time(self): def get_end_time(self):
@ -174,14 +158,7 @@ class ChatSerializers(serializers.Serializer):
condition = base_condition & min_trample_query condition = base_condition & min_trample_query
else: else:
condition = base_condition condition = base_condition
inner_queryset = QuerySet(Chat).filter(application_id=self.data.get("application_id")) return query_set.filter(condition).order_by("-application_chat.update_time")
if 'abstract' in self.data and self.data.get('abstract') is not None:
inner_queryset = inner_queryset.filter(abstract__icontains=self.data.get('abstract'))
return {
'inner_queryset': inner_queryset,
'default_queryset': query_set.filter(condition).order_by("-application_chat.update_time")
}
def list(self, with_valid=True): def list(self, with_valid=True):
if with_valid: if with_valid:
@ -216,6 +193,7 @@ class ChatSerializers(serializers.Serializer):
[])) for [])) for
key, node in search_dataset_node_list]) key, node in search_dataset_node_list])
improve_paragraph_list = row.get('improve_paragraph_list') improve_paragraph_list = row.get('improve_paragraph_list')
vote_status_map = {'-1': '未投票', '0': '赞同', '1': '反对'} vote_status_map = {'-1': '未投票', '0': '赞同', '1': '反对'}
return [str(row.get('chat_id')), row.get('abstract'), row.get('problem_text'), padding_problem_text, return [str(row.get('chat_id')), row.get('abstract'), row.get('problem_text'), padding_problem_text,
row.get('answer_text'), vote_status_map.get(row.get('vote_status')), reference_paragraph_len, row.get('answer_text'), vote_status_map.get(row.get('vote_status')), reference_paragraph_len,
@ -223,9 +201,8 @@ class ChatSerializers(serializers.Serializer):
"\n".join([ "\n".join([
f"{improve_paragraph_list[index].get('title')}\n{improve_paragraph_list[index].get('content')}" f"{improve_paragraph_list[index].get('title')}\n{improve_paragraph_list[index].get('content')}"
for index in range(len(improve_paragraph_list))]), for index in range(len(improve_paragraph_list))]),
row.get('asker').get('user_name'),
row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'), row.get('message_tokens') + row.get('answer_tokens'), row.get('run_time'),
str(row.get('create_time').astimezone(pytz.timezone(TIME_ZONE)).strftime('%Y-%m-%d %H:%M:%S') str(row.get('create_time').strftime('%Y-%m-%d %H:%M:%S')
)] )]
def export(self, data, with_valid=True): def export(self, data, with_valid=True):
@ -245,14 +222,9 @@ class ChatSerializers(serializers.Serializer):
worksheet = workbook.active worksheet = workbook.active
worksheet.title = 'Sheet1' worksheet.title = 'Sheet1'
headers = [gettext('Conversation ID'), gettext('summary'), gettext('User Questions'), headers = ['会话ID', '摘要', '用户问题', '优化后问题', '回答', '用户反馈', '引用分段数',
gettext('Problem after optimization'), '分段标题+内容',
gettext('answer'), gettext('User feedback'), '标注', '消耗tokens', '耗时s', '提问时间']
gettext('Reference segment number'),
gettext('Section title + content'),
gettext('Annotation'), gettext('USER'), gettext('Consuming tokens'),
gettext('Time consumed (s)'),
gettext('Question Time')]
for col_idx, header in enumerate(headers, 1): for col_idx, header in enumerate(headers, 1):
cell = worksheet.cell(row=1, column=col_idx) cell = worksheet.cell(row=1, column=col_idx)
cell.value = header cell.value = header
@ -265,10 +237,6 @@ class ChatSerializers(serializers.Serializer):
cell = worksheet.cell(row=row_idx, column=col_idx) cell = worksheet.cell(row=row_idx, column=col_idx)
if isinstance(value, str): if isinstance(value, str):
value = re.sub(ILLEGAL_CHARACTERS_RE, '', value) value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)
if isinstance(value, datetime.datetime):
eastern = pytz.timezone(TIME_ZONE)
c = datetime.timezone(eastern._utcoffset)
value = value.astimezone(c)
cell.value = value cell.value = value
output = BytesIO() output = BytesIO()
@ -291,16 +259,16 @@ class ChatSerializers(serializers.Serializer):
with_table_name=False) with_table_name=False)
class OpenChat(serializers.Serializer): class OpenChat(serializers.Serializer):
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
user_id = self.data.get('user_id') user_id = self.data.get('user_id')
application_id = self.data.get('application_id') application_id = self.data.get('application_id')
if not QuerySet(Application).filter(id=application_id, user_id=user_id).exists(): if not QuerySet(Application).filter(id=application_id, user_id=user_id).exists():
raise AppApiException(500, gettext('Application does not exist')) raise AppApiException(500, '应用不存在')
def open(self): def open(self):
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
@ -318,9 +286,7 @@ class ChatSerializers(serializers.Serializer):
work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application_id).order_by( work_flow_version = QuerySet(WorkFlowVersion).filter(application_id=application_id).order_by(
'-create_time')[0:1].first() '-create_time')[0:1].first()
if work_flow_version is None: if work_flow_version is None:
raise AppApiException(500, raise AppApiException(500, "应用未发布,请发布后再使用")
gettext(
"The application has not been published. Please use it after publishing."))
chat_cache.set(chat_id, chat_cache.set(chat_id,
ChatInfo(chat_id, [], ChatInfo(chat_id, [],
[], [],
@ -343,8 +309,8 @@ class ChatSerializers(serializers.Serializer):
return chat_id return chat_id
class OpenWorkFlowChat(serializers.Serializer): class OpenWorkFlowChat(serializers.Serializer):
work_flow = WorkFlowSerializers(error_messages=ErrMessage.uuid(_("Workflow"))) work_flow = WorkFlowSerializers(error_messages=ErrMessage.uuid("工作流"))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
def open(self): def open(self):
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
@ -366,29 +332,26 @@ class ChatSerializers(serializers.Serializer):
return chat_id return chat_id
class OpenTempChat(serializers.Serializer): class OpenTempChat(serializers.Serializer):
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID"))) user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
id = serializers.UUIDField(required=False, allow_null=True, id = serializers.UUIDField(required=False, allow_null=True,
error_messages=ErrMessage.uuid(_("Application ID"))) error_messages=ErrMessage.uuid("应用id"))
model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
error_messages=ErrMessage.uuid(_("Model id"))) error_messages=ErrMessage.uuid("模型id"))
multiple_rounds_dialogue = serializers.BooleanField(required=True, multiple_rounds_dialogue = serializers.BooleanField(required=True,
error_messages=ErrMessage.boolean( error_messages=ErrMessage.boolean("多轮会话"))
_("Multi-round conversation")))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True), dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
error_messages=ErrMessage.list(_("Related Datasets"))) error_messages=ErrMessage.list("关联数据集"))
# 数据集相关设置 # 数据集相关设置
dataset_setting = DatasetSettingSerializer(required=True) dataset_setting = DatasetSettingSerializer(required=True)
# 模型相关设置 # 模型相关设置
model_setting = ModelSettingSerializer(required=True) model_setting = ModelSettingSerializer(required=True)
# 问题补全 # 问题补全
problem_optimization = serializers.BooleanField(required=True, problem_optimization = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("问题补全"))
error_messages=ErrMessage.boolean(_("Question completion")))
# 模型相关设置 # 模型相关设置
model_params_setting = serializers.JSONField(required=False, model_params_setting = serializers.JSONField(required=False, error_messages=ErrMessage.dict("模型参数相关设置"))
error_messages=ErrMessage.dict(_("Model parameter settings")))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
@ -402,7 +365,7 @@ class ChatSerializers(serializers.Serializer):
if 'id' in self.data and self.data.get('id') is not None: if 'id' in self.data and self.data.get('id') is not None:
application = QuerySet(Application).filter(id=self.data.get('id')).first() application = QuerySet(Application).filter(id=self.data.get('id')).first()
if application is None: if application is None:
raise AppApiException(500, gettext("Application does not exist")) raise AppApiException(500, "应用不存在")
return application.user_id return application.user_id
return self.data.get('user_id') return self.data.get('user_id')
@ -445,19 +408,18 @@ class ChatSerializerModel(serializers.ModelSerializer):
class ChatRecordSerializer(serializers.Serializer): class ChatRecordSerializer(serializers.Serializer):
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Application ID"))) application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
chat_record_id = serializers.UUIDField(required=True, chat_record_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话记录id"))
error_messages=ErrMessage.uuid(_("Conversation record id")))
def is_valid(self, *, current_role=None, raise_exception=False): def is_valid(self, *, current_role=None, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
application_access_token = QuerySet(ApplicationAccessToken).filter( application_access_token = QuerySet(ApplicationAccessToken).filter(
application_id=self.data.get('application_id')).first() application_id=self.data.get('application_id')).first()
if application_access_token is None: if application_access_token is None:
raise AppApiException(500, gettext('Application authentication information does not exist')) raise AppApiException(500, '不存在的应用认证信息')
if not application_access_token.show_source and current_role == RoleConstants.APPLICATION_ACCESS_TOKEN.value: if not application_access_token.show_source and current_role == RoleConstants.APPLICATION_ACCESS_TOKEN.value:
raise AppApiException(500, gettext('Displaying knowledge sources is not enabled')) raise AppApiException(500, '未开启显示知识来源')
def get_chat_record(self): def get_chat_record(self):
chat_record_id = self.data.get('chat_record_id') chat_record_id = self.data.get('chat_record_id')
@ -475,13 +437,13 @@ class ChatRecordSerializer(serializers.Serializer):
self.is_valid(current_role=current_role, raise_exception=True) self.is_valid(current_role=current_role, raise_exception=True)
chat_record = self.get_chat_record() chat_record = self.get_chat_record()
if chat_record is None: if chat_record is None:
raise AppApiException(500, gettext("Conversation does not exist")) raise AppApiException(500, "对话不存在")
return ChatRecordSerializer.Query.reset_chat_record(chat_record) return ChatRecordSerializer.Query.reset_chat_record(chat_record)
class Query(serializers.Serializer): class Query(serializers.Serializer):
application_id = serializers.UUIDField(required=True) application_id = serializers.UUIDField(required=True)
chat_id = serializers.UUIDField(required=True) chat_id = serializers.UUIDField(required=True)
order_asc = serializers.BooleanField(required=False, allow_null=True) order_asc = serializers.BooleanField(required=False)
def list(self, with_valid=True): def list(self, with_valid=True):
if with_valid: if with_valid:
@ -537,27 +499,23 @@ class ChatRecordSerializer(serializers.Serializer):
return page return page
class Vote(serializers.Serializer): class Vote(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
chat_record_id = serializers.UUIDField(required=True, chat_record_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话记录id"))
error_messages=ErrMessage.uuid(_("Conversation record id")))
vote_status = serializers.ChoiceField(choices=VoteChoices.choices, vote_status = serializers.ChoiceField(choices=VoteChoices.choices, error_messages=ErrMessage.uuid("投标状态"))
error_messages=ErrMessage.uuid(_("Bidding Status")))
@transaction.atomic @transaction.atomic
def vote(self, with_valid=True): def vote(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
if not try_lock(self.data.get('chat_record_id')): if not try_lock(self.data.get('chat_record_id')):
raise AppApiException(500, raise AppApiException(500, "正在对当前会话纪要进行投票中,请勿重复发送请求")
gettext(
"Voting on the current session minutes, please do not send repeated requests"))
try: try:
chat_record_details_model = QuerySet(ChatRecord).get(id=self.data.get('chat_record_id'), chat_record_details_model = QuerySet(ChatRecord).get(id=self.data.get('chat_record_id'),
chat_id=self.data.get('chat_id')) chat_id=self.data.get('chat_id'))
if chat_record_details_model is None: if chat_record_details_model is None:
raise AppApiException(500, gettext("Non-existent conversation chat_record_id")) raise AppApiException(500, "不存在的对话 chat_record_id")
vote_status = self.data.get("vote_status") vote_status = self.data.get("vote_status")
if chat_record_details_model.vote_status == VoteChoices.UN_VOTE: if chat_record_details_model.vote_status == VoteChoices.UN_VOTE:
if vote_status == VoteChoices.STAR: if vote_status == VoteChoices.STAR:
@ -574,18 +532,18 @@ class ChatRecordSerializer(serializers.Serializer):
chat_record_details_model.vote_status = VoteChoices.UN_VOTE chat_record_details_model.vote_status = VoteChoices.UN_VOTE
chat_record_details_model.save() chat_record_details_model.save()
else: else:
raise AppApiException(500, gettext("Already voted, please cancel first and then vote again")) raise AppApiException(500, "已经投票过,请先取消后再进行投票")
finally: finally:
un_lock(self.data.get('chat_record_id')) un_lock(self.data.get('chat_record_id'))
return True return True
class ImproveSerializer(serializers.Serializer): class ImproveSerializer(serializers.Serializer):
title = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True, title = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("Section title"))) error_messages=ErrMessage.char("段落标题"))
content = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Paragraph content"))) content = serializers.CharField(required=True, error_messages=ErrMessage.char("段落内容"))
problem_text = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True, problem_text = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,
error_messages=ErrMessage.char(_("question"))) error_messages=ErrMessage.char("问题"))
class ParagraphModel(serializers.ModelSerializer): class ParagraphModel(serializers.ModelSerializer):
class Meta: class Meta:
@ -593,10 +551,9 @@ class ChatRecordSerializer(serializers.Serializer):
fields = "__all__" fields = "__all__"
class ChatRecordImprove(serializers.Serializer): class ChatRecordImprove(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
chat_record_id = serializers.UUIDField(required=True, chat_record_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话记录id"))
error_messages=ErrMessage.uuid(_("Conversation record id")))
def get(self, with_valid=True): def get(self, with_valid=True):
if with_valid: if with_valid:
@ -605,7 +562,7 @@ class ChatRecordSerializer(serializers.Serializer):
chat_id = self.data.get('chat_id') chat_id = self.data.get('chat_id')
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()
if chat_record is None: if chat_record is None:
raise AppApiException(500, gettext('Conversation record does not exist')) raise AppApiException(500, '不存在的对话记录')
if chat_record.improve_paragraph_id_list is None or len(chat_record.improve_paragraph_id_list) == 0: if chat_record.improve_paragraph_id_list is None or len(chat_record.improve_paragraph_id_list) == 0:
return [] return []
@ -619,20 +576,19 @@ class ChatRecordSerializer(serializers.Serializer):
return [ChatRecordSerializer.ParagraphModel(p).data for p in paragraph_model_list] return [ChatRecordSerializer.ParagraphModel(p).data for p in paragraph_model_list]
class Improve(serializers.Serializer): class Improve(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
chat_record_id = serializers.UUIDField(required=True, chat_record_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话记录id"))
error_messages=ErrMessage.uuid(_("Conversation record id")))
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Knowledge base id"))) dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("知识库id"))
document_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Document id"))) document_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("文档id"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
if not QuerySet(Document).filter(id=self.data.get('document_id'), if not QuerySet(Document).filter(id=self.data.get('document_id'),
dataset_id=self.data.get('dataset_id')).exists(): dataset_id=self.data.get('dataset_id')).exists():
raise AppApiException(500, gettext("The document id is incorrect")) raise AppApiException(500, "文档id不正确")
@staticmethod @staticmethod
def post_embedding_paragraph(chat_record, paragraph_id, dataset_id): def post_embedding_paragraph(chat_record, paragraph_id, dataset_id):
@ -651,7 +607,7 @@ class ChatRecordSerializer(serializers.Serializer):
chat_id = self.data.get('chat_id') chat_id = self.data.get('chat_id')
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()
if chat_record is None: if chat_record is None:
raise AppApiException(500, gettext('Conversation record does not exist')) raise AppApiException(500, '不存在的对话记录')
document_id = self.data.get("document_id") document_id = self.data.get("document_id")
dataset_id = self.data.get("dataset_id") dataset_id = self.data.get("dataset_id")
@ -678,16 +634,15 @@ class ChatRecordSerializer(serializers.Serializer):
return ChatRecordSerializerModel(chat_record).data, paragraph.id, dataset_id return ChatRecordSerializerModel(chat_record).data, paragraph.id, dataset_id
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID"))) chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话id"))
chat_record_id = serializers.UUIDField(required=True, chat_record_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("对话记录id"))
error_messages=ErrMessage.uuid(_("Conversation record id")))
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Knowledge base id"))) dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("知识库id"))
document_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Document id"))) document_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("文档id"))
paragraph_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Paragraph id"))) paragraph_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("段落id"))
def delete(self, with_valid=True): def delete(self, with_valid=True):
if with_valid: if with_valid:
@ -700,12 +655,9 @@ class ChatRecordSerializer(serializers.Serializer):
paragraph_id = self.data.get('paragraph_id') paragraph_id = self.data.get('paragraph_id')
chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first() chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()
if chat_record is None: if chat_record is None:
raise AppApiException(500, gettext('Conversation record does not exist')) raise AppApiException(500, '不存在的对话记录')
if not chat_record.improve_paragraph_id_list.__contains__(uuid.UUID(paragraph_id)): if not chat_record.improve_paragraph_id_list.__contains__(uuid.UUID(paragraph_id)):
message = lazy_format( raise AppApiException(500, f'段落id错误,当前对话记录不存在【{paragraph_id}】段落id')
_('The paragraph id is wrong. The current conversation record does not exist. [{paragraph_id}] paragraph id'),
paragraph_id=paragraph_id)
raise AppApiException(500, message)
chat_record.improve_paragraph_id_list = [row for row in chat_record.improve_paragraph_id_list if chat_record.improve_paragraph_id_list = [row for row in chat_record.improve_paragraph_id_list if
str(row) != paragraph_id] str(row) != paragraph_id]
chat_record.save() chat_record.save()
@ -715,15 +667,15 @@ class ChatRecordSerializer(serializers.Serializer):
return o.delete() return o.delete()
class PostImprove(serializers.Serializer): class PostImprove(serializers.Serializer):
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Knowledge base id"))) dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("知识库id"))
document_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Document id"))) document_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("文档id"))
chat_ids = serializers.ListSerializer(child=serializers.UUIDField(), required=True, chat_ids = serializers.ListSerializer(child=serializers.UUIDField(), required=True,
error_messages=ErrMessage.list(_("Conversation ID"))) error_messages=ErrMessage.list("对话id"))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
if not Document.objects.filter(id=self.data['document_id'], dataset_id=self.data['dataset_id']).exists(): if not Document.objects.filter(id=self.data['document_id'], dataset_id=self.data['dataset_id']).exists():
raise AppApiException(500, gettext("The document id is incorrect")) raise AppApiException(500, "文档id不正确")
@staticmethod @staticmethod
def post_embedding_paragraph(paragraph_ids, dataset_id): def post_embedding_paragraph(paragraph_ids, dataset_id):
@ -742,7 +694,7 @@ class ChatRecordSerializer(serializers.Serializer):
# 获取所有聊天记录 # 获取所有聊天记录
chat_record_list = list(ChatRecord.objects.filter(chat_id__in=chat_ids)) chat_record_list = list(ChatRecord.objects.filter(chat_id__in=chat_ids))
if len(chat_record_list) < len(chat_ids): if len(chat_record_list) < len(chat_ids):
raise AppApiException(500, gettext("Conversation records that do not exist")) raise AppApiException(500, "存在不存在的对话记录")
# 批量创建段落和问题映射 # 批量创建段落和问题映射
paragraphs = [] paragraphs = []

View File

@ -10,8 +10,7 @@ SELECT
application_chat_record_temp."index" as "index", application_chat_record_temp."index" as "index",
application_chat_record_temp.improve_paragraph_list as improve_paragraph_list, application_chat_record_temp.improve_paragraph_list as improve_paragraph_list,
application_chat_record_temp.vote_status as vote_status, application_chat_record_temp.vote_status as vote_status,
application_chat_record_temp.create_time as create_time, application_chat_record_temp.create_time as create_time
to_json(application_chat.asker) as asker
FROM FROM
application_chat application_chat application_chat application_chat
LEFT JOIN ( LEFT JOIN (
@ -23,8 +22,6 @@ FROM
chat_id chat_id
FROM FROM
application_chat_record application_chat_record
WHERE chat_id IN (
SELECT id FROM application_chat ${inner_queryset})
GROUP BY GROUP BY
application_chat_record.chat_id application_chat_record.chat_id
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id ) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
@ -37,5 +34,4 @@ FROM
END as improve_paragraph_list END as improve_paragraph_list
FROM FROM
application_chat_record application_chat_record application_chat_record application_chat_record
) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id" ) application_chat_record_temp ON application_chat_record_temp.chat_id = application_chat."id"
${default_queryset}

View File

@ -1,5 +1,5 @@
SELECT SELECT
*,to_json(asker) as asker *
FROM FROM
application_chat application_chat application_chat application_chat
LEFT JOIN ( LEFT JOIN (
@ -11,9 +11,6 @@ FROM
chat_id chat_id
FROM FROM
application_chat_record application_chat_record
WHERE chat_id IN (
SELECT id FROM application_chat ${inner_queryset})
GROUP BY GROUP BY
application_chat_record.chat_id application_chat_record.chat_id
) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id ) chat_record_temp ON application_chat."id" = chat_record_temp.chat_id
${default_queryset}

View File

@ -9,7 +9,6 @@
from drf_yasg import openapi from drf_yasg import openapi
from common.mixins.api_mixin import ApiMixin from common.mixins.api_mixin import ApiMixin
from django.utils.translation import gettext_lazy as _
class ApplicationApi(ApiMixin): class ApplicationApi(ApiMixin):
@ -21,7 +20,7 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_FORM, in_=openapi.IN_FORM,
type=openapi.TYPE_FILE, type=openapi.TYPE_FILE,
required=True, required=True,
description=_('Upload files')) description='上传文件')
] ]
class Authentication(ApiMixin): class Authentication(ApiMixin):
@ -31,22 +30,12 @@ class ApplicationApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['access_token', ], required=['access_token', ],
properties={ properties={
'access_token': openapi.Schema(type=openapi.TYPE_STRING, 'access_token': openapi.Schema(type=openapi.TYPE_STRING, title="应用认证token",
title=_("Application authentication token"), description="应用认证token"),
description=_("Application authentication token")),
} }
) )
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_STRING,
title=_("Application authentication token"),
description=_("Application authentication token"),
default="token"
)
@staticmethod @staticmethod
def get_response_body_api(): def get_response_body_api():
return openapi.Schema( return openapi.Schema(
@ -54,35 +43,27 @@ class ApplicationApi(ApiMixin):
required=['id', 'name', 'desc', 'model_id', 'dialogue_number', 'user_id', 'status', 'create_time', required=['id', 'name', 'desc', 'model_id', 'dialogue_number', 'user_id', 'status', 'create_time',
'update_time'], 'update_time'],
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title="", description=_("Primary key id")), 'id': openapi.Schema(type=openapi.TYPE_STRING, title="", description="主键id"),
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"),
description=_("Application Name")), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Description"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
description=_("Application Description")), "dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, title="多轮对话次数",
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model id"), description=_("Model id")), description="多轮对话次数"),
"dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, 'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"),
title=_("Number of multi-round conversations"),
description=_("Number of multi-round conversations")),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title=_("Opening remarks"),
description=_("Opening remarks")),
'example': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING), 'example': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("Example List"), description=_("Example List")), title="示例列表", description="示例列表"),
'user_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Affiliation user"), 'user_id': openapi.Schema(type=openapi.TYPE_STRING, title="所属用户", description="所属用户"),
description=_("Affiliation user")),
'status': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is publish"), description=_('Is publish')), 'status': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否发布", description='是否发布'),
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Creation time"), 'create_time': openapi.Schema(type=openapi.TYPE_STRING, title="创建时间", description='创建时间'),
description=_('Creation time')),
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Modification time"), 'update_time': openapi.Schema(type=openapi.TYPE_STRING, title="修改时间", description='修改时间'),
description=_('Modification time')),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"), title="关联知识库Id列表",
description=_( description="关联知识库Id列表(查询详情的时候返回)")
"List of associated knowledge base IDs (returned when querying details)"))
} }
) )
@ -93,11 +74,11 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='model_type', in_=openapi.IN_QUERY, openapi.Parameter(name='model_type', in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False, required=False,
description=_('Model Type')), description='模型类型'),
] ]
class ApiKey(ApiMixin): class ApiKey(ApiMixin):
@ -107,7 +88,7 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')) description='应用id')
] ]
@ -118,12 +99,12 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='api_key_id', openapi.Parameter(name='api_key_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application api_key id')) description='应用api_key id')
] ]
@staticmethod @staticmethod
@ -132,33 +113,11 @@ class ApplicationApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=[], required=[],
properties={ properties={
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is activation"), 'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否激活",
description=_("Is activation")), description="是否激活"),
'allow_cross_domain': openapi.Schema(type=openapi.TYPE_BOOLEAN, 'allow_cross_domain': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否允许跨域",
title=_("Is cross-domain allowed"), description="是否允许跨域"),
description=_("Is cross-domain allowed")), 'cross_domain_list': openapi.Schema(type=openapi.TYPE_ARRAY, title='跨域列表',
'cross_domain_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_('Cross-domain list'),
items=openapi.Schema(type=openapi.TYPE_STRING))
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Primary key id"),
description=_("Primary key id")),
'secret_key': openapi.Schema(type=openapi.TYPE_STRING, title=_("Secret key"),
description=_("Secret key")),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is activation"),
description=_("Is activation")),
'application_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application ID"),
description=_("Application ID")),
'allow_cross_domain': openapi.Schema(type=openapi.TYPE_BOOLEAN,
title=_("Is cross-domain allowed"),
description=_("Is cross-domain allowed")),
'cross_domain_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_('Cross-domain list'),
items=openapi.Schema(type=openapi.TYPE_STRING)) items=openapi.Schema(type=openapi.TYPE_STRING))
} }
) )
@ -170,7 +129,7 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')) description='应用id')
] ]
@ -180,55 +139,18 @@ class ApplicationApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=[], required=[],
properties={ properties={
'access_token_reset': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Reset Token"), 'access_token_reset': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="重置Token",
description=_("Reset Token")), description="重置Token"),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is activation"), 'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否激活", description="是否激活"),
description=_("Is activation")), 'access_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="访问次数", description="访问次数"),
'access_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of visits"), 'white_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否开启白名单",
description=_("Number of visits")), description="是否开启白名单"),
'white_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Whether to enable whitelist"),
description=_("Whether to enable whitelist")),
'white_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'white_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), title=_("Whitelist"), items=openapi.Schema(type=openapi.TYPE_STRING), title="白名单列表",
description=_("Whitelist")), description="白名单列表"),
'show_source': openapi.Schema(type=openapi.TYPE_BOOLEAN, 'show_source': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否显示知识来源",
title=_("Whether to display knowledge sources"), description="是否显示知识来源"),
description=_("Whether to display knowledge sources")),
'language': openapi.Schema(type=openapi.TYPE_STRING,
title=_("language"),
description=_("language"))
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[],
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Primary key id"),
description=_("Primary key id")),
'access_token': openapi.Schema(type=openapi.TYPE_STRING, title=_("Access Token"),
description=_("Access Token")),
'access_token_reset': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Reset Token"),
description=_("Reset Token")),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is activation"),
description=_("Is activation")),
'access_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of visits"),
description=_("Number of visits")),
'white_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Whether to enable whitelist"),
description=_("Whether to enable whitelist")),
'white_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), title=_("Whitelist"),
description=_("Whitelist")),
'show_source': openapi.Schema(type=openapi.TYPE_BOOLEAN,
title=_("Whether to display knowledge sources"),
description=_("Whether to display knowledge sources")),
'language': openapi.Schema(type=openapi.TYPE_STRING,
title=_("language"),
description=_("language"))
} }
) )
@ -239,46 +161,37 @@ class ApplicationApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=[], required=[],
properties={ properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"),
description=_("Application Name")), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Description"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
description=_("Application Description")), "dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, title="多轮对话次数",
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model id"), description="多轮对话次数"),
description=_("Model id")), 'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"),
"dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER,
title=_("Number of multi-round conversations"),
description=_("Number of multi-round conversations")),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title=_("Opening remarks"),
description=_("Opening remarks")),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"), title="关联知识库Id列表", description="关联知识库Id列表"),
description=_("List of associated knowledge base IDs")),
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(), 'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(), 'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Problem Optimization"), 'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
description=_("Whether to enable problem optimization"), description="是否开启问题优化", default=True),
default=True),
'icon': openapi.Schema(type=openapi.TYPE_STRING, title="icon", 'icon': openapi.Schema(type=openapi.TYPE_STRING, title="icon",
description="icon", default="/ui/favicon.ico"), description="icon", default="/ui/favicon.ico"),
'type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Type"), 'type': openapi.Schema(type=openapi.TYPE_STRING, title="应用类型",
description=_("Application Type SIMPLE | WORK_FLOW")), description="应用类型 简易:SIMPLE|工作流:WORK_FLOW"),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api(), 'work_flow': ApplicationApi.WorkFlow.get_request_body_api(),
'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING, 'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING, title='问题优化提示词',
title=_('Question optimization tips'), description="问题优化提示词",
description=_("Question optimization tips"), default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中"),
default=_( 'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音模型ID",
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag")), description="文字转语音模型ID"),
'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech model ID"), 'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字模型id",
description=_("Text-to-speech model ID")), description="语音转文字模型id"),
'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Speech-to-text model id"), 'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description=_("Speech-to-text model id")), description="语音转文字是否开启"),
'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is speech-to-text enabled"), 'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description=_("Is speech-to-text enabled")), description="语音转文字是否开启"),
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is text-to-speech enabled"), 'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音类型",
description=_("Is text-to-speech enabled")), description="文字转语音类型")
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech type"),
description=_("Text-to-speech type"))
} }
) )
@ -291,11 +204,11 @@ class ApplicationApi(ApiMixin):
required=[''], required=[''],
properties={ properties={
'nodes': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT), 'nodes': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT),
title=_("Node List"), description=_("Node List"), title="节点列表", description="节点列表",
default=[]), default=[]),
'edges': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT), 'edges': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_OBJECT),
title=_('Connection List'), description=_("Connection List"), title='连线列表', description="连线列表",
default=[]), default={}),
} }
) )
@ -307,31 +220,24 @@ class ApplicationApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=[''], required=[''],
properties={ properties={
'top_n': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Reference segment number"), 'top_n': openapi.Schema(type=openapi.TYPE_NUMBER, title="引用分段数", description="引用分段数",
description=_("Reference segment number"),
default=5), default=5),
'similarity': openapi.Schema(type=openapi.TYPE_NUMBER, title=_('Similarity'), 'similarity': openapi.Schema(type=openapi.TYPE_NUMBER, title='相似度', description="相似度",
description=_("Similarity"),
default=0.6), default=0.6),
'max_paragraph_char_number': openapi.Schema(type=openapi.TYPE_NUMBER, 'max_paragraph_char_number': openapi.Schema(type=openapi.TYPE_NUMBER, title='最多引用字符数',
title=_('Maximum number of quoted characters'), description="最多引用字符数", default=3000),
description=_("Maximum number of quoted characters"), 'search_mode': openapi.Schema(type=openapi.TYPE_STRING, title='检索模式',
default=3000),
'search_mode': openapi.Schema(type=openapi.TYPE_STRING, title=_('Retrieval Mode'),
description="embedding|keywords|blend", default='embedding'), description="embedding|keywords|blend", default='embedding'),
'no_references_setting': openapi.Schema(type=openapi.TYPE_OBJECT, 'no_references_setting': openapi.Schema(type=openapi.TYPE_OBJECT, title='检索模式',
title=_('No reference segment settings'),
required=['status', 'value'], required=['status', 'value'],
properties={ properties={
'status': openapi.Schema(type=openapi.TYPE_STRING, 'status': openapi.Schema(type=openapi.TYPE_STRING,
title=_("state"), title="状态",
description=_( description="ai作答:ai_questioning,指定回答:designated_answer",
"ai_questioning|designated_answer"),
default='ai_questioning'), default='ai_questioning'),
'value': openapi.Schema(type=openapi.TYPE_STRING, 'value': openapi.Schema(type=openapi.TYPE_STRING,
title=_("value"), title="",
description=_( description="ai作答:就是题词,指定回答:就是指定回答内容",
"ai_questioning: is the title, designated_answer: is the designated answer content"),
default='{question}'), default='{question}'),
}), }),
} }
@ -344,26 +250,23 @@ class ApplicationApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['prompt'], required=['prompt'],
properties={ properties={
'prompt': openapi.Schema(type=openapi.TYPE_STRING, title=_("Prompt word"), 'prompt': openapi.Schema(type=openapi.TYPE_STRING, title="提示词", description="提示词",
description=_("Prompt word"), default=('已知信息:'
default=_(("Known information:\n" '\n{data}'
"{data}\n" '\n回答要求:'
"Answer requirements:\n" '\n- 如果你不知道答案或者没有从获取答案,请回答“没有在知识库中查找到相关信息,建议咨询相关技术支持或参考官方文档进行操作”。'
"- If you don't know the answer or don't get the answer, please answer \"No relevant information found in the knowledge base, it is recommended to consult relevant technical support or refer to official documents for operation\".\n" '\n- 避免提及你是从<data></data>中获得的知识。'
"- Avoid mentioning that you got the knowledge from <data></data>.\n" '\n- 请保持答案与<data></data>中描述的一致。'
"- Please keep the answer consistent with the description in <data></data>.\n" '\n- 请使用markdown 语法优化答案的格式。'
"- Please use markdown syntax to optimize the format of the answer.\n" '\n- <data></data>中的图片链接、链接地址和脚本语言请完整返回。'
"- Please return the image link, link address and script language in <data></data> completely.\n" '\n- 请使用与问题相同的语言来回答。'
"- Please answer in the same language as the question.\n" '\n问题:'
"Question:\n" '\n{question}')),
"{question}"))),
'system': openapi.Schema(type=openapi.TYPE_STRING, title=_("System prompt words (role)"), 'system': openapi.Schema(type=openapi.TYPE_STRING, title="系统提示词(角色)",
description=_("System prompt words (role)")), description="系统提示词(角色)"),
'no_references_prompt': openapi.Schema(type=openapi.TYPE_STRING, 'no_references_prompt': openapi.Schema(type=openapi.TYPE_STRING, title="无引用分段提示词",
title=_("No citation segmentation prompt"), default="{question}", description="无引用分段提示词")
default="{question}",
description=_("No citation segmentation prompt"))
} }
) )
@ -385,96 +288,36 @@ class ApplicationApi(ApiMixin):
return openapi.Schema( return openapi.Schema(
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting', required=['name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting',
'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type', 'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type'],
'work_flow'],
properties={ properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="应用名称", description="应用名称"),
description=_("Application Name")), 'desc': openapi.Schema(type=openapi.TYPE_STRING, title="应用描述", description="应用描述"),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Description"), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
description=_("Application Description")), "dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER, title="多轮对话次数",
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model id"), description="多轮对话次数"),
description=_("Model id")), 'prologue': openapi.Schema(type=openapi.TYPE_STRING, title="开场白", description="开场白"),
"dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER,
title=_("Number of multi-round conversations"),
description=_("Number of multi-round conversations")),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title=_("Opening remarks"),
description=_("Opening remarks")),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"), title="关联知识库Id列表", description="关联知识库Id列表"),
description=_("List of associated knowledge base IDs")),
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(), 'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(), 'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Problem Optimization"), 'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
description=_("Problem Optimization"), default=True), description="是否开启问题优化", default=True),
'type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Type"), 'type': openapi.Schema(type=openapi.TYPE_STRING, title="应用类型",
description=_("Application Type SIMPLE | WORK_FLOW")), description="应用类型 简易:SIMPLE|工作流:WORK_FLOW"),
'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING, 'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING, title='问题优化提示词',
title=_('Question optimization tips'), description="问题优化提示词",
description=_("Question optimization tips"), default="()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中"),
default=_( 'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音模型ID",
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag")), description="文字转语音模型ID"),
'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech model ID"), 'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字模型id",
description=_("Text-to-speech model ID")), description="语音转文字模型id"),
'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Speech-to-text model id"), 'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description=_("Speech-to-text model id")), description="语音转文字是否开启"),
'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is speech-to-text enabled"), 'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title="语音转文字是否开启",
description=_("Is speech-to-text enabled")), description="语音转文字是否开启"),
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is text-to-speech enabled"), 'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title="文字转语音类型",
description=_("Is text-to-speech enabled")), description="文字转语音类型")
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech type"),
description=_("Text-to-speech type")),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api(),
}
)
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['id', 'name', 'desc', 'model_id', 'dialogue_number', 'dataset_setting', 'model_setting',
'problem_optimization', 'stt_model_enable', 'stt_model_enable', 'tts_type',
'work_flow'],
properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Primary key id"),
description=_("Primary key id")),
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Name"),
description=_("Application Name")),
'desc': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Description"),
description=_("Application Description")),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model id"),
description=_("Model id")),
"dialogue_number": openapi.Schema(type=openapi.TYPE_NUMBER,
title=_("Number of multi-round conversations"),
description=_("Number of multi-round conversations")),
'prologue': openapi.Schema(type=openapi.TYPE_STRING, title=_("Opening remarks"),
description=_("Opening remarks")),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"),
description=_("List of associated knowledge base IDs")),
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Problem Optimization"),
description=_("Problem Optimization"), default=True),
'type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application Type"),
description=_("Application Type SIMPLE | WORK_FLOW")),
'problem_optimization_prompt': openapi.Schema(type=openapi.TYPE_STRING,
title=_('Question optimization tips'),
description=_("Question optimization tips"),
default=_(
"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag")),
'tts_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech model ID"),
description=_("Text-to-speech model ID")),
'stt_model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Speech-to-text model id"),
description=_("Speech-to-text model id")),
'stt_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is speech-to-text enabled"),
description=_("Is speech-to-text enabled")),
'tts_model_enable': openapi.Schema(type=openapi.TYPE_STRING, title=_("Is text-to-speech enabled"),
description=_("Is text-to-speech enabled")),
'tts_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text-to-speech type"),
description=_("Text-to-speech type")),
'work_flow': ApplicationApi.WorkFlow.get_request_body_api(),
} }
) )
@ -485,12 +328,12 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False, required=False,
description=_('Application Name')), description='应用名称'),
openapi.Parameter(name='desc', openapi.Parameter(name='desc',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False, required=False,
description=_('Application Description')) description='应用描述')
] ]
class Export(ApiMixin): class Export(ApiMixin):
@ -500,7 +343,7 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
] ]
@ -511,7 +354,7 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_FORM, in_=openapi.IN_FORM,
type=openapi.TYPE_FILE, type=openapi.TYPE_FILE,
required=True, required=True,
description=_('Upload image files')) description='上传图片文件')
] ]
class Operate(ApiMixin): class Operate(ApiMixin):
@ -521,28 +364,6 @@ class ApplicationApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
] ]
class TextToSpeech(ApiMixin):
@staticmethod
def get_request_params_api():
return [openapi.Parameter(name='application_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Application ID')),
]
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[],
properties={
'text': openapi.Schema(type=openapi.TYPE_STRING, title=_("Text"),
description=_("Text")),
}
)

View File

@ -9,7 +9,7 @@
from drf_yasg import openapi from drf_yasg import openapi
from common.mixins.api_mixin import ApiMixin from common.mixins.api_mixin import ApiMixin
from django.utils.translation import gettext_lazy as _
class ApplicationStatisticsApi(ApiMixin): class ApplicationStatisticsApi(ApiMixin):
@staticmethod @staticmethod
@ -18,17 +18,17 @@ class ApplicationStatisticsApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='start_time', openapi.Parameter(name='start_time',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Start time')), description='开始时间'),
openapi.Parameter(name='end_time', openapi.Parameter(name='end_time',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('End time')), description='结束时间'),
] ]
class ChatRecordAggregate(ApiMixin): class ChatRecordAggregate(ApiMixin):
@ -38,21 +38,21 @@ class ApplicationStatisticsApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['star_num', 'trample_num', 'tokens_num', 'chat_record_count'], required=['star_num', 'trample_num', 'tokens_num', 'chat_record_count'],
properties={ properties={
'star_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of Likes"), 'star_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点赞数量",
description=_("Number of Likes")), description="点赞数量"),
'trample_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of thumbs-downs"), description=_("Number of thumbs-downs")), 'trample_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点踩数量", description="点踩数量"),
'tokens_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of tokens used"), 'tokens_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="token使用数量",
description=_("Number of tokens used")), description="token使用数量"),
'chat_record_count': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of conversations"), 'chat_record_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="对话次数",
description=_("Number of conversations")), description="对话次数"),
'customer_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of customers"), 'customer_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="客户数量",
description=_("Number of customers")), description="客户数量"),
'customer_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of new customers"), 'customer_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="客户新增数量",
description=_("Number of new customers")), description="客户新增数量"),
'day': openapi.Schema(type=openapi.TYPE_STRING, 'day': openapi.Schema(type=openapi.TYPE_STRING,
title=_("time"), title="日期",
description=_("Time, this field is only available when querying trends")), description="日期,只有查询趋势的时候才有该字段"),
} }
) )
@ -63,11 +63,11 @@ class ApplicationStatisticsApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['added_count'], required=['added_count'],
properties={ properties={
'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("New quantity"), description=_("New quantity")), 'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="新增数量", description="新增数量"),
'day': openapi.Schema(type=openapi.TYPE_STRING, 'day': openapi.Schema(type=openapi.TYPE_STRING,
title=_("time"), title="时间",
description=_("time")), description="时间"),
} }
) )
@ -78,9 +78,9 @@ class ApplicationStatisticsApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['added_count'], required=['added_count'],
properties={ properties={
'today_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Today's new quantity"), 'today_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="今日新增数量",
description=_("Today's new quantity")), description="今日新增数量"),
'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("New quantity"), description=_("New quantity")), 'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="新增数量", description="新增数量"),
} }
) )

View File

@ -9,7 +9,6 @@
from drf_yasg import openapi from drf_yasg import openapi
from common.mixins.api_mixin import ApiMixin from common.mixins.api_mixin import ApiMixin
from django.utils.translation import gettext_lazy as _
class ApplicationVersionApi(ApiMixin): class ApplicationVersionApi(ApiMixin):
@ -19,16 +18,13 @@ class ApplicationVersionApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['id', 'name', 'work_flow', 'create_time', 'update_time'], required=['id', 'name', 'work_flow', 'create_time', 'update_time'],
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Primary key id"), 'id': openapi.Schema(type=openapi.TYPE_NUMBER, title="主键id",
description=_("Primary key id")), description="主键id"),
'name': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Version Name"), 'name': openapi.Schema(type=openapi.TYPE_NUMBER, title="版本名称",
description=_("Version Name")), description="版本名称"),
'work_flow': openapi.Schema(type=openapi.TYPE_STRING, title=_("Workflow data"), 'work_flow': openapi.Schema(type=openapi.TYPE_STRING, title="工作流数据", description='工作流数据'),
description=_('Workflow data')), 'create_time': openapi.Schema(type=openapi.TYPE_STRING, title="创建时间", description='创建时间'),
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Creation time"), 'update_time': openapi.Schema(type=openapi.TYPE_STRING, title="修改时间", description='修改时间')
description=_('Creation time')),
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Modification time"),
description=_('Modification time'))
} }
) )
@ -39,12 +35,12 @@ class ApplicationVersionApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='name', openapi.Parameter(name='name',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=False, required=False,
description=_('Version Name'))] description='版本名称')]
class Operate(ApiMixin): class Operate(ApiMixin):
@staticmethod @staticmethod
@ -53,12 +49,12 @@ class ApplicationVersionApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='work_flow_version_id', openapi.Parameter(name='work_flow_version_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application version id')), ] description='应用版本id'), ]
class Edit(ApiMixin): class Edit(ApiMixin):
@staticmethod @staticmethod
@ -67,7 +63,7 @@ class ApplicationVersionApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=[], required=[],
properties={ properties={
'name': openapi.Schema(type=openapi.TYPE_STRING, title=_("Version Name"), 'name': openapi.Schema(type=openapi.TYPE_STRING, title="版本名称",
description=_("Version Name")) description="版本名称")
} }
) )

View File

@ -10,7 +10,6 @@ from drf_yasg import openapi
from application.swagger_api.application_api import ApplicationApi from application.swagger_api.application_api import ApplicationApi
from common.mixins.api_mixin import ApiMixin from common.mixins.api_mixin import ApiMixin
from django.utils.translation import gettext_lazy as _
class ChatClientHistoryApi(ApiMixin): class ChatClientHistoryApi(ApiMixin):
@ -20,143 +19,31 @@ class ChatClientHistoryApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')) description='应用id')
] ]
class Operate(ApiMixin):
@staticmethod
def get_request_params_api():
return [openapi.Parameter(name='application_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Application ID')),
openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Conversation ID')),
]
class ReAbstract(ApiMixin):
@staticmethod
def get_request_body_api():
return openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['abstract'],
properties={
'abstract': openapi.Schema(type=openapi.TYPE_STRING, title=_("abstract"),
description=_("abstract"))
}
)
class OpenAIChatApi(ApiMixin): class OpenAIChatApi(ApiMixin):
@staticmethod
def get_response_body_api():
return openapi.Responses(responses={
200: openapi.Response(description=_('response parameters'),
schema=openapi.Schema(type=openapi.TYPE_OBJECT,
required=['id',
'choices'],
properties={
'id': openapi.Schema(
type=openapi.TYPE_STRING,
title=_(
"Conversation ID")),
'choices': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'message'],
properties={
'finish_reason': openapi.Schema(
type=openapi.TYPE_STRING, ),
'index': openapi.Schema(
type=openapi.TYPE_INTEGER),
'answer_list': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'content'],
properties={
'content': openapi.Schema(
type=openapi.TYPE_STRING),
'view_type': openapi.Schema(
type=openapi.TYPE_STRING),
'runtime_node_id': openapi.Schema(
type=openapi.TYPE_STRING),
'chat_record_id': openapi.Schema(
type=openapi.TYPE_STRING),
'reasoning_content': openapi.Schema(
type=openapi.TYPE_STRING),
}
)),
'message': openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'content'],
properties={
'content': openapi.Schema(
type=openapi.TYPE_STRING),
'role': openapi.Schema(
type=openapi.TYPE_STRING)
}),
}
)),
'created': openapi.Schema(
type=openapi.TYPE_INTEGER),
'model': openapi.Schema(
type=openapi.TYPE_STRING),
'object': openapi.Schema(
type=openapi.TYPE_STRING),
'usage': openapi.Schema(
type=openapi.TYPE_OBJECT,
required=[
'completion_tokens',
'prompt_tokens',
'total_tokens'],
properties={
'completion_tokens': openapi.Schema(
type=openapi.TYPE_INTEGER),
'prompt_tokens': openapi.Schema(
type=openapi.TYPE_INTEGER),
'total_tokens': openapi.Schema(
type=openapi.TYPE_INTEGER)
})
}))})
@staticmethod @staticmethod
def get_request_body_api(): def get_request_body_api():
return openapi.Schema(type=openapi.TYPE_OBJECT, return openapi.Schema(type=openapi.TYPE_OBJECT,
required=['message'], required=['message'],
properties={ properties={
'messages': openapi.Schema(type=openapi.TYPE_ARRAY, title=_("problem"), 'messages': openapi.Schema(type=openapi.TYPE_ARRAY, title="问题", description="问题",
description=_("problem"),
items=openapi.Schema(type=openapi.TYPE_OBJECT, items=openapi.Schema(type=openapi.TYPE_OBJECT,
required=['role', 'content'], required=['role', 'content'],
properties={ properties={
'content': openapi.Schema( 'content': openapi.Schema(
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
title=_("Question content"), title="问题内容", default=''),
default=''),
'role': openapi.Schema( 'role': openapi.Schema(
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
title=_('role'), title='角色', default="user")
default="user")
} }
)), )),
'chat_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Conversation ID")), 'chat_id': openapi.Schema(type=openapi.TYPE_STRING, title="对话id"),
're_chat': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("regenerate"), 're_chat': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="重新生成", default=False),
default=False), 'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="流式输出", default=True)
'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Stream Output"),
default=True)
}) })
@ -168,68 +55,9 @@ class ChatApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['message'], required=['message'],
properties={ properties={
'message': openapi.Schema(type=openapi.TYPE_STRING, title=_("problem"), description=_("problem")), 'message': openapi.Schema(type=openapi.TYPE_STRING, title="问题", description="问题"),
're_chat': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("regenerate"), default=False), 're_chat': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="重新生成", default=False),
'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Is it streaming output"), default=True), 'stream': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="重新生成", default=True)
'form_data': openapi.Schema(type=openapi.TYPE_OBJECT, title=_("Form data"),
description=_("Form data"),
default={}),
'image_list': openapi.Schema(
type=openapi.TYPE_ARRAY,
title=_("Image list"),
description=_("Image list"),
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'name': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Image name")),
'url': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Image URL")),
'file_id': openapi.Schema(type=openapi.TYPE_STRING),
}
),
default=[]
),
'document_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_("Document list"),
description=_("Document list"),
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
# 定义对象的具体属性
'name': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Document name")),
'url': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Document URL")),
'file_id': openapi.Schema(type=openapi.TYPE_STRING),
}
),
default=[]),
'audio_list': openapi.Schema(type=openapi.TYPE_ARRAY, title=_("Audio list"),
description=_("Audio list"),
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'name': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Audio name")),
'url': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Audio URL")),
'file_id': openapi.Schema(type=openapi.TYPE_STRING),
}
),
default=[]),
'runtime_node_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Runtime node id"),
description=_("Runtime node id"),
default=""),
'node_data': openapi.Schema(type=openapi.TYPE_OBJECT, title=_("Node data"),
description=_("Node data"),
default={}),
'chat_record_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Conversation record id"),
description=_("Conversation record id"),
default=""),
'child_node': openapi.Schema(type=openapi.TYPE_STRING, title=_("Child node"),
description=_("Child node"),
default={}),
} }
) )
@ -243,26 +71,26 @@ class ChatApi(ApiMixin):
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title="id", 'id': openapi.Schema(type=openapi.TYPE_STRING, title="id",
description="id", default="xx"), description="id", default="xx"),
'application_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application ID"), 'application_id': openapi.Schema(type=openapi.TYPE_STRING, title="应用id",
description=_("Application ID"), default=_('Application ID')), description="应用id", default='应用id'),
'abstract': openapi.Schema(type=openapi.TYPE_STRING, title=_("abstract"), 'abstract': openapi.Schema(type=openapi.TYPE_STRING, title="摘要",
description=_("abstract"), default=_('abstract')), description="摘要", default='摘要'),
'chat_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Conversation ID"), 'chat_id': openapi.Schema(type=openapi.TYPE_STRING, title="对话id",
description=_("Conversation ID"), default=_("Conversation ID")), description="对话id", default="对话id"),
'chat_record_count': openapi.Schema(type=openapi.TYPE_STRING, title=_("Number of dialogue questions"), 'chat_record_count': openapi.Schema(type=openapi.TYPE_STRING, title="对话提问数量",
description=_("Number of dialogue questions"), description="对话提问数量",
default=0), default="对话提问数量"),
'mark_sum': openapi.Schema(type=openapi.TYPE_STRING, title=_("Number of tags"), 'mark_sum': openapi.Schema(type=openapi.TYPE_STRING, title="标记数量",
description=_("Number of tags"), default=1), description="标记数量", default=1),
'star_num': openapi.Schema(type=openapi.TYPE_STRING, title=_("Number of likes"), 'star_num': openapi.Schema(type=openapi.TYPE_STRING, title="点赞数量",
description=_("Number of likes"), default=1), description="点赞数量", default=1),
'trample_num': openapi.Schema(type=openapi.TYPE_NUMBER, title=_("Number of clicks"), 'trample_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点踩数量",
description=_("Number of clicks"), default=1), description="点踩数量", default=1),
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Change time"), 'update_time': openapi.Schema(type=openapi.TYPE_STRING, title="修改时间",
description=_("Change time"), description="修改时间",
default="1970-01-01 00:00:00"), default="1970-01-01 00:00:00"),
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Creation time"), 'create_time': openapi.Schema(type=openapi.TYPE_STRING, title="创建时间",
description=_("Creation time"), description="创建时间",
default="1970-01-01 00:00:00" default="1970-01-01 00:00:00"
) )
} }
@ -275,7 +103,7 @@ class ChatApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
] ]
@ -298,64 +126,41 @@ class ChatApi(ApiMixin):
required=['model_id', 'multiple_rounds_dialogue', 'dataset_setting', 'model_setting', required=['model_id', 'multiple_rounds_dialogue', 'dataset_setting', 'model_setting',
'problem_optimization'], 'problem_optimization'],
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Application ID"), 'id': openapi.Schema(type=openapi.TYPE_STRING, title="应用id",
description=_( description="应用id,修改的时候传,创建的时候不传"),
"Application ID, pass when modifying, do not pass when creating")), 'model_id': openapi.Schema(type=openapi.TYPE_STRING, title="模型id", description="模型id"),
'model_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Model ID"),
description=_("Model ID")),
'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY, 'dataset_id_list': openapi.Schema(type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING), items=openapi.Schema(type=openapi.TYPE_STRING),
title=_("List of associated knowledge base IDs"), title="关联知识库Id列表", description="关联知识库Id列表"),
description=_("List of associated knowledge base IDs")), 'multiple_rounds_dialogue': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否开启多轮会话",
'multiple_rounds_dialogue': openapi.Schema(type=openapi.TYPE_BOOLEAN, description="是否开启多轮会话"),
title=_("Do you want to initiate multiple sessions"),
description=_(
"Do you want to initiate multiple sessions")),
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(), 'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(), 'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Problem optimization"), 'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
description=_("Do you want to enable problem optimization"), description="是否开启问题优化", default=True)
default=True)
} }
) )
@staticmethod
def get_response_body_api():
return openapi.Schema(
type=openapi.TYPE_STRING,
title=_("Conversation ID"),
description=_("Conversation ID"),
default="chat_id"
)
@staticmethod @staticmethod
def get_request_params_api(): def get_request_params_api():
return [openapi.Parameter(name='application_id', return [openapi.Parameter(name='application_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='history_day', openapi.Parameter(name='history_day',
in_=openapi.IN_QUERY, in_=openapi.IN_QUERY,
type=openapi.TYPE_NUMBER, type=openapi.TYPE_NUMBER,
required=True, required=True,
description=_('Historical days')), description='历史天数'),
openapi.Parameter(name='abstract', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False, openapi.Parameter(name='abstract', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False,
description=_("abstract")), description="摘要"),
openapi.Parameter(name='min_star', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False, openapi.Parameter(name='min_star', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False,
description=_("Minimum number of likes")), description="最小点赞数"),
openapi.Parameter(name='min_trample', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False, openapi.Parameter(name='min_trample', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False,
description=_("Minimum number of clicks")), description="最小点踩数"),
openapi.Parameter(name='comparer', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False, openapi.Parameter(name='comparer', in_=openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False,
description=_("or|and comparator")), description="or|and 比较器")
openapi.Parameter(name='start_time', in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING,
required=True,
description=_('start time')),
openapi.Parameter(name='end_time', in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING,
required=True,
description=_('End time')),
] ]
@ -366,17 +171,12 @@ class ChatRecordApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='chat_id', openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation ID')), description='对话id'),
openapi.Parameter(name='order_asc',
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
required=False,
description=_('Is it ascending order')),
] ]
@staticmethod @staticmethod
@ -389,39 +189,34 @@ class ChatRecordApi(ApiMixin):
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title="id", 'id': openapi.Schema(type=openapi.TYPE_STRING, title="id",
description="id", default="xx"), description="id", default="xx"),
'chat': openapi.Schema(type=openapi.TYPE_STRING, title=_("Session log id"), 'chat': openapi.Schema(type=openapi.TYPE_STRING, title="会话日志id",
description=_("Conversation log id"), default=_('Conversation log id')), description="会话日志id", default='会话日志id'),
'vote_status': openapi.Schema(type=openapi.TYPE_STRING, title=_("Voting Status"), 'vote_status': openapi.Schema(type=openapi.TYPE_STRING, title="投票状态",
description=_("Voting Status"), default=_("Voting Status")), description="投票状态", default="投票状态"),
'dataset': openapi.Schema(type=openapi.TYPE_STRING, title=_("Dataset id"), description=_("Dataset id"), 'dataset': openapi.Schema(type=openapi.TYPE_STRING, title="数据集id", description="数据集id",
default=_("Dataset id")), default="数据集id"),
'paragraph': openapi.Schema(type=openapi.TYPE_STRING, title=_("Paragraph id"), 'paragraph': openapi.Schema(type=openapi.TYPE_STRING, title="段落id",
description=_("Paragraph id"), default=1), description="段落id", default=1),
'source_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Resource ID"), 'source_id': openapi.Schema(type=openapi.TYPE_STRING, title="资源id",
description=_("Resource ID"), default=1), description="资源id", default=1),
'source_type': openapi.Schema(type=openapi.TYPE_STRING, title=_("Resource Type"), 'source_type': openapi.Schema(type=openapi.TYPE_STRING, title="资源类型",
description=_("Resource Type"), default='xxx'), description="资源类型", default='xxx'),
'message_tokens': openapi.Schema(type=openapi.TYPE_INTEGER, 'message_tokens': openapi.Schema(type=openapi.TYPE_INTEGER, title="问题消耗token数量",
title=_("Number of tokens consumed by the question"), description="问题消耗token数量", default=0),
description=_("Number of tokens consumed by the question"), default=0), 'answer_tokens': openapi.Schema(type=openapi.TYPE_INTEGER, title="答案消耗token数量",
'answer_tokens': openapi.Schema(type=openapi.TYPE_INTEGER, description="答案消耗token数量", default=0),
title=_("The number of tokens consumed by the answer"), 'improve_paragraph_id_list': openapi.Schema(type=openapi.TYPE_STRING, title="改进标注列表",
description=_("The number of tokens consumed by the answer"), description="改进标注列表",
default=0),
'improve_paragraph_id_list': openapi.Schema(type=openapi.TYPE_STRING,
title=_("Improved annotation list"),
description=_("Improved annotation list"),
default=[]), default=[]),
'index': openapi.Schema(type=openapi.TYPE_STRING, 'index': openapi.Schema(type=openapi.TYPE_STRING, title="对应会话 对应下标",
title=_("Corresponding session Corresponding subscript"), description="对应会话id对应下标",
description=_("Corresponding session id corresponding subscript"), default="对应会话id对应下标"
default=0
), ),
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Modification time"), 'update_time': openapi.Schema(type=openapi.TYPE_STRING, title="修改时间",
description=_("Modification time"), description="修改时间",
default="1970-01-01 00:00:00"), default="1970-01-01 00:00:00"),
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Creation time"), 'create_time': openapi.Schema(type=openapi.TYPE_STRING, title="创建时间",
description=_("Creation time"), description="创建时间",
default="1970-01-01 00:00:00" default="1970-01-01 00:00:00"
) )
} }
@ -435,27 +230,27 @@ class ImproveApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='chat_id', openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation ID')), description='会话id'),
openapi.Parameter(name='chat_record_id', openapi.Parameter(name='chat_record_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation record id')), description='会话记录id'),
openapi.Parameter(name='dataset_id', openapi.Parameter(name='dataset_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Knowledge base id')), description='知识库id'),
openapi.Parameter(name='document_id', openapi.Parameter(name='document_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Document id')), description='文档id'),
] ]
@staticmethod @staticmethod
@ -464,10 +259,10 @@ class ImproveApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['content'], required=['content'],
properties={ properties={
'title': openapi.Schema(type=openapi.TYPE_STRING, title=_("Section title"), 'title': openapi.Schema(type=openapi.TYPE_STRING, title="段落标题",
description=_("Section title")), description="段落标题"),
'content': openapi.Schema(type=openapi.TYPE_STRING, title=_("Paragraph content"), 'content': openapi.Schema(type=openapi.TYPE_STRING, title="段落内容",
description=_("Paragraph content")) description="段落内容")
} }
) )
@ -478,12 +273,12 @@ class ImproveApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['dataset_id', 'document_id', 'chat_ids'], required=['dataset_id', 'document_id', 'chat_ids'],
properties={ properties={
'dataset_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Knowledge base id"), 'dataset_id': openapi.Schema(type=openapi.TYPE_STRING, title="知识库id",
description=_("Knowledge base id")), description="知识库id"),
'document_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Document id"), 'document_id': openapi.Schema(type=openapi.TYPE_STRING, title="文档id",
description=_("Document id")), description="文档id"),
'chat_ids': openapi.Schema(type=openapi.TYPE_ARRAY, title=_("Conversation id list"), 'chat_ids': openapi.Schema(type=openapi.TYPE_ARRAY, title="会话id列表",
description=_("Conversation id list"), description="会话id列表",
items=openapi.Schema(type=openapi.TYPE_STRING)) items=openapi.Schema(type=openapi.TYPE_STRING))
} }
@ -495,12 +290,12 @@ class ImproveApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='dataset_id', openapi.Parameter(name='dataset_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Knowledge base id')), description='知识库id'),
] ]
@ -512,17 +307,17 @@ class VoteApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='chat_id', openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation ID')), description='会话id'),
openapi.Parameter(name='chat_record_id', openapi.Parameter(name='chat_record_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation record id')) description='会话记录id')
] ]
@staticmethod @staticmethod
@ -531,8 +326,8 @@ class VoteApi(ApiMixin):
type=openapi.TYPE_OBJECT, type=openapi.TYPE_OBJECT,
required=['vote_status'], required=['vote_status'],
properties={ properties={
'vote_status': openapi.Schema(type=openapi.TYPE_STRING, title=_("Voting Status"), 'vote_status': openapi.Schema(type=openapi.TYPE_STRING, title="投票状态",
description=_("-1: Cancel vote | 0: Agree | 1: Oppose")), description="-1:取消投票|0:赞同|1:反对"),
} }
) )
@ -545,17 +340,17 @@ class ChatRecordImproveApi(ApiMixin):
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Application ID')), description='应用id'),
openapi.Parameter(name='chat_id', openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation ID')), description='会话id'),
openapi.Parameter(name='chat_record_id', openapi.Parameter(name='chat_record_id',
in_=openapi.IN_PATH, in_=openapi.IN_PATH,
type=openapi.TYPE_STRING, type=openapi.TYPE_STRING,
required=True, required=True,
description=_('Conversation record id')) description='会话记录id')
] ]
@staticmethod @staticmethod
@ -568,28 +363,27 @@ class ChatRecordImproveApi(ApiMixin):
properties={ properties={
'id': openapi.Schema(type=openapi.TYPE_STRING, title="id", 'id': openapi.Schema(type=openapi.TYPE_STRING, title="id",
description="id", default="xx"), description="id", default="xx"),
'content': openapi.Schema(type=openapi.TYPE_STRING, title=_("Paragraph content"), 'content': openapi.Schema(type=openapi.TYPE_STRING, title="段落内容",
description=_("Paragraph content"), default=_('Paragraph content')), description="段落内容", default='段落内容'),
'title': openapi.Schema(type=openapi.TYPE_STRING, title=_("title"), 'title': openapi.Schema(type=openapi.TYPE_STRING, title="标题",
description=_("title"), default=_("Description of xxx")), description="标题", default="xxx的描述"),
'hit_num': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of hits"), 'hit_num': openapi.Schema(type=openapi.TYPE_INTEGER, title="命中数量", description="命中数量",
description=_("Number of hits"),
default=1), default=1),
'star_num': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of Likes"), 'star_num': openapi.Schema(type=openapi.TYPE_INTEGER, title="点赞数量",
description=_("Number of Likes"), default=1), description="点赞数量", default=1),
'trample_num': openapi.Schema(type=openapi.TYPE_INTEGER, title=_("Number of thumbs-downs"), 'trample_num': openapi.Schema(type=openapi.TYPE_INTEGER, title="点踩数量",
description=_("Number of thumbs-downs"), default=1), description="点踩数", default=1),
'dataset_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Knowledge base id"), 'dataset_id': openapi.Schema(type=openapi.TYPE_STRING, title="知识库id",
description=_("Knowledge base id"), default='xxx'), description="知识库id", default='xxx'),
'document_id': openapi.Schema(type=openapi.TYPE_STRING, title=_("Document id"), 'document_id': openapi.Schema(type=openapi.TYPE_STRING, title="文档id",
description=_("Document id"), default='xxx'), description="文档id", default='xxx'),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title=_("Availability"), 'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="是否可用",
description=_("Availability"), default=True), description="是否可用", default=True),
'update_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Modification time"), 'update_time': openapi.Schema(type=openapi.TYPE_STRING, title="修改时间",
description=_("Modification time"), description="修改时间",
default="1970-01-01 00:00:00"), default="1970-01-01 00:00:00"),
'create_time': openapi.Schema(type=openapi.TYPE_STRING, title=_("Creation time"), 'create_time': openapi.Schema(type=openapi.TYPE_STRING, title="创建时间",
description=_("Creation time"), description="创建时间",
default="1970-01-01 00:00:00" default="1970-01-01 00:00:00"
) )
} }

View File

@ -9,7 +9,6 @@ urlpatterns = [
path('application/profile', views.Application.Profile.as_view(), name='application/profile'), path('application/profile', views.Application.Profile.as_view(), name='application/profile'),
path('application/embed', views.Application.Embed.as_view()), path('application/embed', views.Application.Embed.as_view()),
path('application/authentication', views.Application.Authentication.as_view()), path('application/authentication', views.Application.Authentication.as_view()),
path('application/mcp_servers', views.Application.McpServers.as_view()),
path('application/<str:application_id>/publish', views.Application.Publish.as_view()), path('application/<str:application_id>/publish', views.Application.Publish.as_view()),
path('application/<str:application_id>/edit_icon', views.Application.EditIcon.as_view()), path('application/<str:application_id>/edit_icon', views.Application.EditIcon.as_view()),
path('application/<str:application_id>/export', views.Application.Export.as_view()), path('application/<str:application_id>/export', views.Application.Export.as_view()),

View File

@ -13,24 +13,21 @@ from rest_framework.views import APIView
from application.serializers.application_version_serializers import ApplicationVersionSerializer from application.serializers.application_version_serializers import ApplicationVersionSerializer
from application.swagger_api.application_version_api import ApplicationVersionApi from application.swagger_api.application_version_api import ApplicationVersionApi
from application.views import get_application_operation_object
from common.auth import has_permissions, TokenAuth from common.auth import has_permissions, TokenAuth
from common.constants.permission_constants import PermissionConstants, CompareConstants, ViewPermission, RoleConstants, \ from common.constants.permission_constants import PermissionConstants, CompareConstants, ViewPermission, RoleConstants, \
Permission, Group, Operate Permission, Group, Operate
from common.log.log import log
from common.response import result from common.response import result
from django.utils.translation import gettext_lazy as _
class ApplicationVersionView(APIView): class ApplicationVersionView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the application list"), @swagger_auto_schema(operation_summary="获取应用列表",
operation_id=_("Get the application list"), operation_id="获取应用列表",
manual_parameters=ApplicationVersionApi.Query.get_request_params_api(), manual_parameters=ApplicationVersionApi.Query.get_request_params_api(),
responses=result.get_api_array_response(ApplicationVersionApi.get_response_body_api()), responses=result.get_api_array_response(ApplicationVersionApi.get_response_body_api()),
tags=[_('Application/Version')]) tags=['应用/版本'])
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND) @has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request, application_id: str): def get(self, request: Request, application_id: str):
return result.success( return result.success(
@ -42,12 +39,12 @@ class ApplicationVersionView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the list of application versions by page"), @swagger_auto_schema(operation_summary="分页获取应用版本列表",
operation_id=_("Get the list of application versions by page"), operation_id="分页获取应用版本列表",
manual_parameters=result.get_page_request_params( manual_parameters=result.get_page_request_params(
ApplicationVersionApi.Query.get_request_params_api()), ApplicationVersionApi.Query.get_request_params_api()),
responses=result.get_page_api_response(ApplicationVersionApi.get_response_body_api()), responses=result.get_page_api_response(ApplicationVersionApi.get_response_body_api()),
tags=[_('Application/Version')]) tags=['应用/版本'])
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND) @has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request, application_id: str, current_page: int, page_size: int): def get(self, request: Request, application_id: str, current_page: int, page_size: int):
return result.success( return result.success(
@ -60,11 +57,11 @@ class ApplicationVersionView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get application version details"), @swagger_auto_schema(operation_summary="获取应用版本详情",
operation_id=_("Get application version details"), operation_id="获取应用版本详情",
manual_parameters=ApplicationVersionApi.Operate.get_request_params_api(), manual_parameters=ApplicationVersionApi.Operate.get_request_params_api(),
responses=result.get_api_response(ApplicationVersionApi.get_response_body_api()), responses=result.get_api_response(ApplicationVersionApi.get_response_body_api()),
tags=[_('Application/Version')]) tags=['应用/版本'])
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND) @has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request, application_id: str, work_flow_version_id: str): def get(self, request: Request, application_id: str, work_flow_version_id: str):
return result.success( return result.success(
@ -73,19 +70,17 @@ class ApplicationVersionView(APIView):
'application_id': application_id, 'work_flow_version_id': work_flow_version_id}).one()) 'application_id': application_id, 'work_flow_version_id': work_flow_version_id}).one())
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Modify application version information"), @swagger_auto_schema(operation_summary="修改应用版本信息",
operation_id=_("Modify application version information"), operation_id="修改应用版本信息",
manual_parameters=ApplicationVersionApi.Operate.get_request_params_api(), manual_parameters=ApplicationVersionApi.Operate.get_request_params_api(),
request_body=ApplicationVersionApi.Edit.get_request_body_api(), request_body=ApplicationVersionApi.Edit.get_request_body_api(),
responses=result.get_api_response(ApplicationVersionApi.get_response_body_api()), responses=result.get_api_response(ApplicationVersionApi.get_response_body_api()),
tags=[_('Application/Version')]) tags=['应用/版本'])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND)) compare=CompareConstants.AND))
@log(menu='Application', operate="Modify application version information",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, work_flow_version_id: str): def put(self, request: Request, application_id: str, work_flow_version_id: str):
return result.success( return result.success(
ApplicationVersionSerializer.Operate( ApplicationVersionSerializer.Operate(

View File

@ -9,9 +9,7 @@
from django.core import cache from django.core import cache
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _, gettext
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from langchain_core.prompts import PromptTemplate
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request from rest_framework.request import Request
@ -21,12 +19,10 @@ from application.serializers.application_serializers import ApplicationSerialize
from application.serializers.application_statistics_serializers import ApplicationStatisticsSerializer from application.serializers.application_statistics_serializers import ApplicationStatisticsSerializer
from application.swagger_api.application_api import ApplicationApi from application.swagger_api.application_api import ApplicationApi
from application.swagger_api.application_statistics_api import ApplicationStatisticsApi from application.swagger_api.application_statistics_api import ApplicationStatisticsApi
from application.views.common import get_application_operation_object
from common.auth import TokenAuth, has_permissions from common.auth import TokenAuth, has_permissions
from common.constants.permission_constants import CompareConstants, PermissionConstants, Permission, Group, Operate, \ from common.constants.permission_constants import CompareConstants, PermissionConstants, Permission, Group, Operate, \
ViewPermission, RoleConstants ViewPermission, RoleConstants
from common.exception.app_exception import AppAuthenticationFailed from common.exception.app_exception import AppAuthenticationFailed
from common.log.log import log
from common.response import result from common.response import result
from common.swagger_api.common_api import CommonApi from common.swagger_api.common_api import CommonApi
from common.util.common import query_params_to_single_dict from common.util.common import query_params_to_single_dict
@ -40,9 +36,9 @@ class ApplicationStatistics(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("User Statistics"), @swagger_auto_schema(operation_summary="用户统计",
operation_id=_("User Statistics"), operation_id="用户统计",
tags=[_("Application/Statistics")], tags=["应用/统计"],
manual_parameters=ApplicationStatisticsApi.get_request_params_api(), manual_parameters=ApplicationStatisticsApi.get_request_params_api(),
responses=result.get_api_response( responses=result.get_api_response(
ApplicationStatisticsApi.CustomerCount.get_response_body_api()) ApplicationStatisticsApi.CustomerCount.get_response_body_api())
@ -65,9 +61,9 @@ class ApplicationStatistics(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("User demographic trends"), @swagger_auto_schema(operation_summary="用户统计趋势",
operation_id=_("User demographic trends"), operation_id="用户统计趋势",
tags=[_("Application/Statistics")], tags=["应用/统计"],
manual_parameters=ApplicationStatisticsApi.get_request_params_api(), manual_parameters=ApplicationStatisticsApi.get_request_params_api(),
responses=result.get_api_array_response( responses=result.get_api_array_response(
ApplicationStatisticsApi.CustomerCountTrend.get_response_body_api())) ApplicationStatisticsApi.CustomerCountTrend.get_response_body_api()))
@ -89,9 +85,9 @@ class ApplicationStatistics(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Conversation statistics"), @swagger_auto_schema(operation_summary="对话相关统计",
operation_id=_("Conversation statistics"), operation_id="对话相关统计",
tags=[_("Application/Statistics")], tags=["应用/统计"],
manual_parameters=ApplicationStatisticsApi.get_request_params_api(), manual_parameters=ApplicationStatisticsApi.get_request_params_api(),
responses=result.get_api_response( responses=result.get_api_response(
ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api()) ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api())
@ -114,9 +110,9 @@ class ApplicationStatistics(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Dialogue-related statistical trends"), @swagger_auto_schema(operation_summary="对话相关统计趋势",
operation_id=_("Dialogue-related statistical trends"), operation_id="对话相关统计趋势",
tags=[_("Application/Statistics")], tags=["应用/统计"],
manual_parameters=ApplicationStatisticsApi.get_request_params_api(), manual_parameters=ApplicationStatisticsApi.get_request_params_api(),
responses=result.get_api_array_response( responses=result.get_api_array_response(
ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api()) ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api())
@ -144,9 +140,9 @@ class Application(APIView):
parser_classes = [MultiPartParser] parser_classes = [MultiPartParser]
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Modify application icon"), @swagger_auto_schema(operation_summary="修改应用icon",
operation_id=_("Modify application icon"), operation_id="修改应用icon",
tags=[_('Application')], tags=['应用'],
manual_parameters=ApplicationApi.EditApplicationIcon.get_request_params_api(), manual_parameters=ApplicationApi.EditApplicationIcon.get_request_params_api(),
request_body=ApplicationApi.Operate.get_request_body_api()) request_body=ApplicationApi.Operate.get_request_body_api())
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
@ -155,8 +151,6 @@ class Application(APIView):
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT, compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT,
compare=CompareConstants.AND) compare=CompareConstants.AND)
@log(menu='Application', operate="Modify application icon",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str): def put(self, request: Request, application_id: str):
return result.success( return result.success(
ApplicationSerializer.IconOperate( ApplicationSerializer.IconOperate(
@ -167,13 +161,12 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser] parser_classes = [MultiPartParser]
@action(methods="POST", detail=False) @action(methods="GET", detail=False)
@swagger_auto_schema(operation_summary=_("Import Application"), operation_id=_("Import Application"), @swagger_auto_schema(operation_summary="导入应用", operation_id="导入应用",
manual_parameters=ApplicationApi.Import.get_request_params_api(), manual_parameters=ApplicationApi.Import.get_request_params_api(),
tags=[_("Application")] tags=["应用"]
) )
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER) @has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
@log(menu='Application', operate="Import Application")
def post(self, request: Request): def post(self, request: Request):
return result.success(ApplicationSerializer.Import( return result.success(ApplicationSerializer.Import(
data={'user_id': request.user.id, 'file': request.FILES.get('file')}).import_()) data={'user_id': request.user.id, 'file': request.FILES.get('file')}).import_())
@ -182,23 +175,21 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods="GET", detail=False) @action(methods="GET", detail=False)
@swagger_auto_schema(operation_summary=_("Export Application"), operation_id=_("Export Application"), @swagger_auto_schema(operation_summary="导出应用", operation_id="导出应用",
manual_parameters=ApplicationApi.Export.get_request_params_api(), manual_parameters=ApplicationApi.Export.get_request_params_api(),
tags=[_("Application")] tags=["应用"]
) )
@has_permissions(lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, @has_permissions(lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))) dynamic_tag=keywords.get('application_id')))
@log(menu='Application', operate="Export Application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def get(self, request: Request, application_id: str): def get(self, request: Request, application_id: str):
return ApplicationSerializer.Operate( return ApplicationSerializer.Operate(
data={'application_id': application_id, 'user_id': request.user.id}).export() data={'application_id': application_id, 'user_id': request.user.id}).export()
class Embed(APIView): class Embed(APIView):
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Get embedded js"), @swagger_auto_schema(operation_summary="获取嵌入js",
operation_id=_("Get embedded js"), operation_id="获取嵌入js",
tags=[_("Application")], tags=["应用"],
manual_parameters=ApplicationApi.ApiKey.get_request_params_api()) manual_parameters=ApplicationApi.ApiKey.get_request_params_api())
def get(self, request: Request): def get(self, request: Request):
return ApplicationSerializer.Embed( return ApplicationSerializer.Embed(
@ -209,9 +200,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Get a list of models"), @swagger_auto_schema(operation_summary="获取模型列表",
operation_id=_("Get a list of models"), operation_id="获取模型列表",
tags=[_("Application")], tags=["应用"],
manual_parameters=ApplicationApi.Model.get_request_params_api()) manual_parameters=ApplicationApi.Model.get_request_params_api())
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
@ -228,9 +219,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get model parameter form"), @swagger_auto_schema(operation_summary="获取模型参数表单",
operation_id=_("Get model parameter form"), operation_id="获取模型参数表单",
tags=[_("Application")]) tags=["模型"])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
@ -246,9 +237,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Get a list of function libraries"), @swagger_auto_schema(operation_summary="获取函数库列表",
operation_id=_("Get a list of function libraries"), operation_id="获取函数库列表",
tags=[_("Application")]) tags=["应用"])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
@ -264,9 +255,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Get library details"), @swagger_auto_schema(operation_summary="获取函数库列表",
operation_id=_("Get library details"), operation_id="获取函数库列表",
tags=[_("Application")], tags=["应用"],
) )
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
@ -283,9 +274,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the list of apps created by the current user"), @swagger_auto_schema(operation_summary="获取当前人创建的应用列表",
operation_id=_("Get the list of apps created by the current user"), operation_id="获取当前人创建的应用列表",
tags=[_("Application/Chat")]) tags=["应用/会话"])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
@ -301,9 +292,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=["GET"], detail=False) @action(methods=["GET"], detail=False)
@swagger_auto_schema(operation_summary=_("Get application data"), @swagger_auto_schema(operation_summary="获取应用数据",
operation_id=_("Get application data"), operation_id="获取应用数据",
tags=[_("Application")], tags=["应用"],
) )
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
@ -320,9 +311,9 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get application related information"), @swagger_auto_schema(operation_summary="获取应用相关信息",
operation_id=_("Get application related information"), operation_id="获取应用相关信息",
tags=[_("Application/Chat")]) tags=["应用/会话"])
def get(self, request: Request): def get(self, request: Request):
if 'application_id' in request.auth.keywords: if 'application_id' in request.auth.keywords:
return result.success(ApplicationSerializer.Operate( return result.success(ApplicationSerializer.Operate(
@ -334,26 +325,24 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Add ApiKey"), @swagger_auto_schema(operation_summary="新增ApiKey",
operation_id=_("Add ApiKey"), operation_id="新增ApiKey",
tags=[_('Application/API_KEY')], tags=['应用/API_KEY'],
manual_parameters=ApplicationApi.ApiKey.get_request_params_api()) manual_parameters=ApplicationApi.ApiKey.get_request_params_api())
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND)) compare=CompareConstants.AND))
@log(menu='Application', operate="Add ApiKey",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str): def post(self, request: Request, application_id: str):
return result.success( return result.success(
ApplicationSerializer.ApplicationKeySerializer( ApplicationSerializer.ApplicationKeySerializer(
data={'application_id': application_id, 'user_id': request.user.id}).generate()) data={'application_id': application_id, 'user_id': request.user.id}).generate())
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the application API_KEY list"), @swagger_auto_schema(operation_summary="获取应用API_KEY列表",
operation_id=_("Get the application API_KEY list"), operation_id="获取应用API_KEY列表",
tags=[_('Application/API_KEY')], tags=['应用/API_KEY'],
manual_parameters=ApplicationApi.ApiKey.get_request_params_api() manual_parameters=ApplicationApi.ApiKey.get_request_params_api()
) )
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
@ -369,20 +358,17 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Modify application API_KEY"), @swagger_auto_schema(operation_summary="修改应用API_KEY",
operation_id=_("Modify application API_KEY"), operation_id="修改应用API_KEY",
tags=[_('Application/API_KEY')], tags=['应用/API_KEY'],
manual_parameters=ApplicationApi.ApiKey.Operate.get_request_params_api(), manual_parameters=ApplicationApi.ApiKey.Operate.get_request_params_api(),
request_body=ApplicationApi.ApiKey.Operate.get_request_body_api(), request_body=ApplicationApi.ApiKey.Operate.get_request_body_api())
responses=result.get_api_response(ApplicationApi.ApiKey.Operate.get_response_body_api()))
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT, compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT,
compare=CompareConstants.AND) compare=CompareConstants.AND)
@log(menu='Application', operate="Modify application API_KEY",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, api_key_id: str): def put(self, request: Request, application_id: str, api_key_id: str):
return result.success( return result.success(
ApplicationSerializer.ApplicationKeySerializer.Operate( ApplicationSerializer.ApplicationKeySerializer.Operate(
@ -390,9 +376,9 @@ class Application(APIView):
'api_key_id': api_key_id}).edit(request.data)) 'api_key_id': api_key_id}).edit(request.data))
@action(methods=['DELETE'], detail=False) @action(methods=['DELETE'], detail=False)
@swagger_auto_schema(operation_summary=_("Delete Application API_KEY"), @swagger_auto_schema(operation_summary="删除应用API_KEY",
operation_id=_("Delete Application API_KEY"), operation_id="删除应用API_KEY",
tags=[_('Application/API_KEY')], tags=['应用/API_KEY'],
manual_parameters=ApplicationApi.ApiKey.Operate.get_request_params_api()) manual_parameters=ApplicationApi.ApiKey.Operate.get_request_params_api())
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
@ -400,8 +386,6 @@ class Application(APIView):
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), PermissionConstants.APPLICATION_DELETE, compare=CompareConstants.AND), PermissionConstants.APPLICATION_DELETE,
compare=CompareConstants.AND) compare=CompareConstants.AND)
@log(menu='Application', operate="Delete Application API_KEY",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, api_key_id: str): def delete(self, request: Request, application_id: str, api_key_id: str):
return result.success( return result.success(
ApplicationSerializer.ApplicationKeySerializer.Operate( ApplicationSerializer.ApplicationKeySerializer.Operate(
@ -412,29 +396,26 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Modify Application AccessToken"), @swagger_auto_schema(operation_summary="修改 应用AccessToken",
operation_id=_("Modify Application AccessToken"), operation_id="修改 应用AccessToken",
tags=[_('Application/Public Access')], tags=['应用/公开访问'],
manual_parameters=ApplicationApi.AccessToken.get_request_params_api(), manual_parameters=ApplicationApi.AccessToken.get_request_params_api(),
request_body=ApplicationApi.AccessToken.get_request_body_api(), request_body=ApplicationApi.AccessToken.get_request_body_api())
responses=result.get_api_response(ApplicationApi.AccessToken.get_response_body_api()))
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND)) compare=CompareConstants.AND))
@log(menu='Application', operate="Modify Application AccessToken",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str): def put(self, request: Request, application_id: str):
return result.success( return result.success(
ApplicationSerializer.AccessTokenSerializer(data={'application_id': application_id}).edit( ApplicationSerializer.AccessTokenSerializer(data={'application_id': application_id}).edit(
request.data)) request.data))
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the application AccessToken information"), @swagger_auto_schema(operation_summary="获取应用 AccessToken信息",
operation_id=_("Get the application AccessToken information"), operation_id="获取应用 AccessToken信息",
manual_parameters=ApplicationApi.AccessToken.get_request_params_api(), manual_parameters=ApplicationApi.AccessToken.get_request_params_api(),
tags=[_('Application/Public Access')], tags=['应用/公开访问'],
) )
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
@ -454,11 +435,10 @@ class Application(APIView):
"Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"}, ) "Access-Control-Allow-Headers": "Origin,Content-Type,Cookie,Accept,Token"}, )
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Application Certification"), @swagger_auto_schema(operation_summary="应用认证",
operation_id=_("Application Certification"), operation_id="应用认证",
request_body=ApplicationApi.Authentication.get_request_body_api(), request_body=ApplicationApi.Authentication.get_request_body_api(),
responses=result.get_api_response(ApplicationApi.Authentication.get_response_body_api()), tags=["应用/认证"],
tags=[_("Application/Certification")],
security=[]) security=[])
def post(self, request: Request): def post(self, request: Request):
return result.success( return result.success(
@ -472,23 +452,20 @@ class Application(APIView):
) )
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Create an application"), @swagger_auto_schema(operation_summary="创建应用",
operation_id=_("Create an application"), operation_id="创建应用",
request_body=ApplicationApi.Create.get_request_body_api(), request_body=ApplicationApi.Create.get_request_body_api(),
responses=result.get_api_response(ApplicationApi.Create.get_response_body_api()), tags=['应用'])
tags=[_('Application')])
@has_permissions(PermissionConstants.APPLICATION_CREATE, compare=CompareConstants.AND) @has_permissions(PermissionConstants.APPLICATION_CREATE, compare=CompareConstants.AND)
@log(menu='Application', operate="Create an application",
get_operation_object=lambda r, k: {'name': r.data.get('name')})
def post(self, request: Request): def post(self, request: Request):
return result.success(ApplicationSerializer.Create(data={'user_id': request.user.id}).insert(request.data)) return result.success(ApplicationSerializer.Create(data={'user_id': request.user.id}).insert(request.data))
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the application list"), @swagger_auto_schema(operation_summary="获取应用列表",
operation_id=_("Get the application list"), operation_id="获取应用列表",
manual_parameters=ApplicationApi.Query.get_request_params_api(), manual_parameters=ApplicationApi.Query.get_request_params_api(),
responses=result.get_api_array_response(ApplicationApi.get_response_body_api()), responses=result.get_api_array_response(ApplicationApi.get_response_body_api()),
tags=[_('Application')]) tags=['应用'])
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND) @has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request): def get(self, request: Request):
return result.success( return result.success(
@ -499,10 +476,10 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods="GET", detail=False) @action(methods="GET", detail=False)
@swagger_auto_schema(operation_summary=_("Hit Test List"), operation_id=_("Hit Test List"), @swagger_auto_schema(operation_summary="命中测试列表", operation_id="命中测试列表",
manual_parameters=CommonApi.HitTestApi.get_request_params_api(), manual_parameters=CommonApi.HitTestApi.get_request_params_api(),
responses=result.get_api_array_response(CommonApi.HitTestApi.get_response_body_api()), responses=result.get_api_array_response(CommonApi.HitTestApi.get_response_body_api()),
tags=[_("Application")]) tags=["应用"])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN, [RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN,
RoleConstants.APPLICATION_KEY], RoleConstants.APPLICATION_KEY],
@ -523,19 +500,17 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Publishing an application"), @swagger_auto_schema(operation_summary="发布应用",
operation_id=_("Publishing an application"), operation_id="发布应用",
manual_parameters=ApplicationApi.Operate.get_request_params_api(), manual_parameters=ApplicationApi.Operate.get_request_params_api(),
request_body=ApplicationApi.Publish.get_request_body_api(), request_body=ApplicationApi.Publish.get_request_body_api(),
responses=result.get_default_response(), responses=result.get_default_response(),
tags=[_('Application')]) tags=['应用'])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND)) compare=CompareConstants.AND))
@log(menu='Application', operate="Publishing an application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str): def put(self, request: Request, application_id: str):
return result.success( return result.success(
ApplicationSerializer.Operate( ApplicationSerializer.Operate(
@ -545,11 +520,11 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['DELETE'], detail=False) @action(methods=['DELETE'], detail=False)
@swagger_auto_schema(operation_summary=_("Deleting application"), @swagger_auto_schema(operation_summary="删除应用",
operation_id=_("Deleting application"), operation_id="删除应用",
manual_parameters=ApplicationApi.Operate.get_request_params_api(), manual_parameters=ApplicationApi.Operate.get_request_params_api(),
responses=result.get_default_response(), responses=result.get_default_response(),
tags=[_('Application')]) tags=['应用'])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
@ -557,27 +532,23 @@ class Application(APIView):
compare=CompareConstants.AND), compare=CompareConstants.AND),
lambda r, k: Permission(group=Group.APPLICATION, operate=Operate.DELETE, lambda r, k: Permission(group=Group.APPLICATION, operate=Operate.DELETE,
dynamic_tag=k.get('application_id')), compare=CompareConstants.AND) dynamic_tag=k.get('application_id')), compare=CompareConstants.AND)
@log(menu='Application', operate="Deleting application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str): def delete(self, request: Request, application_id: str):
return result.success(ApplicationSerializer.Operate( return result.success(ApplicationSerializer.Operate(
data={'application_id': application_id, 'user_id': request.user.id}).delete( data={'application_id': application_id, 'user_id': request.user.id}).delete(
with_valid=True)) with_valid=True))
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Modify the application"), @swagger_auto_schema(operation_summary="修改应用",
operation_id=_("Modify the application"), operation_id="修改应用",
manual_parameters=ApplicationApi.Operate.get_request_params_api(), manual_parameters=ApplicationApi.Operate.get_request_params_api(),
request_body=ApplicationApi.Edit.get_request_body_api(), request_body=ApplicationApi.Edit.get_request_body_api(),
responses=result.get_api_array_response(ApplicationApi.get_response_body_api()), responses=result.get_api_array_response(ApplicationApi.get_response_body_api()),
tags=[_('Application')]) tags=['应用'])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND)) compare=CompareConstants.AND))
@log(menu='Application', operate="Modify the application",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str): def put(self, request: Request, application_id: str):
return result.success( return result.success(
ApplicationSerializer.Operate( ApplicationSerializer.Operate(
@ -585,11 +556,11 @@ class Application(APIView):
request.data)) request.data))
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get application details"), @swagger_auto_schema(operation_summary="获取应用详情",
operation_id=_("Get application details"), operation_id="获取应用详情",
manual_parameters=ApplicationApi.Operate.get_request_params_api(), manual_parameters=ApplicationApi.Operate.get_request_params_api(),
responses=result.get_api_array_response(ApplicationApi.get_response_body_api()), responses=result.get_api_array_response(ApplicationApi.get_response_body_api()),
tags=[_('Application')]) tags=['应用'])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN, [RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN,
RoleConstants.APPLICATION_KEY], RoleConstants.APPLICATION_KEY],
@ -604,12 +575,12 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the knowledge base available to the current application"), @swagger_auto_schema(operation_summary="获取当前应用可使用的知识库",
operation_id=_("Get the knowledge base available to the current application"), operation_id="获取当前应用可使用的知识库",
manual_parameters=ApplicationApi.Operate.get_request_params_api(), manual_parameters=ApplicationApi.Operate.get_request_params_api(),
responses=result.get_api_array_response( responses=result.get_api_array_response(
DataSetSerializers.Query.get_response_body_api()), DataSetSerializers.Query.get_response_body_api()),
tags=[_('Application')]) tags=['应用'])
@has_permissions(ViewPermission([RoleConstants.ADMIN, RoleConstants.USER], @has_permissions(ViewPermission([RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, [lambda r, keywords: Permission(group=Group.APPLICATION,
operate=Operate.USE, operate=Operate.USE,
@ -624,12 +595,12 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the application list by page"), @swagger_auto_schema(operation_summary="分页获取应用列表",
operation_id=_("Get the application list by page"), operation_id="分页获取应用列表",
manual_parameters=result.get_page_request_params( manual_parameters=result.get_page_request_params(
ApplicationApi.Query.get_request_params_api()), ApplicationApi.Query.get_request_params_api()),
responses=result.get_page_api_response(ApplicationApi.get_response_body_api()), responses=result.get_page_api_response(ApplicationApi.get_response_body_api()),
tags=[_('Application')]) tags=['应用'])
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND) @has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request, current_page: int, page_size: int): def get(self, request: Request, current_page: int, page_size: int):
return result.success( return result.success(
@ -657,12 +628,6 @@ class Application(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("text to speech"),
operation_id=_("text to speech"),
manual_parameters=ApplicationApi.TextToSpeech.get_request_params_api(),
request_body=ApplicationApi.TextToSpeech.get_request_body_api(),
responses=result.get_default_response(),
tags=[_('Application')])
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN],
[lambda r, keywords: Permission(group=Group.APPLICATION, [lambda r, keywords: Permission(group=Group.APPLICATION,
@ -688,19 +653,8 @@ class Application(APIView):
dynamic_tag=keywords.get( dynamic_tag=keywords.get(
'application_id'))], 'application_id'))],
compare=CompareConstants.AND)) compare=CompareConstants.AND))
@log(menu='Application', operate="trial listening",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str): def post(self, request: Request, application_id: str):
byte_data = ApplicationSerializer.Operate( byte_data = ApplicationSerializer.Operate(
data={'application_id': application_id, 'user_id': request.user.id}).play_demo_text(request.data) data={'application_id': application_id, 'user_id': request.user.id}).play_demo_text(request.data)
return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3', return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',
'Content-Disposition': 'attachment; filename="abc.mp3"'}) 'Content-Disposition': 'attachment; filename="abc.mp3"'})
class McpServers(APIView):
authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False)
@has_permissions(PermissionConstants.APPLICATION_READ, compare=CompareConstants.AND)
def get(self, request: Request):
return result.success(ApplicationSerializer.McpServers(
data={'mcp_servers': request.query_params.get('mcp_servers')}).get_mcp_servers())

View File

@ -7,11 +7,8 @@
@desc: @desc:
""" """
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.views import APIView from rest_framework.views import APIView
@ -19,12 +16,10 @@ from application.serializers.chat_message_serializers import ChatMessageSerializ
from application.serializers.chat_serializers import ChatSerializers, ChatRecordSerializer from application.serializers.chat_serializers import ChatSerializers, ChatRecordSerializer
from application.swagger_api.chat_api import ChatApi, VoteApi, ChatRecordApi, ImproveApi, ChatRecordImproveApi, \ from application.swagger_api.chat_api import ChatApi, VoteApi, ChatRecordApi, ImproveApi, ChatRecordImproveApi, \
ChatClientHistoryApi, OpenAIChatApi ChatClientHistoryApi, OpenAIChatApi
from application.views import get_application_operation_object
from common.auth import TokenAuth, has_permissions, OpenAIKeyAuth from common.auth import TokenAuth, has_permissions, OpenAIKeyAuth
from common.constants.authentication_type import AuthenticationType from common.constants.authentication_type import AuthenticationType
from common.constants.permission_constants import Permission, Group, Operate, \ from common.constants.permission_constants import Permission, Group, Operate, \
RoleConstants, ViewPermission, CompareConstants RoleConstants, ViewPermission, CompareConstants
from common.log.log import log
from common.response import result from common.response import result
from common.util.common import query_params_to_single_dict from common.util.common import query_params_to_single_dict
from dataset.serializers.file_serializers import FileSerializer from dataset.serializers.file_serializers import FileSerializer
@ -34,11 +29,10 @@ class Openai(APIView):
authentication_classes = [OpenAIKeyAuth] authentication_classes = [OpenAIKeyAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("OpenAI Interface Dialogue"), @swagger_auto_schema(operation_summary="openai接口对话",
operation_id=_("OpenAI Interface Dialogue"), operation_id="openai接口对话",
request_body=OpenAIChatApi.get_request_body_api(), request_body=OpenAIChatApi.get_request_body_api(),
responses=OpenAIChatApi.get_response_body_api(), tags=["openai对话"])
tags=[_("OpenAI Dialogue")])
def post(self, request: Request, application_id: str): def post(self, request: Request, application_id: str):
return OpenAIChatSerializer(data={'application_id': application_id, 'client_id': request.auth.client_id, return OpenAIChatSerializer(data={'application_id': application_id, 'client_id': request.auth.client_id,
'client_type': request.auth.client_type}).chat(request.data) 'client_type': request.auth.client_type}).chat(request.data)
@ -51,18 +45,16 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Export conversation"), @swagger_auto_schema(operation_summary="导出对话",
operation_id=_("Export conversation"), operation_id="导出对话",
manual_parameters=ChatApi.get_request_params_api(), manual_parameters=ChatApi.get_request_params_api(),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))]) dynamic_tag=keywords.get('application_id'))])
) )
@log(menu='Conversation Log', operate="Export conversation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str): def post(self, request: Request, application_id: str):
return ChatSerializers.Query( return ChatSerializers.Query(
data={**query_params_to_single_dict(request.query_params), 'application_id': application_id, data={**query_params_to_single_dict(request.query_params), 'application_id': application_id,
@ -72,10 +64,10 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the session id according to the application id"), @swagger_auto_schema(operation_summary="获取会话id,根据应用id",
operation_id=_("Get the session id according to the application id"), operation_id="获取会话id,根据应用id",
manual_parameters=ChatApi.OpenChat.get_request_params_api(), manual_parameters=ChatApi.OpenChat.get_request_params_api(),
tags=[_("Application/Chat")]) tags=["应用/会话"])
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN, ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_ACCESS_TOKEN,
RoleConstants.APPLICATION_KEY], RoleConstants.APPLICATION_KEY],
@ -91,24 +83,22 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the workflow temporary session id"), @swagger_auto_schema(operation_summary="获取工作流临时会话id",
operation_id=_("Get the workflow temporary session id"), operation_id="获取工作流临时会话id",
request_body=ChatApi.OpenWorkFlowTemp.get_request_body_api(), request_body=ChatApi.OpenWorkFlowTemp.get_request_body_api(),
responses=result.get_api_response(ChatApi.OpenTempChat.get_response_body_api()), tags=["应用/会话"])
tags=[_("Application/Chat")])
def post(self, request: Request): def post(self, request: Request):
return result.success(ChatSerializers.OpenWorkFlowChat( return result.success(ChatSerializers.OpenWorkFlowChat(
data={'user_id': request.user.id, **request.data}).open()) data={**request.data, 'user_id': request.user.id}).open())
class OpenTemp(APIView): class OpenTemp(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Get a temporary session id"), @swagger_auto_schema(operation_summary="获取会话id(根据模型id,知识库列表,是否多轮会话)",
operation_id=_("Get a temporary session id"), operation_id="获取会话id",
request_body=ChatApi.OpenTempChat.get_request_body_api(), request_body=ChatApi.OpenTempChat.get_request_body_api(),
responses=result.get_api_response(ChatApi.OpenTempChat.get_response_body_api()), tags=["应用/会话"])
tags=[_("Application/Chat")])
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER) @has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
def post(self, request: Request): def post(self, request: Request):
return result.success(ChatSerializers.OpenTempChat( return result.success(ChatSerializers.OpenTempChat(
@ -118,10 +108,10 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("dialogue"), @swagger_auto_schema(operation_summary="对话",
operation_id=_("dialogue"), operation_id="对话",
request_body=ChatApi.get_request_body_api(), request_body=ChatApi.get_request_body_api(),
tags=[_("Application/Chat")]) tags=["应用/会话"])
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY, ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY,
RoleConstants.APPLICATION_ACCESS_TOKEN], RoleConstants.APPLICATION_ACCESS_TOKEN],
@ -146,8 +136,6 @@ class ChatView(APIView):
'document_list') if 'document_list' in request.data else [], 'document_list') if 'document_list' in request.data else [],
'audio_list': request.data.get( 'audio_list': request.data.get(
'audio_list') if 'audio_list' in request.data else [], 'audio_list') if 'audio_list' in request.data else [],
'other_list': request.data.get(
'other_list') if 'other_list' in request.data else [],
'client_type': request.auth.client_type, 'client_type': request.auth.client_type,
'node_id': request.data.get('node_id', None), 'node_id': request.data.get('node_id', None),
'runtime_node_id': request.data.get('runtime_node_id', None), 'runtime_node_id': request.data.get('runtime_node_id', None),
@ -157,11 +145,11 @@ class ChatView(APIView):
).chat() ).chat()
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the conversation list"), @swagger_auto_schema(operation_summary="获取对话列表",
operation_id=_("Get the conversation list"), operation_id="获取对话列表",
manual_parameters=ChatApi.get_request_params_api(), manual_parameters=ChatApi.get_request_params_api(),
responses=result.get_api_array_response(ChatApi.get_response_body_api()), responses=result.get_api_array_response(ChatApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
@ -177,17 +165,15 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['DELETE'], detail=False) @action(methods=['DELETE'], detail=False)
@swagger_auto_schema(operation_summary=_("Delete a conversation"), @swagger_auto_schema(operation_summary="删除对话",
operation_id=_("Delete a conversation"), operation_id="删除对话",
tags=[_("Application/Conversation Log")]) tags=["应用/对话日志"])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.ADMIN, RoleConstants.USER], [RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), compare=CompareConstants.AND),
compare=CompareConstants.AND) compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Delete a conversation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, chat_id: str): def delete(self, request: Request, application_id: str, chat_id: str):
return result.success( return result.success(
ChatSerializers.Operate( ChatSerializers.Operate(
@ -198,12 +184,12 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get client conversation list by paging"), @swagger_auto_schema(operation_summary="分页获取客户端对话列表",
operation_id=_("Get client conversation list by paging"), operation_id="分页获取客户端对话列表",
manual_parameters=result.get_page_request_params( manual_parameters=result.get_page_request_params(
ChatClientHistoryApi.get_request_params_api()), ChatClientHistoryApi.get_request_params_api()),
responses=result.get_page_api_response(ChatApi.get_response_body_api()), responses=result.get_page_api_response(ChatApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.APPLICATION_ACCESS_TOKEN], ViewPermission([RoleConstants.APPLICATION_ACCESS_TOKEN],
@ -220,52 +206,30 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['DELETE'], detail=False) @action(methods=['DELETE'], detail=False)
@swagger_auto_schema(operation_summary=_("Client deletes conversation"), @swagger_auto_schema(operation_summary="客户端删除对话",
operation_id=_("Client deletes conversation"), operation_id="客户端删除对话",
tags=[_("Application/Conversation Log")]) tags=["应用/对话日志"])
@has_permissions(ViewPermission( @has_permissions(ViewPermission(
[RoleConstants.APPLICATION_ACCESS_TOKEN], [RoleConstants.APPLICATION_ACCESS_TOKEN],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))], dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND), compare=CompareConstants.AND),
compare=CompareConstants.AND) compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Client deletes conversation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, chat_id: str): def delete(self, request: Request, application_id: str, chat_id: str):
return result.success( return result.success(
ChatSerializers.Operate( ChatSerializers.Operate(
data={'application_id': application_id, 'user_id': request.user.id, data={'application_id': application_id, 'user_id': request.user.id,
'chat_id': chat_id}).logic_delete()) 'chat_id': chat_id}).logic_delete())
@action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Client modifies dialogue summary"),
operation_id=_("Client modifies dialogue summary"),
request_body=ChatClientHistoryApi.Operate.ReAbstract.get_request_body_api(),
responses=result.get_default_response(),
tags=[_("Application/Conversation Log")])
@has_permissions(ViewPermission(
[RoleConstants.APPLICATION_ACCESS_TOKEN, RoleConstants.ADMIN, RoleConstants.USER],
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))],
compare=CompareConstants.AND),
compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Client modifies dialogue summary",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, chat_id: str):
return result.success(
ChatSerializers.Operate(
data={'application_id': application_id, 'user_id': request.user.id,
'chat_id': chat_id}).re_abstract(request.data))
class Page(APIView): class Page(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the conversation list by page"), @swagger_auto_schema(operation_summary="分页获取对话列表",
operation_id=_("Get the conversation list by page"), operation_id="分页获取对话列表",
manual_parameters=result.get_page_request_params(ChatApi.get_request_params_api()), manual_parameters=result.get_page_request_params(ChatApi.get_request_params_api()),
responses=result.get_page_api_response(ChatApi.get_response_body_api()), responses=result.get_page_api_response(ChatApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
@ -285,11 +249,11 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get conversation record details"), @swagger_auto_schema(operation_summary="获取对话记录详情",
operation_id=_("Get conversation record details"), operation_id="获取对话记录详情",
manual_parameters=ChatRecordApi.get_request_params_api(), manual_parameters=ChatRecordApi.get_request_params_api(),
responses=result.get_api_array_response(ChatRecordApi.get_response_body_api()), responses=result.get_api_array_response(ChatRecordApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY, ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY,
@ -304,11 +268,11 @@ class ChatView(APIView):
'chat_record_id': chat_record_id}).one(request.auth.current_role)) 'chat_record_id': chat_record_id}).one(request.auth.current_role))
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get a list of conversation records"), @swagger_auto_schema(operation_summary="获取对话记录列表",
operation_id=_("Get a list of conversation records"), operation_id="获取对话记录列表",
manual_parameters=ChatRecordApi.get_request_params_api(), manual_parameters=ChatRecordApi.get_request_params_api(),
responses=result.get_api_array_response(ChatRecordApi.get_response_body_api()), responses=result.get_api_array_response(ChatRecordApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
@ -324,12 +288,12 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the conversation history list by page"), @swagger_auto_schema(operation_summary="获取对话记录列表",
operation_id=_("Get the conversation history list by page"), operation_id="获取对话记录列表",
manual_parameters=result.get_page_request_params( manual_parameters=result.get_page_request_params(
ChatRecordApi.get_request_params_api()), ChatRecordApi.get_request_params_api()),
responses=result.get_page_api_response(ChatRecordApi.get_response_body_api()), responses=result.get_page_api_response(ChatRecordApi.get_response_body_api()),
tags=[_("Application/Conversation Log")] tags=["应用/对话日志"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY],
@ -346,12 +310,12 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Like, Dislike"), @swagger_auto_schema(operation_summary="点赞,点踩",
operation_id=_("Like, Dislike"), operation_id="点赞,点踩",
manual_parameters=VoteApi.get_request_params_api(), manual_parameters=VoteApi.get_request_params_api(),
request_body=VoteApi.get_request_body_api(), request_body=VoteApi.get_request_body_api(),
responses=result.get_default_response(), responses=result.get_default_response(),
tags=[_("Application/Chat")] tags=["应用/会话"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY, ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY,
@ -359,8 +323,6 @@ class ChatView(APIView):
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE,
dynamic_tag=keywords.get('application_id'))]) dynamic_tag=keywords.get('application_id'))])
) )
@log(menu='Conversation Log', operate="Like, Dislike",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, chat_id: str, chat_record_id: str): def put(self, request: Request, application_id: str, chat_id: str, chat_record_id: str):
return result.success(ChatRecordSerializer.Vote( return result.success(ChatRecordSerializer.Vote(
data={'vote_status': request.data.get('vote_status'), 'chat_id': chat_id, data={'vote_status': request.data.get('vote_status'), 'chat_id': chat_id,
@ -370,11 +332,11 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['GET'], detail=False) @action(methods=['GET'], detail=False)
@swagger_auto_schema(operation_summary=_("Get the list of marked paragraphs"), @swagger_auto_schema(operation_summary="获取标注段落列表信息",
operation_id=_("Get the list of marked paragraphs"), operation_id="获取标注段落列表信息",
manual_parameters=ChatRecordImproveApi.get_request_params_api(), manual_parameters=ChatRecordImproveApi.get_request_params_api(),
responses=result.get_api_response(ChatRecordImproveApi.get_response_body_api()), responses=result.get_api_response(ChatRecordImproveApi.get_response_body_api()),
tags=[_("Application/Conversation Log/Annotation")] tags=["应用/对话日志/标注"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER],
@ -389,12 +351,12 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['PUT'], detail=False) @action(methods=['PUT'], detail=False)
@swagger_auto_schema(operation_summary=_("Annotation"), @swagger_auto_schema(operation_summary="标注",
operation_id=_("Annotation"), operation_id="标注",
manual_parameters=ImproveApi.get_request_params_api(), manual_parameters=ImproveApi.get_request_params_api(),
request_body=ImproveApi.get_request_body_api(), request_body=ImproveApi.get_request_body_api(),
responses=result.get_api_response(ChatRecordApi.get_response_body_api()), responses=result.get_api_response(ChatRecordApi.get_response_body_api()),
tags=[_("Application/Conversation Log/Annotation")] tags=["应用/对话日志/标注"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER],
@ -408,8 +370,6 @@ class ChatView(APIView):
'dataset_id'))], 'dataset_id'))],
compare=CompareConstants.AND compare=CompareConstants.AND
), compare=CompareConstants.AND) ), compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Annotation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def put(self, request: Request, application_id: str, chat_id: str, chat_record_id: str, dataset_id: str, def put(self, request: Request, application_id: str, chat_id: str, chat_record_id: str, dataset_id: str,
document_id: str): document_id: str):
return result.success(ChatRecordSerializer.Improve( return result.success(ChatRecordSerializer.Improve(
@ -417,12 +377,11 @@ class ChatView(APIView):
'dataset_id': dataset_id, 'document_id': document_id}).improve(request.data)) 'dataset_id': dataset_id, 'document_id': document_id}).improve(request.data))
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Add to Knowledge Base"), @swagger_auto_schema(operation_summary="添加至知识库",
operation_id=_("Add to Knowledge Base"), operation_id="添加至知识库",
manual_parameters=ImproveApi.get_request_params_api_post(), manual_parameters=ImproveApi.get_request_params_api_post(),
request_body=ImproveApi.get_request_body_api_post(), request_body=ImproveApi.get_request_body_api_post(),
responses=result.get_default_response(), tags=["应用/对话日志/添加至知识库"]
tags=[_("Application/Conversation Log/Add to Knowledge Base")]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER],
@ -436,8 +395,6 @@ class ChatView(APIView):
'dataset_id'))], 'dataset_id'))],
compare=CompareConstants.AND compare=CompareConstants.AND
), compare=CompareConstants.AND) ), compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Add to Knowledge Base",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def post(self, request: Request, application_id: str, dataset_id: str): def post(self, request: Request, application_id: str, dataset_id: str):
return result.success(ChatRecordSerializer.PostImprove().post_improve(request.data)) return result.success(ChatRecordSerializer.PostImprove().post_improve(request.data))
@ -445,11 +402,11 @@ class ChatView(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
@action(methods=['DELETE'], detail=False) @action(methods=['DELETE'], detail=False)
@swagger_auto_schema(operation_summary=_("Delete a Annotation"), @swagger_auto_schema(operation_summary="标注",
operation_id=_("Delete a Annotation"), operation_id="标注",
manual_parameters=ImproveApi.get_request_params_api(), manual_parameters=ImproveApi.get_request_params_api(),
responses=result.get_api_response(ChatRecordApi.get_response_body_api()), responses=result.get_api_response(ChatRecordApi.get_response_body_api()),
tags=[_("Application/Conversation Log/Annotation")] tags=["应用/对话日志/标注"]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER], ViewPermission([RoleConstants.ADMIN, RoleConstants.USER],
@ -463,8 +420,6 @@ class ChatView(APIView):
'dataset_id'))], 'dataset_id'))],
compare=CompareConstants.AND compare=CompareConstants.AND
), compare=CompareConstants.AND) ), compare=CompareConstants.AND)
@log(menu='Conversation Log', operate="Delete a Annotation",
get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))
def delete(self, request: Request, application_id: str, chat_id: str, chat_record_id: str, def delete(self, request: Request, application_id: str, chat_id: str, chat_record_id: str,
dataset_id: str, dataset_id: str,
document_id: str, paragraph_id: str): document_id: str, paragraph_id: str):
@ -475,29 +430,12 @@ class ChatView(APIView):
class UploadFile(APIView): class UploadFile(APIView):
authentication_classes = [TokenAuth] authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser]
@action(methods=['POST'], detail=False) @action(methods=['POST'], detail=False)
@swagger_auto_schema(operation_summary=_("Upload files"), @swagger_auto_schema(operation_summary="上传文件",
operation_id=_("Upload files"), operation_id="上传文件",
manual_parameters=[ manual_parameters=ChatRecordApi.get_request_params_api(),
openapi.Parameter(name='application_id', tags=["应用/对话日志"]
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Application ID')),
openapi.Parameter(name='chat_id',
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
required=True,
description=_('Conversation ID')),
openapi.Parameter(name='file',
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
required=True,
description=_('Upload file'))
],
tags=[_("Application/Conversation Log")]
) )
@has_permissions( @has_permissions(
ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY, ViewPermission([RoleConstants.ADMIN, RoleConstants.USER, RoleConstants.APPLICATION_KEY,

View File

@ -1,21 +0,0 @@
# coding=utf-8
"""
@project: MaxKB
@Author
@file common.py
@date2025/3/25 16:56
@desc:
"""
from django.db.models import QuerySet
from application.models import Application
def get_application_operation_object(application_id):
application_model = QuerySet(model=Application).filter(id=application_id).first()
if application_model is not None:
return {
"name": application_model.name
}
return {}

View File

@ -16,7 +16,7 @@ from rest_framework.authentication import TokenAuthentication
from common.exception.app_exception import AppAuthenticationFailed, AppEmbedIdentityFailed, AppChatNumOutOfBoundsFailed, \ from common.exception.app_exception import AppAuthenticationFailed, AppEmbedIdentityFailed, AppChatNumOutOfBoundsFailed, \
ChatException, AppApiException ChatException, AppApiException
from django.utils.translation import gettext_lazy as _
token_cache = cache.caches['token_cache'] token_cache = cache.caches['token_cache']
@ -59,19 +59,19 @@ class OpenAIKeyAuth(TokenAuthentication):
auth = auth.replace('Bearer ', '') auth = auth.replace('Bearer ', '')
# 未认证 # 未认证
if auth is None: if auth is None:
raise AppAuthenticationFailed(1003, _('Not logged in, please log in first')) raise AppAuthenticationFailed(1003, '未登录,请先登录')
try: try:
token_details = TokenDetails(auth) token_details = TokenDetails(auth)
for handle in handles: for handle in handles:
if handle.support(request, auth, token_details.get_token_details): if handle.support(request, auth, token_details.get_token_details):
return handle.handle(request, auth, token_details.get_token_details) return handle.handle(request, auth, token_details.get_token_details)
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户")
except Exception as e: except Exception as e:
traceback.format_exc() traceback.format_exc()
if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e, if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e,
AppApiException): AppApiException):
raise e raise e
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户")
class TokenAuth(TokenAuthentication): class TokenAuth(TokenAuthentication):
@ -80,16 +80,16 @@ class TokenAuth(TokenAuthentication):
auth = request.META.get('HTTP_AUTHORIZATION') auth = request.META.get('HTTP_AUTHORIZATION')
# 未认证 # 未认证
if auth is None: if auth is None:
raise AppAuthenticationFailed(1003, _('Not logged in, please log in first')) raise AppAuthenticationFailed(1003, '未登录,请先登录')
try: try:
token_details = TokenDetails(auth) token_details = TokenDetails(auth)
for handle in handles: for handle in handles:
if handle.support(request, auth, token_details.get_token_details): if handle.support(request, auth, token_details.get_token_details):
return handle.handle(request, auth, token_details.get_token_details) return handle.handle(request, auth, token_details.get_token_details)
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户")
except Exception as e: except Exception as e:
traceback.format_exc() traceback.format_exc()
if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e, if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e,
AppApiException): AppApiException):
raise e raise e
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user')) raise AppAuthenticationFailed(1002, "身份验证信息不正确!非法用户")

View File

@ -11,7 +11,7 @@ from typing import List
from common.constants.permission_constants import ViewPermission, CompareConstants, RoleConstants, PermissionConstants, \ from common.constants.permission_constants import ViewPermission, CompareConstants, RoleConstants, PermissionConstants, \
Permission Permission
from common.exception.app_exception import AppUnauthorizedFailed from common.exception.app_exception import AppUnauthorizedFailed
from django.utils.translation import gettext_lazy as _
def exist_permissions_by_permission_constants(user_permission: List[PermissionConstants], def exist_permissions_by_permission_constants(user_permission: List[PermissionConstants],
permission_list: List[PermissionConstants]): permission_list: List[PermissionConstants]):
@ -91,7 +91,7 @@ def has_permissions(*permission, compare=CompareConstants.OR):
# 判断是否有权限 # 判断是否有权限
if any(exit_list) if compare == CompareConstants.OR else all(exit_list): if any(exit_list) if compare == CompareConstants.OR else all(exit_list):
return func(view, request, **kwargs) return func(view, request, **kwargs)
raise AppUnauthorizedFailed(403, _('No permission to access')) raise AppUnauthorizedFailed(403, "没有权限访问")
return run return run

View File

@ -13,16 +13,15 @@ from common.auth.handle.auth_base_handle import AuthBaseHandle
from common.constants.authentication_type import AuthenticationType from common.constants.authentication_type import AuthenticationType
from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, Auth from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, Auth
from common.exception.app_exception import AppAuthenticationFailed from common.exception.app_exception import AppAuthenticationFailed
from django.utils.translation import gettext_lazy as _
class ApplicationKey(AuthBaseHandle): class ApplicationKey(AuthBaseHandle):
def handle(self, request, token: str, get_token_details): def handle(self, request, token: str, get_token_details):
application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=token).first() application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=token).first()
if application_api_key is None: if application_api_key is None:
raise AppAuthenticationFailed(500, _('Secret key is invalid')) raise AppAuthenticationFailed(500, "secret_key 无效")
if not application_api_key.is_active: if not application_api_key.is_active:
raise AppAuthenticationFailed(500, _('Secret key is invalid')) raise AppAuthenticationFailed(500, "secret_key 无效")
permission_list = [Permission(group=Group.APPLICATION, permission_list = [Permission(group=Group.APPLICATION,
operate=Operate.USE, operate=Operate.USE,
dynamic_tag=str( dynamic_tag=str(

View File

@ -15,7 +15,7 @@ from common.constants.permission_constants import RoleConstants, Permission, Gro
from common.exception.app_exception import AppAuthenticationFailed, ChatException from common.exception.app_exception import AppAuthenticationFailed, ChatException
from common.models.db_model_manage import DBModelManage from common.models.db_model_manage import DBModelManage
from common.util.common import password_encrypt from common.util.common import password_encrypt
from django.utils.translation import gettext_lazy as _
class PublicAccessToken(AuthBaseHandle): class PublicAccessToken(AuthBaseHandle):
def support(self, request, token: str, get_token_details): def support(self, request, token: str, get_token_details):
@ -45,13 +45,13 @@ class PublicAccessToken(AuthBaseHandle):
if application_setting.authentication_value.get('type') != authentication.get( if application_setting.authentication_value.get('type') != authentication.get(
'type') or password_encrypt( 'type') or password_encrypt(
application_setting.authentication_value.get('value')) != authentication.get('value'): application_setting.authentication_value.get('value')) != authentication.get('value'):
raise ChatException(1002, _('Authentication information is incorrect')) raise ChatException(1002, "身份验证信息不正确")
if application_access_token is None: if application_access_token is None:
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) raise AppAuthenticationFailed(1002, "身份验证信息不正确")
if not application_access_token.is_active: if not application_access_token.is_active:
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) raise AppAuthenticationFailed(1002, "身份验证信息不正确")
if not application_access_token.access_token == auth_details.get('access_token'): if not application_access_token.access_token == auth_details.get('access_token'):
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect')) raise AppAuthenticationFailed(1002, "身份验证信息不正确")
return application_access_token.application.user, Auth( return application_access_token.application.user, Auth(
role_list=[RoleConstants.APPLICATION_ACCESS_TOKEN], role_list=[RoleConstants.APPLICATION_ACCESS_TOKEN],

View File

@ -17,7 +17,7 @@ from users.models import User
from django.core import cache from django.core import cache
from users.models.user import get_user_dynamics_permission from users.models.user import get_user_dynamics_permission
from django.utils.translation import gettext_lazy as _
token_cache = cache.caches['token_cache'] token_cache = cache.caches['token_cache']
@ -31,7 +31,7 @@ class UserToken(AuthBaseHandle):
def handle(self, request, token: str, get_token_details): def handle(self, request, token: str, get_token_details):
cache_token = token_cache.get(token) cache_token = token_cache.get(token)
if cache_token is None: if cache_token is None:
raise AppAuthenticationFailed(1002, _('Login expired')) raise AppAuthenticationFailed(1002, "登录过期")
auth_details = get_token_details() auth_details = get_token_details()
user = QuerySet(User).get(id=auth_details['id']) user = QuerySet(User).get(id=auth_details['id'])
# 续期 # 续期

View File

@ -11,50 +11,35 @@ import time
from common.cache.mem_cache import MemCache from common.cache.mem_cache import MemCache
_lock = threading.Lock() lock = threading.Lock()
locks = {}
class ModelManage: class ModelManage:
cache = MemCache('model', {}) cache = MemCache('model', {})
up_clear_time = time.time() up_clear_time = time.time()
@staticmethod
def _get_lock(_id):
lock = locks.get(_id)
if lock is None:
with _lock:
lock = locks.get(_id)
if lock is None:
lock = threading.Lock()
locks[_id] = lock
return lock
@staticmethod @staticmethod
def get_model(_id, get_model): def get_model(_id, get_model):
model_instance = ModelManage.cache.get(_id) # 获取锁
if model_instance is None: lock.acquire()
lock = ModelManage._get_lock(_id) try:
with lock: model_instance = ModelManage.cache.get(_id)
model_instance = ModelManage.cache.get(_id) if model_instance is None or not model_instance.is_cache_model():
if model_instance is None:
model_instance = get_model(_id)
ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)
else:
if model_instance.is_cache_model():
ModelManage.cache.touch(_id, timeout=60 * 60 * 8)
else:
model_instance = get_model(_id) model_instance = get_model(_id)
ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8) ModelManage.cache.set(_id, model_instance, timeout=60 * 30)
ModelManage.clear_timeout_cache() return model_instance
return model_instance # 续期
ModelManage.cache.touch(_id, timeout=60 * 30)
ModelManage.clear_timeout_cache()
return model_instance
finally:
# 释放锁
lock.release()
@staticmethod @staticmethod
def clear_timeout_cache(): def clear_timeout_cache():
if time.time() - ModelManage.up_clear_time > 60 * 60: if time.time() - ModelManage.up_clear_time > 60:
threading.Thread(target=lambda: ModelManage.cache.clear_timeout_data()).start() ModelManage.cache.clear_timeout_data()
ModelManage.up_clear_time = time.time()
@staticmethod @staticmethod
def delete_key(_id): def delete_key(_id):

View File

@ -6,7 +6,7 @@
@date2023/9/5 14:01 @date2023/9/5 14:01
@desc: 用于swagger 分组 @desc: 用于swagger 分组
""" """
from drf_yasg.generators import OpenAPISchemaGenerator
from drf_yasg.inspectors import SwaggerAutoSchema from drf_yasg.inspectors import SwaggerAutoSchema
tags_dict = { tags_dict = {
@ -20,10 +20,10 @@ class CustomSwaggerAutoSchema(SwaggerAutoSchema):
if "api" in tags and operation_keys: if "api" in tags and operation_keys:
return [tags_dict.get(operation_keys[1]) if operation_keys[1] in tags_dict else operation_keys[1]] return [tags_dict.get(operation_keys[1]) if operation_keys[1] in tags_dict else operation_keys[1]]
return tags return tags
class CustomOpenAPISchemaGenerator(OpenAPISchemaGenerator):
def get_schema(self, request=None, public=False): def get_schema(self, request=None, public=False):
schema = super().get_schema(request, public) schema = super().get_schema(request, public)
schema.schemes = ['https', 'http'] if request.is_secure():
return schema schema.schemes = ['https']
else:
schema.schemes = ['http']
return schema

View File

@ -9,7 +9,6 @@
from enum import Enum from enum import Enum
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from django.utils.translation import gettext_lazy as _
class ExceptionCodeConstantsValue: class ExceptionCodeConstantsValue:
@ -28,16 +27,13 @@ class ExceptionCodeConstantsValue:
class ExceptionCodeConstants(Enum): class ExceptionCodeConstants(Enum):
INCORRECT_USERNAME_AND_PASSWORD = ExceptionCodeConstantsValue(1000, _('The username or password is incorrect')) INCORRECT_USERNAME_AND_PASSWORD = ExceptionCodeConstantsValue(1000, "用户名或者密码不正确")
NOT_AUTHENTICATION = ExceptionCodeConstantsValue(1001, _('Please log in first and bring the user Token')) NOT_AUTHENTICATION = ExceptionCodeConstantsValue(1001, "请先登录,并携带用户Token")
EMAIL_SEND_ERROR = ExceptionCodeConstantsValue(1002, _('Email sending failed')) EMAIL_SEND_ERROR = ExceptionCodeConstantsValue(1002, "邮件发送失败")
EMAIL_FORMAT_ERROR = ExceptionCodeConstantsValue(1003, _('Email format error')) EMAIL_FORMAT_ERROR = ExceptionCodeConstantsValue(1003, "邮箱格式错误")
EMAIL_IS_EXIST = ExceptionCodeConstantsValue(1004, _('The email has been registered, please log in directly')) EMAIL_IS_EXIST = ExceptionCodeConstantsValue(1004, "邮箱已经被注册,请勿重复注册")
EMAIL_IS_NOT_EXIST = ExceptionCodeConstantsValue(1005, _('The email is not registered, please register first')) EMAIL_IS_NOT_EXIST = ExceptionCodeConstantsValue(1005, "邮箱尚未注册,请先注册")
CODE_ERROR = ExceptionCodeConstantsValue(1005, CODE_ERROR = ExceptionCodeConstantsValue(1005, "验证码不正确,或者验证码过期")
_('The verification code is incorrect or the verification code has expired')) USERNAME_IS_EXIST = ExceptionCodeConstantsValue(1006, "用户名已被使用,请使用其他用户名")
USERNAME_IS_EXIST = ExceptionCodeConstantsValue(1006, _('The username has been registered, please log in directly')) USERNAME_ERROR = ExceptionCodeConstantsValue(1006, "用户名不能为空,并且长度在6-20")
USERNAME_ERROR = ExceptionCodeConstantsValue(1006, PASSWORD_NOT_EQ_RE_PASSWORD = ExceptionCodeConstantsValue(1007, "密码与确认密码不一致")
_('The username cannot be empty and must be between 6 and 20 characters long.'))
PASSWORD_NOT_EQ_RE_PASSWORD = ExceptionCodeConstantsValue(1007,
_('Password and confirmation password are inconsistent'))

View File

@ -7,7 +7,7 @@
""" """
from enum import Enum from enum import Enum
from typing import List from typing import List
from django.utils.translation import gettext_lazy as _
class Group(Enum): class Group(Enum):
""" """
@ -58,10 +58,10 @@ class Role:
class RoleConstants(Enum): class RoleConstants(Enum):
ADMIN = Role(_("ADMIN"), _('Admin, prefabs are not currently used'), RoleGroup.USER) ADMIN = Role("管理员", "管理员,预制目前不会使用", RoleGroup.USER)
USER = Role(_("USER"), _('All user permissions'), RoleGroup.USER) USER = Role("用户", "用户所有权限", RoleGroup.USER)
APPLICATION_ACCESS_TOKEN = Role(_('chat'), _('Only has application dialog interface permissions'), RoleGroup.APPLICATION_ACCESS_TOKEN), APPLICATION_ACCESS_TOKEN = Role("会话", "只拥有应用会话框接口权限", RoleGroup.APPLICATION_ACCESS_TOKEN),
APPLICATION_KEY = Role(_('Apply private key'), _('Apply private key'), RoleGroup.APPLICATION_KEY) APPLICATION_KEY = Role("应用私钥", "应用私钥", RoleGroup.APPLICATION_KEY)
class Permission: class Permission:

Some files were not shown because too many files have changed in this diff Show More