Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17f22347ae | ||
|
|
22aaf8960b | ||
|
|
0046ef7707 | ||
|
|
68b1d063f7 | ||
|
|
5e6c3001bd | ||
|
|
7ed4e963aa | ||
|
|
001d868cbd | ||
|
|
6610b4cee5 | ||
|
|
cbbe28f40d | ||
|
|
603187393a | ||
|
|
411e938e3b | ||
|
|
610da4f662 | ||
|
|
3ec80f9dda | ||
|
|
91c5818236 | ||
|
|
c436454cd4 | ||
|
|
a877d4831d | ||
|
|
d522308a29 | ||
|
|
85744b72e5 | ||
|
|
f0b7051e1a | ||
|
|
3b23d6764f | ||
|
|
9b7c74a5d9 | ||
|
|
4d105d7bd7 | ||
|
|
eee779a923 | ||
|
|
ab847c81fa | ||
|
|
b217ee414f | ||
|
|
23dc6edb99 | ||
|
|
79df8825c8 | ||
|
|
71c50b7e20 | ||
|
|
af98fd29bf | ||
|
|
cddea83e65 | ||
|
|
9f16739518 | ||
|
|
3f0da88ff7 | ||
|
|
cc63af8e72 | ||
|
|
bf2268b0af | ||
|
|
00b4cc3cd4 | ||
|
|
f546db5437 | ||
|
|
f8aaa57f31 | ||
|
|
cabcf94be3 | ||
|
|
2d6624cf9e | ||
|
|
02982df0d4 | ||
|
|
421a24c38d | ||
|
|
d7f75d17cc | ||
|
|
5d9ad430af | ||
|
|
46eca01fa3 | ||
|
|
4d9c22bfc6 | ||
|
|
52e59cf4df | ||
|
|
688b8fe114 | ||
|
|
aecdfa2d5c | ||
|
|
cb8feb732f | ||
|
|
c490bdfbf9 | ||
|
|
e7494d632c | ||
|
|
e3006f98c9 | ||
|
|
b34baf1e3a | ||
|
|
372dc7ac1a | ||
|
|
66a62e6c13 | ||
|
|
04c0a9ad45 | ||
|
|
0944ca9d91 | ||
|
|
d468f8b75c | ||
|
|
6e256507d3 | ||
|
|
a33ce09e6e | ||
|
|
9f973bb703 | ||
|
|
6d0a605c5f | ||
|
|
a948bf6ee8 | ||
|
|
b8999e367a | ||
|
|
59ad091e69 | ||
|
|
598e030a7e | ||
|
|
774a17cedf | ||
|
|
d889e1b233 | ||
|
|
32d85fb896 | ||
|
|
af308b99a3 | ||
|
|
49d9c60a53 | ||
|
|
ed83df972f | ||
|
|
3124728e03 | ||
|
|
af469ea5bd | ||
|
|
2a27568537 | ||
|
|
1d3e96ffa6 | ||
|
|
b7b1396c51 | ||
|
|
71bcf75d9a | ||
|
|
850c2273ee | ||
|
|
78d41a27cc | ||
|
|
0f8625cac2 | ||
|
|
cb09dbef66 | ||
|
|
5692f9b33b | ||
|
|
fdfbbde10d | ||
|
|
a27462d58b | ||
|
|
91da622df5 | ||
|
|
373b5047fd | ||
|
|
36610b6acf | ||
|
|
eab0ac3a13 | ||
|
|
f637ae4794 | ||
|
|
ffb07eb24b | ||
|
|
f101fcd0e7 | ||
|
|
fc0f75d13b | ||
|
|
1e045a0187 | ||
|
|
cdf64d4ee2 | ||
|
|
8fd75e6965 | ||
|
|
0b8faade6f | ||
|
|
d56cedfc67 | ||
|
|
906857b28a | ||
|
|
9513155fa4 | ||
|
|
a6356be348 | ||
|
|
f33ef92f0c | ||
|
|
d435230059 |
@@ -1,9 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd web && npm install
|
||||
pipx install poetry
|
||||
|
||||
echo 'alias start-api="cd /workspaces/dify/api && flask run --host 0.0.0.0 --port=5001 --debug"' >> ~/.bashrc
|
||||
echo 'alias start-worker="cd /workspaces/dify/api && celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace"' >> ~/.bashrc
|
||||
echo 'alias start-worker="cd /workspaces/dify/api && 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 && npm run dev"' >> ~/.bashrc
|
||||
echo 'alias start-containers="cd /workspaces/dify/docker && docker-compose -f docker-compose.middleware.yaml -p dify up -d"' >> ~/.bashrc
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Ensure that .sh scripts use LF as line separator, even if they are checked out
|
||||
# to Windows(NTFS) file-system, by a user of Docker for Window.
|
||||
# These .sh scripts will be run from the Container after `docker compose up -d`.
|
||||
# If they appear to be CRLF style, Dash from the Container will fail to execute
|
||||
# them.
|
||||
|
||||
*.sh text eol=lf
|
||||
@@ -1,13 +1,21 @@
|
||||
# Checklist:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please review the checklist below before submitting your pull request.
|
||||
|
||||
- [ ] Please open an issue before creating a PR or link to an existing issue
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I ran `dev/reformat`(backend) and `cd web && npx lint-staged`(frontend) to appease the lint gods
|
||||
|
||||
# Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
||||
Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. Close issue syntax: `Fixes #<issue number>`, see [documentation](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for more details.
|
||||
|
||||
Fixes # (issue)
|
||||
Fixes
|
||||
|
||||
## Type of Change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
@@ -15,18 +23,12 @@ Please delete options that are not relevant.
|
||||
- [ ] Improvement, including but not limited to code refactoring, performance optimization, and UI/UX improvement
|
||||
- [ ] Dependency upgrade
|
||||
|
||||
# How Has This Been Tested?
|
||||
# Testing Instructions
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
|
||||
|
||||
- [ ] TODO
|
||||
- [ ] Test A
|
||||
- [ ] Test B
|
||||
|
||||
|
||||
# Suggested Checklist:
|
||||
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I ran `dev/reformat`(backend) and `cd web && npx lint-staged`(frontend) to appease the lint gods
|
||||
- [ ] `optional` I have made corresponding changes to the documentation
|
||||
- [ ] `optional` I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] `optional` New and existing unit tests pass locally with my changes
|
||||
|
||||
@@ -60,6 +60,9 @@ jobs:
|
||||
cp docker/.env.example docker/.env
|
||||
cp docker/middleware.env.example docker/middleware.env
|
||||
|
||||
- name: Expose Service Ports
|
||||
run: sh .github/workflows/expose_service_ports.sh
|
||||
|
||||
- name: Set up Sandbox
|
||||
uses: hoverkraft-tech/compose-action@v2.0.0
|
||||
with:
|
||||
|
||||
@@ -48,18 +48,18 @@ jobs:
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_USER }}
|
||||
password: ${{ env.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
|
||||
@@ -38,6 +38,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: poetry install -C api
|
||||
|
||||
- name: Prepare middleware env
|
||||
run: |
|
||||
cd docker
|
||||
cp middleware.env.example middleware.env
|
||||
|
||||
- name: Set up Middlewares
|
||||
uses: hoverkraft-tech/compose-action@v2.0.0
|
||||
with:
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
yq eval '.services.weaviate.ports += ["8080:8080"]' -i docker/docker-compose.yaml
|
||||
yq eval '.services.qdrant.ports += ["6333:6333"]' -i docker/docker-compose.yaml
|
||||
yq eval '.services.chroma.ports += ["8000:8000"]' -i docker/docker-compose.yaml
|
||||
yq eval '.services["milvus-standalone"].ports += ["19530:19530"]' -i docker/docker-compose.yaml
|
||||
yq eval '.services.pgvector.ports += ["5433:5432"]' -i docker/docker-compose.yaml
|
||||
yq eval '.services["pgvecto-rs"].ports += ["5431:5432"]' -i docker/docker-compose.yaml
|
||||
|
||||
echo "Ports exposed for sandbox, weaviate, qdrant, chroma, milvus, pgvector, pgvecto-rs."
|
||||
@@ -174,3 +174,5 @@ sdks/python-client/dify_client.egg-info
|
||||
.vscode/*
|
||||
!.vscode/launch.json
|
||||
pyrightconfig.json
|
||||
|
||||
.idea/
|
||||
|
||||
Vendored
+1
-2
@@ -13,7 +13,6 @@
|
||||
"jinja": true,
|
||||
"env": {
|
||||
"FLASK_APP": "app.py",
|
||||
"FLASK_DEBUG": "1",
|
||||
"GEVENT_SUPPORT": "True"
|
||||
},
|
||||
"args": [
|
||||
@@ -48,7 +47,7 @@
|
||||
"--loglevel",
|
||||
"info",
|
||||
"-Q",
|
||||
"dataset,generation,mail,ops_trace"
|
||||
"dataset,generation,mail,ops_trace,app_deletion"
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -192,6 +192,11 @@ If you'd like to configure a highly-available setup, there are community-contrib
|
||||
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### Using Terraform for Deployment
|
||||
|
||||
##### 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)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -175,6 +175,12 @@ docker compose up -d
|
||||
- [رسم بياني Helm من قبل @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [ملف YAML من قبل @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### استخدام Terraform للتوزيع
|
||||
|
||||
##### Azure Global
|
||||
استخدم [terraform](https://www.terraform.io/) لنشر Dify على Azure بنقرة واحدة.
|
||||
- [Azure Terraform بواسطة @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||
|
||||
|
||||
## المساهمة
|
||||
|
||||
|
||||
@@ -197,6 +197,12 @@ docker compose up -d
|
||||
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [YAML 文件 by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### 使用 Terraform 部署
|
||||
|
||||
##### Azure Global
|
||||
使用 [terraform](https://www.terraform.io/) 一键部署 Dify 到 Azure。
|
||||
- [Azure Terraform by @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#langgenius/dify&Date)
|
||||
|
||||
@@ -199,6 +199,12 @@ Si desea configurar una configuración de alta disponibilidad, la comunidad prop
|
||||
- [Gráfico Helm por @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [Ficheros YAML por @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### Uso de Terraform para el despliegue
|
||||
|
||||
##### 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)
|
||||
|
||||
|
||||
## Contribuir
|
||||
|
||||
|
||||
@@ -197,6 +197,12 @@ Si vous souhaitez configurer une configuration haute disponibilité, la communau
|
||||
- [Helm Chart par @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [Fichier YAML par @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### Utilisation de Terraform pour le déploiement
|
||||
|
||||
##### 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)
|
||||
|
||||
|
||||
## Contribuer
|
||||
|
||||
|
||||
@@ -196,6 +196,12 @@ docker compose up -d
|
||||
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### Terraformを使用したデプロイ
|
||||
|
||||
##### Azure Global
|
||||
[terraform](https://www.terraform.io/) を使用して、AzureにDifyをワンクリックでデプロイします。
|
||||
- [nikawangのAzure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
||||
|
||||
|
||||
## 貢献
|
||||
|
||||
|
||||
@@ -197,6 +197,13 @@ If you'd like to configure a highly-available setup, there are community-contrib
|
||||
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### Terraform atorlugu pilersitsineq
|
||||
|
||||
##### Azure Global
|
||||
Atoruk [terraform](https://www.terraform.io/) Dify-mik Azure-mut ataatsikkut ikkussuilluarlugu.
|
||||
- [Azure Terraform atorlugu @nikawang](https://github.com/nikawang/dify-azure-terraform)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md).
|
||||
|
||||
@@ -190,6 +190,12 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했
|
||||
- [Helm Chart by @BorisPolonsky](https://github.com/BorisPolonsky/dify-helm)
|
||||
- [YAML file by @Winson-030](https://github.com/Winson-030/dify-kubernetes)
|
||||
|
||||
#### Terraform을 사용한 배포
|
||||
|
||||
##### Azure Global
|
||||
[terraform](https://www.terraform.io/)을 사용하여 Azure에 Dify를 원클릭으로 배포하세요.
|
||||
- [nikawang의 Azure Terraform](https://github.com/nikawang/dify-azure-terraform)
|
||||
|
||||
## 기여
|
||||
|
||||
코드에 기여하고 싶은 분들은 [기여 가이드](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)를 참조하세요.
|
||||
|
||||
@@ -72,6 +72,13 @@ TENCENT_COS_SECRET_ID=your-secret-id
|
||||
TENCENT_COS_REGION=your-region
|
||||
TENCENT_COS_SCHEME=your-scheme
|
||||
|
||||
# OCI Storage configuration
|
||||
OCI_ENDPOINT=your-endpoint
|
||||
OCI_BUCKET_NAME=your-bucket-name
|
||||
OCI_ACCESS_KEY=your-access-key
|
||||
OCI_SECRET_KEY=your-secret-key
|
||||
OCI_REGION=your-region
|
||||
|
||||
# CORS configuration
|
||||
WEB_API_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,*
|
||||
CONSOLE_CORS_ALLOW_ORIGINS=http://127.0.0.1:3000,*
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@ WORKDIR /app/api
|
||||
|
||||
# Install Poetry
|
||||
ENV POETRY_VERSION=1.8.3
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir --upgrade poetry==${POETRY_VERSION}
|
||||
RUN pip install --no-cache-dir poetry==${POETRY_VERSION}
|
||||
|
||||
# Configure Poetry
|
||||
ENV POETRY_CACHE_DIR=/tmp/poetry_cache
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@
|
||||
|
||||
```bash
|
||||
cd ../docker
|
||||
cp middleware.env.example middleware.env
|
||||
docker compose -f docker-compose.middleware.yaml -p dify up -d
|
||||
cd ../api
|
||||
```
|
||||
@@ -66,7 +67,7 @@
|
||||
10. If you need to debug local async processing, please start the worker service.
|
||||
|
||||
```bash
|
||||
poetry run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace
|
||||
poetry run python -m celery -A app.celery worker -P gevent -c 1 --loglevel INFO -Q dataset,generation,mail,ops_trace,app_deletion
|
||||
```
|
||||
|
||||
The started celery app handles the async tasks, e.g. dataset importing and documents indexing.
|
||||
|
||||
+5
-3
@@ -1,8 +1,8 @@
|
||||
import os
|
||||
|
||||
from configs.app_config import DifyConfig
|
||||
from configs import dify_config
|
||||
|
||||
if not os.environ.get("DEBUG") or os.environ.get("DEBUG", "false").lower() != 'true':
|
||||
if os.environ.get("DEBUG", "false").lower() != 'true':
|
||||
from gevent import monkey
|
||||
|
||||
monkey.patch_all()
|
||||
@@ -43,6 +43,8 @@ from extensions import (
|
||||
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
|
||||
from models import account, dataset, model, source, task, tool, tools, web
|
||||
from services.account_service import AccountService
|
||||
|
||||
@@ -81,7 +83,7 @@ def create_flask_app_with_configs() -> Flask:
|
||||
with configs loaded from .env file
|
||||
"""
|
||||
dify_app = DifyApp(__name__)
|
||||
dify_app.config.from_mapping(DifyConfig().model_dump())
|
||||
dify_app.config.from_mapping(dify_config.model_dump())
|
||||
|
||||
# populate configs into system environment variables
|
||||
for key, value in dify_app.config.items():
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import click
|
||||
from flask import current_app
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from configs import dify_config
|
||||
from constants.languages import languages
|
||||
from core.rag.datasource.vdb.vector_factory import Vector
|
||||
from core.rag.datasource.vdb.vector_type import VectorType
|
||||
@@ -112,7 +113,7 @@ def reset_encrypt_key_pair():
|
||||
After the reset, all LLM credentials will become invalid, requiring re-entry.
|
||||
Only support SELF_HOSTED mode.
|
||||
"""
|
||||
if current_app.config['EDITION'] != 'SELF_HOSTED':
|
||||
if dify_config.EDITION != 'SELF_HOSTED':
|
||||
click.echo(click.style('Sorry, only support SELF_HOSTED mode.', fg='red'))
|
||||
return
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .app_config import DifyConfig
|
||||
|
||||
dify_config = DifyConfig()
|
||||
@@ -1,4 +1,5 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from pydantic import Field, computed_field
|
||||
from pydantic_settings import SettingsConfigDict
|
||||
|
||||
from configs.deploy import DeploymentConfig
|
||||
from configs.enterprise import EnterpriseFeatureConfig
|
||||
@@ -9,9 +10,6 @@ from configs.packaging import PackagingInfo
|
||||
|
||||
|
||||
class DifyConfig(
|
||||
# based on pydantic-settings
|
||||
BaseSettings,
|
||||
|
||||
# Packaging info
|
||||
PackagingInfo,
|
||||
|
||||
@@ -31,13 +29,39 @@ class DifyConfig(
|
||||
# **Before using, please contact business@dify.ai by email to inquire about licensing matters.**
|
||||
EnterpriseFeatureConfig,
|
||||
):
|
||||
DEBUG: bool = Field(default=False, description='whether to enable debug mode.')
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
# read from dotenv format config file
|
||||
env_file='.env',
|
||||
env_file_encoding='utf-8',
|
||||
env_ignore_empty=True,
|
||||
frozen=True,
|
||||
|
||||
# ignore extra attributes
|
||||
extra='ignore',
|
||||
)
|
||||
|
||||
CODE_MAX_NUMBER: int = 9223372036854775807
|
||||
CODE_MIN_NUMBER: int = -9223372036854775808
|
||||
CODE_MAX_STRING_LENGTH: int = 80000
|
||||
CODE_MAX_STRING_ARRAY_LENGTH: int = 30
|
||||
CODE_MAX_OBJECT_ARRAY_LENGTH: int = 30
|
||||
CODE_MAX_NUMBER_ARRAY_LENGTH: int = 1000
|
||||
|
||||
HTTP_REQUEST_MAX_CONNECT_TIMEOUT: int = 300
|
||||
HTTP_REQUEST_MAX_READ_TIMEOUT: int = 600
|
||||
HTTP_REQUEST_MAX_WRITE_TIMEOUT: int = 600
|
||||
HTTP_REQUEST_NODE_MAX_BINARY_SIZE: int = 1024 * 1024 * 10
|
||||
|
||||
@computed_field
|
||||
def HTTP_REQUEST_NODE_READABLE_MAX_BINARY_SIZE(self) -> str:
|
||||
return f'{self.HTTP_REQUEST_NODE_MAX_BINARY_SIZE / 1024 / 1024:.2f}MB'
|
||||
|
||||
HTTP_REQUEST_NODE_MAX_TEXT_SIZE: int = 1024 * 1024
|
||||
|
||||
@computed_field
|
||||
def HTTP_REQUEST_NODE_READABLE_MAX_TEXT_SIZE(self) -> str:
|
||||
return f'{self.HTTP_REQUEST_NODE_MAX_TEXT_SIZE / 1024 / 1024:.2f}MB'
|
||||
|
||||
SSRF_PROXY_HTTP_URL: str | None = None
|
||||
SSRF_PROXY_HTTPS_URL: str | None = None
|
||||
@@ -1,7 +1,8 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class DeploymentConfig(BaseModel):
|
||||
class DeploymentConfig(BaseSettings):
|
||||
"""
|
||||
Deployment configs
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class EnterpriseFeatureConfig(BaseModel):
|
||||
class EnterpriseFeatureConfig(BaseSettings):
|
||||
"""
|
||||
Enterprise feature configs.
|
||||
**Before using, please contact business@dify.ai by email to inquire about licensing matters.**
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from configs.extra.notion_config import NotionConfig
|
||||
from configs.extra.sentry_config import SentryConfig
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class NotionConfig(BaseModel):
|
||||
class NotionConfig(BaseSettings):
|
||||
"""
|
||||
Notion integration configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, NonNegativeFloat
|
||||
from pydantic import Field, NonNegativeFloat
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class SentryConfig(BaseModel):
|
||||
class SentryConfig(BaseSettings):
|
||||
"""
|
||||
Sentry configs
|
||||
"""
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import AliasChoices, BaseModel, Field, NonNegativeInt, PositiveInt, computed_field
|
||||
from pydantic import AliasChoices, Field, NonNegativeInt, PositiveInt, computed_field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
from configs.feature.hosted_service import HostedServiceConfig
|
||||
|
||||
|
||||
class SecurityConfig(BaseModel):
|
||||
class SecurityConfig(BaseSettings):
|
||||
"""
|
||||
Secret Key configs
|
||||
"""
|
||||
@@ -17,8 +18,12 @@ class SecurityConfig(BaseModel):
|
||||
default=None,
|
||||
)
|
||||
|
||||
RESET_PASSWORD_TOKEN_EXPIRY_HOURS: PositiveInt = Field(
|
||||
description='Expiry time in hours for reset token',
|
||||
default=24,
|
||||
)
|
||||
|
||||
class AppExecutionConfig(BaseModel):
|
||||
class AppExecutionConfig(BaseSettings):
|
||||
"""
|
||||
App Execution configs
|
||||
"""
|
||||
@@ -28,7 +33,7 @@ class AppExecutionConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class CodeExecutionSandboxConfig(BaseModel):
|
||||
class CodeExecutionSandboxConfig(BaseSettings):
|
||||
"""
|
||||
Code Execution Sandbox configs
|
||||
"""
|
||||
@@ -43,36 +48,36 @@ class CodeExecutionSandboxConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class EndpointConfig(BaseModel):
|
||||
class EndpointConfig(BaseSettings):
|
||||
"""
|
||||
Module URL configs
|
||||
"""
|
||||
CONSOLE_API_URL: str = Field(
|
||||
description='The backend URL prefix of the console API.'
|
||||
'used to concatenate the login authorization callback or notion integration callback.',
|
||||
default='https://cloud.dify.ai',
|
||||
default='',
|
||||
)
|
||||
|
||||
CONSOLE_WEB_URL: str = Field(
|
||||
description='The front-end URL prefix of the console web.'
|
||||
'used to concatenate some front-end addresses and for CORS configuration use.',
|
||||
default='https://cloud.dify.ai',
|
||||
default='',
|
||||
)
|
||||
|
||||
SERVICE_API_URL: str = Field(
|
||||
description='Service API Url prefix.'
|
||||
'used to display Service API Base Url to the front-end.',
|
||||
default='https://api.dify.ai',
|
||||
default='',
|
||||
)
|
||||
|
||||
APP_WEB_URL: str = Field(
|
||||
description='WebApp Url prefix.'
|
||||
'used to display WebAPP API Base Url to the front-end.',
|
||||
default='https://udify.app',
|
||||
default='',
|
||||
)
|
||||
|
||||
|
||||
class FileAccessConfig(BaseModel):
|
||||
class FileAccessConfig(BaseSettings):
|
||||
"""
|
||||
File Access configs
|
||||
"""
|
||||
@@ -82,7 +87,7 @@ class FileAccessConfig(BaseModel):
|
||||
'Url is signed and has expiration time.',
|
||||
validation_alias=AliasChoices('FILES_URL', 'CONSOLE_API_URL'),
|
||||
alias_priority=1,
|
||||
default='https://cloud.dify.ai',
|
||||
default='',
|
||||
)
|
||||
|
||||
FILES_ACCESS_TIMEOUT: int = Field(
|
||||
@@ -91,7 +96,7 @@ class FileAccessConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class FileUploadConfig(BaseModel):
|
||||
class FileUploadConfig(BaseSettings):
|
||||
"""
|
||||
File Uploading configs
|
||||
"""
|
||||
@@ -116,7 +121,7 @@ class FileUploadConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HttpConfig(BaseModel):
|
||||
class HttpConfig(BaseSettings):
|
||||
"""
|
||||
HTTP configs
|
||||
"""
|
||||
@@ -136,7 +141,7 @@ class HttpConfig(BaseModel):
|
||||
def CONSOLE_CORS_ALLOW_ORIGINS(self) -> list[str]:
|
||||
return self.inner_CONSOLE_CORS_ALLOW_ORIGINS.split(',')
|
||||
|
||||
inner_WEB_API_CORS_ALLOW_ORIGINS: Optional[str] = Field(
|
||||
inner_WEB_API_CORS_ALLOW_ORIGINS: str = Field(
|
||||
description='',
|
||||
validation_alias=AliasChoices('WEB_API_CORS_ALLOW_ORIGINS'),
|
||||
default='*',
|
||||
@@ -148,7 +153,7 @@ class HttpConfig(BaseModel):
|
||||
return self.inner_WEB_API_CORS_ALLOW_ORIGINS.split(',')
|
||||
|
||||
|
||||
class InnerAPIConfig(BaseModel):
|
||||
class InnerAPIConfig(BaseSettings):
|
||||
"""
|
||||
Inner API configs
|
||||
"""
|
||||
@@ -163,7 +168,7 @@ class InnerAPIConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class LoggingConfig(BaseModel):
|
||||
class LoggingConfig(BaseSettings):
|
||||
"""
|
||||
Logging configs
|
||||
"""
|
||||
@@ -195,7 +200,7 @@ class LoggingConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class ModelLoadBalanceConfig(BaseModel):
|
||||
class ModelLoadBalanceConfig(BaseSettings):
|
||||
"""
|
||||
Model load balance configs
|
||||
"""
|
||||
@@ -205,7 +210,7 @@ class ModelLoadBalanceConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class BillingConfig(BaseModel):
|
||||
class BillingConfig(BaseSettings):
|
||||
"""
|
||||
Platform Billing Configurations
|
||||
"""
|
||||
@@ -215,7 +220,7 @@ class BillingConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class UpdateConfig(BaseModel):
|
||||
class UpdateConfig(BaseSettings):
|
||||
"""
|
||||
Update configs
|
||||
"""
|
||||
@@ -225,7 +230,7 @@ class UpdateConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class WorkflowConfig(BaseModel):
|
||||
class WorkflowConfig(BaseSettings):
|
||||
"""
|
||||
Workflow feature configs
|
||||
"""
|
||||
@@ -246,7 +251,7 @@ class WorkflowConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class OAuthConfig(BaseModel):
|
||||
class OAuthConfig(BaseSettings):
|
||||
"""
|
||||
oauth configs
|
||||
"""
|
||||
@@ -276,7 +281,7 @@ class OAuthConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class ModerationConfig(BaseModel):
|
||||
class ModerationConfig(BaseSettings):
|
||||
"""
|
||||
Moderation in app configs.
|
||||
"""
|
||||
@@ -288,7 +293,7 @@ class ModerationConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class ToolConfig(BaseModel):
|
||||
class ToolConfig(BaseSettings):
|
||||
"""
|
||||
Tool configs
|
||||
"""
|
||||
@@ -299,7 +304,7 @@ class ToolConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class MailConfig(BaseModel):
|
||||
class MailConfig(BaseSettings):
|
||||
"""
|
||||
Mail Configurations
|
||||
"""
|
||||
@@ -355,7 +360,7 @@ class MailConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class RagEtlConfig(BaseModel):
|
||||
class RagEtlConfig(BaseSettings):
|
||||
"""
|
||||
RAG ETL Configurations.
|
||||
"""
|
||||
@@ -381,7 +386,7 @@ class RagEtlConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class DataSetConfig(BaseModel):
|
||||
class DataSetConfig(BaseSettings):
|
||||
"""
|
||||
Dataset configs
|
||||
"""
|
||||
@@ -392,7 +397,7 @@ class DataSetConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class WorkspaceConfig(BaseModel):
|
||||
class WorkspaceConfig(BaseSettings):
|
||||
"""
|
||||
Workspace configs
|
||||
"""
|
||||
@@ -403,7 +408,7 @@ class WorkspaceConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class IndexingConfig(BaseModel):
|
||||
class IndexingConfig(BaseSettings):
|
||||
"""
|
||||
Indexing configs.
|
||||
"""
|
||||
@@ -414,7 +419,7 @@ class IndexingConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class ImageFormatConfig(BaseModel):
|
||||
class ImageFormatConfig(BaseSettings):
|
||||
MULTIMODAL_SEND_IMAGE_FORMAT: str = Field(
|
||||
description='multi model send image format, support base64, url, default is base64',
|
||||
default='base64',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, NonNegativeInt
|
||||
from pydantic import Field, NonNegativeInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class HostedOpenAiConfig(BaseModel):
|
||||
class HostedOpenAiConfig(BaseSettings):
|
||||
"""
|
||||
Hosted OpenAI service config
|
||||
"""
|
||||
@@ -68,7 +69,7 @@ class HostedOpenAiConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedAzureOpenAiConfig(BaseModel):
|
||||
class HostedAzureOpenAiConfig(BaseSettings):
|
||||
"""
|
||||
Hosted OpenAI service config
|
||||
"""
|
||||
@@ -94,7 +95,7 @@ class HostedAzureOpenAiConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedAnthropicConfig(BaseModel):
|
||||
class HostedAnthropicConfig(BaseSettings):
|
||||
"""
|
||||
Hosted Azure OpenAI service config
|
||||
"""
|
||||
@@ -125,7 +126,7 @@ class HostedAnthropicConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedMinmaxConfig(BaseModel):
|
||||
class HostedMinmaxConfig(BaseSettings):
|
||||
"""
|
||||
Hosted Minmax service config
|
||||
"""
|
||||
@@ -136,7 +137,7 @@ class HostedMinmaxConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedSparkConfig(BaseModel):
|
||||
class HostedSparkConfig(BaseSettings):
|
||||
"""
|
||||
Hosted Spark service config
|
||||
"""
|
||||
@@ -147,7 +148,7 @@ class HostedSparkConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedZhipuAIConfig(BaseModel):
|
||||
class HostedZhipuAIConfig(BaseSettings):
|
||||
"""
|
||||
Hosted Minmax service config
|
||||
"""
|
||||
@@ -158,7 +159,7 @@ class HostedZhipuAIConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedModerationConfig(BaseModel):
|
||||
class HostedModerationConfig(BaseSettings):
|
||||
"""
|
||||
Hosted Moderation service config
|
||||
"""
|
||||
@@ -174,7 +175,7 @@ class HostedModerationConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class HostedFetchAppTemplateConfig(BaseModel):
|
||||
class HostedFetchAppTemplateConfig(BaseSettings):
|
||||
"""
|
||||
Hosted Moderation service config
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, NonNegativeInt, PositiveInt, computed_field
|
||||
from pydantic import Field, NonNegativeInt, PositiveInt, computed_field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
from configs.middleware.cache.redis_config import RedisConfig
|
||||
from configs.middleware.storage.aliyun_oss_storage_config import AliyunOSSStorageConfig
|
||||
from configs.middleware.storage.amazon_s3_storage_config import S3StorageConfig
|
||||
from configs.middleware.storage.azure_blob_storage_config import AzureBlobStorageConfig
|
||||
from configs.middleware.storage.google_cloud_storage_config import GoogleCloudStorageConfig
|
||||
from configs.middleware.storage.oci_storage_config import OCIStorageConfig
|
||||
from configs.middleware.storage.tencent_cos_storage_config import TencentCloudCOSStorageConfig
|
||||
from configs.middleware.vdb.chroma_config import ChromaConfig
|
||||
from configs.middleware.vdb.milvus_config import MilvusConfig
|
||||
@@ -21,7 +23,7 @@ from configs.middleware.vdb.tidb_vector_config import TiDBVectorConfig
|
||||
from configs.middleware.vdb.weaviate_config import WeaviateConfig
|
||||
|
||||
|
||||
class StorageConfig(BaseModel):
|
||||
class StorageConfig(BaseSettings):
|
||||
STORAGE_TYPE: str = Field(
|
||||
description='storage type,'
|
||||
' default to `local`,'
|
||||
@@ -35,14 +37,14 @@ class StorageConfig(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class VectorStoreConfig(BaseModel):
|
||||
class VectorStoreConfig(BaseSettings):
|
||||
VECTOR_STORE: Optional[str] = Field(
|
||||
description='vector store type',
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
class KeywordStoreConfig(BaseModel):
|
||||
class KeywordStoreConfig(BaseSettings):
|
||||
KEYWORD_STORE: str = Field(
|
||||
description='keyword store type',
|
||||
default='jieba',
|
||||
@@ -80,6 +82,11 @@ class DatabaseConfig:
|
||||
default='',
|
||||
)
|
||||
|
||||
DB_EXTRAS: str = Field(
|
||||
description='db extras options. Example: keepalives_idle=60&keepalives=1',
|
||||
default='',
|
||||
)
|
||||
|
||||
SQLALCHEMY_DATABASE_URI_SCHEME: str = Field(
|
||||
description='db uri scheme',
|
||||
default='postgresql',
|
||||
@@ -88,7 +95,12 @@ class DatabaseConfig:
|
||||
@computed_field
|
||||
@property
|
||||
def SQLALCHEMY_DATABASE_URI(self) -> str:
|
||||
db_extras = f"?client_encoding={self.DB_CHARSET}" if self.DB_CHARSET else ""
|
||||
db_extras = (
|
||||
f"{self.DB_EXTRAS}&client_encoding={self.DB_CHARSET}"
|
||||
if self.DB_CHARSET
|
||||
else self.DB_EXTRAS
|
||||
).strip("&")
|
||||
db_extras = f"?{db_extras}" if db_extras else ""
|
||||
return (f"{self.SQLALCHEMY_DATABASE_URI_SCHEME}://"
|
||||
f"{self.DB_USERNAME}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_DATABASE}"
|
||||
f"{db_extras}")
|
||||
@@ -113,7 +125,7 @@ class DatabaseConfig:
|
||||
default=False,
|
||||
)
|
||||
|
||||
SQLALCHEMY_ECHO: bool = Field(
|
||||
SQLALCHEMY_ECHO: bool | str = Field(
|
||||
description='whether to enable SqlAlchemy echo',
|
||||
default=False,
|
||||
)
|
||||
@@ -143,7 +155,7 @@ class CeleryConfig(DatabaseConfig):
|
||||
|
||||
@computed_field
|
||||
@property
|
||||
def CELERY_RESULT_BACKEND(self) -> str:
|
||||
def CELERY_RESULT_BACKEND(self) -> str | None:
|
||||
return 'db+{}'.format(self.SQLALCHEMY_DATABASE_URI) \
|
||||
if self.CELERY_BACKEND == 'database' else self.CELERY_BROKER_URL
|
||||
|
||||
@@ -167,6 +179,7 @@ class MiddlewareConfig(
|
||||
GoogleCloudStorageConfig,
|
||||
TencentCloudCOSStorageConfig,
|
||||
S3StorageConfig,
|
||||
OCIStorageConfig,
|
||||
|
||||
# configs of vdb and vdb providers
|
||||
VectorStoreConfig,
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, NonNegativeInt, PositiveInt
|
||||
from pydantic import Field, NonNegativeInt, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class RedisConfig(BaseModel):
|
||||
class RedisConfig(BaseSettings):
|
||||
"""
|
||||
Redis configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class AliyunOSSStorageConfig(BaseModel):
|
||||
class AliyunOSSStorageConfig(BaseSettings):
|
||||
"""
|
||||
Aliyun storage configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class S3StorageConfig(BaseModel):
|
||||
class S3StorageConfig(BaseSettings):
|
||||
"""
|
||||
S3 storage configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class AzureBlobStorageConfig(BaseModel):
|
||||
class AzureBlobStorageConfig(BaseSettings):
|
||||
"""
|
||||
Azure Blob storage configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class GoogleCloudStorageConfig(BaseModel):
|
||||
class GoogleCloudStorageConfig(BaseSettings):
|
||||
"""
|
||||
Google Cloud storage configs
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class OCIStorageConfig(BaseSettings):
|
||||
"""
|
||||
OCI storage configs
|
||||
"""
|
||||
|
||||
OCI_ENDPOINT: Optional[str] = Field(
|
||||
description='OCI storage endpoint',
|
||||
default=None,
|
||||
)
|
||||
|
||||
OCI_REGION: Optional[str] = Field(
|
||||
description='OCI storage region',
|
||||
default=None,
|
||||
)
|
||||
|
||||
OCI_BUCKET_NAME: Optional[str] = Field(
|
||||
description='OCI storage bucket name',
|
||||
default=None,
|
||||
)
|
||||
|
||||
OCI_ACCESS_KEY: Optional[str] = Field(
|
||||
description='OCI storage access key',
|
||||
default=None,
|
||||
)
|
||||
|
||||
OCI_SECRET_KEY: Optional[str] = Field(
|
||||
description='OCI storage secret key',
|
||||
default=None,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class TencentCloudCOSStorageConfig(BaseModel):
|
||||
class TencentCloudCOSStorageConfig(BaseSettings):
|
||||
"""
|
||||
Tencent Cloud COS storage configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class ChromaConfig(BaseModel):
|
||||
class ChromaConfig(BaseSettings):
|
||||
"""
|
||||
Chroma configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class MilvusConfig(BaseModel):
|
||||
class MilvusConfig(BaseSettings):
|
||||
"""
|
||||
Milvus configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class OpenSearchConfig(BaseModel):
|
||||
class OpenSearchConfig(BaseSettings):
|
||||
"""
|
||||
OpenSearch configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class OracleConfig(BaseModel):
|
||||
class OracleConfig(BaseSettings):
|
||||
"""
|
||||
ORACLE configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class PGVectorConfig(BaseModel):
|
||||
class PGVectorConfig(BaseSettings):
|
||||
"""
|
||||
PGVector configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class PGVectoRSConfig(BaseModel):
|
||||
class PGVectoRSConfig(BaseSettings):
|
||||
"""
|
||||
PGVectoRS configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, NonNegativeInt, PositiveInt
|
||||
from pydantic import Field, NonNegativeInt, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class QdrantConfig(BaseModel):
|
||||
class QdrantConfig(BaseSettings):
|
||||
"""
|
||||
Qdrant configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class RelytConfig(BaseModel):
|
||||
class RelytConfig(BaseSettings):
|
||||
"""
|
||||
Relyt configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, NonNegativeInt, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class TencentVectorDBConfig(BaseModel):
|
||||
class TencentVectorDBConfig(BaseSettings):
|
||||
"""
|
||||
Tencent Vector configs
|
||||
"""
|
||||
@@ -24,7 +25,7 @@ class TencentVectorDBConfig(BaseModel):
|
||||
)
|
||||
|
||||
TENCENT_VECTOR_DB_USERNAME: Optional[str] = Field(
|
||||
description='Tencent Vector password',
|
||||
description='Tencent Vector username',
|
||||
default=None,
|
||||
)
|
||||
|
||||
@@ -38,7 +39,12 @@ class TencentVectorDBConfig(BaseModel):
|
||||
default=1,
|
||||
)
|
||||
|
||||
TENCENT_VECTOR_DB_REPLICAS: PositiveInt = Field(
|
||||
TENCENT_VECTOR_DB_REPLICAS: NonNegativeInt = Field(
|
||||
description='Tencent Vector replicas',
|
||||
default=2,
|
||||
)
|
||||
|
||||
TENCENT_VECTOR_DB_DATABASE: Optional[str] = Field(
|
||||
description='Tencent Vector Database',
|
||||
default=None,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class TiDBVectorConfig(BaseModel):
|
||||
class TiDBVectorConfig(BaseSettings):
|
||||
"""
|
||||
TiDB Vector configs
|
||||
"""
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, PositiveInt
|
||||
from pydantic import Field, PositiveInt
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class WeaviateConfig(BaseModel):
|
||||
class WeaviateConfig(BaseSettings):
|
||||
"""
|
||||
Weaviate configs
|
||||
"""
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class PackagingInfo(BaseModel):
|
||||
class PackagingInfo(BaseSettings):
|
||||
"""
|
||||
Packaging build information
|
||||
"""
|
||||
|
||||
CURRENT_VERSION: str = Field(
|
||||
description='Dify version',
|
||||
default='0.6.12',
|
||||
default='0.6.13',
|
||||
)
|
||||
|
||||
COMMIT_SHA: str = Field(
|
||||
|
||||
@@ -14,7 +14,7 @@ language_timezone_mapping = {
|
||||
'vi-VN': 'Asia/Ho_Chi_Minh',
|
||||
'ro-RO': 'Europe/Bucharest',
|
||||
'pl-PL': 'Europe/Warsaw',
|
||||
'hi-IN': 'Asia/Kolkata'
|
||||
'hi-IN': 'Asia/Kolkata',
|
||||
}
|
||||
|
||||
languages = list(language_timezone_mapping.keys())
|
||||
|
||||
+352
-568
File diff suppressed because one or more lines are too long
@@ -30,7 +30,7 @@ from .app import (
|
||||
)
|
||||
|
||||
# Import auth controllers
|
||||
from .auth import activate, data_source_bearer_auth, data_source_oauth, login, oauth
|
||||
from .auth import activate, data_source_bearer_auth, data_source_oauth, forgot_password, login, oauth
|
||||
|
||||
# Import billing controllers
|
||||
from .billing import billing
|
||||
|
||||
@@ -190,6 +190,10 @@ class AppExportApi(Resource):
|
||||
@get_app_model
|
||||
def get(self, app_model):
|
||||
"""Export app"""
|
||||
# The role of the current user in the ta table must be admin, owner, or editor
|
||||
if not current_user.is_editor:
|
||||
raise Forbidden()
|
||||
|
||||
app_service = AppService()
|
||||
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,7 @@ from flask_login import current_user
|
||||
from flask_restful import Resource
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from configs import dify_config
|
||||
from controllers.console import api
|
||||
from libs.login import login_required
|
||||
from libs.oauth_data_source import NotionOAuth
|
||||
@@ -16,11 +17,11 @@ from ..wraps import account_initialization_required
|
||||
|
||||
def get_oauth_providers():
|
||||
with current_app.app_context():
|
||||
notion_oauth = NotionOAuth(client_id=current_app.config.get('NOTION_CLIENT_ID'),
|
||||
client_secret=current_app.config.get(
|
||||
'NOTION_CLIENT_SECRET'),
|
||||
redirect_uri=current_app.config.get(
|
||||
'CONSOLE_API_URL') + '/console/api/oauth/data-source/callback/notion')
|
||||
if not dify_config.NOTION_CLIENT_ID or not dify_config.NOTION_CLIENT_SECRET:
|
||||
return {}
|
||||
notion_oauth = NotionOAuth(client_id=dify_config.NOTION_CLIENT_ID,
|
||||
client_secret=dify_config.NOTION_CLIENT_SECRET,
|
||||
redirect_uri=dify_config.CONSOLE_API_URL + '/console/api/oauth/data-source/callback/notion')
|
||||
|
||||
OAUTH_PROVIDERS = {
|
||||
'notion': notion_oauth
|
||||
@@ -39,8 +40,10 @@ class OAuthDataSource(Resource):
|
||||
print(vars(oauth_provider))
|
||||
if not oauth_provider:
|
||||
return {'error': 'Invalid provider'}, 400
|
||||
if current_app.config.get('NOTION_INTEGRATION_TYPE') == 'internal':
|
||||
internal_secret = current_app.config.get('NOTION_INTERNAL_SECRET')
|
||||
if dify_config.NOTION_INTEGRATION_TYPE == 'internal':
|
||||
internal_secret = dify_config.NOTION_INTERNAL_SECRET
|
||||
if not internal_secret:
|
||||
return {'error': 'Internal secret is not set'},
|
||||
oauth_provider.save_internal_access_token(internal_secret)
|
||||
return { 'data': '' }
|
||||
else:
|
||||
@@ -60,13 +63,13 @@ class OAuthDataSourceCallback(Resource):
|
||||
if 'code' in request.args:
|
||||
code = request.args.get('code')
|
||||
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?type=notion&code={code}')
|
||||
return redirect(f'{dify_config.CONSOLE_WEB_URL}?type=notion&code={code}')
|
||||
elif 'error' in request.args:
|
||||
error = request.args.get('error')
|
||||
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?type=notion&error={error}')
|
||||
return redirect(f'{dify_config.CONSOLE_WEB_URL}?type=notion&error={error}')
|
||||
else:
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?type=notion&error=Access denied')
|
||||
return redirect(f'{dify_config.CONSOLE_WEB_URL}?type=notion&error=Access denied')
|
||||
|
||||
|
||||
class OAuthDataSourceBinding(Resource):
|
||||
|
||||
@@ -5,3 +5,28 @@ class ApiKeyAuthFailedError(BaseHTTPException):
|
||||
error_code = 'auth_failed'
|
||||
description = "{message}"
|
||||
code = 500
|
||||
|
||||
|
||||
class InvalidEmailError(BaseHTTPException):
|
||||
error_code = 'invalid_email'
|
||||
description = "The email address is not valid."
|
||||
code = 400
|
||||
|
||||
|
||||
class PasswordMismatchError(BaseHTTPException):
|
||||
error_code = 'password_mismatch'
|
||||
description = "The passwords do not match."
|
||||
code = 400
|
||||
|
||||
|
||||
class InvalidTokenError(BaseHTTPException):
|
||||
error_code = 'invalid_or_expired_token'
|
||||
description = "The token is invalid or has expired."
|
||||
code = 400
|
||||
|
||||
|
||||
class PasswordResetRateLimitExceededError(BaseHTTPException):
|
||||
error_code = 'password_reset_rate_limit_exceeded'
|
||||
description = "Password reset rate limit exceeded. Try again later."
|
||||
code = 429
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import base64
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
from flask_restful import Resource, reqparse
|
||||
|
||||
from controllers.console import api
|
||||
from controllers.console.auth.error import (
|
||||
InvalidEmailError,
|
||||
InvalidTokenError,
|
||||
PasswordMismatchError,
|
||||
PasswordResetRateLimitExceededError,
|
||||
)
|
||||
from controllers.console.setup import setup_required
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import email as email_validate
|
||||
from libs.password import hash_password, valid_password
|
||||
from models.account import Account
|
||||
from services.account_service import AccountService
|
||||
from services.errors.account import RateLimitExceededError
|
||||
|
||||
|
||||
class ForgotPasswordSendEmailApi(Resource):
|
||||
|
||||
@setup_required
|
||||
def post(self):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('email', type=str, required=True, location='json')
|
||||
args = parser.parse_args()
|
||||
|
||||
email = args['email']
|
||||
|
||||
if not email_validate(email):
|
||||
raise InvalidEmailError()
|
||||
|
||||
account = Account.query.filter_by(email=email).first()
|
||||
|
||||
if account:
|
||||
try:
|
||||
AccountService.send_reset_password_email(account=account)
|
||||
except RateLimitExceededError:
|
||||
logging.warning(f"Rate limit exceeded for email: {account.email}")
|
||||
raise PasswordResetRateLimitExceededError()
|
||||
else:
|
||||
# Return success to avoid revealing email registration status
|
||||
logging.warning(f"Attempt to reset password for unregistered email: {email}")
|
||||
|
||||
return {"result": "success"}
|
||||
|
||||
|
||||
class ForgotPasswordCheckApi(Resource):
|
||||
|
||||
@setup_required
|
||||
def post(self):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('token', type=str, required=True, nullable=False, location='json')
|
||||
args = parser.parse_args()
|
||||
token = args['token']
|
||||
|
||||
reset_data = AccountService.get_reset_password_data(token)
|
||||
|
||||
if reset_data is None:
|
||||
return {'is_valid': False, 'email': None}
|
||||
return {'is_valid': True, 'email': reset_data.get('email')}
|
||||
|
||||
|
||||
class ForgotPasswordResetApi(Resource):
|
||||
|
||||
@setup_required
|
||||
def post(self):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('token', type=str, required=True, nullable=False, location='json')
|
||||
parser.add_argument('new_password', type=valid_password, required=True, nullable=False, location='json')
|
||||
parser.add_argument('password_confirm', type=valid_password, required=True, nullable=False, location='json')
|
||||
args = parser.parse_args()
|
||||
|
||||
new_password = args['new_password']
|
||||
password_confirm = args['password_confirm']
|
||||
|
||||
if str(new_password).strip() != str(password_confirm).strip():
|
||||
raise PasswordMismatchError()
|
||||
|
||||
token = args['token']
|
||||
reset_data = AccountService.get_reset_password_data(token)
|
||||
|
||||
if reset_data is None:
|
||||
raise InvalidTokenError()
|
||||
|
||||
AccountService.revoke_reset_password_token(token)
|
||||
|
||||
salt = secrets.token_bytes(16)
|
||||
base64_salt = base64.b64encode(salt).decode()
|
||||
|
||||
password_hashed = hash_password(new_password, salt)
|
||||
base64_password_hashed = base64.b64encode(password_hashed).decode()
|
||||
|
||||
account = Account.query.filter_by(email=reset_data.get('email')).first()
|
||||
account.password = base64_password_hashed
|
||||
account.password_salt = base64_salt
|
||||
db.session.commit()
|
||||
|
||||
return {'result': 'success'}
|
||||
|
||||
|
||||
api.add_resource(ForgotPasswordSendEmailApi, '/forgot-password')
|
||||
api.add_resource(ForgotPasswordCheckApi, '/forgot-password/validity')
|
||||
api.add_resource(ForgotPasswordResetApi, '/forgot-password/resets')
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import cast
|
||||
|
||||
import flask_login
|
||||
from flask import current_app, request
|
||||
from flask import request
|
||||
from flask_restful import Resource, reqparse
|
||||
|
||||
import services
|
||||
@@ -56,14 +56,14 @@ class LogoutApi(Resource):
|
||||
class ResetPasswordApi(Resource):
|
||||
@setup_required
|
||||
def get(self):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('email', type=email, required=True, location='json')
|
||||
args = parser.parse_args()
|
||||
# parser = reqparse.RequestParser()
|
||||
# parser.add_argument('email', type=email, required=True, location='json')
|
||||
# args = parser.parse_args()
|
||||
|
||||
# import mailchimp_transactional as MailchimpTransactional
|
||||
# from mailchimp_transactional.api_client import ApiClientError
|
||||
|
||||
account = {'email': args['email']}
|
||||
# account = {'email': args['email']}
|
||||
# account = AccountService.get_by_email(args['email'])
|
||||
# if account is None:
|
||||
# raise ValueError('Email not found')
|
||||
@@ -71,22 +71,22 @@ class ResetPasswordApi(Resource):
|
||||
# AccountService.update_password(account, new_password)
|
||||
|
||||
# todo: Send email
|
||||
MAILCHIMP_API_KEY = current_app.config['MAILCHIMP_TRANSACTIONAL_API_KEY']
|
||||
# MAILCHIMP_API_KEY = current_app.config['MAILCHIMP_TRANSACTIONAL_API_KEY']
|
||||
# mailchimp = MailchimpTransactional(MAILCHIMP_API_KEY)
|
||||
|
||||
message = {
|
||||
'from_email': 'noreply@example.com',
|
||||
'to': [{'email': account.email}],
|
||||
'subject': 'Reset your Dify password',
|
||||
'html': """
|
||||
<p>Dear User,</p>
|
||||
<p>The Dify team has generated a new password for you, details as follows:</p>
|
||||
<p><strong>{new_password}</strong></p>
|
||||
<p>Please change your password to log in as soon as possible.</p>
|
||||
<p>Regards,</p>
|
||||
<p>The Dify Team</p>
|
||||
"""
|
||||
}
|
||||
# message = {
|
||||
# 'from_email': 'noreply@example.com',
|
||||
# 'to': [{'email': account['email']}],
|
||||
# 'subject': 'Reset your Dify password',
|
||||
# 'html': """
|
||||
# <p>Dear User,</p>
|
||||
# <p>The Dify team has generated a new password for you, details as follows:</p>
|
||||
# <p><strong>{new_password}</strong></p>
|
||||
# <p>Please change your password to log in as soon as possible.</p>
|
||||
# <p>Regards,</p>
|
||||
# <p>The Dify Team</p>
|
||||
# """
|
||||
# }
|
||||
|
||||
# response = mailchimp.messages.send({
|
||||
# 'message': message,
|
||||
|
||||
@@ -6,6 +6,7 @@ import requests
|
||||
from flask import current_app, redirect, request
|
||||
from flask_restful import Resource
|
||||
|
||||
from configs import dify_config
|
||||
from constants.languages import languages
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import get_remote_ip
|
||||
@@ -18,22 +19,24 @@ from .. import api
|
||||
|
||||
def get_oauth_providers():
|
||||
with current_app.app_context():
|
||||
github_oauth = GitHubOAuth(client_id=current_app.config.get('GITHUB_CLIENT_ID'),
|
||||
client_secret=current_app.config.get(
|
||||
'GITHUB_CLIENT_SECRET'),
|
||||
redirect_uri=current_app.config.get(
|
||||
'CONSOLE_API_URL') + '/console/api/oauth/authorize/github')
|
||||
if not dify_config.GITHUB_CLIENT_ID or not dify_config.GITHUB_CLIENT_SECRET:
|
||||
github_oauth = None
|
||||
else:
|
||||
github_oauth = GitHubOAuth(
|
||||
client_id=dify_config.GITHUB_CLIENT_ID,
|
||||
client_secret=dify_config.GITHUB_CLIENT_SECRET,
|
||||
redirect_uri=dify_config.CONSOLE_API_URL + '/console/api/oauth/authorize/github',
|
||||
)
|
||||
if not dify_config.GOOGLE_CLIENT_ID or not dify_config.GOOGLE_CLIENT_SECRET:
|
||||
google_oauth = None
|
||||
else:
|
||||
google_oauth = GoogleOAuth(
|
||||
client_id=dify_config.GOOGLE_CLIENT_ID,
|
||||
client_secret=dify_config.GOOGLE_CLIENT_SECRET,
|
||||
redirect_uri=dify_config.CONSOLE_API_URL + '/console/api/oauth/authorize/google',
|
||||
)
|
||||
|
||||
google_oauth = GoogleOAuth(client_id=current_app.config.get('GOOGLE_CLIENT_ID'),
|
||||
client_secret=current_app.config.get(
|
||||
'GOOGLE_CLIENT_SECRET'),
|
||||
redirect_uri=current_app.config.get(
|
||||
'CONSOLE_API_URL') + '/console/api/oauth/authorize/google')
|
||||
|
||||
OAUTH_PROVIDERS = {
|
||||
'github': github_oauth,
|
||||
'google': google_oauth
|
||||
}
|
||||
OAUTH_PROVIDERS = {'github': github_oauth, 'google': google_oauth}
|
||||
return OAUTH_PROVIDERS
|
||||
|
||||
|
||||
@@ -63,8 +66,7 @@ class OAuthCallback(Resource):
|
||||
token = oauth_provider.get_access_token(code)
|
||||
user_info = oauth_provider.get_user_info(token)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logging.exception(
|
||||
f"An error occurred during the OAuth process with {provider}: {e.response.text}")
|
||||
logging.exception(f'An error occurred during the OAuth process with {provider}: {e.response.text}')
|
||||
return {'error': 'OAuth process failed'}, 400
|
||||
|
||||
account = _generate_account(provider, user_info)
|
||||
@@ -81,7 +83,7 @@ class OAuthCallback(Resource):
|
||||
|
||||
token = AccountService.login(account, ip_address=get_remote_ip(request))
|
||||
|
||||
return redirect(f'{current_app.config.get("CONSOLE_WEB_URL")}?console_token={token}')
|
||||
return redirect(f'{dify_config.CONSOLE_WEB_URL}?console_token={token}')
|
||||
|
||||
|
||||
def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) -> Optional[Account]:
|
||||
@@ -101,11 +103,7 @@ def _generate_account(provider: str, user_info: OAuthUserInfo):
|
||||
# Create account
|
||||
account_name = user_info.name if user_info.name else 'Dify'
|
||||
account = RegisterService.register(
|
||||
email=user_info.email,
|
||||
name=account_name,
|
||||
password=None,
|
||||
open_id=user_info.id,
|
||||
provider=provider
|
||||
email=user_info.email, name=account_name, password=None, open_id=user_info.id, provider=provider
|
||||
)
|
||||
|
||||
# Set interface language
|
||||
|
||||
@@ -226,6 +226,15 @@ class DatasetApi(Resource):
|
||||
except services.errors.dataset.DatasetInUseError:
|
||||
raise DatasetInUseError()
|
||||
|
||||
class DatasetUseCheckApi(Resource):
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self, dataset_id):
|
||||
dataset_id_str = str(dataset_id)
|
||||
|
||||
dataset_is_using = DatasetService.dataset_use_check(dataset_id_str)
|
||||
return {'is_using': dataset_is_using}, 200
|
||||
|
||||
class DatasetQueryApi(Resource):
|
||||
|
||||
@@ -562,6 +571,7 @@ class DatasetErrorDocs(Resource):
|
||||
|
||||
api.add_resource(DatasetListApi, '/datasets')
|
||||
api.add_resource(DatasetApi, '/datasets/<uuid:dataset_id>')
|
||||
api.add_resource(DatasetUseCheckApi, '/datasets/<uuid:dataset_id>/use-check')
|
||||
api.add_resource(DatasetQueryApi, '/datasets/<uuid:dataset_id>/queries')
|
||||
api.add_resource(DatasetErrorDocs, '/datasets/<uuid:dataset_id>/error-docs')
|
||||
api.add_resource(DatasetIndexingEstimateApi, '/datasets/indexing-estimate')
|
||||
|
||||
@@ -3,11 +3,10 @@ from functools import wraps
|
||||
from flask import current_app, request
|
||||
from flask_restful import Resource, reqparse
|
||||
|
||||
from extensions.ext_database import db
|
||||
from libs.helper import email, get_remote_ip, str_len
|
||||
from libs.password import valid_password
|
||||
from models.model import DifySetup
|
||||
from services.account_service import AccountService, RegisterService, TenantService
|
||||
from services.account_service import RegisterService, TenantService
|
||||
|
||||
from . import api
|
||||
from .error import AlreadySetupError, NotInitValidateError, NotSetupError
|
||||
@@ -51,28 +50,17 @@ class SetupApi(Resource):
|
||||
required=True, location='json')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Register
|
||||
account = RegisterService.register(
|
||||
# setup
|
||||
RegisterService.setup(
|
||||
email=args['email'],
|
||||
name=args['name'],
|
||||
password=args['password']
|
||||
password=args['password'],
|
||||
ip_address=get_remote_ip(request)
|
||||
)
|
||||
|
||||
TenantService.create_owner_tenant_if_not_exist(account)
|
||||
|
||||
setup()
|
||||
AccountService.update_last_login(account, ip_address=get_remote_ip(request))
|
||||
|
||||
return {'result': 'success'}, 201
|
||||
|
||||
|
||||
def setup():
|
||||
dify_setup = DifySetup(
|
||||
version=current_app.config['CURRENT_VERSION']
|
||||
)
|
||||
db.session.add(dify_setup)
|
||||
|
||||
|
||||
def setup_required(view):
|
||||
@wraps(view)
|
||||
def decorated(*args, **kwargs):
|
||||
|
||||
@@ -245,6 +245,8 @@ class AccountIntegrateApi(Resource):
|
||||
return {'data': integrate_data}
|
||||
|
||||
|
||||
|
||||
|
||||
# Register API resources
|
||||
api.add_resource(AccountInitApi, '/account/init')
|
||||
api.add_resource(AccountProfileApi, '/account/profile')
|
||||
|
||||
@@ -114,6 +114,10 @@ class VariableEntity(BaseModel):
|
||||
default: Optional[str] = None
|
||||
hint: Optional[str] = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.variable
|
||||
|
||||
|
||||
class ExternalDataVariableEntity(BaseModel):
|
||||
"""
|
||||
|
||||
@@ -1,52 +1,56 @@
|
||||
from collections.abc import Mapping
|
||||
from typing import Any, Optional
|
||||
|
||||
from core.app.app_config.entities import AppConfig, VariableEntity
|
||||
|
||||
|
||||
class BaseAppGenerator:
|
||||
def _get_cleaned_inputs(self, user_inputs: dict, app_config: AppConfig):
|
||||
if user_inputs is None:
|
||||
user_inputs = {}
|
||||
|
||||
filtered_inputs = {}
|
||||
|
||||
def _get_cleaned_inputs(self, user_inputs: Optional[Mapping[str, Any]], app_config: AppConfig) -> Mapping[str, Any]:
|
||||
user_inputs = user_inputs or {}
|
||||
# Filter input variables from form configuration, handle required fields, default values, and option values
|
||||
variables = app_config.variables
|
||||
for variable_config in variables:
|
||||
variable = variable_config.variable
|
||||
|
||||
if (variable not in user_inputs
|
||||
or user_inputs[variable] is None
|
||||
or (isinstance(user_inputs[variable], str) and user_inputs[variable] == '')):
|
||||
if variable_config.required:
|
||||
raise ValueError(f"{variable} is required in input form")
|
||||
else:
|
||||
filtered_inputs[variable] = variable_config.default if variable_config.default is not None else ""
|
||||
continue
|
||||
|
||||
value = user_inputs[variable]
|
||||
|
||||
if value is not None:
|
||||
if variable_config.type != VariableEntity.Type.NUMBER and not isinstance(value, str):
|
||||
raise ValueError(f"{variable} in input form must be a string")
|
||||
elif variable_config.type == VariableEntity.Type.NUMBER and isinstance(value, str):
|
||||
if '.' in value:
|
||||
value = float(value)
|
||||
else:
|
||||
value = int(value)
|
||||
|
||||
if variable_config.type == VariableEntity.Type.SELECT:
|
||||
options = variable_config.options if variable_config.options is not None else []
|
||||
if value not in options:
|
||||
raise ValueError(f"{variable} in input form must be one of the following: {options}")
|
||||
elif variable_config.type in [VariableEntity.Type.TEXT_INPUT, VariableEntity.Type.PARAGRAPH]:
|
||||
if variable_config.max_length is not None:
|
||||
max_length = variable_config.max_length
|
||||
if len(value) > max_length:
|
||||
raise ValueError(f'{variable} in input form must be less than {max_length} characters')
|
||||
|
||||
if value and isinstance(value, str):
|
||||
filtered_inputs[variable] = value.replace('\x00', '')
|
||||
else:
|
||||
filtered_inputs[variable] = value if value is not None else None
|
||||
|
||||
filtered_inputs = {var.name: self._validate_input(inputs=user_inputs, var=var) for var in variables}
|
||||
filtered_inputs = {k: self._sanitize_value(v) for k, v in filtered_inputs.items()}
|
||||
return filtered_inputs
|
||||
|
||||
def _validate_input(self, *, inputs: Mapping[str, Any], var: VariableEntity):
|
||||
user_input_value = inputs.get(var.name)
|
||||
if var.required and not user_input_value:
|
||||
raise ValueError(f'{var.name} is required in input form')
|
||||
if not var.required and not user_input_value:
|
||||
# TODO: should we return None here if the default value is None?
|
||||
return var.default or ''
|
||||
if (
|
||||
var.type
|
||||
in (
|
||||
VariableEntity.Type.TEXT_INPUT,
|
||||
VariableEntity.Type.SELECT,
|
||||
VariableEntity.Type.PARAGRAPH,
|
||||
)
|
||||
and user_input_value
|
||||
and not isinstance(user_input_value, str)
|
||||
):
|
||||
raise ValueError(f"(type '{var.type}') {var.name} in input form must be a string")
|
||||
if var.type == VariableEntity.Type.NUMBER and isinstance(user_input_value, str):
|
||||
# may raise ValueError if user_input_value is not a valid number
|
||||
try:
|
||||
if '.' in user_input_value:
|
||||
return float(user_input_value)
|
||||
else:
|
||||
return int(user_input_value)
|
||||
except ValueError:
|
||||
raise ValueError(f"{var.name} in input form must be a valid number")
|
||||
if var.type == VariableEntity.Type.SELECT:
|
||||
options = var.options or []
|
||||
if user_input_value not in options:
|
||||
raise ValueError(f'{var.name} in input form must be one of the following: {options}')
|
||||
elif var.type in (VariableEntity.Type.TEXT_INPUT, VariableEntity.Type.PARAGRAPH):
|
||||
if var.max_length and user_input_value and len(user_input_value) > var.max_length:
|
||||
raise ValueError(f'{var.name} in input form must be less than {var.max_length} characters')
|
||||
|
||||
return user_input_value
|
||||
|
||||
def _sanitize_value(self, value: Any) -> Any:
|
||||
if isinstance(value, str):
|
||||
return value.replace('\x00', '')
|
||||
return value
|
||||
|
||||
@@ -94,7 +94,6 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
||||
application_generate_entity=application_generate_entity,
|
||||
invoke_from=invoke_from,
|
||||
stream=stream,
|
||||
call_depth=call_depth,
|
||||
)
|
||||
|
||||
def _generate(
|
||||
@@ -104,7 +103,6 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
||||
application_generate_entity: WorkflowAppGenerateEntity,
|
||||
invoke_from: InvokeFrom,
|
||||
stream: bool = True,
|
||||
call_depth: int = 0
|
||||
) -> Union[dict, Generator[dict, None, None]]:
|
||||
"""
|
||||
Generate App response.
|
||||
@@ -166,10 +164,10 @@ class WorkflowAppGenerator(BaseAppGenerator):
|
||||
"""
|
||||
if not node_id:
|
||||
raise ValueError('node_id is required')
|
||||
|
||||
|
||||
if args.get('inputs') is None:
|
||||
raise ValueError('inputs is required')
|
||||
|
||||
|
||||
extras = {
|
||||
"auto_generate_conversation_name": False
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import os
|
||||
|
||||
import requests
|
||||
|
||||
from configs import dify_config
|
||||
from models.api_based_extension import APIBasedExtensionPoint
|
||||
|
||||
|
||||
@@ -31,10 +30,10 @@ class APIBasedExtensionRequestor:
|
||||
try:
|
||||
# proxy support for security
|
||||
proxies = None
|
||||
if os.environ.get("SSRF_PROXY_HTTP_URL") and os.environ.get("SSRF_PROXY_HTTPS_URL"):
|
||||
if dify_config.SSRF_PROXY_HTTP_URL and dify_config.SSRF_PROXY_HTTPS_URL:
|
||||
proxies = {
|
||||
'http': os.environ.get("SSRF_PROXY_HTTP_URL"),
|
||||
'https': os.environ.get("SSRF_PROXY_HTTPS_URL"),
|
||||
'http': dify_config.SSRF_PROXY_HTTP_URL,
|
||||
'https': dify_config.SSRF_PROXY_HTTPS_URL,
|
||||
}
|
||||
|
||||
response = requests.request(
|
||||
|
||||
@@ -186,7 +186,7 @@ class MessageFileParser:
|
||||
}
|
||||
|
||||
response = requests.head(url, headers=headers, allow_redirects=True)
|
||||
if response.status_code == 200:
|
||||
if response.status_code in {200, 304}:
|
||||
return True, ""
|
||||
else:
|
||||
return False, "URL does not exist."
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from enum import Enum
|
||||
from threading import Lock
|
||||
@@ -9,6 +8,7 @@ from httpx import get, post
|
||||
from pydantic import BaseModel
|
||||
from yarl import URL
|
||||
|
||||
from configs import dify_config
|
||||
from core.helper.code_executor.entities import CodeDependency
|
||||
from core.helper.code_executor.javascript.javascript_transformer import NodeJsTemplateTransformer
|
||||
from core.helper.code_executor.jinja2.jinja2_transformer import Jinja2TemplateTransformer
|
||||
@@ -18,8 +18,8 @@ from core.helper.code_executor.template_transformer import TemplateTransformer
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Code Executor
|
||||
CODE_EXECUTION_ENDPOINT = os.environ.get('CODE_EXECUTION_ENDPOINT', 'http://sandbox:8194')
|
||||
CODE_EXECUTION_API_KEY = os.environ.get('CODE_EXECUTION_API_KEY', 'dify-sandbox')
|
||||
CODE_EXECUTION_ENDPOINT = dify_config.CODE_EXECUTION_ENDPOINT
|
||||
CODE_EXECUTION_API_KEY = dify_config.CODE_EXECUTION_API_KEY
|
||||
|
||||
CODE_EXECUTION_TIMEOUT= (10, 60)
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ from core.model_runtime.entities.message_entities import (
|
||||
UserPromptMessage,
|
||||
)
|
||||
from extensions.ext_database import db
|
||||
from models.model import AppMode, Conversation, Message
|
||||
from models.model import AppMode, Conversation, Message, MessageFile
|
||||
from models.workflow import WorkflowRun
|
||||
|
||||
|
||||
class TokenBufferMemory:
|
||||
@@ -30,33 +31,46 @@ class TokenBufferMemory:
|
||||
app_record = self.conversation.app
|
||||
|
||||
# fetch limited messages, and return reversed
|
||||
query = db.session.query(Message).filter(
|
||||
query = db.session.query(
|
||||
Message.id,
|
||||
Message.query,
|
||||
Message.answer,
|
||||
Message.created_at,
|
||||
Message.workflow_run_id
|
||||
).filter(
|
||||
Message.conversation_id == self.conversation.id,
|
||||
Message.answer != ''
|
||||
).order_by(Message.created_at.desc())
|
||||
|
||||
if message_limit and message_limit > 0:
|
||||
messages = query.limit(message_limit).all()
|
||||
message_limit = message_limit if message_limit <= 500 else 500
|
||||
else:
|
||||
messages = query.all()
|
||||
message_limit = 500
|
||||
|
||||
messages = query.limit(message_limit).all()
|
||||
|
||||
messages = list(reversed(messages))
|
||||
message_file_parser = MessageFileParser(
|
||||
tenant_id=app_record.tenant_id,
|
||||
app_id=app_record.id
|
||||
)
|
||||
|
||||
prompt_messages = []
|
||||
for message in messages:
|
||||
files = message.message_files
|
||||
files = db.session.query(MessageFile).filter(MessageFile.message_id == message.id).all()
|
||||
if files:
|
||||
file_extra_config = None
|
||||
if self.conversation.mode not in [AppMode.ADVANCED_CHAT.value, AppMode.WORKFLOW.value]:
|
||||
file_extra_config = FileUploadConfigManager.convert(message.app_model_config.to_dict())
|
||||
file_extra_config = FileUploadConfigManager.convert(self.conversation.model_config)
|
||||
else:
|
||||
file_extra_config = FileUploadConfigManager.convert(
|
||||
message.workflow_run.workflow.features_dict,
|
||||
is_vision=False
|
||||
)
|
||||
if message.workflow_run_id:
|
||||
workflow_run = (db.session.query(WorkflowRun)
|
||||
.filter(WorkflowRun.id == message.workflow_run_id).first())
|
||||
|
||||
if workflow_run:
|
||||
file_extra_config = FileUploadConfigManager.convert(
|
||||
workflow_run.workflow.features_dict,
|
||||
is_vision=False
|
||||
)
|
||||
|
||||
if file_extra_config:
|
||||
file_objs = message_file_parser.transform_message_files(
|
||||
@@ -136,4 +150,4 @@ class TokenBufferMemory:
|
||||
message = f"{role}: {m.content}"
|
||||
string_messages.append(message)
|
||||
|
||||
return "\n".join(string_messages)
|
||||
return "\n".join(string_messages)
|
||||
|
||||
@@ -5,6 +5,8 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
- stream-tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -5,6 +5,8 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
- stream-tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -5,6 +5,8 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
- stream-tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -5,6 +5,8 @@ model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
- tool-call
|
||||
- stream-tool-call
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
# standard import
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import time
|
||||
from collections.abc import Generator
|
||||
from typing import Optional, Union, cast
|
||||
|
||||
# 3rd import
|
||||
import boto3
|
||||
import requests
|
||||
from anthropic import AnthropicBedrock, Stream
|
||||
from anthropic.types import (
|
||||
ContentBlockDeltaEvent,
|
||||
Message,
|
||||
MessageDeltaEvent,
|
||||
MessageStartEvent,
|
||||
MessageStopEvent,
|
||||
MessageStreamEvent,
|
||||
)
|
||||
from botocore.config import Config
|
||||
from botocore.exceptions import (
|
||||
ClientError,
|
||||
@@ -27,7 +19,8 @@ from botocore.exceptions import (
|
||||
)
|
||||
from cohere import ChatMessage
|
||||
|
||||
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage
|
||||
# local import
|
||||
from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta
|
||||
from core.model_runtime.entities.message_entities import (
|
||||
AssistantPromptMessage,
|
||||
ImagePromptMessageContent,
|
||||
@@ -36,9 +29,9 @@ from core.model_runtime.entities.message_entities import (
|
||||
PromptMessageTool,
|
||||
SystemPromptMessage,
|
||||
TextPromptMessageContent,
|
||||
ToolPromptMessage,
|
||||
UserPromptMessage,
|
||||
)
|
||||
from core.model_runtime.entities.model_entities import PriceType
|
||||
from core.model_runtime.errors.invoke import (
|
||||
InvokeAuthorizationError,
|
||||
InvokeBadRequestError,
|
||||
@@ -73,10 +66,10 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
:param user: unique user id
|
||||
:return: full response or stream response chunk generator result
|
||||
"""
|
||||
|
||||
# invoke anthropic models via anthropic official SDK
|
||||
# TODO: consolidate different invocation methods for models based on base model capabilities
|
||||
# invoke anthropic models via boto3 client
|
||||
if "anthropic" in model:
|
||||
return self._generate_anthropic(model, credentials, prompt_messages, model_parameters, stop, stream, user)
|
||||
return self._generate_anthropic(model, credentials, prompt_messages, model_parameters, stop, stream, user, tools)
|
||||
# invoke Cohere models via boto3 client
|
||||
if "cohere.command-r" in model:
|
||||
return self._generate_cohere_chat(model, credentials, prompt_messages, model_parameters, stop, stream, user, tools)
|
||||
@@ -159,7 +152,7 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
|
||||
|
||||
def _generate_anthropic(self, model: str, credentials: dict, prompt_messages: list[PromptMessage], model_parameters: dict,
|
||||
stop: Optional[list[str]] = None, stream: bool = True, user: Optional[str] = None) -> Union[LLMResult, Generator]:
|
||||
stop: Optional[list[str]] = None, stream: bool = True, user: Optional[str] = None, tools: Optional[list[PromptMessageTool]] = None,) -> Union[LLMResult, Generator]:
|
||||
"""
|
||||
Invoke Anthropic large language model
|
||||
|
||||
@@ -171,48 +164,35 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
:param stream: is stream response
|
||||
:return: full response or stream response chunk generator result
|
||||
"""
|
||||
# use Anthropic official SDK references
|
||||
# - https://docs.anthropic.com/claude/reference/claude-on-amazon-bedrock
|
||||
# - https://github.com/anthropics/anthropic-sdk-python
|
||||
client = AnthropicBedrock(
|
||||
aws_access_key=credentials.get("aws_access_key_id"),
|
||||
aws_secret_key=credentials.get("aws_secret_access_key"),
|
||||
aws_region=credentials["aws_region"],
|
||||
)
|
||||
bedrock_client = boto3.client(service_name='bedrock-runtime',
|
||||
aws_access_key_id=credentials.get("aws_access_key_id"),
|
||||
aws_secret_access_key=credentials.get("aws_secret_access_key"),
|
||||
region_name=credentials["aws_region"])
|
||||
|
||||
extra_model_kwargs = {}
|
||||
if stop:
|
||||
extra_model_kwargs['stop_sequences'] = stop
|
||||
system, prompt_message_dicts = self._convert_converse_prompt_messages(prompt_messages)
|
||||
inference_config, additional_model_fields = self._convert_converse_api_model_parameters(model_parameters, stop)
|
||||
|
||||
# Notice: If you request the current version of the SDK to the bedrock server,
|
||||
# you will get the following error message and you need to wait for the service or SDK to be updated.
|
||||
# Response: Error code: 400
|
||||
# {'message': 'Malformed input request: #: subject must not be valid against schema
|
||||
# {"required":["messages"]}#: extraneous key [metadata] is not permitted, please reformat your input and try again.'}
|
||||
# TODO: Open in the future when the interface is properly supported
|
||||
# if user:
|
||||
# ref: https://github.com/anthropics/anthropic-sdk-python/blob/e84645b07ca5267066700a104b4d8d6a8da1383d/src/anthropic/resources/messages.py#L465
|
||||
# extra_model_kwargs['metadata'] = message_create_params.Metadata(user_id=user)
|
||||
parameters = {
|
||||
'modelId': model,
|
||||
'messages': prompt_message_dicts,
|
||||
'inferenceConfig': inference_config,
|
||||
'additionalModelRequestFields': additional_model_fields,
|
||||
}
|
||||
|
||||
system, prompt_message_dicts = self._convert_claude_prompt_messages(prompt_messages)
|
||||
if system and len(system) > 0:
|
||||
parameters['system'] = system
|
||||
|
||||
if system:
|
||||
extra_model_kwargs['system'] = system
|
||||
|
||||
response = client.messages.create(
|
||||
model=model,
|
||||
messages=prompt_message_dicts,
|
||||
stream=stream,
|
||||
**model_parameters,
|
||||
**extra_model_kwargs
|
||||
)
|
||||
if tools:
|
||||
parameters['toolConfig'] = self._convert_converse_tool_config(tools=tools)
|
||||
|
||||
if stream:
|
||||
return self._handle_claude_stream_response(model, credentials, response, prompt_messages)
|
||||
response = bedrock_client.converse_stream(**parameters)
|
||||
return self._handle_converse_stream_response(model, credentials, response, prompt_messages)
|
||||
else:
|
||||
response = bedrock_client.converse(**parameters)
|
||||
return self._handle_converse_response(model, credentials, response, prompt_messages)
|
||||
|
||||
return self._handle_claude_response(model, credentials, response, prompt_messages)
|
||||
|
||||
def _handle_claude_response(self, model: str, credentials: dict, response: Message,
|
||||
def _handle_converse_response(self, model: str, credentials: dict, response: dict,
|
||||
prompt_messages: list[PromptMessage]) -> LLMResult:
|
||||
"""
|
||||
Handle llm chat response
|
||||
@@ -223,17 +203,16 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
:param prompt_messages: prompt messages
|
||||
:return: full response chunk generator result
|
||||
"""
|
||||
|
||||
# transform assistant message to prompt message
|
||||
assistant_prompt_message = AssistantPromptMessage(
|
||||
content=response.content[0].text
|
||||
content=response['output']['message']['content'][0]['text']
|
||||
)
|
||||
|
||||
# calculate num tokens
|
||||
if response.usage:
|
||||
if response['usage']:
|
||||
# transform usage
|
||||
prompt_tokens = response.usage.input_tokens
|
||||
completion_tokens = response.usage.output_tokens
|
||||
prompt_tokens = response['usage']['inputTokens']
|
||||
completion_tokens = response['usage']['outputTokens']
|
||||
else:
|
||||
# calculate num tokens
|
||||
prompt_tokens = self.get_num_tokens(model, credentials, prompt_messages)
|
||||
@@ -242,17 +221,15 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
# transform usage
|
||||
usage = self._calc_response_usage(model, credentials, prompt_tokens, completion_tokens)
|
||||
|
||||
# transform response
|
||||
response = LLMResult(
|
||||
model=response.model,
|
||||
result = LLMResult(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
message=assistant_prompt_message,
|
||||
usage=usage
|
||||
usage=usage,
|
||||
)
|
||||
return result
|
||||
|
||||
return response
|
||||
|
||||
def _handle_claude_stream_response(self, model: str, credentials: dict, response: Stream[MessageStreamEvent],
|
||||
def _handle_converse_stream_response(self, model: str, credentials: dict, response: dict,
|
||||
prompt_messages: list[PromptMessage], ) -> Generator:
|
||||
"""
|
||||
Handle llm chat stream response
|
||||
@@ -271,129 +248,143 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
output_tokens = 0
|
||||
finish_reason = None
|
||||
index = 0
|
||||
tool_calls: list[AssistantPromptMessage.ToolCall] = []
|
||||
tool_use = {}
|
||||
|
||||
for chunk in response:
|
||||
if isinstance(chunk, MessageStartEvent):
|
||||
return_model = chunk.message.model
|
||||
input_tokens = chunk.message.usage.input_tokens
|
||||
elif isinstance(chunk, MessageDeltaEvent):
|
||||
output_tokens = chunk.usage.output_tokens
|
||||
finish_reason = chunk.delta.stop_reason
|
||||
elif isinstance(chunk, MessageStopEvent):
|
||||
for chunk in response['stream']:
|
||||
if 'messageStart' in chunk:
|
||||
return_model = model
|
||||
elif 'messageStop' in chunk:
|
||||
finish_reason = chunk['messageStop']['stopReason']
|
||||
elif 'contentBlockStart' in chunk:
|
||||
tool = chunk['contentBlockStart']['start']['toolUse']
|
||||
tool_use['toolUseId'] = tool['toolUseId']
|
||||
tool_use['name'] = tool['name']
|
||||
elif 'metadata' in chunk:
|
||||
input_tokens = chunk['metadata']['usage']['inputTokens']
|
||||
output_tokens = chunk['metadata']['usage']['outputTokens']
|
||||
usage = self._calc_response_usage(model, credentials, input_tokens, output_tokens)
|
||||
yield LLMResultChunk(
|
||||
model=return_model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=index + 1,
|
||||
index=index,
|
||||
message=AssistantPromptMessage(
|
||||
content=''
|
||||
content='',
|
||||
tool_calls=tool_calls
|
||||
),
|
||||
finish_reason=finish_reason,
|
||||
usage=usage
|
||||
)
|
||||
)
|
||||
elif isinstance(chunk, ContentBlockDeltaEvent):
|
||||
chunk_text = chunk.delta.text if chunk.delta.text else ''
|
||||
full_assistant_content += chunk_text
|
||||
assistant_prompt_message = AssistantPromptMessage(
|
||||
content=chunk_text if chunk_text else '',
|
||||
)
|
||||
index = chunk.index
|
||||
yield LLMResultChunk(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=index,
|
||||
message=assistant_prompt_message,
|
||||
elif 'contentBlockDelta' in chunk:
|
||||
delta = chunk['contentBlockDelta']['delta']
|
||||
if 'text' in delta:
|
||||
chunk_text = delta['text'] if delta['text'] else ''
|
||||
full_assistant_content += chunk_text
|
||||
assistant_prompt_message = AssistantPromptMessage(
|
||||
content=chunk_text if chunk_text else '',
|
||||
)
|
||||
)
|
||||
index = chunk['contentBlockDelta']['contentBlockIndex']
|
||||
yield LLMResultChunk(
|
||||
model=model,
|
||||
prompt_messages=prompt_messages,
|
||||
delta=LLMResultChunkDelta(
|
||||
index=index+1,
|
||||
message=assistant_prompt_message,
|
||||
)
|
||||
)
|
||||
elif 'toolUse' in delta:
|
||||
if 'input' not in tool_use:
|
||||
tool_use['input'] = ''
|
||||
tool_use['input'] += delta['toolUse']['input']
|
||||
elif 'contentBlockStop' in chunk:
|
||||
if 'input' in tool_use:
|
||||
tool_call = AssistantPromptMessage.ToolCall(
|
||||
id=tool_use['toolUseId'],
|
||||
type='function',
|
||||
function=AssistantPromptMessage.ToolCall.ToolCallFunction(
|
||||
name=tool_use['name'],
|
||||
arguments=tool_use['input']
|
||||
)
|
||||
)
|
||||
tool_calls.append(tool_call)
|
||||
tool_use = {}
|
||||
|
||||
except Exception as ex:
|
||||
raise InvokeError(str(ex))
|
||||
|
||||
def _convert_converse_api_model_parameters(self, model_parameters: dict, stop: Optional[list[str]] = None) -> tuple[dict, dict]:
|
||||
inference_config = {}
|
||||
additional_model_fields = {}
|
||||
if 'max_tokens' in model_parameters:
|
||||
inference_config['maxTokens'] = model_parameters['max_tokens']
|
||||
|
||||
def _calc_claude_response_usage(self, model: str, credentials: dict, prompt_tokens: int, completion_tokens: int) -> LLMUsage:
|
||||
"""
|
||||
Calculate response usage
|
||||
if 'temperature' in model_parameters:
|
||||
inference_config['temperature'] = model_parameters['temperature']
|
||||
|
||||
if 'top_p' in model_parameters:
|
||||
inference_config['topP'] = model_parameters['temperature']
|
||||
|
||||
:param model: model name
|
||||
:param credentials: model credentials
|
||||
:param prompt_tokens: prompt tokens
|
||||
:param completion_tokens: completion tokens
|
||||
:return: usage
|
||||
"""
|
||||
# get prompt price info
|
||||
prompt_price_info = self.get_price(
|
||||
model=model,
|
||||
credentials=credentials,
|
||||
price_type=PriceType.INPUT,
|
||||
tokens=prompt_tokens,
|
||||
)
|
||||
if stop:
|
||||
inference_config['stopSequences'] = stop
|
||||
|
||||
if 'top_k' in model_parameters:
|
||||
additional_model_fields['top_k'] = model_parameters['top_k']
|
||||
|
||||
return inference_config, additional_model_fields
|
||||
|
||||
# get completion price info
|
||||
completion_price_info = self.get_price(
|
||||
model=model,
|
||||
credentials=credentials,
|
||||
price_type=PriceType.OUTPUT,
|
||||
tokens=completion_tokens
|
||||
)
|
||||
|
||||
# transform usage
|
||||
usage = LLMUsage(
|
||||
prompt_tokens=prompt_tokens,
|
||||
prompt_unit_price=prompt_price_info.unit_price,
|
||||
prompt_price_unit=prompt_price_info.unit,
|
||||
prompt_price=prompt_price_info.total_amount,
|
||||
completion_tokens=completion_tokens,
|
||||
completion_unit_price=completion_price_info.unit_price,
|
||||
completion_price_unit=completion_price_info.unit,
|
||||
completion_price=completion_price_info.total_amount,
|
||||
total_tokens=prompt_tokens + completion_tokens,
|
||||
total_price=prompt_price_info.total_amount + completion_price_info.total_amount,
|
||||
currency=prompt_price_info.currency,
|
||||
latency=time.perf_counter() - self.started_at
|
||||
)
|
||||
|
||||
return usage
|
||||
|
||||
def _convert_claude_prompt_messages(self, prompt_messages: list[PromptMessage]) -> tuple[str, list[dict]]:
|
||||
def _convert_converse_prompt_messages(self, prompt_messages: list[PromptMessage]) -> tuple[str, list[dict]]:
|
||||
"""
|
||||
Convert prompt messages to dict list and system
|
||||
"""
|
||||
|
||||
system = ""
|
||||
first_loop = True
|
||||
system = []
|
||||
for message in prompt_messages:
|
||||
if isinstance(message, SystemPromptMessage):
|
||||
message.content=message.content.strip()
|
||||
if first_loop:
|
||||
system=message.content
|
||||
first_loop=False
|
||||
else:
|
||||
system+="\n"
|
||||
system+=message.content
|
||||
system.append({"text": message.content})
|
||||
|
||||
prompt_message_dicts = []
|
||||
for message in prompt_messages:
|
||||
if not isinstance(message, SystemPromptMessage):
|
||||
prompt_message_dicts.append(self._convert_claude_prompt_message_to_dict(message))
|
||||
prompt_message_dicts.append(self._convert_prompt_message_to_dict(message))
|
||||
|
||||
return system, prompt_message_dicts
|
||||
|
||||
def _convert_claude_prompt_message_to_dict(self, message: PromptMessage) -> dict:
|
||||
def _convert_converse_tool_config(self, tools: Optional[list[PromptMessageTool]] = None) -> dict:
|
||||
tool_config = {}
|
||||
configs = []
|
||||
if tools:
|
||||
for tool in tools:
|
||||
configs.append(
|
||||
{
|
||||
"toolSpec": {
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"inputSchema": {
|
||||
"json": tool.parameters
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
tool_config["tools"] = configs
|
||||
return tool_config
|
||||
|
||||
def _convert_prompt_message_to_dict(self, message: PromptMessage) -> dict:
|
||||
"""
|
||||
Convert PromptMessage to dict
|
||||
"""
|
||||
if isinstance(message, UserPromptMessage):
|
||||
message = cast(UserPromptMessage, message)
|
||||
if isinstance(message.content, str):
|
||||
message_dict = {"role": "user", "content": message.content}
|
||||
message_dict = {"role": "user", "content": [{'text': message.content}]}
|
||||
else:
|
||||
sub_messages = []
|
||||
for message_content in message.content:
|
||||
if message_content.type == PromptMessageContentType.TEXT:
|
||||
message_content = cast(TextPromptMessageContent, message_content)
|
||||
sub_message_dict = {
|
||||
"type": "text",
|
||||
"text": message_content.data
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
@@ -404,24 +395,24 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
try:
|
||||
image_content = requests.get(message_content.data).content
|
||||
mime_type, _ = mimetypes.guess_type(message_content.data)
|
||||
base64_data = base64.b64encode(image_content).decode('utf-8')
|
||||
except Exception as ex:
|
||||
raise ValueError(f"Failed to fetch image data from url {message_content.data}, {ex}")
|
||||
else:
|
||||
data_split = message_content.data.split(";base64,")
|
||||
mime_type = data_split[0].replace("data:", "")
|
||||
base64_data = data_split[1]
|
||||
image_content = base64.b64decode(base64_data)
|
||||
|
||||
if mime_type not in ["image/jpeg", "image/png", "image/gif", "image/webp"]:
|
||||
raise ValueError(f"Unsupported image type {mime_type}, "
|
||||
f"only support image/jpeg, image/png, image/gif, and image/webp")
|
||||
|
||||
sub_message_dict = {
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": mime_type,
|
||||
"data": base64_data
|
||||
"image": {
|
||||
"format": mime_type.replace('image/', ''),
|
||||
"source": {
|
||||
"bytes": image_content
|
||||
}
|
||||
}
|
||||
}
|
||||
sub_messages.append(sub_message_dict)
|
||||
@@ -429,10 +420,32 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
message_dict = {"role": "user", "content": sub_messages}
|
||||
elif isinstance(message, AssistantPromptMessage):
|
||||
message = cast(AssistantPromptMessage, message)
|
||||
message_dict = {"role": "assistant", "content": message.content}
|
||||
if message.tool_calls:
|
||||
message_dict = {
|
||||
"role": "assistant", "content":[{
|
||||
"toolUse": {
|
||||
"toolUseId": message.tool_calls[0].id,
|
||||
"name": message.tool_calls[0].function.name,
|
||||
"input": json.loads(message.tool_calls[0].function.arguments)
|
||||
}
|
||||
}]
|
||||
}
|
||||
else:
|
||||
message_dict = {"role": "assistant", "content": [{'text': message.content}]}
|
||||
elif isinstance(message, SystemPromptMessage):
|
||||
message = cast(SystemPromptMessage, message)
|
||||
message_dict = {"role": "system", "content": message.content}
|
||||
message_dict = [{'text': message.content}]
|
||||
elif isinstance(message, ToolPromptMessage):
|
||||
message = cast(ToolPromptMessage, message)
|
||||
message_dict = {
|
||||
"role": "user",
|
||||
"content": [{
|
||||
"toolResult": {
|
||||
"toolUseId": message.tool_call_id,
|
||||
"content": [{"json": {"text": message.content}}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
else:
|
||||
raise ValueError(f"Got unknown type {message}")
|
||||
|
||||
@@ -451,11 +464,13 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
"""
|
||||
prefix = model.split('.')[0]
|
||||
model_name = model.split('.')[1]
|
||||
|
||||
if isinstance(prompt_messages, str):
|
||||
prompt = prompt_messages
|
||||
else:
|
||||
prompt = self._convert_messages_to_prompt(prompt_messages, prefix, model_name)
|
||||
|
||||
|
||||
return self._get_num_tokens_by_gpt2(prompt)
|
||||
|
||||
def validate_credentials(self, model: str, credentials: dict) -> None:
|
||||
@@ -539,11 +554,16 @@ class BedrockLargeLanguageModel(LargeLanguageModel):
|
||||
content = message.content
|
||||
|
||||
if isinstance(message, UserPromptMessage):
|
||||
message_text = f"{human_prompt_prefix} {content} {human_prompt_postfix}"
|
||||
body = content
|
||||
if (isinstance(content, list)):
|
||||
body = "".join([c.data for c in content if c.type == PromptMessageContentType.TEXT])
|
||||
message_text = f"{human_prompt_prefix} {body} {human_prompt_postfix}"
|
||||
elif isinstance(message, AssistantPromptMessage):
|
||||
message_text = f"{ai_prompt} {content}"
|
||||
elif isinstance(message, SystemPromptMessage):
|
||||
message_text = content
|
||||
elif isinstance(message, ToolPromptMessage):
|
||||
message_text = f"{human_prompt_prefix} {message.content}"
|
||||
else:
|
||||
raise ValueError(f"Got unknown type {message}")
|
||||
|
||||
|
||||
@@ -111,5 +111,71 @@ ModelConfigs = {
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
},
|
||||
'Moonshot-v1-8k': {
|
||||
'req_params': {
|
||||
'max_prompt_tokens': 8192,
|
||||
'max_new_tokens': 4096,
|
||||
},
|
||||
'model_properties': {
|
||||
'context_size': 8192,
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
},
|
||||
'Moonshot-v1-32k': {
|
||||
'req_params': {
|
||||
'max_prompt_tokens': 32768,
|
||||
'max_new_tokens': 16384,
|
||||
},
|
||||
'model_properties': {
|
||||
'context_size': 32768,
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
},
|
||||
'Moonshot-v1-128k': {
|
||||
'req_params': {
|
||||
'max_prompt_tokens': 131072,
|
||||
'max_new_tokens': 65536,
|
||||
},
|
||||
'model_properties': {
|
||||
'context_size': 131072,
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
},
|
||||
'GLM3-130B': {
|
||||
'req_params': {
|
||||
'max_prompt_tokens': 8192,
|
||||
'max_new_tokens': 4096,
|
||||
},
|
||||
'model_properties': {
|
||||
'context_size': 8192,
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
},
|
||||
'GLM3-130B-Fin': {
|
||||
'req_params': {
|
||||
'max_prompt_tokens': 8192,
|
||||
'max_new_tokens': 4096,
|
||||
},
|
||||
'model_properties': {
|
||||
'context_size': 8192,
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
},
|
||||
'Mistral-7B': {
|
||||
'req_params': {
|
||||
'max_prompt_tokens': 8192,
|
||||
'max_new_tokens': 2048,
|
||||
},
|
||||
'model_properties': {
|
||||
'context_size': 8192,
|
||||
'mode': 'chat',
|
||||
},
|
||||
'features': [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,12 +120,6 @@ model_credential_schema:
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Skylark2-pro-4k
|
||||
value: Skylark2-pro-4k
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Llama3-8B
|
||||
value: Llama3-8B
|
||||
@@ -138,6 +132,42 @@ model_credential_schema:
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Moonshot-v1-8k
|
||||
value: Moonshot-v1-8k
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Moonshot-v1-32k
|
||||
value: Moonshot-v1-32k
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Moonshot-v1-128k
|
||||
value: Moonshot-v1-128k
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: GLM3-130B
|
||||
value: GLM3-130B
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: GLM3-130B-Fin
|
||||
value: GLM3-130B-Fin
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Mistral-7B
|
||||
value: Mistral-7B
|
||||
show_on:
|
||||
- variable: __model_type
|
||||
value: llm
|
||||
- label:
|
||||
en_US: Doubao-embedding
|
||||
value: Doubao-embedding
|
||||
@@ -181,7 +211,7 @@ model_credential_schema:
|
||||
zh_Hans: 模型上下文长度
|
||||
en_US: Model Context Size
|
||||
type: text-input
|
||||
default: '4096'
|
||||
default: "4096"
|
||||
placeholder:
|
||||
zh_Hans: 输入您的模型上下文长度
|
||||
en_US: Enter your Model Context Size
|
||||
@@ -195,7 +225,7 @@ model_credential_schema:
|
||||
label:
|
||||
zh_Hans: 最大 token 上限
|
||||
en_US: Upper Bound for Max Tokens
|
||||
default: '4096'
|
||||
default: "4096"
|
||||
type: text-input
|
||||
placeholder:
|
||||
zh_Hans: 输入您的模型最大 token 上限
|
||||
|
||||
@@ -29,10 +29,8 @@ class ZhipuAI(HttpClient):
|
||||
http_client: httpx.Client | None = None,
|
||||
custom_headers: Mapping[str, str] | None = None
|
||||
) -> None:
|
||||
# if api_key is None:
|
||||
# api_key = os.environ.get("ZHIPUAI_API_KEY")
|
||||
if api_key is None:
|
||||
raise ZhipuAIError("未提供api_key,请通过参数或环境变量提供")
|
||||
raise ZhipuAIError("No api_key provided, please provide it through parameters or environment variables")
|
||||
self.api_key = api_key
|
||||
|
||||
if base_url is None:
|
||||
|
||||
@@ -7,6 +7,8 @@ from typing import Any, Union, cast
|
||||
import httpx
|
||||
import pydantic
|
||||
from httpx import URL, Timeout
|
||||
from tenacity import retry
|
||||
from tenacity.stop import stop_after_attempt
|
||||
|
||||
from . import _errors
|
||||
from ._base_type import NOT_GIVEN, Body, Data, Headers, NotGiven, Query, RequestFiles, ResponseT
|
||||
@@ -221,6 +223,7 @@ class HttpClient:
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
|
||||
@retry(stop=stop_after_attempt(ZHIPUAI_DEFAULT_MAX_RETRIES))
|
||||
def request(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic.version import VERSION as PYDANTIC_VERSION
|
||||
|
||||
PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
|
||||
|
||||
if PYDANTIC_V2:
|
||||
from pydantic_core import Url as Url
|
||||
|
||||
def _model_dump(
|
||||
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
|
||||
) -> Any:
|
||||
return model.model_dump(mode=mode, **kwargs)
|
||||
else:
|
||||
from pydantic import AnyUrl as Url # noqa: F401
|
||||
|
||||
def _model_dump(
|
||||
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
|
||||
) -> Any:
|
||||
return model.dict(**kwargs)
|
||||
@@ -8,16 +8,20 @@ from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6
|
||||
from pathlib import Path, PurePath
|
||||
from re import Pattern
|
||||
from types import GeneratorType
|
||||
from typing import Any, Optional, Union
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic.networks import AnyUrl, NameEmail
|
||||
from pydantic.types import SecretBytes, SecretStr
|
||||
from pydantic_core import Url
|
||||
from pydantic_extra_types.color import Color
|
||||
|
||||
from ._compat import PYDANTIC_V2, Url, _model_dump
|
||||
|
||||
def _model_dump(
|
||||
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
|
||||
) -> Any:
|
||||
return model.model_dump(mode=mode, **kwargs)
|
||||
|
||||
# Taken from Pydantic v1 as is
|
||||
def isoformat(o: Union[datetime.date, datetime.time]) -> str:
|
||||
@@ -109,12 +113,6 @@ def jsonable_encoder(
|
||||
if isinstance(obj, encoder_type):
|
||||
return encoder_instance(obj)
|
||||
if isinstance(obj, BaseModel):
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
encoders: dict[Any, Any] = {}
|
||||
if not PYDANTIC_V2:
|
||||
encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined]
|
||||
if custom_encoder:
|
||||
encoders.update(custom_encoder)
|
||||
obj_dict = _model_dump(
|
||||
obj,
|
||||
mode="json",
|
||||
@@ -131,8 +129,6 @@ def jsonable_encoder(
|
||||
obj_dict,
|
||||
exclude_none=exclude_none,
|
||||
exclude_defaults=exclude_defaults,
|
||||
# TODO: remove when deprecating Pydantic v1
|
||||
custom_encoder=encoders,
|
||||
sqlalchemy_safe=sqlalchemy_safe,
|
||||
)
|
||||
if dataclasses.is_dataclass(obj):
|
||||
|
||||
@@ -27,8 +27,8 @@ class LangfuseConfig(BaseTracingConfig):
|
||||
def set_value(cls, v, info: ValidationInfo):
|
||||
if v is None or v == "":
|
||||
v = 'https://api.langfuse.com'
|
||||
if not v.startswith('https://'):
|
||||
raise ValueError('host must start with https://')
|
||||
if not v.startswith('https://') and not v.startswith('http://'):
|
||||
raise ValueError('host must start with https:// or http://')
|
||||
|
||||
return v
|
||||
|
||||
|
||||
@@ -107,9 +107,21 @@ class LangFuseDataTrace(BaseTraceInstance):
|
||||
|
||||
# through workflow_run_id get all_nodes_execution
|
||||
workflow_nodes_executions = (
|
||||
db.session.query(WorkflowNodeExecution)
|
||||
db.session.query(
|
||||
WorkflowNodeExecution.id,
|
||||
WorkflowNodeExecution.tenant_id,
|
||||
WorkflowNodeExecution.app_id,
|
||||
WorkflowNodeExecution.title,
|
||||
WorkflowNodeExecution.node_type,
|
||||
WorkflowNodeExecution.status,
|
||||
WorkflowNodeExecution.inputs,
|
||||
WorkflowNodeExecution.outputs,
|
||||
WorkflowNodeExecution.created_at,
|
||||
WorkflowNodeExecution.elapsed_time,
|
||||
WorkflowNodeExecution.process_data,
|
||||
WorkflowNodeExecution.execution_metadata,
|
||||
)
|
||||
.filter(WorkflowNodeExecution.workflow_run_id == trace_info.workflow_run_id)
|
||||
.order_by(WorkflowNodeExecution.index.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -121,7 +133,9 @@ class LangFuseDataTrace(BaseTraceInstance):
|
||||
node_type = node_execution.node_type
|
||||
status = node_execution.status
|
||||
if node_type == "llm":
|
||||
inputs = json.loads(node_execution.process_data).get("prompts", {})
|
||||
inputs = json.loads(node_execution.process_data).get(
|
||||
"prompts", {}
|
||||
) if node_execution.process_data else {}
|
||||
else:
|
||||
inputs = json.loads(node_execution.inputs) if node_execution.inputs else {}
|
||||
outputs = (
|
||||
@@ -213,7 +227,9 @@ class LangFuseDataTrace(BaseTraceInstance):
|
||||
end_user_data: EndUser = db.session.query(EndUser).filter(
|
||||
EndUser.id == message_data.from_end_user_id
|
||||
).first()
|
||||
user_id = end_user_data.session_id
|
||||
if end_user_data is not None:
|
||||
user_id = end_user_data.session_id
|
||||
metadata["user_id"] = user_id
|
||||
|
||||
trace_data = LangfuseTrace(
|
||||
id=message_id,
|
||||
|
||||
@@ -100,9 +100,21 @@ class LangSmithDataTrace(BaseTraceInstance):
|
||||
|
||||
# through workflow_run_id get all_nodes_execution
|
||||
workflow_nodes_executions = (
|
||||
db.session.query(WorkflowNodeExecution)
|
||||
db.session.query(
|
||||
WorkflowNodeExecution.id,
|
||||
WorkflowNodeExecution.tenant_id,
|
||||
WorkflowNodeExecution.app_id,
|
||||
WorkflowNodeExecution.title,
|
||||
WorkflowNodeExecution.node_type,
|
||||
WorkflowNodeExecution.status,
|
||||
WorkflowNodeExecution.inputs,
|
||||
WorkflowNodeExecution.outputs,
|
||||
WorkflowNodeExecution.created_at,
|
||||
WorkflowNodeExecution.elapsed_time,
|
||||
WorkflowNodeExecution.process_data,
|
||||
WorkflowNodeExecution.execution_metadata,
|
||||
)
|
||||
.filter(WorkflowNodeExecution.workflow_run_id == trace_info.workflow_run_id)
|
||||
.order_by(WorkflowNodeExecution.index.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -114,7 +126,9 @@ class LangSmithDataTrace(BaseTraceInstance):
|
||||
node_type = node_execution.node_type
|
||||
status = node_execution.status
|
||||
if node_type == "llm":
|
||||
inputs = json.loads(node_execution.process_data).get("prompts", {})
|
||||
inputs = json.loads(node_execution.process_data).get(
|
||||
"prompts", {}
|
||||
) if node_execution.process_data else {}
|
||||
else:
|
||||
inputs = json.loads(node_execution.inputs) if node_execution.inputs else {}
|
||||
outputs = (
|
||||
@@ -181,13 +195,15 @@ class LangSmithDataTrace(BaseTraceInstance):
|
||||
message_id = message_data.id
|
||||
|
||||
user_id = message_data.from_account_id
|
||||
metadata["user_id"] = user_id
|
||||
|
||||
if message_data.from_end_user_id:
|
||||
end_user_data: EndUser = db.session.query(EndUser).filter(
|
||||
EndUser.id == message_data.from_end_user_id
|
||||
).first().session_id
|
||||
end_user_id = end_user_data.session_id
|
||||
metadata["end_user_id"] = end_user_id
|
||||
metadata["user_id"] = user_id
|
||||
).first()
|
||||
if end_user_data is not None:
|
||||
end_user_id = end_user_data.session_id
|
||||
metadata["end_user_id"] = end_user_id
|
||||
|
||||
message_run = LangSmithRunModel(
|
||||
input_tokens=trace_info.message_tokens,
|
||||
|
||||
@@ -352,10 +352,17 @@ class TraceTask:
|
||||
query = workflow_run_inputs.get("query") or workflow_run_inputs.get("sys.query") or ""
|
||||
|
||||
# get workflow_app_log_id
|
||||
workflow_app_log_data = db.session.query(WorkflowAppLog).filter_by(workflow_run_id=workflow_run.id).first()
|
||||
workflow_app_log_data = db.session.query(WorkflowAppLog).filter_by(
|
||||
tenant_id=tenant_id,
|
||||
app_id=workflow_run.app_id,
|
||||
workflow_run_id=workflow_run.id
|
||||
).first()
|
||||
workflow_app_log_id = str(workflow_app_log_data.id) if workflow_app_log_data else None
|
||||
# get message_id
|
||||
message_data = db.session.query(Message.id).filter_by(workflow_run_id=workflow_run_id).first()
|
||||
message_data = db.session.query(Message.id).filter_by(
|
||||
conversation_id=conversation_id,
|
||||
workflow_run_id=workflow_run_id
|
||||
).first()
|
||||
message_id = str(message_data.id) if message_data else None
|
||||
|
||||
metadata = {
|
||||
|
||||
@@ -140,11 +140,10 @@ class NotionExtractor(BaseExtractor):
|
||||
|
||||
def _get_notion_block_data(self, page_id: str) -> list[str]:
|
||||
result_lines_arr = []
|
||||
cur_block_id = page_id
|
||||
start_cursor = None
|
||||
block_url = BLOCK_CHILD_URL_TMPL.format(block_id=page_id)
|
||||
while True:
|
||||
block_url = BLOCK_CHILD_URL_TMPL.format(block_id=cur_block_id)
|
||||
query_dict: dict[str, Any] = {}
|
||||
|
||||
query_dict: dict[str, Any] = {} if not start_cursor else {'start_cursor': start_cursor}
|
||||
res = requests.request(
|
||||
"GET",
|
||||
block_url,
|
||||
@@ -153,7 +152,7 @@ class NotionExtractor(BaseExtractor):
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
json=query_dict
|
||||
params=query_dict
|
||||
)
|
||||
data = res.json()
|
||||
for result in data["results"]:
|
||||
@@ -191,16 +190,16 @@ class NotionExtractor(BaseExtractor):
|
||||
if data["next_cursor"] is None:
|
||||
break
|
||||
else:
|
||||
cur_block_id = data["next_cursor"]
|
||||
start_cursor = data["next_cursor"]
|
||||
return result_lines_arr
|
||||
|
||||
def _read_block(self, block_id: str, num_tabs: int = 0) -> str:
|
||||
"""Read a block."""
|
||||
result_lines_arr = []
|
||||
cur_block_id = block_id
|
||||
start_cursor = None
|
||||
block_url = BLOCK_CHILD_URL_TMPL.format(block_id=block_id)
|
||||
while True:
|
||||
block_url = BLOCK_CHILD_URL_TMPL.format(block_id=cur_block_id)
|
||||
query_dict: dict[str, Any] = {}
|
||||
query_dict: dict[str, Any] = {} if not start_cursor else {'start_cursor': start_cursor}
|
||||
|
||||
res = requests.request(
|
||||
"GET",
|
||||
@@ -210,7 +209,7 @@ class NotionExtractor(BaseExtractor):
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
json=query_dict
|
||||
params=query_dict
|
||||
)
|
||||
data = res.json()
|
||||
if 'results' not in data or data["results"] is None:
|
||||
@@ -249,7 +248,7 @@ class NotionExtractor(BaseExtractor):
|
||||
if data["next_cursor"] is None:
|
||||
break
|
||||
else:
|
||||
cur_block_id = data["next_cursor"]
|
||||
start_cursor = data["next_cursor"]
|
||||
|
||||
result_lines = "\n".join(result_lines_arr)
|
||||
return result_lines
|
||||
@@ -258,10 +257,10 @@ class NotionExtractor(BaseExtractor):
|
||||
"""Read table rows."""
|
||||
done = False
|
||||
result_lines_arr = []
|
||||
cur_block_id = block_id
|
||||
start_cursor = None
|
||||
block_url = BLOCK_CHILD_URL_TMPL.format(block_id=block_id)
|
||||
while not done:
|
||||
block_url = BLOCK_CHILD_URL_TMPL.format(block_id=cur_block_id)
|
||||
query_dict: dict[str, Any] = {}
|
||||
query_dict: dict[str, Any] = {} if not start_cursor else {'start_cursor': start_cursor}
|
||||
|
||||
res = requests.request(
|
||||
"GET",
|
||||
@@ -271,7 +270,7 @@ class NotionExtractor(BaseExtractor):
|
||||
"Content-Type": "application/json",
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
json=query_dict
|
||||
params=query_dict
|
||||
)
|
||||
data = res.json()
|
||||
# get table headers text
|
||||
@@ -300,7 +299,7 @@ class NotionExtractor(BaseExtractor):
|
||||
done = True
|
||||
break
|
||||
else:
|
||||
cur_block_id = data["next_cursor"]
|
||||
start_cursor = data["next_cursor"]
|
||||
|
||||
result_lines = "\n".join(result_lines_arr)
|
||||
return result_lines
|
||||
|
||||
@@ -5,6 +5,7 @@ Here, we will use GoogleSearch as an example to demonstrate how to quickly integ
|
||||
## 1. Prepare the Tool Provider yaml
|
||||
|
||||
### Introduction
|
||||
|
||||
This yaml declares a new tool provider, and includes information like the provider's name, icon, author, and other details that are fetched by the frontend for display.
|
||||
|
||||
### Example
|
||||
@@ -28,9 +29,11 @@ identity: # Basic information of the tool provider
|
||||
- search
|
||||
|
||||
```
|
||||
- The `identity` field is mandatory, it contains the basic information of the tool provider, including author, name, label, description, icon, etc.
|
||||
- The icon needs to be placed in the `_assets` folder of the current module, you can refer to [here](../../provider/builtin/google/_assets/icon.svg).
|
||||
- The `tags` field is optional, it is used to classify the provider, and the frontend can filter the provider according to the tag, for all tags, they have been listed below:
|
||||
|
||||
- The `identity` field is mandatory, it contains the basic information of the tool provider, including author, name, label, description, icon, etc.
|
||||
- The icon needs to be placed in the `_assets` folder of the current module, you can refer to [here](../../provider/builtin/google/_assets/icon.svg).
|
||||
- The `tags` field is optional, it is used to classify the provider, and the frontend can filter the provider according to the tag, for all tags, they have been listed below:
|
||||
|
||||
```python
|
||||
class ToolLabelEnum(Enum):
|
||||
SEARCH = 'search'
|
||||
@@ -56,6 +59,7 @@ identity: # Basic information of the tool provider
|
||||
Google, as a third-party tool, uses the API provided by SerpApi, which requires an API Key to use. This means that this tool needs a credential to use. For tools like `wikipedia`, there is no need to fill in the credential field, you can refer to [here](../../provider/builtin/wikipedia/wikipedia.yaml).
|
||||
|
||||
After configuring the credential field, the effect is as follows:
|
||||
|
||||
```yaml
|
||||
identity:
|
||||
author: Dify
|
||||
@@ -87,6 +91,7 @@ credentials_for_provider: # Credential field
|
||||
- `type`: Credential field type, currently can be either `secret-input`, `text-input`, or `select` , corresponding to password input box, text input box, and drop-down box, respectively. If set to `secret-input`, it will mask the input content on the frontend, and the backend will encrypt the input content.
|
||||
|
||||
## 3. Prepare Tool yaml
|
||||
|
||||
A provider can have multiple tools, each tool needs a yaml file to describe, this file contains the basic information, parameters, output, etc. of the tool.
|
||||
|
||||
Still taking GoogleSearch as an example, we need to create a `tools` module under the `google` module, and create `tools/google_search.yaml`, the content is as follows.
|
||||
@@ -140,21 +145,22 @@ parameters: # Parameter list
|
||||
|
||||
- The `identity` field is mandatory, it contains the basic information of the tool, including name, author, label, description, etc.
|
||||
- `parameters` Parameter list
|
||||
- `name` Parameter name, unique, no duplication with other parameters
|
||||
- `type` Parameter type, currently supports `string`, `number`, `boolean`, `select`, `secret-input` four types, corresponding to string, number, boolean, drop-down box, and encrypted input box, respectively. For sensitive information, we recommend using `secret-input` type
|
||||
- `required` Required or not
|
||||
- In `llm` mode, if the parameter is required, the Agent is required to infer this parameter
|
||||
- In `form` mode, if the parameter is required, the user is required to fill in this parameter on the frontend before the conversation starts
|
||||
- `options` Parameter options
|
||||
- In `llm` mode, Dify will pass all options to LLM, LLM can infer based on these options
|
||||
- In `form` mode, when `type` is `select`, the frontend will display these options
|
||||
- `default` Default value
|
||||
- `label` Parameter label, for frontend display
|
||||
- `human_description` Introduction for frontend display, supports multiple languages
|
||||
- `llm_description` Introduction passed to LLM, in order to make LLM better understand this parameter, we suggest to write as detailed information about this parameter as possible here, so that LLM can understand this parameter
|
||||
- `form` Form type, currently supports `llm`, `form` two types, corresponding to Agent self-inference and frontend filling
|
||||
- `name` Parameter name, unique, no duplication with other parameters
|
||||
- `type` Parameter type, currently supports `string`, `number`, `boolean`, `select`, `secret-input` four types, corresponding to string, number, boolean, drop-down box, and encrypted input box, respectively. For sensitive information, we recommend using `secret-input` type
|
||||
- `required` Required or not
|
||||
- In `llm` mode, if the parameter is required, the Agent is required to infer this parameter
|
||||
- In `form` mode, if the parameter is required, the user is required to fill in this parameter on the frontend before the conversation starts
|
||||
- `options` Parameter options
|
||||
- In `llm` mode, Dify will pass all options to LLM, LLM can infer based on these options
|
||||
- In `form` mode, when `type` is `select`, the frontend will display these options
|
||||
- `default` Default value
|
||||
- `label` Parameter label, for frontend display
|
||||
- `human_description` Introduction for frontend display, supports multiple languages
|
||||
- `llm_description` Introduction passed to LLM, in order to make LLM better understand this parameter, we suggest to write as detailed information about this parameter as possible here, so that LLM can understand this parameter
|
||||
- `form` Form type, currently supports `llm`, `form` two types, corresponding to Agent self-inference and frontend filling
|
||||
|
||||
## 4. Add Tool Logic
|
||||
|
||||
After completing the tool configuration, we can start writing the tool code that defines how it is invoked.
|
||||
|
||||
Create `google_search.py` under the `google/tools` module, the content is as follows.
|
||||
@@ -176,7 +182,7 @@ class GoogleSearchTool(BuiltinTool):
|
||||
query = tool_parameters['query']
|
||||
result_type = tool_parameters['result_type']
|
||||
api_key = self.runtime.credentials['serpapi_api_key']
|
||||
# TODO: search with serpapi
|
||||
# Search with serpapi
|
||||
result = SerpAPI(api_key).run(query, result_type=result_type)
|
||||
|
||||
if result_type == 'text':
|
||||
@@ -185,12 +191,15 @@ class GoogleSearchTool(BuiltinTool):
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
The overall logic of the tool is in the `_invoke` method, this method accepts two parameters: `user_id` and `tool_parameters`, which represent the user ID and tool parameters respectively
|
||||
|
||||
### Return Data
|
||||
|
||||
When the tool returns, you can choose to return one message or multiple messages, here we return one message, using `create_text_message` and `create_link_message` can create a text message or a link message.
|
||||
|
||||
## 5. Add Provider Code
|
||||
|
||||
Finally, we need to create a provider class under the provider module to implement the provider's credential verification logic. If the credential verification fails, it will throw a `ToolProviderCredentialValidationError` exception.
|
||||
|
||||
Create `google.py` under the `google` module, the content is as follows.
|
||||
@@ -227,8 +236,9 @@ class GoogleProvider(BuiltinToolProviderController):
|
||||
```
|
||||
|
||||
## Completion
|
||||
|
||||
After the above steps are completed, we can see this tool on the frontend, and it can be used in the Agent.
|
||||
|
||||
Of course, because google_search needs a credential, before using it, you also need to input your credentials on the frontend.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- azuredalle
|
||||
- stability
|
||||
- wikipedia
|
||||
- nominatim
|
||||
- yahoo
|
||||
- arxiv
|
||||
- pubmed
|
||||
|
||||
@@ -31,6 +31,12 @@ parameters:
|
||||
- value: claude-3-haiku
|
||||
label:
|
||||
en_US: Claude 3
|
||||
- value: llama-3-70b
|
||||
label:
|
||||
en_US: Llama 3
|
||||
- value: mixtral-8x7b
|
||||
label:
|
||||
en_US: Mixtral
|
||||
default: gpt-3.5
|
||||
label:
|
||||
en_US: Choose Model
|
||||
|
||||
@@ -6,7 +6,7 @@ identity:
|
||||
zh_CN: Firecrawl
|
||||
description:
|
||||
en_US: Firecrawl API integration for web crawling and scraping.
|
||||
zh_CN: Firecrawl API 集成,用于网页爬取和数据抓取。
|
||||
zh_Hans: Firecrawl API 集成,用于网页爬取和数据抓取。
|
||||
icon: icon.svg
|
||||
tags:
|
||||
- search
|
||||
@@ -17,20 +17,22 @@ credentials_for_provider:
|
||||
required: true
|
||||
label:
|
||||
en_US: Firecrawl API Key
|
||||
zh_CN: Firecrawl API 密钥
|
||||
zh_Hans: Firecrawl API 密钥
|
||||
placeholder:
|
||||
en_US: Please input your Firecrawl API key
|
||||
zh_CN: 请输入您的 Firecrawl API 密钥
|
||||
zh_Hans: 请输入您的 Firecrawl API 密钥,如果是自托管版本,可以随意填写密钥
|
||||
help:
|
||||
en_US: Get your Firecrawl API key from your Firecrawl account settings.
|
||||
zh_CN: 从您的 Firecrawl 账户设置中获取 Firecrawl API 密钥。
|
||||
en_US: Get your Firecrawl API key from your Firecrawl account settings.If you are using a self-hosted version, you may enter any key at your convenience.
|
||||
zh_Hans: 从您的 Firecrawl 账户设置中获取 Firecrawl API 密钥。如果是自托管版本,可以随意填写密钥。
|
||||
url: https://www.firecrawl.dev/account
|
||||
base_url:
|
||||
type: text-input
|
||||
required: false
|
||||
label:
|
||||
en_US: Firecrawl server's Base URL
|
||||
zh_Hans: Firecrawl服务器的API URL
|
||||
pt_BR: Firecrawl server's Base URL
|
||||
placeholder:
|
||||
en_US: https://www.firecrawl.dev
|
||||
zh_HansL: https://www.firecrawl.dev
|
||||
pt_BR: https://www.firecrawl.dev
|
||||
|
||||
@@ -1,98 +1,93 @@
|
||||
import time
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
|
||||
class FirecrawlApp:
|
||||
def __init__(self, api_key=None, base_url=None):
|
||||
def __init__(self, api_key: str | None = None, base_url: str | None = None):
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url or 'https://api.firecrawl.dev'
|
||||
if self.api_key is None and self.base_url == 'https://api.firecrawl.dev':
|
||||
raise ValueError('No API key provided')
|
||||
if not self.api_key:
|
||||
raise ValueError("API key is required")
|
||||
|
||||
def scrape_url(self, url, params=None) -> dict:
|
||||
def _prepare_headers(self, idempotency_key: str | None = None):
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': f'Bearer {self.api_key}'
|
||||
}
|
||||
json_data = {'url': url}
|
||||
if params:
|
||||
json_data.update(params)
|
||||
response = requests.post(
|
||||
f'{self.base_url}/v0/scrape',
|
||||
headers=headers,
|
||||
json=json_data
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response = response.json()
|
||||
if response['success'] == True:
|
||||
return response['data']
|
||||
else:
|
||||
raise Exception(f'Failed to scrape URL. Error: {response["error"]}')
|
||||
if idempotency_key:
|
||||
headers['Idempotency-Key'] = idempotency_key
|
||||
return headers
|
||||
|
||||
elif response.status_code in [402, 409, 500]:
|
||||
error_message = response.json().get('error', 'Unknown error occurred')
|
||||
raise Exception(f'Failed to scrape URL. Status code: {response.status_code}. Error: {error_message}')
|
||||
else:
|
||||
raise Exception(f'Failed to scrape URL. Status code: {response.status_code}')
|
||||
|
||||
def crawl_url(self, url, params=None, wait_until_done=True, timeout=2) -> str:
|
||||
headers = self._prepare_headers()
|
||||
json_data = {'url': url}
|
||||
if params:
|
||||
json_data.update(params)
|
||||
response = self._post_request(f'{self.base_url}/v0/crawl', json_data, headers)
|
||||
if response.status_code == 200:
|
||||
job_id = response.json().get('jobId')
|
||||
if wait_until_done:
|
||||
return self._monitor_job_status(job_id, headers, timeout)
|
||||
else:
|
||||
return {'jobId': job_id}
|
||||
else:
|
||||
self._handle_error(response, 'start crawl job')
|
||||
|
||||
def check_crawl_status(self, job_id) -> dict:
|
||||
headers = self._prepare_headers()
|
||||
response = self._get_request(f'{self.base_url}/v0/crawl/status/{job_id}', headers)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
self._handle_error(response, 'check crawl status')
|
||||
|
||||
def _prepare_headers(self):
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': f'Bearer {self.api_key}'
|
||||
}
|
||||
|
||||
def _post_request(self, url, data, headers):
|
||||
return requests.post(url, headers=headers, json=data)
|
||||
|
||||
def _get_request(self, url, headers):
|
||||
return requests.get(url, headers=headers)
|
||||
|
||||
def _monitor_job_status(self, job_id, headers, timeout):
|
||||
while True:
|
||||
status_response = self._get_request(f'{self.base_url}/v0/crawl/status/{job_id}', headers)
|
||||
if status_response.status_code == 200:
|
||||
status_data = status_response.json()
|
||||
if status_data['status'] == 'completed':
|
||||
if 'data' in status_data:
|
||||
return status_data['data']
|
||||
else:
|
||||
raise Exception('Crawl job completed but no data was returned')
|
||||
elif status_data['status'] in ['active', 'paused', 'pending', 'queued']:
|
||||
if timeout < 2:
|
||||
timeout = 2
|
||||
time.sleep(timeout) # Wait for the specified timeout before checking again
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
url: str,
|
||||
data: Mapping[str, Any] | None = None,
|
||||
headers: Mapping[str, str] | None = None,
|
||||
retries: int = 3,
|
||||
backoff_factor: float = 0.3,
|
||||
) -> Mapping[str, Any] | None:
|
||||
for i in range(retries):
|
||||
try:
|
||||
response = requests.request(method, url, json=data, headers=headers)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
if i < retries - 1:
|
||||
time.sleep(backoff_factor * (2 ** i))
|
||||
else:
|
||||
raise Exception(f'Crawl job failed or was stopped. Status: {status_data["status"]}')
|
||||
else:
|
||||
self._handle_error(status_response, 'check crawl status')
|
||||
raise
|
||||
return None
|
||||
|
||||
def _handle_error(self, response, action):
|
||||
if response.status_code in [402, 409, 500]:
|
||||
error_message = response.json().get('error', 'Unknown error occurred')
|
||||
raise Exception(f'Failed to {action}. Status code: {response.status_code}. Error: {error_message}')
|
||||
else:
|
||||
raise Exception(f'Unexpected error occurred while trying to {action}. Status code: {response.status_code}')
|
||||
def scrape_url(self, url: str, **kwargs):
|
||||
endpoint = f'{self.base_url}/v0/scrape'
|
||||
headers = self._prepare_headers()
|
||||
data = {'url': url, **kwargs}
|
||||
response = self._request('POST', endpoint, data, headers)
|
||||
if response is None:
|
||||
raise HTTPError("Failed to scrape URL after multiple retries")
|
||||
return response
|
||||
|
||||
def search(self, query: str, **kwargs):
|
||||
endpoint = f'{self.base_url}/v0/search'
|
||||
headers = self._prepare_headers()
|
||||
data = {'query': query, **kwargs}
|
||||
response = self._request('POST', endpoint, data, headers)
|
||||
if response is None:
|
||||
raise HTTPError("Failed to perform search after multiple retries")
|
||||
return response
|
||||
|
||||
def crawl_url(
|
||||
self, url: str, wait: bool = False, poll_interval: int = 5, idempotency_key: str | None = None, **kwargs
|
||||
):
|
||||
endpoint = f'{self.base_url}/v0/crawl'
|
||||
headers = self._prepare_headers(idempotency_key)
|
||||
data = {'url': url, **kwargs}
|
||||
response = self._request('POST', endpoint, data, headers)
|
||||
if response is None:
|
||||
raise HTTPError("Failed to initiate crawl after multiple retries")
|
||||
job_id: str = response['jobId']
|
||||
if wait:
|
||||
return self._monitor_job_status(job_id=job_id, poll_interval=poll_interval)
|
||||
return job_id
|
||||
|
||||
def check_crawl_status(self, job_id: str):
|
||||
endpoint = f'{self.base_url}/v0/crawl/status/{job_id}'
|
||||
headers = self._prepare_headers()
|
||||
response = self._request('GET', endpoint, headers=headers)
|
||||
if response is None:
|
||||
raise HTTPError(f"Failed to check status for job {job_id} after multiple retries")
|
||||
return response
|
||||
|
||||
def _monitor_job_status(self, job_id: str, poll_interval: int):
|
||||
while True:
|
||||
status = self.check_crawl_status(job_id)
|
||||
if status['status'] == 'completed':
|
||||
return status
|
||||
elif status['status'] == 'failed':
|
||||
raise HTTPError(f'Job {job_id} failed: {status["error"]}')
|
||||
time.sleep(poll_interval)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
@@ -7,7 +8,6 @@ from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
class CrawlTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
# initialize the app object with the api key
|
||||
app = FirecrawlApp(api_key=self.runtime.credentials['firecrawl_api_key'], base_url=self.runtime.credentials['base_url'])
|
||||
|
||||
options = {
|
||||
@@ -21,29 +21,16 @@ class CrawlTool(BuiltinTool):
|
||||
}
|
||||
}
|
||||
|
||||
# crawl the url
|
||||
crawl_result = app.crawl_url(
|
||||
url=tool_parameters['url'],
|
||||
params=options,
|
||||
wait_until_done=True,
|
||||
wait=True
|
||||
)
|
||||
|
||||
# reformat crawl result
|
||||
crawl_output = "**Crawl Result**\n\n"
|
||||
try:
|
||||
for result in crawl_result:
|
||||
crawl_output += f"**- Title:** {result.get('metadata', {}).get('title', '')}\n"
|
||||
crawl_output += f"**- Description:** {result.get('metadata', {}).get('description', '')}\n"
|
||||
crawl_output += f"**- URL:** {result.get('metadata', {}).get('ogUrl', '')}\n\n"
|
||||
crawl_output += f"**- Web Content:**\n{result.get('markdown', '')}\n\n"
|
||||
crawl_output += "---\n\n"
|
||||
except Exception as e:
|
||||
crawl_output += f"An error occurred: {str(e)}\n"
|
||||
crawl_output += f"**- Title:** {result.get('metadata', {}).get('title', '')}\n"
|
||||
crawl_output += f"**- Description:** {result.get('metadata', {}).get('description','')}\n"
|
||||
crawl_output += f"**- URL:** {result.get('metadata', {}).get('ogUrl', '')}\n\n"
|
||||
crawl_output += f"**- Web Content:**\n{result.get('markdown', '')}\n\n"
|
||||
crawl_output += "---\n\n"
|
||||
|
||||
if not isinstance(crawl_result, str):
|
||||
crawl_result = json.dumps(crawl_result, ensure_ascii=False, indent=4)
|
||||
|
||||
return self.create_text_message(crawl_output)
|
||||
if not crawl_result:
|
||||
return self.create_text_message("Crawl request failed.")
|
||||
|
||||
return self.create_text_message(crawl_result)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.firecrawl.firecrawl_appx import FirecrawlApp
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class ScrapeTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
app = FirecrawlApp(api_key=self.runtime.credentials['firecrawl_api_key'], base_url=self.runtime.credentials['base_url'])
|
||||
|
||||
crawl_result = app.scrape_url(
|
||||
url=tool_parameters['url'],
|
||||
wait=True
|
||||
)
|
||||
|
||||
if isinstance(crawl_result, dict):
|
||||
result_message = json.dumps(crawl_result, ensure_ascii=False, indent=4)
|
||||
else:
|
||||
result_message = str(crawl_result)
|
||||
|
||||
if not crawl_result:
|
||||
return self.create_text_message("Scrape request failed.")
|
||||
|
||||
return self.create_text_message(result_message)
|
||||
@@ -0,0 +1,23 @@
|
||||
identity:
|
||||
name: scrape
|
||||
author: ahasasjeb
|
||||
label:
|
||||
en_US: Scrape
|
||||
zh_Hans: 抓取
|
||||
description:
|
||||
human:
|
||||
en_US: Extract data from a single URL.
|
||||
zh_Hans: 从单个URL抓取数据。
|
||||
llm: This tool is designed to scrape URL and output the content in Markdown format.
|
||||
parameters:
|
||||
- name: url
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: URL to scrape
|
||||
zh_Hans: 要抓取的URL
|
||||
human_description:
|
||||
en_US: The URL of the website to scrape and extract data from.
|
||||
zh_Hans: 要抓取并提取数据的网站URL。
|
||||
llm_description: The URL of the website that needs to be crawled. This is a required parameter.
|
||||
form: llm
|
||||
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.provider.builtin.firecrawl.firecrawl_appx import FirecrawlApp
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class SearchTool(BuiltinTool):
|
||||
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
app = FirecrawlApp(api_key=self.runtime.credentials['firecrawl_api_key'], base_url=self.runtime.credentials['base_url'])
|
||||
|
||||
crawl_result = app.search(
|
||||
query=tool_parameters['keyword'],
|
||||
wait=True
|
||||
)
|
||||
|
||||
if isinstance(crawl_result, dict):
|
||||
result_message = json.dumps(crawl_result, ensure_ascii=False, indent=4)
|
||||
else:
|
||||
result_message = str(crawl_result)
|
||||
|
||||
if not crawl_result:
|
||||
return self.create_text_message("Search request failed.")
|
||||
|
||||
return self.create_text_message(result_message)
|
||||
@@ -0,0 +1,23 @@
|
||||
identity:
|
||||
name: search
|
||||
author: ahasasjeb
|
||||
label:
|
||||
en_US: Search
|
||||
zh_Hans: 搜索
|
||||
description:
|
||||
human:
|
||||
en_US: Search, and output in Markdown format
|
||||
zh_Hans: 搜索,并且以Markdown格式输出
|
||||
llm: This tool can perform online searches and convert the results to Markdown format.
|
||||
parameters:
|
||||
- name: keyword
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: keyword
|
||||
zh_Hans: 关键词
|
||||
human_description:
|
||||
en_US: Input keywords to use Firecrawl API for search.
|
||||
zh_Hans: 输入关键词即可使用Firecrawl API进行搜索。
|
||||
llm_description: Efficiently extract keywords from user text.
|
||||
form: llm
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from yarl import URL
|
||||
@@ -26,6 +27,15 @@ class JinaReaderTool(BuiltinTool):
|
||||
if 'api_key' in self.runtime.credentials and self.runtime.credentials.get('api_key'):
|
||||
headers['Authorization'] = "Bearer " + self.runtime.credentials.get('api_key')
|
||||
|
||||
request_params = tool_parameters.get('request_params')
|
||||
if request_params is not None and request_params != '':
|
||||
try:
|
||||
request_params = json.loads(request_params)
|
||||
if not isinstance(request_params, dict):
|
||||
raise ValueError("request_params must be a JSON object")
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
raise ValueError(f"Invalid request_params: {e}")
|
||||
|
||||
target_selector = tool_parameters.get('target_selector')
|
||||
if target_selector is not None and target_selector != '':
|
||||
headers['X-Target-Selector'] = target_selector
|
||||
@@ -53,7 +63,8 @@ class JinaReaderTool(BuiltinTool):
|
||||
response = ssrf_proxy.get(
|
||||
str(URL(self._jina_reader_endpoint + url)),
|
||||
headers=headers,
|
||||
timeout=(10, 60)
|
||||
params=request_params,
|
||||
timeout=(10, 60),
|
||||
)
|
||||
|
||||
if tool_parameters.get('summary', False):
|
||||
|
||||
@@ -25,6 +25,22 @@ parameters:
|
||||
pt_BR: used for linking to webpages
|
||||
llm_description: url for scraping
|
||||
form: llm
|
||||
- name: request_params
|
||||
type: string
|
||||
required: false
|
||||
label:
|
||||
en_US: Request params
|
||||
zh_Hans: 请求参数
|
||||
pt_BR: Request params
|
||||
human_description:
|
||||
en_US: |
|
||||
request parameters, format: {"key1": "value1", "key2": "value2"}
|
||||
zh_Hans: |
|
||||
请求参数,格式:{"key1": "value1", "key2": "value2"}
|
||||
pt_BR: |
|
||||
request parameters, format: {"key1": "value1", "key2": "value2"}
|
||||
llm_description: request parameters
|
||||
form: llm
|
||||
- name: target_selector
|
||||
type: string
|
||||
required: false
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="256" width="256" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="c">
|
||||
<stop stop-color="#2d3335" offset="0"/>
|
||||
<stop stop-color="#4c464a" offset=".5"/>
|
||||
<stop stop-color="#384042" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0"/>
|
||||
<stop stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="u" y2="-179.97" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="9" y1="-94.672" x1="94.25"/>
|
||||
<linearGradient id="v" y2="-30.438" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="35.75" y1="-114.44" x1="50.75"/>
|
||||
<linearGradient id="w" y2="-74.625" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="115.25" y1="-163.12" x1="77.625"/>
|
||||
<linearGradient id="x" y2="7.063" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="125.72" y1="-50.438" x1="56.5"/>
|
||||
<linearGradient id="y" y2="-146.8" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="180.75" y1="-163.3" x1="35"/>
|
||||
<linearGradient id="z" y2="-41.609" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="179" y1="-141.11" x1="57"/>
|
||||
<linearGradient id="A" y2="39.75" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="208.25" y1="-26.5" x1="156"/>
|
||||
<linearGradient id="B" y2="-77.297" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="235" y1="-169.3" x1="142.75"/>
|
||||
<linearGradient id="m" y2="-113.72" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="166.5" y1="-161.36" x1="214.25"/>
|
||||
<linearGradient id="b">
|
||||
<stop stop-color="#fff" offset="0"/>
|
||||
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="n" y2="-62.469" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="57.656" y1="-126.5" x1="53.25"/>
|
||||
<linearGradient id="o" y2="-17.125" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="75.719" y1="6.625" x1="39.5"/>
|
||||
<linearGradient id="p" y2="-121.44" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="85.312" y1="-162.34" x1="102"/>
|
||||
<linearGradient id="q" y2="-14.75" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="63.5" y1="-78.094" x1="117.75"/>
|
||||
<linearGradient id="r" y2="-60.75" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="133.94" y1="-125.12" x1="180.69"/>
|
||||
<linearGradient id="s" y2="33.375" xlink:href="#b" gradientUnits="userSpaceOnUse" x2="130.22" y1="-23.5" x1="170.75"/>
|
||||
<linearGradient id="t" y2="-5.656" xlink:href="#b" gradientUnits="userSpaceOnUse" y1="-76" x2="181.75" x1="213.5"/>
|
||||
<linearGradient id="l" y2="-213.25" xlink:href="#a" gradientUnits="userSpaceOnUse" x2="9" gradientTransform="translate(0 -10)" y1="72.064" x1="210.17"/>
|
||||
<linearGradient id="M" y2="150.38" xlink:href="#c" gradientUnits="userSpaceOnUse" x2="133" gradientTransform="matrix(-1 0 0 1 46 0)" y1="150.38" x1="123"/>
|
||||
<linearGradient id="L" y2="143.36" xlink:href="#c" gradientUnits="userSpaceOnUse" x2="130.88" gradientTransform="translate(-210)" y1="134.36" x1="128"/>
|
||||
<linearGradient id="O" y2="150.38" gradientUnits="userSpaceOnUse" y1="150.38" gradientTransform="matrix(-2 0 0 1.7699 174 -86.65)" x2="133" x1="123">
|
||||
<stop stop-color="#2d3335" offset="0"/>
|
||||
<stop stop-color="#4c464a" offset=".5"/>
|
||||
<stop stop-color="#384042" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="N" y2="186.5" gradientUnits="userSpaceOnUse" x2="136" gradientTransform="translate(-210)" y1="186.5" x1="120">
|
||||
<stop stop-color="#f9e295" offset="0"/>
|
||||
<stop stop-color="#f7dd84" offset=".125"/>
|
||||
<stop stop-color="#fff" offset=".206"/>
|
||||
<stop stop-color="#f4ce51" offset=".301"/>
|
||||
<stop stop-color="#f9e7aa" offset=".341"/>
|
||||
<stop stop-color="#efbb0e" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="P" y2="186.5" gradientUnits="userSpaceOnUse" y1="186.5" gradientTransform="translate(-210 67)" x2="136" x1="120">
|
||||
<stop stop-color="#f9e295" offset="0"/>
|
||||
<stop stop-color="#f7dd84" offset=".125"/>
|
||||
<stop stop-color="#fff" offset=".206"/>
|
||||
<stop stop-color="#f4ce51" offset=".301"/>
|
||||
<stop stop-color="#f9e7aa" offset=".341"/>
|
||||
<stop stop-color="#efbb0e" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="J" y2="186.5" gradientUnits="userSpaceOnUse" x2="136" gradientTransform="rotate(45 104.976 51.258) scale(1.39197)" y1="186.5" x1="120">
|
||||
<stop stop-color="#f9e295" offset="0"/>
|
||||
<stop stop-color="#f7dd84" offset=".125"/>
|
||||
<stop stop-color="#fff" offset=".206"/>
|
||||
<stop stop-color="#f4ce51" offset=".301"/>
|
||||
<stop stop-color="#f9e7aa" offset=".341"/>
|
||||
<stop stop-color="#efbb0e" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="U" y2="150.38" gradientUnits="userSpaceOnUse" x2="133" gradientTransform="rotate(-135 332.789 -39.72) scale(2.7839 -2.4637)" y1="150.38" x1="123">
|
||||
<stop stop-color="#2d3335" offset="0"/>
|
||||
<stop stop-color="#4c464a" offset=".5"/>
|
||||
<stop stop-color="#384042" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="T" y2="186.5" gradientUnits="userSpaceOnUse" x2="136" gradientTransform="rotate(45 217.552 97.892) scale(1.39197)" y1="186.5" x1="120">
|
||||
<stop stop-color="#f9e295" offset="0"/>
|
||||
<stop stop-color="#f7dd84" offset=".125"/>
|
||||
<stop stop-color="#fff" offset=".206"/>
|
||||
<stop stop-color="#f4ce51" offset=".301"/>
|
||||
<stop stop-color="#f9e7aa" offset=".341"/>
|
||||
<stop stop-color="#efbb0e" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="S" y2="150.38" xlink:href="#c" gradientUnits="userSpaceOnUse" x2="133" gradientTransform="scale(-1.39197 1.39197) rotate(-45 -28.292 379.342)" y1="150.38" x1="123"/>
|
||||
<linearGradient id="R" y2="143.36" xlink:href="#c" gradientUnits="userSpaceOnUse" x2="130.88" gradientTransform="rotate(45 217.552 97.892) scale(1.39197)" y1="134.36" x1="128"/>
|
||||
<linearGradient id="V" y2="150.38" xlink:href="#a" gradientUnits="userSpaceOnUse" y1="167.73" gradientTransform="rotate(-135 332.789 -39.72) scale(2.7839 -2.4637)" x2="133" x1="108"/>
|
||||
<linearGradient id="W" y2="137.2" gradientUnits="userSpaceOnUse" x2="179.96" gradientTransform="translate(0 -10)" y1="29.815" x1="126.64">
|
||||
<stop stop-color="#fff" offset="0"/>
|
||||
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="Z" y2="137.2" gradientUnits="userSpaceOnUse" y1="29.815" gradientTransform="matrix(-.50295 .1984 .1972 -.43254 276.16 123.42)" x2="179.96" x1="126.64">
|
||||
<stop stop-color="#fff" offset="0"/>
|
||||
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="G" y2="-213.25" gradientUnits="userSpaceOnUse" x2="9" gradientTransform="translate(0 192)" y1="72.064" x1="210.17">
|
||||
<stop offset="0"/>
|
||||
<stop stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="h" y2="4.302" gradientUnits="userSpaceOnUse" x2="122" y1="245.45" x1="122">
|
||||
<stop offset="0"/>
|
||||
<stop stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="e" y2="-213.25" gradientUnits="userSpaceOnUse" x2="9" gradientTransform="matrix(1 -.08088 0 1 0 203.06)" y1="72.064" x1="210.17">
|
||||
<stop offset="0"/>
|
||||
<stop stop-opacity="0" offset="1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="j" clipPathUnits="userSpaceOnUse">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
</clipPath>
|
||||
<clipPath id="k" clipPathUnits="userSpaceOnUse">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
</clipPath>
|
||||
<clipPath id="i" clipPathUnits="userSpaceOnUse">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
</clipPath>
|
||||
<clipPath id="D" clipPathUnits="userSpaceOnUse">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
</clipPath>
|
||||
<clipPath id="E" clipPathUnits="userSpaceOnUse">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
</clipPath>
|
||||
<clipPath id="F" clipPathUnits="userSpaceOnUse">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
</clipPath>
|
||||
<clipPath id="C" clipPathUnits="userSpaceOnUse">
|
||||
<path d="M123.62 141.06c20.832 20.243 54.607 19.289 75.439-2.132s20.832-55.196 0-75.439-54.607-19.289-75.439 2.132-20.832 55.196 0 75.439z" fill="url(#d)"/>
|
||||
</clipPath>
|
||||
<clipPath id="H" clipPathUnits="userSpaceOnUse">
|
||||
<path opacity=".039" d="M9 22.578s7.5 12.393 11.75 26.8C25 63.784 27 74.373 27 74.373s-5.5 13.195-8.25 25.417C16 112.01 13 123.25 13 123.25s5.75 15.535 9.25 29.252S26 176.449 26 176.449s-4 10.574-7.5 24.857-5 31.154-5 31.154 9.25-2.748 28.5-1.055 32.25 3.392 32.25 3.392 12.75-3.782 24-8.191 16.25-7.815 16.25-7.815 5.5.055 22.5 4.43c17 4.376 29.25 6.135 29.25 6.135s13-3.802 26-7.853 26.5-10.143 26.5-10.143-.75-4.94 4.25-24.844 8.75-28.708 8.75-28.708-.514-4.455-3.75-19.447c-1.75-8.108-2.25-4.068-2.25-4.068s16.264-26.155 16.5-40.334c.25-15.02-7.25-36.413-7.25-36.413s-4.75-14.866-7.5-29.143c-2.75-14.278-8.25-25.083-8.25-25.083S195 14.281 187.5 16.388c-7.5 2.107-21 6.948-21 6.948s-9.75-3.461-22-6.72c-12.25-3.26-29.75-3.094-29.75-3.094s-3.25 3.763-22 9.78c-18.75 6.016-27.5 7.973-27.5 7.973s-18.5-7.504-31.5-8.952-24-.06-24.75.252z" fill="url(#e)"/>
|
||||
</clipPath>
|
||||
<radialGradient id="K" gradientUnits="userSpaceOnUse" cy="86" cx="128" gradientTransform="matrix(1.0213 0 0 -1.0213 -212.72 173.83)" r="47">
|
||||
<stop stop-color="#d0e9f2" stop-opacity="0" offset="0"/>
|
||||
<stop stop-color="#d0e9f2" stop-opacity="0" offset=".839"/>
|
||||
<stop stop-color="#d0e9f2" stop-opacity=".282" offset=".943"/>
|
||||
<stop stop-color="#d0e9f2" offset="1"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="Q" gradientUnits="userSpaceOnUse" cy="86" cx="128" gradientTransform="scale(1.4216 -1.4216) rotate(-45 -51.091 19.464)" r="47">
|
||||
<stop stop-color="#d0e9f2" stop-opacity="0" offset="0"/>
|
||||
<stop stop-color="#d0e9f2" stop-opacity="0" offset=".839"/>
|
||||
<stop stop-color="#d0e9f2" stop-opacity=".282" offset=".943"/>
|
||||
<stop stop-color="#d0e9f2" offset="1"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="Y" gradientUnits="userSpaceOnUse" cy="72.588" cx="159.61" gradientTransform="matrix(1.2125 0 0 .83915 -46.348 24.528)" r="38.417">
|
||||
<stop stop-color="#fff" offset="0"/>
|
||||
<stop stop-color="#fff" stop-opacity=".93" offset=".5"/>
|
||||
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="X" gradientUnits="userSpaceOnUse" cx="159.61" cy="72.588" r="38.417" gradientTransform="matrix(1.2125 0 0 .83915 -45.92 25.814)">
|
||||
<stop stop-color="#fff" offset="0"/>
|
||||
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="d" gradientUnits="userSpaceOnUse" cy="86" cx="128" gradientTransform="matrix(.80254 .77986 .80254 -.82522 -10.402 73.423)" r="47">
|
||||
<stop stop-color="#d0e9f2" stop-opacity="0" offset="0"/>
|
||||
<stop stop-color="#d0e9f2" stop-opacity="0" offset=".839"/>
|
||||
<stop stop-color="#d0e9f2" stop-opacity=".282" offset=".943"/>
|
||||
<stop stop-color="#d0e9f2" offset="1"/>
|
||||
</radialGradient>
|
||||
<filter id="I">
|
||||
<feGaussianBlur stdDeviation="4.287"/>
|
||||
</filter>
|
||||
<filter id="f">
|
||||
<feGaussianBlur stdDeviation="6.311"/>
|
||||
</filter>
|
||||
<filter id="g">
|
||||
<feGaussianBlur stdDeviation="4.457"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<path opacity=".7" d="M174.28 35.875c-17.6 0-35.19 6.639-48.72 19.906-.16.164-.33.303-.5.469-22.05 22.056-26.206 55.25-12.47 81.5l-2.28 1.63c2.25 4.23 4.94 8.3 8.1 12.12l-13.32 13.34c-1.34-.58-2.78-1.03-4.56-1.37l-2.842 2.84c-1.11-.48-2.139-.81-3.063-.87L30.5 229.56c-.037 1.1.226 2.19.656 3.28l-.531.54-1.094 1.09c.716 3.74 2.011 6.82 5.469 10.28s6.42 4.63 10.281 5.47l1.094-1.1.719-.71c1.088.46 2.13.78 3.094.84l64.122-64.13c-.24-.97-.62-1.93-1.03-2.9l3-3c-.25-1.73-.68-3.2-1.31-4.6l13.28-13.28c3.82 3.16 7.89 5.85 12.13 8.1l1.62-2.28c26.25 13.74 59.44 9.58 81.5-12.47.17-.17.31-.34.47-.5 26.69-27.22 26.55-70.923-.47-97.94-13.59-13.59-31.41-20.375-49.22-20.375zm-.5 3.281c.11.002.21-.002.31 0a67.89 67.89 0 0 1 5.72.375c.1.01.19.021.28.031l.29.032c.14.017.29.044.43.062.9.11 1.8.228 2.69.375.62.102 1.23.224 1.84.344.35.066.69.116 1.04.187.72.153 1.43.324 2.15.5.31.077.63.138.94.219.07.019.14.043.22.063.95.253 1.89.516 2.84.812.07.023.15.04.22.063.95.3 1.91.625 2.84.969.06.019.11.042.16.062.95.352 1.9.728 2.84 1.125.06.022.11.041.16.063.94.4 1.88.836 2.81 1.281l.13.062c.93.451 1.86.91 2.78 1.407.03.017.06.044.09.062.93.503 1.84 1.013 2.75 1.562.03.02.06.044.1.063.9.551 1.8 1.122 2.68 1.719.55.369 1.09.769 1.63 1.156.36.261.73.512 1.09.781.02.014.05.018.06.031.88.656 1.75 1.36 2.6 2.063a70.875 70.875 0 0 1 5.06 4.594c1.62 1.62 3.14 3.3 4.56 5.031.01.011.03.02.03.031.71.855 1.41 1.715 2.07 2.594.28.379.54.773.81 1.156.37.517.74 1.038 1.09 1.563.02.024.05.038.07.062.59.89 1.16 1.778 1.71 2.688.02.032.05.061.07.094a75.45 75.45 0 0 1 1.56 2.75c.02.032.04.06.06.093.5.921.96 1.844 1.41 2.781l.06.126c.45.929.88 1.868 1.28 2.812l.06.156c.4.936.78 1.895 1.13 2.844l.06.156c.35.949.67 1.885.97 2.844l.06.219c.3.941.56 1.894.82 2.843l.06.219c.08.31.14.627.22.938.17.72.35 1.432.5 2.156.07.342.12.688.18 1.031.12.617.25 1.225.35 1.844.15.893.26 1.791.37 2.688.02.145.05.291.07.437l.03.281.03.282c.1.952.19 1.912.25 2.872.06.94.11 1.9.12 2.85.01.1 0 .2 0 .31.02 1.05.01 2.1-.03 3.15-.03.94-.08 1.86-.15 2.79-.01.12-.02.25-.04.37-.07.92-.16 1.84-.28 2.75-.12.96-.27 1.93-.43 2.88-.02.08-.02.16-.04.25-.02.12-.03.25-.06.37-.16.91-.36 1.82-.56 2.72-.03.13-.06.27-.09.41a86.1 86.1 0 0 1-.69 2.65c-.17.59-.35 1.17-.53 1.75-.18.57-.34 1.13-.53 1.69-.3.86-.61 1.71-.94 2.56-.06.16-.13.32-.19.47-.04.11-.08.23-.12.34-.36.88-.73 1.73-1.13 2.6-.38.82-.77 1.63-1.19 2.44-.08.15-.16.31-.25.46-.41.8-.83 1.6-1.28 2.38-.09.17-.18.34-.28.5-.2.34-.42.66-.62 1-.37.59-.75 1.17-1.13 1.75 0 .01.01.02 0 .03-.49.75-1 1.49-1.53 2.22-.46.64-.95 1.25-1.44 1.87-.19.26-.36.53-.56.79l-.41.5c-.16.2-.33.39-.5.59-.55.67-1.13 1.32-1.72 1.97-.6.67-1.23 1.35-1.87 2-.16.16-.31.34-.47.5-21.13 21.13-52.26 24.49-77.19 11.15l.13-.18c-3.71-1.9-7.26-4.14-10.63-6.78-.54-.43-1.06-.87-1.59-1.32-.51-.43-1.03-.86-1.53-1.31-.09-.08-.19-.16-.28-.25a86.97 86.97 0 0 1-2.41-2.31c-.79-.8-1.56-1.59-2.31-2.41-.09-.09-.17-.19-.25-.28-.45-.5-.88-1.02-1.31-1.53-.45-.53-.89-1.05-1.32-1.59a65.831 65.831 0 0 1-6.78-10.63l-.19.13c-13.33-24.93-9.97-56.061 11.16-77.191.16-.163.34-.308.5-.469.65-.64 1.33-1.269 2-1.875.65-.588 1.3-1.162 1.97-1.719.27-.227.54-.465.81-.687.35-.278.72-.512 1.06-.782.63-.483 1.24-.98 1.88-1.437.74-.53 1.5-1.036 2.25-1.531.58-.383 1.16-.764 1.75-1.125.45-.274.89-.551 1.34-.813.69-.395 1.37-.757 2.07-1.125a61.343 61.343 0 0 1 3.37-1.687c.87-.397 1.72-.769 2.59-1.125.16-.062.32-.096.47-.157.97-.384 1.93-.758 2.91-1.093.56-.192 1.12-.356 1.69-.532.58-.181 1.16-.367 1.75-.531.88-.246 1.77-.48 2.65-.687.07-.017.15-.015.22-.032.42-.095.84-.193 1.25-.281.67-.141 1.36-.287 2.03-.406.09-.015.17-.017.25-.031.95-.164 1.92-.317 2.88-.438a65.183 65.183 0 0 1 9.06-.5z" transform="matrix(1 .06864 0 .8487 0 26.913)" filter="url(#f)" fill="#2d3335"/>
|
||||
<path opacity=".5" d="M9 17.25s7.5 11 11.75 25.75S27 68.5 27 68.5s-5.5 12.75-8.25 24.75-5.75 25-5.75 25 5.75 16 9.25 30S26 170.5 26 170.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-2.5-3.75-17.75S218 121 218 121s1.75-12.5 6.75-25.75S235 68.5 235 68.5s-4.75-15.25-7.5-29.75S219.25 15 219.25 15 195 24 187.5 25.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z" transform="matrix(1 0 0 .84657 0 27.66)" filter="url(#g)" fill="url(#h)"/>
|
||||
<path fill="#ceeeab" d="M9 2.25s7.5 13 11.75 27.75S27 55.5 27 55.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 157.5 26 157.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 106 218 106s1.75-10.5 6.75-23.75S235 55.5 235 55.5s-4.75-15.25-7.5-29.75S219.25 0 219.25 0 195 9 187.5 10.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5S9.75 2 9 2.25z"/>
|
||||
<g transform="translate(0 -10)" clip-path="url(#i)">
|
||||
<path fill="#a6dd8b" d="m110.75 5.5-4.25 7c-.75 9-2.25 13.75-8.5 16.5s-12.25 3.5-11 7.5 13 9.25 14.25 13 8.25 1.75 11 7 2 15.25-3.75 17.25-17.5.5-20.25 9.5-4.75 10.5-9 12.75-7.25 10.5-3.5 16.5 12.25-1.25 15-6.5S98 97.25 98 97.25h23.75l59-1.75 3.25-3.75s3.25 4 2.75 8.75-3.75 14.75.25 17.25 19.5-2 24-7-4.75-28.25-10.5-29.5-18.5-1.75-17-7 11.75 4.5 17.25 3.25 16.75-21 12-25.25-24.25-5.25-25.75-8 21-8 22.25-11-2.5-7.25-6.25-8.5S189 41 182 40.5s-20 5.75-17.25 11.5-6 11-14.5 6.5-24.75-13-21.75-24S150 15.75 150 15.75L110.75 5.5zM97.188 112.72c-1.595.15-4.469 4.69-4.938 7.03-.5 2.5-3.25 6.75-3.5 12.25s4.75 6.75 8.75 6.5 2.75-6.75 2-15c-.25 0-.5-10.25-2-10.75-.094-.03-.206-.04-.312-.03zm92.722 51.66c-6.34.29-6.63 7.46-7.41 8.87-1.25 2.25 2.25 13.75 2 18s-4.75 5.25-9.5 9.75.5 16 11.25 31l44.75 1.25-1-35s4.75-4.25-20.75-24.25c-9.56-7.5-15.54-9.8-19.34-9.62zM39.94 180.91c-4.03-.11-8.282 5.71-9.688 7.59-1.5 2-6.25 5-17 9.5l-14.5 34.25 53 4.25s1.75-11-4.25-15.75-17.25-5.75-18.25-13.25 9.25-10 13.75-14.25 2-11.25-2.25-12.25a3.728 3.728 0 0 0-.812-.09z"/>
|
||||
</g>
|
||||
<path fill="#aac3e7" d="M158.53 75.344c-4.76-.015-9.03.968-11.53 3.156-8 7-35 .75-48.5 7s-13.25 38-14.75 44.5-17.5 20.75-20 23.5-13.25 7.25-19.5 8.5-12.75 7.25-15.5 11c-2.021 2.76-7.406 6.45-10.125 8.22-.046.18-.079.35-.125.53-.134.53-.247 1.08-.375 1.63 4.98-1.61 11.18-8.18 16.625-13.63 6.25-6.25 20-7.75 27.75-11.5S76.75 138.5 89 134.5s21.25 11.75 24.25 18.5 1.75 12.75 3.75 17 11 11.75 11.5 13.5-5 6.5-6.25 8.5-10.5 7-11.75 8.75c-.97 1.35-1.92 6.77-2.31 9.22.41-.17.75-.31 1.12-.47.49-2.53 1.52-7.08 2.94-8.5 2-2 11.25-4.5 12.5-8.5s7-6.5 7-6.5 2.75 4 16 14c8.83 6.67 12.76 15.53 14.41 20.72.67.17 1.53.41 2.03.53-1.22-4.32-4.84-16.24-8.94-20.75-5-5.5-18.5-10.75-22.75-22S108 144.25 115 138.25s16.5-4 28.5 7.5 46.25 5.75 57.75 3.75c9.95-1.73 20.83 14.88 23.91 26.03.59-2.09 1.12-4 1.65-5.75-1.74-1.92-3.69-4.62-5.31-8.28-3.75-8.5-12-13.25-12-13.25s8.75-5 14.75-7.75c1.62-.74 3.01-1.68 4.19-2.66-.16-.77-.26-1.25-.44-2.09l-.09-.44c-4.77 5.56-19.24 9.62-21.66 10.94-2.75 1.5-18.25 3-35.75 4.5s-26.75-7.5-34.25-14.75-13-36-3-38 20 13.75 30 17 21.5-15.75 19.75-27c-1.2-7.734-14-12.625-24.47-12.656zm-53.34 13.281c1.27.001 5.86 1.594 6.56 2.375 2.25 2.5 2 7.25 0 8s-12.75 8-10.5 14.25 1.75 18-3.5 18-8 0-10-2.5-2-12 0-19.75 3.5-15 8-18.25c3.094-2.234 6.63-2.128 9.44-2.125zm13.59 73.095c.2.01 1.91 3.51 4.72 7.03 3 3.75 3.25 8.25 3.25 8.25s-4.25-4.75-6-8-2-7.25-2-7.25c0-.03.02-.03.03-.03z"/>
|
||||
<path opacity=".387" stroke-linejoin="round" d="M122.75 62.25c6.25.5 12.25-2.25 10.75-6.5s-12.5-7.25-13.25-3 2 10.5 2.5 9.5zM108.75 46.75c-8.5-5.5-8.5-7.25-7-8s10.25 5 12.75 8-2.25 2-5.75 0z" stroke="#6d7f42" stroke-linecap="round" fill="none"/>
|
||||
<g clip-path="url(#j)" transform="translate(0 -10)" stroke="#6d7f42" fill="none">
|
||||
<path opacity=".387" stroke-linejoin="round" d="M15.75 151.25c33-2.5 38.25-3.5 36.25-10.25s-8.75-23.5-23-22.5-22.75-6.75-22.75-6.75" stroke-linecap="round"/>
|
||||
<path opacity=".387" d="M17.5 177.25c18.25-10 28.75-8.5 35-11.5s13.25-5.75 15.75-9.5 5.75-11.5 5.75-18-3.5-36 3.25-43.5 17.25-26 26-23.25 12.25 9.75 22 9.5S147 77 149.5 68.75s-15.75-16-16-23 5.5-14.25 20.25-17 23.5-11.5 23.5-11.5"/>
|
||||
<path opacity=".387" stroke-linejoin="round" d="M14.5 161c15.25-1.5 22.25 3.5 31.5 1.75s13.25-3 17.75-5.75 6-3.75 6.25-6.5 1-12.5-3-22S54.5 97 58.75 89.75 64 68.25 74.5 65.25 95.5 59 102.25 64s12.75 14.25 20 14.5S134 77 136.75 74s4.5-10.25 1.75-13.75-15.5-5.5-17.25-9.5-10.75-17-.25-26.75 36.25-8.25 36.25-8.25" stroke-linecap="round"/>
|
||||
<path opacity=".387" stroke-linejoin="round" d="M21.25 159c13.75 1 21.5 4.25 33.5-.5s13.5-5.25 13.75-8.75-.25-15.75-3-20-13-13.75-15.75-24.25.25-41 8.5-48.25 32-7.25 37.5-10.5 5.25-16.75 13-24.5 27-12.25 27-12.25M190 18.5c11.75 5 39 9.75 47.25 40.25M9.75 214c19-12.5 36-1.25 49-4s38.25-37.25 46-37 5.5-11.75 8.5-13.25 5.5 6 11.75 8 24.5-2.75 23.5-5.75-7-7.75-5.5-8.5 8.5 5 12.75 3.5 43.75-1 48.5-13.5 21.5-13.5 21.5-13.5M77.5 233.5c13.5-11.5 23.25-25 28.5-27s12.75-3.75 15.75-8 2.5-10.75 5.25-12 3.75 5.75 8.75 6 36.75-5 42.75-11.75 32.25-12.5 32.5-14.5-1-4.75.25-5.5 24.75-6 24.75-6M168.75 236c9-13.75 20.5-41 29.5-42s10.25-2.5 12.5-4.25 17 2.25 17 2.25" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#k)" transform="translate(0 -10)" stroke="#d38484" stroke-linecap="round" stroke-width="2" fill="none">
|
||||
<path d="m57.75 20-8.5 28.25 18 6.25L75 90.75 54 113l9 10.5L51 135l.5 4.5L71.75 164l14-6.75 20.5 18.5L95.75 204l10.25 8.5-2.75 13"/>
|
||||
<path d="m105.75 212.25 12.5-27.75 11-7 27.5 15.75 20.5-3.75-.25-15.75-10.25-6 12.75-26.25 5.75-3.75 38.75-10"/>
|
||||
</g>
|
||||
<g opacity=".504">
|
||||
<path d="M219.25 330s-24.25 9-31.75 10.5-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75c0 0 7.5 13 11.75 27.75S27 385.5 27 385.5l51.469 5.125 48.971-4 52.25 12.031c10.38-4.418 52.11-4.9 55.31-13.156 0 0-4.75-15.25-7.5-29.75S219.25 330 219.25 330z" fill="#b1e479"/>
|
||||
<path d="M27 385.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23l50.969 16.37 53.721-15.74 52.25 17.53c16.36-4.26 32.39-9.12 48.06-15.41 0 0 1.75-10.5 6.75-23.75S235 385.5 235 385.5c-3.2 8.256-44.93 8.738-55.31 13.156l-52.25-12.031-47.971 4L27 385.5z" fill="#87d531"/>
|
||||
<path d="M231.75 485.5c-17.9 7.67-35.94 14.9-54.31 21.41l-52.25-18.53-50.221 9L26 487.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28z" fill="#ceeeab"/>
|
||||
<path d="M13 433.25s5.75 16 9.25 30S26 487.5 26 487.5l48.969 9.88 50.221-9 52.25 18.53c18.37-6.51 36.41-13.74 54.31-21.41 0 0-.5-4.5-3.75-19.75S218 436 218 436c-15.67 6.29-31.7 11.15-48.06 15.41l-52.25-17.53-53.721 15.74L13 433.25z" fill="#b9e787"/>
|
||||
</g>
|
||||
<g opacity=".522">
|
||||
<path d="M13.719 332.031c-2.729-.007-4.438.125-4.719.219 0 0 7.5 13 11.75 27.75S27 385.5 27 385.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 487.5 26 487.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6l1.5-53.25L63.5 450l16-59.75c-2.22-15.48-9.068-30.046-14.25-44.75 0 0-18.5-9-31.5-11.5-8.125-1.563-15.483-1.956-20.031-1.969z" fill="#83d32b"/>
|
||||
<path d="M114.75 331.75s-3.25 3.5-22 8-27.5 5.75-27.5 5.75c5.182 14.704 12.03 29.27 14.25 44.75L63.5 450l12.25 46.5-1.5 53.25s12.75-2.75 24-6.25 16.25-6.5 16.25-6.5l11.25-48.5-8.25-54.25 11.75-45c-3.44-9.727-8.06-56.93-14.5-57.5z" fill="#b1e479"/>
|
||||
<path d="M219.25 330s-24.25 9-31.75 10.5-21 5.25-21 5.25c4.92-1.358 11.44 45.392 14.25 52.25l-11.25 53.25 9.5 55-12.75 45.5s13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 436 218 436s1.75-10.5 6.75-23.75S235 385.5 235 385.5s-4.75-15.25-7.5-29.75-8.25-25.75-8.25-25.75z" fill="#a4df62"/>
|
||||
<path d="M114.75 331.75c6.44.57 11.06 47.773 14.5 57.5l-11.75 45 8.25 53.25L114.5 537s5.5.5 22.5 6.25 29.25 8.5 29.25 8.5l12.75-45.5-9.5-55L180.75 398c-2.81-6.858-9.33-53.608-14.25-52.25 0 0-9.75-4.25-22-8.5s-29.75-5.5-29.75-5.5z" fill="#ceeeab"/>
|
||||
</g>
|
||||
<path opacity=".039" d="M9-189.75s7.5 13 11.75 27.75S27-136.5 27-136.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26-34.5 26-34.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6S87 25 98.25 21.5 114.5 15 114.5 15s5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8S218 11 223-8.5s8.75-28 8.75-28-.5-4.5-3.75-19.75S218-86 218-86s1.75-10.5 6.75-23.75S235-136.5 235-136.5s-4.75-15.25-7.5-29.75-8.25-25.75-8.25-25.75-24.25 9-31.75 10.5-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z" fill="url(#l)" transform="translate(0 192)"/>
|
||||
<g opacity=".27">
|
||||
<path fill="url(#m)" d="M219.25-182s-24.25 9-31.75 10.5-21 5.25-21 5.25c4.92-1.36 11.44 45.39 14.25 52.25l-.06.28c11.81-4.07 51.21-4.77 54.31-12.78 0 0-4.75-15.25-7.5-29.75S219.25-182 219.25-182z" transform="translate(0 182)"/>
|
||||
<path fill="url(#n)" d="M27-126.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23l50.625 16.281 15.781-58.911L27-126.5z" transform="translate(0 182)"/>
|
||||
<path fill="url(#o)" d="M26-24.5S22-14.25 18.5-.25s-5 30.75-5 30.75.147-.045.438-.094c2.033-.338 11.218-1.5 28.062 1.344 19.25 3.25 32.25 6 32.25 6l1.469-52.5-.75.125L26-24.5z" transform="translate(0 182)"/>
|
||||
<path fill="url(#p)" d="M114.75-180.25s-3.25 3.5-22 8-27.5 5.75-27.5 5.75c5.182 14.7 12.03 29.27 14.25 44.75l-.094.31 48.034-3.94 1.12.26c-3.36-13.33-7.79-54.6-13.81-55.13z" transform="translate(0 182)"/>
|
||||
<path fill="url(#q)" d="M117.59-78.094 63.969-62.375l-.344-.094L63.5-62l12.25 46.5-.031.75 49.471-8.875.53.187.03-.062-8.25-54.25.09-.344z" transform="translate(0 182)"/>
|
||||
<path fill="url(#r)" d="M128.56-125.12c.24.92.47 1.74.69 2.37l-11.66 44.656.1-.031L169.5-60.75l11.19-52.97c-.36.12-.7.25-1 .38l-51.13-11.78z" transform="translate(0 182)"/>
|
||||
<path fill="url(#s)" d="M125.53-23.5 114.5 25s5.5.5 22.5 6.25c2.12.719 4.16 1.406 6.12 2.031 1.97.626 3.86 1.186 5.63 1.719 10.62 3.199 17.5 4.75 17.5 4.75l12.72-45.406c-.51.182-1.02.38-1.53.562L125.53-23.5z" transform="translate(0 182)"/>
|
||||
<path fill="url(#t)" d="M218-76c-15.67 6.288-31.7 11.15-48.06 15.406l-.44-.156 9.5 55-.03.094c17.84-6.36 35.38-13.387 52.78-20.844 0 0-.5-4.5-3.75-19.75S218-76 218-76z" transform="translate(0 182)"/>
|
||||
</g>
|
||||
<g opacity=".043">
|
||||
<path fill="url(#u)" d="M13.719-179.97c-1.106 0-1.972.03-2.719.06-.445.02-.855.04-1.156.07-.03 0-.066-.01-.094 0-.398.03-.656.06-.75.09 0 0 7.5 13 11.75 27.75S27-126.5 27-126.5l51.469 5.12.937-.06.094-.31c-2.22-15.48-9.068-30.05-14.25-44.75 0 0-18.5-9-31.5-11.5-1.018-.2-2.01-.37-3-.53-.596-.1-1.167-.2-1.75-.28-.237-.04-.453-.06-.688-.1a98.772 98.772 0 0 0-3.812-.46c-.735-.08-1.395-.13-2.094-.19-.685-.06-1.353-.11-2-.16-.992-.07-1.89-.12-2.781-.15-1.456-.07-2.769-.1-3.906-.1z" transform="translate(0 182)"/>
|
||||
<path fill="url(#v)" d="M13-78.75s5.75 16 9.25 30S26-24.5 26-24.5l48.969 9.875.75-.125.031-.75L63.5-62l.125-.469L13-78.75z" transform="translate(0 182)"/>
|
||||
<path fill="url(#w)" d="m127.44-125.38-47.971 4h-.063L63.625-62.469l.344.094 53.621-15.719 11.66-44.656c-.22-.63-.45-1.45-.69-2.37l-1.12-.26z" transform="translate(0 182)"/>
|
||||
<path fill="url(#x)" d="M125.19-23.625 75.719-14.75l-1.469 52.5S87 35 98.25 31.5 114.5 25 114.5 25l11.22-48.438-.53-.187z" transform="translate(0 182)"/>
|
||||
<path fill="url(#y)" d="M114.75-180.25c6.02.53 10.45 41.8 13.81 55.13l51.13 11.78c.3-.13.64-.26 1-.38l.06-.28c-2.81-6.86-9.33-53.61-14.25-52.25 0 0-.61-.25-1.72-.72-3.32-1.39-11.09-4.59-20.28-7.78-12.25-4.25-29.75-5.5-29.75-5.5z" transform="translate(0 182)"/>
|
||||
<path fill="url(#z)" d="m117.69-78.125-.1.031-.09.344 8.25 53.25-.22 1 51.91 18.406c.51-.181 1.02-.38 1.53-.562l.03-.094-9.5-55-51.81-17.375z" transform="translate(0 182)"/>
|
||||
<path fill="url(#A)" d="M231.75-26.5c-17.4 7.457-34.94 14.484-52.78 20.844L166.25 39.75s13-2.75 26-5.75 26.5-8 26.5-8S218 21 223 1.5s8.75-28 8.75-28z" transform="translate(0 182)"/>
|
||||
<path fill="url(#B)" d="M235-126.5c-3.1 8.01-42.5 8.71-54.31 12.78L169.5-60.75l.44.156C186.3-64.85 202.33-69.712 218-76c0 0 1.75-10.5 6.75-23.75S235-126.5 235-126.5z" transform="translate(0 182)"/>
|
||||
</g>
|
||||
<g clip-path="url(#C)" transform="matrix(1.2525 .0354 0 1.2525 -27.809 -48.344)">
|
||||
<path fill="#ceeeab" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z"/>
|
||||
<g clip-path="url(#D)">
|
||||
<path fill="#a6dd8b" d="m110.75 5.5-4.25 7c-.75 9-2.25 13.75-8.5 16.5s-12.25 3.5-11 7.5 13 9.25 14.25 13 8.25 1.75 11 7 2 15.25-3.75 17.25-17.5.5-20.25 9.5-4.75 10.5-9 12.75-7.25 10.5-3.5 16.5 12.25-1.25 15-6.5S98 97.25 98 97.25h23.75l59-1.75 3.25-3.75s3.25 4 2.75 8.75-3.75 14.75.25 17.25 19.5-2 24-7-4.75-28.25-10.5-29.5-18.5-1.75-17-7 11.75 4.5 17.25 3.25 16.75-21 12-25.25-24.25-5.25-25.75-8 21-8 22.25-11-2.5-7.25-6.25-8.5S189 41 182 40.5s-20 5.75-17.25 11.5-6 11-14.5 6.5-24.75-13-21.75-24S150 15.75 150 15.75L110.75 5.5zM97.188 112.72c-1.595.15-4.469 4.69-4.938 7.03-.5 2.5-3.25 6.75-3.5 12.25s4.75 6.75 8.75 6.5 2.75-6.75 2-15c-.25 0-.5-10.25-2-10.75-.094-.03-.206-.04-.312-.03zm92.722 51.66c-6.34.29-6.63 7.46-7.41 8.87-1.25 2.25 2.25 13.75 2 18s-4.75 5.25-9.5 9.75.5 16 11.25 31l44.75 1.25-1-35s4.75-4.25-20.75-24.25c-9.56-7.5-15.54-9.8-19.34-9.62zM39.94 180.91c-4.03-.11-8.282 5.71-9.688 7.59-1.5 2-6.25 5-17 9.5l-14.5 34.25 53 4.25s1.75-11-4.25-15.75-17.25-5.75-18.25-13.25 9.25-10 13.75-14.25 2-11.25-2.25-12.25a3.728 3.728 0 0 0-.812-.09z"/>
|
||||
</g>
|
||||
<path fill="#aac3e7" d="M158.53 85.344c-4.76-.015-9.03.968-11.53 3.156-8 7-35 .75-48.5 7s-13.25 38-14.75 44.5-17.5 20.75-20 23.5-13.25 7.25-19.5 8.5-12.75 7.25-15.5 11c-2.021 2.76-7.406 6.45-10.125 8.22-.046.18-.079.35-.125.53-.134.53-.247 1.08-.375 1.63 4.98-1.61 11.18-8.18 16.625-13.63 6.25-6.25 20-7.75 27.75-11.5S76.75 148.5 89 144.5s21.25 11.75 24.25 18.5 1.75 12.75 3.75 17 11 11.75 11.5 13.5-5 6.5-6.25 8.5-10.5 7-11.75 8.75c-.97 1.35-1.92 6.77-2.31 9.22.41-.17.75-.31 1.12-.47.49-2.53 1.52-7.08 2.94-8.5 2-2 11.25-4.5 12.5-8.5s7-6.5 7-6.5 2.75 4 16 14c8.83 6.67 12.76 15.53 14.41 20.72.67.17 1.53.41 2.03.53-1.22-4.32-4.84-16.24-8.94-20.75-5-5.5-18.5-10.75-22.75-22S108 154.25 115 148.25s16.5-4 28.5 7.5 46.25 5.75 57.75 3.75c9.95-1.73 20.83 14.88 23.91 26.03.59-2.09 1.12-4 1.65-5.75-1.74-1.92-3.69-4.62-5.31-8.28-3.75-8.5-12-13.25-12-13.25s8.75-5 14.75-7.75c1.62-.74 3.01-1.68 4.19-2.66-.16-.77-.26-1.25-.44-2.09l-.09-.44c-4.77 5.56-19.24 9.62-21.66 10.94-2.75 1.5-18.25 3-35.75 4.5s-26.75-7.5-34.25-14.75-13-36-3-38 20 13.75 30 17 21.5-15.75 19.75-27c-1.2-7.734-14-12.625-24.47-12.656zm-53.34 13.281c1.27.001 5.86 1.595 6.56 2.375 2.25 2.5 2 7.25 0 8s-12.75 8-10.5 14.25 1.75 18-3.5 18-8 0-10-2.5-2-12 0-19.75 3.5-15 8-18.25c3.094-2.234 6.63-2.128 9.44-2.125zm13.59 73.095c.2.01 1.91 3.51 4.72 7.03 3 3.75 3.25 8.25 3.25 8.25s-4.25-4.75-6-8-2-7.25-2-7.25c0-.03.02-.03.03-.03z"/>
|
||||
<path opacity=".387" stroke-linejoin="round" d="M122.75 72.25c6.25.5 12.25-2.25 10.75-6.5s-12.5-7.25-13.25-3 2 10.5 2.5 9.5zM108.75 56.75c-8.5-5.5-8.5-7.25-7-8s10.25 5 12.75 8-2.25 2-5.75 0z" stroke="#6d7f42" stroke-linecap="round" fill="none"/>
|
||||
<g stroke="#6d7f42" fill="none" clip-path="url(#E)">
|
||||
<path opacity=".387" stroke-linejoin="round" d="M15.75 151.25c33-2.5 38.25-3.5 36.25-10.25s-8.75-23.5-23-22.5-22.75-6.75-22.75-6.75" stroke-linecap="round"/>
|
||||
<path opacity=".387" d="M17.5 177.25c18.25-10 28.75-8.5 35-11.5s13.25-5.75 15.75-9.5 5.75-11.5 5.75-18-3.5-36 3.25-43.5 17.25-26 26-23.25 12.25 9.75 22 9.5S147 77 149.5 68.75s-15.75-16-16-23 5.5-14.25 20.25-17 23.5-11.5 23.5-11.5"/>
|
||||
<path opacity=".387" stroke-linejoin="round" d="M14.5 161c15.25-1.5 22.25 3.5 31.5 1.75s13.25-3 17.75-5.75 6-3.75 6.25-6.5 1-12.5-3-22S54.5 97 58.75 89.75 64 68.25 74.5 65.25 95.5 59 102.25 64s12.75 14.25 20 14.5S134 77 136.75 74s4.5-10.25 1.75-13.75-15.5-5.5-17.25-9.5-10.75-17-.25-26.75 36.25-8.25 36.25-8.25" stroke-linecap="round"/>
|
||||
<path opacity=".387" stroke-linejoin="round" d="M21.25 159c13.75 1 21.5 4.25 33.5-.5s13.5-5.25 13.75-8.75-.25-15.75-3-20-13-13.75-15.75-24.25.25-41 8.5-48.25 32-7.25 37.5-10.5 5.25-16.75 13-24.5 27-12.25 27-12.25M190 18.5c11.75 5 39 9.75 47.25 40.25M9.75 214c19-12.5 36-1.25 49-4s38.25-37.25 46-37 5.5-11.75 8.5-13.25 5.5 6 11.75 8 24.5-2.75 23.5-5.75-7-7.75-5.5-8.5 8.5 5 12.75 3.5 43.75-1 48.5-13.5 21.5-13.5 21.5-13.5M77.5 233.5c13.5-11.5 23.25-25 28.5-27s12.75-3.75 15.75-8 2.5-10.75 5.25-12 3.75 5.75 8.75 6 36.75-5 42.75-11.75 32.25-12.5 32.5-14.5-1-4.75.25-5.5 24.75-6 24.75-6M168.75 236c9-13.75 20.5-41 29.5-42s10.25-2.5 12.5-4.25 17 2.25 17 2.25" stroke-linecap="round"/>
|
||||
</g>
|
||||
<g clip-path="url(#F)" stroke="#d38484" stroke-linecap="round" stroke-width="2" fill="none">
|
||||
<path d="m57.75 20-8.5 28.25 18 6.25L75 90.75 54 113l9 10.5L51 135l.5 4.5L71.75 164l14-6.75 20.5 18.5L95.75 204l10.25 8.5-2.75 13"/>
|
||||
<path d="m105.75 212.25 12.5-27.75 11-7 27.5 15.75 20.5-3.75-.25-15.75-10.25-6 12.75-26.25 5.75-3.75 38.75-10"/>
|
||||
</g>
|
||||
<path opacity=".039" d="M9 12.25s7.5 13 11.75 27.75S27 65.5 27 65.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23 5.75 16 9.25 30S26 167.5 26 167.5s-4 10.25-7.5 24.25-5 30.75-5 30.75 9.25-2 28.5 1.25 32.25 6 32.25 6 12.75-2.75 24-6.25 16.25-6.5 16.25-6.5 5.5.5 22.5 6.25 29.25 8.5 29.25 8.5 13-2.75 26-5.75 26.5-8 26.5-8-.75-5 4.25-24.5 8.75-28 8.75-28-.5-4.5-3.75-19.75S218 116 218 116s1.75-10.5 6.75-23.75S235 65.5 235 65.5s-4.75-15.25-7.5-29.75S219.25 10 219.25 10 195 19 187.5 20.5s-21 5.25-21 5.25-9.75-4.25-22-8.5-29.75-5.5-29.75-5.5-3.25 3.5-22 8-27.5 5.75-27.5 5.75-18.5-9-31.5-11.5-24-2-24.75-1.75z" fill="url(#G)"/>
|
||||
<g opacity=".27">
|
||||
<path fill="url(#m)" d="M219.25-182s-24.25 9-31.75 10.5-21 5.25-21 5.25c4.92-1.36 11.44 45.39 14.25 52.25l-.06.28c11.81-4.07 51.21-4.77 54.31-12.78 0 0-4.75-15.25-7.5-29.75S219.25-182 219.25-182z" transform="translate(0 192)"/>
|
||||
<path fill="url(#n)" d="M27-126.5s-5.5 12.75-8.25 24.75-5.75 23-5.75 23l50.625 16.281 15.781-58.911L27-126.5z" transform="translate(0 192)"/>
|
||||
<path fill="url(#o)" d="M26-24.5S22-14.25 18.5-.25s-5 30.75-5 30.75.147-.045.438-.094c2.033-.338 11.218-1.5 28.062 1.344 19.25 3.25 32.25 6 32.25 6l1.469-52.5-.75.125L26-24.5z" transform="translate(0 192)"/>
|
||||
<path fill="url(#p)" d="M114.75-180.25s-3.25 3.5-22 8-27.5 5.75-27.5 5.75c5.182 14.7 12.03 29.27 14.25 44.75l-.094.31 48.034-3.94 1.12.26c-3.36-13.33-7.79-54.6-13.81-55.13z" transform="translate(0 192)"/>
|
||||
<path fill="url(#q)" d="M117.59-78.094 63.969-62.375l-.344-.094L63.5-62l12.25 46.5-.031.75 49.471-8.875.53.187.03-.062-8.25-54.25.09-.344z" transform="translate(0 192)"/>
|
||||
<path fill="url(#r)" d="M128.56-125.12c.24.92.47 1.74.69 2.37l-11.66 44.656.1-.031L169.5-60.75l11.19-52.97c-.36.12-.7.25-1 .38l-51.13-11.78z" transform="translate(0 192)"/>
|
||||
<path fill="url(#s)" d="M125.53-23.5 114.5 25s5.5.5 22.5 6.25c2.12.719 4.16 1.406 6.12 2.031 1.97.626 3.86 1.186 5.63 1.719 10.62 3.199 17.5 4.75 17.5 4.75l12.72-45.406c-.51.182-1.02.38-1.53.562L125.53-23.5z" transform="translate(0 192)"/>
|
||||
<path fill="url(#t)" d="M218-76c-15.67 6.288-31.7 11.15-48.06 15.406l-.44-.156 9.5 55-.03.094c17.84-6.36 35.38-13.387 52.78-20.844 0 0-.5-4.5-3.75-19.75S218-76 218-76z" transform="translate(0 192)"/>
|
||||
</g>
|
||||
<g opacity=".043">
|
||||
<path fill="url(#u)" d="M13.719-179.97c-1.106 0-1.972.03-2.719.06-.445.02-.855.04-1.156.07-.03 0-.066-.01-.094 0-.398.03-.656.06-.75.09 0 0 7.5 13 11.75 27.75S27-126.5 27-126.5l51.469 5.12.937-.06.094-.31c-2.22-15.48-9.068-30.05-14.25-44.75 0 0-18.5-9-31.5-11.5-1.018-.2-2.01-.37-3-.53-.596-.1-1.167-.2-1.75-.28-.237-.04-.453-.06-.688-.1a98.772 98.772 0 0 0-3.812-.46c-.735-.08-1.395-.13-2.094-.19-.685-.06-1.353-.11-2-.16-.992-.07-1.89-.12-2.781-.15-1.456-.07-2.769-.1-3.906-.1z" transform="translate(0 192)"/>
|
||||
<path fill="url(#v)" d="M13-78.75s5.75 16 9.25 30S26-24.5 26-24.5l48.969 9.875.75-.125.031-.75L63.5-62l.125-.469L13-78.75z" transform="translate(0 192)"/>
|
||||
<path fill="url(#w)" d="m127.44-125.38-47.971 4h-.063L63.625-62.469l.344.094 53.621-15.719 11.66-44.656c-.22-.63-.45-1.45-.69-2.37l-1.12-.26z" transform="translate(0 192)"/>
|
||||
<path fill="url(#x)" d="M125.19-23.625 75.719-14.75l-1.469 52.5S87 35 98.25 31.5 114.5 25 114.5 25l11.22-48.438-.53-.187z" transform="translate(0 192)"/>
|
||||
<path fill="url(#y)" d="M114.75-180.25c6.02.53 10.45 41.8 13.81 55.13l51.13 11.78c.3-.13.64-.26 1-.38l.06-.28c-2.81-6.86-9.33-53.61-14.25-52.25 0 0-.61-.25-1.72-.72-3.32-1.39-11.09-4.59-20.28-7.78-12.25-4.25-29.75-5.5-29.75-5.5z" transform="translate(0 192)"/>
|
||||
<path fill="url(#z)" d="m117.69-78.125-.1.031-.09.344 8.25 53.25-.22 1 51.91 18.406c.51-.181 1.02-.38 1.53-.562l.03-.094-9.5-55-51.81-17.375z" transform="translate(0 192)"/>
|
||||
<path fill="url(#A)" d="M231.75-26.5c-17.4 7.457-34.94 14.484-52.78 20.844L166.25 39.75s13-2.75 26-5.75 26.5-8 26.5-8S218 21 223 1.5s8.75-28 8.75-28z" transform="translate(0 192)"/>
|
||||
<path fill="url(#B)" d="M235-126.5c-3.1 8.01-42.5 8.71-54.31 12.78L169.5-60.75l.44.156C186.3-64.85 202.33-69.712 218-76c0 0 1.75-10.5 6.75-23.75S235-126.5 235-126.5z" transform="translate(0 192)"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M174.28 35.875c-17.6 0-35.19 6.639-48.72 19.906-.16.164-.33.303-.5.469-22.05 22.056-26.206 55.25-12.47 81.5l-2.28 1.63c2.25 4.23 4.94 8.3 8.1 12.12l-13.32 13.34c-1.34-.58-2.78-1.03-4.56-1.37l-2.842 2.84c-1.11-.48-2.139-.81-3.063-.87L30.5 229.56c-.037 1.1.226 2.19.656 3.28l-.531.54-1.094 1.09c.716 3.74 2.011 6.82 5.469 10.28s6.42 4.63 10.281 5.47l1.094-1.1.719-.71c1.088.46 2.13.78 3.094.84l64.122-64.13c-.24-.97-.62-1.93-1.03-2.9l3-3c-.25-1.73-.68-3.2-1.31-4.6l13.28-13.28c3.82 3.16 7.89 5.85 12.13 8.1l1.62-2.28c26.25 13.74 59.44 9.58 81.5-12.47.17-.17.31-.34.47-.5 26.69-27.22 26.55-70.923-.47-97.94-13.59-13.59-31.41-20.375-49.22-20.375zm-.5 3.281c.11.002.21-.002.31 0a67.89 67.89 0 0 1 5.72.375c.1.01.19.021.28.031l.29.032c.14.017.29.044.43.062.9.11 1.8.228 2.69.375.62.102 1.23.224 1.84.344.35.066.69.116 1.04.187.72.153 1.43.324 2.15.5.31.077.63.138.94.219.07.019.14.043.22.063.95.253 1.89.516 2.84.812.07.023.15.04.22.063.95.3 1.91.625 2.84.969.06.019.11.042.16.062.95.352 1.9.728 2.84 1.125.06.022.11.041.16.063.94.4 1.88.836 2.81 1.281l.13.062c.93.451 1.86.91 2.78 1.407.03.017.06.044.09.062.93.503 1.84 1.013 2.75 1.562.03.02.06.044.1.063.9.551 1.8 1.122 2.68 1.719.55.369 1.09.769 1.63 1.156.36.261.73.512 1.09.781.02.014.05.018.06.031.88.656 1.75 1.36 2.6 2.063a70.875 70.875 0 0 1 5.06 4.594c1.62 1.62 3.14 3.3 4.56 5.031.01.011.03.02.03.031.71.855 1.41 1.715 2.07 2.594.28.379.54.773.81 1.156.37.517.74 1.038 1.09 1.563.02.024.05.038.07.062.59.89 1.16 1.778 1.71 2.688.02.032.05.061.07.094a75.45 75.45 0 0 1 1.56 2.75c.02.032.04.06.06.093.5.921.96 1.844 1.41 2.781l.06.126c.45.929.88 1.868 1.28 2.812l.06.156c.4.936.78 1.895 1.13 2.844l.06.156c.35.949.67 1.885.97 2.844l.06.219c.3.941.56 1.894.82 2.843l.06.219c.08.31.14.627.22.938.17.72.35 1.432.5 2.156.07.342.12.688.18 1.031.12.617.25 1.225.35 1.844.15.893.26 1.791.37 2.688.02.145.05.291.07.437l.03.281.03.282c.1.952.19 1.912.25 2.872.06.94.11 1.9.12 2.85.01.1 0 .2 0 .31.02 1.05.01 2.1-.03 3.15-.03.94-.08 1.86-.15 2.79-.01.12-.02.25-.04.37-.07.92-.16 1.84-.28 2.75-.12.96-.27 1.93-.43 2.88-.02.08-.02.16-.04.25-.02.12-.03.25-.06.37-.16.91-.36 1.82-.56 2.72-.03.13-.06.27-.09.41a86.1 86.1 0 0 1-.69 2.65c-.17.59-.35 1.17-.53 1.75-.18.57-.34 1.13-.53 1.69-.3.86-.61 1.71-.94 2.56-.06.16-.13.32-.19.47-.04.11-.08.23-.12.34-.36.88-.73 1.73-1.13 2.6-.38.82-.77 1.63-1.19 2.44-.08.15-.16.31-.25.46-.41.8-.83 1.6-1.28 2.38-.09.17-.18.34-.28.5-.2.34-.42.66-.62 1-.37.59-.75 1.17-1.13 1.75 0 .01.01.02 0 .03-.49.75-1 1.49-1.53 2.22-.46.64-.95 1.25-1.44 1.87-.19.26-.36.53-.56.79l-.41.5c-.16.2-.33.39-.5.59-.55.67-1.13 1.32-1.72 1.97-.6.67-1.23 1.35-1.87 2-.16.16-.31.34-.47.5-21.13 21.13-52.26 24.49-77.19 11.15l.13-.18c-3.71-1.9-7.26-4.14-10.63-6.78-.54-.43-1.06-.87-1.59-1.32-.51-.43-1.03-.86-1.53-1.31-.09-.08-.19-.16-.28-.25a86.97 86.97 0 0 1-2.41-2.31c-.79-.8-1.56-1.59-2.31-2.41-.09-.09-.17-.19-.25-.28-.45-.5-.88-1.02-1.31-1.53-.45-.53-.89-1.05-1.32-1.59a65.831 65.831 0 0 1-6.78-10.63l-.19.13c-13.33-24.93-9.97-56.061 11.16-77.191.16-.163.34-.308.5-.469.65-.64 1.33-1.269 2-1.875.65-.588 1.3-1.162 1.97-1.719.27-.227.54-.465.81-.687.35-.278.72-.512 1.06-.782.63-.483 1.24-.98 1.88-1.437.74-.53 1.5-1.036 2.25-1.531.58-.383 1.16-.764 1.75-1.125.45-.274.89-.551 1.34-.813.69-.395 1.37-.757 2.07-1.125a61.343 61.343 0 0 1 3.37-1.687c.87-.397 1.72-.769 2.59-1.125.16-.062.32-.096.47-.157.97-.384 1.93-.758 2.91-1.093.56-.192 1.12-.356 1.69-.532.58-.181 1.16-.367 1.75-.531.88-.246 1.77-.48 2.65-.687.07-.017.15-.015.22-.032.42-.095.84-.193 1.25-.281.67-.141 1.36-.287 2.03-.406.09-.015.17-.017.25-.031.95-.164 1.92-.317 2.88-.438a65.183 65.183 0 0 1 9.06-.5z" clip-path="url(#H)" transform="matrix(1 .08088 0 1 0 -21.056)" filter="url(#I)" fill="#2d3335"/>
|
||||
<path d="m48.01 227.46-4.807 4.55c-5.007-5.31-10.24-10.7-15.08-16.13l4.491-4.07c6.829 6.53 8.872 8.53 15.396 15.65z" fill="url(#J)"/>
|
||||
<path d="M-95.5 225c28.719 0 52 23.28 52 52s-23.281 52-52 52c-28.72 0-52-23.28-52-52s23.28-52 52-52z" fill-opacity=".476" fill="#d0e9f2"/>
|
||||
<path d="M-82 136c-27.61 0-50-22.39-50-50 0-27.614 22.39-50 50-50 27.614 0 50 22.386 50 50 0 27.61-22.386 50-50 50zm0-3c26.51 0 48-20.49 48-47s-21.49-47-48-47-48 20.49-48 47 21.49 47 48 47z" fill="#2d3335"/>
|
||||
<path fill="#9eaaac" d="M-82 136c-27.61 0-50-22.39-50-50v-.5c.27 27.38 22.55 49.5 50 49.5 27.446 0 49.729-22.12 50-49.5.002.168 0 .332 0 .5 0 27.61-22.386 50-50 50zm-48-50.5V85c0-26.51 21.49-47 48-47s48 20.49 48 47c0 .168.002.333 0 .5C-34.27 59.234-55.658 39-82 39c-26.34 0-47.73 20.234-48 46.5z"/>
|
||||
<path d="M-82 134c26.51 0 48-21.49 48-48s-21.49-48-48-48-48 21.49-48 48 21.49 48 48 48z" fill-opacity=".476" fill="#d0e9f2"/>
|
||||
<path d="M-82 134c26.51 0 48-21.49 48-48s-21.49-48-48-48-48 21.49-48 48 21.49 48 48 48z" fill="url(#K)"/>
|
||||
<path fill="url(#L)" d="m-96.438 130.72-.843 5C-92.451 137.2-87.316 138-82 138s10.451-.8 15.281-2.28l-.843-5C-72.115 132.19-76.959 133-82 133s-9.885-.81-14.438-2.28z"/>
|
||||
<path fill="url(#M)" d="M-82 136.25c1.757 0 3.431.3 5 .81v26.63c-1.569.51-3.243.81-5 .81s-3.431-.3-5-.81v-26.63c1.569-.51 3.243-.81 5-.81z"/>
|
||||
<path d="M-82 150.5c3.514 0 5.613.65 8 2.44v67.12c-3.137.91-4.486 1.44-8 1.44s-4.863-.53-8-1.44v-67.12c2.637-1.79 4.486-2.44 8-2.44z" fill="url(#N)"/>
|
||||
<path d="M-82 154.5c3.514 0 7.238.78 10 2.44v65.12c-2.012 1.79-6.486 2.44-10 2.44s-7.863-.15-10-2.44v-65.12c1.887-1.66 6.486-2.44 10-2.44z" fill="url(#O)"/>
|
||||
<path d="M-82 217.5c3.514 0 5.863.78 8 2.44v5.12c-2.387 1.54-4.486 2.44-8 2.44s-5.738-.9-8-2.44v-5.12c2.387-1.91 4.486-2.44 8-2.44z" fill="url(#P)"/>
|
||||
<path fill="#e0bb41" d="M-82 221.5c-3.514 0-5.613.28-8 2.44v1.12c2.262 1.54 4.486 2.44 8 2.44s5.613-.9 8-2.44v-1.12c-2.512-2.04-4.486-2.44-8-2.44z"/>
|
||||
<path d="M125.06 134.69c-27.176-27.18-27.176-71.251 0-98.431 27.18-27.18 71.25-27.18 98.43 0s27.18 71.251 0 98.431-71.25 27.18-98.43 0zm2.96-2.96c26.09 26.1 67.41 27.08 93.5.99 26.1-26.1 25.11-67.416-.98-93.509-26.09-26.092-67.41-27.077-93.51-.984-26.09 26.093-25.11 67.413.99 93.503z" fill="#2d3335"/>
|
||||
<path fill="#9eaaac" d="M125.06 134.69c-27.176-27.18-27.176-71.251 0-98.431.17-.166.33-.329.5-.493-26.689 27.218-26.526 70.924.49 97.934 27.01 27.02 70.72 27.18 97.93.49-.16.17-.32.33-.49.5-27.18 27.18-71.25 27.18-98.43 0zm2.46-96.955c.17-.167.33-.327.5-.492 26.09-26.093 67.41-25.109 93.5.984 26.1 26.093 27.08 67.413.99 93.503l-.5.5c25.59-26.12 24.46-67.091-1.47-93.019-25.93-25.927-66.9-27.063-93.02-1.476z"/>
|
||||
<path d="M127.03 132.72c26.1 26.09 68.4 26.09 94.49 0 26.1-26.1 26.1-68.4 0-94.493-26.09-26.093-68.39-26.093-94.49 0-26.09 26.093-26.09 68.393 0 94.493z" fill-opacity=".476" fill="#d0e9f2"/>
|
||||
<path d="M127.03 132.72c26.1 26.09 68.4 26.09 94.49 0 26.1-26.1 26.1-68.4 0-94.493-26.09-26.093-68.39-26.093-94.49 0-26.09 26.093-26.09 68.393 0 94.493z" fill="url(#Q)"/>
|
||||
<path fill="url(#R)" d="m116.05 115.28-5.75 4.09c3.3 6.21 7.56 12.05 12.8 17.28 5.23 5.24 11.07 9.5 17.28 12.8l4.09-5.75c-5.92-3.04-11.49-7.01-16.45-11.97s-8.93-10.53-11.97-16.45z"/>
|
||||
<path fill="url(#S)" d="M124.82 134.93c1.73 1.73 3.08 3.67 4.12 5.72l-26.21 26.21a22.302 22.302 0 0 1-5.718-4.12 22.268 22.268 0 0 1-4.121-5.72l26.209-26.21c2.05 1.04 3.99 2.39 5.72 4.12z"/>
|
||||
<path d="M116.27 159.23 50.199 225.3c-3.984-2.19-5.832-3-9.29-6.46s-4.267-5.3-6.459-9.29l66.07-66.07c5.06 3.75 12.12 10.4 15.75 15.75z" fill="url(#T)"/>
|
||||
<path d="M105.78 154.12c3.46 3.46 7.43 6.66 8.52 11.02l-64.101 64.1c-6.517-5.16-14.555-12.79-19.686-19.69l64.1-64.1c3.491.23 7.657 5.16 11.117 8.62l.05.05z" fill="url(#U)"/>
|
||||
<path opacity=".596" d="M105.53 154.36c3.46 3.46 7.68 6.42 8.77 10.78l-64.101 64.1c-7.877-6.91-11.798-10.63-19.686-19.69l64.1-64.1c3.491.23 7.417 5.45 10.877 8.91h.04z" fill="url(#V)"/>
|
||||
<path opacity=".604" d="M131.17 37.05c40.66-32.173 78.13-12.728 77.78 5.303s-56.57 70.357-76.37 66.117-25.45-53.389-1.41-71.42z" fill="url(#W)"/>
|
||||
<text opacity=".461" font-weight="bold" xml:space="preserve" transform="rotate(-8.9)" font-size="19.8" y="69.184" x="99.839" font-family="Monospace" fill="url(#X)"><tspan y="69.184" x="99.839">01011001</tspan><tspan y="93.934" x="99.839">00110101</tspan><tspan y="118.684" x="99.839">10010011</tspan></text>
|
||||
<text font-weight="bold" xml:space="preserve" transform="rotate(-8.9)" font-size="19.8" y="67.898" x="99.41" font-family="Monospace" fill="url(#Y)"><tspan y="67.898" x="99.41">01011001</tspan><tspan y="92.648" x="99.41">00110101</tspan><tspan y="117.398" x="99.41">10010011</tspan></text>
|
||||
<path opacity=".765" d="M219.46 129.09c-26.79 21.98-41.81 21.01-38.07 13.14 3.73-7.87 42.32-41.66 51.45-43.75 9.12-2.093 2.27 18.04-13.38 30.61z" fill="url(#Z)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,23 @@
|
||||
from typing import Any
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin.nominatim.tools.nominatim_search import NominatimSearchTool
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class NominatimProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
try:
|
||||
result = NominatimSearchTool().fork_tool_runtime(
|
||||
runtime={
|
||||
"credentials": credentials,
|
||||
}
|
||||
).invoke(
|
||||
user_id='',
|
||||
tool_parameters={
|
||||
'query': 'London',
|
||||
'limit': 1,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
raise ToolProviderCredentialValidationError(str(e))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user