mirror of
https://github.com/langgenius/dify.git
synced 2026-05-30 21:57:46 +08:00
Compare commits
58 Commits
build/esli
...
feat/exter
| Author | SHA1 | Date | |
|---|---|---|---|
| 9125971da2 | |||
| 6f9d6cd3e1 | |||
| f6074b6545 | |||
| fd4d7e9002 | |||
| 383a60a7df | |||
| 918df23f64 | |||
| bc81d2d30d | |||
| 89290183c6 | |||
| 6508e7e1e4 | |||
| 1955de2463 | |||
| 4ee3743b20 | |||
| e5d8c07508 | |||
| 69c0f3f2ad | |||
| b92fced974 | |||
| 644ab2df35 | |||
| 020766a5e8 | |||
| c9e3a9e56a | |||
| 9c9352bc73 | |||
| 2a1cba9f4d | |||
| 8e73844781 | |||
| 5554cf7b20 | |||
| 1597f34471 | |||
| 1c7cb3fbc0 | |||
| 611f0fb3f6 | |||
| ff0260e564 | |||
| 85deb9d7af | |||
| cfa4825073 | |||
| 5fa86074ed | |||
| d6c604a356 | |||
| c927c97310 | |||
| a69dcb8bee | |||
| 02b06c420e | |||
| a258f8dfdf | |||
| a53b4fb2ff | |||
| 680c1bd41d | |||
| b9b8ec1758 | |||
| 6452c34818 | |||
| 2655dd2026 | |||
| 30dc137ccc | |||
| 573b61b7e8 | |||
| 089da063d4 | |||
| ed92c90a40 | |||
| fbedd08292 | |||
| 19c526120c | |||
| 37f7d5732a | |||
| dcb033d221 | |||
| 9f894bb3b3 | |||
| 89e81873c4 | |||
| 9ca0e56a8a | |||
| e7c77d961b | |||
| a63e15081f | |||
| 0724640bbb | |||
| cb70e12827 | |||
| 067b956b2c | |||
| e7762b731c | |||
| f6c8390b0b | |||
| 4fd57929df | |||
| 517cdb2ca4 |
@ -1,12 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
npm add -g pnpm@9.12.2
|
cd web && npm install
|
||||||
cd web && pnpm install
|
|
||||||
pipx install poetry
|
pipx install poetry
|
||||||
|
|
||||||
echo 'alias start-api="cd /workspaces/dify/api && poetry run python -m flask run --host 0.0.0.0 --port=5001 --debug"' >> ~/.bashrc
|
echo 'alias start-api="cd /workspaces/dify/api && poetry run python -m flask run --host 0.0.0.0 --port=5001 --debug"' >> ~/.bashrc
|
||||||
echo 'alias start-worker="cd /workspaces/dify/api && poetry run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion"' >> ~/.bashrc
|
echo 'alias start-worker="cd /workspaces/dify/api && poetry run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion"' >> ~/.bashrc
|
||||||
echo 'alias start-web="cd /workspaces/dify/web && pnpm dev"' >> ~/.bashrc
|
echo 'alias start-web="cd /workspaces/dify/web && npm run dev"' >> ~/.bashrc
|
||||||
echo 'alias start-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify up -d"' >> ~/.bashrc
|
echo 'alias start-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify up -d"' >> ~/.bashrc
|
||||||
|
|
||||||
source /home/vscode/.bashrc
|
source /home/vscode/.bashrc
|
||||||
12
.github/workflows/api-tests.yml
vendored
12
.github/workflows/api-tests.yml
vendored
@ -27,18 +27,19 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
uses: abatilo/actions-poetry@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: 'poetry'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
api/pyproject.toml
|
api/pyproject.toml
|
||||||
api/poetry.lock
|
api/poetry.lock
|
||||||
|
|
||||||
- name: Install Poetry
|
- name: Poetry check
|
||||||
uses: abatilo/actions-poetry@v3
|
|
||||||
|
|
||||||
- name: Check Poetry lockfile
|
|
||||||
run: |
|
run: |
|
||||||
poetry check -C api --lock
|
poetry check -C api --lock
|
||||||
poetry show -C api
|
poetry show -C api
|
||||||
@ -46,9 +47,6 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: poetry install -C api --with dev
|
run: poetry install -C api --with dev
|
||||||
|
|
||||||
- name: Check dependencies in pyproject.toml
|
|
||||||
run: poetry run -C api bash dev/pytest/pytest_artifacts.sh
|
|
||||||
|
|
||||||
- name: Run Unit tests
|
- name: Run Unit tests
|
||||||
run: poetry run -C api bash dev/pytest/pytest_unit_tests.sh
|
run: poetry run -C api bash dev/pytest/pytest_unit_tests.sh
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/build-push.yml
vendored
2
.github/workflows/build-push.yml
vendored
@ -125,7 +125,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: ${{ env[matrix.image_name_env] }}
|
images: ${{ env[matrix.image_name_env] }}
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-') }}
|
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
type=ref,event=branch
|
type=ref,event=branch
|
||||||
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
|
type=sha,enable=true,priority=100,prefix=,suffix=,format=long
|
||||||
type=raw,value=${{ github.ref_name }},enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
type=raw,value=${{ github.ref_name }},enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
|||||||
7
.github/workflows/db-migration-test.yml
vendored
7
.github/workflows/db-migration-test.yml
vendored
@ -23,17 +23,18 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
uses: abatilo/actions-poetry@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: 'poetry'
|
||||||
cache-dependency-path: |
|
cache-dependency-path: |
|
||||||
api/pyproject.toml
|
api/pyproject.toml
|
||||||
api/poetry.lock
|
api/poetry.lock
|
||||||
|
|
||||||
- name: Install Poetry
|
|
||||||
uses: abatilo/actions-poetry@v3
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: poetry install -C api
|
run: poetry install -C api
|
||||||
|
|
||||||
|
|||||||
13
.github/workflows/style.yml
vendored
13
.github/workflows/style.yml
vendored
@ -24,16 +24,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: api/**
|
files: api/**
|
||||||
|
|
||||||
|
- name: Install Poetry
|
||||||
|
uses: abatilo/actions-poetry@v3
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
python-version: '3.10'
|
python-version: '3.10'
|
||||||
|
|
||||||
- name: Install Poetry
|
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
|
||||||
uses: abatilo/actions-poetry@v3
|
|
||||||
|
|
||||||
- name: Python dependencies
|
- name: Python dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: poetry install -C api --only lint
|
run: poetry install -C api --only lint
|
||||||
@ -76,16 +75,16 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: pnpm
|
cache: yarn
|
||||||
cache-dependency-path: ./web/package.json
|
cache-dependency-path: ./web/package.json
|
||||||
|
|
||||||
- name: Web dependencies
|
- name: Web dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: pnpm install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Web style check
|
- name: Web style check
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: pnpm run lint
|
run: yarn run lint
|
||||||
|
|
||||||
|
|
||||||
superlinter:
|
superlinter:
|
||||||
|
|||||||
6
.github/workflows/tool-test-sdks.yaml
vendored
6
.github/workflows/tool-test-sdks.yaml
vendored
@ -32,10 +32,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: ''
|
cache: ''
|
||||||
cache-dependency-path: 'pnpm-lock.yaml'
|
cache-dependency-path: 'yarn.lock'
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: pnpm install
|
run: yarn install
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm test
|
run: yarn test
|
||||||
|
|||||||
@ -38,11 +38,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: env.FILES_CHANGED == 'true'
|
if: env.FILES_CHANGED == 'true'
|
||||||
run: pnpm install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run npm script
|
- name: Run npm script
|
||||||
if: env.FILES_CHANGED == 'true'
|
if: env.FILES_CHANGED == 'true'
|
||||||
run: pnpm run auto-gen-i18n
|
run: npm run auto-gen-i18n
|
||||||
|
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
if: env.FILES_CHANGED == 'true'
|
if: env.FILES_CHANGED == 'true'
|
||||||
|
|||||||
6
.github/workflows/web-tests.yml
vendored
6
.github/workflows/web-tests.yml
vendored
@ -34,13 +34,13 @@ jobs:
|
|||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: pnpm
|
cache: yarn
|
||||||
cache-dependency-path: ./web/package.json
|
cache-dependency-path: ./web/package.json
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: pnpm install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: steps.changed-files.outputs.any_changed == 'true'
|
if: steps.changed-files.outputs.any_changed == 'true'
|
||||||
run: pnpm test
|
run: yarn test
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -188,6 +188,3 @@ api/.vscode
|
|||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# pnpm
|
|
||||||
/.pnpm-store
|
|
||||||
|
|||||||
10
README.md
10
README.md
@ -17,7 +17,7 @@
|
|||||||
alt="chat on Discord"></a>
|
alt="chat on Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -196,14 +196,10 @@ If you'd like to configure a highly-available setup, there are community-contrib
|
|||||||
|
|
||||||
#### Using Terraform for Deployment
|
#### Using Terraform for Deployment
|
||||||
|
|
||||||
Deploy Dify to Cloud Platform with a single click using [terraform](https://www.terraform.io/)
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
Deploy Dify to Azure with a single click using [terraform](https://www.terraform.io/).
|
||||||
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
@ -223,7 +219,7 @@ At the same time, please consider supporting Dify by sharing it on social media
|
|||||||
* [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions.
|
* [Github Discussion](https://github.com/langgenius/dify/discussions). Best for: sharing feedback and asking questions.
|
||||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
* [Twitter](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
||||||
|
|
||||||
## Star history
|
## Star history
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
alt="chat on Discord"></a>
|
alt="chat on Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -179,13 +179,10 @@ docker compose up -d
|
|||||||
|
|
||||||
#### استخدام Terraform للتوزيع
|
#### استخدام Terraform للتوزيع
|
||||||
|
|
||||||
انشر Dify إلى منصة السحابة بنقرة واحدة باستخدام [terraform](https://www.terraform.io/)
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
استخدم [terraform](https://www.terraform.io/) لنشر Dify على Azure بنقرة واحدة.
|
||||||
- [Azure Terraform بواسطة @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform بواسطة @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform بواسطة @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## المساهمة
|
## المساهمة
|
||||||
|
|
||||||
|
|||||||
10
README_CN.md
10
README_CN.md
@ -17,7 +17,7 @@
|
|||||||
alt="chat on Discord"></a>
|
alt="chat on Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -202,14 +202,10 @@ docker compose up -d
|
|||||||
|
|
||||||
#### 使用 Terraform 部署
|
#### 使用 Terraform 部署
|
||||||
|
|
||||||
使用 [terraform](https://www.terraform.io/) 一键将 Dify 部署到云平台
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
使用 [terraform](https://www.terraform.io/) 一键部署 Dify 到 Azure。
|
||||||
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform by @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#langgenius/dify&Date)
|
[](https://star-history.com/#langgenius/dify&Date)
|
||||||
@ -236,7 +232,7 @@ docker compose up -d
|
|||||||
- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](CONTRIBUTING.md)。
|
- [GitHub Issues](https://github.com/langgenius/dify/issues)。👉:使用 Dify.AI 时遇到的错误和问题,请参阅[贡献指南](CONTRIBUTING.md)。
|
||||||
- [电子邮件支持](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify)。👉:关于使用 Dify.AI 的问题。
|
- [电子邮件支持](mailto:hello@dify.ai?subject=[GitHub]Questions%20About%20Dify)。👉:关于使用 Dify.AI 的问题。
|
||||||
- [Discord](https://discord.gg/FngNHpbcY7)。👉:分享您的应用程序并与社区交流。
|
- [Discord](https://discord.gg/FngNHpbcY7)。👉:分享您的应用程序并与社区交流。
|
||||||
- [X(Twitter)](https://twitter.com/dify_ai)。👉:分享您的应用程序并与社区交流。
|
- [Twitter](https://twitter.com/dify_ai)。👉:分享您的应用程序并与社区交流。
|
||||||
- [商业许可](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry)。👉:有关商业用途许可 Dify.AI 的商业咨询。
|
- [商业许可](mailto:business@dify.ai?subject=[GitHub]Business%20License%20Inquiry)。👉:有关商业用途许可 Dify.AI 的商业咨询。
|
||||||
- [微信]() 👉:扫描下方二维码,添加微信好友,备注 Dify,我们将邀请您加入 Dify 社区。
|
- [微信]() 👉:扫描下方二维码,添加微信好友,备注 Dify,我们将邀请您加入 Dify 社区。
|
||||||
<img src="./images/wechat.png" alt="wechat" width="100"/>
|
<img src="./images/wechat.png" alt="wechat" width="100"/>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
alt="chat en Discord"></a>
|
alt="chat en Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="seguir en X(Twitter)"></a>
|
alt="seguir en Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Descargas de Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Descargas de Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -204,13 +204,10 @@ Si desea configurar una configuración de alta disponibilidad, la comunidad prop
|
|||||||
|
|
||||||
#### Uso de Terraform para el despliegue
|
#### Uso de Terraform para el despliegue
|
||||||
|
|
||||||
Despliega Dify en una plataforma en la nube con un solo clic utilizando [terraform](https://www.terraform.io/)
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
Utiliza [terraform](https://www.terraform.io/) para desplegar Dify en Azure con un solo clic.
|
||||||
- [Azure Terraform por @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform por @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform por @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Contribuir
|
## Contribuir
|
||||||
|
|
||||||
@ -231,7 +228,7 @@ Al mismo tiempo, considera apoyar a Dify compartiéndolo en redes sociales y en
|
|||||||
* [Discusión en GitHub](https://github.com/langgenius/dify/discussions). Lo mejor para: compartir comentarios y hacer preguntas.
|
* [Discusión en GitHub](https://github.com/langgenius/dify/discussions). Lo mejor para: compartir comentarios y hacer preguntas.
|
||||||
* [Reporte de problemas en GitHub](https://github.com/langgenius/dify/issues). Lo mejor para: errores que encuentres usando Dify.AI y propuestas de características. Consulta nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
* [Reporte de problemas en GitHub](https://github.com/langgenius/dify/issues). Lo mejor para: errores que encuentres usando Dify.AI y propuestas de características. Consulta nuestra [Guía de contribución](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
|
* [Discord](https://discord.gg/FngNHpbcY7). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
|
* [Twitter](https://twitter.com/dify_ai). Lo mejor para: compartir tus aplicaciones y pasar el rato con la comunidad.
|
||||||
|
|
||||||
## Historial de Estrellas
|
## Historial de Estrellas
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
alt="chat sur Discord"></a>
|
alt="chat sur Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="suivre sur X(Twitter)"></a>
|
alt="suivre sur Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Tirages Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Tirages Docker" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -202,13 +202,10 @@ Si vous souhaitez configurer une configuration haute disponibilité, la communau
|
|||||||
|
|
||||||
#### Utilisation de Terraform pour le déploiement
|
#### Utilisation de Terraform pour le déploiement
|
||||||
|
|
||||||
Déployez Dify sur une plateforme cloud en un clic en utilisant [terraform](https://www.terraform.io/)
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
Utilisez [terraform](https://www.terraform.io/) pour déployer Dify sur Azure en un clic.
|
||||||
- [Azure Terraform par @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform par @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform par @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Contribuer
|
## Contribuer
|
||||||
|
|
||||||
@ -229,7 +226,7 @@ Dans le même temps, veuillez envisager de soutenir Dify en le partageant sur le
|
|||||||
* [Discussion GitHub](https://github.com/langgenius/dify/discussions). Meilleur pour: partager des commentaires et poser des questions.
|
* [Discussion GitHub](https://github.com/langgenius/dify/discussions). Meilleur pour: partager des commentaires et poser des questions.
|
||||||
* [Problèmes GitHub](https://github.com/langgenius/dify/issues). Meilleur pour: les bogues que vous rencontrez en utilisant Dify.AI et les propositions de fonctionnalités. Consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
* [Problèmes GitHub](https://github.com/langgenius/dify/issues). Meilleur pour: les bogues que vous rencontrez en utilisant Dify.AI et les propositions de fonctionnalités. Consultez notre [Guide de contribution](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). Meilleur pour: partager vos applications et passer du temps avec la communauté.
|
* [Discord](https://discord.gg/FngNHpbcY7). Meilleur pour: partager vos applications et passer du temps avec la communauté.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). Meilleur pour: partager vos applications et passer du temps avec la communauté.
|
* [Twitter](https://twitter.com/dify_ai). Meilleur pour: partager vos applications et passer du temps avec la communauté.
|
||||||
|
|
||||||
## Historique des étoiles
|
## Historique des étoiles
|
||||||
|
|
||||||
|
|||||||
13
README_JA.md
13
README_JA.md
@ -17,7 +17,7 @@
|
|||||||
alt="Discordでチャット"></a>
|
alt="Discordでチャット"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="X(Twitter)でフォロー"></a>
|
alt="Twitterでフォロー"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -68,7 +68,7 @@ DifyはオープンソースのLLMアプリケーション開発プラットフ
|
|||||||
プロンプトの作成、モデルパフォーマンスの比較が行え、チャットベースのアプリに音声合成などの機能も追加できます。
|
プロンプトの作成、モデルパフォーマンスの比較が行え、チャットベースのアプリに音声合成などの機能も追加できます。
|
||||||
|
|
||||||
**4. RAGパイプライン**:
|
**4. RAGパイプライン**:
|
||||||
ドキュメントの取り込みから検索までをカバーする広範なRAG機能ができます。ほかにもPDF、PPT、その他の一般的なドキュメントフォーマットからのテキスト抽出のサポートも提供します。
|
ドキュメントの取り込みから検索までをカバーする広範なRAG機能ができます。ほかにもPDF、PPT、その他の一般的なドキュメントフォーマットからのテキスト抽出のサーポイントも提供します。
|
||||||
|
|
||||||
**5. エージェント機能**:
|
**5. エージェント機能**:
|
||||||
LLM Function CallingやReActに基づくエージェントの定義が可能で、AIエージェント用のプリビルトまたはカスタムツールを追加できます。Difyには、Google検索、DALL·E、Stable Diffusion、WolframAlphaなどのAIエージェント用の50以上の組み込みツールが提供します。
|
LLM Function CallingやReActに基づくエージェントの定義が可能で、AIエージェント用のプリビルトまたはカスタムツールを追加できます。Difyには、Google検索、DALL·E、Stable Diffusion、WolframAlphaなどのAIエージェント用の50以上の組み込みツールが提供します。
|
||||||
@ -201,13 +201,10 @@ docker compose up -d
|
|||||||
|
|
||||||
#### Terraformを使用したデプロイ
|
#### Terraformを使用したデプロイ
|
||||||
|
|
||||||
[terraform](https://www.terraform.io/) を使用して、ワンクリックでDifyをクラウドプラットフォームにデプロイします
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
- [@nikawangによるAzure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
[terraform](https://www.terraform.io/) を使用して、AzureにDifyをワンクリックでデプロイします。
|
||||||
|
- [nikawangのAzure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [@sotazumによるGoogle Cloud Terraform](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## 貢献
|
## 貢献
|
||||||
|
|
||||||
@ -228,7 +225,7 @@ docker compose up -d
|
|||||||
* [Github Discussion](https://github.com/langgenius/dify/discussions). 主に: フィードバックの共有や質問。
|
* [Github Discussion](https://github.com/langgenius/dify/discussions). 主に: フィードバックの共有や質問。
|
||||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). 主に: Dify.AIを使用する際に発生するエラーや問題については、[貢献ガイド](CONTRIBUTING_JA.md)を参照してください
|
* [GitHub Issues](https://github.com/langgenius/dify/issues). 主に: Dify.AIを使用する際に発生するエラーや問題については、[貢献ガイド](CONTRIBUTING_JA.md)を参照してください
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). 主に: アプリケーションの共有やコミュニティとの交流。
|
* [Discord](https://discord.gg/FngNHpbcY7). 主に: アプリケーションの共有やコミュニティとの交流。
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). 主に: アプリケーションの共有やコミュニティとの交流。
|
* [Twitter](https://twitter.com/dify_ai). 主に: アプリケーションの共有やコミュニティとの交流。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
11
README_KL.md
11
README_KL.md
@ -17,7 +17,7 @@
|
|||||||
alt="chat on Discord"></a>
|
alt="chat on Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -202,13 +202,10 @@ If you'd like to configure a highly-available setup, there are community-contrib
|
|||||||
|
|
||||||
#### Terraform atorlugu pilersitsineq
|
#### Terraform atorlugu pilersitsineq
|
||||||
|
|
||||||
wa'logh nIqHom neH ghun deployment toy'wI' [terraform](https://www.terraform.io/) lo'laH.
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
- [Azure Terraform mung @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
Atoruk [terraform](https://www.terraform.io/) Dify-mik Azure-mut ataatsikkut ikkussuilluarlugu.
|
||||||
|
- [Azure Terraform atorlugu @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform qachlot @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@ -231,7 +228,7 @@ At the same time, please consider supporting Dify by sharing it on social media
|
|||||||
). Best for: sharing feedback and asking questions.
|
). Best for: sharing feedback and asking questions.
|
||||||
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
* [GitHub Issues](https://github.com/langgenius/dify/issues). Best for: bugs you encounter using Dify.AI, and feature proposals. See our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
* [Discord](https://discord.gg/FngNHpbcY7). Best for: sharing your applications and hanging out with the community.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
* [Twitter](https://twitter.com/dify_ai). Best for: sharing your applications and hanging out with the community.
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
alt="chat on Discord"></a>
|
alt="chat on Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="follow on X(Twitter)"></a>
|
alt="follow on Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -39,6 +39,7 @@
|
|||||||
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
|
<a href="./README_AR.md"><img alt="README بالعربية" src="https://img.shields.io/badge/العربية-d9d9d9"></a>
|
||||||
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
|
<a href="./README_TR.md"><img alt="Türkçe README" src="https://img.shields.io/badge/Türkçe-d9d9d9"></a>
|
||||||
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
|
<a href="./README_VI.md"><img alt="README Tiếng Việt" src="https://img.shields.io/badge/Ti%E1%BA%BFng%20Vi%E1%BB%87t-d9d9d9"></a>
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
@ -194,14 +195,10 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
|
|||||||
|
|
||||||
#### Terraform을 사용한 배포
|
#### Terraform을 사용한 배포
|
||||||
|
|
||||||
[terraform](https://www.terraform.io/)을 사용하여 단 한 번의 클릭으로 Dify를 클라우드 플랫폼에 배포하십시오
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
[terraform](https://www.terraform.io/)을 사용하여 Azure에 Dify를 원클릭으로 배포하세요.
|
||||||
- [nikawang의 Azure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
- [nikawang의 Azure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [sotazum의 Google Cloud Terraform](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## 기여
|
## 기여
|
||||||
|
|
||||||
코드에 기여하고 싶은 분들은 [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
|
코드에 기여하고 싶은 분들은 [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
|
||||||
|
|||||||
12
README_TR.md
12
README_TR.md
@ -17,7 +17,7 @@
|
|||||||
alt="Discord'da sohbet et"></a>
|
alt="Discord'da sohbet et"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="X(Twitter)'da takip et"></a>
|
alt="Twitter'da takip et"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Çekmeleri" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Çekmeleri" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -200,13 +200,9 @@ Yüksek kullanılabilirliğe sahip bir kurulum yapılandırmak isterseniz, Dify'
|
|||||||
|
|
||||||
#### Dağıtım için Terraform Kullanımı
|
#### Dağıtım için Terraform Kullanımı
|
||||||
|
|
||||||
Dify'ı bulut platformuna tek tıklamayla dağıtın [terraform](https://www.terraform.io/) kullanarak
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
- [Azure Terraform tarafından @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
[Terraform](https://www.terraform.io/) kullanarak Dify'ı Azure'a tek tıklamayla dağıtın.
|
||||||
|
- [@nikawang tarafından Azure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform tarafından @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Katkıda Bulunma
|
## Katkıda Bulunma
|
||||||
|
|
||||||
@ -226,7 +222,7 @@ Aynı zamanda, lütfen Dify'ı sosyal medyada, etkinliklerde ve konferanslarda p
|
|||||||
* [Github Tartışmaları](https://github.com/langgenius/dify/discussions). En uygun: geri bildirim paylaşmak ve soru sormak için.
|
* [Github Tartışmaları](https://github.com/langgenius/dify/discussions). En uygun: geri bildirim paylaşmak ve soru sormak için.
|
||||||
* [GitHub Sorunları](https://github.com/langgenius/dify/issues). En uygun: Dify.AI kullanırken karşılaştığınız hatalar ve özellik önerileri için. [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakın.
|
* [GitHub Sorunları](https://github.com/langgenius/dify/issues). En uygun: Dify.AI kullanırken karşılaştığınız hatalar ve özellik önerileri için. [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakın.
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için.
|
* [Discord](https://discord.gg/FngNHpbcY7). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için.
|
* [Twitter](https://twitter.com/dify_ai). En uygun: uygulamalarınızı paylaşmak ve toplulukla vakit geçirmek için.
|
||||||
|
|
||||||
## Star history
|
## Star history
|
||||||
|
|
||||||
|
|||||||
10
README_VI.md
10
README_VI.md
@ -17,7 +17,7 @@
|
|||||||
alt="chat trên Discord"></a>
|
alt="chat trên Discord"></a>
|
||||||
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
<a href="https://twitter.com/intent/follow?screen_name=dify_ai" target="_blank">
|
||||||
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
<img src="https://img.shields.io/twitter/follow/dify_ai?logo=X&color=%20%23f5f5f5"
|
||||||
alt="theo dõi trên X(Twitter)"></a>
|
alt="theo dõi trên Twitter"></a>
|
||||||
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
<a href="https://hub.docker.com/u/langgenius" target="_blank">
|
||||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/langgenius/dify-web?labelColor=%20%23FDB062&color=%20%23f79009"></a>
|
||||||
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
<a href="https://github.com/langgenius/dify/graphs/commit-activity" target="_blank">
|
||||||
@ -196,14 +196,10 @@ Nếu bạn muốn cấu hình một cài đặt có độ sẵn sàng cao, có
|
|||||||
|
|
||||||
#### Sử dụng Terraform để Triển khai
|
#### Sử dụng Terraform để Triển khai
|
||||||
|
|
||||||
Triển khai Dify lên nền tảng đám mây với một cú nhấp chuột bằng cách sử dụng [terraform](https://www.terraform.io/)
|
|
||||||
|
|
||||||
##### Azure Global
|
##### Azure Global
|
||||||
|
Triển khai Dify lên Azure chỉ với một cú nhấp chuột bằng cách sử dụng [terraform](https://www.terraform.io/).
|
||||||
- [Azure Terraform bởi @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
- [Azure Terraform bởi @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||||
|
|
||||||
##### Google Cloud
|
|
||||||
- [Google Cloud Terraform bởi @sotazum](https://github.com/DeNA/dify-google-cloud-terraform)
|
|
||||||
|
|
||||||
## Đóng góp
|
## Đóng góp
|
||||||
|
|
||||||
Đối với những người muốn đóng góp mã, xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
|
Đối với những người muốn đóng góp mã, xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
|
||||||
@ -223,7 +219,7 @@ Triển khai Dify lên nền tảng đám mây với một cú nhấp chuột b
|
|||||||
* [Thảo luận GitHub](https://github.com/langgenius/dify/discussions). Tốt nhất cho: chia sẻ phản hồi và đặt câu hỏi.
|
* [Thảo luận GitHub](https://github.com/langgenius/dify/discussions). Tốt nhất cho: chia sẻ phản hồi và đặt câu hỏi.
|
||||||
* [Vấn đề GitHub](https://github.com/langgenius/dify/issues). Tốt nhất cho: lỗi bạn gặp phải khi sử dụng Dify.AI và đề xuất tính năng. Xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
|
* [Vấn đề GitHub](https://github.com/langgenius/dify/issues). Tốt nhất cho: lỗi bạn gặp phải khi sử dụng Dify.AI và đề xuất tính năng. Xem [Hướng dẫn Đóng góp](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) của chúng tôi.
|
||||||
* [Discord](https://discord.gg/FngNHpbcY7). Tốt nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
|
* [Discord](https://discord.gg/FngNHpbcY7). Tốt nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
|
||||||
* [X(Twitter)](https://twitter.com/dify_ai). Tốt nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
|
* [Twitter](https://twitter.com/dify_ai). Tốt nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
|
||||||
|
|
||||||
## Lịch sử Yêu thích
|
## Lịch sử Yêu thích
|
||||||
|
|
||||||
|
|||||||
@ -20,9 +20,6 @@ FILES_URL=http://127.0.0.1:5001
|
|||||||
# The time in seconds after the signature is rejected
|
# The time in seconds after the signature is rejected
|
||||||
FILES_ACCESS_TIMEOUT=300
|
FILES_ACCESS_TIMEOUT=300
|
||||||
|
|
||||||
# Access token expiration time in minutes
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=60
|
|
||||||
|
|
||||||
# celery configuration
|
# celery configuration
|
||||||
CELERY_BROKER_URL=redis://:difyai123456@localhost:6379/1
|
CELERY_BROKER_URL=redis://:difyai123456@localhost:6379/1
|
||||||
|
|
||||||
@ -42,7 +39,7 @@ DB_DATABASE=dify
|
|||||||
|
|
||||||
# Storage configuration
|
# Storage configuration
|
||||||
# use for store upload files, private keys...
|
# use for store upload files, private keys...
|
||||||
# storage type: local, s3, azure-blob, google-storage, tencent-cos, huawei-obs, volcengine-tos, baidu-obs, supabase
|
# storage type: local, s3, azure-blob, google-storage, tencent-cos, huawei-obs, volcengine-tos
|
||||||
STORAGE_TYPE=local
|
STORAGE_TYPE=local
|
||||||
STORAGE_LOCAL_PATH=storage
|
STORAGE_LOCAL_PATH=storage
|
||||||
S3_USE_AWS_MANAGED_IAM=false
|
S3_USE_AWS_MANAGED_IAM=false
|
||||||
@ -82,12 +79,6 @@ HUAWEI_OBS_SECRET_KEY=your-secret-key
|
|||||||
HUAWEI_OBS_ACCESS_KEY=your-access-key
|
HUAWEI_OBS_ACCESS_KEY=your-access-key
|
||||||
HUAWEI_OBS_SERVER=your-server-url
|
HUAWEI_OBS_SERVER=your-server-url
|
||||||
|
|
||||||
# Baidu OBS Storage Configuration
|
|
||||||
BAIDU_OBS_BUCKET_NAME=your-bucket-name
|
|
||||||
BAIDU_OBS_SECRET_KEY=your-secret-key
|
|
||||||
BAIDU_OBS_ACCESS_KEY=your-access-key
|
|
||||||
BAIDU_OBS_ENDPOINT=your-server-url
|
|
||||||
|
|
||||||
# OCI Storage configuration
|
# OCI Storage configuration
|
||||||
OCI_ENDPOINT=your-endpoint
|
OCI_ENDPOINT=your-endpoint
|
||||||
OCI_BUCKET_NAME=your-bucket-name
|
OCI_BUCKET_NAME=your-bucket-name
|
||||||
@ -102,16 +93,11 @@ VOLCENGINE_TOS_ACCESS_KEY=your-access-key
|
|||||||
VOLCENGINE_TOS_SECRET_KEY=your-secret-key
|
VOLCENGINE_TOS_SECRET_KEY=your-secret-key
|
||||||
VOLCENGINE_TOS_REGION=your-region
|
VOLCENGINE_TOS_REGION=your-region
|
||||||
|
|
||||||
# Supabase Storage Configuration
|
|
||||||
SUPABASE_BUCKET_NAME=your-bucket-name
|
|
||||||
SUPABASE_API_KEY=your-access-key
|
|
||||||
SUPABASE_URL=your-server-url
|
|
||||||
|
|
||||||
# CORS configuration
|
# CORS configuration
|
||||||
WEB_API_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,*
|
WEB_API_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,*
|
||||||
CONSOLE_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,*
|
CONSOLE_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,*
|
||||||
|
|
||||||
# Vector database configuration, support: weaviate, qdrant, milvus, myscale, relyt, pgvecto_rs, pgvector, pgvector, chroma, opensearch, tidb_vector, vikingdb
|
# Vector database configuration, support: weaviate, qdrant, milvus, myscale, relyt, pgvecto_rs, pgvector, pgvector, chroma, opensearch, tidb_vector
|
||||||
VECTOR_STORE=weaviate
|
VECTOR_STORE=weaviate
|
||||||
|
|
||||||
# Weaviate configuration
|
# Weaviate configuration
|
||||||
@ -211,24 +197,6 @@ OPENSEARCH_USER=admin
|
|||||||
OPENSEARCH_PASSWORD=admin
|
OPENSEARCH_PASSWORD=admin
|
||||||
OPENSEARCH_SECURE=true
|
OPENSEARCH_SECURE=true
|
||||||
|
|
||||||
# Baidu configuration
|
|
||||||
BAIDU_VECTOR_DB_ENDPOINT=http://127.0.0.1:5287
|
|
||||||
BAIDU_VECTOR_DB_CONNECTION_TIMEOUT_MS=30000
|
|
||||||
BAIDU_VECTOR_DB_ACCOUNT=root
|
|
||||||
BAIDU_VECTOR_DB_API_KEY=dify
|
|
||||||
BAIDU_VECTOR_DB_DATABASE=dify
|
|
||||||
BAIDU_VECTOR_DB_SHARD=1
|
|
||||||
BAIDU_VECTOR_DB_REPLICAS=3
|
|
||||||
|
|
||||||
# ViKingDB configuration
|
|
||||||
VIKINGDB_ACCESS_KEY=your-ak
|
|
||||||
VIKINGDB_SECRET_KEY=your-sk
|
|
||||||
VIKINGDB_REGION=cn-shanghai
|
|
||||||
VIKINGDB_HOST=api-vikingdb.xxx.volces.com
|
|
||||||
VIKINGDB_SCHEMA=http
|
|
||||||
VIKINGDB_CONNECTION_TIMEOUT=30
|
|
||||||
VIKINGDB_SOCKET_TIMEOUT=30
|
|
||||||
|
|
||||||
# Upload configuration
|
# Upload configuration
|
||||||
UPLOAD_FILE_SIZE_LIMIT=15
|
UPLOAD_FILE_SIZE_LIMIT=15
|
||||||
UPLOAD_FILE_BATCH_LIMIT=5
|
UPLOAD_FILE_BATCH_LIMIT=5
|
||||||
@ -297,9 +265,6 @@ HTTP_REQUEST_MAX_WRITE_TIMEOUT=600
|
|||||||
HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760
|
HTTP_REQUEST_NODE_MAX_BINARY_SIZE=10485760
|
||||||
HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576
|
HTTP_REQUEST_NODE_MAX_TEXT_SIZE=1048576
|
||||||
|
|
||||||
# Respect X-* headers to redirect clients
|
|
||||||
RESPECT_XFORWARD_HEADERS_ENABLED=false
|
|
||||||
|
|
||||||
# Log file path
|
# Log file path
|
||||||
LOG_FILE=
|
LOG_FILE=
|
||||||
|
|
||||||
|
|||||||
@ -85,4 +85,3 @@
|
|||||||
cd ../
|
cd ../
|
||||||
poetry run -C api bash dev/pytest/pytest_all_tests.sh
|
poetry run -C api bash dev/pytest/pytest_all_tests.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
207
api/app.py
207
api/app.py
@ -10,19 +10,43 @@ if os.environ.get("DEBUG", "false").lower() != "true":
|
|||||||
grpc.experimental.gevent.init_gevent()
|
grpc.experimental.gevent.init_gevent()
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
from flask import Response
|
from flask import Flask, Response, request
|
||||||
|
from flask_cors import CORS
|
||||||
|
from werkzeug.exceptions import Unauthorized
|
||||||
|
|
||||||
from app_factory import create_app
|
import contexts
|
||||||
|
from commands import register_commands
|
||||||
|
from configs import dify_config
|
||||||
|
|
||||||
# DO NOT REMOVE BELOW
|
# DO NOT REMOVE BELOW
|
||||||
from events import event_handlers # noqa: F401
|
from events import event_handlers
|
||||||
|
from extensions import (
|
||||||
|
ext_celery,
|
||||||
|
ext_code_based_extension,
|
||||||
|
ext_compress,
|
||||||
|
ext_database,
|
||||||
|
ext_hosting_provider,
|
||||||
|
ext_login,
|
||||||
|
ext_mail,
|
||||||
|
ext_migrate,
|
||||||
|
ext_redis,
|
||||||
|
ext_sentry,
|
||||||
|
ext_storage,
|
||||||
|
)
|
||||||
|
from extensions.ext_database import db
|
||||||
|
from extensions.ext_login import login_manager
|
||||||
|
from libs.passport import PassportService
|
||||||
|
|
||||||
# TODO: Find a way to avoid importing models here
|
# TODO: Find a way to avoid importing models here
|
||||||
from models import account, dataset, model, source, task, tool, tools, web # noqa: F401
|
from models import account, dataset, model, source, task, tool, tools, web
|
||||||
|
from services.account_service import AccountService
|
||||||
|
|
||||||
# DO NOT REMOVE ABOVE
|
# DO NOT REMOVE ABOVE
|
||||||
|
|
||||||
@ -35,12 +59,187 @@ if hasattr(time, "tzset"):
|
|||||||
time.tzset()
|
time.tzset()
|
||||||
|
|
||||||
|
|
||||||
|
class DifyApp(Flask):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# -------------
|
# -------------
|
||||||
# Configuration
|
# Configuration
|
||||||
# -------------
|
# -------------
|
||||||
|
|
||||||
|
|
||||||
config_type = os.getenv("EDITION", default="SELF_HOSTED") # ce edition first
|
config_type = os.getenv("EDITION", default="SELF_HOSTED") # ce edition first
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Application Factory Function
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def create_flask_app_with_configs() -> Flask:
|
||||||
|
"""
|
||||||
|
create a raw flask app
|
||||||
|
with configs loaded from .env file
|
||||||
|
"""
|
||||||
|
dify_app = DifyApp(__name__)
|
||||||
|
dify_app.config.from_mapping(dify_config.model_dump())
|
||||||
|
|
||||||
|
# populate configs into system environment variables
|
||||||
|
for key, value in dify_app.config.items():
|
||||||
|
if isinstance(value, str):
|
||||||
|
os.environ[key] = value
|
||||||
|
elif isinstance(value, int | float | bool):
|
||||||
|
os.environ[key] = str(value)
|
||||||
|
elif value is None:
|
||||||
|
os.environ[key] = ""
|
||||||
|
|
||||||
|
return dify_app
|
||||||
|
|
||||||
|
|
||||||
|
def create_app() -> Flask:
|
||||||
|
app = create_flask_app_with_configs()
|
||||||
|
|
||||||
|
app.secret_key = app.config["SECRET_KEY"]
|
||||||
|
|
||||||
|
log_handlers = None
|
||||||
|
log_file = app.config.get("LOG_FILE")
|
||||||
|
if log_file:
|
||||||
|
log_dir = os.path.dirname(log_file)
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_handlers = [
|
||||||
|
RotatingFileHandler(
|
||||||
|
filename=log_file,
|
||||||
|
maxBytes=1024 * 1024 * 1024,
|
||||||
|
backupCount=5,
|
||||||
|
),
|
||||||
|
logging.StreamHandler(sys.stdout),
|
||||||
|
]
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=app.config.get("LOG_LEVEL"),
|
||||||
|
format=app.config.get("LOG_FORMAT"),
|
||||||
|
datefmt=app.config.get("LOG_DATEFORMAT"),
|
||||||
|
handlers=log_handlers,
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
log_tz = app.config.get("LOG_TZ")
|
||||||
|
if log_tz:
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
timezone = pytz.timezone(log_tz)
|
||||||
|
|
||||||
|
def time_converter(seconds):
|
||||||
|
return datetime.utcfromtimestamp(seconds).astimezone(timezone).timetuple()
|
||||||
|
|
||||||
|
for handler in logging.root.handlers:
|
||||||
|
handler.formatter.converter = time_converter
|
||||||
|
initialize_extensions(app)
|
||||||
|
register_blueprints(app)
|
||||||
|
register_commands(app)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_extensions(app):
|
||||||
|
# Since the application instance is now created, pass it to each Flask
|
||||||
|
# extension instance to bind it to the Flask application instance (app)
|
||||||
|
ext_compress.init_app(app)
|
||||||
|
ext_code_based_extension.init()
|
||||||
|
ext_database.init_app(app)
|
||||||
|
ext_migrate.init(app, db)
|
||||||
|
ext_redis.init_app(app)
|
||||||
|
ext_storage.init_app(app)
|
||||||
|
ext_celery.init_app(app)
|
||||||
|
ext_login.init_app(app)
|
||||||
|
ext_mail.init_app(app)
|
||||||
|
ext_hosting_provider.init_app(app)
|
||||||
|
ext_sentry.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
# Flask-Login configuration
|
||||||
|
@login_manager.request_loader
|
||||||
|
def load_user_from_request(request_from_flask_login):
|
||||||
|
"""Load user based on the request."""
|
||||||
|
if request.blueprint not in {"console", "inner_api"}:
|
||||||
|
return None
|
||||||
|
# Check if the user_id contains a dot, indicating the old format
|
||||||
|
auth_header = request.headers.get("Authorization", "")
|
||||||
|
if not auth_header:
|
||||||
|
auth_token = request.args.get("_token")
|
||||||
|
if not auth_token:
|
||||||
|
raise Unauthorized("Invalid Authorization token.")
|
||||||
|
else:
|
||||||
|
if " " not in auth_header:
|
||||||
|
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
|
||||||
|
auth_scheme, auth_token = auth_header.split(None, 1)
|
||||||
|
auth_scheme = auth_scheme.lower()
|
||||||
|
if auth_scheme != "bearer":
|
||||||
|
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
|
||||||
|
|
||||||
|
decoded = PassportService().verify(auth_token)
|
||||||
|
user_id = decoded.get("user_id")
|
||||||
|
|
||||||
|
account = AccountService.load_logged_in_account(account_id=user_id, token=auth_token)
|
||||||
|
if account:
|
||||||
|
contexts.tenant_id.set(account.current_tenant_id)
|
||||||
|
return account
|
||||||
|
|
||||||
|
|
||||||
|
@login_manager.unauthorized_handler
|
||||||
|
def unauthorized_handler():
|
||||||
|
"""Handle unauthorized requests."""
|
||||||
|
return Response(
|
||||||
|
json.dumps({"code": "unauthorized", "message": "Unauthorized."}),
|
||||||
|
status=401,
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# register blueprint routers
|
||||||
|
def register_blueprints(app):
|
||||||
|
from controllers.console import bp as console_app_bp
|
||||||
|
from controllers.files import bp as files_bp
|
||||||
|
from controllers.inner_api import bp as inner_api_bp
|
||||||
|
from controllers.service_api import bp as service_api_bp
|
||||||
|
from controllers.web import bp as web_bp
|
||||||
|
|
||||||
|
CORS(
|
||||||
|
service_api_bp,
|
||||||
|
allow_headers=["Content-Type", "Authorization", "X-App-Code"],
|
||||||
|
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
||||||
|
)
|
||||||
|
app.register_blueprint(service_api_bp)
|
||||||
|
|
||||||
|
CORS(
|
||||||
|
web_bp,
|
||||||
|
resources={r"/*": {"origins": app.config["WEB_API_CORS_ALLOW_ORIGINS"]}},
|
||||||
|
supports_credentials=True,
|
||||||
|
allow_headers=["Content-Type", "Authorization", "X-App-Code"],
|
||||||
|
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
||||||
|
expose_headers=["X-Version", "X-Env"],
|
||||||
|
)
|
||||||
|
|
||||||
|
app.register_blueprint(web_bp)
|
||||||
|
|
||||||
|
CORS(
|
||||||
|
console_app_bp,
|
||||||
|
resources={r"/*": {"origins": app.config["CONSOLE_CORS_ALLOW_ORIGINS"]}},
|
||||||
|
supports_credentials=True,
|
||||||
|
allow_headers=["Content-Type", "Authorization"],
|
||||||
|
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
||||||
|
expose_headers=["X-Version", "X-Env"],
|
||||||
|
)
|
||||||
|
|
||||||
|
app.register_blueprint(console_app_bp)
|
||||||
|
|
||||||
|
CORS(files_bp, allow_headers=["Content-Type"], methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"])
|
||||||
|
app.register_blueprint(files_bp)
|
||||||
|
|
||||||
|
app.register_blueprint(inner_api_bp)
|
||||||
|
|
||||||
|
|
||||||
# create app
|
# create app
|
||||||
app = create_app()
|
app = create_app()
|
||||||
celery = app.extensions["celery"]
|
celery = app.extensions["celery"]
|
||||||
|
|||||||
@ -1,213 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
if os.environ.get("DEBUG", "false").lower() != "true":
|
|
||||||
from gevent import monkey
|
|
||||||
|
|
||||||
monkey.patch_all()
|
|
||||||
|
|
||||||
import grpc.experimental.gevent
|
|
||||||
|
|
||||||
grpc.experimental.gevent.init_gevent()
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
from logging.handlers import RotatingFileHandler
|
|
||||||
|
|
||||||
from flask import Flask, Response, request
|
|
||||||
from flask_cors import CORS
|
|
||||||
from werkzeug.exceptions import Unauthorized
|
|
||||||
|
|
||||||
import contexts
|
|
||||||
from commands import register_commands
|
|
||||||
from configs import dify_config
|
|
||||||
from extensions import (
|
|
||||||
ext_celery,
|
|
||||||
ext_code_based_extension,
|
|
||||||
ext_compress,
|
|
||||||
ext_database,
|
|
||||||
ext_hosting_provider,
|
|
||||||
ext_login,
|
|
||||||
ext_mail,
|
|
||||||
ext_migrate,
|
|
||||||
ext_proxy_fix,
|
|
||||||
ext_redis,
|
|
||||||
ext_sentry,
|
|
||||||
ext_storage,
|
|
||||||
)
|
|
||||||
from extensions.ext_database import db
|
|
||||||
from extensions.ext_login import login_manager
|
|
||||||
from libs.passport import PassportService
|
|
||||||
from services.account_service import AccountService
|
|
||||||
|
|
||||||
|
|
||||||
class DifyApp(Flask):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------
|
|
||||||
# Application Factory Function
|
|
||||||
# ----------------------------
|
|
||||||
def create_flask_app_with_configs() -> Flask:
|
|
||||||
"""
|
|
||||||
create a raw flask app
|
|
||||||
with configs loaded from .env file
|
|
||||||
"""
|
|
||||||
dify_app = DifyApp(__name__)
|
|
||||||
dify_app.config.from_mapping(dify_config.model_dump())
|
|
||||||
|
|
||||||
# populate configs into system environment variables
|
|
||||||
for key, value in dify_app.config.items():
|
|
||||||
if isinstance(value, str):
|
|
||||||
os.environ[key] = value
|
|
||||||
elif isinstance(value, int | float | bool):
|
|
||||||
os.environ[key] = str(value)
|
|
||||||
elif value is None:
|
|
||||||
os.environ[key] = ""
|
|
||||||
|
|
||||||
return dify_app
|
|
||||||
|
|
||||||
|
|
||||||
def create_app() -> Flask:
|
|
||||||
app = create_flask_app_with_configs()
|
|
||||||
|
|
||||||
app.secret_key = app.config["SECRET_KEY"]
|
|
||||||
|
|
||||||
log_handlers = None
|
|
||||||
log_file = app.config.get("LOG_FILE")
|
|
||||||
if log_file:
|
|
||||||
log_dir = os.path.dirname(log_file)
|
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
|
||||||
log_handlers = [
|
|
||||||
RotatingFileHandler(
|
|
||||||
filename=log_file,
|
|
||||||
maxBytes=1024 * 1024 * 1024,
|
|
||||||
backupCount=5,
|
|
||||||
),
|
|
||||||
logging.StreamHandler(sys.stdout),
|
|
||||||
]
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=app.config.get("LOG_LEVEL"),
|
|
||||||
format=app.config.get("LOG_FORMAT"),
|
|
||||||
datefmt=app.config.get("LOG_DATEFORMAT"),
|
|
||||||
handlers=log_handlers,
|
|
||||||
force=True,
|
|
||||||
)
|
|
||||||
log_tz = app.config.get("LOG_TZ")
|
|
||||||
if log_tz:
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
timezone = pytz.timezone(log_tz)
|
|
||||||
|
|
||||||
def time_converter(seconds):
|
|
||||||
return datetime.utcfromtimestamp(seconds).astimezone(timezone).timetuple()
|
|
||||||
|
|
||||||
for handler in logging.root.handlers:
|
|
||||||
handler.formatter.converter = time_converter
|
|
||||||
initialize_extensions(app)
|
|
||||||
register_blueprints(app)
|
|
||||||
register_commands(app)
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_extensions(app):
|
|
||||||
# Since the application instance is now created, pass it to each Flask
|
|
||||||
# extension instance to bind it to the Flask application instance (app)
|
|
||||||
ext_compress.init_app(app)
|
|
||||||
ext_code_based_extension.init()
|
|
||||||
ext_database.init_app(app)
|
|
||||||
ext_migrate.init(app, db)
|
|
||||||
ext_redis.init_app(app)
|
|
||||||
ext_storage.init_app(app)
|
|
||||||
ext_celery.init_app(app)
|
|
||||||
ext_login.init_app(app)
|
|
||||||
ext_mail.init_app(app)
|
|
||||||
ext_hosting_provider.init_app(app)
|
|
||||||
ext_sentry.init_app(app)
|
|
||||||
ext_proxy_fix.init_app(app)
|
|
||||||
|
|
||||||
|
|
||||||
# Flask-Login configuration
|
|
||||||
@login_manager.request_loader
|
|
||||||
def load_user_from_request(request_from_flask_login):
|
|
||||||
"""Load user based on the request."""
|
|
||||||
if request.blueprint not in {"console", "inner_api"}:
|
|
||||||
return None
|
|
||||||
# Check if the user_id contains a dot, indicating the old format
|
|
||||||
auth_header = request.headers.get("Authorization", "")
|
|
||||||
if not auth_header:
|
|
||||||
auth_token = request.args.get("_token")
|
|
||||||
if not auth_token:
|
|
||||||
raise Unauthorized("Invalid Authorization token.")
|
|
||||||
else:
|
|
||||||
if " " not in auth_header:
|
|
||||||
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
|
|
||||||
auth_scheme, auth_token = auth_header.split(None, 1)
|
|
||||||
auth_scheme = auth_scheme.lower()
|
|
||||||
if auth_scheme != "bearer":
|
|
||||||
raise Unauthorized("Invalid Authorization header format. Expected 'Bearer <api-key>' format.")
|
|
||||||
|
|
||||||
decoded = PassportService().verify(auth_token)
|
|
||||||
user_id = decoded.get("user_id")
|
|
||||||
|
|
||||||
logged_in_account = AccountService.load_logged_in_account(account_id=user_id)
|
|
||||||
if logged_in_account:
|
|
||||||
contexts.tenant_id.set(logged_in_account.current_tenant_id)
|
|
||||||
return logged_in_account
|
|
||||||
|
|
||||||
|
|
||||||
@login_manager.unauthorized_handler
|
|
||||||
def unauthorized_handler():
|
|
||||||
"""Handle unauthorized requests."""
|
|
||||||
return Response(
|
|
||||||
json.dumps({"code": "unauthorized", "message": "Unauthorized."}),
|
|
||||||
status=401,
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# register blueprint routers
|
|
||||||
def register_blueprints(app):
|
|
||||||
from controllers.console import bp as console_app_bp
|
|
||||||
from controllers.files import bp as files_bp
|
|
||||||
from controllers.inner_api import bp as inner_api_bp
|
|
||||||
from controllers.service_api import bp as service_api_bp
|
|
||||||
from controllers.web import bp as web_bp
|
|
||||||
|
|
||||||
CORS(
|
|
||||||
service_api_bp,
|
|
||||||
allow_headers=["Content-Type", "Authorization", "X-App-Code"],
|
|
||||||
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
|
||||||
)
|
|
||||||
app.register_blueprint(service_api_bp)
|
|
||||||
|
|
||||||
CORS(
|
|
||||||
web_bp,
|
|
||||||
resources={r"/*": {"origins": app.config["WEB_API_CORS_ALLOW_ORIGINS"]}},
|
|
||||||
supports_credentials=True,
|
|
||||||
allow_headers=["Content-Type", "Authorization", "X-App-Code"],
|
|
||||||
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
|
||||||
expose_headers=["X-Version", "X-Env"],
|
|
||||||
)
|
|
||||||
|
|
||||||
app.register_blueprint(web_bp)
|
|
||||||
|
|
||||||
CORS(
|
|
||||||
console_app_bp,
|
|
||||||
resources={r"/*": {"origins": app.config["CONSOLE_CORS_ALLOW_ORIGINS"]}},
|
|
||||||
supports_credentials=True,
|
|
||||||
allow_headers=["Content-Type", "Authorization"],
|
|
||||||
methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"],
|
|
||||||
expose_headers=["X-Version", "X-Env"],
|
|
||||||
)
|
|
||||||
|
|
||||||
app.register_blueprint(console_app_bp)
|
|
||||||
|
|
||||||
CORS(files_bp, allow_headers=["Content-Type"], methods=["GET", "PUT", "POST", "DELETE", "OPTIONS", "PATCH"])
|
|
||||||
app.register_blueprint(files_bp)
|
|
||||||
|
|
||||||
app.register_blueprint(inner_api_bp)
|
|
||||||
@ -259,25 +259,6 @@ def migrate_knowledge_vector_database():
|
|||||||
skipped_count = 0
|
skipped_count = 0
|
||||||
total_count = 0
|
total_count = 0
|
||||||
vector_type = dify_config.VECTOR_STORE
|
vector_type = dify_config.VECTOR_STORE
|
||||||
upper_colletion_vector_types = {
|
|
||||||
VectorType.MILVUS,
|
|
||||||
VectorType.PGVECTOR,
|
|
||||||
VectorType.RELYT,
|
|
||||||
VectorType.WEAVIATE,
|
|
||||||
VectorType.ORACLE,
|
|
||||||
VectorType.ELASTICSEARCH,
|
|
||||||
}
|
|
||||||
lower_colletion_vector_types = {
|
|
||||||
VectorType.ANALYTICDB,
|
|
||||||
VectorType.CHROMA,
|
|
||||||
VectorType.MYSCALE,
|
|
||||||
VectorType.PGVECTO_RS,
|
|
||||||
VectorType.TIDB_VECTOR,
|
|
||||||
VectorType.OPENSEARCH,
|
|
||||||
VectorType.TENCENT,
|
|
||||||
VectorType.BAIDU,
|
|
||||||
VectorType.VIKINGDB,
|
|
||||||
}
|
|
||||||
page = 1
|
page = 1
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -303,9 +284,11 @@ def migrate_knowledge_vector_database():
|
|||||||
skipped_count = skipped_count + 1
|
skipped_count = skipped_count + 1
|
||||||
continue
|
continue
|
||||||
collection_name = ""
|
collection_name = ""
|
||||||
|
if vector_type == VectorType.WEAVIATE:
|
||||||
dataset_id = dataset.id
|
dataset_id = dataset.id
|
||||||
if vector_type in upper_colletion_vector_types:
|
|
||||||
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": VectorType.WEAVIATE, "vector_store": {"class_prefix": collection_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
elif vector_type == VectorType.QDRANT:
|
elif vector_type == VectorType.QDRANT:
|
||||||
if dataset.collection_binding_id:
|
if dataset.collection_binding_id:
|
||||||
dataset_collection_binding = (
|
dataset_collection_binding = (
|
||||||
@ -318,15 +301,55 @@ def migrate_knowledge_vector_database():
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Dataset Collection Binding not found")
|
raise ValueError("Dataset Collection Binding not found")
|
||||||
else:
|
else:
|
||||||
|
dataset_id = dataset.id
|
||||||
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": VectorType.QDRANT, "vector_store": {"class_prefix": collection_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
|
||||||
elif vector_type in lower_colletion_vector_types:
|
elif vector_type == VectorType.MILVUS:
|
||||||
collection_name = Dataset.gen_collection_name_by_id(dataset_id).lower()
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": VectorType.MILVUS, "vector_store": {"class_prefix": collection_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
elif vector_type == VectorType.RELYT:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": "relyt", "vector_store": {"class_prefix": collection_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
elif vector_type == VectorType.TENCENT:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": VectorType.TENCENT, "vector_store": {"class_prefix": collection_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
elif vector_type == VectorType.PGVECTOR:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": VectorType.PGVECTOR, "vector_store": {"class_prefix": collection_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
elif vector_type == VectorType.OPENSEARCH:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {
|
||||||
|
"type": VectorType.OPENSEARCH,
|
||||||
|
"vector_store": {"class_prefix": collection_name},
|
||||||
|
}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
elif vector_type == VectorType.ANALYTICDB:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
collection_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {
|
||||||
|
"type": VectorType.ANALYTICDB,
|
||||||
|
"vector_store": {"class_prefix": collection_name},
|
||||||
|
}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
|
elif vector_type == VectorType.ELASTICSEARCH:
|
||||||
|
dataset_id = dataset.id
|
||||||
|
index_name = Dataset.gen_collection_name_by_id(dataset_id)
|
||||||
|
index_struct_dict = {"type": "elasticsearch", "vector_store": {"class_prefix": index_name}}
|
||||||
|
dataset.index_struct = json.dumps(index_struct_dict)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Vector store {vector_type} is not supported.")
|
raise ValueError(f"Vector store {vector_type} is not supported.")
|
||||||
|
|
||||||
index_struct_dict = {"type": vector_type, "vector_store": {"class_prefix": collection_name}}
|
|
||||||
dataset.index_struct = json.dumps(index_struct_dict)
|
|
||||||
vector = Vector(dataset)
|
vector = Vector(dataset)
|
||||||
click.echo(f"Migrating dataset {dataset.id}.")
|
click.echo(f"Migrating dataset {dataset.id}.")
|
||||||
|
|
||||||
|
|||||||
@ -247,12 +247,6 @@ class HttpConfig(BaseSettings):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
RESPECT_XFORWARD_HEADERS_ENABLED: bool = Field(
|
|
||||||
description="Enable or disable the X-Forwarded-For Proxy Fix middleware from Werkzeug"
|
|
||||||
" to respect X-* headers to redirect clients",
|
|
||||||
default=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class InnerAPIConfig(BaseSettings):
|
class InnerAPIConfig(BaseSettings):
|
||||||
"""
|
"""
|
||||||
@ -360,9 +354,9 @@ class WorkflowConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AuthConfig(BaseSettings):
|
class OAuthConfig(BaseSettings):
|
||||||
"""
|
"""
|
||||||
Configuration for authentication and OAuth
|
Configuration for OAuth authentication
|
||||||
"""
|
"""
|
||||||
|
|
||||||
OAUTH_REDIRECT_PATH: str = Field(
|
OAUTH_REDIRECT_PATH: str = Field(
|
||||||
@ -371,7 +365,7 @@ class AuthConfig(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
GITHUB_CLIENT_ID: Optional[str] = Field(
|
GITHUB_CLIENT_ID: Optional[str] = Field(
|
||||||
description="GitHub OAuth client ID",
|
description="GitHub OAuth client secret",
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -390,11 +384,6 @@ class AuthConfig(BaseSettings):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES: PositiveInt = Field(
|
|
||||||
description="Expiration time for access tokens in minutes",
|
|
||||||
default=60,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ModerationConfig(BaseSettings):
|
class ModerationConfig(BaseSettings):
|
||||||
"""
|
"""
|
||||||
@ -506,16 +495,11 @@ class DataSetConfig(BaseSettings):
|
|||||||
Configuration for dataset management
|
Configuration for dataset management
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLAN_SANDBOX_CLEAN_DAY_SETTING: PositiveInt = Field(
|
CLEAN_DAY_SETTING: PositiveInt = Field(
|
||||||
description="Interval in days for dataset cleanup operations - plan: sandbox",
|
description="Interval in days for dataset cleanup operations",
|
||||||
default=30,
|
default=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
PLAN_PRO_CLEAN_DAY_SETTING: PositiveInt = Field(
|
|
||||||
description="Interval in days for dataset cleanup operations - plan: pro and team",
|
|
||||||
default=7,
|
|
||||||
)
|
|
||||||
|
|
||||||
DATASET_OPERATOR_ENABLED: bool = Field(
|
DATASET_OPERATOR_ENABLED: bool = Field(
|
||||||
description="Enable or disable dataset operator functionality",
|
description="Enable or disable dataset operator functionality",
|
||||||
default=False,
|
default=False,
|
||||||
@ -617,7 +601,6 @@ class PositionConfig(BaseSettings):
|
|||||||
class FeatureConfig(
|
class FeatureConfig(
|
||||||
# place the configs in alphabet order
|
# place the configs in alphabet order
|
||||||
AppExecutionConfig,
|
AppExecutionConfig,
|
||||||
AuthConfig, # Changed from OAuthConfig to AuthConfig
|
|
||||||
BillingConfig,
|
BillingConfig,
|
||||||
CodeExecutionSandboxConfig,
|
CodeExecutionSandboxConfig,
|
||||||
DataSetConfig,
|
DataSetConfig,
|
||||||
@ -632,13 +615,14 @@ class FeatureConfig(
|
|||||||
MailConfig,
|
MailConfig,
|
||||||
ModelLoadBalanceConfig,
|
ModelLoadBalanceConfig,
|
||||||
ModerationConfig,
|
ModerationConfig,
|
||||||
PositionConfig,
|
OAuthConfig,
|
||||||
RagEtlConfig,
|
RagEtlConfig,
|
||||||
SecurityConfig,
|
SecurityConfig,
|
||||||
ToolConfig,
|
ToolConfig,
|
||||||
UpdateConfig,
|
UpdateConfig,
|
||||||
WorkflowConfig,
|
WorkflowConfig,
|
||||||
WorkspaceConfig,
|
WorkspaceConfig,
|
||||||
|
PositionConfig,
|
||||||
# hosted services config
|
# hosted services config
|
||||||
HostedServiceConfig,
|
HostedServiceConfig,
|
||||||
CeleryBeatConfig,
|
CeleryBeatConfig,
|
||||||
|
|||||||
@ -5,14 +5,13 @@ from pydantic import Field, NonNegativeInt, PositiveFloat, PositiveInt, computed
|
|||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
from configs.middleware.cache.redis_config import RedisConfig
|
from configs.middleware.cache.redis_config import RedisConfig
|
||||||
|
from configs.middleware.external.bedrock_config import BedrockConfig
|
||||||
from configs.middleware.storage.aliyun_oss_storage_config import AliyunOSSStorageConfig
|
from configs.middleware.storage.aliyun_oss_storage_config import AliyunOSSStorageConfig
|
||||||
from configs.middleware.storage.amazon_s3_storage_config import S3StorageConfig
|
from configs.middleware.storage.amazon_s3_storage_config import S3StorageConfig
|
||||||
from configs.middleware.storage.azure_blob_storage_config import AzureBlobStorageConfig
|
from configs.middleware.storage.azure_blob_storage_config import AzureBlobStorageConfig
|
||||||
from configs.middleware.storage.baidu_obs_storage_config import BaiduOBSStorageConfig
|
|
||||||
from configs.middleware.storage.google_cloud_storage_config import GoogleCloudStorageConfig
|
from configs.middleware.storage.google_cloud_storage_config import GoogleCloudStorageConfig
|
||||||
from configs.middleware.storage.huawei_obs_storage_config import HuaweiCloudOBSStorageConfig
|
from configs.middleware.storage.huawei_obs_storage_config import HuaweiCloudOBSStorageConfig
|
||||||
from configs.middleware.storage.oci_storage_config import OCIStorageConfig
|
from configs.middleware.storage.oci_storage_config import OCIStorageConfig
|
||||||
from configs.middleware.storage.supabase_storage_config import SupabaseStorageConfig
|
|
||||||
from configs.middleware.storage.tencent_cos_storage_config import TencentCloudCOSStorageConfig
|
from configs.middleware.storage.tencent_cos_storage_config import TencentCloudCOSStorageConfig
|
||||||
from configs.middleware.storage.volcengine_tos_storage_config import VolcengineTOSStorageConfig
|
from configs.middleware.storage.volcengine_tos_storage_config import VolcengineTOSStorageConfig
|
||||||
from configs.middleware.vdb.analyticdb_config import AnalyticdbConfig
|
from configs.middleware.vdb.analyticdb_config import AnalyticdbConfig
|
||||||
@ -28,7 +27,6 @@ from configs.middleware.vdb.qdrant_config import QdrantConfig
|
|||||||
from configs.middleware.vdb.relyt_config import RelytConfig
|
from configs.middleware.vdb.relyt_config import RelytConfig
|
||||||
from configs.middleware.vdb.tencent_vector_config import TencentVectorDBConfig
|
from configs.middleware.vdb.tencent_vector_config import TencentVectorDBConfig
|
||||||
from configs.middleware.vdb.tidb_vector_config import TiDBVectorConfig
|
from configs.middleware.vdb.tidb_vector_config import TiDBVectorConfig
|
||||||
from configs.middleware.vdb.vikingdb_config import VikingDBConfig
|
|
||||||
from configs.middleware.vdb.weaviate_config import WeaviateConfig
|
from configs.middleware.vdb.weaviate_config import WeaviateConfig
|
||||||
|
|
||||||
|
|
||||||
@ -193,22 +191,6 @@ class CeleryConfig(DatabaseConfig):
|
|||||||
return self.CELERY_BROKER_URL.startswith("rediss://") if self.CELERY_BROKER_URL else False
|
return self.CELERY_BROKER_URL.startswith("rediss://") if self.CELERY_BROKER_URL else False
|
||||||
|
|
||||||
|
|
||||||
class InternalTestConfig(BaseSettings):
|
|
||||||
"""
|
|
||||||
Configuration settings for Internal Test
|
|
||||||
"""
|
|
||||||
|
|
||||||
AWS_SECRET_ACCESS_KEY: Optional[str] = Field(
|
|
||||||
description="Internal test AWS secret access key",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
AWS_ACCESS_KEY_ID: Optional[str] = Field(
|
|
||||||
description="Internal test AWS access key ID",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MiddlewareConfig(
|
class MiddlewareConfig(
|
||||||
# place the configs in alphabet order
|
# place the configs in alphabet order
|
||||||
CeleryConfig,
|
CeleryConfig,
|
||||||
@ -219,14 +201,12 @@ class MiddlewareConfig(
|
|||||||
StorageConfig,
|
StorageConfig,
|
||||||
AliyunOSSStorageConfig,
|
AliyunOSSStorageConfig,
|
||||||
AzureBlobStorageConfig,
|
AzureBlobStorageConfig,
|
||||||
BaiduOBSStorageConfig,
|
|
||||||
GoogleCloudStorageConfig,
|
GoogleCloudStorageConfig,
|
||||||
HuaweiCloudOBSStorageConfig,
|
|
||||||
OCIStorageConfig,
|
|
||||||
S3StorageConfig,
|
|
||||||
SupabaseStorageConfig,
|
|
||||||
TencentCloudCOSStorageConfig,
|
TencentCloudCOSStorageConfig,
|
||||||
|
HuaweiCloudOBSStorageConfig,
|
||||||
VolcengineTOSStorageConfig,
|
VolcengineTOSStorageConfig,
|
||||||
|
S3StorageConfig,
|
||||||
|
OCIStorageConfig,
|
||||||
# configs of vdb and vdb providers
|
# configs of vdb and vdb providers
|
||||||
VectorStoreConfig,
|
VectorStoreConfig,
|
||||||
AnalyticdbConfig,
|
AnalyticdbConfig,
|
||||||
@ -243,7 +223,6 @@ class MiddlewareConfig(
|
|||||||
TiDBVectorConfig,
|
TiDBVectorConfig,
|
||||||
WeaviateConfig,
|
WeaviateConfig,
|
||||||
ElasticsearchConfig,
|
ElasticsearchConfig,
|
||||||
InternalTestConfig,
|
BedrockConfig,
|
||||||
VikingDBConfig,
|
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|||||||
20
api/configs/middleware/external/bedrock_config.py
vendored
Normal file
20
api/configs/middleware/external/bedrock_config.py
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class BedrockConfig(BaseSettings):
|
||||||
|
"""
|
||||||
|
bedrock configs
|
||||||
|
"""
|
||||||
|
|
||||||
|
AWS_SECRET_ACCESS_KEY: Optional[str] = Field(
|
||||||
|
description="AWS secret access key",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
AWS_ACCESS_KEY_ID: Optional[str] = Field(
|
||||||
|
description="AWS secret access id",
|
||||||
|
default=None,
|
||||||
|
)
|
||||||
@ -1,29 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class BaiduOBSStorageConfig(BaseModel):
|
|
||||||
"""
|
|
||||||
Configuration settings for Baidu Object Storage Service (OBS)
|
|
||||||
"""
|
|
||||||
|
|
||||||
BAIDU_OBS_BUCKET_NAME: Optional[str] = Field(
|
|
||||||
description="Name of the Baidu OBS bucket to store and retrieve objects (e.g., 'my-obs-bucket')",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_OBS_ACCESS_KEY: Optional[str] = Field(
|
|
||||||
description="Access Key ID for authenticating with Baidu OBS",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_OBS_SECRET_KEY: Optional[str] = Field(
|
|
||||||
description="Secret Access Key for authenticating with Baidu OBS",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_OBS_ENDPOINT: Optional[str] = Field(
|
|
||||||
description="URL of the Baidu OSS endpoint for your chosen region (e.g., 'https://.bj.bcebos.com')",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class SupabaseStorageConfig(BaseModel):
|
|
||||||
"""
|
|
||||||
Configuration settings for Supabase Object Storage Service
|
|
||||||
"""
|
|
||||||
|
|
||||||
SUPABASE_BUCKET_NAME: Optional[str] = Field(
|
|
||||||
description="Name of the Supabase bucket to store and retrieve objects (e.g., 'dify-bucket')",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
SUPABASE_API_KEY: Optional[str] = Field(
|
|
||||||
description="API KEY for authenticating with Supabase",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
SUPABASE_URL: Optional[str] = Field(
|
|
||||||
description="URL of the Supabase",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import Field, NonNegativeInt, PositiveInt
|
|
||||||
from pydantic_settings import BaseSettings
|
|
||||||
|
|
||||||
|
|
||||||
class BaiduVectorDBConfig(BaseSettings):
|
|
||||||
"""
|
|
||||||
Configuration settings for Baidu Vector Database
|
|
||||||
"""
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_ENDPOINT: Optional[str] = Field(
|
|
||||||
description="URL of the Baidu Vector Database service (e.g., 'http://vdb.bj.baidubce.com')",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_CONNECTION_TIMEOUT_MS: PositiveInt = Field(
|
|
||||||
description="Timeout in milliseconds for Baidu Vector Database operations (default is 30000 milliseconds)",
|
|
||||||
default=30000,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_ACCOUNT: Optional[str] = Field(
|
|
||||||
description="Account for authenticating with the Baidu Vector Database",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_API_KEY: Optional[str] = Field(
|
|
||||||
description="API key for authenticating with the Baidu Vector Database service",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_DATABASE: Optional[str] = Field(
|
|
||||||
description="Name of the specific Baidu Vector Database to connect to",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_SHARD: PositiveInt = Field(
|
|
||||||
description="Number of shards for the Baidu Vector Database (default is 1)",
|
|
||||||
default=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
BAIDU_VECTOR_DB_REPLICAS: NonNegativeInt = Field(
|
|
||||||
description="Number of replicas for the Baidu Vector Database (default is 3)",
|
|
||||||
default=3,
|
|
||||||
)
|
|
||||||
@ -14,7 +14,7 @@ class OracleConfig(BaseSettings):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
ORACLE_PORT: PositiveInt = Field(
|
ORACLE_PORT: Optional[PositiveInt] = Field(
|
||||||
description="Port number on which the Oracle database server is listening (default is 1521)",
|
description="Port number on which the Oracle database server is listening (default is 1521)",
|
||||||
default=1521,
|
default=1521,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class PGVectorConfig(BaseSettings):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
PGVECTOR_PORT: PositiveInt = Field(
|
PGVECTOR_PORT: Optional[PositiveInt] = Field(
|
||||||
description="Port number on which the PostgreSQL server is listening (default is 5433)",
|
description="Port number on which the PostgreSQL server is listening (default is 5433)",
|
||||||
default=5433,
|
default=5433,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class PGVectoRSConfig(BaseSettings):
|
|||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
PGVECTO_RS_PORT: PositiveInt = Field(
|
PGVECTO_RS_PORT: Optional[PositiveInt] = Field(
|
||||||
description="Port number on which the PostgreSQL server with PGVecto.RS is listening (default is 5431)",
|
description="Port number on which the PostgreSQL server with PGVecto.RS is listening (default is 5431)",
|
||||||
default=5431,
|
default=5431,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
|
|
||||||
class VikingDBConfig(BaseModel):
|
|
||||||
"""
|
|
||||||
Configuration for connecting to Volcengine VikingDB.
|
|
||||||
Refer to the following documentation for details on obtaining credentials:
|
|
||||||
https://www.volcengine.com/docs/6291/65568
|
|
||||||
"""
|
|
||||||
|
|
||||||
VIKINGDB_ACCESS_KEY: Optional[str] = Field(
|
|
||||||
description="The Access Key provided by Volcengine VikingDB for API authentication."
|
|
||||||
"Refer to the following documentation for details on obtaining credentials:"
|
|
||||||
"https://www.volcengine.com/docs/6291/65568",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
VIKINGDB_SECRET_KEY: Optional[str] = Field(
|
|
||||||
description="The Secret Key provided by Volcengine VikingDB for API authentication.",
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
VIKINGDB_REGION: str = Field(
|
|
||||||
description="The region of the Volcengine VikingDB service.(e.g., 'cn-shanghai', 'cn-beijing').",
|
|
||||||
default="cn-shanghai",
|
|
||||||
)
|
|
||||||
|
|
||||||
VIKINGDB_HOST: str = Field(
|
|
||||||
description="The host of the Volcengine VikingDB service.(e.g., 'api-vikingdb.volces.com', \
|
|
||||||
'api-vikingdb.mlp.cn-shanghai.volces.com')",
|
|
||||||
default="api-vikingdb.mlp.cn-shanghai.volces.com",
|
|
||||||
)
|
|
||||||
|
|
||||||
VIKINGDB_SCHEME: str = Field(
|
|
||||||
description="The scheme of the Volcengine VikingDB service.(e.g., 'http', 'https').",
|
|
||||||
default="http",
|
|
||||||
)
|
|
||||||
|
|
||||||
VIKINGDB_CONNECTION_TIMEOUT: int = Field(
|
|
||||||
description="The connection timeout of the Volcengine VikingDB service.",
|
|
||||||
default=30,
|
|
||||||
)
|
|
||||||
|
|
||||||
VIKINGDB_SOCKET_TIMEOUT: int = Field(
|
|
||||||
description="The socket timeout of the Volcengine VikingDB service.",
|
|
||||||
default=30,
|
|
||||||
)
|
|
||||||
@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description="Dify version",
|
description="Dify version",
|
||||||
default="0.9.2",
|
default="0.8.3",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
|||||||
@ -45,6 +45,7 @@ from .datasets import (
|
|||||||
external,
|
external,
|
||||||
file,
|
file,
|
||||||
hit_testing,
|
hit_testing,
|
||||||
|
test_external,
|
||||||
website,
|
website,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -188,7 +188,6 @@ class ChatConversationApi(Resource):
|
|||||||
subquery.c.from_end_user_session_id.ilike(keyword_filter),
|
subquery.c.from_end_user_session_id.ilike(keyword_filter),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.group_by(Conversation.id)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
account = current_user
|
account = current_user
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from flask_restful import Resource, reqparse
|
|||||||
import services
|
import services
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.setup import setup_required
|
from controllers.console.setup import setup_required
|
||||||
from libs.helper import email, extract_remote_ip
|
from libs.helper import email, get_remote_ip
|
||||||
from libs.password import valid_password
|
from libs.password import valid_password
|
||||||
from models.account import Account
|
from models.account import Account
|
||||||
from services.account_service import AccountService, TenantService
|
from services.account_service import AccountService, TenantService
|
||||||
@ -40,16 +40,17 @@ class LoginApi(Resource):
|
|||||||
"data": "workspace not found, please contact system admin to invite you to join in a workspace",
|
"data": "workspace not found, please contact system admin to invite you to join in a workspace",
|
||||||
}
|
}
|
||||||
|
|
||||||
token_pair = AccountService.login(account=account, ip_address=extract_remote_ip(request))
|
token = AccountService.login(account, ip_address=get_remote_ip(request))
|
||||||
|
|
||||||
return {"result": "success", "data": token_pair.model_dump()}
|
return {"result": "success", "data": token}
|
||||||
|
|
||||||
|
|
||||||
class LogoutApi(Resource):
|
class LogoutApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
def get(self):
|
def get(self):
|
||||||
account = cast(Account, flask_login.current_user)
|
account = cast(Account, flask_login.current_user)
|
||||||
AccountService.logout(account=account)
|
token = request.headers.get("Authorization", "").split(" ")[1]
|
||||||
|
AccountService.logout(account=account, token=token)
|
||||||
flask_login.logout_user()
|
flask_login.logout_user()
|
||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
@ -105,19 +106,5 @@ class ResetPasswordApi(Resource):
|
|||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
|
|
||||||
class RefreshTokenApi(Resource):
|
|
||||||
def post(self):
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("refresh_token", type=str, required=True, location="json")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_token_pair = AccountService.refresh_token(args["refresh_token"])
|
|
||||||
return {"result": "success", "data": new_token_pair.model_dump()}
|
|
||||||
except Exception as e:
|
|
||||||
return {"result": "fail", "data": str(e)}, 401
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(LoginApi, "/login")
|
api.add_resource(LoginApi, "/login")
|
||||||
api.add_resource(LogoutApi, "/logout")
|
api.add_resource(LogoutApi, "/logout")
|
||||||
api.add_resource(RefreshTokenApi, "/refresh-token")
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from flask_restful import Resource
|
|||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from constants.languages import languages
|
from constants.languages import languages
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.helper import extract_remote_ip
|
from libs.helper import get_remote_ip
|
||||||
from libs.oauth import GitHubOAuth, GoogleOAuth, OAuthUserInfo
|
from libs.oauth import GitHubOAuth, GoogleOAuth, OAuthUserInfo
|
||||||
from models.account import Account, AccountStatus
|
from models.account import Account, AccountStatus
|
||||||
from services.account_service import AccountService, RegisterService, TenantService
|
from services.account_service import AccountService, RegisterService, TenantService
|
||||||
@ -81,14 +81,9 @@ class OAuthCallback(Resource):
|
|||||||
|
|
||||||
TenantService.create_owner_tenant_if_not_exist(account)
|
TenantService.create_owner_tenant_if_not_exist(account)
|
||||||
|
|
||||||
token_pair = AccountService.login(
|
token = AccountService.login(account, ip_address=get_remote_ip(request))
|
||||||
account=account,
|
|
||||||
ip_address=extract_remote_ip(request),
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect(
|
return redirect(f"{dify_config.CONSOLE_WEB_URL}?console_token={token}")
|
||||||
f"{dify_config.CONSOLE_WEB_URL}?access_token={token_pair.access_token}&refresh_token={token_pair.refresh_token}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) -> Optional[Account]:
|
def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) -> Optional[Account]:
|
||||||
|
|||||||
@ -613,12 +613,10 @@ class DatasetRetrievalSettingApi(Resource):
|
|||||||
case (
|
case (
|
||||||
VectorType.MILVUS
|
VectorType.MILVUS
|
||||||
| VectorType.RELYT
|
| VectorType.RELYT
|
||||||
|
| VectorType.PGVECTOR
|
||||||
| VectorType.TIDB_VECTOR
|
| VectorType.TIDB_VECTOR
|
||||||
| VectorType.CHROMA
|
| VectorType.CHROMA
|
||||||
| VectorType.TENCENT
|
| VectorType.TENCENT
|
||||||
| VectorType.PGVECTO_RS
|
|
||||||
| VectorType.BAIDU
|
|
||||||
| VectorType.VIKINGDB
|
|
||||||
):
|
):
|
||||||
return {"retrieval_method": [RetrievalMethod.SEMANTIC_SEARCH.value]}
|
return {"retrieval_method": [RetrievalMethod.SEMANTIC_SEARCH.value]}
|
||||||
case (
|
case (
|
||||||
@ -629,7 +627,6 @@ class DatasetRetrievalSettingApi(Resource):
|
|||||||
| VectorType.MYSCALE
|
| VectorType.MYSCALE
|
||||||
| VectorType.ORACLE
|
| VectorType.ORACLE
|
||||||
| VectorType.ELASTICSEARCH
|
| VectorType.ELASTICSEARCH
|
||||||
| VectorType.PGVECTOR
|
|
||||||
):
|
):
|
||||||
return {
|
return {
|
||||||
"retrieval_method": [
|
"retrieval_method": [
|
||||||
@ -655,8 +652,6 @@ class DatasetRetrievalSettingMockApi(Resource):
|
|||||||
| VectorType.CHROMA
|
| VectorType.CHROMA
|
||||||
| VectorType.TENCENT
|
| VectorType.TENCENT
|
||||||
| VectorType.PGVECTO_RS
|
| VectorType.PGVECTO_RS
|
||||||
| VectorType.BAIDU
|
|
||||||
| VectorType.VIKINGDB
|
|
||||||
):
|
):
|
||||||
return {"retrieval_method": [RetrievalMethod.SEMANTIC_SEARCH.value]}
|
return {"retrieval_method": [RetrievalMethod.SEMANTIC_SEARCH.value]}
|
||||||
case (
|
case (
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
|||||||
|
|
||||||
import services
|
import services
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
from controllers.console.app.error import ProviderNotInitializeError
|
||||||
from controllers.console.datasets.error import DatasetNameDuplicateError
|
from controllers.console.datasets.error import DatasetNameDuplicateError
|
||||||
from controllers.console.setup import setup_required
|
from controllers.console.setup import setup_required
|
||||||
from controllers.console.wraps import account_initialization_required
|
from controllers.console.wraps import account_initialization_required
|
||||||
@ -13,7 +14,6 @@ from libs.login import login_required
|
|||||||
from services.dataset_service import DatasetService
|
from services.dataset_service import DatasetService
|
||||||
from services.external_knowledge_service import ExternalDatasetService
|
from services.external_knowledge_service import ExternalDatasetService
|
||||||
from services.hit_testing_service import HitTestingService
|
from services.hit_testing_service import HitTestingService
|
||||||
from services.knowledge_service import ExternalDatasetTestService
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_name(name):
|
def _validate_name(name):
|
||||||
@ -158,6 +158,48 @@ class ExternalApiUseCheckApi(Resource):
|
|||||||
return {"is_using": external_knowledge_api_is_using, "count": count}, 200
|
return {"is_using": external_knowledge_api_is_using, "count": count}, 200
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalDatasetInitApi(Resource):
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
|
def post(self):
|
||||||
|
# The role of the current user in the ta table must be admin, owner, or editor
|
||||||
|
if not current_user.is_editor:
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("external_knowledge_api_id", type=str, required=True, nullable=True, location="json")
|
||||||
|
# parser.add_argument('name', nullable=False, required=True,
|
||||||
|
# help='name is required. Name must be between 1 to 100 characters.',
|
||||||
|
# type=_validate_name)
|
||||||
|
# parser.add_argument('description', type=str, required=True, nullable=True, location='json')
|
||||||
|
parser.add_argument("data_source", type=dict, required=True, nullable=True, location="json")
|
||||||
|
parser.add_argument("process_parameter", type=dict, required=True, nullable=True, location="json")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# The role of the current user in the ta table must be admin, owner, or editor, or dataset_operator
|
||||||
|
if not current_user.is_dataset_editor:
|
||||||
|
raise Forbidden()
|
||||||
|
|
||||||
|
# validate args
|
||||||
|
ExternalDatasetService.document_create_args_validate(
|
||||||
|
current_user.current_tenant_id, args["external_knowledge_api_id"], args["process_parameter"]
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
dataset, documents, batch = ExternalDatasetService.init_external_dataset(
|
||||||
|
tenant_id=current_user.current_tenant_id,
|
||||||
|
user_id=current_user.id,
|
||||||
|
args=args,
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
raise ProviderNotInitializeError(ex.description)
|
||||||
|
response = {"dataset": dataset, "documents": documents, "batch": batch}
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ExternalDatasetCreateApi(Resource):
|
class ExternalDatasetCreateApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@ -233,31 +275,8 @@ class ExternalKnowledgeHitTestingApi(Resource):
|
|||||||
raise InternalServerError(str(e))
|
raise InternalServerError(str(e))
|
||||||
|
|
||||||
|
|
||||||
class BedrockRetrievalApi(Resource):
|
|
||||||
# this api is only for internal testing
|
|
||||||
def post(self):
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
parser.add_argument("retrieval_setting", nullable=False, required=True, type=dict, location="json")
|
|
||||||
parser.add_argument(
|
|
||||||
"query",
|
|
||||||
nullable=False,
|
|
||||||
required=True,
|
|
||||||
type=str,
|
|
||||||
)
|
|
||||||
parser.add_argument("knowledge_id", nullable=False, required=True, type=str)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Call the knowledge retrieval service
|
|
||||||
result = ExternalDatasetTestService.knowledge_retrieval(
|
|
||||||
args["retrieval_setting"], args["query"], args["knowledge_id"]
|
|
||||||
)
|
|
||||||
return result, 200
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(ExternalKnowledgeHitTestingApi, "/datasets/<uuid:dataset_id>/external-hit-testing")
|
api.add_resource(ExternalKnowledgeHitTestingApi, "/datasets/<uuid:dataset_id>/external-hit-testing")
|
||||||
api.add_resource(ExternalDatasetCreateApi, "/datasets/external")
|
api.add_resource(ExternalDatasetCreateApi, "/datasets/external")
|
||||||
api.add_resource(ExternalApiTemplateListApi, "/datasets/external-knowledge-api")
|
api.add_resource(ExternalApiTemplateListApi, "/datasets/external-knowledge-api")
|
||||||
api.add_resource(ExternalApiTemplateApi, "/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>")
|
api.add_resource(ExternalApiTemplateApi, "/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>")
|
||||||
api.add_resource(ExternalApiUseCheckApi, "/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>/use-check")
|
api.add_resource(ExternalApiUseCheckApi, "/datasets/external-knowledge-api/<uuid:external_knowledge_api_id>/use-check")
|
||||||
# this api is only for internal test
|
|
||||||
api.add_resource(BedrockRetrievalApi, "/test/retrieval")
|
|
||||||
|
|||||||
@ -1,24 +1,88 @@
|
|||||||
from flask_restful import Resource
|
import logging
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_restful import Resource, marshal, reqparse
|
||||||
|
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
||||||
|
|
||||||
|
import services
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase
|
from controllers.console.app.error import (
|
||||||
|
CompletionRequestError,
|
||||||
|
ProviderModelCurrentlyNotSupportError,
|
||||||
|
ProviderNotInitializeError,
|
||||||
|
ProviderQuotaExceededError,
|
||||||
|
)
|
||||||
|
from controllers.console.datasets.error import DatasetNotInitializedError
|
||||||
from controllers.console.setup import setup_required
|
from controllers.console.setup import setup_required
|
||||||
from controllers.console.wraps import account_initialization_required
|
from controllers.console.wraps import account_initialization_required
|
||||||
|
from core.errors.error import (
|
||||||
|
LLMBadRequestError,
|
||||||
|
ModelCurrentlyNotSupportError,
|
||||||
|
ProviderTokenNotInitError,
|
||||||
|
QuotaExceededError,
|
||||||
|
)
|
||||||
|
from core.model_runtime.errors.invoke import InvokeError
|
||||||
|
from fields.hit_testing_fields import hit_testing_record_fields
|
||||||
from libs.login import login_required
|
from libs.login import login_required
|
||||||
|
from services.dataset_service import DatasetService
|
||||||
|
from services.hit_testing_service import HitTestingService
|
||||||
|
|
||||||
|
|
||||||
class HitTestingApi(Resource, DatasetsHitTestingBase):
|
class HitTestingApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
@login_required
|
@login_required
|
||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def post(self, dataset_id):
|
def post(self, dataset_id):
|
||||||
dataset_id_str = str(dataset_id)
|
dataset_id_str = str(dataset_id)
|
||||||
|
|
||||||
dataset = self.get_and_validate_dataset(dataset_id_str)
|
dataset = DatasetService.get_dataset(dataset_id_str)
|
||||||
args = self.parse_args()
|
if dataset is None:
|
||||||
self.hit_testing_args_check(args)
|
raise NotFound("Dataset not found.")
|
||||||
|
|
||||||
return self.perform_hit_testing(dataset, args)
|
try:
|
||||||
|
DatasetService.check_dataset_permission(dataset, current_user)
|
||||||
|
except services.errors.account.NoPermissionError as e:
|
||||||
|
raise Forbidden(str(e))
|
||||||
|
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("query", type=str, location="json")
|
||||||
|
parser.add_argument("retrieval_model", type=dict, required=False, location="json")
|
||||||
|
parser.add_argument("external_retrieval_model", type=dict, required=False, location="json")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
HitTestingService.hit_testing_args_check(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = HitTestingService.retrieve(
|
||||||
|
dataset=dataset,
|
||||||
|
query=args["query"],
|
||||||
|
account=current_user,
|
||||||
|
retrieval_model=args["retrieval_model"],
|
||||||
|
external_retrieval_model=args["external_retrieval_model"],
|
||||||
|
limit=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"query": response["query"], "records": marshal(response["records"], hit_testing_record_fields)}
|
||||||
|
except services.errors.index.IndexNotInitializedError:
|
||||||
|
raise DatasetNotInitializedError()
|
||||||
|
except ProviderTokenNotInitError as ex:
|
||||||
|
raise ProviderNotInitializeError(ex.description)
|
||||||
|
except QuotaExceededError:
|
||||||
|
raise ProviderQuotaExceededError()
|
||||||
|
except ModelCurrentlyNotSupportError:
|
||||||
|
raise ProviderModelCurrentlyNotSupportError()
|
||||||
|
except LLMBadRequestError:
|
||||||
|
raise ProviderNotInitializeError(
|
||||||
|
"No Embedding Model or Reranking Model available. Please configure a valid provider "
|
||||||
|
"in the Settings -> Model Provider."
|
||||||
|
)
|
||||||
|
except InvokeError as e:
|
||||||
|
raise CompletionRequestError(e.description)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("Hit testing failed.")
|
||||||
|
raise InternalServerError(str(e))
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(HitTestingApi, "/datasets/<uuid:dataset_id>/hit-testing")
|
api.add_resource(HitTestingApi, "/datasets/<uuid:dataset_id>/hit-testing")
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from flask_login import current_user
|
|
||||||
from flask_restful import marshal, reqparse
|
|
||||||
from werkzeug.exceptions import Forbidden, InternalServerError, NotFound
|
|
||||||
|
|
||||||
import services.dataset_service
|
|
||||||
from controllers.console.app.error import (
|
|
||||||
CompletionRequestError,
|
|
||||||
ProviderModelCurrentlyNotSupportError,
|
|
||||||
ProviderNotInitializeError,
|
|
||||||
ProviderQuotaExceededError,
|
|
||||||
)
|
|
||||||
from controllers.console.datasets.error import DatasetNotInitializedError
|
|
||||||
from core.errors.error import (
|
|
||||||
LLMBadRequestError,
|
|
||||||
ModelCurrentlyNotSupportError,
|
|
||||||
ProviderTokenNotInitError,
|
|
||||||
QuotaExceededError,
|
|
||||||
)
|
|
||||||
from core.model_runtime.errors.invoke import InvokeError
|
|
||||||
from fields.hit_testing_fields import hit_testing_record_fields
|
|
||||||
from services.dataset_service import DatasetService
|
|
||||||
from services.hit_testing_service import HitTestingService
|
|
||||||
|
|
||||||
|
|
||||||
class DatasetsHitTestingBase:
|
|
||||||
@staticmethod
|
|
||||||
def get_and_validate_dataset(dataset_id: str):
|
|
||||||
dataset = DatasetService.get_dataset(dataset_id)
|
|
||||||
if dataset is None:
|
|
||||||
raise NotFound("Dataset not found.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
DatasetService.check_dataset_permission(dataset, current_user)
|
|
||||||
except services.errors.account.NoPermissionError as e:
|
|
||||||
raise Forbidden(str(e))
|
|
||||||
|
|
||||||
return dataset
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def hit_testing_args_check(args):
|
|
||||||
HitTestingService.hit_testing_args_check(args)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_args():
|
|
||||||
parser = reqparse.RequestParser()
|
|
||||||
|
|
||||||
parser.add_argument("query", type=str, location="json")
|
|
||||||
parser.add_argument("retrieval_model", type=dict, required=False, location="json")
|
|
||||||
parser.add_argument("external_retrieval_model", type=dict, required=False, location="json")
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def perform_hit_testing(dataset, args):
|
|
||||||
try:
|
|
||||||
response = HitTestingService.retrieve(
|
|
||||||
dataset=dataset,
|
|
||||||
query=args["query"],
|
|
||||||
account=current_user,
|
|
||||||
retrieval_model=args["retrieval_model"],
|
|
||||||
external_retrieval_model=args["external_retrieval_model"],
|
|
||||||
limit=10,
|
|
||||||
)
|
|
||||||
return {"query": response["query"], "records": marshal(response["records"], hit_testing_record_fields)}
|
|
||||||
except services.errors.index.IndexNotInitializedError:
|
|
||||||
raise DatasetNotInitializedError()
|
|
||||||
except ProviderTokenNotInitError as ex:
|
|
||||||
raise ProviderNotInitializeError(ex.description)
|
|
||||||
except QuotaExceededError:
|
|
||||||
raise ProviderQuotaExceededError()
|
|
||||||
except ModelCurrentlyNotSupportError:
|
|
||||||
raise ProviderModelCurrentlyNotSupportError()
|
|
||||||
except LLMBadRequestError:
|
|
||||||
raise ProviderNotInitializeError(
|
|
||||||
"No Embedding Model or Reranking Model available. Please configure a valid provider "
|
|
||||||
"in the Settings -> Model Provider."
|
|
||||||
)
|
|
||||||
except InvokeError as e:
|
|
||||||
raise CompletionRequestError(e.description)
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValueError(str(e))
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception("Hit testing failed.")
|
|
||||||
raise InternalServerError(str(e))
|
|
||||||
33
api/controllers/console/datasets/test_external.py
Normal file
33
api/controllers/console/datasets/test_external.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
|
from controllers.console import api
|
||||||
|
from controllers.console.setup import setup_required
|
||||||
|
from controllers.console.wraps import account_initialization_required
|
||||||
|
from libs.login import login_required
|
||||||
|
from services.external_knowledge_service import ExternalDatasetService
|
||||||
|
|
||||||
|
|
||||||
|
class TestExternalApi(Resource):
|
||||||
|
def post(self):
|
||||||
|
parser = reqparse.RequestParser()
|
||||||
|
parser.add_argument("retrieval_setting", nullable=False, required=True, type=dict, location="json")
|
||||||
|
parser.add_argument(
|
||||||
|
"query",
|
||||||
|
nullable=False,
|
||||||
|
required=True,
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"knowledge_id",
|
||||||
|
nullable=False,
|
||||||
|
required=True,
|
||||||
|
type=str,
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
result = ExternalDatasetService.test_external_knowledge_retrieval(
|
||||||
|
args["retrieval_setting"], args["query"], args["knowledge_id"]
|
||||||
|
)
|
||||||
|
return result, 200
|
||||||
|
|
||||||
|
|
||||||
|
api.add_resource(TestExternalApi, "/retrieval")
|
||||||
@ -14,9 +14,7 @@ class WebsiteCrawlApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument(
|
parser.add_argument("provider", type=str, choices=["firecrawl"], required=True, nullable=True, location="json")
|
||||||
"provider", type=str, choices=["firecrawl", "jinareader"], required=True, nullable=True, location="json"
|
|
||||||
)
|
|
||||||
parser.add_argument("url", type=str, required=True, nullable=True, location="json")
|
parser.add_argument("url", type=str, required=True, nullable=True, location="json")
|
||||||
parser.add_argument("options", type=dict, required=True, nullable=True, location="json")
|
parser.add_argument("options", type=dict, required=True, nullable=True, location="json")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -35,7 +33,7 @@ class WebsiteCrawlStatusApi(Resource):
|
|||||||
@account_initialization_required
|
@account_initialization_required
|
||||||
def get(self, job_id: str):
|
def get(self, job_id: str):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("provider", type=str, choices=["firecrawl", "jinareader"], required=True, location="args")
|
parser.add_argument("provider", type=str, choices=["firecrawl"], required=True, location="args")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
# get crawl status
|
# get crawl status
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from flask import request
|
|||||||
from flask_restful import Resource, reqparse
|
from flask_restful import Resource, reqparse
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from libs.helper import StrLen, email, extract_remote_ip
|
from libs.helper import StrLen, email, get_remote_ip
|
||||||
from libs.password import valid_password
|
from libs.password import valid_password
|
||||||
from models.model import DifySetup
|
from models.model import DifySetup
|
||||||
from services.account_service import RegisterService, TenantService
|
from services.account_service import RegisterService, TenantService
|
||||||
@ -46,7 +46,7 @@ class SetupApi(Resource):
|
|||||||
|
|
||||||
# setup
|
# setup
|
||||||
RegisterService.setup(
|
RegisterService.setup(
|
||||||
email=args["email"], name=args["name"], password=args["password"], ip_address=extract_remote_ip(request)
|
email=args["email"], name=args["name"], password=args["password"], ip_address=get_remote_ip(request)
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"result": "success"}, 201
|
return {"result": "success"}, 201
|
||||||
|
|||||||
@ -38,7 +38,6 @@ class VersionApi(Resource):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
content = json.loads(response.content)
|
content = json.loads(response.content)
|
||||||
if _has_new_version(latest_version=content["version"], current_version=f"{args.get('current_version')}"):
|
|
||||||
result["version"] = content["version"]
|
result["version"] = content["version"]
|
||||||
result["release_date"] = content["releaseDate"]
|
result["release_date"] = content["releaseDate"]
|
||||||
result["release_notes"] = content["releaseNotes"]
|
result["release_notes"] = content["releaseNotes"]
|
||||||
@ -46,44 +45,4 @@ class VersionApi(Resource):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _has_new_version(*, latest_version: str, current_version: str) -> bool:
|
|
||||||
def parse_version(version: str) -> tuple:
|
|
||||||
# Split version into parts and pre-release suffix if any
|
|
||||||
parts = version.split("-")
|
|
||||||
version_parts = parts[0].split(".")
|
|
||||||
pre_release = parts[1] if len(parts) > 1 else None
|
|
||||||
|
|
||||||
# Validate version format
|
|
||||||
if len(version_parts) != 3:
|
|
||||||
raise ValueError(f"Invalid version format: {version}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Convert version parts to integers
|
|
||||||
major, minor, patch = map(int, version_parts)
|
|
||||||
return (major, minor, patch, pre_release)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(f"Invalid version format: {version}")
|
|
||||||
|
|
||||||
latest = parse_version(latest_version)
|
|
||||||
current = parse_version(current_version)
|
|
||||||
|
|
||||||
# Compare major, minor, and patch versions
|
|
||||||
for latest_part, current_part in zip(latest[:3], current[:3]):
|
|
||||||
if latest_part > current_part:
|
|
||||||
return True
|
|
||||||
elif latest_part < current_part:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# If versions are equal, check pre-release suffixes
|
|
||||||
if latest[3] is None and current[3] is not None:
|
|
||||||
return True
|
|
||||||
elif latest[3] is not None and current[3] is None:
|
|
||||||
return False
|
|
||||||
elif latest[3] is not None and current[3] is not None:
|
|
||||||
# Simple string comparison for pre-release versions
|
|
||||||
return latest[3] > current[3]
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(VersionApi, "/version")
|
api.add_resource(VersionApi, "/version")
|
||||||
|
|||||||
@ -126,12 +126,13 @@ class ModelProviderIconApi(Resource):
|
|||||||
Get model provider icon
|
Get model provider icon
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@setup_required
|
||||||
|
@login_required
|
||||||
|
@account_initialization_required
|
||||||
def get(self, provider: str, icon_type: str, lang: str):
|
def get(self, provider: str, icon_type: str, lang: str):
|
||||||
model_provider_service = ModelProviderService()
|
model_provider_service = ModelProviderService()
|
||||||
icon, mimetype = model_provider_service.get_model_provider_icon(
|
icon, mimetype = model_provider_service.get_model_provider_icon(
|
||||||
provider=provider,
|
provider=provider, icon_type=icon_type, lang=lang
|
||||||
icon_type=icon_type,
|
|
||||||
lang=lang,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return send_file(io.BytesIO(icon), mimetype=mimetype)
|
return send_file(io.BytesIO(icon), mimetype=mimetype)
|
||||||
|
|||||||
@ -72,9 +72,8 @@ class DefaultModelApi(Resource):
|
|||||||
provider=model_setting["provider"],
|
provider=model_setting["provider"],
|
||||||
model=model_setting["model"],
|
model=model_setting["model"],
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except Exception:
|
||||||
logging.exception(f"{model_setting['model_type']} save error: {ex}")
|
logging.warning(f"{model_setting['model_type']} save error")
|
||||||
raise ex
|
|
||||||
|
|
||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
from libs.exception import BaseHTTPException
|
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedFileTypeError(BaseHTTPException):
|
|
||||||
error_code = "unsupported_file_type"
|
|
||||||
description = "File type not allowed."
|
|
||||||
code = 415
|
|
||||||
@ -4,7 +4,7 @@ from werkzeug.exceptions import NotFound
|
|||||||
|
|
||||||
import services
|
import services
|
||||||
from controllers.files import api
|
from controllers.files import api
|
||||||
from controllers.files.error import UnsupportedFileTypeError
|
from libs.exception import BaseHTTPException
|
||||||
from services.account_service import TenantService
|
from services.account_service import TenantService
|
||||||
from services.file_service import FileService
|
from services.file_service import FileService
|
||||||
|
|
||||||
@ -50,3 +50,9 @@ class WorkspaceWebappLogoApi(Resource):
|
|||||||
|
|
||||||
api.add_resource(ImagePreviewApi, "/files/<uuid:file_id>/image-preview")
|
api.add_resource(ImagePreviewApi, "/files/<uuid:file_id>/image-preview")
|
||||||
api.add_resource(WorkspaceWebappLogoApi, "/files/workspaces/<uuid:workspace_id>/webapp-logo")
|
api.add_resource(WorkspaceWebappLogoApi, "/files/workspaces/<uuid:workspace_id>/webapp-logo")
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedFileTypeError(BaseHTTPException):
|
||||||
|
error_code = "unsupported_file_type"
|
||||||
|
description = "File type not allowed."
|
||||||
|
code = 415
|
||||||
|
|||||||
@ -3,8 +3,8 @@ from flask_restful import Resource, reqparse
|
|||||||
from werkzeug.exceptions import Forbidden, NotFound
|
from werkzeug.exceptions import Forbidden, NotFound
|
||||||
|
|
||||||
from controllers.files import api
|
from controllers.files import api
|
||||||
from controllers.files.error import UnsupportedFileTypeError
|
|
||||||
from core.tools.tool_file_manager import ToolFileManager
|
from core.tools.tool_file_manager import ToolFileManager
|
||||||
|
from libs.exception import BaseHTTPException
|
||||||
|
|
||||||
|
|
||||||
class ToolFilePreviewApi(Resource):
|
class ToolFilePreviewApi(Resource):
|
||||||
@ -43,3 +43,9 @@ class ToolFilePreviewApi(Resource):
|
|||||||
|
|
||||||
|
|
||||||
api.add_resource(ToolFilePreviewApi, "/files/tools/<uuid:file_id>.<string:extension>")
|
api.add_resource(ToolFilePreviewApi, "/files/tools/<uuid:file_id>.<string:extension>")
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedFileTypeError(BaseHTTPException):
|
||||||
|
error_code = "unsupported_file_type"
|
||||||
|
description = "File type not allowed."
|
||||||
|
code = 415
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from libs.external_api import ExternalApi
|
|||||||
bp = Blueprint("service_api", __name__, url_prefix="/v1")
|
bp = Blueprint("service_api", __name__, url_prefix="/v1")
|
||||||
api = ExternalApi(bp)
|
api = ExternalApi(bp)
|
||||||
|
|
||||||
|
|
||||||
from . import index
|
from . import index
|
||||||
from .app import app, audio, completion, conversation, file, message, workflow
|
from .app import app, audio, completion, conversation, file, message, workflow
|
||||||
from .dataset import dataset, document, hit_testing, segment
|
from .dataset import dataset, document, segment
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
from controllers.console.datasets.hit_testing_base import DatasetsHitTestingBase
|
|
||||||
from controllers.service_api import api
|
|
||||||
from controllers.service_api.wraps import DatasetApiResource
|
|
||||||
|
|
||||||
|
|
||||||
class HitTestingApi(DatasetApiResource, DatasetsHitTestingBase):
|
|
||||||
def post(self, tenant_id, dataset_id):
|
|
||||||
dataset_id_str = str(dataset_id)
|
|
||||||
|
|
||||||
dataset = self.get_and_validate_dataset(dataset_id_str)
|
|
||||||
args = self.parse_args()
|
|
||||||
self.hit_testing_args_check(args)
|
|
||||||
|
|
||||||
return self.perform_hit_testing(dataset, args)
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(HitTestingApi, "/datasets/<uuid:dataset_id>/hit-testing")
|
|
||||||
@ -369,7 +369,7 @@ class CotAgentRunner(BaseAgentRunner, ABC):
|
|||||||
return message
|
return message
|
||||||
|
|
||||||
def _organize_historic_prompt_messages(
|
def _organize_historic_prompt_messages(
|
||||||
self, current_session_messages: Optional[list[PromptMessage]] = None
|
self, current_session_messages: list[PromptMessage] = None
|
||||||
) -> list[PromptMessage]:
|
) -> list[PromptMessage]:
|
||||||
"""
|
"""
|
||||||
organize historic prompt messages
|
organize historic prompt messages
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class CotChatAgentRunner(CotAgentRunner):
|
|||||||
|
|
||||||
return SystemPromptMessage(content=system_prompt)
|
return SystemPromptMessage(content=system_prompt)
|
||||||
|
|
||||||
def _organize_user_query(self, query, prompt_messages: list[PromptMessage]) -> list[PromptMessage]:
|
def _organize_user_query(self, query, prompt_messages: list[PromptMessage] = None) -> list[PromptMessage]:
|
||||||
"""
|
"""
|
||||||
Organize user query
|
Organize user query
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from core.agent.cot_agent_runner import CotAgentRunner
|
from core.agent.cot_agent_runner import CotAgentRunner
|
||||||
from core.model_runtime.entities.message_entities import AssistantPromptMessage, PromptMessage, UserPromptMessage
|
from core.model_runtime.entities.message_entities import AssistantPromptMessage, PromptMessage, UserPromptMessage
|
||||||
@ -22,7 +21,7 @@ class CotCompletionAgentRunner(CotAgentRunner):
|
|||||||
|
|
||||||
return system_prompt
|
return system_prompt
|
||||||
|
|
||||||
def _organize_historic_prompt(self, current_session_messages: Optional[list[PromptMessage]] = None) -> str:
|
def _organize_historic_prompt(self, current_session_messages: list[PromptMessage] = None) -> str:
|
||||||
"""
|
"""
|
||||||
Organize historic prompt
|
Organize historic prompt
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Union
|
||||||
|
|
||||||
from core.agent.base_agent_runner import BaseAgentRunner
|
from core.agent.base_agent_runner import BaseAgentRunner
|
||||||
from core.app.apps.base_app_queue_manager import PublishFrom
|
from core.app.apps.base_app_queue_manager import PublishFrom
|
||||||
@ -370,7 +370,7 @@ class FunctionCallAgentRunner(BaseAgentRunner):
|
|||||||
return tool_calls
|
return tool_calls
|
||||||
|
|
||||||
def _init_system_message(
|
def _init_system_message(
|
||||||
self, prompt_template: str, prompt_messages: Optional[list[PromptMessage]] = None
|
self, prompt_template: str, prompt_messages: list[PromptMessage] = None
|
||||||
) -> list[PromptMessage]:
|
) -> list[PromptMessage]:
|
||||||
"""
|
"""
|
||||||
Initialize system message
|
Initialize system message
|
||||||
@ -385,7 +385,7 @@ class FunctionCallAgentRunner(BaseAgentRunner):
|
|||||||
|
|
||||||
return prompt_messages
|
return prompt_messages
|
||||||
|
|
||||||
def _organize_user_query(self, query, prompt_messages: list[PromptMessage]) -> list[PromptMessage]:
|
def _organize_user_query(self, query, prompt_messages: list[PromptMessage] = None) -> list[PromptMessage]:
|
||||||
"""
|
"""
|
||||||
Organize user query
|
Organize user query
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class CotAgentOutputParser:
|
|||||||
) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]:
|
) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]:
|
||||||
def parse_action(json_str):
|
def parse_action(json_str):
|
||||||
try:
|
try:
|
||||||
action = json.loads(json_str, strict=False)
|
action = json.loads(json_str)
|
||||||
action_name = None
|
action_name = None
|
||||||
action_input = None
|
action_input = None
|
||||||
|
|
||||||
@ -62,8 +62,6 @@ class CotAgentOutputParser:
|
|||||||
thought_str = "thought:"
|
thought_str = "thought:"
|
||||||
thought_idx = 0
|
thought_idx = 0
|
||||||
|
|
||||||
last_character = ""
|
|
||||||
|
|
||||||
for response in llm_response:
|
for response in llm_response:
|
||||||
if response.delta.usage:
|
if response.delta.usage:
|
||||||
usage_dict["usage"] = response.delta.usage
|
usage_dict["usage"] = response.delta.usage
|
||||||
@ -76,29 +74,27 @@ class CotAgentOutputParser:
|
|||||||
while index < len(response):
|
while index < len(response):
|
||||||
steps = 1
|
steps = 1
|
||||||
delta = response[index : index + steps]
|
delta = response[index : index + steps]
|
||||||
yield_delta = False
|
last_character = response[index - 1] if index > 0 else ""
|
||||||
|
|
||||||
if delta == "`":
|
if delta == "`":
|
||||||
last_character = delta
|
|
||||||
code_block_cache += delta
|
code_block_cache += delta
|
||||||
code_block_delimiter_count += 1
|
code_block_delimiter_count += 1
|
||||||
else:
|
else:
|
||||||
if not in_code_block:
|
if not in_code_block:
|
||||||
if code_block_delimiter_count > 0:
|
if code_block_delimiter_count > 0:
|
||||||
last_character = delta
|
|
||||||
yield code_block_cache
|
yield code_block_cache
|
||||||
code_block_cache = ""
|
code_block_cache = ""
|
||||||
else:
|
else:
|
||||||
last_character = delta
|
|
||||||
code_block_cache += delta
|
code_block_cache += delta
|
||||||
code_block_delimiter_count = 0
|
code_block_delimiter_count = 0
|
||||||
|
|
||||||
if not in_code_block and not in_json:
|
if not in_code_block and not in_json:
|
||||||
if delta.lower() == action_str[action_idx] and action_idx == 0:
|
if delta.lower() == action_str[action_idx] and action_idx == 0:
|
||||||
if last_character not in {"\n", " ", ""}:
|
if last_character not in {"\n", " ", ""}:
|
||||||
yield_delta = True
|
index += steps
|
||||||
else:
|
yield delta
|
||||||
last_character = delta
|
continue
|
||||||
|
|
||||||
action_cache += delta
|
action_cache += delta
|
||||||
action_idx += 1
|
action_idx += 1
|
||||||
if action_idx == len(action_str):
|
if action_idx == len(action_str):
|
||||||
@ -107,7 +103,6 @@ class CotAgentOutputParser:
|
|||||||
index += steps
|
index += steps
|
||||||
continue
|
continue
|
||||||
elif delta.lower() == action_str[action_idx] and action_idx > 0:
|
elif delta.lower() == action_str[action_idx] and action_idx > 0:
|
||||||
last_character = delta
|
|
||||||
action_cache += delta
|
action_cache += delta
|
||||||
action_idx += 1
|
action_idx += 1
|
||||||
if action_idx == len(action_str):
|
if action_idx == len(action_str):
|
||||||
@ -117,16 +112,16 @@ class CotAgentOutputParser:
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if action_cache:
|
if action_cache:
|
||||||
last_character = delta
|
|
||||||
yield action_cache
|
yield action_cache
|
||||||
action_cache = ""
|
action_cache = ""
|
||||||
action_idx = 0
|
action_idx = 0
|
||||||
|
|
||||||
if delta.lower() == thought_str[thought_idx] and thought_idx == 0:
|
if delta.lower() == thought_str[thought_idx] and thought_idx == 0:
|
||||||
if last_character not in {"\n", " ", ""}:
|
if last_character not in {"\n", " ", ""}:
|
||||||
yield_delta = True
|
index += steps
|
||||||
else:
|
yield delta
|
||||||
last_character = delta
|
continue
|
||||||
|
|
||||||
thought_cache += delta
|
thought_cache += delta
|
||||||
thought_idx += 1
|
thought_idx += 1
|
||||||
if thought_idx == len(thought_str):
|
if thought_idx == len(thought_str):
|
||||||
@ -135,7 +130,6 @@ class CotAgentOutputParser:
|
|||||||
index += steps
|
index += steps
|
||||||
continue
|
continue
|
||||||
elif delta.lower() == thought_str[thought_idx] and thought_idx > 0:
|
elif delta.lower() == thought_str[thought_idx] and thought_idx > 0:
|
||||||
last_character = delta
|
|
||||||
thought_cache += delta
|
thought_cache += delta
|
||||||
thought_idx += 1
|
thought_idx += 1
|
||||||
if thought_idx == len(thought_str):
|
if thought_idx == len(thought_str):
|
||||||
@ -145,20 +139,12 @@ class CotAgentOutputParser:
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if thought_cache:
|
if thought_cache:
|
||||||
last_character = delta
|
|
||||||
yield thought_cache
|
yield thought_cache
|
||||||
thought_cache = ""
|
thought_cache = ""
|
||||||
thought_idx = 0
|
thought_idx = 0
|
||||||
|
|
||||||
if yield_delta:
|
|
||||||
index += steps
|
|
||||||
last_character = delta
|
|
||||||
yield delta
|
|
||||||
continue
|
|
||||||
|
|
||||||
if code_block_delimiter_count == 3:
|
if code_block_delimiter_count == 3:
|
||||||
if in_code_block:
|
if in_code_block:
|
||||||
last_character = delta
|
|
||||||
yield from extra_json_from_code_block(code_block_cache)
|
yield from extra_json_from_code_block(code_block_cache)
|
||||||
code_block_cache = ""
|
code_block_cache = ""
|
||||||
|
|
||||||
@ -170,10 +156,8 @@ class CotAgentOutputParser:
|
|||||||
if delta == "{":
|
if delta == "{":
|
||||||
json_quote_count += 1
|
json_quote_count += 1
|
||||||
in_json = True
|
in_json = True
|
||||||
last_character = delta
|
|
||||||
json_cache += delta
|
json_cache += delta
|
||||||
elif delta == "}":
|
elif delta == "}":
|
||||||
last_character = delta
|
|
||||||
json_cache += delta
|
json_cache += delta
|
||||||
if json_quote_count > 0:
|
if json_quote_count > 0:
|
||||||
json_quote_count -= 1
|
json_quote_count -= 1
|
||||||
@ -184,19 +168,16 @@ class CotAgentOutputParser:
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
if in_json:
|
if in_json:
|
||||||
last_character = delta
|
|
||||||
json_cache += delta
|
json_cache += delta
|
||||||
|
|
||||||
if got_json:
|
if got_json:
|
||||||
got_json = False
|
got_json = False
|
||||||
last_character = delta
|
|
||||||
yield parse_action(json_cache)
|
yield parse_action(json_cache)
|
||||||
json_cache = ""
|
json_cache = ""
|
||||||
json_quote_count = 0
|
json_quote_count = 0
|
||||||
in_json = False
|
in_json = False
|
||||||
|
|
||||||
if not in_code_block and not in_json:
|
if not in_code_block and not in_json:
|
||||||
last_character = delta
|
|
||||||
yield delta.replace("`", "")
|
yield delta.replace("`", "")
|
||||||
|
|
||||||
index += steps
|
index += steps
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from flask import Flask, current_app
|
|||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
import contexts
|
import contexts
|
||||||
from constants import UUID_NIL
|
|
||||||
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
|
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
|
||||||
from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager
|
from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager
|
||||||
from core.app.apps.advanced_chat.app_runner import AdvancedChatAppRunner
|
from core.app.apps.advanced_chat.app_runner import AdvancedChatAppRunner
|
||||||
@ -114,7 +113,6 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
|
|||||||
# always enable retriever resource in debugger mode
|
# always enable retriever resource in debugger mode
|
||||||
app_config.additional_features.show_retrieve_source = True
|
app_config.additional_features.show_retrieve_source = True
|
||||||
|
|
||||||
workflow_run_id = str(uuid.uuid4())
|
|
||||||
# init application generate entity
|
# init application generate entity
|
||||||
application_generate_entity = AdvancedChatAppGenerateEntity(
|
application_generate_entity = AdvancedChatAppGenerateEntity(
|
||||||
task_id=str(uuid.uuid4()),
|
task_id=str(uuid.uuid4()),
|
||||||
@ -123,13 +121,12 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator):
|
|||||||
inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config),
|
inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config),
|
||||||
query=query,
|
query=query,
|
||||||
files=file_objs,
|
files=file_objs,
|
||||||
parent_message_id=args.get("parent_message_id") if invoke_from != InvokeFrom.SERVICE_API else UUID_NIL,
|
parent_message_id=args.get("parent_message_id"),
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
invoke_from=invoke_from,
|
invoke_from=invoke_from,
|
||||||
extras=extras,
|
extras=extras,
|
||||||
trace_manager=trace_manager,
|
trace_manager=trace_manager,
|
||||||
workflow_run_id=workflow_run_id,
|
|
||||||
)
|
)
|
||||||
contexts.tenant_id.set(application_generate_entity.app_config.tenant_id)
|
contexts.tenant_id.set(application_generate_entity.app_config.tenant_id)
|
||||||
|
|
||||||
|
|||||||
@ -149,9 +149,6 @@ class AdvancedChatAppRunner(WorkflowBasedAppRunner):
|
|||||||
SystemVariableKey.CONVERSATION_ID: self.conversation.id,
|
SystemVariableKey.CONVERSATION_ID: self.conversation.id,
|
||||||
SystemVariableKey.USER_ID: user_id,
|
SystemVariableKey.USER_ID: user_id,
|
||||||
SystemVariableKey.DIALOGUE_COUNT: conversation_dialogue_count,
|
SystemVariableKey.DIALOGUE_COUNT: conversation_dialogue_count,
|
||||||
SystemVariableKey.APP_ID: app_config.app_id,
|
|
||||||
SystemVariableKey.WORKFLOW_ID: app_config.workflow_id,
|
|
||||||
SystemVariableKey.WORKFLOW_RUN_ID: self.application_generate_entity.workflow_run_id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# init variable pool
|
# init variable pool
|
||||||
|
|||||||
@ -45,7 +45,6 @@ from core.app.entities.task_entities import (
|
|||||||
from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline
|
from core.app.task_pipeline.based_generate_task_pipeline import BasedGenerateTaskPipeline
|
||||||
from core.app.task_pipeline.message_cycle_manage import MessageCycleManage
|
from core.app.task_pipeline.message_cycle_manage import MessageCycleManage
|
||||||
from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage
|
from core.app.task_pipeline.workflow_cycle_manage import WorkflowCycleManage
|
||||||
from core.model_runtime.entities.llm_entities import LLMUsage
|
|
||||||
from core.model_runtime.utils.encoders import jsonable_encoder
|
from core.model_runtime.utils.encoders import jsonable_encoder
|
||||||
from core.ops.ops_trace_manager import TraceQueueManager
|
from core.ops.ops_trace_manager import TraceQueueManager
|
||||||
from core.workflow.enums import SystemVariableKey
|
from core.workflow.enums import SystemVariableKey
|
||||||
@ -56,7 +55,6 @@ from models.account import Account
|
|||||||
from models.model import Conversation, EndUser, Message
|
from models.model import Conversation, EndUser, Message
|
||||||
from models.workflow import (
|
from models.workflow import (
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowNodeExecution,
|
|
||||||
WorkflowRunStatus,
|
WorkflowRunStatus,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,7 +71,6 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc
|
|||||||
_workflow: Workflow
|
_workflow: Workflow
|
||||||
_user: Union[Account, EndUser]
|
_user: Union[Account, EndUser]
|
||||||
_workflow_system_variables: dict[SystemVariableKey, Any]
|
_workflow_system_variables: dict[SystemVariableKey, Any]
|
||||||
_wip_workflow_node_executions: dict[str, WorkflowNodeExecution]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -110,14 +107,9 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc
|
|||||||
SystemVariableKey.FILES: application_generate_entity.files,
|
SystemVariableKey.FILES: application_generate_entity.files,
|
||||||
SystemVariableKey.CONVERSATION_ID: conversation.id,
|
SystemVariableKey.CONVERSATION_ID: conversation.id,
|
||||||
SystemVariableKey.USER_ID: user_id,
|
SystemVariableKey.USER_ID: user_id,
|
||||||
SystemVariableKey.DIALOGUE_COUNT: conversation.dialogue_count,
|
|
||||||
SystemVariableKey.APP_ID: application_generate_entity.app_config.app_id,
|
|
||||||
SystemVariableKey.WORKFLOW_ID: workflow.id,
|
|
||||||
SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self._task_state = WorkflowTaskState()
|
self._task_state = WorkflowTaskState()
|
||||||
self._wip_workflow_node_executions = {}
|
|
||||||
|
|
||||||
self._conversation_name_generate_thread = None
|
self._conversation_name_generate_thread = None
|
||||||
|
|
||||||
@ -239,7 +231,6 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
break
|
break
|
||||||
if tts_publisher:
|
|
||||||
yield MessageAudioEndStreamResponse(audio="", task_id=task_id)
|
yield MessageAudioEndStreamResponse(audio="", task_id=task_id)
|
||||||
|
|
||||||
def _process_stream_response(
|
def _process_stream_response(
|
||||||
@ -513,10 +504,6 @@ class AdvancedChatAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCyc
|
|||||||
self._message.total_price = usage.total_price
|
self._message.total_price = usage.total_price
|
||||||
self._message.currency = usage.currency
|
self._message.currency = usage.currency
|
||||||
|
|
||||||
self._task_state.metadata["usage"] = jsonable_encoder(usage)
|
|
||||||
else:
|
|
||||||
self._task_state.metadata["usage"] = jsonable_encoder(LLMUsage.empty_usage())
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
message_was_created.send(
|
message_was_created.send(
|
||||||
|
|||||||
@ -8,7 +8,6 @@ from typing import Any, Literal, Union, overload
|
|||||||
from flask import Flask, current_app
|
from flask import Flask, current_app
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from constants import UUID_NIL
|
|
||||||
from core.app.app_config.easy_ui_based_app.model_config.converter import ModelConfigConverter
|
from core.app.app_config.easy_ui_based_app.model_config.converter import ModelConfigConverter
|
||||||
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
|
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
|
||||||
from core.app.apps.agent_chat.app_config_manager import AgentChatAppConfigManager
|
from core.app.apps.agent_chat.app_config_manager import AgentChatAppConfigManager
|
||||||
@ -128,7 +127,7 @@ class AgentChatAppGenerator(MessageBasedAppGenerator):
|
|||||||
inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config),
|
inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config),
|
||||||
query=query,
|
query=query,
|
||||||
files=file_objs,
|
files=file_objs,
|
||||||
parent_message_id=args.get("parent_message_id") if invoke_from != InvokeFrom.SERVICE_API else UUID_NIL,
|
parent_message_id=args.get("parent_message_id"),
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
invoke_from=invoke_from,
|
invoke_from=invoke_from,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ from typing import Any, Literal, Union, overload
|
|||||||
from flask import Flask, current_app
|
from flask import Flask, current_app
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from constants import UUID_NIL
|
|
||||||
from core.app.app_config.easy_ui_based_app.model_config.converter import ModelConfigConverter
|
from core.app.app_config.easy_ui_based_app.model_config.converter import ModelConfigConverter
|
||||||
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
|
from core.app.app_config.features.file_upload.manager import FileUploadConfigManager
|
||||||
from core.app.apps.base_app_queue_manager import AppQueueManager, GenerateTaskStoppedError, PublishFrom
|
from core.app.apps.base_app_queue_manager import AppQueueManager, GenerateTaskStoppedError, PublishFrom
|
||||||
@ -129,7 +128,7 @@ class ChatAppGenerator(MessageBasedAppGenerator):
|
|||||||
inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config),
|
inputs=conversation.inputs if conversation else self._get_cleaned_inputs(inputs, app_config),
|
||||||
query=query,
|
query=query,
|
||||||
files=file_objs,
|
files=file_objs,
|
||||||
parent_message_id=args.get("parent_message_id") if invoke_from != InvokeFrom.SERVICE_API else UUID_NIL,
|
parent_message_id=args.get("parent_message_id"),
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
invoke_from=invoke_from,
|
invoke_from=invoke_from,
|
||||||
|
|||||||
@ -99,7 +99,6 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
user_id = user.id if isinstance(user, Account) else user.session_id
|
user_id = user.id if isinstance(user, Account) else user.session_id
|
||||||
trace_manager = TraceQueueManager(app_model.id, user_id)
|
trace_manager = TraceQueueManager(app_model.id, user_id)
|
||||||
|
|
||||||
workflow_run_id = str(uuid.uuid4())
|
|
||||||
# init application generate entity
|
# init application generate entity
|
||||||
application_generate_entity = WorkflowAppGenerateEntity(
|
application_generate_entity = WorkflowAppGenerateEntity(
|
||||||
task_id=str(uuid.uuid4()),
|
task_id=str(uuid.uuid4()),
|
||||||
@ -111,7 +110,6 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
|||||||
invoke_from=invoke_from,
|
invoke_from=invoke_from,
|
||||||
call_depth=call_depth,
|
call_depth=call_depth,
|
||||||
trace_manager=trace_manager,
|
trace_manager=trace_manager,
|
||||||
workflow_run_id=workflow_run_id,
|
|
||||||
)
|
)
|
||||||
contexts.tenant_id.set(application_generate_entity.app_config.tenant_id)
|
contexts.tenant_id.set(application_generate_entity.app_config.tenant_id)
|
||||||
|
|
||||||
|
|||||||
@ -90,9 +90,6 @@ class WorkflowAppRunner(WorkflowBasedAppRunner):
|
|||||||
system_inputs = {
|
system_inputs = {
|
||||||
SystemVariableKey.FILES: files,
|
SystemVariableKey.FILES: files,
|
||||||
SystemVariableKey.USER_ID: user_id,
|
SystemVariableKey.USER_ID: user_id,
|
||||||
SystemVariableKey.APP_ID: app_config.app_id,
|
|
||||||
SystemVariableKey.WORKFLOW_ID: app_config.workflow_id,
|
|
||||||
SystemVariableKey.WORKFLOW_RUN_ID: self.application_generate_entity.workflow_run_id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
variable_pool = VariablePool(
|
variable_pool = VariablePool(
|
||||||
|
|||||||
@ -52,7 +52,6 @@ from models.workflow import (
|
|||||||
Workflow,
|
Workflow,
|
||||||
WorkflowAppLog,
|
WorkflowAppLog,
|
||||||
WorkflowAppLogCreatedFrom,
|
WorkflowAppLogCreatedFrom,
|
||||||
WorkflowNodeExecution,
|
|
||||||
WorkflowRun,
|
WorkflowRun,
|
||||||
WorkflowRunStatus,
|
WorkflowRunStatus,
|
||||||
)
|
)
|
||||||
@ -70,7 +69,6 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa
|
|||||||
_task_state: WorkflowTaskState
|
_task_state: WorkflowTaskState
|
||||||
_application_generate_entity: WorkflowAppGenerateEntity
|
_application_generate_entity: WorkflowAppGenerateEntity
|
||||||
_workflow_system_variables: dict[SystemVariableKey, Any]
|
_workflow_system_variables: dict[SystemVariableKey, Any]
|
||||||
_wip_workflow_node_executions: dict[str, WorkflowNodeExecution]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -99,13 +97,9 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa
|
|||||||
self._workflow_system_variables = {
|
self._workflow_system_variables = {
|
||||||
SystemVariableKey.FILES: application_generate_entity.files,
|
SystemVariableKey.FILES: application_generate_entity.files,
|
||||||
SystemVariableKey.USER_ID: user_id,
|
SystemVariableKey.USER_ID: user_id,
|
||||||
SystemVariableKey.APP_ID: application_generate_entity.app_config.app_id,
|
|
||||||
SystemVariableKey.WORKFLOW_ID: workflow.id,
|
|
||||||
SystemVariableKey.WORKFLOW_RUN_ID: application_generate_entity.workflow_run_id,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self._task_state = WorkflowTaskState()
|
self._task_state = WorkflowTaskState()
|
||||||
self._wip_workflow_node_executions = {}
|
|
||||||
|
|
||||||
def process(self) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]:
|
def process(self) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]:
|
||||||
"""
|
"""
|
||||||
@ -218,7 +212,6 @@ class WorkflowAppGenerateTaskPipeline(BasedGenerateTaskPipeline, WorkflowCycleMa
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
break
|
break
|
||||||
if tts_publisher:
|
|
||||||
yield MessageAudioEndStreamResponse(audio="", task_id=task_id)
|
yield MessageAudioEndStreamResponse(audio="", task_id=task_id)
|
||||||
|
|
||||||
def _process_stream_response(
|
def _process_stream_response(
|
||||||
|
|||||||
@ -2,9 +2,8 @@ from collections.abc import Mapping
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
|
||||||
from constants import UUID_NIL
|
|
||||||
from core.app.app_config.entities import AppConfig, EasyUIBasedAppConfig, WorkflowUIBasedAppConfig
|
from core.app.app_config.entities import AppConfig, EasyUIBasedAppConfig, WorkflowUIBasedAppConfig
|
||||||
from core.entities.provider_configuration import ProviderModelBundle
|
from core.entities.provider_configuration import ProviderModelBundle
|
||||||
from core.file.file_obj import FileVar
|
from core.file.file_obj import FileVar
|
||||||
@ -117,36 +116,13 @@ class EasyUIBasedAppGenerateEntity(AppGenerateEntity):
|
|||||||
model_config = ConfigDict(protected_namespaces=())
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
|
|
||||||
|
|
||||||
class ConversationAppGenerateEntity(AppGenerateEntity):
|
class ChatAppGenerateEntity(EasyUIBasedAppGenerateEntity):
|
||||||
"""
|
|
||||||
Base entity for conversation-based app generation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
conversation_id: Optional[str] = None
|
|
||||||
parent_message_id: Optional[str] = Field(
|
|
||||||
default=None,
|
|
||||||
description=(
|
|
||||||
"Starting from v0.9.0, parent_message_id is used to support message regeneration for internal chat API."
|
|
||||||
"For service API, we need to ensure its forward compatibility, "
|
|
||||||
"so passing in the parent_message_id as request arg is not supported for now. "
|
|
||||||
"It needs to be set to UUID_NIL so that the subsequent processing will treat it as legacy messages."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@field_validator("parent_message_id")
|
|
||||||
@classmethod
|
|
||||||
def validate_parent_message_id(cls, v, info: ValidationInfo):
|
|
||||||
if info.data.get("invoke_from") == InvokeFrom.SERVICE_API and v != UUID_NIL:
|
|
||||||
raise ValueError("parent_message_id should be UUID_NIL for service API")
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class ChatAppGenerateEntity(ConversationAppGenerateEntity, EasyUIBasedAppGenerateEntity):
|
|
||||||
"""
|
"""
|
||||||
Chat Application Generate Entity.
|
Chat Application Generate Entity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
conversation_id: Optional[str] = None
|
||||||
|
parent_message_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class CompletionAppGenerateEntity(EasyUIBasedAppGenerateEntity):
|
class CompletionAppGenerateEntity(EasyUIBasedAppGenerateEntity):
|
||||||
@ -157,15 +133,16 @@ class CompletionAppGenerateEntity(EasyUIBasedAppGenerateEntity):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AgentChatAppGenerateEntity(ConversationAppGenerateEntity, EasyUIBasedAppGenerateEntity):
|
class AgentChatAppGenerateEntity(EasyUIBasedAppGenerateEntity):
|
||||||
"""
|
"""
|
||||||
Agent Chat Application Generate Entity.
|
Agent Chat Application Generate Entity.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
conversation_id: Optional[str] = None
|
||||||
|
parent_message_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class AdvancedChatAppGenerateEntity(ConversationAppGenerateEntity):
|
class AdvancedChatAppGenerateEntity(AppGenerateEntity):
|
||||||
"""
|
"""
|
||||||
Advanced Chat Application Generate Entity.
|
Advanced Chat Application Generate Entity.
|
||||||
"""
|
"""
|
||||||
@ -173,7 +150,8 @@ class AdvancedChatAppGenerateEntity(ConversationAppGenerateEntity):
|
|||||||
# app config
|
# app config
|
||||||
app_config: WorkflowUIBasedAppConfig
|
app_config: WorkflowUIBasedAppConfig
|
||||||
|
|
||||||
workflow_run_id: Optional[str] = None
|
conversation_id: Optional[str] = None
|
||||||
|
parent_message_id: Optional[str] = None
|
||||||
query: str
|
query: str
|
||||||
|
|
||||||
class SingleIterationRunEntity(BaseModel):
|
class SingleIterationRunEntity(BaseModel):
|
||||||
@ -194,7 +172,6 @@ class WorkflowAppGenerateEntity(AppGenerateEntity):
|
|||||||
|
|
||||||
# app config
|
# app config
|
||||||
app_config: WorkflowUIBasedAppConfig
|
app_config: WorkflowUIBasedAppConfig
|
||||||
workflow_run_id: Optional[str] = None
|
|
||||||
|
|
||||||
class SingleIterationRunEntity(BaseModel):
|
class SingleIterationRunEntity(BaseModel):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
class VariableError(ValueError):
|
class VariableError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -248,7 +248,6 @@ class EasyUIBasedGenerateTaskPipeline(BasedGenerateTaskPipeline, MessageCycleMan
|
|||||||
else:
|
else:
|
||||||
start_listener_time = time.time()
|
start_listener_time = time.time()
|
||||||
yield MessageAudioStreamResponse(audio=audio.audio, task_id=task_id)
|
yield MessageAudioStreamResponse(audio=audio.audio, task_id=task_id)
|
||||||
if publisher:
|
|
||||||
yield MessageAudioEndStreamResponse(audio="", task_id=task_id)
|
yield MessageAudioEndStreamResponse(audio="", task_id=task_id)
|
||||||
|
|
||||||
def _process_stream_response(
|
def _process_stream_response(
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import logging
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from flask import Flask, current_app
|
from flask import Flask, current_app
|
||||||
|
|
||||||
from configs import dify_config
|
|
||||||
from core.app.entities.app_invoke_entities import (
|
from core.app.entities.app_invoke_entities import (
|
||||||
AdvancedChatAppGenerateEntity,
|
AdvancedChatAppGenerateEntity,
|
||||||
AgentChatAppGenerateEntity,
|
AgentChatAppGenerateEntity,
|
||||||
@ -84,9 +82,7 @@ class MessageCycleManage:
|
|||||||
try:
|
try:
|
||||||
name = LLMGenerator.generate_conversation_name(app_model.tenant_id, query)
|
name = LLMGenerator.generate_conversation_name(app_model.tenant_id, query)
|
||||||
conversation.name = name
|
conversation.name = name
|
||||||
except Exception as e:
|
except:
|
||||||
if dify_config.DEBUG:
|
|
||||||
logging.exception(f"generate conversation name failed: {e}")
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
db.session.merge(conversation)
|
db.session.merge(conversation)
|
||||||
|
|||||||
@ -57,7 +57,6 @@ class WorkflowCycleManage:
|
|||||||
_user: Union[Account, EndUser]
|
_user: Union[Account, EndUser]
|
||||||
_task_state: WorkflowTaskState
|
_task_state: WorkflowTaskState
|
||||||
_workflow_system_variables: dict[SystemVariableKey, Any]
|
_workflow_system_variables: dict[SystemVariableKey, Any]
|
||||||
_wip_workflow_node_executions: dict[str, WorkflowNodeExecution]
|
|
||||||
|
|
||||||
def _handle_workflow_run_start(self) -> WorkflowRun:
|
def _handle_workflow_run_start(self) -> WorkflowRun:
|
||||||
max_sequence = (
|
max_sequence = (
|
||||||
@ -86,9 +85,6 @@ class WorkflowCycleManage:
|
|||||||
|
|
||||||
# init workflow run
|
# init workflow run
|
||||||
workflow_run = WorkflowRun()
|
workflow_run = WorkflowRun()
|
||||||
workflow_run_id = self._workflow_system_variables[SystemVariableKey.WORKFLOW_RUN_ID]
|
|
||||||
if workflow_run_id:
|
|
||||||
workflow_run.id = workflow_run_id
|
|
||||||
workflow_run.tenant_id = self._workflow.tenant_id
|
workflow_run.tenant_id = self._workflow.tenant_id
|
||||||
workflow_run.app_id = self._workflow.app_id
|
workflow_run.app_id = self._workflow.app_id
|
||||||
workflow_run.sequence_number = new_sequence_number
|
workflow_run.sequence_number = new_sequence_number
|
||||||
@ -252,8 +248,6 @@ class WorkflowCycleManage:
|
|||||||
db.session.refresh(workflow_node_execution)
|
db.session.refresh(workflow_node_execution)
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
|
||||||
self._wip_workflow_node_executions[workflow_node_execution.node_execution_id] = workflow_node_execution
|
|
||||||
|
|
||||||
return workflow_node_execution
|
return workflow_node_execution
|
||||||
|
|
||||||
def _handle_workflow_node_execution_success(self, event: QueueNodeSucceededEvent) -> WorkflowNodeExecution:
|
def _handle_workflow_node_execution_success(self, event: QueueNodeSucceededEvent) -> WorkflowNodeExecution:
|
||||||
@ -266,36 +260,20 @@ class WorkflowCycleManage:
|
|||||||
|
|
||||||
inputs = WorkflowEntry.handle_special_values(event.inputs)
|
inputs = WorkflowEntry.handle_special_values(event.inputs)
|
||||||
outputs = WorkflowEntry.handle_special_values(event.outputs)
|
outputs = WorkflowEntry.handle_special_values(event.outputs)
|
||||||
execution_metadata = (
|
|
||||||
json.dumps(jsonable_encoder(event.execution_metadata)) if event.execution_metadata else None
|
|
||||||
)
|
|
||||||
finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
||||||
elapsed_time = (finished_at - event.start_at).total_seconds()
|
|
||||||
|
|
||||||
db.session.query(WorkflowNodeExecution).filter(WorkflowNodeExecution.id == workflow_node_execution.id).update(
|
|
||||||
{
|
|
||||||
WorkflowNodeExecution.status: WorkflowNodeExecutionStatus.SUCCEEDED.value,
|
|
||||||
WorkflowNodeExecution.inputs: json.dumps(inputs) if inputs else None,
|
|
||||||
WorkflowNodeExecution.process_data: json.dumps(event.process_data) if event.process_data else None,
|
|
||||||
WorkflowNodeExecution.outputs: json.dumps(outputs) if outputs else None,
|
|
||||||
WorkflowNodeExecution.execution_metadata: execution_metadata,
|
|
||||||
WorkflowNodeExecution.finished_at: finished_at,
|
|
||||||
WorkflowNodeExecution.elapsed_time: elapsed_time,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
db.session.close()
|
|
||||||
|
|
||||||
workflow_node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED.value
|
workflow_node_execution.status = WorkflowNodeExecutionStatus.SUCCEEDED.value
|
||||||
workflow_node_execution.inputs = json.dumps(inputs) if inputs else None
|
workflow_node_execution.inputs = json.dumps(inputs) if inputs else None
|
||||||
workflow_node_execution.process_data = json.dumps(event.process_data) if event.process_data else None
|
workflow_node_execution.process_data = json.dumps(event.process_data) if event.process_data else None
|
||||||
workflow_node_execution.outputs = json.dumps(outputs) if outputs else None
|
workflow_node_execution.outputs = json.dumps(outputs) if outputs else None
|
||||||
workflow_node_execution.execution_metadata = execution_metadata
|
workflow_node_execution.execution_metadata = (
|
||||||
workflow_node_execution.finished_at = finished_at
|
json.dumps(jsonable_encoder(event.execution_metadata)) if event.execution_metadata else None
|
||||||
workflow_node_execution.elapsed_time = elapsed_time
|
)
|
||||||
|
workflow_node_execution.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
|
workflow_node_execution.elapsed_time = (workflow_node_execution.finished_at - event.start_at).total_seconds()
|
||||||
|
|
||||||
self._wip_workflow_node_executions.pop(workflow_node_execution.node_execution_id)
|
db.session.commit()
|
||||||
|
db.session.refresh(workflow_node_execution)
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
return workflow_node_execution
|
return workflow_node_execution
|
||||||
|
|
||||||
@ -309,33 +287,18 @@ class WorkflowCycleManage:
|
|||||||
|
|
||||||
inputs = WorkflowEntry.handle_special_values(event.inputs)
|
inputs = WorkflowEntry.handle_special_values(event.inputs)
|
||||||
outputs = WorkflowEntry.handle_special_values(event.outputs)
|
outputs = WorkflowEntry.handle_special_values(event.outputs)
|
||||||
finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
||||||
elapsed_time = (finished_at - event.start_at).total_seconds()
|
|
||||||
|
|
||||||
db.session.query(WorkflowNodeExecution).filter(WorkflowNodeExecution.id == workflow_node_execution.id).update(
|
|
||||||
{
|
|
||||||
WorkflowNodeExecution.status: WorkflowNodeExecutionStatus.FAILED.value,
|
|
||||||
WorkflowNodeExecution.error: event.error,
|
|
||||||
WorkflowNodeExecution.inputs: json.dumps(inputs) if inputs else None,
|
|
||||||
WorkflowNodeExecution.process_data: json.dumps(event.process_data) if event.process_data else None,
|
|
||||||
WorkflowNodeExecution.outputs: json.dumps(outputs) if outputs else None,
|
|
||||||
WorkflowNodeExecution.finished_at: finished_at,
|
|
||||||
WorkflowNodeExecution.elapsed_time: elapsed_time,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
db.session.close()
|
|
||||||
|
|
||||||
workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value
|
workflow_node_execution.status = WorkflowNodeExecutionStatus.FAILED.value
|
||||||
workflow_node_execution.error = event.error
|
workflow_node_execution.error = event.error
|
||||||
|
workflow_node_execution.finished_at = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
workflow_node_execution.inputs = json.dumps(inputs) if inputs else None
|
workflow_node_execution.inputs = json.dumps(inputs) if inputs else None
|
||||||
workflow_node_execution.process_data = json.dumps(event.process_data) if event.process_data else None
|
workflow_node_execution.process_data = json.dumps(event.process_data) if event.process_data else None
|
||||||
workflow_node_execution.outputs = json.dumps(outputs) if outputs else None
|
workflow_node_execution.outputs = json.dumps(outputs) if outputs else None
|
||||||
workflow_node_execution.finished_at = finished_at
|
workflow_node_execution.elapsed_time = (workflow_node_execution.finished_at - event.start_at).total_seconds()
|
||||||
workflow_node_execution.elapsed_time = elapsed_time
|
|
||||||
|
|
||||||
self._wip_workflow_node_executions.pop(workflow_node_execution.node_execution_id)
|
db.session.commit()
|
||||||
|
db.session.refresh(workflow_node_execution)
|
||||||
|
db.session.close()
|
||||||
|
|
||||||
return workflow_node_execution
|
return workflow_node_execution
|
||||||
|
|
||||||
@ -712,7 +675,17 @@ class WorkflowCycleManage:
|
|||||||
:param node_execution_id: workflow node execution id
|
:param node_execution_id: workflow node execution id
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
workflow_node_execution = self._wip_workflow_node_executions.get(node_execution_id)
|
workflow_node_execution = (
|
||||||
|
db.session.query(WorkflowNodeExecution)
|
||||||
|
.filter(
|
||||||
|
WorkflowNodeExecution.tenant_id == self._application_generate_entity.app_config.tenant_id,
|
||||||
|
WorkflowNodeExecution.app_id == self._application_generate_entity.app_config.app_id,
|
||||||
|
WorkflowNodeExecution.workflow_id == self._workflow.id,
|
||||||
|
WorkflowNodeExecution.triggered_from == WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value,
|
||||||
|
WorkflowNodeExecution.node_execution_id == node_execution_id,
|
||||||
|
)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
if not workflow_node_execution:
|
if not workflow_node_execution:
|
||||||
raise Exception(f"Workflow node execution not found: {node_execution_id}")
|
raise Exception(f"Workflow node execution not found: {node_execution_id}")
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import os
|
||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from typing import Any, Optional, TextIO, Union
|
from typing import Any, Optional, TextIO, Union
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from configs import dify_config
|
|
||||||
from core.ops.entities.trace_entity import TraceTaskName
|
from core.ops.entities.trace_entity import TraceTaskName
|
||||||
from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
|
from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
|
||||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
@ -50,7 +50,6 @@ class DifyAgentCallbackHandler(BaseModel):
|
|||||||
tool_inputs: Mapping[str, Any],
|
tool_inputs: Mapping[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Do nothing."""
|
"""Do nothing."""
|
||||||
if dify_config.DEBUG:
|
|
||||||
print_text("\n[on_tool_start] ToolCall:" + tool_name + "\n" + str(tool_inputs) + "\n", color=self.color)
|
print_text("\n[on_tool_start] ToolCall:" + tool_name + "\n" + str(tool_inputs) + "\n", color=self.color)
|
||||||
|
|
||||||
def on_tool_end(
|
def on_tool_end(
|
||||||
@ -63,7 +62,6 @@ class DifyAgentCallbackHandler(BaseModel):
|
|||||||
trace_manager: Optional[TraceQueueManager] = None,
|
trace_manager: Optional[TraceQueueManager] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""If not the final action, print out observation."""
|
"""If not the final action, print out observation."""
|
||||||
if dify_config.DEBUG:
|
|
||||||
print_text("\n[on_tool_end]\n", color=self.color)
|
print_text("\n[on_tool_end]\n", color=self.color)
|
||||||
print_text("Tool: " + tool_name + "\n", color=self.color)
|
print_text("Tool: " + tool_name + "\n", color=self.color)
|
||||||
print_text("Inputs: " + str(tool_inputs) + "\n", color=self.color)
|
print_text("Inputs: " + str(tool_inputs) + "\n", color=self.color)
|
||||||
@ -84,12 +82,10 @@ class DifyAgentCallbackHandler(BaseModel):
|
|||||||
|
|
||||||
def on_tool_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None:
|
def on_tool_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None:
|
||||||
"""Do nothing."""
|
"""Do nothing."""
|
||||||
if dify_config.DEBUG:
|
|
||||||
print_text("\n[on_tool_error] Error: " + str(error) + "\n", color="red")
|
print_text("\n[on_tool_error] Error: " + str(error) + "\n", color="red")
|
||||||
|
|
||||||
def on_agent_start(self, thought: str) -> None:
|
def on_agent_start(self, thought: str) -> None:
|
||||||
"""Run on agent start."""
|
"""Run on agent start."""
|
||||||
if dify_config.DEBUG:
|
|
||||||
if thought:
|
if thought:
|
||||||
print_text(
|
print_text(
|
||||||
"\n[on_agent_start] \nCurrent Loop: " + str(self.current_loop) + "\nThought: " + thought + "\n",
|
"\n[on_agent_start] \nCurrent Loop: " + str(self.current_loop) + "\nThought: " + thought + "\n",
|
||||||
@ -100,7 +96,6 @@ class DifyAgentCallbackHandler(BaseModel):
|
|||||||
|
|
||||||
def on_agent_finish(self, color: Optional[str] = None, **kwargs: Any) -> None:
|
def on_agent_finish(self, color: Optional[str] = None, **kwargs: Any) -> None:
|
||||||
"""Run on agent end."""
|
"""Run on agent end."""
|
||||||
if dify_config.DEBUG:
|
|
||||||
print_text("\n[on_agent_finish]\n Loop: " + str(self.current_loop) + "\n", color=self.color)
|
print_text("\n[on_agent_finish]\n Loop: " + str(self.current_loop) + "\n", color=self.color)
|
||||||
|
|
||||||
self.current_loop += 1
|
self.current_loop += 1
|
||||||
@ -108,9 +103,9 @@ class DifyAgentCallbackHandler(BaseModel):
|
|||||||
@property
|
@property
|
||||||
def ignore_agent(self) -> bool:
|
def ignore_agent(self) -> bool:
|
||||||
"""Whether to ignore agent callbacks."""
|
"""Whether to ignore agent callbacks."""
|
||||||
return not dify_config.DEBUG
|
return not os.environ.get("DEBUG") or os.environ.get("DEBUG").lower() != "true"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ignore_chat_model(self) -> bool:
|
def ignore_chat_model(self) -> bool:
|
||||||
"""Whether to ignore chat model callbacks."""
|
"""Whether to ignore chat model callbacks."""
|
||||||
return not dify_config.DEBUG
|
return not os.environ.get("DEBUG") or os.environ.get("DEBUG").lower() != "true"
|
||||||
|
|||||||
@ -44,6 +44,7 @@ class DatasetIndexToolCallbackHandler:
|
|||||||
DocumentSegment.index_node_id == document.metadata["doc_id"]
|
DocumentSegment.index_node_id == document.metadata["doc_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# if 'dataset_id' in document.metadata:
|
||||||
if "dataset_id" in document.metadata:
|
if "dataset_id" in document.metadata:
|
||||||
query = query.filter(DocumentSegment.dataset_id == document.metadata["dataset_id"])
|
query = query.filter(DocumentSegment.dataset_id == document.metadata["dataset_id"])
|
||||||
|
|
||||||
|
|||||||
@ -5,12 +5,11 @@ from typing import Optional, cast
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from configs import dify_config
|
from core.embedding.embedding_constant import EmbeddingInputType
|
||||||
from core.entities.embedding_type import EmbeddingInputType
|
|
||||||
from core.model_manager import ModelInstance
|
from core.model_manager import ModelInstance
|
||||||
from core.model_runtime.entities.model_entities import ModelPropertyKey
|
from core.model_runtime.entities.model_entities import ModelPropertyKey
|
||||||
from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel
|
from core.model_runtime.model_providers.__base.text_embedding_model import TextEmbeddingModel
|
||||||
from core.rag.embedding.embedding_base import Embeddings
|
from core.rag.datasource.entity.embedding import Embeddings
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from extensions.ext_redis import redis_client
|
from extensions.ext_redis import redis_client
|
||||||
from libs import helper
|
from libs import helper
|
||||||
@ -111,8 +110,6 @@ class CacheEmbedding(Embeddings):
|
|||||||
embedding_results = embedding_result.embeddings[0]
|
embedding_results = embedding_result.embeddings[0]
|
||||||
embedding_results = (embedding_results / np.linalg.norm(embedding_results)).tolist()
|
embedding_results = (embedding_results / np.linalg.norm(embedding_results)).tolist()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if dify_config.DEBUG:
|
|
||||||
logging.exception(f"Failed to embed query text: {ex}")
|
|
||||||
raise ex
|
raise ex
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -125,8 +122,6 @@ class CacheEmbedding(Embeddings):
|
|||||||
encoded_str = encoded_vector.decode("utf-8")
|
encoded_str = encoded_vector.decode("utf-8")
|
||||||
redis_client.setex(embedding_cache_key, 600, encoded_str)
|
redis_client.setex(embedding_cache_key, 600, encoded_str)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
if dify_config.DEBUG:
|
|
||||||
logging.exception("Failed to add embedding to redis %s", ex)
|
logging.exception("Failed to add embedding to redis %s", ex)
|
||||||
raise ex
|
|
||||||
|
|
||||||
return embedding_results
|
return embedding_results
|
||||||
@ -119,7 +119,7 @@ class ProviderConfiguration(BaseModel):
|
|||||||
credentials = model_configuration.credentials
|
credentials = model_configuration.credentials
|
||||||
break
|
break
|
||||||
|
|
||||||
if not credentials and self.custom_configuration.provider:
|
if self.custom_configuration.provider:
|
||||||
credentials = self.custom_configuration.provider.credentials
|
credentials = self.custom_configuration.provider.credentials
|
||||||
|
|
||||||
return credentials
|
return credentials
|
||||||
|
|||||||
@ -198,8 +198,6 @@ class MessageFileParser:
|
|||||||
if "amazonaws.com" not in parsed_url.netloc:
|
if "amazonaws.com" not in parsed_url.netloc:
|
||||||
return False
|
return False
|
||||||
query_params = parse_qs(parsed_url.query)
|
query_params = parse_qs(parsed_url.query)
|
||||||
|
|
||||||
def check_presign_v2(query_params):
|
|
||||||
required_params = ["Signature", "Expires"]
|
required_params = ["Signature", "Expires"]
|
||||||
for param in required_params:
|
for param in required_params:
|
||||||
if param not in query_params:
|
if param not in query_params:
|
||||||
@ -209,23 +207,7 @@ class MessageFileParser:
|
|||||||
signature = query_params["Signature"][0]
|
signature = query_params["Signature"][0]
|
||||||
if not re.match(r"^[A-Za-z0-9+/]+={0,2}$", signature):
|
if not re.match(r"^[A-Za-z0-9+/]+={0,2}$", signature):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_presign_v4(query_params):
|
|
||||||
required_params = ["X-Amz-Signature", "X-Amz-Expires"]
|
|
||||||
for param in required_params:
|
|
||||||
if param not in query_params:
|
|
||||||
return False
|
|
||||||
if not query_params["X-Amz-Expires"][0].isdigit():
|
|
||||||
return False
|
|
||||||
signature = query_params["X-Amz-Signature"][0]
|
|
||||||
if not re.match(r"^[A-Za-z0-9+/]+={0,2}$", signature):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
return check_presign_v4(query_params) or check_presign_v2(query_params)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@ -211,9 +211,9 @@ class IndexingRunner:
|
|||||||
tenant_id: str,
|
tenant_id: str,
|
||||||
extract_settings: list[ExtractSetting],
|
extract_settings: list[ExtractSetting],
|
||||||
tmp_processing_rule: dict,
|
tmp_processing_rule: dict,
|
||||||
doc_form: Optional[str] = None,
|
doc_form: str = None,
|
||||||
doc_language: str = "English",
|
doc_language: str = "English",
|
||||||
dataset_id: Optional[str] = None,
|
dataset_id: str = None,
|
||||||
indexing_technique: str = "economy",
|
indexing_technique: str = "economy",
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -58,11 +58,7 @@ class TokenBufferMemory:
|
|||||||
# instead of all messages from the conversation, we only need to extract messages
|
# instead of all messages from the conversation, we only need to extract messages
|
||||||
# that belong to the thread of last message
|
# that belong to the thread of last message
|
||||||
thread_messages = extract_thread_messages(messages)
|
thread_messages = extract_thread_messages(messages)
|
||||||
|
|
||||||
# for newly created message, its answer is temporarily empty, we don't need to add it to memory
|
|
||||||
if thread_messages and not thread_messages[0].answer:
|
|
||||||
thread_messages.pop(0)
|
thread_messages.pop(0)
|
||||||
|
|
||||||
messages = list(reversed(thread_messages))
|
messages = list(reversed(thread_messages))
|
||||||
|
|
||||||
message_file_parser = MessageFileParser(tenant_id=app_record.tenant_id, app_id=app_record.id)
|
message_file_parser = MessageFileParser(tenant_id=app_record.tenant_id, app_id=app_record.id)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import os
|
|||||||
from collections.abc import Callable, Generator, Sequence
|
from collections.abc import Callable, Generator, Sequence
|
||||||
from typing import IO, Optional, Union, cast
|
from typing import IO, Optional, Union, cast
|
||||||
|
|
||||||
from core.entities.embedding_type import EmbeddingInputType
|
from core.embedding.embedding_constant import EmbeddingInputType
|
||||||
from core.entities.provider_configuration import ProviderConfiguration, ProviderModelBundle
|
from core.entities.provider_configuration import ProviderConfiguration, ProviderModelBundle
|
||||||
from core.entities.provider_entities import ModelLoadBalancingConfiguration
|
from core.entities.provider_entities import ModelLoadBalancingConfiguration
|
||||||
from core.errors.error import ProviderTokenNotInitError
|
from core.errors.error import ProviderTokenNotInitError
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk
|
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk
|
||||||
@ -14,7 +13,7 @@ _TEXT_COLOR_MAPPING = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Callback(ABC):
|
class Callback:
|
||||||
"""
|
"""
|
||||||
Base class for callbacks.
|
Base class for callbacks.
|
||||||
Only for LLM.
|
Only for LLM.
|
||||||
@ -22,7 +21,6 @@ class Callback(ABC):
|
|||||||
|
|
||||||
raise_error: bool = False
|
raise_error: bool = False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def on_before_invoke(
|
def on_before_invoke(
|
||||||
self,
|
self,
|
||||||
llm_instance: AIModel,
|
llm_instance: AIModel,
|
||||||
@ -50,7 +48,6 @@ class Callback(ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def on_new_chunk(
|
def on_new_chunk(
|
||||||
self,
|
self,
|
||||||
llm_instance: AIModel,
|
llm_instance: AIModel,
|
||||||
@ -80,7 +77,6 @@ class Callback(ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def on_after_invoke(
|
def on_after_invoke(
|
||||||
self,
|
self,
|
||||||
llm_instance: AIModel,
|
llm_instance: AIModel,
|
||||||
@ -110,7 +106,6 @@ class Callback(ABC):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def on_invoke_error(
|
def on_invoke_error(
|
||||||
self,
|
self,
|
||||||
llm_instance: AIModel,
|
llm_instance: AIModel,
|
||||||
|
|||||||
@ -1,310 +0,0 @@
|
|||||||
## Custom Integration of Pre-defined Models
|
|
||||||
|
|
||||||
### Introduction
|
|
||||||
|
|
||||||
After completing the vendors integration, the next step is to connect the vendor's models. To illustrate the entire connection process, we will use Xinference as an example to demonstrate a complete vendor integration.
|
|
||||||
|
|
||||||
It is important to note that for custom models, each model connection requires a complete vendor credential.
|
|
||||||
|
|
||||||
Unlike pre-defined models, a custom vendor integration always includes the following two parameters, which do not need to be defined in the vendor YAML file.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
As mentioned earlier, vendors do not need to implement validate_provider_credential. The runtime will automatically call the corresponding model layer's validate_credentials to validate the credentials based on the model type and name selected by the user.
|
|
||||||
|
|
||||||
### Writing the Vendor YAML
|
|
||||||
|
|
||||||
First, we need to identify the types of models supported by the vendor we are integrating.
|
|
||||||
|
|
||||||
Currently supported model types are as follows:
|
|
||||||
|
|
||||||
- `llm` Text Generation Models
|
|
||||||
|
|
||||||
- `text_embedding` Text Embedding Models
|
|
||||||
|
|
||||||
- `rerank` Rerank Models
|
|
||||||
|
|
||||||
- `speech2text` Speech-to-Text
|
|
||||||
|
|
||||||
- `tts` Text-to-Speech
|
|
||||||
|
|
||||||
- `moderation` Moderation
|
|
||||||
|
|
||||||
Xinference supports LLM, Text Embedding, and Rerank. So we will start by writing xinference.yaml.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
provider: xinference #Define the vendor identifier
|
|
||||||
label: # Vendor display name, supports both en_US (English) and zh_Hans (Simplified Chinese). If zh_Hans is not set, it will use en_US by default.
|
|
||||||
en_US: Xorbits Inference
|
|
||||||
icon_small: # Small icon, refer to other vendors' icons stored in the _assets directory within the vendor implementation directory; follows the same language policy as the label
|
|
||||||
en_US: icon_s_en.svg
|
|
||||||
icon_large: # Large icon
|
|
||||||
en_US: icon_l_en.svg
|
|
||||||
help: # Help information
|
|
||||||
title:
|
|
||||||
en_US: How to deploy Xinference
|
|
||||||
zh_Hans: 如何部署 Xinference
|
|
||||||
url:
|
|
||||||
en_US: https://github.com/xorbitsai/inference
|
|
||||||
supported_model_types: # Supported model types. Xinference supports LLM, Text Embedding, and Rerank
|
|
||||||
- llm
|
|
||||||
- text-embedding
|
|
||||||
- rerank
|
|
||||||
configurate_methods: # Since Xinference is a locally deployed vendor with no predefined models, users need to deploy whatever models they need according to Xinference documentation. Thus, it only supports custom models.
|
|
||||||
- customizable-model
|
|
||||||
provider_credential_schema:
|
|
||||||
credential_form_schemas:
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Then, we need to determine what credentials are required to define a model in Xinference.
|
|
||||||
|
|
||||||
- Since it supports three different types of models, we need to specify the model_type to denote the model type. Here is how we can define it:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
provider_credential_schema:
|
|
||||||
credential_form_schemas:
|
|
||||||
- variable: model_type
|
|
||||||
type: select
|
|
||||||
label:
|
|
||||||
en_US: Model type
|
|
||||||
zh_Hans: 模型类型
|
|
||||||
required: true
|
|
||||||
options:
|
|
||||||
- value: text-generation
|
|
||||||
label:
|
|
||||||
en_US: Language Model
|
|
||||||
zh_Hans: 语言模型
|
|
||||||
- value: embeddings
|
|
||||||
label:
|
|
||||||
en_US: Text Embedding
|
|
||||||
- value: reranking
|
|
||||||
label:
|
|
||||||
en_US: Rerank
|
|
||||||
```
|
|
||||||
|
|
||||||
- Next, each model has its own model_name, so we need to define that here:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- variable: model_name
|
|
||||||
type: text-input
|
|
||||||
label:
|
|
||||||
en_US: Model name
|
|
||||||
zh_Hans: 模型名称
|
|
||||||
required: true
|
|
||||||
placeholder:
|
|
||||||
zh_Hans: 填写模型名称
|
|
||||||
en_US: Input model name
|
|
||||||
```
|
|
||||||
|
|
||||||
- Specify the Xinference local deployment address:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- variable: server_url
|
|
||||||
label:
|
|
||||||
zh_Hans: 服务器URL
|
|
||||||
en_US: Server url
|
|
||||||
type: text-input
|
|
||||||
required: true
|
|
||||||
placeholder:
|
|
||||||
zh_Hans: 在此输入Xinference的服务器地址,如 https://example.com/xxx
|
|
||||||
en_US: Enter the url of your Xinference, for example https://example.com/xxx
|
|
||||||
```
|
|
||||||
|
|
||||||
- Each model has a unique model_uid, so we also need to define that here:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- variable: model_uid
|
|
||||||
label:
|
|
||||||
zh_Hans: 模型UID
|
|
||||||
en_US: Model uid
|
|
||||||
type: text-input
|
|
||||||
required: true
|
|
||||||
placeholder:
|
|
||||||
zh_Hans: 在此输入您的Model UID
|
|
||||||
en_US: Enter the model uid
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, we have completed the basic definition of the vendor.
|
|
||||||
|
|
||||||
### Writing the Model Code
|
|
||||||
|
|
||||||
Next, let's take the `llm` type as an example and write `xinference.llm.llm.py`.
|
|
||||||
|
|
||||||
In `llm.py`, create a Xinference LLM class, we name it `XinferenceAILargeLanguageModel` (this can be arbitrary), inheriting from the `__base.large_language_model.LargeLanguageModel` base class, and implement the following methods:
|
|
||||||
|
|
||||||
- LLM Invocation
|
|
||||||
|
|
||||||
Implement the core method for LLM invocation, supporting both stream and synchronous responses.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def _invoke(self, model: str, credentials: dict,
|
|
||||||
prompt_messages: list[PromptMessage], model_parameters: dict,
|
|
||||||
tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
|
|
||||||
stream: bool = True, user: Optional[str] = None) \
|
|
||||||
-> Union[LLMResult, Generator]:
|
|
||||||
"""
|
|
||||||
Invoke large language model
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: model credentials
|
|
||||||
:param prompt_messages: prompt messages
|
|
||||||
:param model_parameters: model parameters
|
|
||||||
:param tools: tools for tool usage
|
|
||||||
:param stop: stop words
|
|
||||||
:param stream: is the response a stream
|
|
||||||
:param user: unique user id
|
|
||||||
:return: full response or stream response chunk generator result
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
When implementing, ensure to use two functions to return data separately for synchronous and stream responses. This is important because Python treats functions containing the `yield` keyword as generator functions, mandating them to return `Generator` types. Here’s an example (note that the example uses simplified parameters; in real implementation, use the parameter list as defined above):
|
|
||||||
|
|
||||||
```python
|
|
||||||
def _invoke(self, stream: bool, **kwargs) \
|
|
||||||
-> Union[LLMResult, Generator]:
|
|
||||||
if stream:
|
|
||||||
return self._handle_stream_response(**kwargs)
|
|
||||||
return self._handle_sync_response(**kwargs)
|
|
||||||
|
|
||||||
def _handle_stream_response(self, **kwargs) -> Generator:
|
|
||||||
for chunk in response:
|
|
||||||
yield chunk
|
|
||||||
def _handle_sync_response(self, **kwargs) -> LLMResult:
|
|
||||||
return LLMResult(**response)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Pre-compute Input Tokens
|
|
||||||
|
|
||||||
If the model does not provide an interface for pre-computing tokens, you can return 0 directly.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],tools: Optional[list[PromptMessageTool]] = None) -> int:
|
|
||||||
"""
|
|
||||||
Get number of tokens for given prompt messages
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: model credentials
|
|
||||||
:param prompt_messages: prompt messages
|
|
||||||
:param tools: tools for tool usage
|
|
||||||
:return: token count
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Sometimes, you might not want to return 0 directly. In such cases, you can use `self._get_num_tokens_by_gpt2(text: str)` to get pre-computed tokens. This method is provided by the `AIModel` base class, and it uses GPT2's Tokenizer for calculation. However, it should be noted that this is only a substitute and may not be fully accurate.
|
|
||||||
|
|
||||||
- Model Credentials Validation
|
|
||||||
|
|
||||||
Similar to vendor credentials validation, this method validates individual model credentials.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
|
||||||
"""
|
|
||||||
Validate model credentials
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: model credentials
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
- Model Parameter Schema
|
|
||||||
|
|
||||||
Unlike custom types, since the YAML file does not define which parameters a model supports, we need to dynamically generate the model parameter schema.
|
|
||||||
|
|
||||||
For instance, Xinference supports `max_tokens`, `temperature`, and `top_p` parameters.
|
|
||||||
|
|
||||||
However, some vendors may support different parameters for different models. For example, the `OpenLLM` vendor supports `top_k`, but not all models provided by this vendor support `top_k`. Let's say model A supports `top_k` but model B does not. In such cases, we need to dynamically generate the model parameter schema, as illustrated below:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None:
|
|
||||||
"""
|
|
||||||
used to define customizable model schema
|
|
||||||
"""
|
|
||||||
rules = [
|
|
||||||
ParameterRule(
|
|
||||||
name='temperature', type=ParameterType.FLOAT,
|
|
||||||
use_template='temperature',
|
|
||||||
label=I18nObject(
|
|
||||||
zh_Hans='温度', en_US='Temperature'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
ParameterRule(
|
|
||||||
name='top_p', type=ParameterType.FLOAT,
|
|
||||||
use_template='top_p',
|
|
||||||
label=I18nObject(
|
|
||||||
zh_Hans='Top P', en_US='Top P'
|
|
||||||
)
|
|
||||||
),
|
|
||||||
ParameterRule(
|
|
||||||
name='max_tokens', type=ParameterType.INT,
|
|
||||||
use_template='max_tokens',
|
|
||||||
min=1,
|
|
||||||
default=512,
|
|
||||||
label=I18nObject(
|
|
||||||
zh_Hans='最大生成长度', en_US='Max Tokens'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# if model is A, add top_k to rules
|
|
||||||
if model == 'A':
|
|
||||||
rules.append(
|
|
||||||
ParameterRule(
|
|
||||||
name='top_k', type=ParameterType.INT,
|
|
||||||
use_template='top_k',
|
|
||||||
min=1,
|
|
||||||
default=50,
|
|
||||||
label=I18nObject(
|
|
||||||
zh_Hans='Top K', en_US='Top K'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
some NOT IMPORTANT code here
|
|
||||||
"""
|
|
||||||
|
|
||||||
entity = AIModelEntity(
|
|
||||||
model=model,
|
|
||||||
label=I18nObject(
|
|
||||||
en_US=model
|
|
||||||
),
|
|
||||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
|
||||||
model_type=model_type,
|
|
||||||
model_properties={
|
|
||||||
ModelPropertyKey.MODE: ModelType.LLM,
|
|
||||||
},
|
|
||||||
parameter_rules=rules
|
|
||||||
)
|
|
||||||
|
|
||||||
return entity
|
|
||||||
```
|
|
||||||
|
|
||||||
- Exception Error Mapping
|
|
||||||
|
|
||||||
When a model invocation error occurs, it should be mapped to the runtime's specified `InvokeError` type, enabling Dify to handle different errors appropriately.
|
|
||||||
|
|
||||||
Runtime Errors:
|
|
||||||
|
|
||||||
- `InvokeConnectionError` Connection error during invocation
|
|
||||||
- `InvokeServerUnavailableError` Service provider unavailable
|
|
||||||
- `InvokeRateLimitError` Rate limit reached
|
|
||||||
- `InvokeAuthorizationError` Authorization failure
|
|
||||||
- `InvokeBadRequestError` Invalid request parameters
|
|
||||||
|
|
||||||
```python
|
|
||||||
@property
|
|
||||||
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
|
|
||||||
"""
|
|
||||||
Map model invoke error to unified error
|
|
||||||
The key is the error type thrown to the caller
|
|
||||||
The value is the error type thrown by the model,
|
|
||||||
which needs to be converted into a unified error type for the caller.
|
|
||||||
|
|
||||||
:return: Invoke error mapping
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
For interface method details, see: [Interfaces](./interfaces.md). For specific implementations, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py).
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 230 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 205 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 262 KiB |
@ -1,173 +0,0 @@
|
|||||||
## Predefined Model Integration
|
|
||||||
|
|
||||||
After completing the vendor integration, the next step is to integrate the models from the vendor.
|
|
||||||
|
|
||||||
First, we need to determine the type of model to be integrated and create the corresponding model type `module` under the respective vendor's directory.
|
|
||||||
|
|
||||||
Currently supported model types are:
|
|
||||||
|
|
||||||
- `llm` Text Generation Model
|
|
||||||
- `text_embedding` Text Embedding Model
|
|
||||||
- `rerank` Rerank Model
|
|
||||||
- `speech2text` Speech-to-Text
|
|
||||||
- `tts` Text-to-Speech
|
|
||||||
- `moderation` Moderation
|
|
||||||
|
|
||||||
Continuing with `Anthropic` as an example, `Anthropic` only supports LLM, so create a `module` named `llm` under `model_providers.anthropic`.
|
|
||||||
|
|
||||||
For predefined models, we first need to create a YAML file named after the model under the `llm` `module`, such as `claude-2.1.yaml`.
|
|
||||||
|
|
||||||
### Prepare Model YAML
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
model: claude-2.1 # Model identifier
|
|
||||||
# Display name of the model, which can be set to en_US English or zh_Hans Chinese. If zh_Hans is not set, it will default to en_US.
|
|
||||||
# This can also be omitted, in which case the model identifier will be used as the label
|
|
||||||
label:
|
|
||||||
en_US: claude-2.1
|
|
||||||
model_type: llm # Model type, claude-2.1 is an LLM
|
|
||||||
features: # Supported features, agent-thought supports Agent reasoning, vision supports image understanding
|
|
||||||
- agent-thought
|
|
||||||
model_properties: # Model properties
|
|
||||||
mode: chat # LLM mode, complete for text completion models, chat for conversation models
|
|
||||||
context_size: 200000 # Maximum context size
|
|
||||||
parameter_rules: # Parameter rules for the model call; only LLM requires this
|
|
||||||
- name: temperature # Parameter variable name
|
|
||||||
# Five default configuration templates are provided: temperature/top_p/max_tokens/presence_penalty/frequency_penalty
|
|
||||||
# The template variable name can be set directly in use_template, which will use the default configuration in entities.defaults.PARAMETER_RULE_TEMPLATE
|
|
||||||
# Additional configuration parameters will override the default configuration if set
|
|
||||||
use_template: temperature
|
|
||||||
- name: top_p
|
|
||||||
use_template: top_p
|
|
||||||
- name: top_k
|
|
||||||
label: # Display name of the parameter
|
|
||||||
zh_Hans: 取样数量
|
|
||||||
en_US: Top k
|
|
||||||
type: int # Parameter type, supports float/int/string/boolean
|
|
||||||
help: # Help information, describing the parameter's function
|
|
||||||
zh_Hans: 仅从每个后续标记的前 K 个选项中采样。
|
|
||||||
en_US: Only sample from the top K options for each subsequent token.
|
|
||||||
required: false # Whether the parameter is mandatory; can be omitted
|
|
||||||
- name: max_tokens_to_sample
|
|
||||||
use_template: max_tokens
|
|
||||||
default: 4096 # Default value of the parameter
|
|
||||||
min: 1 # Minimum value of the parameter, applicable to float/int only
|
|
||||||
max: 4096 # Maximum value of the parameter, applicable to float/int only
|
|
||||||
pricing: # Pricing information
|
|
||||||
input: '8.00' # Input unit price, i.e., prompt price
|
|
||||||
output: '24.00' # Output unit price, i.e., response content price
|
|
||||||
unit: '0.000001' # Price unit, meaning the above prices are per 100K
|
|
||||||
currency: USD # Price currency
|
|
||||||
```
|
|
||||||
|
|
||||||
It is recommended to prepare all model configurations before starting the implementation of the model code.
|
|
||||||
|
|
||||||
You can also refer to the YAML configuration information under the corresponding model type directories of other vendors in the `model_providers` directory. For the complete YAML rules, refer to: [Schema](schema.md#aimodelentity).
|
|
||||||
|
|
||||||
### Implement the Model Call Code
|
|
||||||
|
|
||||||
Next, create a Python file named `llm.py` under the `llm` `module` to write the implementation code.
|
|
||||||
|
|
||||||
Create an Anthropic LLM class named `AnthropicLargeLanguageModel` (or any other name), inheriting from the `__base.large_language_model.LargeLanguageModel` base class, and implement the following methods:
|
|
||||||
|
|
||||||
- LLM Call
|
|
||||||
|
|
||||||
Implement the core method for calling the LLM, supporting both streaming and synchronous responses.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def _invoke(self, model: str, credentials: dict,
|
|
||||||
prompt_messages: list[PromptMessage], model_parameters: dict,
|
|
||||||
tools: Optional[list[PromptMessageTool]] = None, stop: Optional[list[str]] = None,
|
|
||||||
stream: bool = True, user: Optional[str] = None) \
|
|
||||||
-> Union[LLMResult, Generator]:
|
|
||||||
"""
|
|
||||||
Invoke large language model
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: model credentials
|
|
||||||
:param prompt_messages: prompt messages
|
|
||||||
:param model_parameters: model parameters
|
|
||||||
:param tools: tools for tool calling
|
|
||||||
:param stop: stop words
|
|
||||||
:param stream: is stream response
|
|
||||||
:param user: unique user id
|
|
||||||
:return: full response or stream response chunk generator result
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure to use two functions for returning data, one for synchronous returns and the other for streaming returns, because Python identifies functions containing the `yield` keyword as generator functions, fixing the return type to `Generator`. Thus, synchronous and streaming returns need to be implemented separately, as shown below (note that the example uses simplified parameters, for actual implementation follow the above parameter list):
|
|
||||||
|
|
||||||
```python
|
|
||||||
def _invoke(self, stream: bool, **kwargs) \
|
|
||||||
-> Union[LLMResult, Generator]:
|
|
||||||
if stream:
|
|
||||||
return self._handle_stream_response(**kwargs)
|
|
||||||
return self._handle_sync_response(**kwargs)
|
|
||||||
|
|
||||||
def _handle_stream_response(self, **kwargs) -> Generator:
|
|
||||||
for chunk in response:
|
|
||||||
yield chunk
|
|
||||||
def _handle_sync_response(self, **kwargs) -> LLMResult:
|
|
||||||
return LLMResult(**response)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Pre-compute Input Tokens
|
|
||||||
|
|
||||||
If the model does not provide an interface to precompute tokens, return 0 directly.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage],
|
|
||||||
tools: Optional[list[PromptMessageTool]] = None) -> int:
|
|
||||||
"""
|
|
||||||
Get number of tokens for given prompt messages
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: model credentials
|
|
||||||
:param prompt_messages: prompt messages
|
|
||||||
:param tools: tools for tool calling
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
- Validate Model Credentials
|
|
||||||
|
|
||||||
Similar to vendor credential validation, but specific to a single model.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
|
||||||
"""
|
|
||||||
Validate model credentials
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: model credentials
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
- Map Invoke Errors
|
|
||||||
|
|
||||||
When a model call fails, map it to a specific `InvokeError` type as required by Runtime, allowing Dify to handle different errors accordingly.
|
|
||||||
|
|
||||||
Runtime Errors:
|
|
||||||
|
|
||||||
- `InvokeConnectionError` Connection error
|
|
||||||
|
|
||||||
- `InvokeServerUnavailableError` Service provider unavailable
|
|
||||||
- `InvokeRateLimitError` Rate limit reached
|
|
||||||
- `InvokeAuthorizationError` Authorization failed
|
|
||||||
- `InvokeBadRequestError` Parameter error
|
|
||||||
|
|
||||||
```python
|
|
||||||
@property
|
|
||||||
def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
|
|
||||||
"""
|
|
||||||
Map model invoke error to unified error
|
|
||||||
The key is the error type thrown to the caller
|
|
||||||
The value is the error type thrown by the model,
|
|
||||||
which needs to be converted into a unified error type for the caller.
|
|
||||||
|
|
||||||
:return: Invoke error mapping
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
For interface method explanations, see: [Interfaces](./interfaces.md). For detailed implementation, refer to: [llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model_providers/anthropic/llm/llm.py).
|
|
||||||
@ -58,7 +58,7 @@ provider_credential_schema: # Provider credential rules, as Anthropic only supp
|
|||||||
en_US: Enter your API URL
|
en_US: Enter your API URL
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also refer to the YAML configuration information under other provider directories in `model_providers`. The complete YAML rules are available at: [Schema](schema.md#provider).
|
You can also refer to the YAML configuration information under other provider directories in `model_providers`. The complete YAML rules are available at: [Schema](schema.md#Provider).
|
||||||
|
|
||||||
### Implementing Provider Code
|
### Implementing Provider Code
|
||||||
|
|
||||||
|
|||||||
@ -117,7 +117,7 @@ model_credential_schema:
|
|||||||
en_US: Enter your API Base
|
en_US: Enter your API Base
|
||||||
```
|
```
|
||||||
|
|
||||||
也可以参考 `model_providers` 目录下其他供应商目录下的 YAML 配置信息,完整的 YAML 规则见:[Schema](schema.md#provider)。
|
也可以参考 `model_providers` 目录下其他供应商目录下的 YAML 配置信息,完整的 YAML 规则见:[Schema](schema.md#Provider)。
|
||||||
|
|
||||||
#### 实现供应商代码
|
#### 实现供应商代码
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,7 @@ class LargeLanguageModel(AIModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if "response_format" in model_parameters and model_parameters["response_format"] in {"JSON", "XML"}:
|
if "response_format" in model_parameters:
|
||||||
result = self._code_block_mode_wrapper(
|
result = self._code_block_mode_wrapper(
|
||||||
model=model,
|
model=model,
|
||||||
credentials=credentials,
|
credentials=credentials,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ from typing import Optional
|
|||||||
|
|
||||||
from pydantic import ConfigDict
|
from pydantic import ConfigDict
|
||||||
|
|
||||||
from core.entities.embedding_type import EmbeddingInputType
|
from core.embedding.embedding_constant import EmbeddingInputType
|
||||||
from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
|
from core.model_runtime.entities.model_entities import ModelPropertyKey, ModelType
|
||||||
from core.model_runtime.entities.text_embedding_entities import TextEmbeddingResult
|
from core.model_runtime.entities.text_embedding_entities import TextEmbeddingResult
|
||||||
from core.model_runtime.model_providers.__base.ai_model import AIModel
|
from core.model_runtime.model_providers.__base.ai_model import AIModel
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Any, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import ConfigDict
|
from pydantic import ConfigDict
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ class TTSModel(AIModel):
|
|||||||
else:
|
else:
|
||||||
return [{"name": d["name"], "value": d["mode"]} for d in voices]
|
return [{"name": d["name"], "value": d["mode"]} for d in voices]
|
||||||
|
|
||||||
def _get_model_default_voice(self, model: str, credentials: dict) -> Any:
|
def _get_model_default_voice(self, model: str, credentials: dict) -> any:
|
||||||
"""
|
"""
|
||||||
Get voice for given tts model
|
Get voice for given tts model
|
||||||
|
|
||||||
|
|||||||
@ -40,4 +40,3 @@
|
|||||||
- fireworks
|
- fireworks
|
||||||
- mixedbread
|
- mixedbread
|
||||||
- nomic
|
- nomic
|
||||||
- voyage
|
|
||||||
|
|||||||
@ -169,7 +169,7 @@ class AnthropicLargeLanguageModel(LargeLanguageModel):
|
|||||||
stop: Optional[list[str]] = None,
|
stop: Optional[list[str]] = None,
|
||||||
stream: bool = True,
|
stream: bool = True,
|
||||||
user: Optional[str] = None,
|
user: Optional[str] = None,
|
||||||
callbacks: Optional[list[Callback]] = None,
|
callbacks: list[Callback] = None,
|
||||||
) -> Union[LLMResult, Generator]:
|
) -> Union[LLMResult, Generator]:
|
||||||
"""
|
"""
|
||||||
Code block mode wrapper for invoking large language model
|
Code block mode wrapper for invoking large language model
|
||||||
|
|||||||
@ -1081,97 +1081,8 @@ LLM_BASE_MODELS = [
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AzureBaseModel(
|
|
||||||
base_model_name="o1-preview",
|
|
||||||
entity=AIModelEntity(
|
|
||||||
model="fake-deployment-name",
|
|
||||||
label=I18nObject(
|
|
||||||
en_US="fake-deployment-name-label",
|
|
||||||
),
|
|
||||||
model_type=ModelType.LLM,
|
|
||||||
features=[
|
|
||||||
ModelFeature.AGENT_THOUGHT,
|
|
||||||
],
|
|
||||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
|
||||||
model_properties={
|
|
||||||
ModelPropertyKey.MODE: LLMMode.CHAT.value,
|
|
||||||
ModelPropertyKey.CONTEXT_SIZE: 128000,
|
|
||||||
},
|
|
||||||
parameter_rules=[
|
|
||||||
ParameterRule(
|
|
||||||
name="temperature",
|
|
||||||
**PARAMETER_RULE_TEMPLATE[DefaultParameterName.TEMPERATURE],
|
|
||||||
),
|
|
||||||
ParameterRule(
|
|
||||||
name="top_p",
|
|
||||||
**PARAMETER_RULE_TEMPLATE[DefaultParameterName.TOP_P],
|
|
||||||
),
|
|
||||||
ParameterRule(
|
|
||||||
name="response_format",
|
|
||||||
label=I18nObject(zh_Hans="回复格式", en_US="response_format"),
|
|
||||||
type="string",
|
|
||||||
help=I18nObject(
|
|
||||||
zh_Hans="指定模型必须输出的格式", en_US="specifying the format that the model must output"
|
|
||||||
),
|
|
||||||
required=False,
|
|
||||||
options=["text", "json_object"],
|
|
||||||
),
|
|
||||||
_get_max_tokens(default=512, min_val=1, max_val=32768),
|
|
||||||
],
|
|
||||||
pricing=PriceConfig(
|
|
||||||
input=15.00,
|
|
||||||
output=60.00,
|
|
||||||
unit=0.000001,
|
|
||||||
currency="USD",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AzureBaseModel(
|
|
||||||
base_model_name="o1-mini",
|
|
||||||
entity=AIModelEntity(
|
|
||||||
model="fake-deployment-name",
|
|
||||||
label=I18nObject(
|
|
||||||
en_US="fake-deployment-name-label",
|
|
||||||
),
|
|
||||||
model_type=ModelType.LLM,
|
|
||||||
features=[
|
|
||||||
ModelFeature.AGENT_THOUGHT,
|
|
||||||
],
|
|
||||||
fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
|
|
||||||
model_properties={
|
|
||||||
ModelPropertyKey.MODE: LLMMode.CHAT.value,
|
|
||||||
ModelPropertyKey.CONTEXT_SIZE: 128000,
|
|
||||||
},
|
|
||||||
parameter_rules=[
|
|
||||||
ParameterRule(
|
|
||||||
name="temperature",
|
|
||||||
**PARAMETER_RULE_TEMPLATE[DefaultParameterName.TEMPERATURE],
|
|
||||||
),
|
|
||||||
ParameterRule(
|
|
||||||
name="top_p",
|
|
||||||
**PARAMETER_RULE_TEMPLATE[DefaultParameterName.TOP_P],
|
|
||||||
),
|
|
||||||
ParameterRule(
|
|
||||||
name="response_format",
|
|
||||||
label=I18nObject(zh_Hans="回复格式", en_US="response_format"),
|
|
||||||
type="string",
|
|
||||||
help=I18nObject(
|
|
||||||
zh_Hans="指定模型必须输出的格式", en_US="specifying the format that the model must output"
|
|
||||||
),
|
|
||||||
required=False,
|
|
||||||
options=["text", "json_object"],
|
|
||||||
),
|
|
||||||
_get_max_tokens(default=512, min_val=1, max_val=65536),
|
|
||||||
],
|
|
||||||
pricing=PriceConfig(
|
|
||||||
input=3.00,
|
|
||||||
output=12.00,
|
|
||||||
unit=0.000001,
|
|
||||||
currency="USD",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
EMBEDDING_BASE_MODELS = [
|
EMBEDDING_BASE_MODELS = [
|
||||||
AzureBaseModel(
|
AzureBaseModel(
|
||||||
base_model_name="text-embedding-ada-002",
|
base_model_name="text-embedding-ada-002",
|
||||||
|
|||||||
@ -53,9 +53,6 @@ model_credential_schema:
|
|||||||
type: select
|
type: select
|
||||||
required: true
|
required: true
|
||||||
options:
|
options:
|
||||||
- label:
|
|
||||||
en_US: 2024-09-01-preview
|
|
||||||
value: 2024-09-01-preview
|
|
||||||
- label:
|
- label:
|
||||||
en_US: 2024-08-01-preview
|
en_US: 2024-08-01-preview
|
||||||
value: 2024-08-01-preview
|
value: 2024-08-01-preview
|
||||||
@ -123,18 +120,6 @@ model_credential_schema:
|
|||||||
show_on:
|
show_on:
|
||||||
- variable: __model_type
|
- variable: __model_type
|
||||||
value: llm
|
value: llm
|
||||||
- label:
|
|
||||||
en_US: o1-mini
|
|
||||||
value: o1-mini
|
|
||||||
show_on:
|
|
||||||
- variable: __model_type
|
|
||||||
value: llm
|
|
||||||
- label:
|
|
||||||
en_US: o1-preview
|
|
||||||
value: o1-preview
|
|
||||||
show_on:
|
|
||||||
- variable: __model_type
|
|
||||||
value: llm
|
|
||||||
- label:
|
- label:
|
||||||
en_US: gpt-4o-mini
|
en_US: gpt-4o-mini
|
||||||
value: gpt-4o-mini
|
value: gpt-4o-mini
|
||||||
|
|||||||
@ -119,15 +119,7 @@ class AzureOpenAILargeLanguageModel(_CommonAzureOpenAI, LargeLanguageModel):
|
|||||||
try:
|
try:
|
||||||
client = AzureOpenAI(**self._to_credential_kwargs(credentials))
|
client = AzureOpenAI(**self._to_credential_kwargs(credentials))
|
||||||
|
|
||||||
if model.startswith("o1"):
|
if ai_model_entity.entity.model_properties.get(ModelPropertyKey.MODE) == LLMMode.CHAT.value:
|
||||||
client.chat.completions.create(
|
|
||||||
messages=[{"role": "user", "content": "ping"}],
|
|
||||||
model=model,
|
|
||||||
temperature=1,
|
|
||||||
max_completion_tokens=20,
|
|
||||||
stream=False,
|
|
||||||
)
|
|
||||||
elif ai_model_entity.entity.model_properties.get(ModelPropertyKey.MODE) == LLMMode.CHAT.value:
|
|
||||||
# chat model
|
# chat model
|
||||||
client.chat.completions.create(
|
client.chat.completions.create(
|
||||||
messages=[{"role": "user", "content": "ping"}],
|
messages=[{"role": "user", "content": "ping"}],
|
||||||
@ -320,24 +312,10 @@ class AzureOpenAILargeLanguageModel(_CommonAzureOpenAI, LargeLanguageModel):
|
|||||||
if user:
|
if user:
|
||||||
extra_model_kwargs["user"] = user
|
extra_model_kwargs["user"] = user
|
||||||
|
|
||||||
# clear illegal prompt messages
|
|
||||||
prompt_messages = self._clear_illegal_prompt_messages(model, prompt_messages)
|
|
||||||
|
|
||||||
block_as_stream = False
|
|
||||||
if model.startswith("o1"):
|
|
||||||
if stream:
|
|
||||||
block_as_stream = True
|
|
||||||
stream = False
|
|
||||||
|
|
||||||
if "stream_options" in extra_model_kwargs:
|
|
||||||
del extra_model_kwargs["stream_options"]
|
|
||||||
|
|
||||||
if "stop" in extra_model_kwargs:
|
|
||||||
del extra_model_kwargs["stop"]
|
|
||||||
|
|
||||||
# chat model
|
# chat model
|
||||||
|
messages = [self._convert_prompt_message_to_dict(m) for m in prompt_messages]
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
messages=[self._convert_prompt_message_to_dict(m) for m in prompt_messages],
|
messages=messages,
|
||||||
model=model,
|
model=model,
|
||||||
stream=stream,
|
stream=stream,
|
||||||
**model_parameters,
|
**model_parameters,
|
||||||
@ -347,91 +325,7 @@ class AzureOpenAILargeLanguageModel(_CommonAzureOpenAI, LargeLanguageModel):
|
|||||||
if stream:
|
if stream:
|
||||||
return self._handle_chat_generate_stream_response(model, credentials, response, prompt_messages, tools)
|
return self._handle_chat_generate_stream_response(model, credentials, response, prompt_messages, tools)
|
||||||
|
|
||||||
block_result = self._handle_chat_generate_response(model, credentials, response, prompt_messages, tools)
|
return self._handle_chat_generate_response(model, credentials, response, prompt_messages, tools)
|
||||||
|
|
||||||
if block_as_stream:
|
|
||||||
return self._handle_chat_block_as_stream_response(block_result, prompt_messages, stop)
|
|
||||||
|
|
||||||
return block_result
|
|
||||||
|
|
||||||
def _handle_chat_block_as_stream_response(
|
|
||||||
self,
|
|
||||||
block_result: LLMResult,
|
|
||||||
prompt_messages: list[PromptMessage],
|
|
||||||
stop: Optional[list[str]] = None,
|
|
||||||
) -> Generator[LLMResultChunk, None, None]:
|
|
||||||
"""
|
|
||||||
Handle llm chat response
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param credentials: credentials
|
|
||||||
:param response: response
|
|
||||||
:param prompt_messages: prompt messages
|
|
||||||
:param tools: tools for tool calling
|
|
||||||
:param stop: stop words
|
|
||||||
:return: llm response chunk generator
|
|
||||||
"""
|
|
||||||
text = block_result.message.content
|
|
||||||
text = cast(str, text)
|
|
||||||
|
|
||||||
if stop:
|
|
||||||
text = self.enforce_stop_tokens(text, stop)
|
|
||||||
|
|
||||||
yield LLMResultChunk(
|
|
||||||
model=block_result.model,
|
|
||||||
prompt_messages=prompt_messages,
|
|
||||||
system_fingerprint=block_result.system_fingerprint,
|
|
||||||
delta=LLMResultChunkDelta(
|
|
||||||
index=0,
|
|
||||||
message=AssistantPromptMessage(content=text),
|
|
||||||
finish_reason="stop",
|
|
||||||
usage=block_result.usage,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def _clear_illegal_prompt_messages(self, model: str, prompt_messages: list[PromptMessage]) -> list[PromptMessage]:
|
|
||||||
"""
|
|
||||||
Clear illegal prompt messages for OpenAI API
|
|
||||||
|
|
||||||
:param model: model name
|
|
||||||
:param prompt_messages: prompt messages
|
|
||||||
:return: cleaned prompt messages
|
|
||||||
"""
|
|
||||||
checklist = ["gpt-4-turbo", "gpt-4-turbo-2024-04-09"]
|
|
||||||
|
|
||||||
if model in checklist:
|
|
||||||
# count how many user messages are there
|
|
||||||
user_message_count = len([m for m in prompt_messages if isinstance(m, UserPromptMessage)])
|
|
||||||
if user_message_count > 1:
|
|
||||||
for prompt_message in prompt_messages:
|
|
||||||
if isinstance(prompt_message, UserPromptMessage):
|
|
||||||
if isinstance(prompt_message.content, list):
|
|
||||||
prompt_message.content = "\n".join(
|
|
||||||
[
|
|
||||||
item.data
|
|
||||||
if item.type == PromptMessageContentType.TEXT
|
|
||||||
else "[IMAGE]"
|
|
||||||
if item.type == PromptMessageContentType.IMAGE
|
|
||||||
else ""
|
|
||||||
for item in prompt_message.content
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
if model.startswith("o1"):
|
|
||||||
system_message_count = len([m for m in prompt_messages if isinstance(m, SystemPromptMessage)])
|
|
||||||
if system_message_count > 0:
|
|
||||||
new_prompt_messages = []
|
|
||||||
for prompt_message in prompt_messages:
|
|
||||||
if isinstance(prompt_message, SystemPromptMessage):
|
|
||||||
prompt_message = UserPromptMessage(
|
|
||||||
content=prompt_message.content,
|
|
||||||
name=prompt_message.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_prompt_messages.append(prompt_message)
|
|
||||||
prompt_messages = new_prompt_messages
|
|
||||||
|
|
||||||
return prompt_messages
|
|
||||||
|
|
||||||
def _handle_chat_generate_response(
|
def _handle_chat_generate_response(
|
||||||
self,
|
self,
|
||||||
@ -666,7 +560,7 @@ class AzureOpenAILargeLanguageModel(_CommonAzureOpenAI, LargeLanguageModel):
|
|||||||
tokens_per_message = 4
|
tokens_per_message = 4
|
||||||
# if there's a name, the role is omitted
|
# if there's a name, the role is omitted
|
||||||
tokens_per_name = -1
|
tokens_per_name = -1
|
||||||
elif model.startswith("gpt-35-turbo") or model.startswith("gpt-4") or model.startswith("o1"):
|
elif model.startswith("gpt-35-turbo") or model.startswith("gpt-4"):
|
||||||
tokens_per_message = 3
|
tokens_per_message = 3
|
||||||
tokens_per_name = 1
|
tokens_per_name = 1
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import numpy as np
|
|||||||
import tiktoken
|
import tiktoken
|
||||||
from openai import AzureOpenAI
|
from openai import AzureOpenAI
|
||||||
|
|
||||||
from core.entities.embedding_type import EmbeddingInputType
|
from core.embedding.embedding_constant import EmbeddingInputType
|
||||||
from core.model_runtime.entities.model_entities import AIModelEntity, PriceType
|
from core.model_runtime.entities.model_entities import AIModelEntity, PriceType
|
||||||
from core.model_runtime.entities.text_embedding_entities import EmbeddingUsage, TextEmbeddingResult
|
from core.model_runtime.entities.text_embedding_entities import EmbeddingUsage, TextEmbeddingResult
|
||||||
from core.model_runtime.errors.validate import CredentialsValidateFailedError
|
from core.model_runtime.errors.validate import CredentialsValidateFailedError
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import copy
|
import copy
|
||||||
from typing import Any, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from openai import AzureOpenAI
|
from openai import AzureOpenAI
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel):
|
|||||||
|
|
||||||
def _invoke(
|
def _invoke(
|
||||||
self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, user: Optional[str] = None
|
self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, user: Optional[str] = None
|
||||||
) -> Any:
|
) -> any:
|
||||||
"""
|
"""
|
||||||
_invoke text2speech model
|
_invoke text2speech model
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class AzureOpenAIText2SpeechModel(_CommonAzureOpenAI, TTSModel):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise CredentialsValidateFailedError(str(ex))
|
raise CredentialsValidateFailedError(str(ex))
|
||||||
|
|
||||||
def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, voice: str) -> Any:
|
def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, voice: str) -> any:
|
||||||
"""
|
"""
|
||||||
_tts_invoke_streaming text2speech model
|
_tts_invoke_streaming text2speech model
|
||||||
:param model: model name
|
:param model: model name
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user