mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 10:12:51 +00:00
Compare commits
156 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
847755b1c2 | ||
|
|
b57455d0ee | ||
|
|
2a257edff9 | ||
|
|
d47699331c | ||
|
|
90c64d77dd | ||
|
|
e1ada3ffe2 | ||
|
|
b62c79fda6 | ||
|
|
3d9e7dd4b1 | ||
|
|
8ff15865a7 | ||
|
|
48899d55d1 | ||
|
|
1cc4107bfe | ||
|
|
b13cd03706 | ||
|
|
69f024492b | ||
|
|
a9c46cd7e0 | ||
|
|
a9e9f5b085 | ||
|
|
e12b1fe14e | ||
|
|
7ce66a7bf3 | ||
|
|
decd3395db | ||
|
|
9d7a383348 | ||
|
|
8f7d91798b | ||
|
|
81a3af2c8b | ||
|
|
2ec0d22b14 | ||
|
|
27a77dc657 | ||
|
|
187e9c1e4e | ||
|
|
e5bab10824 | ||
|
|
347f4a0b03 | ||
|
|
289ebf42a6 | ||
|
|
71fdce08d7 | ||
|
|
adc5af9cef | ||
|
|
ce2ab322f6 | ||
|
|
a7e31b94c7 | ||
|
|
8498687794 | ||
|
|
190ca3e198 | ||
|
|
c1ddec1a61 | ||
|
|
1ba8077e95 | ||
|
|
9a42bd2302 | ||
|
|
35b662a52d | ||
|
|
a4faf52261 | ||
|
|
a30316d87a | ||
|
|
a4d10cbe3b | ||
|
|
ceccf9f1fa | ||
|
|
3964db20dc | ||
|
|
57ada0708f | ||
|
|
a1a92a833a | ||
|
|
5e0d8048f9 | ||
|
|
8903b35aec | ||
|
|
fa4f7e99fd | ||
|
|
b0630b3ddd | ||
|
|
b2bf69740c | ||
|
|
8cf66b9eca | ||
|
|
1db8577ca6 | ||
|
|
949e4dea9e | ||
|
|
c12988bc8a | ||
|
|
2728453f6c | ||
|
|
ccf43bbcd9 | ||
|
|
8d8de53e38 | ||
|
|
7faf556771 | ||
|
|
76ec8ad6f6 | ||
|
|
0cf05a76a0 | ||
|
|
357edbfbe0 | ||
|
|
0609a9afc8 | ||
|
|
96e59a018f | ||
|
|
79b2de8893 | ||
|
|
0c7cca035e | ||
|
|
00a3e5ddc3 | ||
|
|
a6533c0db7 | ||
|
|
704077d066 | ||
|
|
59ee0c1270 | ||
|
|
b37cc3ba1c | ||
|
|
dfe6d0a91b | ||
|
|
5813eedd4f | ||
|
|
363150380d | ||
|
|
47f9c04664 | ||
|
|
17cd88edda | ||
|
|
d33b620dc8 | ||
|
|
b95cb20704 | ||
|
|
4ff1944b60 | ||
|
|
1c6b0f8a86 | ||
|
|
d85801fe58 | ||
|
|
e79e7d505d | ||
|
|
33b1cd65b0 | ||
|
|
8d503c8bf8 | ||
|
|
df7f922013 | ||
|
|
a0541203e4 | ||
|
|
fb64731cd8 | ||
|
|
d960a18711 | ||
|
|
ee35cc21e9 | ||
|
|
e4a60daa17 | ||
|
|
7bcb770ee5 | ||
|
|
b5b09dc8b4 | ||
|
|
b1f6092620 | ||
|
|
c8441cfd73 | ||
|
|
7e4b147576 | ||
|
|
9cd082089a | ||
|
|
d4541e23f9 | ||
|
|
5e7e91cced | ||
|
|
d9787bb548 | ||
|
|
131b5b3bbe | ||
|
|
e58f95832b | ||
|
|
f24337d5f3 | ||
|
|
d6f1d25b59 | ||
|
|
0ec198fa43 | ||
|
|
77e96624ee | ||
|
|
5e02809db2 | ||
|
|
6fe001fcf8 | ||
|
|
f646102262 | ||
|
|
b97f4e16ba | ||
|
|
0c14306889 | ||
|
|
f1d043f67b | ||
|
|
c39a6e81d7 | ||
|
|
9c56c7e198 | ||
|
|
6484fef8ea | ||
|
|
8a194481ac | ||
|
|
072b817792 | ||
|
|
d32f7d36a6 | ||
|
|
54c9d4e725 | ||
|
|
d2637c3de2 | ||
|
|
2550324003 | ||
|
|
bf52dd8174 | ||
|
|
2ecec57d2f | ||
|
|
b5fda0e020 | ||
|
|
45a60cd9a7 | ||
|
|
2b82675853 | ||
|
|
1d3bf1ca73 | ||
|
|
04cb9c96fe | ||
|
|
45d8ac2eee | ||
|
|
eb60331c88 | ||
|
|
8fc9b0a22d | ||
|
|
ec5fd9e343 | ||
|
|
1e49939c38 | ||
|
|
5a7b23aa00 | ||
|
|
791505b7b8 | ||
|
|
da10649adb | ||
|
|
a025e3960d | ||
|
|
98ed348de9 | ||
|
|
b9dcc36b31 | ||
|
|
7a3d3844ae | ||
|
|
6ea2cf149a | ||
|
|
c30677d8b0 | ||
|
|
a1a2fb5628 | ||
|
|
f9cb0e24d6 | ||
|
|
2dc42183cb | ||
|
|
c781c11d26 | ||
|
|
e178cfe5c0 | ||
|
|
3b24373cd0 | ||
|
|
125ed8aa7a | ||
|
|
0b60a03e5d | ||
|
|
bb3f17ebfe | ||
|
|
1695710cbe | ||
|
|
ebe8506c67 | ||
|
|
3c3bd9884f | ||
|
|
f188383fea | ||
|
|
bbab359813 | ||
|
|
8381ca5287 | ||
|
|
f5282bf1e7 | ||
|
|
c0ffc0aaf5 |
|
|
@ -1,4 +1,2 @@
|
||||||
.git*
|
.git*
|
||||||
.idea*
|
.idea*
|
||||||
*.md
|
|
||||||
.venv/
|
|
||||||
|
|
@ -6,4 +6,12 @@ updates:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
timezone: "Asia/Shanghai"
|
timezone: "Asia/Shanghai"
|
||||||
day: "friday"
|
day: "friday"
|
||||||
target-branch: "v3"
|
target-branch: "v2"
|
||||||
|
groups:
|
||||||
|
python-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
# ignore:
|
||||||
|
# - dependency-name: "pymupdf"
|
||||||
|
# versions: ["*"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,33 @@ 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-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check Disk Space
|
||||||
|
run: df -h
|
||||||
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
uses: jlumbroso/free-disk-space@main
|
||||||
|
with:
|
||||||
|
tool-cache: true
|
||||||
|
android: true
|
||||||
|
dotnet: true
|
||||||
|
haskell: true
|
||||||
|
large-packages: true
|
||||||
|
docker-images: true
|
||||||
|
swap-storage: true
|
||||||
|
- name: Check Disk Space
|
||||||
|
run: df -h
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref_name }}
|
ref: main
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
id: prepare
|
id: prepare
|
||||||
run: |
|
run: |
|
||||||
DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-base
|
DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-python-pg
|
||||||
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
|
DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}
|
||||||
TAG_NAME=python3.11-pg17.6
|
TAG_NAME=python3.11-pg15.8
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest"
|
||||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||||
echo ::set-output name=version::${TAG_NAME}
|
echo ::set-output name=version::${TAG_NAME}
|
||||||
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \
|
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \
|
||||||
|
|
@ -37,7 +51,8 @@ jobs:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
cache-image: false
|
# 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
|
||||||
|
|
@ -48,4 +63,4 @@ jobs:
|
||||||
password: ${{ secrets.GH_TOKEN }}
|
password: ${{ secrets.GH_TOKEN }}
|
||||||
- name: Docker Buildx (build-and-push)
|
- name: Docker Buildx (build-and-push)
|
||||||
run: |
|
run: |
|
||||||
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-base
|
docker buildx build --output "type=image,push=true" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-python-pg
|
||||||
|
|
@ -5,7 +5,7 @@ on:
|
||||||
inputs:
|
inputs:
|
||||||
dockerImageTag:
|
dockerImageTag:
|
||||||
description: 'Docker Image Tag'
|
description: 'Docker Image Tag'
|
||||||
default: 'v2.0.2'
|
default: 'v1.0.1'
|
||||||
required: true
|
required: true
|
||||||
architecture:
|
architecture:
|
||||||
description: 'Architecture'
|
description: 'Architecture'
|
||||||
|
|
@ -19,12 +19,26 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push-vector-model-to-ghcr:
|
build-and-push-vector-model-to-ghcr:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
|
- name: Check Disk Space
|
||||||
|
run: df -h
|
||||||
|
- name: Free Disk Space (Ubuntu)
|
||||||
|
uses: jlumbroso/free-disk-space@main
|
||||||
|
with:
|
||||||
|
tool-cache: true
|
||||||
|
android: true
|
||||||
|
dotnet: true
|
||||||
|
haskell: true
|
||||||
|
large-packages: true
|
||||||
|
docker-images: true
|
||||||
|
swap-storage: true
|
||||||
|
- name: Check Disk Space
|
||||||
|
run: df -h
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref_name }}
|
ref: main
|
||||||
- name: Prepare
|
- name: Prepare
|
||||||
id: prepare
|
id: prepare
|
||||||
run: |
|
run: |
|
||||||
|
|
@ -42,7 +56,8 @@ jobs:
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
cache-image: false
|
# 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
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
name: build-and-push
|
name: build-and-push
|
||||||
|
|
||||||
run-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }}) (${{ github.event.inputs.architecture }})
|
run-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }})
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
dockerImageTag:
|
dockerImageTag:
|
||||||
description: 'Image Tag'
|
description: 'Image Tag'
|
||||||
default: 'v2.1.1-dev'
|
default: 'v1.10.7-dev'
|
||||||
required: true
|
required: true
|
||||||
dockerImageTagWithLatest:
|
dockerImageTagWithLatest:
|
||||||
description: '是否发布latest tag(正式发版时选择,测试版本切勿选择)'
|
description: '是否发布latest tag(正式发版时选择,测试版本切勿选择)'
|
||||||
|
|
@ -38,14 +38,9 @@ jobs:
|
||||||
if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}
|
if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clear Work Dir
|
|
||||||
run: |
|
|
||||||
ls -la
|
|
||||||
rm -rf -- ./* ./.??*
|
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
if: ${{ contains(github.event.inputs.architecture, ',') }}
|
|
||||||
uses: jlumbroso/free-disk-space@main
|
uses: jlumbroso/free-disk-space@main
|
||||||
with:
|
with:
|
||||||
tool-cache: true
|
tool-cache: true
|
||||||
|
|
@ -53,8 +48,8 @@ jobs:
|
||||||
dotnet: true
|
dotnet: true
|
||||||
haskell: true
|
haskell: true
|
||||||
large-packages: true
|
large-packages: true
|
||||||
docker-images: false
|
docker-images: true
|
||||||
swap-storage: false
|
swap-storage: true
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -69,17 +64,15 @@ jobs:
|
||||||
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
||||||
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
||||||
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
|
||||||
else
|
else
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
||||||
fi
|
fi
|
||||||
echo "buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
||||||
--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=`git rev-parse --short HEAD` --no-cache \
|
||||||
${DOCKER_IMAGE_TAGS} ." >> $GITHUB_OUTPUT
|
${DOCKER_IMAGE_TAGS} .
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
|
||||||
cache-image: false
|
|
||||||
- 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
|
||||||
|
|
@ -94,12 +87,6 @@ jobs:
|
||||||
registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }}
|
registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }}
|
||||||
username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }}
|
username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
|
password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}
|
||||||
- name: Build Web
|
|
||||||
run: |
|
|
||||||
docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile
|
|
||||||
rm -rf ./ui
|
|
||||||
cp -r ./web-build-output/ui ./
|
|
||||||
rm -rf ./web-build-output
|
|
||||||
- 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
|
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
|
||||||
|
|
@ -109,14 +96,9 @@ jobs:
|
||||||
if: ${{ contains(github.event.inputs.registry, 'dockerhub') }}
|
if: ${{ contains(github.event.inputs.registry, 'dockerhub') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Clear Work Dir
|
|
||||||
run: |
|
|
||||||
ls -la
|
|
||||||
rm -rf -- ./* ./.??*
|
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
if: ${{ contains(github.event.inputs.architecture, ',') }}
|
|
||||||
uses: jlumbroso/free-disk-space@main
|
uses: jlumbroso/free-disk-space@main
|
||||||
with:
|
with:
|
||||||
tool-cache: true
|
tool-cache: true
|
||||||
|
|
@ -124,8 +106,8 @@ jobs:
|
||||||
dotnet: true
|
dotnet: true
|
||||||
haskell: true
|
haskell: true
|
||||||
large-packages: true
|
large-packages: true
|
||||||
docker-images: false
|
docker-images: true
|
||||||
swap-storage: false
|
swap-storage: true
|
||||||
- name: Check Disk Space
|
- name: Check Disk Space
|
||||||
run: df -h
|
run: df -h
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|
@ -140,17 +122,15 @@ jobs:
|
||||||
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
TAG_NAME=${{ github.event.inputs.dockerImageTag }}
|
||||||
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}
|
||||||
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*}"
|
||||||
else
|
else
|
||||||
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
DOCKER_IMAGE_TAGS="--tag ${DOCKER_IMAGE}:${TAG_NAME}"
|
||||||
fi
|
fi
|
||||||
echo "buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --memory-swap -1 \
|
||||||
--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=`git rev-parse --short HEAD` --no-cache \
|
||||||
${DOCKER_IMAGE_TAGS} ." >> $GITHUB_OUTPUT
|
${DOCKER_IMAGE_TAGS} .
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
|
||||||
cache-image: false
|
|
||||||
- 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
|
||||||
|
|
@ -164,12 +144,6 @@ jobs:
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build Web
|
|
||||||
run: |
|
|
||||||
docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile
|
|
||||||
rm -rf ./ui
|
|
||||||
cp -r ./web-build-output/ui ./
|
|
||||||
rm -rf ./web-build-output
|
|
||||||
- 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
|
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
name: Typos Check
|
name: Typos Check
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
|
@ -11,19 +12,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions Repository
|
- name: Checkout Actions Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
ref: ${{ github.ref_name }}
|
|
||||||
- name: Create config file
|
|
||||||
run: |
|
|
||||||
cat <<EOF > typo-check-config.toml
|
|
||||||
[files]
|
|
||||||
extend-exclude = [
|
|
||||||
"**/*_svg",
|
|
||||||
"**/migrations/**"
|
|
||||||
]
|
|
||||||
EOF
|
|
||||||
- name: Check spelling
|
- name: Check spelling
|
||||||
uses: crate-ci/typos@master
|
uses: crate-ci/typos@master
|
||||||
with:
|
|
||||||
config: ./typo-check-config.toml
|
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,9 @@ celerybeat.pid
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
# env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
# ENV/
|
ENV/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
|
|
@ -183,7 +183,5 @@ apps/xpack
|
||||||
data
|
data
|
||||||
.dev
|
.dev
|
||||||
poetry.lock
|
poetry.lock
|
||||||
uv.lock
|
apps/setting/models_provider/impl/*/icon/
|
||||||
apps/models_provider/impl/*/icon/
|
tmp/
|
||||||
tmp/
|
|
||||||
config.yml
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
'apps/setting/models_provider/impl/*/icon/*'
|
||||||
|
]
|
||||||
|
|
@ -27,4 +27,4 @@ When reporting issues, always include:
|
||||||
* Snapshots or log files if needed
|
* Snapshots or log files if needed
|
||||||
|
|
||||||
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can
|
||||||
replace those parts with "REDACTED" or other strings like "****".
|
replace those parts with "REDACTED" or other strings like "****".
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
|
|
|
||||||
75
README.md
75
README.md
|
|
@ -14,7 +14,7 @@
|
||||||
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 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.
|
||||||
|
|
||||||
- **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 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.
|
||||||
- **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.
|
- **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.
|
||||||
- **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.
|
- **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.
|
||||||
- **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.).
|
- **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.
|
- **Multi Modal**: Native support for input and output text, image, audio and video.
|
||||||
|
|
@ -24,7 +24,7 @@ MaxKB = Max Knowledge Brain, it is an open-source platform for building enterpri
|
||||||
Execute the script below to start a MaxKB container using Docker:
|
Execute the script below to start a MaxKB container using Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb 1panel/maxkb
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials:
|
Access MaxKB web interface at `http://your_server_ip:8080` with default admin credentials:
|
||||||
|
|
@ -32,18 +32,18 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr
|
||||||
- username: admin
|
- username: admin
|
||||||
- password: MaxKB@123..
|
- password: MaxKB@123..
|
||||||
|
|
||||||
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/v2/installation/offline_installtion/) 进行安装。
|
中国用户如遇到 Docker 镜像 Pull 失败问题,请参照该 [离线安装文档](https://maxkb.cn/docs/installation/offline_installtion/) 进行安装。
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<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://github.com/user-attachments/assets/eb285512-a66a-4752-8941-c65ed1592238" alt="MaxKB Demo1" /></td>
|
<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/user-attachments/assets/f732f1f5-472c-4fd2-93c1-a277eda83d04" alt="MaxKB Demo2" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-models.png" alt="MaxKB Demo2" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/user-attachments/assets/c927474a-9a23-4830-822f-5db26025c9b2" alt="MaxKB Demo3" /></td>
|
<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/e6268996-a46d-4e58-9f30-31139df78ad2" alt="MaxKB Demo4" /></td>
|
<td style="padding: 5px;background-color:#fff;"><img src= "https://maxkb.hk/images/screenshot-function.png" alt="MaxKB Demo4" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
@ -54,6 +54,67 @@ Access MaxKB web interface at `http://your_server_ip:8080` with default admin cr
|
||||||
- LLM Framework:[LangChain](https://www.langchain.com/)
|
- LLM Framework:[LangChain](https://www.langchain.com/)
|
||||||
- Database:[PostgreSQL + pgvector](https://www.postgresql.org/)
|
- Database:[PostgreSQL + pgvector](https://www.postgresql.org/)
|
||||||
|
|
||||||
|
## Feature Comparison
|
||||||
|
|
||||||
|
<table style="width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th align="center">Feature</th>
|
||||||
|
<th align="center">LangChain</th>
|
||||||
|
<th align="center">Dify.AI</th>
|
||||||
|
<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
|
||||||
|
|
||||||
[](https://star-history.com/#1Panel-dev/MaxKB&Date)
|
[](https://star-history.com/#1Panel-dev/MaxKB&Date)
|
||||||
|
|
|
||||||
20
README_CN.md
20
README_CN.md
|
|
@ -14,12 +14,12 @@
|
||||||
</p>
|
</p>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
MaxKB = Max Knowledge Brain,是一个强大易用的企业级智能体平台,致力于解决企业 AI 落地面临的技术门槛高、部署成本高、迭代周期长等问题,助力企业在人工智能时代赢得先机。秉承“开箱即用,伴随成长”的设计理念,MaxKB 支持企业快速接入主流大模型,高效构建专属知识库,并提供从基础问答(RAG)、复杂流程自动化(工作流)到智能体(Agent)的渐进式升级路径,全面赋能智能客服、智能办公助手等多种应用场景。
|
MaxKB = Max Knowledge Brain,是一款强大易用的企业级智能体平台,支持 RAG 检索增强生成、工作流编排、MCP 工具调用能力。MaxKB 支持对接各种主流大语言模型,广泛应用于智能客服、企业内部知识库问答、员工助手、学术研究与教育等场景。
|
||||||
|
|
||||||
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果;
|
- **RAG 检索增强生成**:高效搭建本地 AI 知识库,支持直接上传文档 / 自动爬取在线文档,支持文本自动拆分、向量化,有效减少大模型幻觉,提升问答效果;
|
||||||
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求;
|
- **灵活编排**:内置强大的工作流引擎、函数库和 MCP 工具调用能力,支持编排 AI 工作过程,满足复杂业务场景下的需求;
|
||||||
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度;
|
- **无缝嵌入**:支持零编码快速嵌入到第三方业务系统,让已有系统快速拥有智能问答能力,提高用户满意度;
|
||||||
- **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Qwen 3 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。
|
- **模型中立**:支持对接各种大模型,包括本地私有大模型(DeepSeek R1 / Llama 3 / Qwen 2 等)、国内公共大模型(通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等)和国外公共大模型(OpenAI / Claude / Gemini 等)。
|
||||||
|
|
||||||
MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
||||||
|
|
||||||
|
|
@ -27,10 +27,10 @@ MaxKB 三分钟视频介绍:https://www.bilibili.com/video/BV18JypYeEkj/
|
||||||
|
|
||||||
```
|
```
|
||||||
# Linux 机器
|
# Linux 机器
|
||||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb
|
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 机器
|
# Windows 机器
|
||||||
docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb
|
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
|
# 用户名: admin
|
||||||
# 密码: MaxKB@123..
|
# 密码: MaxKB@123..
|
||||||
|
|
@ -38,8 +38,8 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/opt/maxkb
|
||||||
|
|
||||||
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB;
|
||||||
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
- 如果是内网环境,推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署;
|
||||||
- MaxKB 不同产品产品版本的对比请参见:[MaxKB 产品版本对比](https://maxkb.cn/price);
|
- MaxKB 产品版本分为社区版和专业版,详情请参见:[MaxKB 产品版本对比](https://maxkb.cn/pricing.html);
|
||||||
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://fit2cloud.com/maxkb/download/introduce-maxkb_202507.pdf)。
|
- 如果您需要向团队介绍 MaxKB,可以使用这个 [官方 PPT 材料](https://maxkb.cn/download/introduce-maxkb_202503.pdf)。
|
||||||
|
|
||||||
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
如你有更多问题,可以查看使用手册,或者通过论坛与我们交流。
|
||||||
|
|
||||||
|
|
@ -54,12 +54,12 @@ docker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/opt/maxkb
|
||||||
|
|
||||||
<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://github.com/user-attachments/assets/eb285512-a66a-4752-8941-c65ed1592238" 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://github.com/user-attachments/assets/f732f1f5-472c-4fd2-93c1-a277eda83d04" 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://github.com/user-attachments/assets/c927474a-9a23-4830-822f-5db26025c9b2" 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://github.com/user-attachments/assets/e6268996-a46d-4e58-9f30-31139df78ad2" 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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,4 @@
|
||||||
- [MaxKB 应用案例:重磅!陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA)
|
- [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/2JbVp0-kr9Hfp-0whH4cvg)
|
||||||
- [MaxKB 应用案例:建筑材料工业信息中心完成DeepSeek本地化部署,推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)
|
- [MaxKB 应用案例:建筑材料工业信息中心完成DeepSeek本地化部署,推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)
|
||||||
- [MaxKB 应用案例:一起DeepSeek!福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)
|
- [MaxKB 应用案例:一起DeepSeek!福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
@project: MaxKB
|
|
||||||
@Author:虎虎
|
|
||||||
@file: application_access_token.py
|
|
||||||
@date:2025/6/9 17:46
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
|
|
||||||
from application.serializers.application_access_token import AccessTokenEditSerializer
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationAccessTokenAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
), OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="应用id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return AccessTokenEditSerializer
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
@project: MaxKB
|
|
||||||
@Author:虎虎
|
|
||||||
@file: application.py
|
|
||||||
@date:2025/5/26 16:59
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \
|
|
||||||
ApplicationImportRequest, ApplicationEditSerializer, TextToSpeechRequest, SpeechToTextRequest, PlayDemoTextRequest
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest):
|
|
||||||
work_flow = serializers.DictField(required=True, label=_("Workflow Objects"))
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCreateResponse(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationCreateSerializer.ApplicationResponse()
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationListResult(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationListResponse(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPageResult(ResultPageSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationListResponse(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationQueryAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="current_page",
|
|
||||||
description=_("Current page"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="page_size",
|
|
||||||
description=_("Page size"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="folder_id",
|
|
||||||
description=_("folder id"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="name",
|
|
||||||
description=_("Application Name"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="desc",
|
|
||||||
description=_("Application Description"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="user_id",
|
|
||||||
description=_("User ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="publish_status",
|
|
||||||
description=_("Publish status") + '(published|unpublished)',
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='query',
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationListResult
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_page_response():
|
|
||||||
return ApplicationPageResult
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCreateAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationCreateRequest
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationCreateResponse
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationImportAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
ApplicationCreateAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationImportRequest
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationOperateAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="应用id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationExportAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return ApplicationOperateAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return DefaultResultSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationEditAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationEditSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class TextToSpeechAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return ApplicationOperateAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return TextToSpeechRequest
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return DefaultResultSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class SpeechToTextAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return ApplicationOperateAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return SpeechToTextRequest
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return DefaultResultSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class PlayDemoTextAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return ApplicationOperateAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return PlayDemoTextRequest
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return DefaultResultSerializer
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
|
|
||||||
from application.serializers.application_api_key import EditApplicationKeySerializer, ApplicationKeySerializerModel
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
from common.result import ResultSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationKeyListResult(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationKeySerializerModel(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationKeyResult(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationKeySerializerModel()
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationKeyAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationKeyResult
|
|
||||||
|
|
||||||
class List(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationKeyListResult
|
|
||||||
|
|
||||||
class Operate(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [*ApplicationKeyAPI.get_parameters(), OpenApiParameter(
|
|
||||||
name="api_key_id",
|
|
||||||
description="ApiKeyId",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return EditApplicationKeySerializer
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
@project: MaxKB
|
|
||||||
@Author:虎虎
|
|
||||||
@file: application_chat.py
|
|
||||||
@date:2025/6/10 13:54
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
|
|
||||||
from application.serializers.application_chat import ApplicationChatQuerySerializers, \
|
|
||||||
ApplicationChatResponseSerializers, ApplicationChatRecordExportRequest
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
from common.result import ResultSerializer, ResultPageSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatListResponseSerializers(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationChatResponseSerializers(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatPageResponseSerializers(ResultPageSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationChatResponseSerializers(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatQueryAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationChatQuerySerializers
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
), OpenApiParameter(
|
|
||||||
name="start_time",
|
|
||||||
description="start Time",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="end_time",
|
|
||||||
description="end Time",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="abstract",
|
|
||||||
description="summary",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="username",
|
|
||||||
description="username",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="min_star",
|
|
||||||
description=_("Minimum number of likes"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="min_trample",
|
|
||||||
description=_("Minimum number of clicks"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="comparer",
|
|
||||||
description=_("Comparator"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationChatListResponseSerializers
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatQueryPageAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationChatQueryAPI.get_request()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
*ApplicationChatQueryAPI.get_parameters(),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="current_page",
|
|
||||||
description=_("Current page"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="page_size",
|
|
||||||
description=_("Page size"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationChatPageResponseSerializers
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatExportAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationChatRecordExportRequest
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return ApplicationChatQueryAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return None
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
@project: MaxKB
|
|
||||||
@Author:虎虎
|
|
||||||
@file: application_chat_record.py
|
|
||||||
@date:2025/6/10 15:19
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
|
|
||||||
from application.serializers.application_chat_record import ApplicationChatRecordAddKnowledgeSerializer, \
|
|
||||||
ApplicationChatRecordImproveInstanceSerializer
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordQueryAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="Application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="chat_id",
|
|
||||||
description=_("Chat ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="order_asc",
|
|
||||||
description=_("Is it in order"),
|
|
||||||
type=OpenApiTypes.BOOL,
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordPageQueryAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [*ApplicationChatRecordQueryAPI.get_parameters(),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="current_page",
|
|
||||||
description=_("Current page"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="page_size",
|
|
||||||
description=_("Page size"),
|
|
||||||
type=OpenApiTypes.INT,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)]
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordImproveParagraphAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationChatRecordImproveInstanceSerializer
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="Application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="chat_id",
|
|
||||||
description=_("Chat ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="chat_record_id",
|
|
||||||
description=_("Chat Record ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="knowledge_id",
|
|
||||||
description=_("Knowledge ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="document_id",
|
|
||||||
description=_("Document ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
class Operate(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [*ApplicationChatRecordImproveParagraphAPI.get_parameters(), OpenApiParameter(
|
|
||||||
name="paragraph_id",
|
|
||||||
description=_("Paragraph ID"),
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)]
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationChatRecordAddKnowledgeAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_request():
|
|
||||||
return ApplicationChatRecordAddKnowledgeSerializer
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="Application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)]
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
@project: MaxKB
|
|
||||||
@Author:虎虎
|
|
||||||
@file: application_stats.py
|
|
||||||
@date:2025/6/9 20:45
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
|
|
||||||
from application.serializers.application_stats import ApplicationStatsSerializer
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
from common.result import ResultSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationStatsResult(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationStatsSerializer(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationStatsAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="start_time",
|
|
||||||
description="start Time",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="end_time",
|
|
||||||
description="end Time",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationStatsResult
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
@project: MaxKB
|
|
||||||
@Author:虎虎
|
|
||||||
@file: application_version.py
|
|
||||||
@date:2025/6/4 17:33
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
|
||||||
|
|
||||||
from application.serializers.application_version import ApplicationVersionModelSerializer
|
|
||||||
from common.mixins.api_mixin import APIMixin
|
|
||||||
from common.result import ResultSerializer, PageDataResponse, ResultPageSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationListVersionResult(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationVersionModelSerializer(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationPageVersionResult(ResultPageSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationVersionModelSerializer(many=True)
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationWorkflowVersionResult(ResultSerializer):
|
|
||||||
def get_data(self):
|
|
||||||
return ApplicationVersionModelSerializer()
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationVersionAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="workspace_id",
|
|
||||||
description="工作空间id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_id",
|
|
||||||
description="application ID",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationVersionOperateAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="application_version_id",
|
|
||||||
description="工作流版本id",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
location='path',
|
|
||||||
required=True,
|
|
||||||
)
|
|
||||||
, *ApplicationVersionAPI.get_parameters()
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationWorkflowVersionResult
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationVersionListAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return [
|
|
||||||
OpenApiParameter(
|
|
||||||
name="name",
|
|
||||||
description="Version Name",
|
|
||||||
type=OpenApiTypes.STR,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
, *ApplicationVersionAPI.get_parameters()]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationListVersionResult
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationVersionPageAPI(APIMixin):
|
|
||||||
@staticmethod
|
|
||||||
def get_parameters():
|
|
||||||
return ApplicationVersionListAPI.get_parameters()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_response():
|
|
||||||
return ApplicationPageVersionResult
|
|
||||||
|
|
@ -12,45 +12,42 @@ from typing import Type
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from knowledge.models import Paragraph
|
from dataset.models import Paragraph
|
||||||
|
|
||||||
|
|
||||||
class ParagraphPipelineModel:
|
class ParagraphPipelineModel:
|
||||||
|
|
||||||
def __init__(self, _id: str, document_id: str, knowledge_id: str, content: str, title: str, status: str,
|
def __init__(self, _id: str, document_id: str, dataset_id: str, content: str, title: str, status: str,
|
||||||
is_active: bool, comprehensive_score: float, similarity: float, knowledge_name: str,
|
is_active: bool, comprehensive_score: float, similarity: float, dataset_name: str, document_name: str,
|
||||||
document_name: str,
|
hit_handling_method: str, directly_return_similarity: float, meta: dict = None):
|
||||||
hit_handling_method: str, directly_return_similarity: float, knowledge_type, meta: dict = None):
|
|
||||||
self.id = _id
|
self.id = _id
|
||||||
self.document_id = document_id
|
self.document_id = document_id
|
||||||
self.knowledge_id = knowledge_id
|
self.dataset_id = dataset_id
|
||||||
self.content = content
|
self.content = content
|
||||||
self.title = title
|
self.title = title
|
||||||
self.status = status,
|
self.status = status,
|
||||||
self.is_active = is_active
|
self.is_active = is_active
|
||||||
self.comprehensive_score = comprehensive_score
|
self.comprehensive_score = comprehensive_score
|
||||||
self.similarity = similarity
|
self.similarity = similarity
|
||||||
self.knowledge_name = knowledge_name
|
self.dataset_name = dataset_name
|
||||||
self.document_name = document_name
|
self.document_name = document_name
|
||||||
self.hit_handling_method = hit_handling_method
|
self.hit_handling_method = hit_handling_method
|
||||||
self.directly_return_similarity = directly_return_similarity
|
self.directly_return_similarity = directly_return_similarity
|
||||||
self.meta = meta
|
self.meta = meta
|
||||||
self.knowledge_type = knowledge_type
|
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'document_id': self.document_id,
|
'document_id': self.document_id,
|
||||||
'knowledge_id': self.knowledge_id,
|
'dataset_id': self.dataset_id,
|
||||||
'content': self.content,
|
'content': self.content,
|
||||||
'title': self.title,
|
'title': self.title,
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'is_active': self.is_active,
|
'is_active': self.is_active,
|
||||||
'comprehensive_score': self.comprehensive_score,
|
'comprehensive_score': self.comprehensive_score,
|
||||||
'similarity': self.similarity,
|
'similarity': self.similarity,
|
||||||
'knowledge_name': self.knowledge_name,
|
'dataset_name': self.dataset_name,
|
||||||
'document_name': self.document_name,
|
'document_name': self.document_name,
|
||||||
'knowledge_type': self.knowledge_type,
|
|
||||||
'meta': self.meta,
|
'meta': self.meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,8 +57,7 @@ class ParagraphPipelineModel:
|
||||||
self.paragraph = {}
|
self.paragraph = {}
|
||||||
self.comprehensive_score = None
|
self.comprehensive_score = None
|
||||||
self.document_name = None
|
self.document_name = None
|
||||||
self.knowledge_name = None
|
self.dataset_name = None
|
||||||
self.knowledge_type = None
|
|
||||||
self.hit_handling_method = None
|
self.hit_handling_method = None
|
||||||
self.directly_return_similarity = 0.9
|
self.directly_return_similarity = 0.9
|
||||||
self.meta = {}
|
self.meta = {}
|
||||||
|
|
@ -70,7 +66,7 @@ class ParagraphPipelineModel:
|
||||||
if isinstance(paragraph, Paragraph):
|
if isinstance(paragraph, Paragraph):
|
||||||
self.paragraph = {'id': paragraph.id,
|
self.paragraph = {'id': paragraph.id,
|
||||||
'document_id': paragraph.document_id,
|
'document_id': paragraph.document_id,
|
||||||
'knowledge_id': paragraph.knowledge_id,
|
'dataset_id': paragraph.dataset_id,
|
||||||
'content': paragraph.content,
|
'content': paragraph.content,
|
||||||
'title': paragraph.title,
|
'title': paragraph.title,
|
||||||
'status': paragraph.status,
|
'status': paragraph.status,
|
||||||
|
|
@ -80,12 +76,8 @@ class ParagraphPipelineModel:
|
||||||
self.paragraph = paragraph
|
self.paragraph = paragraph
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_knowledge_name(self, knowledge_name):
|
def add_dataset_name(self, dataset_name):
|
||||||
self.knowledge_name = knowledge_name
|
self.dataset_name = dataset_name
|
||||||
return self
|
|
||||||
|
|
||||||
def add_knowledge_type(self, knowledge_type):
|
|
||||||
self.knowledge_type = knowledge_type
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_document_name(self, document_name):
|
def add_document_name(self, document_name):
|
||||||
|
|
@ -114,13 +106,12 @@ class ParagraphPipelineModel:
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')),
|
return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')),
|
||||||
str(self.paragraph.get('knowledge_id')),
|
str(self.paragraph.get('dataset_id')),
|
||||||
self.paragraph.get('content'), self.paragraph.get('title'),
|
self.paragraph.get('content'), self.paragraph.get('title'),
|
||||||
self.paragraph.get('status'),
|
self.paragraph.get('status'),
|
||||||
self.paragraph.get('is_active'),
|
self.paragraph.get('is_active'),
|
||||||
self.comprehensive_score, self.similarity, self.knowledge_name,
|
self.comprehensive_score, self.similarity, self.dataset_name,
|
||||||
self.document_name, self.hit_handling_method, self.directly_return_similarity,
|
self.document_name, self.hit_handling_method, self.directly_return_similarity,
|
||||||
self.knowledge_type,
|
|
||||||
self.meta)
|
self.meta)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,12 @@ from common.handle.impl.response.system_to_response import SystemToResponse
|
||||||
|
|
||||||
class PipelineManage:
|
class PipelineManage:
|
||||||
def __init__(self, step_list: List[Type[IBaseChatPipelineStep]],
|
def __init__(self, step_list: List[Type[IBaseChatPipelineStep]],
|
||||||
base_to_response: BaseToResponse = SystemToResponse(),
|
base_to_response: BaseToResponse = SystemToResponse()):
|
||||||
debug=False):
|
|
||||||
# 步骤执行器
|
# 步骤执行器
|
||||||
self.step_list = [step() for step in step_list]
|
self.step_list = [step() for step in step_list]
|
||||||
# 上下文
|
# 上下文
|
||||||
self.context = {'message_tokens': 0, 'answer_tokens': 0}
|
self.context = {'message_tokens': 0, 'answer_tokens': 0}
|
||||||
self.base_to_response = base_to_response
|
self.base_to_response = base_to_response
|
||||||
self.debug = debug
|
|
||||||
|
|
||||||
def run(self, context: Dict = None):
|
def run(self, context: Dict = None):
|
||||||
self.context['start_time'] = time.time()
|
self.context['start_time'] = time.time()
|
||||||
|
|
@ -46,7 +44,6 @@ class PipelineManage:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.step_list: List[Type[IBaseChatPipelineStep]] = []
|
self.step_list: List[Type[IBaseChatPipelineStep]] = []
|
||||||
self.base_to_response = SystemToResponse()
|
self.base_to_response = SystemToResponse()
|
||||||
self.debug = False
|
|
||||||
|
|
||||||
def append_step(self, step: Type[IBaseChatPipelineStep]):
|
def append_step(self, step: Type[IBaseChatPipelineStep]):
|
||||||
self.step_list.append(step)
|
self.step_list.append(step)
|
||||||
|
|
@ -56,9 +53,5 @@ class PipelineManage:
|
||||||
self.base_to_response = base_to_response
|
self.base_to_response = base_to_response
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_debug(self, debug):
|
|
||||||
self.debug = debug
|
|
||||||
return self
|
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
return PipelineManage(step_list=self.step_list, base_to_response=self.base_to_response, debug=self.debug)
|
return PipelineManage(step_list=self.step_list, base_to_response=self.base_to_response)
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,9 @@ 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
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from application.serializers.application import NoReferencesSetting
|
from application.serializers.application_serializers import NoReferencesSetting
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
|
|
||||||
class ModelField(serializers.Field):
|
class ModelField(serializers.Field):
|
||||||
|
|
@ -44,7 +45,7 @@ class PostResponseHandler:
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str,
|
def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str,
|
||||||
answer_text,
|
answer_text,
|
||||||
manage, step, padding_problem_text: str = None, **kwargs):
|
manage, step, padding_problem_text: str = None, client_id=None, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -52,36 +53,35 @@ 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),
|
||||||
label=_("Conversation list"))
|
error_messages=ErrMessage.list(_("Conversation list")))
|
||||||
model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Model id"))
|
model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid(_("Model id")))
|
||||||
# 段落列表
|
# 段落列表
|
||||||
paragraph_list = serializers.ListField(label=_("Paragraph List"))
|
paragraph_list = serializers.ListField(error_messages=ErrMessage.list(_("Paragraph List")))
|
||||||
# 对话id
|
# 对话id
|
||||||
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
chat_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("Conversation ID")))
|
||||||
# 用户问题
|
# 用户问题
|
||||||
problem_text = serializers.CharField(required=True, label=_("User Questions"))
|
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.uuid(_("User Questions")))
|
||||||
# 后置处理器
|
# 后置处理器
|
||||||
post_response_handler = InstanceField(model_type=PostResponseHandler,
|
post_response_handler = InstanceField(model_type=PostResponseHandler,
|
||||||
label=_("Post-processor"))
|
error_messages=ErrMessage.base(_("Post-processor")))
|
||||||
# 补全问题
|
# 补全问题
|
||||||
padding_problem_text = serializers.CharField(required=False,
|
padding_problem_text = serializers.CharField(required=False,
|
||||||
label=_("Completion Question"))
|
error_messages=ErrMessage.base(_("Completion Question")))
|
||||||
# 是否使用流的形式输出
|
# 是否使用流的形式输出
|
||||||
stream = serializers.BooleanField(required=False, label=_("Streaming Output"))
|
stream = serializers.BooleanField(required=False, error_messages=ErrMessage.base(_("Streaming Output")))
|
||||||
chat_user_id = serializers.CharField(required=True, label=_("Chat user id"))
|
client_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client id")))
|
||||||
|
client_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Client Type")))
|
||||||
chat_user_type = serializers.CharField(required=True, label=_("Chat user Type"))
|
|
||||||
# 未查询到引用分段
|
# 未查询到引用分段
|
||||||
no_references_setting = NoReferencesSetting(required=True,
|
no_references_setting = NoReferencesSetting(required=True,
|
||||||
label=_("No reference segment settings"))
|
error_messages=ErrMessage.base(_("No reference segment settings")))
|
||||||
|
|
||||||
workspace_id = serializers.CharField(required=True, label=_("Workspace ID"))
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
|
||||||
|
|
||||||
model_setting = serializers.DictField(required=True, allow_null=True,
|
model_setting = serializers.DictField(required=True, allow_null=True,
|
||||||
label=_("Model settings"))
|
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,
|
||||||
label=_("Model parameter settings"))
|
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)
|
||||||
|
|
@ -102,9 +102,9 @@ class IChatStep(IBaseChatPipelineStep):
|
||||||
chat_id, problem_text,
|
chat_id, problem_text,
|
||||||
post_response_handler: PostResponseHandler,
|
post_response_handler: PostResponseHandler,
|
||||||
model_id: str = None,
|
model_id: str = None,
|
||||||
workspace_id: str = None,
|
user_id: str = None,
|
||||||
paragraph_list=None,
|
paragraph_list=None,
|
||||||
manage: PipelineManage = None,
|
manage: PipelineManage = None,
|
||||||
padding_problem_text: str = None, stream: bool = True, chat_user_id=None, chat_user_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, model_setting=None, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import uuid_utils.compat as uuid
|
import uuid
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
@ -18,24 +18,22 @@ 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
|
||||||
from langchain_core.messages import AIMessageChunk, SystemMessage
|
from langchain_core.messages import AIMessageChunk
|
||||||
from rest_framework import status
|
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.flow.tools import Reasoning
|
||||||
from application.models import ApplicationChatUserStats, ChatUserType
|
from application.models.api_key_model import ApplicationPublicAccessClient
|
||||||
from common.utils.logger import maxkb_logger
|
from common.constants.authentication_type import AuthenticationType
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
|
||||||
|
|
||||||
def add_access_num(chat_user_id=None, chat_user_type=None, application_id=None):
|
def add_access_num(client_id=None, client_type=None, application_id=None):
|
||||||
if [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__(
|
if client_type == AuthenticationType.APPLICATION_ACCESS_TOKEN.value and application_id is not None:
|
||||||
chat_user_type) and application_id is not None:
|
application_public_access_client = (QuerySet(ApplicationPublicAccessClient).filter(client_id=client_id,
|
||||||
application_public_access_client = (QuerySet(ApplicationChatUserStats).filter(chat_user_id=chat_user_id,
|
application_id=application_id)
|
||||||
chat_user_type=chat_user_type,
|
|
||||||
application_id=application_id)
|
|
||||||
.first())
|
.first())
|
||||||
if application_public_access_client is not None:
|
if application_public_access_client is not None:
|
||||||
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
||||||
|
|
@ -65,7 +63,7 @@ def event_content(response,
|
||||||
message_list: List[BaseMessage],
|
message_list: List[BaseMessage],
|
||||||
problem_text: str,
|
problem_text: str,
|
||||||
padding_problem_text: str = None,
|
padding_problem_text: str = None,
|
||||||
chat_user_id=None, chat_user_type=None,
|
client_id=None, client_type=None,
|
||||||
is_ai_chat: bool = None,
|
is_ai_chat: bool = None,
|
||||||
model_setting=None):
|
model_setting=None):
|
||||||
if model_setting is None:
|
if model_setting is None:
|
||||||
|
|
@ -126,24 +124,26 @@ 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,
|
all_text, manage, step, padding_problem_text, client_id,
|
||||||
reasoning_content=reasoning_content if reasoning_content_enable else '')
|
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,
|
||||||
{'node_is_end': True, 'view_type': 'many_view',
|
{'node_is_end': True, 'view_type': 'many_view',
|
||||||
'node_type': 'ai-chat-node'})
|
'node_type': 'ai-chat-node'})
|
||||||
if not manage.debug:
|
add_access_num(client_id, client_type, manage.context.get('application_id'))
|
||||||
add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
maxkb_logger.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 = 'Exception:' + 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, reasoning_content='')
|
all_text, manage, step, padding_problem_text, client_id, reasoning_content='',
|
||||||
if not manage.debug:
|
asker=asker)
|
||||||
add_access_num(chat_user_id, chat_user_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), 'ai-chat-node',
|
||||||
[], all_text,
|
[], all_text,
|
||||||
False,
|
False,
|
||||||
|
|
@ -160,28 +160,27 @@ class BaseChatStep(IChatStep):
|
||||||
problem_text,
|
problem_text,
|
||||||
post_response_handler: PostResponseHandler,
|
post_response_handler: PostResponseHandler,
|
||||||
model_id: str = None,
|
model_id: str = None,
|
||||||
workspace_id: str = None,
|
user_id: str = None,
|
||||||
paragraph_list=None,
|
paragraph_list=None,
|
||||||
manage: PipelineManage = None,
|
manage: PipelineManage = None,
|
||||||
padding_problem_text: str = None,
|
padding_problem_text: str = None,
|
||||||
stream: bool = True,
|
stream: bool = True,
|
||||||
chat_user_id=None, chat_user_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,
|
model_setting=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_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, chat_user_id, chat_user_type,
|
manage, padding_problem_text, client_id, client_type, no_references_setting,
|
||||||
no_references_setting,
|
|
||||||
model_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, chat_user_id, chat_user_type, no_references_setting,
|
manage, padding_problem_text, client_id, client_type, no_references_setting,
|
||||||
model_setting)
|
model_setting)
|
||||||
|
|
||||||
def get_details(self, manage, **kwargs):
|
def get_details(self, manage, **kwargs):
|
||||||
|
|
@ -198,8 +197,7 @@ class BaseChatStep(IChatStep):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_message_list(message_list: List[BaseMessage], answer_text):
|
def reset_message_list(message_list: List[BaseMessage], answer_text):
|
||||||
result = [{'role': 'user' if isinstance(message, HumanMessage) else (
|
result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for
|
||||||
'system' if isinstance(message, SystemMessage) else 'ai'), 'content': message.content} for
|
|
||||||
message
|
message
|
||||||
in
|
in
|
||||||
message_list]
|
message_list]
|
||||||
|
|
@ -237,17 +235,16 @@ 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,
|
||||||
chat_user_id=None, chat_user_type=None,
|
client_id=None, client_type=None,
|
||||||
no_references_setting=None,
|
no_references_setting=None,
|
||||||
model_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.uuid7()
|
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, chat_user_id, chat_user_type, is_ai_chat,
|
padding_problem_text, client_id, client_type, is_ai_chat, model_setting),
|
||||||
model_setting),
|
|
||||||
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'
|
||||||
|
|
@ -283,14 +280,14 @@ 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,
|
||||||
chat_user_id=None, chat_user_type=None, no_references_setting=None,
|
client_id=None, client_type=None, no_references_setting=None,
|
||||||
model_setting=None):
|
model_setting=None):
|
||||||
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
|
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
|
||||||
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
|
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
|
||||||
reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')
|
reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')
|
||||||
reasoning = Reasoning(reasoning_content_start,
|
reasoning = Reasoning(reasoning_content_start,
|
||||||
reasoning_content_end)
|
reasoning_content_end)
|
||||||
chat_record_id = uuid.uuid7()
|
chat_record_id = uuid.uuid1()
|
||||||
# 调用模型
|
# 调用模型
|
||||||
try:
|
try:
|
||||||
chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,
|
chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,
|
||||||
|
|
@ -310,11 +307,12 @@ class BaseChatStep(IChatStep):
|
||||||
else:
|
else:
|
||||||
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get(
|
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get(
|
||||||
'reasoning_content')
|
'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,
|
content, manage, self, padding_problem_text, client_id,
|
||||||
reasoning_content=reasoning_content)
|
reasoning_content=reasoning_content if reasoning_content_enable else '',
|
||||||
if not manage.debug:
|
asker=asker)
|
||||||
add_access_num(chat_user_id, chat_user_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,
|
content, True,
|
||||||
request_token, response_token,
|
request_token, response_token,
|
||||||
|
|
@ -327,9 +325,10 @@ class BaseChatStep(IChatStep):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
all_text = 'Exception:' + str(e)
|
all_text = 'Exception:' + 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, reasoning_content='')
|
all_text, manage, self, padding_problem_text, client_id, reasoning_content='',
|
||||||
if not manage.debug:
|
asker=asker)
|
||||||
add_access_num(chat_user_id, chat_user_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)
|
||||||
|
|
|
||||||
|
|
@ -16,35 +16,34 @@ 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
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from application.serializers.application import NoReferencesSetting
|
from application.serializers.application_serializers import NoReferencesSetting
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
|
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, label=_("question"))
|
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("question")))
|
||||||
# 段落列表
|
# 段落列表
|
||||||
paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True),
|
paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True),
|
||||||
label=_("Paragraph List"))
|
error_messages=ErrMessage.list(_("Paragraph 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),
|
||||||
label=_("History Questions"))
|
error_messages=ErrMessage.list(_("History Questions")))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
|
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
||||||
# 最大携带知识库段落长度
|
# 最大携带知识库段落长度
|
||||||
max_paragraph_char_number = serializers.IntegerField(required=True,
|
max_paragraph_char_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(
|
||||||
label=_("Maximum length of the knowledge base paragraph"))
|
_("Maximum length of the knowledge base paragraph")))
|
||||||
# 模板
|
# 模板
|
||||||
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
||||||
system = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
system = serializers.CharField(required=False, allow_null=True, allow_blank=True,
|
||||||
label=_("System prompt words (role)"))
|
error_messages=ErrMessage.char(_("System prompt words (role)")))
|
||||||
# 补齐问题
|
# 补齐问题
|
||||||
padding_problem_text = serializers.CharField(required=False,
|
padding_problem_text = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Completion problem")))
|
||||||
label=_("Completion problem"))
|
|
||||||
# 未查询到引用分段
|
# 未查询到引用分段
|
||||||
no_references_setting = NoReferencesSetting(required=True,
|
no_references_setting = NoReferencesSetting(required=True, error_messages=ErrMessage.base(_("No reference segment settings")))
|
||||||
label=_("No reference segment settings"))
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineMode
|
||||||
from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \
|
from application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \
|
||||||
IGenerateHumanMessageStep
|
IGenerateHumanMessageStep
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from common.utils.common import flat_map
|
from common.util.split_model import flat_map
|
||||||
|
|
||||||
|
|
||||||
class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):
|
class BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):
|
||||||
|
|
|
||||||
|
|
@ -16,20 +16,22 @@ 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.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
|
||||||
|
|
||||||
|
|
||||||
class IResetProblemStep(IBaseChatPipelineStep):
|
class IResetProblemStep(IBaseChatPipelineStep):
|
||||||
class InstanceSerializer(serializers.Serializer):
|
class InstanceSerializer(serializers.Serializer):
|
||||||
# 问题文本
|
# 问题文本
|
||||||
problem_text = serializers.CharField(required=True, label=_("question"))
|
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.float(_("question")))
|
||||||
# 历史对答
|
# 历史对答
|
||||||
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
||||||
label=_("History Questions"))
|
error_messages=ErrMessage.list(_("History Questions")))
|
||||||
# 大语言模型
|
# 大语言模型
|
||||||
model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Model id"))
|
model_id = serializers.UUIDField(required=False, allow_null=True, error_messages=ErrMessage.uuid(_("Model id")))
|
||||||
workspace_id = serializers.CharField(required=True, label=_("User ID"))
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
|
||||||
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
|
problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
|
||||||
label=_("Question completion prompt"))
|
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
|
||||||
|
|
@ -50,6 +52,6 @@ class IResetProblemStep(IBaseChatPipelineStep):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
||||||
problem_optimization_prompt=None,
|
problem_optimization_prompt=None,
|
||||||
workspace_id=None,
|
user_id=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ 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
|
||||||
from application.models import ChatRecord
|
from application.models import ChatRecord
|
||||||
from common.utils.split_model import flat_map
|
from common.util.split_model import flat_map
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_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")
|
"() 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")
|
||||||
|
|
@ -23,9 +23,9 @@ prompt = _(
|
||||||
class BaseResetProblemStep(IResetProblemStep):
|
class BaseResetProblemStep(IResetProblemStep):
|
||||||
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,
|
||||||
problem_optimization_prompt=None,
|
problem_optimization_prompt=None,
|
||||||
workspace_id=None,
|
user_id=None,
|
||||||
**kwargs) -> str:
|
**kwargs) -> str:
|
||||||
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id) if model_id is not None else None
|
chat_model = get_model_instance_by_model_user_id(model_id, user_id) if model_id is not None else None
|
||||||
if chat_model is None:
|
if chat_model is None:
|
||||||
return problem_text
|
return problem_text
|
||||||
start_index = len(history_chat_record) - 3
|
start_index = len(history_chat_record) - 3
|
||||||
|
|
|
||||||
|
|
@ -16,62 +16,62 @@ 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
|
||||||
from application.chat_pipeline.pipeline_manage import PipelineManage
|
from application.chat_pipeline.pipeline_manage import PipelineManage
|
||||||
|
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, label=_("question"))
|
problem_text = serializers.CharField(required=True, error_messages=ErrMessage.char(_("question")))
|
||||||
# 系统补全问题文本
|
# 系统补全问题文本
|
||||||
padding_problem_text = serializers.CharField(required=False,
|
padding_problem_text = serializers.CharField(required=False,
|
||||||
label=_("System completes question text"))
|
error_messages=ErrMessage.char(_("System completes question text")))
|
||||||
# 需要查询的数据集id列表
|
# 需要查询的数据集id列表
|
||||||
knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
||||||
label=_("Dataset id list"))
|
error_messages=ErrMessage.list(_("Dataset id list")))
|
||||||
# 需要排除的文档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),
|
||||||
label=_("List of document ids to exclude"))
|
error_messages=ErrMessage.list(_("List of document ids to exclude")))
|
||||||
# 需要排除向量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),
|
||||||
label=_("List of exclusion vector ids"))
|
error_messages=ErrMessage.list(_("List of exclusion vector ids")))
|
||||||
# 需要查询的条数
|
# 需要查询的条数
|
||||||
top_n = serializers.IntegerField(required=True,
|
top_n = serializers.IntegerField(required=True,
|
||||||
label=_("Reference segment number"))
|
error_messages=ErrMessage.integer(_("Reference segment number")))
|
||||||
# 相似度 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,
|
||||||
label=_("Similarity"))
|
error_messages=ErrMessage.float(_("Similarity")))
|
||||||
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=_("The type only supports embedding|keywords|blend"), code=500)
|
||||||
], label=_("Retrieval Mode"))
|
], error_messages=ErrMessage.char(_("Retrieval Mode")))
|
||||||
workspace_id = serializers.CharField(required=True, label=_("Workspace ID"))
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User 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
|
||||||
|
|
||||||
def _run(self, manage: PipelineManage):
|
def _run(self, manage: PipelineManage):
|
||||||
paragraph_list = self.execute(**self.context['step_args'], manage=manage)
|
paragraph_list = self.execute(**self.context['step_args'])
|
||||||
manage.context['paragraph_list'] = paragraph_list
|
manage.context['paragraph_list'] = paragraph_list
|
||||||
self.context['paragraph_list'] = paragraph_list
|
self.context['paragraph_list'] = paragraph_list
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str],
|
def execute(self, problem_text: str, dataset_id_list: list[str], exclude_document_id_list: list[str],
|
||||||
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
||||||
search_mode: str = None,
|
search_mode: str = None,
|
||||||
workspace_id=None,
|
user_id=None,
|
||||||
manage: PipelineManage = None,
|
|
||||||
**kwargs) -> List[ParagraphPipelineModel]:
|
**kwargs) -> List[ParagraphPipelineModel]:
|
||||||
"""
|
"""
|
||||||
关于 用户和补全问题 说明: 补全问题如果有就使用补全问题去查询 反之就用用户原始问题查询
|
关于 用户和补全问题 说明: 补全问题如果有就使用补全问题去查询 反之就用用户原始问题查询
|
||||||
:param similarity: 相关性
|
:param similarity: 相关性
|
||||||
:param top_n: 查询多少条
|
:param top_n: 查询多少条
|
||||||
:param problem_text: 用户问题
|
:param problem_text: 用户问题
|
||||||
:param knowledge_id_list: 需要查询的数据集id列表
|
:param dataset_id_list: 需要查询的数据集id列表
|
||||||
:param exclude_document_id_list: 需要排除的文档id
|
:param exclude_document_id_list: 需要排除的文档id
|
||||||
:param exclude_paragraph_id_list: 需要排除段落id
|
:param exclude_paragraph_id_list: 需要排除段落id
|
||||||
:param padding_problem_text 补全问题
|
:param padding_problem_text 补全问题
|
||||||
:param search_mode 检索模式
|
:param search_mode 检索模式
|
||||||
:param workspace_id 工作空间id
|
:param user_id 用户id
|
||||||
:return: 段落列表
|
:return: 段落列表
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -16,58 +16,51 @@ 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
|
||||||
from common.config.embedding_config import VectorStore, ModelManage
|
from common.config.embedding_config import VectorStore, ModelManage
|
||||||
from common.constants.permission_constants import RoleConstants
|
|
||||||
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
|
||||||
from common.db.search import native_search
|
from common.db.search import native_search
|
||||||
from common.utils.common import get_file_content
|
from common.util.file_util import get_file_content
|
||||||
from knowledge.models import Paragraph, Knowledge
|
from dataset.models import Paragraph, DataSet
|
||||||
from knowledge.models import SearchMode
|
from embedding.models import SearchMode
|
||||||
from maxkb.conf import PROJECT_DIR
|
from setting.models import Model
|
||||||
from models_provider.models import Model
|
from setting.models_provider import get_model
|
||||||
from models_provider.tools import get_model, get_model_by_id
|
from smartdoc.conf import PROJECT_DIR
|
||||||
|
|
||||||
|
|
||||||
def reset_meta(meta):
|
def get_model_by_id(_id, user_id):
|
||||||
if not meta.get('allow_download', False):
|
model = QuerySet(Model).filter(id=_id).first()
|
||||||
return {'allow_download': False}
|
if model is None:
|
||||||
return meta
|
raise Exception(_("Model does not exist"))
|
||||||
|
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(message)
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
def get_embedding_id(knowledge_id_list):
|
def get_embedding_id(dataset_id_list):
|
||||||
knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list)
|
dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list)
|
||||||
if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1:
|
if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1:
|
||||||
raise Exception(
|
raise Exception(_("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled."))
|
||||||
_("The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled."))
|
if len(dataset_list) == 0:
|
||||||
if len(knowledge_list) == 0:
|
|
||||||
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base"))
|
raise Exception(_("The knowledge base setting is wrong, please reset the knowledge base"))
|
||||||
return knowledge_list[0].embedding_model_id
|
return dataset_list[0].embedding_mode_id
|
||||||
|
|
||||||
|
|
||||||
class BaseSearchDatasetStep(ISearchDatasetStep):
|
class BaseSearchDatasetStep(ISearchDatasetStep):
|
||||||
|
|
||||||
def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str],
|
def execute(self, problem_text: str, dataset_id_list: list[str], exclude_document_id_list: list[str],
|
||||||
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,
|
||||||
search_mode: str = None,
|
search_mode: str = None,
|
||||||
workspace_id=None,
|
user_id=None,
|
||||||
manage=None,
|
|
||||||
**kwargs) -> List[ParagraphPipelineModel]:
|
**kwargs) -> List[ParagraphPipelineModel]:
|
||||||
get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')
|
if len(dataset_id_list) == 0:
|
||||||
chat_user_type = manage.context.get('chat_user_type')
|
|
||||||
if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:
|
|
||||||
knowledge_id_list = get_knowledge_list_of_authorized(manage.context.get('chat_user_id'),
|
|
||||||
knowledge_id_list)
|
|
||||||
if len(knowledge_id_list) == 0:
|
|
||||||
return []
|
return []
|
||||||
exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text
|
exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text
|
||||||
model_id = get_embedding_id(knowledge_id_list)
|
model_id = get_embedding_id(dataset_id_list)
|
||||||
model = get_model_by_id(model_id, workspace_id)
|
model = get_model_by_id(model_id, user_id)
|
||||||
if model.model_type != "EMBEDDING":
|
|
||||||
raise Exception(_("Model does not exist"))
|
|
||||||
self.context['model_name'] = model.name
|
self.context['model_name'] = model.name
|
||||||
embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model))
|
embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model))
|
||||||
embedding_value = embedding_model.embed_query(exec_problem_text)
|
embedding_value = embedding_model.embed_query(exec_problem_text)
|
||||||
vector = VectorStore.get_embedding_vector()
|
vector = VectorStore.get_embedding_vector()
|
||||||
embedding_list = vector.query(exec_problem_text, embedding_value, knowledge_id_list, exclude_document_id_list,
|
embedding_list = vector.query(exec_problem_text, embedding_value, dataset_id_list, exclude_document_id_list,
|
||||||
exclude_paragraph_id_list, True, top_n, similarity, SearchMode(search_mode))
|
exclude_paragraph_id_list, True, top_n, similarity, SearchMode(search_mode))
|
||||||
if embedding_list is None:
|
if embedding_list is None:
|
||||||
return []
|
return []
|
||||||
|
|
@ -85,12 +78,11 @@ class BaseSearchDatasetStep(ISearchDatasetStep):
|
||||||
.add_paragraph(paragraph)
|
.add_paragraph(paragraph)
|
||||||
.add_similarity(find_embedding.get('similarity'))
|
.add_similarity(find_embedding.get('similarity'))
|
||||||
.add_comprehensive_score(find_embedding.get('comprehensive_score'))
|
.add_comprehensive_score(find_embedding.get('comprehensive_score'))
|
||||||
.add_knowledge_name(paragraph.get('knowledge_name'))
|
.add_dataset_name(paragraph.get('dataset_name'))
|
||||||
.add_knowledge_type(paragraph.get('knowledge_type'))
|
|
||||||
.add_document_name(paragraph.get('document_name'))
|
.add_document_name(paragraph.get('document_name'))
|
||||||
.add_hit_handling_method(paragraph.get('hit_handling_method'))
|
.add_hit_handling_method(paragraph.get('hit_handling_method'))
|
||||||
.add_directly_return_similarity(paragraph.get('directly_return_similarity'))
|
.add_directly_return_similarity(paragraph.get('directly_return_similarity'))
|
||||||
.add_meta(reset_meta(paragraph.get('meta')))
|
.add_meta(paragraph.get('meta'))
|
||||||
.build())
|
.build())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -110,7 +102,7 @@ class BaseSearchDatasetStep(ISearchDatasetStep):
|
||||||
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
||||||
get_file_content(
|
get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
'list_knowledge_paragraph_by_paragraph_id.sql')),
|
'list_dataset_paragraph_by_paragraph_id.sql')),
|
||||||
with_table_name=True)
|
with_table_name=True)
|
||||||
# 如果向量库中存在脏数据 直接删除
|
# 如果向量库中存在脏数据 直接删除
|
||||||
if len(paragraph_list) != len(paragraph_id_list):
|
if len(paragraph_list) != len(paragraph_id_list):
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,6 @@
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Dict
|
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from rest_framework.exceptions import ErrorDetail, ValidationError
|
|
||||||
|
|
||||||
from common.exception.app_exception import AppApiException
|
|
||||||
from common.utils.common import group_by
|
|
||||||
from models_provider.models import Model
|
|
||||||
from models_provider.tools import get_model_credential
|
|
||||||
from tools.models.tool import Tool
|
|
||||||
|
|
||||||
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node',
|
|
||||||
'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node']
|
|
||||||
|
|
||||||
|
|
||||||
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, real_node_id,
|
||||||
|
|
@ -57,207 +42,3 @@ class NodeChunk:
|
||||||
|
|
||||||
def is_end(self):
|
def is_end(self):
|
||||||
return self.status == 200
|
return self.status == 200
|
||||||
|
|
||||||
|
|
||||||
class Edge:
|
|
||||||
def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords):
|
|
||||||
self.id = _id
|
|
||||||
self.type = _type
|
|
||||||
self.sourceNodeId = sourceNodeId
|
|
||||||
self.targetNodeId = targetNodeId
|
|
||||||
for keyword in keywords:
|
|
||||||
self.__setattr__(keyword, keywords.get(keyword))
|
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
|
||||||
def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs):
|
|
||||||
self.id = _id
|
|
||||||
self.type = _type
|
|
||||||
self.x = x
|
|
||||||
self.y = y
|
|
||||||
self.properties = properties
|
|
||||||
for keyword in kwargs:
|
|
||||||
self.__setattr__(keyword, kwargs.get(keyword))
|
|
||||||
|
|
||||||
|
|
||||||
class EdgeNode:
|
|
||||||
edge: Edge
|
|
||||||
node: Node
|
|
||||||
|
|
||||||
def __init__(self, edge, node):
|
|
||||||
self.edge = edge
|
|
||||||
self.node = node
|
|
||||||
|
|
||||||
|
|
||||||
class Workflow:
|
|
||||||
"""
|
|
||||||
节点列表
|
|
||||||
"""
|
|
||||||
nodes: List[Node]
|
|
||||||
"""
|
|
||||||
线列表
|
|
||||||
"""
|
|
||||||
edges: List[Edge]
|
|
||||||
"""
|
|
||||||
节点id:node
|
|
||||||
"""
|
|
||||||
node_map: Dict[str, Node]
|
|
||||||
"""
|
|
||||||
节点id:当前节点id上面的所有节点
|
|
||||||
"""
|
|
||||||
up_node_map: Dict[str, List[EdgeNode]]
|
|
||||||
"""
|
|
||||||
节点id:当前节点id下面的所有节点
|
|
||||||
"""
|
|
||||||
next_node_map: Dict[str, List[EdgeNode]]
|
|
||||||
|
|
||||||
def __init__(self, nodes: List[Node], edges: List[Edge]):
|
|
||||||
self.nodes = nodes
|
|
||||||
self.edges = edges
|
|
||||||
self.node_map = {node.id: node for node in nodes}
|
|
||||||
|
|
||||||
self.up_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.sourceNodeId)) for
|
|
||||||
edge in edges] for
|
|
||||||
key, edges in
|
|
||||||
group_by(edges, key=lambda edge: edge.targetNodeId).items()}
|
|
||||||
|
|
||||||
self.next_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.targetNodeId)) for edge in edges] for
|
|
||||||
key, edges in
|
|
||||||
group_by(edges, key=lambda edge: edge.sourceNodeId).items()}
|
|
||||||
|
|
||||||
def get_node(self, node_id):
|
|
||||||
"""
|
|
||||||
根据node_id 获取节点信息
|
|
||||||
@param node_id: node_id
|
|
||||||
@return: 节点信息
|
|
||||||
"""
|
|
||||||
return self.node_map.get(node_id)
|
|
||||||
|
|
||||||
def get_up_edge_nodes(self, node_id) -> List[EdgeNode]:
|
|
||||||
"""
|
|
||||||
根据节点id 获取当前连接前置节点和连线
|
|
||||||
@param node_id: 节点id
|
|
||||||
@return: 节点连线列表
|
|
||||||
"""
|
|
||||||
return self.up_node_map.get(node_id)
|
|
||||||
|
|
||||||
def get_next_edge_nodes(self, node_id) -> List[EdgeNode]:
|
|
||||||
"""
|
|
||||||
根据节点id 获取当前连接目标节点和连线
|
|
||||||
@param node_id: 节点id
|
|
||||||
@return: 节点连线列表
|
|
||||||
"""
|
|
||||||
return self.next_node_map.get(node_id)
|
|
||||||
|
|
||||||
def get_up_nodes(self, node_id) -> List[Node]:
|
|
||||||
"""
|
|
||||||
根据节点id 获取当前连接前置节点
|
|
||||||
@param node_id: 节点id
|
|
||||||
@return: 节点列表
|
|
||||||
"""
|
|
||||||
return [en.node for en in self.up_node_map.get(node_id)]
|
|
||||||
|
|
||||||
def get_next_nodes(self, node_id) -> List[Node]:
|
|
||||||
"""
|
|
||||||
根据节点id 获取当前连接目标节点
|
|
||||||
@param node_id: 节点id
|
|
||||||
@return: 节点列表
|
|
||||||
"""
|
|
||||||
return [en.node for en in self.next_node_map.get(node_id, [])]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def new_instance(flow_obj: Dict):
|
|
||||||
nodes = flow_obj.get('nodes')
|
|
||||||
edges = flow_obj.get('edges')
|
|
||||||
nodes = [Node(node.get('id'), node.get('type'), **node)
|
|
||||||
for node in nodes]
|
|
||||||
edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges]
|
|
||||||
return Workflow(nodes, edges)
|
|
||||||
|
|
||||||
def get_start_node(self):
|
|
||||||
return self.get_node('start-node')
|
|
||||||
|
|
||||||
def get_search_node(self):
|
|
||||||
return [node for node in self.nodes if node.type == 'search-dataset-node']
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
"""
|
|
||||||
校验工作流数据
|
|
||||||
"""
|
|
||||||
self.is_valid_model_params()
|
|
||||||
self.is_valid_start_node()
|
|
||||||
self.is_valid_base_node()
|
|
||||||
self.is_valid_work_flow()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_valid_node_params(node: Node):
|
|
||||||
from application.flow.step_node import get_node
|
|
||||||
get_node(node.type)(node, None, None)
|
|
||||||
|
|
||||||
def is_valid_node(self, node: Node):
|
|
||||||
self.is_valid_node_params(node)
|
|
||||||
if node.type == 'condition-node':
|
|
||||||
branch_list = node.properties.get('node_data').get('branch')
|
|
||||||
for branch in branch_list:
|
|
||||||
source_anchor_id = f"{node.id}_{branch.get('id')}_right"
|
|
||||||
edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id]
|
|
||||||
if len(edge_list) == 0:
|
|
||||||
raise AppApiException(500,
|
|
||||||
_('The branch {branch} of the {node} node needs to be connected').format(
|
|
||||||
node=node.properties.get("stepName"), branch=branch.get("type")))
|
|
||||||
|
|
||||||
else:
|
|
||||||
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):
|
|
||||||
raise AppApiException(500, _("{node} Nodes cannot be considered as end nodes").format(
|
|
||||||
node=node.properties.get("stepName")))
|
|
||||||
|
|
||||||
def is_valid_work_flow(self, up_node=None):
|
|
||||||
if up_node is None:
|
|
||||||
up_node = self.get_start_node()
|
|
||||||
self.is_valid_node(up_node)
|
|
||||||
next_nodes = self.get_next_nodes(up_node)
|
|
||||||
for next_node in next_nodes:
|
|
||||||
self.is_valid_work_flow(next_node)
|
|
||||||
|
|
||||||
def is_valid_start_node(self):
|
|
||||||
start_node_list = [node for node in self.nodes if node.id == 'start-node']
|
|
||||||
if len(start_node_list) == 0:
|
|
||||||
raise AppApiException(500, _('The starting node is required'))
|
|
||||||
if len(start_node_list) > 1:
|
|
||||||
raise AppApiException(500, _('There can only be one starting node'))
|
|
||||||
|
|
||||||
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')]
|
|
||||||
for node in node_list:
|
|
||||||
model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first()
|
|
||||||
if model is None:
|
|
||||||
raise ValidationError(ErrorDetail(
|
|
||||||
_('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)
|
|
||||||
model_params_setting = node.properties.get('node_data', {}).get('model_params_setting')
|
|
||||||
model_params_setting_form = credential.get_model_params_setting_form(
|
|
||||||
model.model_name)
|
|
||||||
if model_params_setting is None:
|
|
||||||
model_params_setting = model_params_setting_form.get_default_form_data()
|
|
||||||
node.properties.get('node_data', {})['model_params_setting'] = model_params_setting
|
|
||||||
if node.properties.get('status', 200) != 200:
|
|
||||||
raise ValidationError(
|
|
||||||
ErrorDetail(_("Node {node} is unavailable").format(node.properties.get("stepName"))))
|
|
||||||
node_list = [node for node in self.nodes if (node.type == 'function-lib-node')]
|
|
||||||
for node in node_list:
|
|
||||||
function_lib_id = node.properties.get('node_data', {}).get('function_lib_id')
|
|
||||||
if function_lib_id is None:
|
|
||||||
raise ValidationError(ErrorDetail(
|
|
||||||
_('The library ID of node {node} cannot be empty').format(node=node.properties.get("stepName"))))
|
|
||||||
f_lib = QuerySet(Tool).filter(id=function_lib_id).first()
|
|
||||||
if f_lib is None:
|
|
||||||
raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format(
|
|
||||||
node=node.properties.get("stepName"))))
|
|
||||||
|
|
||||||
def is_valid_base_node(self):
|
|
||||||
base_node_list = [node for node in self.nodes if node.id == 'base-node']
|
|
||||||
if len(base_node_list) == 0:
|
|
||||||
raise AppApiException(500, _('Basic information node is required'))
|
|
||||||
if len(base_node_list) > 1:
|
|
||||||
raise AppApiException(500, _('There can only be one basic information node'))
|
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,13 @@ from rest_framework import serializers
|
||||||
from rest_framework.exceptions import ValidationError, ErrorDetail
|
from rest_framework.exceptions import ValidationError, ErrorDetail
|
||||||
|
|
||||||
from application.flow.common import Answer, NodeChunk
|
from application.flow.common import Answer, NodeChunk
|
||||||
from application.models import ApplicationChatUserStats
|
from application.models import ChatRecord
|
||||||
from application.models import ChatRecord, ChatUserType
|
from application.models.api_key_model import ApplicationPublicAccessClient
|
||||||
|
from common.constants.authentication_type import AuthenticationType
|
||||||
from common.field.common import InstanceField
|
from common.field.common import InstanceField
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
chat_cache = cache
|
chat_cache = cache.caches['chat_cache']
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
@ -44,14 +46,16 @@ def is_interrupt(node, step_variable: Dict, global_variable: Dict):
|
||||||
|
|
||||||
|
|
||||||
class WorkFlowPostHandler:
|
class WorkFlowPostHandler:
|
||||||
def __init__(self, chat_info):
|
def __init__(self, chat_info, client_id, client_type):
|
||||||
self.chat_info = chat_info
|
self.chat_info = chat_info
|
||||||
|
self.client_id = client_id
|
||||||
|
self.client_type = client_type
|
||||||
|
|
||||||
def handler(self, workflow):
|
def handler(self, chat_id,
|
||||||
workflow_body = workflow.get_body()
|
chat_record_id,
|
||||||
question = workflow_body.get('question')
|
answer,
|
||||||
chat_record_id = workflow_body.get('chat_record_id')
|
workflow):
|
||||||
chat_id = workflow_body.get('chat_id')
|
question = workflow.params['question']
|
||||||
details = workflow.get_runtime_details()
|
details = workflow.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])
|
||||||
|
|
@ -80,16 +84,15 @@ 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.chat_info.append_chat_record(chat_record, self.client_id, asker)
|
||||||
self.chat_info.set_cache()
|
# 重新设置缓存
|
||||||
|
chat_cache.set(chat_id,
|
||||||
if not self.chat_info.debug and [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__(
|
self.chat_info, timeout=60 * 30)
|
||||||
workflow_body.get('chat_user_type')):
|
if self.client_type == AuthenticationType.APPLICATION_ACCESS_TOKEN.value:
|
||||||
application_public_access_client = (QuerySet(ApplicationChatUserStats)
|
application_public_access_client = (QuerySet(ApplicationPublicAccessClient)
|
||||||
.filter(chat_user_id=workflow_body.get('chat_user_id'),
|
.filter(client_id=self.client_id,
|
||||||
chat_user_type=workflow_body.get('chat_user_type'),
|
application_id=self.chat_info.application.id).first())
|
||||||
application_id=self.chat_info.application_id).first())
|
|
||||||
if application_public_access_client is not None:
|
if application_public_access_client is not None:
|
||||||
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
application_public_access_client.access_num = application_public_access_client.access_num + 1
|
||||||
application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1
|
application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1
|
||||||
|
|
@ -120,36 +123,31 @@ class NodeResult:
|
||||||
|
|
||||||
|
|
||||||
class ReferenceAddressSerializer(serializers.Serializer):
|
class ReferenceAddressSerializer(serializers.Serializer):
|
||||||
node_id = serializers.CharField(required=True, label="节点id")
|
node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id"))
|
||||||
fields = serializers.ListField(
|
fields = serializers.ListField(
|
||||||
child=serializers.CharField(required=True, label="节点字段"), required=True,
|
child=serializers.CharField(required=True, error_messages=ErrMessage.char("节点字段")), required=True,
|
||||||
label="节点字段数组")
|
error_messages=ErrMessage.list("节点字段数组"))
|
||||||
|
|
||||||
|
|
||||||
class FlowParamsSerializer(serializers.Serializer):
|
class FlowParamsSerializer(serializers.Serializer):
|
||||||
# 历史对答
|
# 历史对答
|
||||||
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
|
||||||
label="历史对答")
|
error_messages=ErrMessage.list("历史对答"))
|
||||||
|
|
||||||
question = serializers.CharField(required=True, label="用户问题")
|
question = serializers.CharField(required=True, error_messages=ErrMessage.list("用户问题"))
|
||||||
|
|
||||||
chat_id = serializers.CharField(required=True, label="对话id")
|
chat_id = serializers.CharField(required=True, error_messages=ErrMessage.list("对话id"))
|
||||||
|
|
||||||
chat_record_id = serializers.CharField(required=True, label="对话记录id")
|
chat_record_id = serializers.CharField(required=True, error_messages=ErrMessage.char("对话记录id"))
|
||||||
|
|
||||||
stream = serializers.BooleanField(required=True, label="流式输出")
|
stream = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("流式输出"))
|
||||||
|
|
||||||
chat_user_id = serializers.CharField(required=False, label="对话用户id")
|
client_id = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端id"))
|
||||||
|
|
||||||
chat_user_type = serializers.CharField(required=False, label="对话用户类型")
|
client_type = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端类型"))
|
||||||
|
|
||||||
workspace_id = serializers.CharField(required=True, label="工作空间id")
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
||||||
|
re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("换个答案"))
|
||||||
application_id = serializers.CharField(required=True, label="应用id")
|
|
||||||
|
|
||||||
re_chat = serializers.BooleanField(required=True, label="换个答案")
|
|
||||||
|
|
||||||
debug = serializers.BooleanField(required=True, label="是否debug")
|
|
||||||
|
|
||||||
|
|
||||||
class INode:
|
class INode:
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,26 @@ from .ai_chat_step_node import *
|
||||||
from .application_node import BaseApplicationNode
|
from .application_node import BaseApplicationNode
|
||||||
from .condition_node import *
|
from .condition_node import *
|
||||||
from .direct_reply_node import *
|
from .direct_reply_node import *
|
||||||
from .document_extract_node import *
|
|
||||||
from .form_node import *
|
from .form_node import *
|
||||||
from .image_generate_step_node import *
|
from .function_lib_node import *
|
||||||
from .image_understand_step_node import *
|
from .function_node import *
|
||||||
from .mcp_node import BaseMcpNode
|
|
||||||
from .question_node import *
|
from .question_node import *
|
||||||
from .reranker_node import *
|
from .reranker_node import *
|
||||||
from .search_knowledge_node import *
|
|
||||||
|
from .document_extract_node import *
|
||||||
|
from .image_understand_step_node import *
|
||||||
|
from .image_generate_step_node import *
|
||||||
|
|
||||||
|
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 .tool_lib_node import *
|
|
||||||
from .tool_node import *
|
|
||||||
from .variable_assign_node import BaseVariableAssignNode
|
from .variable_assign_node import BaseVariableAssignNode
|
||||||
|
from .mcp_node import BaseMcpNode
|
||||||
|
|
||||||
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseQuestionNode,
|
node_list = [BaseStartStepNode, BaseChatNode, BaseSearchDatasetNode, BaseQuestionNode,
|
||||||
BaseConditionNode, BaseReplyNode,
|
BaseConditionNode, BaseReplyNode,
|
||||||
BaseToolNodeNode, BaseToolLibNodeNode, BaseRerankerNode, BaseApplicationNode,
|
BaseFunctionNodeNode, BaseFunctionLibNodeNode, BaseRerankerNode, BaseApplicationNode,
|
||||||
BaseDocumentExtractNode,
|
BaseDocumentExtractNode,
|
||||||
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,
|
BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,
|
||||||
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode]
|
BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode]
|
||||||
|
|
|
||||||
|
|
@ -12,34 +12,30 @@ 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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
|
|
||||||
class ChatNodeSerializer(serializers.Serializer):
|
class ChatNodeSerializer(serializers.Serializer):
|
||||||
model_id = serializers.CharField(required=True, label=_("Model id"))
|
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
||||||
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
label=_("Role Setting"))
|
error_messages=ErrMessage.char(_("Role Setting")))
|
||||||
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
|
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,
|
||||||
label=_('Whether to return content'))
|
error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
|
|
||||||
model_params_setting = serializers.DictField(required=False,
|
model_params_setting = serializers.DictField(required=False,
|
||||||
label=_("Model parameter settings"))
|
error_messages=ErrMessage.dict(_("Model parameter settings")))
|
||||||
model_setting = serializers.DictField(required=False,
|
model_setting = serializers.DictField(required=False,
|
||||||
label='Model settings')
|
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,
|
||||||
label=_("Context Type"))
|
error_messages=ErrMessage.char(_("Context Type")))
|
||||||
mcp_enable = serializers.BooleanField(required=False, label=_("Whether to enable MCP"))
|
mcp_enable = serializers.BooleanField(required=False,
|
||||||
mcp_servers = serializers.JSONField(required=False, label=_("MCP Server"))
|
error_messages=ErrMessage.boolean(_("Whether to enable MCP")))
|
||||||
mcp_tool_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("MCP Tool ID"))
|
mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server")))
|
||||||
mcp_tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True, label=_("MCP Tool IDs"), )
|
|
||||||
mcp_source = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("MCP Source"))
|
|
||||||
|
|
||||||
tool_enable = serializers.BooleanField(required=False, default=False, label=_("Whether to enable tools"))
|
|
||||||
tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,
|
|
||||||
label=_("Tool IDs"), )
|
|
||||||
|
|
||||||
|
|
||||||
class IChatNode(INode):
|
class IChatNode(INode):
|
||||||
|
|
@ -58,10 +54,5 @@ class IChatNode(INode):
|
||||||
model_setting=None,
|
model_setting=None,
|
||||||
mcp_enable=False,
|
mcp_enable=False,
|
||||||
mcp_servers=None,
|
mcp_servers=None,
|
||||||
mcp_tool_id=None,
|
|
||||||
mcp_tool_ids=None,
|
|
||||||
mcp_source=None,
|
|
||||||
tool_enable=False,
|
|
||||||
tool_ids=None,
|
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,12 @@
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from types import AsyncGeneratorType
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
import uuid_utils.compat as uuid
|
|
||||||
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, AIMessageChunk, ToolMessage
|
||||||
|
|
@ -26,13 +23,9 @@ 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 application.flow.tools import Reasoning
|
||||||
from common.utils.logger import maxkb_logger
|
from setting.models import Model
|
||||||
from common.utils.rsa_util import rsa_long_decrypt
|
from setting.models_provider import get_model_credential
|
||||||
from common.utils.tool_code import ToolExecutor
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
from maxkb.const import CONFIG
|
|
||||||
from models_provider.models import Model
|
|
||||||
from models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id
|
|
||||||
from tools.models import Tool
|
|
||||||
|
|
||||||
tool_message_template = """
|
tool_message_template = """
|
||||||
<details>
|
<details>
|
||||||
|
|
@ -40,26 +33,14 @@ tool_message_template = """
|
||||||
<strong>Called MCP Tool: <em>%s</em></strong>
|
<strong>Called MCP Tool: <em>%s</em></strong>
|
||||||
</summary>
|
</summary>
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
tool_message_json_template = """
|
|
||||||
```json
|
```json
|
||||||
%s
|
%s
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def generate_tool_message_template(name, context):
|
|
||||||
if '```' in context:
|
|
||||||
return tool_message_template % (name, context)
|
|
||||||
else:
|
|
||||||
return tool_message_template % (name, tool_message_json_template % (context))
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
reasoning_content: str):
|
||||||
chat_model = node_variable.get('chat_model')
|
chat_model = node_variable.get('chat_model')
|
||||||
|
|
@ -123,17 +104,16 @@ def write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INo
|
||||||
|
|
||||||
|
|
||||||
async def _yield_mcp_response(chat_model, message_list, mcp_servers):
|
async def _yield_mcp_response(chat_model, message_list, mcp_servers):
|
||||||
client = MultiServerMCPClient(json.loads(mcp_servers))
|
async with MultiServerMCPClient(json.loads(mcp_servers)) as client:
|
||||||
tools = await client.get_tools()
|
agent = create_react_agent(chat_model, client.get_tools())
|
||||||
agent = create_react_agent(chat_model, tools)
|
response = agent.astream({"messages": message_list}, stream_mode='messages')
|
||||||
response = agent.astream({"messages": message_list}, stream_mode='messages')
|
async for chunk in response:
|
||||||
async for chunk in response:
|
if isinstance(chunk[0], ToolMessage):
|
||||||
if isinstance(chunk[0], ToolMessage):
|
content = tool_message_template % (chunk[0].name, chunk[0].content)
|
||||||
content = generate_tool_message_template(chunk[0].name, chunk[0].content)
|
chunk[0].content = content
|
||||||
chunk[0].content = content
|
yield chunk[0]
|
||||||
yield chunk[0]
|
if isinstance(chunk[0], AIMessageChunk):
|
||||||
if isinstance(chunk[0], AIMessageChunk):
|
yield chunk[0]
|
||||||
yield chunk[0]
|
|
||||||
|
|
||||||
|
|
||||||
def mcp_response_generator(chat_model, message_list, mcp_servers):
|
def mcp_response_generator(chat_model, message_list, mcp_servers):
|
||||||
|
|
@ -147,7 +127,7 @@ def mcp_response_generator(chat_model, message_list, mcp_servers):
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
maxkb_logger.error(f'Exception: {e}', traceback.format_exc())
|
print(f'exception: {e}')
|
||||||
finally:
|
finally:
|
||||||
loop.close()
|
loop.close()
|
||||||
|
|
||||||
|
|
@ -172,9 +152,8 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
||||||
reasoning_result = reasoning.get_reasoning_content(response)
|
reasoning_result = reasoning.get_reasoning_content(response)
|
||||||
reasoning_result_end = reasoning.get_end_reasoning_content()
|
reasoning_result_end = reasoning.get_end_reasoning_content()
|
||||||
content = reasoning_result.get('content') + reasoning_result_end.get('content')
|
content = reasoning_result.get('content') + reasoning_result_end.get('content')
|
||||||
meta = {**response.response_metadata, **response.additional_kwargs}
|
if 'reasoning_content' in response.response_metadata:
|
||||||
if 'reasoning_content' in meta:
|
reasoning_content = response.response_metadata.get('reasoning_content', '')
|
||||||
reasoning_content = meta.get('reasoning_content', '')
|
|
||||||
else:
|
else:
|
||||||
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content')
|
reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content')
|
||||||
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
|
_write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)
|
||||||
|
|
@ -218,11 +197,6 @@ class BaseChatNode(IChatNode):
|
||||||
model_setting=None,
|
model_setting=None,
|
||||||
mcp_enable=False,
|
mcp_enable=False,
|
||||||
mcp_servers=None,
|
mcp_servers=None,
|
||||||
mcp_tool_id=None,
|
|
||||||
mcp_tool_ids=None,
|
|
||||||
mcp_source=None,
|
|
||||||
tool_enable=False,
|
|
||||||
tool_ids=None,
|
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
if dialogue_type is None:
|
if dialogue_type is None:
|
||||||
dialogue_type = 'WORKFLOW'
|
dialogue_type = 'WORKFLOW'
|
||||||
|
|
@ -233,9 +207,8 @@ class BaseChatNode(IChatNode):
|
||||||
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
|
model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',
|
||||||
'reasoning_content_start': '<think>'}
|
'reasoning_content_start': '<think>'}
|
||||||
self.context['model_setting'] = model_setting
|
self.context['model_setting'] = model_setting
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
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_workspace_id(model_id, workspace_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,
|
||||||
self.runtime_node_id)
|
self.runtime_node_id)
|
||||||
self.context['history_message'] = history_message
|
self.context['history_message'] = history_message
|
||||||
|
|
@ -246,13 +219,12 @@ class BaseChatNode(IChatNode):
|
||||||
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
|
||||||
|
|
||||||
# 处理 MCP 请求
|
if mcp_enable and mcp_servers is not None and '"stdio"' not in mcp_servers:
|
||||||
mcp_result = self._handle_mcp_request(
|
r = mcp_response_generator(chat_model, message_list, mcp_servers)
|
||||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids, chat_model, message_list,
|
return NodeResult(
|
||||||
history_message, question
|
{'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
||||||
)
|
'history_message': history_message, 'question': question.content}, {},
|
||||||
if mcp_result:
|
_write_context=write_context_stream)
|
||||||
return mcp_result
|
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
r = chat_model.stream(message_list)
|
r = chat_model.stream(message_list)
|
||||||
|
|
@ -265,57 +237,6 @@ class BaseChatNode(IChatNode):
|
||||||
'history_message': history_message, 'question': question.content}, {},
|
'history_message': history_message, 'question': question.content}, {},
|
||||||
_write_context=write_context)
|
_write_context=write_context)
|
||||||
|
|
||||||
def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids,
|
|
||||||
chat_model, message_list, history_message, question):
|
|
||||||
if not mcp_enable and not tool_enable:
|
|
||||||
return None
|
|
||||||
|
|
||||||
mcp_servers_config = {}
|
|
||||||
|
|
||||||
# 迁移过来mcp_source是None
|
|
||||||
if mcp_source is None:
|
|
||||||
mcp_source = 'custom'
|
|
||||||
if mcp_enable:
|
|
||||||
# 兼容老数据
|
|
||||||
if not mcp_tool_ids:
|
|
||||||
mcp_tool_ids = []
|
|
||||||
if mcp_tool_id:
|
|
||||||
mcp_tool_ids = list(set(mcp_tool_ids + [mcp_tool_id]))
|
|
||||||
if mcp_source == 'custom' and mcp_servers is not None and '"stdio"' not in mcp_servers:
|
|
||||||
mcp_servers_config = json.loads(mcp_servers)
|
|
||||||
elif mcp_tool_ids:
|
|
||||||
mcp_tools = QuerySet(Tool).filter(id__in=mcp_tool_ids).values()
|
|
||||||
for mcp_tool in mcp_tools:
|
|
||||||
if mcp_tool and mcp_tool['is_active']:
|
|
||||||
mcp_servers_config = {**mcp_servers_config, **json.loads(mcp_tool['code'])}
|
|
||||||
|
|
||||||
if tool_enable:
|
|
||||||
if tool_ids and len(tool_ids) > 0: # 如果有工具ID,则将其转换为MCP
|
|
||||||
self.context['tool_ids'] = tool_ids
|
|
||||||
self.context['execute_ids'] = []
|
|
||||||
for tool_id in tool_ids:
|
|
||||||
tool = QuerySet(Tool).filter(id=tool_id).first()
|
|
||||||
if not tool.is_active:
|
|
||||||
continue
|
|
||||||
executor = ToolExecutor(CONFIG.get('SANDBOX'))
|
|
||||||
if tool.init_params is not None:
|
|
||||||
params = json.loads(rsa_long_decrypt(tool.init_params))
|
|
||||||
else:
|
|
||||||
params = {}
|
|
||||||
_id, tool_config = executor.get_tool_mcp_config(tool.code, params)
|
|
||||||
|
|
||||||
self.context['execute_ids'].append(_id)
|
|
||||||
mcp_servers_config[str(tool.id)] = tool_config
|
|
||||||
|
|
||||||
if len(mcp_servers_config) > 0:
|
|
||||||
r = mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config))
|
|
||||||
return NodeResult(
|
|
||||||
{'result': r, 'chat_model': chat_model, 'message_list': message_list,
|
|
||||||
'history_message': history_message, 'question': question.content}, {},
|
|
||||||
_write_context=write_context_stream)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_history_message(history_chat_record, dialogue_number, dialogue_type, runtime_node_id):
|
def get_history_message(history_chat_record, dialogue_number, dialogue_type, runtime_node_id):
|
||||||
start_index = len(history_chat_record) - dialogue_number
|
start_index = len(history_chat_record) - dialogue_number
|
||||||
|
|
@ -348,14 +269,6 @@ class BaseChatNode(IChatNode):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
# 删除临时生成的MCP代码文件
|
|
||||||
if self.context.get('execute_ids'):
|
|
||||||
executor = ToolExecutor(CONFIG.get('SANDBOX'))
|
|
||||||
# 清理工具代码文件,延时删除,避免文件被占用
|
|
||||||
for tool_id in self.context.get('execute_ids'):
|
|
||||||
code_path = f'{executor.sandbox_path}/execute/{tool_id}.py'
|
|
||||||
if os.path.exists(code_path):
|
|
||||||
os.remove(code_path)
|
|
||||||
return {
|
return {
|
||||||
'name': self.node.properties.get('stepName'),
|
'name': self.node.properties.get('stepName'),
|
||||||
"index": index,
|
"index": index,
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,24 @@ from typing import Type
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ApplicationNodeSerializer(serializers.Serializer):
|
class ApplicationNodeSerializer(serializers.Serializer):
|
||||||
application_id = serializers.CharField(required=True, label=_("Application ID"))
|
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application ID")))
|
||||||
question_reference_address = serializers.ListField(required=True,
|
question_reference_address = serializers.ListField(required=True,
|
||||||
label=_("User Questions"))
|
error_messages=ErrMessage.list(_("User Questions")))
|
||||||
api_input_field_list = serializers.ListField(required=False, label=_("API Input Fields"))
|
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("API Input Fields")))
|
||||||
user_input_field_list = serializers.ListField(required=False,
|
user_input_field_list = serializers.ListField(required=False,
|
||||||
label=_("User Input Fields"))
|
error_messages=ErrMessage.uuid(_("User Input Fields")))
|
||||||
image_list = serializers.ListField(required=False, label=_("picture"))
|
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
|
||||||
document_list = serializers.ListField(required=False, label=_("document"))
|
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
|
||||||
audio_list = serializers.ListField(required=False, label=_("Audio"))
|
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio")))
|
||||||
child_node = serializers.DictField(required=False, allow_null=True,
|
child_node = serializers.DictField(required=False, allow_null=True,
|
||||||
label=_("Child Nodes"))
|
error_messages=ErrMessage.dict(_("Child Nodes")))
|
||||||
node_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data"))
|
node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data")))
|
||||||
|
|
||||||
|
|
||||||
class IApplicationNode(INode):
|
class IApplicationNode(INode):
|
||||||
|
|
@ -74,7 +75,7 @@ class IApplicationNode(INode):
|
||||||
if 'file_id' not in audio:
|
if 'file_id' not in audio:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
|
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails."))
|
||||||
return self.execute(**{**self.flow_params_serializer.data, **self.node_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,
|
||||||
message=str(question), **kwargs)
|
message=str(question), **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import re
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from application.flow.common import Answer
|
from application.flow.common import Answer
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.application_node.i_application_node import IApplicationNode
|
from application.flow.step_node.application_node.i_application_node import IApplicationNode
|
||||||
|
|
@ -171,21 +171,16 @@ class BaseApplicationNode(IApplicationNode):
|
||||||
if self.node_params.get('is_result', False):
|
if self.node_params.get('is_result', False):
|
||||||
self.answer_text = details.get('answer')
|
self.answer_text = details.get('answer')
|
||||||
|
|
||||||
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat,
|
def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,
|
||||||
chat_user_id,
|
|
||||||
chat_user_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,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
from chat.serializers.chat import ChatSerializers
|
from application.serializers.chat_message_serializers import ChatMessageSerializer
|
||||||
if application_id == self.workflow_manage.get_body().get('application_id'):
|
|
||||||
raise Exception(_("The sub application cannot use the current node"))
|
|
||||||
# 生成嵌入应用的chat_id
|
# 生成嵌入应用的chat_id
|
||||||
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[0:1024],
|
||||||
'chat_user_id': chat_user_id,
|
'client_id': client_id,
|
||||||
'chat_user_type': chat_user_type
|
|
||||||
})
|
})
|
||||||
if app_document_list is None:
|
if app_document_list is None:
|
||||||
app_document_list = []
|
app_document_list = []
|
||||||
|
|
@ -202,26 +197,22 @@ class BaseApplicationNode(IApplicationNode):
|
||||||
child_node_value = child_node.get('child_node')
|
child_node_value = child_node.get('child_node')
|
||||||
application_node_dict = self.context.get('application_node_dict')
|
application_node_dict = self.context.get('application_node_dict')
|
||||||
reset_application_node_dict(application_node_dict, runtime_node_id, node_data)
|
reset_application_node_dict(application_node_dict, runtime_node_id, node_data)
|
||||||
response = ChatSerializers(data={
|
|
||||||
"chat_id": current_chat_id,
|
|
||||||
"chat_user_id": chat_user_id,
|
|
||||||
'chat_user_type': chat_user_type,
|
|
||||||
'application_id': application_id,
|
|
||||||
'debug': False
|
|
||||||
}).chat(instance=
|
|
||||||
{'message': message,
|
|
||||||
're_chat': re_chat,
|
|
||||||
'stream': stream,
|
|
||||||
'document_list': app_document_list,
|
|
||||||
'image_list': app_image_list,
|
|
||||||
'audio_list': app_audio_list,
|
|
||||||
'runtime_node_id': runtime_node_id,
|
|
||||||
'chat_record_id': record_id,
|
|
||||||
'child_node': child_node_value,
|
|
||||||
'node_data': node_data,
|
|
||||||
'form_data': kwargs}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
response = ChatMessageSerializer(
|
||||||
|
data={'chat_id': current_chat_id, 'message': message,
|
||||||
|
're_chat': re_chat,
|
||||||
|
'stream': stream,
|
||||||
|
'application_id': application_id,
|
||||||
|
'client_id': client_id,
|
||||||
|
'client_type': client_type,
|
||||||
|
'document_list': app_document_list,
|
||||||
|
'image_list': app_image_list,
|
||||||
|
'audio_list': app_audio_list,
|
||||||
|
'runtime_node_id': runtime_node_id,
|
||||||
|
'chat_record_id': record_id,
|
||||||
|
'child_node': child_node_value,
|
||||||
|
'node_data': node_data,
|
||||||
|
'form_data': kwargs}).chat()
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
if stream:
|
if stream:
|
||||||
content_generator = response.streaming_content
|
content_generator = response.streaming_content
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,19 @@ 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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
|
|
||||||
class ConditionSerializer(serializers.Serializer):
|
class ConditionSerializer(serializers.Serializer):
|
||||||
compare = serializers.CharField(required=True, label=_("Comparator"))
|
compare = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Comparator")))
|
||||||
value = serializers.CharField(required=True, label=_("value"))
|
value = serializers.CharField(required=True, error_messages=ErrMessage.char(_("value")))
|
||||||
field = serializers.ListField(required=True, label=_("Fields"))
|
field = serializers.ListField(required=True, error_messages=ErrMessage.char(_("Fields")))
|
||||||
|
|
||||||
|
|
||||||
class ConditionBranchSerializer(serializers.Serializer):
|
class ConditionBranchSerializer(serializers.Serializer):
|
||||||
id = serializers.CharField(required=True, label=_("Branch id"))
|
id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch id")))
|
||||||
type = serializers.CharField(required=True, label=_("Branch Type"))
|
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch Type")))
|
||||||
condition = serializers.CharField(required=True, label=_("Condition or|and"))
|
condition = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Condition or|and")))
|
||||||
conditions = ConditionSerializer(many=True)
|
conditions = ConditionSerializer(many=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,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.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ReplyNodeParamsSerializer(serializers.Serializer):
|
class ReplyNodeParamsSerializer(serializers.Serializer):
|
||||||
reply_type = serializers.CharField(required=True, label=_("Response Type"))
|
reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Response Type")))
|
||||||
fields = serializers.ListField(required=False, label=_("Reference Field"))
|
fields = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Reference Field")))
|
||||||
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
label=_("Direct answer content"))
|
error_messages=ErrMessage.char(_("Direct answer content")))
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ 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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
|
|
||||||
class DocumentExtractNodeSerializer(serializers.Serializer):
|
class DocumentExtractNodeSerializer(serializers.Serializer):
|
||||||
document_list = serializers.ListField(required=False, label=_("document"))
|
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document")))
|
||||||
|
|
||||||
|
|
||||||
class IDocumentExtractNode(INode):
|
class IDocumentExtractNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ from django.db.models import QuerySet
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
|
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
|
||||||
from knowledge.models import File, FileSourceType
|
from dataset.models import File
|
||||||
from knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle
|
from dataset.serializers.document_serializers import split_handles, parse_table_handle_list, FileBufferHandle
|
||||||
from oss.serializers.file import FileSerializer
|
from dataset.serializers.file_serializers import FileSerializer
|
||||||
|
|
||||||
|
|
||||||
def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
|
def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
|
||||||
|
|
@ -37,11 +37,11 @@ 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):
|
def save_context(self, details, workflow_manage):
|
||||||
self.context['content'] = details.get('content')
|
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
|
||||||
|
|
||||||
|
|
@ -61,18 +61,12 @@ class BaseDocumentExtractNode(IDocumentExtractNode):
|
||||||
'application_id': str(application.id) if application.id else None,
|
'application_id': str(application.id) if application.id else None,
|
||||||
'file_id': str(image.id)
|
'file_id': str(image.id)
|
||||||
}
|
}
|
||||||
file_bytes = image.meta.pop('content')
|
file = bytes_to_uploaded_file(image.image, image.image_name)
|
||||||
f = bytes_to_uploaded_file(file_bytes, image.file_name)
|
FileSerializer(data={'file': file, 'meta': meta}).upload()
|
||||||
FileSerializer(data={
|
|
||||||
'file': f,
|
|
||||||
'meta': meta,
|
|
||||||
'source_id': meta['application_id'],
|
|
||||||
'source_type': FileSourceType.APPLICATION.value
|
|
||||||
}).upload()
|
|
||||||
|
|
||||||
for doc in document:
|
for doc in document:
|
||||||
file = QuerySet(File).filter(id=doc['file_id']).first()
|
file = QuerySet(File).filter(id=doc['file_id']).first()
|
||||||
buffer = io.BytesIO(file.get_bytes())
|
buffer = io.BytesIO(file.get_byte().tobytes())
|
||||||
buffer.name = doc['name'] # this is the important line
|
buffer.name = doc['name'] # this is the important line
|
||||||
|
|
||||||
for split_handle in (parse_table_handle_list + split_handles):
|
for split_handle in (parse_table_handle_list + split_handles):
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,14 @@ from typing import Type
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class FormNodeParamsSerializer(serializers.Serializer):
|
class FormNodeParamsSerializer(serializers.Serializer):
|
||||||
form_field_list = serializers.ListField(required=True, label=_("Form Configuration"))
|
form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Form Configuration")))
|
||||||
form_content_format = serializers.CharField(required=True, label=_('Form output content'))
|
form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Form output content')))
|
||||||
form_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data"))
|
form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data")))
|
||||||
|
|
||||||
|
|
||||||
class IFormNode(INode):
|
class IFormNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -16,29 +16,6 @@ from application.flow.common import Answer
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.form_node.i_form_node import IFormNode
|
from application.flow.step_node.form_node.i_form_node import IFormNode
|
||||||
|
|
||||||
multi_select_list = [
|
|
||||||
'MultiSelect',
|
|
||||||
'MultiRow'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_default_option(option_list, _type, value_field):
|
|
||||||
try:
|
|
||||||
if option_list is not None and isinstance(option_list, list) and len(option_list) > 0:
|
|
||||||
default_value_list = [o.get(value_field) for o in option_list if o.get('default')]
|
|
||||||
if len(default_value_list) == 0:
|
|
||||||
return [option_list[0].get(
|
|
||||||
value_field)] if multi_select_list.__contains__(_type) else option_list[0].get(
|
|
||||||
value_field)
|
|
||||||
else:
|
|
||||||
if multi_select_list.__contains__(_type):
|
|
||||||
return default_value_list
|
|
||||||
else:
|
|
||||||
return default_value_list[0]
|
|
||||||
except Exception as _:
|
|
||||||
pass
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
if step_variable is not None:
|
if step_variable is not None:
|
||||||
|
|
@ -51,13 +28,6 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
node.context['run_time'] = time.time() - node.context['start_time']
|
node.context['run_time'] = time.time() - node.context['start_time']
|
||||||
|
|
||||||
|
|
||||||
def generate_prompt(workflow_manage, _value):
|
|
||||||
try:
|
|
||||||
return workflow_manage.generate_prompt(_value)
|
|
||||||
except Exception as e:
|
|
||||||
return _value
|
|
||||||
|
|
||||||
|
|
||||||
class BaseFormNode(IFormNode):
|
class BaseFormNode(IFormNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
form_data = details.get('form_data', None)
|
form_data = details.get('form_data', None)
|
||||||
|
|
@ -74,37 +44,6 @@ class BaseFormNode(IFormNode):
|
||||||
for key in form_data:
|
for key in form_data:
|
||||||
self.context[key] = form_data[key]
|
self.context[key] = form_data[key]
|
||||||
|
|
||||||
def reset_field(self, field):
|
|
||||||
reset_field = ['field', 'label', 'default_value']
|
|
||||||
for f in reset_field:
|
|
||||||
_value = field[f]
|
|
||||||
if _value is None:
|
|
||||||
continue
|
|
||||||
if isinstance(_value, str):
|
|
||||||
field[f] = generate_prompt(self.workflow_manage, _value)
|
|
||||||
elif f == 'label':
|
|
||||||
_label_value = _value.get('label')
|
|
||||||
_value['label'] = generate_prompt(self.workflow_manage, _label_value)
|
|
||||||
tooltip = _value.get('attrs').get('tooltip')
|
|
||||||
if tooltip is not None:
|
|
||||||
_value.get('attrs')['tooltip'] = generate_prompt(self.workflow_manage, tooltip)
|
|
||||||
|
|
||||||
if ['SingleSelect', 'MultiSelect', 'RadioCard', 'RadioRow', 'MultiRow'].__contains__(field.get('input_type')):
|
|
||||||
if field.get('assignment_method') == 'ref_variables':
|
|
||||||
option_list = self.workflow_manage.get_reference_field(field.get('option_list')[0],
|
|
||||||
field.get('option_list')[1:])
|
|
||||||
option_list = option_list if isinstance(option_list, list) else []
|
|
||||||
field['option_list'] = option_list
|
|
||||||
field['default_value'] = get_default_option(option_list, field.get('input_type'),
|
|
||||||
field.get('value_field'))
|
|
||||||
|
|
||||||
if ['JsonInput'].__contains__(field.get('input_type')):
|
|
||||||
if field.get('default_value_assignment_method') == 'ref_variables':
|
|
||||||
field['default_value'] = self.workflow_manage.get_reference_field(field.get('default_value')[0],
|
|
||||||
field.get('default_value')[1:])
|
|
||||||
|
|
||||||
return field
|
|
||||||
|
|
||||||
def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:
|
def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:
|
||||||
if form_data is not None:
|
if form_data is not None:
|
||||||
self.context['is_submit'] = True
|
self.context['is_submit'] = True
|
||||||
|
|
@ -113,7 +52,6 @@ class BaseFormNode(IFormNode):
|
||||||
self.context[key] = form_data.get(key)
|
self.context[key] = form_data.get(key)
|
||||||
else:
|
else:
|
||||||
self.context['is_submit'] = False
|
self.context['is_submit'] = False
|
||||||
form_field_list = [self.reset_field(field) for field in form_field_list]
|
|
||||||
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)}
|
||||||
|
|
@ -122,7 +60,6 @@ class BaseFormNode(IFormNode):
|
||||||
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 NodeResult(
|
return NodeResult(
|
||||||
{'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {},
|
{'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {},
|
||||||
_write_context=write_context)
|
_write_context=write_context)
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,35 @@
|
||||||
"""
|
"""
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from django.db import connection
|
|
||||||
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.flow.i_step_node import INode, NodeResult
|
from application.flow.i_step_node import INode, NodeResult
|
||||||
from common.field.common import ObjectField
|
from common.field.common import ObjectField
|
||||||
from tools.models.tool import Tool
|
from common.util.field_message import ErrMessage
|
||||||
|
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, label=_('Variable Name'))
|
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name')))
|
||||||
value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
|
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list])
|
||||||
|
|
||||||
|
|
||||||
class FunctionLibNodeParamsSerializer(serializers.Serializer):
|
class FunctionLibNodeParamsSerializer(serializers.Serializer):
|
||||||
tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID'))
|
function_lib_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('Library ID')))
|
||||||
input_field_list = InputField(required=True, many=True)
|
input_field_list = InputField(required=True, many=True)
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
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(Tool).filter(id=self.data.get('tool_lib_id')).first()
|
f_lib = QuerySet(FunctionLib).filter(id=self.data.get('function_lib_id')).first()
|
||||||
# 归还链接到连接池
|
|
||||||
connection.close()
|
|
||||||
if f_lib is None:
|
if f_lib is None:
|
||||||
raise Exception(_('The function has been deleted'))
|
raise Exception(_('The function has been deleted'))
|
||||||
|
|
||||||
|
|
||||||
class IToolLibNode(INode):
|
class IFunctionLibNode(INode):
|
||||||
type = 'tool-lib-node'
|
type = 'function-lib-node'
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
return FunctionLibNodeParamsSerializer
|
return FunctionLibNodeParamsSerializer
|
||||||
|
|
@ -47,5 +44,5 @@ class IToolLibNode(INode):
|
||||||
def _run(self):
|
def _run(self):
|
||||||
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)
|
||||||
|
|
||||||
def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:
|
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
@date:2024/8/8 17:48
|
@date:2024/8/8 17:48
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .base_tool_lib_node import BaseToolLibNodeNode
|
from .base_function_lib_node import BaseFunctionLibNodeNode
|
||||||
|
|
@ -14,15 +14,14 @@ from django.db.models import QuerySet
|
||||||
from django.utils.translation import gettext as _
|
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.tool_lib_node.i_tool_lib_node import IToolLibNode
|
from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
|
||||||
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
|
||||||
from common.exception.app_exception import AppApiException
|
from common.exception.app_exception import AppApiException
|
||||||
from common.utils.rsa_util import rsa_long_decrypt
|
from common.util.function_code import FunctionExecutor
|
||||||
from common.utils.tool_code import ToolExecutor
|
from common.util.rsa_util import rsa_long_decrypt
|
||||||
from maxkb.const import CONFIG
|
from function_lib.models.function import FunctionLib
|
||||||
from tools.models import Tool
|
from smartdoc.const import CONFIG
|
||||||
|
|
||||||
function_executor = ToolExecutor(CONFIG.get('SANDBOX'))
|
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
@ -46,47 +45,35 @@ def get_field_value(debug_field_list, name, is_required):
|
||||||
|
|
||||||
|
|
||||||
def valid_reference_value(_type, value, name):
|
def valid_reference_value(_type, value, name):
|
||||||
try:
|
if _type == 'int':
|
||||||
if _type == 'int':
|
instance_type = int | float
|
||||||
instance_type = int | float
|
elif _type == 'float':
|
||||||
elif _type == 'float':
|
instance_type = float | int
|
||||||
instance_type = float | int
|
elif _type == 'dict':
|
||||||
elif _type == 'dict':
|
instance_type = dict
|
||||||
value = json.loads(value) if isinstance(value, str) else value
|
elif _type == 'array':
|
||||||
instance_type = dict
|
instance_type = list
|
||||||
elif _type == 'array':
|
elif _type == 'string':
|
||||||
value = json.loads(value) if isinstance(value, str) else value
|
instance_type = str
|
||||||
instance_type = list
|
else:
|
||||||
elif _type == 'string':
|
raise Exception(_('Field: {name} Type: {_type} Value: {value} Unsupported types').format(name=name,
|
||||||
instance_type = str
|
_type=_type))
|
||||||
else:
|
|
||||||
raise Exception(_(
|
|
||||||
'Field: {name} Type: {_type} Value: {value} Unsupported types'
|
|
||||||
).format(name=name, _type=_type))
|
|
||||||
except:
|
|
||||||
return value
|
|
||||||
if not isinstance(value, instance_type):
|
if not isinstance(value, instance_type):
|
||||||
raise Exception(_(
|
raise Exception(
|
||||||
'Field: {name} Type: {_type} Value: {value} Type error'
|
_('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,
|
||||||
).format(name=name, _type=_type, value=value))
|
value=value))
|
||||||
return 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) or isinstance(value, list)) and len(value) == 0)):
|
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
|
||||||
|
return None
|
||||||
|
if not is_required and source == 'reference' and (value is None or len(value) == 0):
|
||||||
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:])
|
||||||
if value is None:
|
valid_reference_value(_type, value, name)
|
||||||
if not is_required:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise Exception(_(
|
|
||||||
'Field: {name} Type: {_type} is required'
|
|
||||||
).format(name=name, _type=_type))
|
|
||||||
value = valid_reference_value(_type, value, name)
|
|
||||||
if _type == 'int':
|
if _type == 'int':
|
||||||
return int(value)
|
return int(value)
|
||||||
if _type == 'float':
|
if _type == 'float':
|
||||||
|
|
@ -114,28 +101,24 @@ def convert_value(name: str, value, _type, is_required, source, node):
|
||||||
value=value))
|
value=value))
|
||||||
|
|
||||||
|
|
||||||
def valid_function(tool_lib, workspace_id):
|
def valid_function(function_lib, user_id):
|
||||||
if tool_lib is None:
|
if function_lib is None:
|
||||||
raise Exception(_('Tool does not exist'))
|
raise Exception(_('Function does not exist'))
|
||||||
get_authorized_tool = DatabaseModelManage.get_model("get_authorized_tool")
|
if function_lib.permission_type == 'PRIVATE' and str(function_lib.user_id) != str(user_id):
|
||||||
if tool_lib and tool_lib.workspace_id != workspace_id and get_authorized_tool is not None:
|
raise Exception(_('No permission to use this function {name}').format(name=function_lib.name))
|
||||||
tool_lib = get_authorized_tool(QuerySet(Tool).filter(id=tool_lib.id), workspace_id).first()
|
if not function_lib.is_active:
|
||||||
if tool_lib is None:
|
raise Exception(_('Function {name} is unavailable').format(name=function_lib.name))
|
||||||
raise Exception(_("Tool does not exist"))
|
|
||||||
if not tool_lib.is_active:
|
|
||||||
raise Exception(_("Tool is not active"))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseToolLibNodeNode(IToolLibNode):
|
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'):
|
if self.node_params.get('is_result'):
|
||||||
self.answer_text = str(details.get('result'))
|
self.answer_text = str(details.get('result'))
|
||||||
|
|
||||||
def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:
|
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first()
|
||||||
tool_lib = QuerySet(Tool).filter(id=tool_lib_id).first()
|
valid_function(function_lib, self.flow_params_serializer.data.get('user_id'))
|
||||||
valid_function(tool_lib, workspace_id)
|
|
||||||
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)
|
||||||
|
|
@ -143,16 +126,15 @@ class BaseToolLibNodeNode(IToolLibNode):
|
||||||
[{'value': get_field_value(input_field_list, field.get('name'), field.get('is_required'),
|
[{'value': get_field_value(input_field_list, field.get('name'), field.get('is_required'),
|
||||||
), **field}
|
), **field}
|
||||||
for field in
|
for field in
|
||||||
tool_lib.input_field_list]}
|
function_lib.input_field_list]}
|
||||||
|
|
||||||
self.context['params'] = params
|
self.context['params'] = params
|
||||||
# 合并初始化参数
|
# 合并初始化参数
|
||||||
init_params_default_value = {i["field"]: i.get('default_value') for i in tool_lib.init_field_list}
|
if function_lib.init_params is not None:
|
||||||
if tool_lib.init_params is not None:
|
all_params = json.loads(rsa_long_decrypt(function_lib.init_params)) | params
|
||||||
all_params = init_params_default_value | json.loads(rsa_long_decrypt(tool_lib.init_params)) | params
|
|
||||||
else:
|
else:
|
||||||
all_params = init_params_default_value | params
|
all_params = params
|
||||||
result = function_executor.exec_code(tool_lib.code, all_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):
|
||||||
|
|
@ -15,23 +15,23 @@ 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.field.common import ObjectField
|
from common.field.common import ObjectField
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.utils.formatting import lazy_format
|
from rest_framework.utils.formatting import lazy_format
|
||||||
|
|
||||||
|
|
||||||
class InputField(serializers.Serializer):
|
class InputField(serializers.Serializer):
|
||||||
name = serializers.CharField(required=True, label=_('Variable Name'))
|
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name')))
|
||||||
is_required = serializers.BooleanField(required=True, label=_("Is this field required"))
|
is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean(_("Is this field required")))
|
||||||
type = serializers.CharField(required=True, label=_("type"), validators=[
|
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("type")), 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=_("The field only supports string|int|dict|array|float"), code=500)
|
||||||
])
|
])
|
||||||
source = serializers.CharField(required=True, label=_("source"), validators=[
|
source = serializers.CharField(required=True, error_messages=ErrMessage.char(_("source")), 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=_("The field only supports custom|reference"), code=500)
|
||||||
])
|
])
|
||||||
value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
|
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), 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)
|
||||||
|
|
@ -43,16 +43,15 @@ class InputField(serializers.Serializer):
|
||||||
|
|
||||||
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, label=_("function"))
|
code = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function")))
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
class IToolNode(INode):
|
class IFunctionNode(INode):
|
||||||
type = 'tool-node'
|
type = 'function-node'
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
return FunctionNodeParamsSerializer
|
return FunctionNodeParamsSerializer
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
@date:2024/8/13 11:19
|
@date:2024/8/13 11:19
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .base_tool_node import BaseToolNodeNode
|
from .base_function_node import BaseFunctionNodeNode
|
||||||
|
|
@ -8,16 +8,16 @@
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
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.tool_node.i_tool_node import IToolNode
|
from application.flow.step_node.function_node.i_function_node import IFunctionNode
|
||||||
from common.utils.tool_code import ToolExecutor
|
from common.exception.app_exception import AppApiException
|
||||||
from maxkb.const import CONFIG
|
from common.util.function_code import FunctionExecutor
|
||||||
|
from smartdoc.const import CONFIG
|
||||||
|
|
||||||
function_executor = ToolExecutor(CONFIG.get('SANDBOX'))
|
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
|
||||||
|
|
||||||
|
|
||||||
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
|
||||||
|
|
@ -32,47 +32,30 @@ 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):
|
||||||
try:
|
if _type == 'int':
|
||||||
if _type == 'int':
|
instance_type = int | float
|
||||||
instance_type = int | float
|
elif _type == 'float':
|
||||||
elif _type == 'float':
|
instance_type = float | int
|
||||||
instance_type = float | int
|
elif _type == 'dict':
|
||||||
elif _type == 'dict':
|
instance_type = dict
|
||||||
value = json.loads(value) if isinstance(value, str) else value
|
elif _type == 'array':
|
||||||
instance_type = dict
|
instance_type = list
|
||||||
elif _type == 'array':
|
elif _type == 'string':
|
||||||
value = json.loads(value) if isinstance(value, str) else value
|
instance_type = str
|
||||||
instance_type = list
|
else:
|
||||||
elif _type == 'string':
|
raise Exception(500, f'字段:{name}类型:{_type} 不支持的类型')
|
||||||
instance_type = str
|
|
||||||
else:
|
|
||||||
raise Exception(_(
|
|
||||||
'Field: {name} Type: {_type} Value: {value} Unsupported types'
|
|
||||||
).format(name=name, _type=_type))
|
|
||||||
except:
|
|
||||||
return value
|
|
||||||
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))
|
|
||||||
return 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) or isinstance(value, list)) and len(value) == 0)):
|
if not is_required and (value is None or (isinstance(value, str) and len(value) == 0)):
|
||||||
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:])
|
||||||
if value is None:
|
valid_reference_value(_type, value, name)
|
||||||
if not is_required:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise Exception(_(
|
|
||||||
'Field: {name} Type: {_type} is required'
|
|
||||||
).format(name=name, _type=_type))
|
|
||||||
value = valid_reference_value(_type, value, name)
|
|
||||||
if _type == 'int':
|
if _type == 'int':
|
||||||
return int(value)
|
return int(value)
|
||||||
if _type == 'float':
|
if _type == 'float':
|
||||||
|
|
@ -87,20 +70,18 @@ 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))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseToolNodeNode(IToolNode):
|
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):
|
if self.node_params.get('is_result', False):
|
||||||
|
|
@ -2,31 +2,31 @@
|
||||||
|
|
||||||
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
|
||||||
|
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, label=_("Model id"))
|
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
||||||
|
|
||||||
prompt = serializers.CharField(required=True, label=_("Prompt word (positive)"))
|
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word (positive)")))
|
||||||
|
|
||||||
negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"),
|
negative_prompt = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Prompt word (negative)")),
|
||||||
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,
|
||||||
label=_("Number of multi-round conversations"))
|
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',
|
||||||
label=_("Conversation storage type"))
|
error_messages=ErrMessage.char(_("Conversation storage type")))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
model_params_setting = serializers.JSONField(required=False, default=dict,
|
model_params_setting = serializers.JSONField(required=False, default=dict,
|
||||||
label=_("Model parameter settings"))
|
error_messages=ErrMessage.json(_("Model parameter settings")))
|
||||||
|
|
||||||
|
|
||||||
class IImageGenerateNode(INode):
|
class IImageGenerateNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode
|
from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode
|
||||||
from common.utils.common import bytes_to_uploaded_file
|
from common.util.common import bytes_to_uploaded_file
|
||||||
from knowledge.models import FileSourceType
|
from dataset.serializers.file_serializers import FileSerializer
|
||||||
from oss.serializers.file import FileSerializer
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
|
||||||
|
|
||||||
|
|
||||||
class BaseImageGenerateNode(IImageGenerateNode):
|
class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
|
|
@ -24,10 +23,10 @@ class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
model_params_setting,
|
model_params_setting,
|
||||||
chat_record_id,
|
chat_record_id,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
|
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
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
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_workspace_id(model_id, workspace_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)
|
||||||
|
|
@ -35,26 +34,19 @@ class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
message_list = self.generate_message_list(question, history_message)
|
message_list = self.generate_message_list(question, history_message)
|
||||||
self.context['message_list'] = message_list
|
self.context['message_list'] = message_list
|
||||||
self.context['dialogue_type'] = dialogue_type
|
self.context['dialogue_type'] = dialogue_type
|
||||||
self.context['negative_prompt'] = negative_prompt
|
print(message_list)
|
||||||
image_urls = tti_model.generate_image(question, negative_prompt)
|
image_urls = tti_model.generate_image(question, negative_prompt)
|
||||||
# 保存图片
|
# 保存图片
|
||||||
file_urls = []
|
file_urls = []
|
||||||
for image_url in image_urls:
|
for image_url in image_urls:
|
||||||
file_name = 'generated_image.png'
|
file_name = 'generated_image.png'
|
||||||
if isinstance(image_url, str) and image_url.startswith('http'):
|
file = bytes_to_uploaded_file(requests.get(image_url).content, file_name)
|
||||||
image_url = requests.get(image_url).content
|
|
||||||
file = bytes_to_uploaded_file(image_url, file_name)
|
|
||||||
meta = {
|
meta = {
|
||||||
'debug': False if application.id else True,
|
'debug': False if application.id else True,
|
||||||
'chat_id': chat_id,
|
'chat_id': chat_id,
|
||||||
'application_id': str(application.id) if application.id else None,
|
'application_id': str(application.id) if application.id else None,
|
||||||
}
|
}
|
||||||
file_url = FileSerializer(data={
|
file_url = FileSerializer(data={'file': file, 'meta': meta}).upload()
|
||||||
'file': file,
|
|
||||||
'meta': meta,
|
|
||||||
'source_id': meta['application_id'],
|
|
||||||
'source_type': FileSourceType.APPLICATION.value
|
|
||||||
}).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"" for path in file_urls])
|
answer = ' '.join([f"" for path in file_urls])
|
||||||
|
|
@ -126,6 +118,5 @@ class BaseImageGenerateNode(IImageGenerateNode):
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'err_message': self.err_message,
|
'err_message': self.err_message,
|
||||||
'image_list': self.context.get('image_list'),
|
'image_list': self.context.get('image_list'),
|
||||||
'dialogue_type': self.context.get('dialogue_type'),
|
'dialogue_type': self.context.get('dialogue_type')
|
||||||
'negative_prompt': self.context.get('negative_prompt'),
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,26 @@ from typing import Type
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class ImageUnderstandNodeSerializer(serializers.Serializer):
|
class ImageUnderstandNodeSerializer(serializers.Serializer):
|
||||||
model_id = serializers.CharField(required=True, label=_("Model id"))
|
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
||||||
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
label=_("Role Setting"))
|
error_messages=ErrMessage.char(_("Role Setting")))
|
||||||
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
|
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
||||||
|
|
||||||
dialogue_type = serializers.CharField(required=True, label=_("Conversation storage type"))
|
dialogue_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Conversation storage type")))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
image_list = serializers.ListField(required=False, label=_("picture"))
|
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture")))
|
||||||
|
|
||||||
model_params_setting = serializers.JSONField(required=False, default=dict,
|
model_params_setting = serializers.JSONField(required=False, default=dict,
|
||||||
label=_("Model parameter settings"))
|
error_messages=ErrMessage.json(_("Model parameter settings")))
|
||||||
|
|
||||||
|
|
||||||
class IImageUnderstandNode(INode):
|
class IImageUnderstandNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import base64
|
import base64
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from imghdr import what
|
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
|
@ -10,8 +10,9 @@ from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AI
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
|
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
|
||||||
from knowledge.models import File
|
from dataset.models import File
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
from imghdr import what
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -59,9 +60,9 @@ def write_context(node_variable: Dict, workflow_variable: Dict, node: INode, wor
|
||||||
|
|
||||||
def file_id_to_base64(file_id: str):
|
def file_id_to_base64(file_id: str):
|
||||||
file = QuerySet(File).filter(id=file_id).first()
|
file = QuerySet(File).filter(id=file_id).first()
|
||||||
file_bytes = file.get_bytes()
|
file_bytes = file.get_byte()
|
||||||
base64_image = base64.b64encode(file_bytes).decode("utf-8")
|
base64_image = base64.b64encode(file_bytes).decode("utf-8")
|
||||||
return [base64_image, what(None, file_bytes)]
|
return [base64_image, what(None, file_bytes.tobytes())]
|
||||||
|
|
||||||
|
|
||||||
class BaseImageUnderstandNode(IImageUnderstandNode):
|
class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
|
|
@ -79,9 +80,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
# 处理不正确的参数
|
# 处理不正确的参数
|
||||||
if image is None or not isinstance(image, list):
|
if image is None or not isinstance(image, list):
|
||||||
image = []
|
image = []
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
print(model_params_setting)
|
||||||
image_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
image_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_for_details(history_chat_record, dialogue_number)
|
history_message = self.get_history_message_for_details(history_chat_record, dialogue_number)
|
||||||
self.context['history_message'] = history_message
|
self.context['history_message'] = history_message
|
||||||
|
|
@ -130,7 +130,7 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
file_id_list = [image.get('file_id') for image in image_list]
|
file_id_list = [image.get('file_id') for image in image_list]
|
||||||
return HumanMessage(content=[
|
return HumanMessage(content=[
|
||||||
{'type': 'text', 'text': data['question']},
|
{'type': 'text', 'text': data['question']},
|
||||||
*[{'type': 'image_url', 'image_url': {'url': f'./oss/file/{file_id}'}} for file_id in file_id_list]
|
*[{'type': 'image_url', 'image_url': {'url': f'/api/file/{file_id}'}} for file_id in file_id_list]
|
||||||
|
|
||||||
])
|
])
|
||||||
return HumanMessage(content=chat_record.problem_text)
|
return HumanMessage(content=chat_record.problem_text)
|
||||||
|
|
@ -155,8 +155,7 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
return HumanMessage(
|
return HumanMessage(
|
||||||
content=[
|
content=[
|
||||||
{'type': 'text', 'text': data['question']},
|
{'type': 'text', 'text': data['question']},
|
||||||
*[{'type': 'image_url',
|
*[{'type': 'image_url', 'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for
|
||||||
'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for
|
|
||||||
base64_image in image_base64_list]
|
base64_image in image_base64_list]
|
||||||
])
|
])
|
||||||
return HumanMessage(content=chat_record.problem_text)
|
return HumanMessage(content=chat_record.problem_text)
|
||||||
|
|
@ -171,11 +170,10 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
|
||||||
for img in image:
|
for img in image:
|
||||||
file_id = img['file_id']
|
file_id = img['file_id']
|
||||||
file = QuerySet(File).filter(id=file_id).first()
|
file = QuerySet(File).filter(id=file_id).first()
|
||||||
image_bytes = file.get_bytes()
|
image_bytes = file.get_byte()
|
||||||
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
||||||
image_format = what(None, image_bytes)
|
image_format = what(None, image_bytes.tobytes())
|
||||||
images.append(
|
images.append({'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})
|
||||||
{'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})
|
|
||||||
messages = [HumanMessage(
|
messages = [HumanMessage(
|
||||||
content=[
|
content=[
|
||||||
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},
|
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,24 @@
|
||||||
|
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class McpNodeSerializer(serializers.Serializer):
|
class McpNodeSerializer(serializers.Serializer):
|
||||||
mcp_servers = serializers.JSONField(required=True, label=_("Mcp servers"))
|
mcp_servers = serializers.JSONField(required=True,
|
||||||
mcp_server = serializers.CharField(required=True, label=_("Mcp server"))
|
error_messages=ErrMessage.char(_("Mcp servers")))
|
||||||
mcp_tool = serializers.CharField(required=True, label=_("Mcp tool"))
|
|
||||||
mcp_tool_id = serializers.CharField(required=False, label=_("Mcp tool"), allow_null=True, allow_blank=True)
|
mcp_server = serializers.CharField(required=True,
|
||||||
mcp_source = serializers.CharField(required=False, label=_("Mcp source"), allow_blank=True, allow_null=True)
|
error_messages=ErrMessage.char(_("Mcp server")))
|
||||||
tool_params = serializers.DictField(required=True, label=_("Tool parameters"))
|
|
||||||
|
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):
|
class IMcpNode(INode):
|
||||||
|
|
@ -26,5 +31,5 @@ class IMcpNode(INode):
|
||||||
def _run(self):
|
def _run(self):
|
||||||
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)
|
||||||
|
|
||||||
def execute(self, mcp_servers, mcp_server, mcp_tool, mcp_tool_id, mcp_source, tool_params, **kwargs) -> NodeResult:
|
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@ import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode
|
from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode
|
||||||
from tools.models import Tool
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMcpNode(IMcpNode):
|
class BaseMcpNode(IMcpNode):
|
||||||
|
|
@ -16,30 +14,20 @@ class BaseMcpNode(IMcpNode):
|
||||||
self.context['result'] = details.get('result')
|
self.context['result'] = details.get('result')
|
||||||
self.context['tool_params'] = details.get('tool_params')
|
self.context['tool_params'] = details.get('tool_params')
|
||||||
self.context['mcp_tool'] = details.get('mcp_tool')
|
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, mcp_tool_id, mcp_source, tool_params,**kwargs) -> NodeResult:
|
def execute(self, mcp_servers, mcp_server, mcp_tool, tool_params, **kwargs) -> NodeResult:
|
||||||
if mcp_source == 'referencing':
|
servers = json.loads(mcp_servers)
|
||||||
if not mcp_tool_id:
|
params = json.loads(json.dumps(tool_params))
|
||||||
raise ValueError("MCP tool ID is required when mcp_source is 'referencing'.")
|
params = self.handle_variables(params)
|
||||||
tool = QuerySet(Tool).filter(id=mcp_tool_id).first()
|
|
||||||
if not tool:
|
|
||||||
raise ValueError(f"Tool with ID {mcp_tool_id} not found.")
|
|
||||||
if not tool.is_active:
|
|
||||||
raise ValueError(f"Tool with ID {mcp_tool_id} is inactive.")
|
|
||||||
servers = json.loads(tool.code)
|
|
||||||
params = json.loads(json.dumps(tool_params))
|
|
||||||
params = self.handle_variables(params)
|
|
||||||
else:
|
|
||||||
servers = json.loads(mcp_servers)
|
|
||||||
params = json.loads(json.dumps(tool_params))
|
|
||||||
params = self.handle_variables(params)
|
|
||||||
|
|
||||||
async def call_tool(t, a):
|
async def call_tool(s, session, t, a):
|
||||||
client = MultiServerMCPClient(servers)
|
async with MultiServerMCPClient(s) as client:
|
||||||
async with client.session(mcp_server) as s:
|
s = await client.sessions[session].call_tool(t, a)
|
||||||
return await s.call_tool(t, a)
|
return s
|
||||||
|
|
||||||
res = asyncio.run(call_tool(mcp_tool, params))
|
res = asyncio.run(call_tool(servers, mcp_server, mcp_tool, params))
|
||||||
return NodeResult(
|
return NodeResult(
|
||||||
{'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})
|
{'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,23 @@
|
||||||
"""
|
"""
|
||||||
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
|
||||||
|
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, label=_("Model id"))
|
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
||||||
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
|
||||||
label=_("Role Setting"))
|
error_messages=ErrMessage.char(_("Role Setting")))
|
||||||
prompt = serializers.CharField(required=True, label=_("Prompt word"))
|
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
|
||||||
# 多轮对话数量
|
# 多轮对话数量
|
||||||
dialogue_number = serializers.IntegerField(required=True, label=
|
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations")))
|
||||||
_("Number of multi-round conversations"))
|
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
model_params_setting = serializers.DictField(required=False, error_messages=ErrMessage.integer(_("Model parameter settings")))
|
||||||
model_params_setting = serializers.DictField(required=False,
|
|
||||||
label=_("Model parameter settings"))
|
|
||||||
|
|
||||||
|
|
||||||
class IQuestionNode(INode):
|
class IQuestionNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,9 @@ from langchain_core.messages import BaseMessage
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult, INode
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.question_node.i_question_node import IQuestionNode
|
from application.flow.step_node.question_node.i_question_node import IQuestionNode
|
||||||
from models_provider.models import Model
|
from setting.models import Model
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential
|
from setting.models_provider import get_model_credential
|
||||||
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -87,9 +88,8 @@ class BaseQuestionNode(IQuestionNode):
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
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)
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
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_workspace_id(model_id, workspace_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)
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,19 @@ from typing import Type
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
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,
|
||||||
label=_("Reference segment number"))
|
error_messages=ErrMessage.integer(_("Reference segment number")))
|
||||||
# 相似度 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,
|
||||||
label=_("Reference segment number"))
|
error_messages=ErrMessage.float(_("Reference segment number")))
|
||||||
max_paragraph_char_number = serializers.IntegerField(required=True,
|
max_paragraph_char_number = serializers.IntegerField(required=True,
|
||||||
label=_("Maximum number of words in a quoted segment"))
|
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment")))
|
||||||
|
|
||||||
|
|
||||||
class RerankerStepNodeSerializer(serializers.Serializer):
|
class RerankerStepNodeSerializer(serializers.Serializer):
|
||||||
|
|
@ -32,8 +32,6 @@ class RerankerStepNodeSerializer(serializers.Serializer):
|
||||||
question_reference_address = serializers.ListField(required=True)
|
question_reference_address = serializers.ListField(required=True)
|
||||||
reranker_model_id = serializers.UUIDField(required=True)
|
reranker_model_id = serializers.UUIDField(required=True)
|
||||||
reranker_reference_list = serializers.ListField(required=True, child=serializers.ListField(required=True))
|
reranker_reference_list = serializers.ListField(required=True, child=serializers.ListField(required=True))
|
||||||
show_knowledge = serializers.BooleanField(required=True,
|
|
||||||
label=_("The results are displayed in the knowledge sources"))
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -57,6 +55,6 @@ class IRerankerNode(INode):
|
||||||
|
|
||||||
reranker_list=reranker_list)
|
reranker_list=reranker_list)
|
||||||
|
|
||||||
def execute(self, question, reranker_setting, reranker_list, reranker_model_id,show_knowledge,
|
def execute(self, question, reranker_setting, reranker_list, reranker_model_id,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from langchain_core.documents import Document
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode
|
from application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
|
||||||
|
|
||||||
def merge_reranker_list(reranker_list, result=None):
|
def merge_reranker_list(reranker_list, result=None):
|
||||||
|
|
@ -24,9 +24,11 @@ def merge_reranker_list(reranker_list, result=None):
|
||||||
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")
|
title = document.get("title")
|
||||||
|
dataset_name = document.get("dataset_name")
|
||||||
|
document_name = document.get('document_name')
|
||||||
result.append(
|
result.append(
|
||||||
Document(page_content=str(document) if len(content) == 0 else content,
|
Document(page_content=str(document) if len(content) == 0 else content,
|
||||||
metadata={'title': title, **document}))
|
metadata={'title': title, 'dataset_name': dataset_name, 'document_name': document_name}))
|
||||||
else:
|
else:
|
||||||
result.append(Document(page_content=str(document), metadata={}))
|
result.append(Document(page_content=str(document), metadata={}))
|
||||||
return result
|
return result
|
||||||
|
|
@ -61,20 +63,6 @@ def reset_result_list(result_list: List[Document], document_list: List[Document]
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
def get_none_result(question):
|
|
||||||
return NodeResult(
|
|
||||||
{'document_list': [], 'question': question,
|
|
||||||
'result_list': [], 'result': ''}, {})
|
|
||||||
|
|
||||||
|
|
||||||
def reset_metadata(metadata):
|
|
||||||
meta = metadata.get('meta')
|
|
||||||
if isinstance(metadata.get('meta'), dict):
|
|
||||||
if not meta.get('allow_download', False):
|
|
||||||
metadata['meta'] = {'allow_download': False}
|
|
||||||
return metadata
|
|
||||||
|
|
||||||
|
|
||||||
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', [])
|
||||||
|
|
@ -83,22 +71,16 @@ class BaseRerankerNode(IRerankerNode):
|
||||||
self.context['result_list'] = details.get('result_list')
|
self.context['result_list'] = details.get('result_list')
|
||||||
self.context['result'] = details.get('result')
|
self.context['result'] = details.get('result')
|
||||||
|
|
||||||
def execute(self, question, reranker_setting, reranker_list, reranker_model_id, show_knowledge,
|
def execute(self, question, reranker_setting, reranker_list, reranker_model_id,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
self.context['show_knowledge'] = show_knowledge
|
|
||||||
documents = merge_reranker_list(reranker_list)
|
documents = merge_reranker_list(reranker_list)
|
||||||
documents = [d for d in documents if d.page_content and len(d.page_content) > 0]
|
|
||||||
if len(documents) == 0:
|
|
||||||
return get_none_result(question)
|
|
||||||
top_n = reranker_setting.get('top_n', 3)
|
top_n = reranker_setting.get('top_n', 3)
|
||||||
self.context['document_list'] = [
|
self.context['document_list'] = [{'page_content': document.page_content, 'metadata': document.metadata} for
|
||||||
{'page_content': document.page_content, 'metadata': reset_metadata(document.metadata)} for
|
document in documents]
|
||||||
document in documents]
|
|
||||||
self.context['question'] = question
|
self.context['question'] = question
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
reranker_model = get_model_instance_by_model_user_id(reranker_model_id,
|
||||||
reranker_model = get_model_instance_by_model_workspace_id(reranker_model_id,
|
self.flow_params_serializer.data.get('user_id'),
|
||||||
workspace_id,
|
top_n=top_n)
|
||||||
top_n=top_n)
|
|
||||||
result = reranker_model.compress_documents(
|
result = reranker_model.compress_documents(
|
||||||
documents,
|
documents,
|
||||||
question)
|
question)
|
||||||
|
|
@ -110,7 +92,6 @@ class BaseRerankerNode(IRerankerNode):
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
return {
|
return {
|
||||||
'show_knowledge': self.context.get('show_knowledge'),
|
|
||||||
'name': self.node.properties.get('stepName'),
|
'name': self.node.properties.get('stepName'),
|
||||||
"index": index,
|
"index": index,
|
||||||
'document_list': self.context.get('document_list'),
|
'document_list': self.context.get('document_list'),
|
||||||
|
|
|
||||||
|
|
@ -13,37 +13,34 @@ from django.core import validators
|
||||||
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
|
||||||
from common.utils.common import flat_map
|
from common.util.common import flat_map
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
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,
|
||||||
label=_("Reference segment number"))
|
error_messages=ErrMessage.integer(_("Reference segment number")))
|
||||||
# 相似度 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,
|
||||||
label=_('similarity'))
|
error_messages=ErrMessage.float(_('similarity')))
|
||||||
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=_("The type only supports embedding|keywords|blend"), code=500)
|
||||||
], label=_("Retrieval Mode"))
|
], error_messages=ErrMessage.char(_("Retrieval Mode")))
|
||||||
max_paragraph_char_number = serializers.IntegerField(required=True,
|
max_paragraph_char_number = serializers.IntegerField(required=True,
|
||||||
label=_("Maximum number of words in a quoted segment"))
|
error_messages=ErrMessage.float(_("Maximum number of words in a quoted segment")))
|
||||||
|
|
||||||
|
|
||||||
class SearchDatasetStepNodeSerializer(serializers.Serializer):
|
class SearchDatasetStepNodeSerializer(serializers.Serializer):
|
||||||
# 需要查询的数据集id列表
|
# 需要查询的数据集id列表
|
||||||
knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
dataset_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),
|
||||||
label=_("Dataset id list"))
|
error_messages=ErrMessage.list(_("Dataset id list")))
|
||||||
knowledge_setting = DatasetSettingSerializer(required=True)
|
dataset_setting = DatasetSettingSerializer(required=True)
|
||||||
|
|
||||||
question_reference_address = serializers.ListField(required=True)
|
question_reference_address = serializers.ListField(required=True)
|
||||||
|
|
||||||
show_knowledge = serializers.BooleanField(required=True,
|
|
||||||
label=_("The results are displayed in the knowledge sources"))
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -55,8 +52,8 @@ def get_paragraph_list(chat_record, node_id):
|
||||||
'paragraph_list', []) is not None and key == node_id])
|
'paragraph_list', []) is not None and key == node_id])
|
||||||
|
|
||||||
|
|
||||||
class ISearchKnowledgeStepNode(INode):
|
class ISearchDatasetStepNode(INode):
|
||||||
type = 'search-knowledge-node'
|
type = 'search-dataset-node'
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
return SearchDatasetStepNodeSerializer
|
return SearchDatasetStepNodeSerializer
|
||||||
|
|
@ -76,7 +73,7 @@ class ISearchKnowledgeStepNode(INode):
|
||||||
return self.execute(**self.node_params_serializer.data, question=str(question),
|
return self.execute(**self.node_params_serializer.data, question=str(question),
|
||||||
exclude_paragraph_id_list=exclude_paragraph_id_list)
|
exclude_paragraph_id_list=exclude_paragraph_id_list)
|
||||||
|
|
||||||
def execute(self, dataset_id_list, dataset_setting, question, show_knowledge,
|
def execute(self, dataset_id_list, dataset_setting, question,
|
||||||
exclude_paragraph_id_list=None,
|
exclude_paragraph_id_list=None,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
pass
|
pass
|
||||||
|
|
@ -6,4 +6,4 @@
|
||||||
@date:2024/6/11 15:35
|
@date:2024/6/11 15:35
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
from .base_search_knowledge_node import BaseSearchKnowledgeNode
|
from .base_search_dataset_node import BaseSearchDatasetNode
|
||||||
|
|
@ -9,28 +9,26 @@
|
||||||
import os
|
import os
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db import connection
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
from django.db import connection
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.search_knowledge_node.i_search_knowledge_node import ISearchKnowledgeStepNode
|
from application.flow.step_node.search_dataset_node.i_search_dataset_node import ISearchDatasetStepNode
|
||||||
from common.config.embedding_config import VectorStore
|
from common.config.embedding_config import VectorStore
|
||||||
from common.constants.permission_constants import RoleConstants
|
|
||||||
from common.database_model_manage.database_model_manage import DatabaseModelManage
|
|
||||||
from common.db.search import native_search
|
from common.db.search import native_search
|
||||||
from common.utils.common import get_file_content
|
from common.util.file_util import get_file_content
|
||||||
from knowledge.models import Document, Paragraph, Knowledge, SearchMode
|
from dataset.models import Document, Paragraph, DataSet
|
||||||
from maxkb.conf import PROJECT_DIR
|
from embedding.models import SearchMode
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
from smartdoc.conf import PROJECT_DIR
|
||||||
|
|
||||||
|
|
||||||
def get_embedding_id(dataset_id_list):
|
def get_embedding_id(dataset_id_list):
|
||||||
dataset_list = QuerySet(Knowledge).filter(id__in=dataset_id_list)
|
dataset_list = QuerySet(DataSet).filter(id__in=dataset_id_list)
|
||||||
if len(set([dataset.embedding_model_id for dataset in dataset_list])) > 1:
|
if len(set([dataset.embedding_mode_id for dataset in dataset_list])) > 1:
|
||||||
raise Exception("关联知识库的向量模型不一致,无法召回分段。")
|
raise Exception("关联知识库的向量模型不一致,无法召回分段。")
|
||||||
if len(dataset_list) == 0:
|
if len(dataset_list) == 0:
|
||||||
raise Exception("知识库设置错误,请重新设置知识库")
|
raise Exception("知识库设置错误,请重新设置知识库")
|
||||||
return dataset_list[0].embedding_model_id
|
return dataset_list[0].embedding_mode_id
|
||||||
|
|
||||||
|
|
||||||
def get_none_result(question):
|
def get_none_result(question):
|
||||||
|
|
@ -46,16 +44,10 @@ def reset_title(title):
|
||||||
return f"#### {title}\n"
|
return f"#### {title}\n"
|
||||||
|
|
||||||
|
|
||||||
def reset_meta(meta):
|
class BaseSearchDatasetNode(ISearchDatasetStepNode):
|
||||||
if not meta.get('allow_download', False):
|
|
||||||
return {'allow_download': False}
|
|
||||||
return meta
|
|
||||||
|
|
||||||
|
|
||||||
class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
result = details.get('paragraph_list', [])
|
result = details.get('paragraph_list', [])
|
||||||
knowledge_setting = self.node_params_serializer.data.get('knowledge_setting')
|
dataset_setting = self.node_params_serializer.data.get('dataset_setting')
|
||||||
directly_return = '\n'.join(
|
directly_return = '\n'.join(
|
||||||
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in result if
|
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in result if
|
||||||
paragraph.get('is_hit_handling_method')])
|
paragraph.get('is_hit_handling_method')])
|
||||||
|
|
@ -65,34 +57,26 @@ class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
||||||
self.context['is_hit_handling_method_list'] = [row for row in result if row.get('is_hit_handling_method')]
|
self.context['is_hit_handling_method_list'] = [row for row in result if row.get('is_hit_handling_method')]
|
||||||
self.context['data'] = '\n'.join(
|
self.context['data'] = '\n'.join(
|
||||||
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in
|
[f"{paragraph.get('title', '')}:{paragraph.get('content')}" for paragraph in
|
||||||
result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)]
|
result])[0:dataset_setting.get('max_paragraph_char_number', 5000)]
|
||||||
self.context['directly_return'] = directly_return
|
self.context['directly_return'] = directly_return
|
||||||
|
|
||||||
def execute(self, knowledge_id_list, knowledge_setting, question, show_knowledge,
|
def execute(self, dataset_id_list, dataset_setting, question,
|
||||||
exclude_paragraph_id_list=None,
|
exclude_paragraph_id_list=None,
|
||||||
**kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
self.context['question'] = question
|
self.context['question'] = question
|
||||||
self.context['show_knowledge'] = show_knowledge
|
if len(dataset_id_list) == 0:
|
||||||
get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')
|
|
||||||
chat_user_type = self.workflow_manage.get_body().get('chat_user_type')
|
|
||||||
if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:
|
|
||||||
knowledge_id_list = get_knowledge_list_of_authorized(self.workflow_manage.get_body().get('chat_user_id'),
|
|
||||||
knowledge_id_list)
|
|
||||||
if len(knowledge_id_list) == 0:
|
|
||||||
return get_none_result(question)
|
return get_none_result(question)
|
||||||
model_id = get_embedding_id(knowledge_id_list)
|
model_id = get_embedding_id(dataset_id_list)
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
embedding_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'))
|
||||||
embedding_model = get_model_instance_by_model_workspace_id(model_id, workspace_id)
|
|
||||||
embedding_value = embedding_model.embed_query(question)
|
embedding_value = embedding_model.embed_query(question)
|
||||||
vector = VectorStore.get_embedding_vector()
|
vector = VectorStore.get_embedding_vector()
|
||||||
exclude_document_id_list = [str(document.id) for document in
|
exclude_document_id_list = [str(document.id) for document in
|
||||||
QuerySet(Document).filter(
|
QuerySet(Document).filter(
|
||||||
knowledge_id__in=knowledge_id_list,
|
dataset_id__in=dataset_id_list,
|
||||||
is_active=False)]
|
is_active=False)]
|
||||||
embedding_list = vector.query(question, embedding_value, knowledge_id_list, exclude_document_id_list,
|
embedding_list = vector.query(question, embedding_value, dataset_id_list, exclude_document_id_list,
|
||||||
exclude_paragraph_id_list, True, knowledge_setting.get('top_n'),
|
exclude_paragraph_id_list, True, dataset_setting.get('top_n'),
|
||||||
knowledge_setting.get('similarity'),
|
dataset_setting.get('similarity'), SearchMode(dataset_setting.get('search_mode')))
|
||||||
SearchMode(knowledge_setting.get('search_mode')))
|
|
||||||
# 手动关闭数据库连接
|
# 手动关闭数据库连接
|
||||||
connection.close()
|
connection.close()
|
||||||
if embedding_list is None:
|
if embedding_list is None:
|
||||||
|
|
@ -104,7 +88,7 @@ class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
||||||
'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:knowledge_setting.get('max_paragraph_char_number', 5000)],
|
result])[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
|
||||||
|
|
@ -127,9 +111,8 @@ class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
||||||
'update_time': paragraph.get('update_time').strftime("%Y-%m-%d %H:%M:%S"),
|
'update_time': paragraph.get('update_time').strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
'create_time': paragraph.get('create_time').strftime("%Y-%m-%d %H:%M:%S"),
|
'create_time': paragraph.get('create_time').strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
'id': str(paragraph.get('id')),
|
'id': str(paragraph.get('id')),
|
||||||
'knowledge_id': str(paragraph.get('knowledge_id')),
|
'dataset_id': str(paragraph.get('dataset_id')),
|
||||||
'document_id': str(paragraph.get('document_id')),
|
'document_id': str(paragraph.get('document_id'))
|
||||||
'meta': reset_meta(paragraph.get('meta'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -140,7 +123,7 @@ class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
||||||
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),
|
||||||
get_file_content(
|
get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
os.path.join(PROJECT_DIR, "apps", "application", 'sql',
|
||||||
'list_knowledge_paragraph_by_paragraph_id.sql')),
|
'list_dataset_paragraph_by_paragraph_id.sql')),
|
||||||
with_table_name=True)
|
with_table_name=True)
|
||||||
# 如果向量库中存在脏数据 直接删除
|
# 如果向量库中存在脏数据 直接删除
|
||||||
if len(paragraph_list) != len(paragraph_id_list):
|
if len(paragraph_list) != len(paragraph_id_list):
|
||||||
|
|
@ -153,7 +136,6 @@ class BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
return {
|
return {
|
||||||
'name': self.node.properties.get('stepName'),
|
'name': self.node.properties.get('stepName'),
|
||||||
'show_knowledge': self.context.get('show_knowledge'),
|
|
||||||
'question': self.context.get('question'),
|
'question': self.context.get('question'),
|
||||||
"index": index,
|
"index": index,
|
||||||
'run_time': self.context.get('run_time'),
|
'run_time': self.context.get('run_time'),
|
||||||
|
|
@ -5,17 +5,16 @@ from typing import Type
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class SpeechToTextNodeSerializer(serializers.Serializer):
|
class SpeechToTextNodeSerializer(serializers.Serializer):
|
||||||
stt_model_id = serializers.CharField(required=True, label=_("Model id"))
|
stt_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
audio_list = serializers.ListField(required=True,
|
audio_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("The audio file cannot be empty")))
|
||||||
label=_("The audio file cannot be empty"))
|
|
||||||
|
|
||||||
|
|
||||||
class ISpeechToTextNode(INode):
|
class ISpeechToTextNode(INode):
|
||||||
|
|
@ -29,8 +28,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(
|
raise ValueError(_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails"))
|
||||||
_("Parameter value error: The uploaded audio lacks file_id, and the audio upload fails"))
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
import time
|
||||||
|
import io
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
from pydub import AudioSegment
|
||||||
from application.flow.i_step_node import NodeResult
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
from application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode
|
from application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode
|
||||||
from common.utils.common import split_and_transcribe, any_to_mp3
|
from common.util.common import split_and_transcribe, any_to_mp3
|
||||||
from knowledge.models import File
|
from dataset.models import File
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
|
|
||||||
|
|
||||||
class BaseSpeechToTextNode(ISpeechToTextNode):
|
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')
|
||||||
self.context['result'] = details.get('answer')
|
|
||||||
if self.node_params.get('is_result', False):
|
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:
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
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_workspace_id(stt_model_id, workspace_id)
|
|
||||||
audio_list = audio
|
audio_list = audio
|
||||||
self.context['audio_list'] = audio
|
self.context['audio_list'] = audio
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ class BaseSpeechToTextNode(ISpeechToTextNode):
|
||||||
# 根据file_name 吧文件转成mp3格式
|
# 根据file_name 吧文件转成mp3格式
|
||||||
file_format = file.file_name.split('.')[-1]
|
file_format = file.file_name.split('.')[-1]
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_format}') as temp_file:
|
with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_format}') as temp_file:
|
||||||
temp_file.write(file.get_bytes())
|
temp_file.write(file.get_byte().tobytes())
|
||||||
temp_file_path = temp_file.name
|
temp_file_path = temp_file.name
|
||||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_amr_file:
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_amr_file:
|
||||||
temp_mp3_path = temp_amr_file.name
|
temp_mp3_path = temp_amr_file.name
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Type
|
from typing import List, Type
|
||||||
from django.utils import timezone
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
|
|
@ -17,32 +17,24 @@ from application.flow.step_node.start_node.i_start_node import IStarNode
|
||||||
|
|
||||||
|
|
||||||
def get_default_global_variable(input_field_list: List):
|
def get_default_global_variable(input_field_list: List):
|
||||||
return {
|
return {item.get('variable'): item.get('default_value') for item in input_field_list if
|
||||||
item.get('variable') or item.get('field'): item.get('default_value')
|
item.get('default_value', None) is not None}
|
||||||
for item in input_field_list
|
|
||||||
if item.get('default_value', None) is not None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_global_variable(node):
|
def get_global_variable(node):
|
||||||
body = node.workflow_manage.get_body()
|
|
||||||
history_chat_record = node.flow_params_serializer.data.get('history_chat_record', [])
|
history_chat_record = node.flow_params_serializer.data.get('history_chat_record', [])
|
||||||
history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in
|
history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in
|
||||||
history_chat_record]
|
history_chat_record]
|
||||||
chat_id = node.flow_params_serializer.data.get('chat_id')
|
chat_id = node.flow_params_serializer.data.get('chat_id')
|
||||||
return {'time': timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(),
|
return {'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(),
|
||||||
'history_context': history_context, 'chat_id': str(chat_id), **node.workflow_manage.form_data,
|
'history_context': history_context, 'chat_id': str(chat_id), **node.workflow_manage.form_data}
|
||||||
'chat_user_id': body.get('chat_user_id'),
|
|
||||||
'chat_user_type': body.get('chat_user_type'),
|
|
||||||
'chat_user': body.get('chat_user')}
|
|
||||||
|
|
||||||
|
|
||||||
class BaseStartStepNode(IStarNode):
|
class BaseStartStepNode(IStarNode):
|
||||||
def save_context(self, details, workflow_manage):
|
def save_context(self, details, workflow_manage):
|
||||||
base_node = self.workflow_manage.get_base_node()
|
base_node = self.workflow_manage.get_base_node()
|
||||||
default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', []))
|
default_global_variable = get_default_global_variable(base_node.properties.get('input_field_list', []))
|
||||||
default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', []))
|
workflow_variable = {**default_global_variable, **get_global_variable(self)}
|
||||||
workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)}
|
|
||||||
self.context['question'] = details.get('question')
|
self.context['question'] = details.get('question')
|
||||||
self.context['run_time'] = details.get('run_time')
|
self.context['run_time'] = details.get('run_time')
|
||||||
self.context['document'] = details.get('document_list')
|
self.context['document'] = details.get('document_list')
|
||||||
|
|
@ -55,16 +47,14 @@ class BaseStartStepNode(IStarNode):
|
||||||
workflow_manage.context[key] = value
|
workflow_manage.context[key] = value
|
||||||
for item in details.get('global_fields', []):
|
for item in details.get('global_fields', []):
|
||||||
workflow_manage.context[item.get('key')] = item.get('value')
|
workflow_manage.context[item.get('key')] = item.get('value')
|
||||||
self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable()
|
|
||||||
|
|
||||||
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def execute(self, question, **kwargs) -> NodeResult:
|
def execute(self, question, **kwargs) -> NodeResult:
|
||||||
base_node = self.workflow_manage.get_base_node()
|
base_node = self.workflow_manage.get_base_node()
|
||||||
default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', []))
|
default_global_variable = get_default_global_variable(base_node.properties.get('input_field_list', []))
|
||||||
default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', []))
|
workflow_variable = {**default_global_variable, **get_global_variable(self)}
|
||||||
workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)}
|
|
||||||
"""
|
"""
|
||||||
开始节点 初始化全局变量
|
开始节点 初始化全局变量
|
||||||
"""
|
"""
|
||||||
|
|
@ -74,9 +64,7 @@ class BaseStartStepNode(IStarNode):
|
||||||
'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,
|
'other': self.workflow_manage.other_list,
|
||||||
|
|
||||||
}
|
}
|
||||||
self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable()
|
|
||||||
return NodeResult(node_variable, workflow_variable)
|
return NodeResult(node_variable, workflow_variable)
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,18 @@ from typing import Type
|
||||||
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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class TextToSpeechNodeSerializer(serializers.Serializer):
|
class TextToSpeechNodeSerializer(serializers.Serializer):
|
||||||
tts_model_id = serializers.CharField(required=True, label=_("Model id"))
|
tts_model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id")))
|
||||||
|
|
||||||
is_result = serializers.BooleanField(required=False,
|
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
|
||||||
label=_('Whether to return content'))
|
|
||||||
|
|
||||||
content_list = serializers.ListField(required=True, label=_("Text content"))
|
content_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Text content")))
|
||||||
model_params_setting = serializers.DictField(required=False,
|
model_params_setting = serializers.DictField(required=False,
|
||||||
label=_("Model parameter settings"))
|
error_messages=ErrMessage.integer(_("Model parameter settings")))
|
||||||
|
|
||||||
|
|
||||||
class ITextToSpeechNode(INode):
|
class ITextToSpeechNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,12 @@ import mimetypes
|
||||||
|
|
||||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult, INode
|
||||||
|
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
|
||||||
from application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode
|
from application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode
|
||||||
from common.utils.common import _remove_empty_lines
|
from dataset.models import File
|
||||||
from knowledge.models import FileSourceType
|
from dataset.serializers.file_serializers import FileSerializer
|
||||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
from setting.models_provider.tools import get_model_instance_by_model_user_id
|
||||||
from oss.serializers.file import FileSerializer
|
|
||||||
from pydub import AudioSegment
|
|
||||||
|
|
||||||
|
|
||||||
def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
|
def bytes_to_uploaded_file(file_bytes, file_name="generated_audio.mp3"):
|
||||||
|
|
@ -38,78 +37,31 @@ 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')
|
||||||
self.context['result'] = details.get('result')
|
|
||||||
if self.node_params.get('is_result', False):
|
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,
|
||||||
max_length=1024, **kwargs) -> NodeResult:
|
**kwargs) -> NodeResult:
|
||||||
# 分割文本为合理片段
|
self.context['content'] = content
|
||||||
content = _remove_empty_lines(content)
|
model = get_model_instance_by_model_user_id(tts_model_id, self.flow_params_serializer.data.get('user_id'),
|
||||||
content_chunks = [content[i:i + max_length]
|
**model_params_setting)
|
||||||
for i in range(0, len(content), max_length)]
|
audio_byte = model.text_to_speech(content)
|
||||||
|
# 需要把这个音频文件存储到数据库中
|
||||||
# 生成并收集所有音频片段
|
file_name = 'generated_audio.mp3'
|
||||||
audio_segments = []
|
file = bytes_to_uploaded_file(audio_byte, file_name)
|
||||||
temp_files = []
|
|
||||||
|
|
||||||
for i, chunk in enumerate(content_chunks):
|
|
||||||
self.context['content'] = chunk
|
|
||||||
workspace_id = self.workflow_manage.get_body().get('workspace_id')
|
|
||||||
model = get_model_instance_by_model_workspace_id(
|
|
||||||
tts_model_id, workspace_id, **model_params_setting)
|
|
||||||
|
|
||||||
audio_byte = model.text_to_speech(chunk)
|
|
||||||
|
|
||||||
# 保存为临时音频文件用于合并
|
|
||||||
temp_file = io.BytesIO(audio_byte)
|
|
||||||
audio_segment = AudioSegment.from_file(temp_file)
|
|
||||||
audio_segments.append(audio_segment)
|
|
||||||
temp_files.append(temp_file)
|
|
||||||
|
|
||||||
# 合并所有音频片段
|
|
||||||
combined_audio = AudioSegment.empty()
|
|
||||||
for segment in audio_segments:
|
|
||||||
combined_audio += segment
|
|
||||||
|
|
||||||
# 将合并后的音频转为字节流
|
|
||||||
output_buffer = io.BytesIO()
|
|
||||||
combined_audio.export(output_buffer, format="mp3")
|
|
||||||
combined_bytes = output_buffer.getvalue()
|
|
||||||
|
|
||||||
# 存储合并后的音频文件
|
|
||||||
file_name = 'combined_audio.mp3'
|
|
||||||
file = bytes_to_uploaded_file(combined_bytes, file_name)
|
|
||||||
|
|
||||||
application = self.workflow_manage.work_flow_post_handler.chat_info.application
|
application = self.workflow_manage.work_flow_post_handler.chat_info.application
|
||||||
meta = {
|
meta = {
|
||||||
'debug': False if application.id else True,
|
'debug': False if application.id else True,
|
||||||
'chat_id': chat_id,
|
'chat_id': chat_id,
|
||||||
'application_id': str(application.id) if application.id else None,
|
'application_id': str(application.id) if application.id else None,
|
||||||
}
|
}
|
||||||
|
file_url = FileSerializer(data={'file': file, 'meta': meta}).upload()
|
||||||
file_url = FileSerializer(data={
|
# 拼接一个audio标签的src属性
|
||||||
'file': file,
|
audio_label = f'<audio src="{file_url}" controls style = "width: 300px; height: 43px"></audio>'
|
||||||
'meta': meta,
|
|
||||||
'source_id': meta['application_id'],
|
|
||||||
'source_type': FileSourceType.APPLICATION.value
|
|
||||||
}).upload()
|
|
||||||
|
|
||||||
# 生成音频标签
|
|
||||||
audio_label = f'<audio src="{file_url}" controls style="width: 300px; height: 43px"></audio>'
|
|
||||||
file_id = file_url.split('/')[-1]
|
file_id = file_url.split('/')[-1]
|
||||||
audio_list = [{'file_id': file_id, 'file_name': file_name, 'url': file_url}]
|
audio_list = [{'file_id': file_id, 'file_name': file_name, 'url': file_url}]
|
||||||
|
return NodeResult({'answer': audio_label, 'result': audio_list}, {})
|
||||||
# 关闭所有临时文件
|
|
||||||
for temp_file in temp_files:
|
|
||||||
temp_file.close()
|
|
||||||
output_buffer.close()
|
|
||||||
|
|
||||||
return NodeResult({
|
|
||||||
'answer': audio_label,
|
|
||||||
'result': audio_list
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
def get_details(self, index: int, **kwargs):
|
def get_details(self, index: int, **kwargs):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ 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
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
|
||||||
|
|
||||||
class VariableAssignNodeParamsSerializer(serializers.Serializer):
|
class VariableAssignNodeParamsSerializer(serializers.Serializer):
|
||||||
variable_list = serializers.ListField(required=True,
|
variable_list = serializers.ListField(required=True,
|
||||||
label=_("Reference Field"))
|
error_messages=ErrMessage.list(_("Reference Field")))
|
||||||
|
|
||||||
|
|
||||||
class IVariableAssignNode(INode):
|
class IVariableAssignNode(INode):
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,8 @@
|
||||||
import json
|
import json
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
|
|
||||||
from application.flow.i_step_node import NodeResult
|
from application.flow.i_step_node import NodeResult
|
||||||
from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode
|
from application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode
|
||||||
from application.models import Chat
|
|
||||||
|
|
||||||
|
|
||||||
class BaseVariableAssignNode(IVariableAssignNode):
|
class BaseVariableAssignNode(IVariableAssignNode):
|
||||||
|
|
@ -14,56 +11,40 @@ class BaseVariableAssignNode(IVariableAssignNode):
|
||||||
self.context['variable_list'] = details.get('variable_list')
|
self.context['variable_list'] = details.get('variable_list')
|
||||||
self.context['result_list'] = details.get('result_list')
|
self.context['result_list'] = details.get('result_list')
|
||||||
|
|
||||||
def global_evaluation(self, variable, value):
|
|
||||||
self.workflow_manage.context[variable['fields'][1]] = value
|
|
||||||
|
|
||||||
def chat_evaluation(self, variable, value):
|
|
||||||
self.workflow_manage.chat_context[variable['fields'][1]] = value
|
|
||||||
|
|
||||||
def handle(self, variable, evaluation):
|
|
||||||
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'])
|
|
||||||
evaluation(variable, val)
|
|
||||||
result['output_value'] = variable['value'] = val
|
|
||||||
elif variable['type'] == 'string':
|
|
||||||
# 变量解析 例如:{{global.xxx}}
|
|
||||||
val = self.workflow_manage.generate_prompt(variable['value'])
|
|
||||||
evaluation(variable, val)
|
|
||||||
result['output_value'] = val
|
|
||||||
else:
|
|
||||||
val = variable['value']
|
|
||||||
evaluation(variable, val)
|
|
||||||
result['output_value'] = val
|
|
||||||
else:
|
|
||||||
reference = self.get_reference_content(variable['reference'])
|
|
||||||
evaluation(variable, reference)
|
|
||||||
result['output_value'] = reference
|
|
||||||
return result
|
|
||||||
|
|
||||||
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
|
def execute(self, variable_list, stream, **kwargs) -> NodeResult:
|
||||||
#
|
#
|
||||||
result_list = []
|
result_list = []
|
||||||
is_chat = False
|
|
||||||
for variable in variable_list:
|
for variable in variable_list:
|
||||||
if 'fields' not in variable:
|
if 'fields' not in variable:
|
||||||
continue
|
continue
|
||||||
if 'global' == variable['fields'][0]:
|
if 'global' == variable['fields'][0]:
|
||||||
result = self.handle(variable, self.global_evaluation)
|
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)
|
result_list.append(result)
|
||||||
if 'chat' == variable['fields'][0]:
|
|
||||||
result = self.handle(variable, self.chat_evaluation)
|
|
||||||
result_list.append(result)
|
|
||||||
is_chat = True
|
|
||||||
if is_chat:
|
|
||||||
self.workflow_manage.get_chat_info().set_chat_variable(self.workflow_manage.chat_context)
|
|
||||||
return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})
|
return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})
|
||||||
|
|
||||||
def get_reference_content(self, fields: List[str]):
|
def get_reference_content(self, fields: List[str]):
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from django.http import StreamingHttpResponse
|
||||||
from langchain_core.messages import BaseMessageChunk, BaseMessage
|
from langchain_core.messages import BaseMessageChunk, BaseMessage
|
||||||
|
|
||||||
from application.flow.i_step_node import WorkFlowPostHandler
|
from application.flow.i_step_node import WorkFlowPostHandler
|
||||||
from common.result import result
|
from common.response import result
|
||||||
|
|
||||||
|
|
||||||
class Reasoning:
|
class Reasoning:
|
||||||
|
|
@ -60,10 +60,7 @@ class Reasoning:
|
||||||
if not self.reasoning_content_is_end:
|
if not self.reasoning_content_is_end:
|
||||||
self.reasoning_content_is_end = True
|
self.reasoning_content_is_end = True
|
||||||
self.content += self.all_content
|
self.content += self.all_content
|
||||||
return {'content': self.all_content,
|
return {'content': self.all_content, 'reasoning_content': ''}
|
||||||
'reasoning_content': chunk.additional_kwargs.get('reasoning_content',
|
|
||||||
'') if chunk.additional_kwargs else ''
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
if self.reasoning_content_is_start:
|
if self.reasoning_content_is_start:
|
||||||
self.reasoning_content_chunk += chunk.content
|
self.reasoning_content_chunk += chunk.content
|
||||||
|
|
@ -71,9 +68,7 @@ class Reasoning:
|
||||||
self.reasoning_content_end_tag_prefix)
|
self.reasoning_content_end_tag_prefix)
|
||||||
if self.reasoning_content_is_end:
|
if self.reasoning_content_is_end:
|
||||||
self.content += chunk.content
|
self.content += chunk.content
|
||||||
return {'content': chunk.content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content',
|
return {'content': chunk.content, 'reasoning_content': ''}
|
||||||
'') if chunk.additional_kwargs else ''
|
|
||||||
}
|
|
||||||
# 是否包含结束
|
# 是否包含结束
|
||||||
if reasoning_content_end_tag_prefix_index > -1:
|
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:
|
if len(self.reasoning_content_chunk) - reasoning_content_end_tag_prefix_index >= self.reasoning_content_end_tag_len:
|
||||||
|
|
@ -98,9 +93,7 @@ class Reasoning:
|
||||||
else:
|
else:
|
||||||
if self.reasoning_content_is_end:
|
if self.reasoning_content_is_end:
|
||||||
self.content += chunk.content
|
self.content += chunk.content
|
||||||
return {'content': chunk.content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content',
|
return {'content': chunk.content, 'reasoning_content': ''}
|
||||||
'') if chunk.additional_kwargs else ''
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
# aaa
|
# aaa
|
||||||
result = {'content': '', 'reasoning_content': self.reasoning_content_chunk}
|
result = {'content': '', 'reasoning_content': self.reasoning_content_chunk}
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,166 @@ 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, connection
|
from django.db import close_old_connections
|
||||||
|
from django.db.models import QuerySet
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.translation import get_language
|
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 application.flow import tools
|
from application.flow import tools
|
||||||
from application.flow.common import Workflow
|
|
||||||
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.handle.base_to_response import BaseToResponse
|
from common.handle.base_to_response import BaseToResponse
|
||||||
from common.handle.impl.response.system_to_response import SystemToResponse
|
from common.handle.impl.response.system_to_response import SystemToResponse
|
||||||
|
from function_lib.models.function import FunctionLib
|
||||||
|
from setting.models import Model
|
||||||
|
from setting.models_provider import get_model_credential
|
||||||
|
|
||||||
executor = ThreadPoolExecutor(max_workers=200)
|
executor = ThreadPoolExecutor(max_workers=200)
|
||||||
|
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords):
|
||||||
|
self.id = _id
|
||||||
|
self.type = _type
|
||||||
|
self.sourceNodeId = sourceNodeId
|
||||||
|
self.targetNodeId = targetNodeId
|
||||||
|
for keyword in keywords:
|
||||||
|
self.__setattr__(keyword, keywords.get(keyword))
|
||||||
|
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs):
|
||||||
|
self.id = _id
|
||||||
|
self.type = _type
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.properties = properties
|
||||||
|
for keyword in kwargs:
|
||||||
|
self.__setattr__(keyword, kwargs.get(keyword))
|
||||||
|
|
||||||
|
|
||||||
|
end_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node',
|
||||||
|
'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node']
|
||||||
|
|
||||||
|
|
||||||
|
class Flow:
|
||||||
|
def __init__(self, nodes: List[Node], edges: List[Edge]):
|
||||||
|
self.nodes = nodes
|
||||||
|
self.edges = edges
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_instance(flow_obj: Dict):
|
||||||
|
nodes = flow_obj.get('nodes')
|
||||||
|
edges = flow_obj.get('edges')
|
||||||
|
nodes = [Node(node.get('id'), node.get('type'), **node)
|
||||||
|
for node in nodes]
|
||||||
|
edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges]
|
||||||
|
return Flow(nodes, edges)
|
||||||
|
|
||||||
|
def get_start_node(self):
|
||||||
|
start_node_list = [node for node in self.nodes if node.id == 'start-node']
|
||||||
|
return start_node_list[0]
|
||||||
|
|
||||||
|
def get_search_node(self):
|
||||||
|
return [node for node in self.nodes if node.type == 'search-dataset-node']
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
"""
|
||||||
|
校验工作流数据
|
||||||
|
"""
|
||||||
|
self.is_valid_model_params()
|
||||||
|
self.is_valid_start_node()
|
||||||
|
self.is_valid_base_node()
|
||||||
|
self.is_valid_work_flow()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_valid_node_params(node: Node):
|
||||||
|
get_node(node.type)(node, None, None)
|
||||||
|
|
||||||
|
def is_valid_node(self, node: Node):
|
||||||
|
self.is_valid_node_params(node)
|
||||||
|
if node.type == 'condition-node':
|
||||||
|
branch_list = node.properties.get('node_data').get('branch')
|
||||||
|
for branch in branch_list:
|
||||||
|
source_anchor_id = f"{node.id}_{branch.get('id')}_right"
|
||||||
|
edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id]
|
||||||
|
if len(edge_list) == 0:
|
||||||
|
raise AppApiException(500,
|
||||||
|
_('The branch {branch} of the {node} node needs to be connected').format(
|
||||||
|
node=node.properties.get("stepName"), branch=branch.get("type")))
|
||||||
|
|
||||||
|
else:
|
||||||
|
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):
|
||||||
|
raise AppApiException(500, _("{node} Nodes cannot be considered as end nodes").format(
|
||||||
|
node=node.properties.get("stepName")))
|
||||||
|
|
||||||
|
def get_next_nodes(self, node: Node):
|
||||||
|
edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]
|
||||||
|
node_list = reduce(lambda x, y: [*x, *y],
|
||||||
|
[[node for node in self.nodes if node.id == edge.targetNodeId] for edge in edge_list],
|
||||||
|
[])
|
||||||
|
if len(node_list) == 0 and not end_nodes.__contains__(node.type):
|
||||||
|
raise AppApiException(500,
|
||||||
|
_("The next node that does not exist"))
|
||||||
|
return node_list
|
||||||
|
|
||||||
|
def is_valid_work_flow(self, up_node=None):
|
||||||
|
if up_node is None:
|
||||||
|
up_node = self.get_start_node()
|
||||||
|
self.is_valid_node(up_node)
|
||||||
|
next_nodes = self.get_next_nodes(up_node)
|
||||||
|
for next_node in next_nodes:
|
||||||
|
self.is_valid_work_flow(next_node)
|
||||||
|
|
||||||
|
def is_valid_start_node(self):
|
||||||
|
start_node_list = [node for node in self.nodes if node.id == 'start-node']
|
||||||
|
if len(start_node_list) == 0:
|
||||||
|
raise AppApiException(500, _('The starting node is required'))
|
||||||
|
if len(start_node_list) > 1:
|
||||||
|
raise AppApiException(500, _('There can only be one starting node'))
|
||||||
|
|
||||||
|
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')]
|
||||||
|
for node in node_list:
|
||||||
|
model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first()
|
||||||
|
if model is None:
|
||||||
|
raise ValidationError(ErrorDetail(
|
||||||
|
_('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)
|
||||||
|
model_params_setting = node.properties.get('node_data', {}).get('model_params_setting')
|
||||||
|
model_params_setting_form = credential.get_model_params_setting_form(
|
||||||
|
model.model_name)
|
||||||
|
if model_params_setting is None:
|
||||||
|
model_params_setting = model_params_setting_form.get_default_form_data()
|
||||||
|
node.properties.get('node_data', {})['model_params_setting'] = model_params_setting
|
||||||
|
if node.properties.get('status', 200) != 200:
|
||||||
|
raise ValidationError(
|
||||||
|
ErrorDetail(_("Node {node} is unavailable").format(node.properties.get("stepName"))))
|
||||||
|
node_list = [node for node in self.nodes if (node.type == 'function-lib-node')]
|
||||||
|
for node in node_list:
|
||||||
|
function_lib_id = node.properties.get('node_data', {}).get('function_lib_id')
|
||||||
|
if function_lib_id is None:
|
||||||
|
raise ValidationError(ErrorDetail(
|
||||||
|
_('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()
|
||||||
|
if f_lib is None:
|
||||||
|
raise ValidationError(ErrorDetail(_("The function library for node {node} is not available").format(
|
||||||
|
node=node.properties.get("stepName"))))
|
||||||
|
|
||||||
|
def is_valid_base_node(self):
|
||||||
|
base_node_list = [node for node in self.nodes if node.id == 'base-node']
|
||||||
|
if len(base_node_list) == 0:
|
||||||
|
raise AppApiException(500, _('Basic information node is required'))
|
||||||
|
if len(base_node_list) > 1:
|
||||||
|
raise AppApiException(500, _('There can only be one basic information node'))
|
||||||
|
|
||||||
|
|
||||||
class NodeResultFuture:
|
class NodeResultFuture:
|
||||||
def __init__(self, r, e, status=200):
|
def __init__(self, r, e, status=200):
|
||||||
self.r = r
|
self.r = r
|
||||||
|
|
@ -90,7 +234,7 @@ class NodeChunkManage:
|
||||||
|
|
||||||
|
|
||||||
class WorkflowManage:
|
class WorkflowManage:
|
||||||
def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler,
|
def __init__(self, flow: Flow, params, work_flow_post_handler: WorkFlowPostHandler,
|
||||||
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,
|
||||||
|
|
@ -117,7 +261,6 @@ class WorkflowManage:
|
||||||
self.params = params
|
self.params = params
|
||||||
self.flow = flow
|
self.flow = flow
|
||||||
self.context = {}
|
self.context = {}
|
||||||
self.chat_context = {}
|
|
||||||
self.node_chunk_manage = NodeChunkManage(self)
|
self.node_chunk_manage = NodeChunkManage(self)
|
||||||
self.work_flow_post_handler = work_flow_post_handler
|
self.work_flow_post_handler = work_flow_post_handler
|
||||||
self.current_node = None
|
self.current_node = None
|
||||||
|
|
@ -132,7 +275,6 @@ class WorkflowManage:
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.field_list = []
|
self.field_list = []
|
||||||
self.global_field_list = []
|
self.global_field_list = []
|
||||||
self.chat_field_list = []
|
|
||||||
self.init_fields()
|
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)
|
||||||
|
|
@ -142,7 +284,6 @@ class WorkflowManage:
|
||||||
def init_fields(self):
|
def init_fields(self):
|
||||||
field_list = []
|
field_list = []
|
||||||
global_field_list = []
|
global_field_list = []
|
||||||
chat_field_list = []
|
|
||||||
for node in self.flow.nodes:
|
for node in self.flow.nodes:
|
||||||
properties = node.properties
|
properties = node.properties
|
||||||
node_name = properties.get('stepName')
|
node_name = properties.get('stepName')
|
||||||
|
|
@ -157,16 +298,10 @@ class WorkflowManage:
|
||||||
if global_fields is not None:
|
if global_fields is not None:
|
||||||
for global_field in global_fields:
|
for global_field in global_fields:
|
||||||
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
|
global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})
|
||||||
chat_fields = node_config.get('chatFields')
|
field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
|
||||||
if chat_fields is not None:
|
global_field_list.sort(key=lambda f: len(f.get('node_name')), reverse=True)
|
||||||
for chat_field in chat_fields:
|
|
||||||
chat_field_list.append({**chat_field, 'node_id': node_id, 'node_name': node_name})
|
|
||||||
field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
|
||||||
global_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
|
||||||
chat_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)
|
|
||||||
self.field_list = field_list
|
self.field_list = field_list
|
||||||
self.global_field_list = global_field_list
|
self.global_field_list = global_field_list
|
||||||
self.chat_field_list = chat_field_list
|
|
||||||
|
|
||||||
def append_answer(self, content):
|
def append_answer(self, content):
|
||||||
self.answer += content
|
self.answer += content
|
||||||
|
|
@ -232,7 +367,9 @@ class WorkflowManage:
|
||||||
'\n\n'.join([a.get('content') for a in answer]) for answer in
|
'\n\n'.join([a.get('content') for a in answer]) for answer in
|
||||||
answer_text_list)
|
answer_text_list)
|
||||||
answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])
|
answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])
|
||||||
self.work_flow_post_handler.handler(self)
|
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
|
||||||
|
answer_text,
|
||||||
|
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,
|
||||||
|
|
@ -247,9 +384,6 @@ class WorkflowManage:
|
||||||
self.run_chain_async(current_node, node_result_future, language)
|
self.run_chain_async(current_node, node_result_future, language)
|
||||||
return tools.to_stream_response_simple(self.await_result())
|
return tools.to_stream_response_simple(self.await_result())
|
||||||
|
|
||||||
def get_body(self):
|
|
||||||
return self.params
|
|
||||||
|
|
||||||
def is_run(self, timeout=0.5):
|
def is_run(self, timeout=0.5):
|
||||||
future_list_len = len(self.future_list)
|
future_list_len = len(self.future_list)
|
||||||
try:
|
try:
|
||||||
|
|
@ -286,7 +420,9 @@ class WorkflowManage:
|
||||||
'message_tokens' in row and row.get('message_tokens') is not None])
|
'message_tokens' in row and row.get('message_tokens') is not None])
|
||||||
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])
|
||||||
self.work_flow_post_handler.handler(self)
|
self.work_flow_post_handler.handler(self.params['chat_id'], self.params['chat_record_id'],
|
||||||
|
self.answer,
|
||||||
|
self)
|
||||||
yield self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
|
yield self.base_to_response.to_stream_chunk_response(self.params['chat_id'],
|
||||||
self.params['chat_record_id'],
|
self.params['chat_record_id'],
|
||||||
'',
|
'',
|
||||||
|
|
@ -433,8 +569,6 @@ class WorkflowManage:
|
||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
current_node.node_chunk.end()
|
current_node.node_chunk.end()
|
||||||
# 归还链接到连接池
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
def run_node_async(self, node):
|
def run_node_async(self, node):
|
||||||
future = executor.submit(self.run_node, node)
|
future = executor.submit(self.run_node, node)
|
||||||
|
|
@ -456,9 +590,6 @@ class WorkflowManage:
|
||||||
return current_node.node_params.get('is_result', not self._has_next_node(
|
return current_node.node_params.get('is_result', not self._has_next_node(
|
||||||
current_node, current_node_result)) if current_node.node_params is not None else False
|
current_node, current_node_result)) if current_node.node_params is not None else False
|
||||||
|
|
||||||
def get_chat_info(self):
|
|
||||||
return self.work_flow_post_handler.chat_info
|
|
||||||
|
|
||||||
def get_chunk_content(self, chunk, is_end=False):
|
def get_chunk_content(self, chunk, is_end=False):
|
||||||
return 'data: ' + json.dumps(
|
return 'data: ' + json.dumps(
|
||||||
{'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True,
|
{'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True,
|
||||||
|
|
@ -468,14 +599,15 @@ class WorkflowManage:
|
||||||
"""
|
"""
|
||||||
是否有下一个可运行的节点
|
是否有下一个可运行的节点
|
||||||
"""
|
"""
|
||||||
next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or []
|
if node_result is not None and node_result.is_assertion_result():
|
||||||
for next_edge_node in next_edge_node_list:
|
for edge in self.flow.edges:
|
||||||
if node_result is not None and node_result.is_assertion_result():
|
|
||||||
edge = next_edge_node.edge
|
|
||||||
if (edge.sourceNodeId == current_node.id and
|
if (edge.sourceNodeId == current_node.id and
|
||||||
f"{edge.sourceNodeId}_{node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
f"{edge.sourceNodeId}_{node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
||||||
return True
|
return True
|
||||||
return len(next_edge_node_list) > 0
|
else:
|
||||||
|
for edge in self.flow.edges:
|
||||||
|
if edge.sourceNodeId == current_node.id:
|
||||||
|
return True
|
||||||
|
|
||||||
def has_next_node(self, node_result: NodeResult | None):
|
def has_next_node(self, node_result: NodeResult | None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -525,6 +657,26 @@ class WorkflowManage:
|
||||||
return [[]]
|
return [[]]
|
||||||
return [[item.to_dict() for item in r] for r in result]
|
return [[item.to_dict() for item in r] for r in result]
|
||||||
|
|
||||||
|
def get_next_node(self):
|
||||||
|
"""
|
||||||
|
获取下一个可运行的所有节点
|
||||||
|
"""
|
||||||
|
if self.current_node is None:
|
||||||
|
node = self.get_start_node()
|
||||||
|
node_instance = get_node(node.type)(node, self.params, self)
|
||||||
|
return node_instance
|
||||||
|
if self.current_result is not None and self.current_result.is_assertion_result():
|
||||||
|
for edge in self.flow.edges:
|
||||||
|
if (edge.sourceNodeId == self.current_node.id and
|
||||||
|
f"{edge.sourceNodeId}_{self.current_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
||||||
|
return self.get_node_cls_by_id(edge.targetNodeId)
|
||||||
|
else:
|
||||||
|
for edge in self.flow.edges:
|
||||||
|
if edge.sourceNodeId == self.current_node.id:
|
||||||
|
return self.get_node_cls_by_id(edge.targetNodeId)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def dependent_node(up_node_id, node):
|
def dependent_node(up_node_id, node):
|
||||||
if not node.node_chunk.is_end():
|
if not node.node_chunk.is_end():
|
||||||
|
|
@ -561,14 +713,14 @@ class WorkflowManage:
|
||||||
if current_node_result.is_interrupt_exec(current_node):
|
if current_node_result.is_interrupt_exec(current_node):
|
||||||
return []
|
return []
|
||||||
node_list = []
|
node_list = []
|
||||||
next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or []
|
|
||||||
if current_node_result is not None and current_node_result.is_assertion_result():
|
if current_node_result is not None and current_node_result.is_assertion_result():
|
||||||
for edge_node in next_edge_node_list:
|
for edge in self.flow.edges:
|
||||||
edge = edge_node.edge
|
if (edge.sourceNodeId == current_node.id and
|
||||||
next_node = edge_node.node
|
|
||||||
if (
|
|
||||||
f"{edge.sourceNodeId}_{current_node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
f"{edge.sourceNodeId}_{current_node_result.node_variable.get('branch_id')}_right" == edge.sourceAnchorId):
|
||||||
if next_node.properties.get('condition', "AND") == 'AND':
|
next_node = [node for node in self.flow.nodes if node.id == edge.targetNodeId]
|
||||||
|
if len(next_node) == 0:
|
||||||
|
continue
|
||||||
|
if next_node[0].properties.get('condition', "AND") == 'AND':
|
||||||
if self.dependent_node_been_executed(edge.targetNodeId):
|
if self.dependent_node_been_executed(edge.targetNodeId):
|
||||||
node_list.append(
|
node_list.append(
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
|
|
@ -578,19 +730,20 @@ class WorkflowManage:
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
[*current_node.up_node_id_list, current_node.node.id]))
|
[*current_node.up_node_id_list, current_node.node.id]))
|
||||||
else:
|
else:
|
||||||
for edge_node in next_edge_node_list:
|
for edge in self.flow.edges:
|
||||||
edge = edge_node.edge
|
if edge.sourceNodeId == current_node.id:
|
||||||
next_node = edge_node.node
|
next_node = [node for node in self.flow.nodes if node.id == edge.targetNodeId]
|
||||||
if next_node.properties.get('condition', "AND") == 'AND':
|
if len(next_node) == 0:
|
||||||
if self.dependent_node_been_executed(edge.targetNodeId):
|
continue
|
||||||
|
if next_node[0].properties.get('condition', "AND") == 'AND':
|
||||||
|
if self.dependent_node_been_executed(edge.targetNodeId):
|
||||||
|
node_list.append(
|
||||||
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
|
[*current_node.up_node_id_list, current_node.node.id]))
|
||||||
|
else:
|
||||||
node_list.append(
|
node_list.append(
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
self.get_node_cls_by_id(edge.targetNodeId,
|
||||||
[*current_node.up_node_id_list, current_node.node.id]))
|
[*current_node.up_node_id_list, current_node.node.id]))
|
||||||
else:
|
|
||||||
node_list.append(
|
|
||||||
self.get_node_cls_by_id(edge.targetNodeId,
|
|
||||||
[*current_node.up_node_id_list, current_node.node.id]))
|
|
||||||
|
|
||||||
return node_list
|
return node_list
|
||||||
|
|
||||||
def get_reference_field(self, node_id: str, fields: List[str]):
|
def get_reference_field(self, node_id: str, fields: List[str]):
|
||||||
|
|
@ -601,18 +754,12 @@ class WorkflowManage:
|
||||||
"""
|
"""
|
||||||
if node_id == 'global':
|
if node_id == 'global':
|
||||||
return INode.get_field(self.context, fields)
|
return INode.get_field(self.context, fields)
|
||||||
elif node_id == 'chat':
|
|
||||||
return INode.get_field(self.chat_context, fields)
|
|
||||||
else:
|
else:
|
||||||
node = self.get_node_by_id(node_id)
|
return self.get_node_by_id(node_id).get_reference_field(fields)
|
||||||
if node:
|
|
||||||
return node.get_reference_field(fields)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_workflow_content(self):
|
def get_workflow_content(self):
|
||||||
context = {
|
context = {
|
||||||
'global': self.context,
|
'global': self.context,
|
||||||
'chat': self.chat_context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for node in self.node_context:
|
for node in self.node_context:
|
||||||
|
|
@ -630,10 +777,6 @@ class WorkflowManage:
|
||||||
globeLabelNew = f"global.{field.get('value')}"
|
globeLabelNew = f"global.{field.get('value')}"
|
||||||
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
|
globeValue = f"context.get('global').get('{field.get('value', '')}','')"
|
||||||
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)
|
prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)
|
||||||
for field in self.chat_field_list:
|
|
||||||
chatLabel = f"chat.{field.get('value')}"
|
|
||||||
chatValue = f"context.get('chat').get('{field.get('value', '')}','')"
|
|
||||||
prompt = prompt.replace(chatLabel, chatValue)
|
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
def generate_prompt(self, prompt: str):
|
def generate_prompt(self, prompt: str):
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,10 @@
|
||||||
# Generated by Django 5.2.4 on 2025-07-14 11:45
|
# Generated by Django 4.1.10 on 2024-03-18 16:02
|
||||||
from django.db.models import QuerySet
|
|
||||||
|
|
||||||
import application.models.application
|
import application.models.application
|
||||||
import application.models.application_chat
|
|
||||||
import common.encoder.encoder
|
|
||||||
import django.contrib.postgres.fields
|
import django.contrib.postgres.fields
|
||||||
import django.db.models.deletion
|
|
||||||
import mptt.fields
|
|
||||||
import uuid_utils.compat
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
def insert_default_data(apps, schema_editor):
|
|
||||||
# 创建一个根模块(没有父节点)
|
|
||||||
QuerySet(application.models.application.ApplicationFolder).create(id='default', name='根目录',
|
|
||||||
user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default')
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
@ -22,8 +12,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('knowledge', '0001_initial'),
|
('dataset', '0001_initial'),
|
||||||
('models_provider', '0001_initial'),
|
('setting', '0001_initial'),
|
||||||
('users', '0001_initial'),
|
('users', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -31,196 +21,65 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Application',
|
name='Application',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
('name', models.CharField(max_length=128, verbose_name='应用名称')),
|
||||||
('is_publish', models.BooleanField(default=False, verbose_name='是否发布')),
|
|
||||||
('name', models.CharField(db_index=True, max_length=128, verbose_name='应用名称')),
|
|
||||||
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),
|
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),
|
||||||
('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')),
|
('prologue', models.CharField(default='', max_length=1024, verbose_name='开场白')),
|
||||||
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),
|
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),
|
||||||
('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),
|
('dataset_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),
|
||||||
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),
|
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),
|
||||||
('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
|
||||||
('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
|
||||||
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),
|
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),
|
||||||
('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')),
|
('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='setting.model')),
|
||||||
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='users.user')),
|
||||||
('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')),
|
|
||||||
('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')),
|
|
||||||
('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')),
|
|
||||||
('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')),
|
|
||||||
('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')),
|
|
||||||
('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')),
|
|
||||||
('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')),
|
|
||||||
('clean_time', models.IntegerField(default=180, verbose_name='清理时间')),
|
|
||||||
('publish_time', models.DateTimeField(blank=True, default=None, null=True, verbose_name='发布时间')),
|
|
||||||
('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')),
|
|
||||||
('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')),
|
|
||||||
('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')),
|
|
||||||
('stt_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='models_provider.model')),
|
|
||||||
('tts_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='models_provider.model')),
|
|
||||||
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),
|
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'application',
|
'db_table': 'application',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationAccessToken',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
|
||||||
('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')),
|
|
||||||
('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')),
|
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')),
|
|
||||||
('access_num', models.IntegerField(default=100, verbose_name='访问次数')),
|
|
||||||
('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')),
|
|
||||||
('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')),
|
|
||||||
('show_source', models.BooleanField(default=False, verbose_name='是否显示知识来源')),
|
|
||||||
('show_exec', models.BooleanField(default=False, verbose_name='是否显示执行详情')),
|
|
||||||
('authentication', models.BooleanField(default=False, verbose_name='是否需要认证')),
|
|
||||||
('authentication_value', models.JSONField(default=dict, verbose_name='认证的值')),
|
|
||||||
('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_access_token',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationApiKey',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')),
|
|
||||||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='是否开启')),
|
|
||||||
('allow_cross_domain', models.BooleanField(default=False, verbose_name='是否允许跨域')),
|
|
||||||
('cross_domain_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_api_key',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationFolder',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
|
||||||
('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')),
|
|
||||||
('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')),
|
|
||||||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
|
||||||
('lft', models.PositiveIntegerField(editable=False)),
|
|
||||||
('rght', models.PositiveIntegerField(editable=False)),
|
|
||||||
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
|
|
||||||
('level', models.PositiveIntegerField(editable=False)),
|
|
||||||
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='application.applicationfolder')),
|
|
||||||
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_folder',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='application',
|
|
||||||
name='folder',
|
|
||||||
field=models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='application.applicationfolder', verbose_name='文件夹id'),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationKnowledgeMapping',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='application.application')),
|
|
||||||
('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_knowledge_mapping',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='ApplicationVersion',
|
|
||||||
fields=[
|
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
|
||||||
('name', models.CharField(default='', max_length=128, verbose_name='版本名称')),
|
|
||||||
('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')),
|
|
||||||
('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')),
|
|
||||||
('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),
|
|
||||||
('application_name', models.CharField(max_length=128, verbose_name='应用名称')),
|
|
||||||
('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),
|
|
||||||
('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')),
|
|
||||||
('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),
|
|
||||||
('model_id', models.UUIDField(blank=True, null=True, verbose_name='大语言模型')),
|
|
||||||
('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),
|
|
||||||
('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),
|
|
||||||
('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
|
||||||
('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),
|
|
||||||
('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),
|
|
||||||
('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')),
|
|
||||||
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),
|
|
||||||
('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')),
|
|
||||||
('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')),
|
|
||||||
('tts_model_id', models.UUIDField(blank=True, null=True, verbose_name='文本转语音模型id')),
|
|
||||||
('stt_model_id', models.UUIDField(blank=True, null=True, verbose_name='语音转文本模型id')),
|
|
||||||
('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')),
|
|
||||||
('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')),
|
|
||||||
('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')),
|
|
||||||
('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')),
|
|
||||||
('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')),
|
|
||||||
('clean_time', models.IntegerField(default=180, verbose_name='清理时间')),
|
|
||||||
('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')),
|
|
||||||
('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
|
||||||
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'application_version',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Chat',
|
name='Chat',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
('abstract', models.CharField(max_length=1024, verbose_name='摘要')),
|
('abstract', models.CharField(max_length=256, verbose_name='摘要')),
|
||||||
('chat_user_id', models.CharField(default=None, null=True, verbose_name='对话用户id')),
|
|
||||||
('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='客户端类型')),
|
|
||||||
('is_deleted', models.BooleanField(default=False, verbose_name='逻辑删除')),
|
|
||||||
('asker', models.JSONField(default=application.models.application_chat.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者')),
|
|
||||||
('meta', models.JSONField(default=dict, verbose_name='元数据')),
|
|
||||||
('star_num', models.IntegerField(default=0, verbose_name='点赞数量')),
|
|
||||||
('trample_num', models.IntegerField(default=0, verbose_name='点踩数量')),
|
|
||||||
('chat_record_count', models.IntegerField(default=0, verbose_name='对话次数')),
|
|
||||||
('mark_sum', models.IntegerField(default=0, verbose_name='标记数量')),
|
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'application_chat',
|
'db_table': 'application_chat',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationAccessToken',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
|
('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')),
|
||||||
|
('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')),
|
||||||
|
('access_num', models.IntegerField(default=100, verbose_name='访问次数')),
|
||||||
|
('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')),
|
||||||
|
('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_access_token',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ChatRecord',
|
name='ChatRecord',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
('vote_status', models.CharField(choices=[('-1', '未投票'), ('0', '赞同'), ('1', '反对')], default='-1', max_length=10, verbose_name='投票')),
|
('vote_status', models.CharField(choices=[('-1', '未投票'), ('0', '赞同'), ('1', '反对')], default='-1', max_length=10, verbose_name='投票')),
|
||||||
('problem_text', models.CharField(max_length=10240, verbose_name='问题')),
|
('problem_text', models.CharField(max_length=1024, verbose_name='问题')),
|
||||||
('answer_text', models.CharField(max_length=40960, verbose_name='答案')),
|
('answer_text', models.CharField(max_length=4096, verbose_name='答案')),
|
||||||
('answer_text_list', django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')),
|
|
||||||
('message_tokens', models.IntegerField(default=0, verbose_name='请求token数量')),
|
('message_tokens', models.IntegerField(default=0, verbose_name='请求token数量')),
|
||||||
('answer_tokens', models.IntegerField(default=0, verbose_name='响应token数量')),
|
('answer_tokens', models.IntegerField(default=0, verbose_name='响应token数量')),
|
||||||
('const', models.IntegerField(default=0, verbose_name='总费用')),
|
('const', models.IntegerField(default=0, verbose_name='总费用')),
|
||||||
('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情')),
|
('details', models.JSONField(default=dict, verbose_name='对话详情')),
|
||||||
('improve_paragraph_id_list', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True), default=list, size=None, verbose_name='改进标注列表')),
|
('improve_paragraph_id_list', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True), default=list, size=None, verbose_name='改进标注列表')),
|
||||||
('run_time', models.FloatField(default=0, verbose_name='运行时长')),
|
('run_time', models.FloatField(default=0, verbose_name='运行时长')),
|
||||||
('index', models.IntegerField(verbose_name='对话下标')),
|
('index', models.IntegerField(verbose_name='对话下标')),
|
||||||
|
|
@ -231,21 +90,45 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ApplicationChatUserStats',
|
name='ApplicationPublicAccessClient',
|
||||||
fields=[
|
fields=[
|
||||||
('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
('id', models.UUIDField(primary_key=True, serialize=False, verbose_name='公共访问链接客户端id')),
|
||||||
('chat_user_id', models.UUIDField(default=uuid_utils.compat.uuid7, verbose_name='对话用户id')),
|
|
||||||
('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='对话用户类型')),
|
|
||||||
('access_num', models.IntegerField(default=0, verbose_name='访问总次数次数')),
|
('access_num', models.IntegerField(default=0, verbose_name='访问总次数次数')),
|
||||||
('intraday_access_num', models.IntegerField(default=0, verbose_name='当日访问次数')),
|
('intraday_access_num', models.IntegerField(default=0, verbose_name='当日访问次数')),
|
||||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'db_table': 'application_chat_user_stats',
|
'db_table': 'application_public_access_client',
|
||||||
'indexes': [models.Index(fields=['application_id', 'chat_user_id'], name='application_applica_1652ba_idx')],
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationDatasetMapping',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
||||||
|
('dataset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dataset.dataset')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_dataset_mapping',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationApiKey',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='是否开启')),
|
||||||
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_api_key',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.RunPython(insert_default_data)
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-03-28 13:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chat',
|
||||||
|
name='client_id',
|
||||||
|
field=models.UUIDField(default=None, null=True, verbose_name='客户端id'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-04-23 11:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0002_chat_client_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='icon',
|
||||||
|
field=models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='应用icon'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-04-25 11:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0003_application_icon'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicationaccesstoken',
|
||||||
|
name='show_source',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='是否显示知识来源'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-04-29 13:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0004_applicationaccesstoken_show_source'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='chat',
|
||||||
|
name='abstract',
|
||||||
|
field=models.CharField(max_length=1024, verbose_name='摘要'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='chatrecord',
|
||||||
|
name='answer_text',
|
||||||
|
field=models.CharField(max_length=40960, verbose_name='答案'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-05-08 13:57
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0005_alter_chat_abstract_alter_chatrecord_answer_text'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicationapikey',
|
||||||
|
name='allow_cross_domain',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='是否允许跨域'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicationapikey',
|
||||||
|
name='cross_domain_list',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-05-24 11:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0006_applicationapikey_allow_cross_domain_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='application',
|
||||||
|
name='prologue',
|
||||||
|
field=models.CharField(default='', max_length=4096, verbose_name='开场白'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-06-13 11:46
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0007_alter_application_prologue'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chat',
|
||||||
|
name='is_deleted',
|
||||||
|
field=models.BooleanField(default=False, verbose_name=''),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-06-25 16:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0008_chat_is_deleted'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='work_flow',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='工作流数据'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WorkFlowVersion',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),
|
||||||
|
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'application_work_flow_version',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 4.2.13 on 2024-07-15 15:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import common.encoder.encoder
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('application', '0009_application_type_application_work_flow_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='chatrecord',
|
||||||
|
name='details',
|
||||||
|
field=models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-08-23 14:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0010_alter_chatrecord_details'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='model_params_setting',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='模型参数相关设置'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-09-05 14:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('setting', '0006_alter_model_status'),
|
||||||
|
('application', '0011_application_model_params_setting'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='stt_model',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='setting.model'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='stt_model_enable',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='语音识别模型是否启用'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='tts_model',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='setting.model'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='tts_model_enable',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='语音合成模型是否启用'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-09-12 11:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0012_application_stt_model_application_stt_model_enable_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='tts_type',
|
||||||
|
field=models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-09-13 18:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0013_application_tts_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='problem_optimization_prompt',
|
||||||
|
field=models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-09-18 16:14
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import psycopg
|
import psycopg2
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
from psycopg2 import extensions
|
||||||
|
|
||||||
from maxkb.const import CONFIG
|
from smartdoc.const import CONFIG
|
||||||
|
|
||||||
|
|
||||||
def get_connect(db_name):
|
def get_connect(db_name):
|
||||||
|
|
@ -15,7 +17,7 @@ def get_connect(db_name):
|
||||||
"port": CONFIG.get('DB_PORT')
|
"port": CONFIG.get('DB_PORT')
|
||||||
}
|
}
|
||||||
# 建立连接
|
# 建立连接
|
||||||
connect = psycopg.connect(**conn_params)
|
connect = psycopg2.connect(**conn_params)
|
||||||
return connect
|
return connect
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,12 +28,13 @@ def sql_execute(conn, reindex_sql: str, alter_database_sql: str):
|
||||||
@param conn:
|
@param conn:
|
||||||
@param alter_database_sql:
|
@param alter_database_sql:
|
||||||
"""
|
"""
|
||||||
conn.autocommit = True
|
conn.set_isolation_level(extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||||
with conn.cursor() as cursor:
|
with conn.cursor() as cursor:
|
||||||
cursor.execute(reindex_sql, [])
|
cursor.execute(reindex_sql, [])
|
||||||
cursor.execute(alter_database_sql, [])
|
cursor.execute(alter_database_sql, [])
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
||||||
|
|
||||||
def re_index(apps, schema_editor):
|
def re_index(apps, schema_editor):
|
||||||
app_db_name = CONFIG.get('DB_NAME')
|
app_db_name = CONFIG.get('DB_NAME')
|
||||||
try:
|
try:
|
||||||
|
|
@ -51,9 +54,8 @@ def re_index_database(db_name):
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("system_manage", "0001_initial"),
|
('application', '0014_application_problem_optimization_prompt'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-09-26 13:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0015_re_database_index'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='chatrecord',
|
||||||
|
name='problem_text',
|
||||||
|
field=models.CharField(max_length=10240, verbose_name='问题'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-10-16 13:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0016_alter_chatrecord_problem_text'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='tts_model_params_setting',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='模型参数相关设置'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-10-16 15:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
UPDATE "public".application_work_flow_version
|
||||||
|
SET "name" = TO_CHAR(create_time, 'YYYY-MM-DD HH24:MI:SS');
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('application', '0017_application_tts_model_params_setting'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='clean_time',
|
||||||
|
field=models.IntegerField(default=180, verbose_name='清理时间'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflowversion',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(default='', max_length=128, verbose_name='版本名称'),
|
||||||
|
),
|
||||||
|
migrations.RunSQL(sql),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflowversion',
|
||||||
|
name='publish_user_id',
|
||||||
|
field=models.UUIDField(default=None, null=True, verbose_name='发布者id'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='workflowversion',
|
||||||
|
name='publish_user_name',
|
||||||
|
field=models.CharField(default='', max_length=128, verbose_name='发布者名称'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-11-13 10:13
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
UPDATE application_chat_record
|
||||||
|
SET answer_text_list=ARRAY[jsonb_build_object('content',answer_text)]
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('application', '0018_workflowversion_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='file_upload_enable',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='文件上传是否启用'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='file_upload_setting',
|
||||||
|
field=models.JSONField(default=dict, verbose_name='文件上传相关设置'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chatrecord',
|
||||||
|
name='answer_text_list',
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')
|
||||||
|
),
|
||||||
|
migrations.RunSQL(sql)
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
from django.db import migrations, connection
|
||||||
|
|
||||||
|
batch_update_update_time = """
|
||||||
|
UPDATE application_chat ac
|
||||||
|
SET update_time = acr_max.max_update_time
|
||||||
|
FROM (
|
||||||
|
SELECT chat_id, MAX(update_time) AS max_update_time
|
||||||
|
FROM application_chat_record
|
||||||
|
GROUP BY chat_id
|
||||||
|
) acr_max
|
||||||
|
WHERE ac.id = acr_max.chat_id;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('application', '0019_application_file_upload_enable_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunSQL(batch_update_update_time),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-12-27 18:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
run_sql = """
|
||||||
|
UPDATE application_public_access_client
|
||||||
|
SET client_id="id"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('application', '0020_application_record_update_time'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicationpublicaccessclient',
|
||||||
|
name='client_id',
|
||||||
|
field=models.UUIDField(default=uuid.uuid1, verbose_name='公共访问链接客户端id'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicationpublicaccessclient',
|
||||||
|
name='id',
|
||||||
|
field=models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False,
|
||||||
|
verbose_name='主键id'),
|
||||||
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='applicationpublicaccessclient',
|
||||||
|
index=models.Index(fields=['client_id'], name='application_client__4de9af_idx'),
|
||||||
|
),
|
||||||
|
migrations.RunSQL(run_sql)
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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='自动播放'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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='自动发送'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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='语言')
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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='开场白'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 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='访问者'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue