diff --git a/.github/workflows/vdb-tests.yml b/.github/workflows/vdb-tests.yml index 3e73747220..5e3f7a557a 100644 --- a/.github/workflows/vdb-tests.yml +++ b/.github/workflows/vdb-tests.yml @@ -76,7 +76,6 @@ jobs: milvus-standalone pgvecto-rs pgvector - opengauss chroma elasticsearch diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index e32db548a4..acee26af2f 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -31,25 +31,24 @@ jobs: uses: tj-actions/changed-files@v45 with: files: web/** - # to run pnpm, should install package canvas, but it always install failed on amd64 under ubuntu-latest - # - name: Install pnpm - # uses: pnpm/action-setup@v4 - # with: - # version: 10 - # run_install: false - - # - name: Setup Node.js - # uses: actions/setup-node@v4 - # if: steps.changed-files.outputs.any_changed == 'true' - # with: - # node-version: 20 - # cache: pnpm - # cache-dependency-path: ./web/package.json - - # - name: Install dependencies - # if: steps.changed-files.outputs.any_changed == 'true' - # run: pnpm install --frozen-lockfile - - # - name: Run tests - # if: steps.changed-files.outputs.any_changed == 'true' - # run: pnpm test + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + if: steps.changed-files.outputs.any_changed == 'true' + with: + node-version: 20 + cache: pnpm + cache-dependency-path: ./web/package.json + + - name: Install dependencies + if: steps.changed-files.outputs.any_changed == 'true' + run: pnpm install --frozen-lockfile + + - name: Run tests + if: steps.changed-files.outputs.any_changed == 'true' + run: pnpm test diff --git a/.gitignore b/.gitignore index 7c5f4851c9..819a249581 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ celerybeat.pid # Environments .env +.env-local .venv env/ venv/ diff --git a/CONTRIBUTING_ES.md b/CONTRIBUTING_ES.md new file mode 100644 index 0000000000..261aa0fda1 --- /dev/null +++ b/CONTRIBUTING_ES.md @@ -0,0 +1,93 @@ +# CONTRIBUIR + +Así que estás buscando contribuir a Dify - eso es fantástico, estamos ansiosos por ver lo que haces. Como una startup con personal y financiación limitados, tenemos grandes ambiciones de diseñar el flujo de trabajo más intuitivo para construir y gestionar aplicaciones LLM. Cualquier ayuda de la comunidad cuenta, realmente. + +Necesitamos ser ágiles y enviar rápidamente dado donde estamos, pero también queremos asegurarnos de que colaboradores como tú obtengan una experiencia lo más fluida posible al contribuir. Hemos elaborado esta guía de contribución con ese propósito, con el objetivo de familiarizarte con la base de código y cómo trabajamos con los colaboradores, para que puedas pasar rápidamente a la parte divertida. + +Esta guía, como Dify mismo, es un trabajo en constante progreso. Agradecemos mucho tu comprensión si a veces se queda atrás del proyecto real, y damos la bienvenida a cualquier comentario para que podamos mejorar. + +En términos de licencia, por favor tómate un minuto para leer nuestro breve [Acuerdo de Licencia y Colaborador](./LICENSE). La comunidad también se adhiere al [código de conducta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md). + +## Antes de empezar + +¿Buscas algo en lo que trabajar? Explora nuestros [buenos primeros issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) y elige uno para comenzar. + +¿Tienes un nuevo modelo o herramienta genial para añadir? Abre un PR en nuestro [repositorio de plugins](https://github.com/langgenius/dify-plugins) y muéstranos lo que has construido. + +¿Necesitas actualizar un modelo existente, herramienta o corregir algunos errores? Dirígete a nuestro [repositorio oficial de plugins](https://github.com/langgenius/dify-official-plugins) y haz tu magia. + +¡Únete a la diversión, contribuye y construyamos algo increíble juntos! 💡✨ + +No olvides vincular un issue existente o abrir uno nuevo en la descripción del PR. + +### Informes de errores + +> [!IMPORTANT] +> Por favor, asegúrate de incluir la siguiente información al enviar un informe de error: + +- Un título claro y descriptivo +- Una descripción detallada del error, incluyendo cualquier mensaje de error +- Pasos para reproducir el error +- Comportamiento esperado +- **Logs**, si están disponibles, para problemas del backend, esto es realmente importante, puedes encontrarlos en los logs de docker-compose +- Capturas de pantalla o videos, si es aplicable + +Cómo priorizamos: + + | Tipo de Issue | Prioridad | + | ------------------------------------------------------------ | --------------- | + | Errores en funciones principales (servicio en la nube, no poder iniciar sesión, aplicaciones que no funcionan, fallos de seguridad) | Crítica | + | Errores no críticos, mejoras de rendimiento | Prioridad Media | + | Correcciones menores (errores tipográficos, UI confusa pero funcional) | Prioridad Baja | + +### Solicitudes de funcionalidades + +> [!NOTE] +> Por favor, asegúrate de incluir la siguiente información al enviar una solicitud de funcionalidad: + +- Un título claro y descriptivo +- Una descripción detallada de la funcionalidad +- Un caso de uso para la funcionalidad +- Cualquier otro contexto o capturas de pantalla sobre la solicitud de funcionalidad + +Cómo priorizamos: + + | Tipo de Funcionalidad | Prioridad | + | ------------------------------------------------------------ | --------------- | + | Funcionalidades de alta prioridad etiquetadas por un miembro del equipo | Prioridad Alta | + | Solicitudes populares de funcionalidades de nuestro [tablero de comentarios de la comunidad](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Prioridad Media | + | Funcionalidades no principales y mejoras menores | Prioridad Baja | + | Valiosas pero no inmediatas | Futura-Funcionalidad | +## Enviando tu PR + +### Proceso de Pull Request + +1. Haz un fork del repositorio +2. Antes de redactar un PR, por favor crea un issue para discutir los cambios que quieres hacer +3. Crea una nueva rama para tus cambios +4. Por favor añade pruebas para tus cambios en consecuencia +5. Asegúrate de que tu código pasa las pruebas existentes +6. Por favor vincula el issue en la descripción del PR, `fixes #` +7. ¡Fusiona tu código! +### Configuración del proyecto + +#### Frontend + +Para configurar el servicio frontend, por favor consulta nuestra [guía completa](https://github.com/langgenius/dify/blob/main/web/README.md) en el archivo `web/README.md`. Este documento proporciona instrucciones detalladas para ayudarte a configurar el entorno frontend correctamente. + +#### Backend + +Para configurar el servicio backend, por favor consulta nuestras [instrucciones detalladas](https://github.com/langgenius/dify/blob/main/api/README.md) en el archivo `api/README.md`. Este documento contiene una guía paso a paso para ayudarte a poner en marcha el backend sin problemas. + +#### Otras cosas a tener en cuenta + +Recomendamos revisar este documento cuidadosamente antes de proceder con la configuración, ya que contiene información esencial sobre: +- Requisitos previos y dependencias +- Pasos de instalación +- Detalles de configuración +- Consejos comunes de solución de problemas + +No dudes en contactarnos si encuentras algún problema durante el proceso de configuración. +## Obteniendo Ayuda + +Si alguna vez te quedas atascado o tienes una pregunta urgente mientras contribuyes, simplemente envíanos tus consultas a través del issue relacionado de GitHub, o únete a nuestro [Discord](https://discord.gg/8Tpq4AcN9c) para una charla rápida. \ No newline at end of file diff --git a/CONTRIBUTING_FR.md b/CONTRIBUTING_FR.md new file mode 100644 index 0000000000..c3418f86cc --- /dev/null +++ b/CONTRIBUTING_FR.md @@ -0,0 +1,93 @@ +# CONTRIBUER + +Vous cherchez donc à contribuer à Dify - c'est fantastique, nous avons hâte de voir ce que vous allez faire. En tant que startup avec un personnel et un financement limités, nous avons de grandes ambitions pour concevoir le flux de travail le plus intuitif pour construire et gérer des applications LLM. Toute aide de la communauté compte, vraiment. + +Nous devons être agiles et livrer rapidement compte tenu de notre position, mais nous voulons aussi nous assurer que des contributeurs comme vous obtiennent une expérience aussi fluide que possible lors de leur contribution. Nous avons élaboré ce guide de contribution dans ce but, visant à vous familiariser avec la base de code et comment nous travaillons avec les contributeurs, afin que vous puissiez rapidement passer à la partie amusante. + +Ce guide, comme Dify lui-même, est un travail en constante évolution. Nous apprécions grandement votre compréhension si parfois il est en retard par rapport au projet réel, et nous accueillons tout commentaire pour nous aider à nous améliorer. + +En termes de licence, veuillez prendre une minute pour lire notre bref [Accord de Licence et de Contributeur](./LICENSE). La communauté adhère également au [code de conduite](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md). + +## Avant de vous lancer + +Vous cherchez quelque chose à réaliser ? Parcourez nos [problèmes pour débutants](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) et choisissez-en un pour commencer ! + +Vous avez un nouveau modèle ou un nouvel outil à ajouter ? Ouvrez une PR dans notre [dépôt de plugins](https://github.com/langgenius/dify-plugins) et montrez-nous ce que vous avez créé. + +Vous devez mettre à jour un modèle existant, un outil ou corriger des bugs ? Rendez-vous sur notre [dépôt officiel de plugins](https://github.com/langgenius/dify-official-plugins) et faites votre magie ! + +Rejoignez l'aventure, contribuez, et construisons ensemble quelque chose d'extraordinaire ! 💡✨ + +N'oubliez pas de lier un problème existant ou d'ouvrir un nouveau problème dans la description de votre PR. + +### Rapports de bugs + +> [!IMPORTANT] +> Veuillez vous assurer d'inclure les informations suivantes lors de la soumission d'un rapport de bug : + +- Un titre clair et descriptif +- Une description détaillée du bug, y compris tous les messages d'erreur +- Les étapes pour reproduire le bug +- Comportement attendu +- **Logs**, si disponibles, pour les problèmes de backend, c'est vraiment important, vous pouvez les trouver dans les logs de docker-compose +- Captures d'écran ou vidéos, si applicable + +Comment nous priorisons : + + | Type de Problème | Priorité | + | ------------------------------------------------------------ | --------------- | + | Bugs dans les fonctions principales (service cloud, impossibilité de se connecter, applications qui ne fonctionnent pas, failles de sécurité) | Critique | + | Bugs non critiques, améliorations de performance | Priorité Moyenne | + | Corrections mineures (fautes de frappe, UI confuse mais fonctionnelle) | Priorité Basse | + +### Demandes de fonctionnalités + +> [!NOTE] +> Veuillez vous assurer d'inclure les informations suivantes lors de la soumission d'une demande de fonctionnalité : + +- Un titre clair et descriptif +- Une description détaillée de la fonctionnalité +- Un cas d'utilisation pour la fonctionnalité +- Tout autre contexte ou captures d'écran concernant la demande de fonctionnalité + +Comment nous priorisons : + + | Type de Fonctionnalité | Priorité | + | ------------------------------------------------------------ | --------------- | + | Fonctionnalités hautement prioritaires étiquetées par un membre de l'équipe | Priorité Haute | + | Demandes populaires de fonctionnalités de notre [tableau de feedback communautaire](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Priorité Moyenne | + | Fonctionnalités non essentielles et améliorations mineures | Priorité Basse | + | Précieuses mais non immédiates | Fonctionnalité Future | +## Soumettre votre PR + +### Processus de Pull Request + +1. Forkez le dépôt +2. Avant de rédiger une PR, veuillez créer un problème pour discuter des changements que vous souhaitez apporter +3. Créez une nouvelle branche pour vos changements +4. Veuillez ajouter des tests pour vos changements en conséquence +5. Assurez-vous que votre code passe les tests existants +6. Veuillez lier le problème dans la description de la PR, `fixes #` +7. Faites fusionner votre code ! +### Configuration du projet + +#### Frontend + +Pour configurer le service frontend, veuillez consulter notre [guide complet](https://github.com/langgenius/dify/blob/main/web/README.md) dans le fichier `web/README.md`. Ce document fournit des instructions détaillées pour vous aider à configurer correctement l'environnement frontend. + +#### Backend + +Pour configurer le service backend, veuillez consulter nos [instructions détaillées](https://github.com/langgenius/dify/blob/main/api/README.md) dans le fichier `api/README.md`. Ce document contient un guide étape par étape pour vous aider à faire fonctionner le backend sans problème. + +#### Autres choses à noter + +Nous recommandons de revoir attentivement ce document avant de procéder à la configuration, car il contient des informations essentielles sur : +- Prérequis et dépendances +- Étapes d'installation +- Détails de configuration +- Conseils courants de dépannage + +N'hésitez pas à nous contacter si vous rencontrez des problèmes pendant le processus de configuration. +## Obtenir de l'aide + +Si jamais vous êtes bloqué ou avez une question urgente en contribuant, envoyez-nous simplement vos questions via le problème GitHub concerné, ou rejoignez notre [Discord](https://discord.gg/8Tpq4AcN9c) pour une discussion rapide. \ No newline at end of file diff --git a/CONTRIBUTING_KR.md b/CONTRIBUTING_KR.md new file mode 100644 index 0000000000..fcf44d495a --- /dev/null +++ b/CONTRIBUTING_KR.md @@ -0,0 +1,93 @@ +# 기여하기 + +Dify에 기여하려고 하시는군요 - 정말 멋집니다, 당신이 무엇을 할지 기대가 됩니다. 인력과 자금이 제한된 스타트업으로서, 우리는 LLM 애플리케이션을 구축하고 관리하기 위한 가장 직관적인 워크플로우를 설계하고자 하는 큰 야망을 가지고 있습니다. 커뮤니티의 모든 도움은 정말 중요합니다. + +우리는 현재 상황에서 민첩하게 빠르게 배포해야 하지만, 동시에 당신과 같은 기여자들이 기여하는 과정에서 최대한 원활한 경험을 얻을 수 있도록 하고 싶습니다. 우리는 이러한 목적으로 이 기여 가이드를 작성했으며, 여러분이 코드베이스와 우리가 기여자들과 어떻게 협업하는지에 대해 친숙해질 수 있도록 돕고, 빠르게 재미있는 부분으로 넘어갈 수 있도록 하고자 합니다. + +이 가이드는 Dify 자체와 마찬가지로 끊임없이 진행 중인 작업입니다. 때로는 실제 프로젝트보다 뒤처질 수 있다는 점을 이해해 주시면 감사하겠으며, 개선을 위한 피드백은 언제든지 환영합니다. + +라이센스 측면에서, 간략한 [라이센스 및 기여자 동의서](./LICENSE)를 읽어보는 시간을 가져주세요. 커뮤니티는 또한 [행동 강령](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md)을 준수합니다. + +## 시작하기 전에 + +처리할 작업을 찾고 계신가요? [초보자를 위한 이슈](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)를 살펴보고 시작할 것을 선택하세요! + +추가할 새로운 모델 런타임이나 도구가 있나요? 우리의 [플러그인 저장소](https://github.com/langgenius/dify-plugins)에 PR을 열고 당신이 만든 것을 보여주세요. + +기존 모델 런타임, 도구를 업데이트하거나 버그를 수정해야 하나요? 우리의 [공식 플러그인 저장소](https://github.com/langgenius/dify-official-plugins)로 가서 당신의 마법을 펼치세요! + +함께 즐기고, 기여하고, 멋진 것을 함께 만들어 봅시다! 💡✨ + +PR 설명에 기존 이슈를 연결하거나 새 이슈를 여는 것을 잊지 마세요. + +### 버그 보고 + +> [!IMPORTANT] +> 버그 보고서를 제출할 때 다음 정보를 포함해 주세요: + +- 명확하고 설명적인 제목 +- 오류 메시지를 포함한 버그에 대한 상세한 설명 +- 버그를 재현하는 단계 +- 예상되는 동작 +- 가능한 경우 **로그**, 백엔드 이슈의 경우 매우 중요합니다. docker-compose 로그에서 찾을 수 있습니다 +- 해당되는 경우 스크린샷 또는 비디오 + +우선순위 결정 방법: + + | 이슈 유형 | 우선순위 | + | ------------------------------------------------------------ | --------------- | + | 핵심 기능의 버그(클라우드 서비스, 로그인 불가, 애플리케이션 작동 불능, 보안 취약점) | 중대 | + | 비중요 버그, 성능 향상 | 중간 우선순위 | + | 사소한 수정(오타, 혼란스럽지만 작동하는 UI) | 낮은 우선순위 | + +### 기능 요청 + +> [!NOTE] +> 기능 요청을 제출할 때 다음 정보를 포함해 주세요: + +- 명확하고 설명적인 제목 +- 기능에 대한 상세한 설명 +- 해당 기능의 사용 사례 +- 기능 요청에 관한 기타 컨텍스트 또는 스크린샷 + +우선순위 결정 방법: + + | 기능 유형 | 우선순위 | + | ------------------------------------------------------------ | --------------- | + | 팀 구성원에 의해 레이블이 지정된 고우선순위 기능 | 높은 우선순위 | + | 우리의 [커뮤니티 피드백 보드](https://github.com/langgenius/dify/discussions/categories/feedbacks)에서 인기 있는 기능 요청 | 중간 우선순위 | + | 비핵심 기능 및 사소한 개선 | 낮은 우선순위 | + | 가치 있지만 즉시 필요하지 않은 기능 | 미래 기능 | +## PR 제출하기 + +### Pull Request 프로세스 + +1. 저장소를 포크하세요 +2. PR을 작성하기 전에, 변경하고자 하는 내용에 대해 논의하기 위한 이슈를 생성해 주세요 +3. 변경 사항을 위한 새 브랜치를 만드세요 +4. 변경 사항에 대한 테스트를 적절히 추가해 주세요 +5. 코드가 기존 테스트를 통과하는지 확인하세요 +6. PR 설명에 이슈를 연결해 주세요, `fixes #<이슈_번호>` +7. 병합 완료! +### 프로젝트 설정하기 + +#### 프론트엔드 + +프론트엔드 서비스를 설정하려면, `web/README.md` 파일에 있는 우리의 [종합 가이드](https://github.com/langgenius/dify/blob/main/web/README.md)를 참조하세요. 이 문서는 프론트엔드 환경을 적절히 설정하는 데 도움이 되는 자세한 지침을 제공합니다. + +#### 백엔드 + +백엔드 서비스를 설정하려면, `api/README.md` 파일에 있는 우리의 [상세 지침](https://github.com/langgenius/dify/blob/main/api/README.md)을 참조하세요. 이 문서는 백엔드를 원활하게 실행하는 데 도움이 되는 단계별 가이드를 포함하고 있습니다. + +#### 기타 참고 사항 + +설정을 진행하기 전에 이 문서를 주의 깊게 검토하는 것을 권장합니다. 다음과 같은 필수 정보가 포함되어 있습니다: +- 필수 조건 및 종속성 +- 설치 단계 +- 구성 세부 정보 +- 일반적인 문제 해결 팁 + +설정 과정에서 문제가 발생하면 언제든지 연락해 주세요. +## 도움 받기 + +기여하는 동안 막히거나 긴급한 질문이 있으면, 관련 GitHub 이슈를 통해 질문을 보내거나, 빠른 대화를 위해 우리의 [Discord](https://discord.gg/8Tpq4AcN9c)에 참여하세요. \ No newline at end of file diff --git a/CONTRIBUTING_PT.md b/CONTRIBUTING_PT.md new file mode 100644 index 0000000000..bba76c17ee --- /dev/null +++ b/CONTRIBUTING_PT.md @@ -0,0 +1,93 @@ +# CONTRIBUINDO + +Então você está procurando contribuir para o Dify - isso é incrível, mal podemos esperar para ver o que você vai fazer. Como uma startup com equipe e financiamento limitados, temos grandes ambições de projetar o fluxo de trabalho mais intuitivo para construir e gerenciar aplicações LLM. Qualquer ajuda da comunidade conta, verdadeiramente. + +Precisamos ser ágeis e entregar rapidamente considerando onde estamos, mas também queremos garantir que colaboradores como você tenham uma experiência o mais tranquila possível ao contribuir. Montamos este guia de contribuição com esse propósito, visando familiarizá-lo com a base de código e como trabalhamos com os colaboradores, para que você possa rapidamente passar para a parte divertida. + +Este guia, como o próprio Dify, é um trabalho em constante evolução. Agradecemos muito a sua compreensão se às vezes ele ficar atrasado em relação ao projeto real, e damos as boas-vindas a qualquer feedback para que possamos melhorar. + +Em termos de licenciamento, por favor, dedique um minuto para ler nosso breve [Acordo de Licença e Contribuidor](./LICENSE). A comunidade também adere ao [código de conduta](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md). + +## Antes de começar + +Procurando algo para resolver? Navegue por nossos [problemas para iniciantes](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) e escolha um para começar! + +Tem um novo modelo ou ferramenta para adicionar? Abra um PR em nosso [repositório de plugins](https://github.com/langgenius/dify-plugins) e mostre-nos o que você construiu. + +Precisa atualizar um modelo existente, ferramenta ou corrigir alguns bugs? Vá para nosso [repositório oficial de plugins](https://github.com/langgenius/dify-official-plugins) e faça sua mágica! + +Junte-se à diversão, contribua e vamos construir algo incrível juntos! 💡✨ + +Não se esqueça de vincular um problema existente ou abrir um novo problema na descrição do PR. + +### Relatórios de bugs + +> [!IMPORTANT] +> Por favor, certifique-se de incluir as seguintes informações ao enviar um relatório de bug: + +- Um título claro e descritivo +- Uma descrição detalhada do bug, incluindo quaisquer mensagens de erro +- Passos para reproduzir o bug +- Comportamento esperado +- **Logs**, se disponíveis, para problemas de backend, isso é realmente importante, você pode encontrá-los nos logs do docker-compose +- Capturas de tela ou vídeos, se aplicável + +Como priorizamos: + + | Tipo de Problema | Prioridade | + | ------------------------------------------------------------ | --------------- | + | Bugs em funções centrais (serviço em nuvem, não conseguir fazer login, aplicações não funcionando, falhas de segurança) | Crítica | + | Bugs não críticos, melhorias de desempenho | Prioridade Média | + | Correções menores (erros de digitação, interface confusa mas funcional) | Prioridade Baixa | + +### Solicitações de recursos + +> [!NOTE] +> Por favor, certifique-se de incluir as seguintes informações ao enviar uma solicitação de recurso: + +- Um título claro e descritivo +- Uma descrição detalhada do recurso +- Um caso de uso para o recurso +- Qualquer outro contexto ou capturas de tela sobre a solicitação de recurso + +Como priorizamos: + + | Tipo de Recurso | Prioridade | + | ------------------------------------------------------------ | --------------- | + | Recursos de alta prioridade conforme rotulado por um membro da equipe | Prioridade Alta | + | Solicitações populares de recursos do nosso [quadro de feedback da comunidade](https://github.com/langgenius/dify/discussions/categories/feedbacks) | Prioridade Média | + | Recursos não essenciais e melhorias menores | Prioridade Baixa | + | Valiosos mas não imediatos | Recurso Futuro | +## Enviando seu PR + +### Processo de Pull Request + +1. Faça um fork do repositório +2. Antes de elaborar um PR, por favor crie um problema para discutir as mudanças que você quer fazer +3. Crie um novo branch para suas alterações +4. Por favor, adicione testes para suas alterações conforme apropriado +5. Certifique-se de que seu código passa nos testes existentes +6. Por favor, vincule o problema na descrição do PR, `fixes #` +7. Faça o merge do seu código! +### Configurando o projeto + +#### Frontend + +Para configurar o serviço frontend, por favor consulte nosso [guia abrangente](https://github.com/langgenius/dify/blob/main/web/README.md) no arquivo `web/README.md`. Este documento fornece instruções detalhadas para ajudá-lo a configurar o ambiente frontend adequadamente. + +#### Backend + +Para configurar o serviço backend, por favor consulte nossas [instruções detalhadas](https://github.com/langgenius/dify/blob/main/api/README.md) no arquivo `api/README.md`. Este documento contém um guia passo a passo para ajudá-lo a colocar o backend em funcionamento sem problemas. + +#### Outras coisas a observar + +Recomendamos revisar este documento cuidadosamente antes de prosseguir com a configuração, pois ele contém informações essenciais sobre: +- Pré-requisitos e dependências +- Etapas de instalação +- Detalhes de configuração +- Dicas comuns de solução de problemas + +Sinta-se à vontade para entrar em contato se encontrar quaisquer problemas durante o processo de configuração. +## Obtendo Ajuda + +Se você ficar preso ou tiver uma dúvida urgente enquanto contribui, simplesmente envie suas perguntas através do problema relacionado no GitHub, ou entre no nosso [Discord](https://discord.gg/8Tpq4AcN9c) para uma conversa rápida. \ No newline at end of file diff --git a/CONTRIBUTING_TR.md b/CONTRIBUTING_TR.md new file mode 100644 index 0000000000..4e216d22a4 --- /dev/null +++ b/CONTRIBUTING_TR.md @@ -0,0 +1,93 @@ +# KATKIDA BULUNMAK + +Demek Dify'a katkıda bulunmak istiyorsunuz - bu harika, ne yapacağınızı görmek için sabırsızlanıyoruz. Sınırlı personel ve finansmana sahip bir startup olarak, LLM uygulamaları oluşturmak ve yönetmek için en sezgisel iş akışını tasarlama konusunda büyük hedeflerimiz var. Topluluktan gelen her türlü yardım gerçekten önemli. + +Bulunduğumuz noktada çevik olmamız ve hızlı hareket etmemiz gerekiyor, ancak sizin gibi katkıda bulunanların mümkün olduğunca sorunsuz bir deneyim yaşamasını da sağlamak istiyoruz. Bu katkı rehberini bu amaçla hazırladık; sizi kod tabanıyla ve katkıda bulunanlarla nasıl çalıştığımızla tanıştırmayı, böylece hızlıca eğlenceli kısma geçebilmenizi hedefliyoruz. + +Bu rehber, Dify'ın kendisi gibi, sürekli gelişen bir çalışmadır. Bazen gerçek projenin gerisinde kalırsa anlayışınız için çok minnettarız ve gelişmemize yardımcı olacak her türlü geri bildirimi memnuniyetle karşılıyoruz. + +Lisanslama konusunda, lütfen kısa [Lisans ve Katkıda Bulunan Anlaşmamızı](./LICENSE) okumak için bir dakikanızı ayırın. Topluluk ayrıca [davranış kurallarına](https://github.com/langgenius/.github/blob/main/CODE_OF_CONDUCT.md) da uyar. + +## Başlamadan Önce + +Üzerinde çalışacak bir şey mi arıyorsunuz? [İlk katkıda bulunanlar için iyi sorunlarımıza](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) göz atın ve başlamak için birini seçin! + +Eklenecek harika bir yeni model runtime'ı veya aracınız mı var? [Eklenti depomuzda](https://github.com/langgenius/dify-plugins) bir PR açın ve ne yaptığınızı bize gösterin. + +Mevcut bir model runtime'ını, aracı güncellemek veya bazı hataları düzeltmek mi istiyorsunuz? [Resmi eklenti depomuza](https://github.com/langgenius/dify-official-plugins) gidin ve sihrinizi gösterin! + +Eğlenceye katılın, katkıda bulunun ve birlikte harika bir şeyler inşa edelim! 💡✨ + +PR açıklamasında mevcut bir sorunu bağlamayı veya yeni bir sorun açmayı unutmayın. + +### Hata Raporları + +> [!IMPORTANT] +> Lütfen bir hata raporu gönderirken aşağıdaki bilgileri dahil ettiğinizden emin olun: + +- Net ve açıklayıcı bir başlık +- Hata mesajları dahil hatanın ayrıntılı bir açıklaması +- Hatayı tekrarlamak için adımlar +- Beklenen davranış +- Mümkünse **Loglar**, backend sorunları için, bu gerçekten önemlidir, bunları docker-compose loglarında bulabilirsiniz +- Uygunsa ekran görüntüleri veya videolar + +Nasıl önceliklendiriyoruz: + + | Sorun Türü | Öncelik | + | ------------------------------------------------------------ | --------------- | + | Temel işlevlerdeki hatalar (bulut hizmeti, giriş yapamama, çalışmayan uygulamalar, güvenlik açıkları) | Kritik | + | Kritik olmayan hatalar, performans artışları | Orta Öncelik | + | Küçük düzeltmeler (yazım hataları, kafa karıştırıcı ama çalışan UI) | Düşük Öncelik | + +### Özellik İstekleri + +> [!NOTE] +> Lütfen bir özellik isteği gönderirken aşağıdaki bilgileri dahil ettiğinizden emin olun: + +- Net ve açıklayıcı bir başlık +- Özelliğin ayrıntılı bir açıklaması +- Özellik için bir kullanım durumu +- Özellik isteği hakkında diğer bağlamlar veya ekran görüntüleri + +Nasıl önceliklendiriyoruz: + + | Özellik Türü | Öncelik | + | ------------------------------------------------------------ | --------------- | + | Bir ekip üyesi tarafından etiketlenen Yüksek Öncelikli Özellikler | Yüksek Öncelik | + | [Topluluk geri bildirim panosundan](https://github.com/langgenius/dify/discussions/categories/feedbacks) popüler özellik istekleri | Orta Öncelik | + | Temel olmayan özellikler ve küçük geliştirmeler | Düşük Öncelik | + | Değerli ama acil olmayan | Gelecek-Özellik | +## PR'nizi Göndermek + +### Pull Request Süreci + +1. Depoyu fork edin +2. Bir PR taslağı oluşturmadan önce, yapmak istediğiniz değişiklikleri tartışmak için lütfen bir sorun oluşturun +3. Değişiklikleriniz için yeni bir dal oluşturun +4. Lütfen değişiklikleriniz için uygun testler ekleyin +5. Kodunuzun mevcut testleri geçtiğinden emin olun +6. Lütfen PR açıklamasında sorunu bağlayın, `fixes #` +7. Kodunuzu birleştirin! +### Projeyi Kurma + +#### Frontend + +Frontend hizmetini kurmak için, lütfen `web/README.md` dosyasındaki kapsamlı [rehberimize](https://github.com/langgenius/dify/blob/main/web/README.md) bakın. Bu belge, frontend ortamını düzgün bir şekilde kurmanıza yardımcı olacak ayrıntılı talimatlar sağlar. + +#### Backend + +Backend hizmetini kurmak için, lütfen `api/README.md` dosyasındaki detaylı [talimatlarımıza](https://github.com/langgenius/dify/blob/main/api/README.md) bakın. Bu belge, backend'i sorunsuz bir şekilde çalıştırmanıza yardımcı olacak adım adım bir kılavuz içerir. + +#### Dikkat Edilecek Diğer Şeyler + +Kuruluma geçmeden önce bu belgeyi dikkatlice incelemenizi öneririz, çünkü şunlar hakkında temel bilgiler içerir: +- Ön koşullar ve bağımlılıklar +- Kurulum adımları +- Yapılandırma detayları +- Yaygın sorun giderme ipuçları + +Kurulum süreci sırasında herhangi bir sorunla karşılaşırsanız bizimle iletişime geçmekten çekinmeyin. +## Yardım Almak + +Katkıda bulunurken takılırsanız veya yanıcı bir sorunuz olursa, sorularınızı ilgili GitHub sorunu aracılığıyla bize gönderin veya hızlı bir sohbet için [Discord'umuza](https://discord.gg/8Tpq4AcN9c) katılın. \ No newline at end of file diff --git a/api/configs/feature/hosted_service/__init__.py b/api/configs/feature/hosted_service/__init__.py index 71d06f4623..18ef1ed45b 100644 --- a/api/configs/feature/hosted_service/__init__.py +++ b/api/configs/feature/hosted_service/__init__.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import Field, NonNegativeInt, computed_field +from pydantic import Field, NonNegativeInt from pydantic_settings import BaseSettings diff --git a/api/configs/remote_settings_sources/__init__.py b/api/configs/remote_settings_sources/__init__.py index 4f3878d13b..be5d5d896e 100644 --- a/api/configs/remote_settings_sources/__init__.py +++ b/api/configs/remote_settings_sources/__init__.py @@ -1,5 +1,3 @@ -from typing import Optional - from pydantic import Field from .apollo import ApolloSettingsSourceInfo diff --git a/api/controllers/service_api/app/workflow.py b/api/controllers/service_api/app/workflow.py index 1cd7e5bf23..2854a43505 100644 --- a/api/controllers/service_api/app/workflow.py +++ b/api/controllers/service_api/app/workflow.py @@ -54,7 +54,7 @@ workflow_run_fields = { class WorkflowRunDetailApi(Resource): @validate_app_token @marshal_with(workflow_run_fields) - def get(self, app_model: App, workflow_id: str): + def get(self, app_model: App, workflow_run_id: str): """ Get a workflow task running detail """ @@ -62,7 +62,7 @@ class WorkflowRunDetailApi(Resource): if app_mode != AppMode.WORKFLOW: raise NotWorkflowAppError() - workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_id).first() + workflow_run = db.session.query(WorkflowRun).filter(WorkflowRun.id == workflow_run_id).first() return workflow_run @@ -163,6 +163,6 @@ class WorkflowAppLogApi(Resource): api.add_resource(WorkflowRunApi, "/workflows/run") -api.add_resource(WorkflowRunDetailApi, "/workflows/run/") +api.add_resource(WorkflowRunDetailApi, "/workflows/run/") api.add_resource(WorkflowTaskStopApi, "/workflows/tasks//stop") api.add_resource(WorkflowAppLogApi, "/workflows/logs") diff --git a/api/controllers/service_api/dataset/dataset.py b/api/controllers/service_api/dataset/dataset.py index 8ab743dc44..d813ae7ebd 100644 --- a/api/controllers/service_api/dataset/dataset.py +++ b/api/controllers/service_api/dataset/dataset.py @@ -142,6 +142,7 @@ class DatasetApi(DatasetApiResource): Deletes a dataset given its ID. Args: + _: ignore dataset_id (UUID): The ID of the dataset to be deleted. Returns: diff --git a/api/controllers/web/passport.py b/api/controllers/web/passport.py index 4625c1f43d..e30998c803 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -19,6 +19,8 @@ class PassportResource(Resource): def get(self): system_features = FeatureService.get_system_features() app_code = request.headers.get("X-App-Code") + user_id = request.args.get("user_id") + if app_code is None: raise Unauthorized("X-App-Code header is missing.") @@ -36,16 +38,33 @@ class PassportResource(Resource): if not app_model or app_model.status != "normal" or not app_model.enable_site: raise NotFound() - end_user = EndUser( - tenant_id=app_model.tenant_id, - app_id=app_model.id, - type="browser", - is_anonymous=True, - session_id=generate_session_id(), - ) - - db.session.add(end_user) - db.session.commit() + if user_id: + end_user = ( + db.session.query(EndUser).filter(EndUser.app_id == app_model.id, EndUser.session_id == user_id).first() + ) + + if end_user: + pass + else: + end_user = EndUser( + tenant_id=app_model.tenant_id, + app_id=app_model.id, + type="browser", + is_anonymous=True, + session_id=user_id, + ) + db.session.add(end_user) + db.session.commit() + else: + end_user = EndUser( + tenant_id=app_model.tenant_id, + app_id=app_model.id, + type="browser", + is_anonymous=True, + session_id=generate_session_id(), + ) + db.session.add(end_user) + db.session.commit() payload = { "iss": site.app_id, diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index 13c4e4c3d1..48c92ea2db 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -332,7 +332,7 @@ class BaseAgentRunner(AppRunner): agent_thought = updated_agent_thought if thought: - agent_thought.thought = thought + agent_thought.thought += thought if tool_name: agent_thought.tool = tool_name diff --git a/api/core/agent/output_parser/cot_output_parser.py b/api/core/agent/output_parser/cot_output_parser.py index 61fa774ea5..7c8f09e6b9 100644 --- a/api/core/agent/output_parser/cot_output_parser.py +++ b/api/core/agent/output_parser/cot_output_parser.py @@ -12,39 +12,45 @@ class CotAgentOutputParser: def handle_react_stream_output( cls, llm_response: Generator[LLMResultChunk, None, None], usage_dict: dict ) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]: - def parse_action(json_str): - try: - action = json.loads(json_str, strict=False) - action_name = None - action_input = None - - # cohere always returns a list - if isinstance(action, list) and len(action) == 1: - action = action[0] - - for key, value in action.items(): - if "input" in key.lower(): - action_input = value - else: - action_name = value - - if action_name is not None and action_input is not None: - return AgentScratchpadUnit.Action( - action_name=action_name, - action_input=action_input, - ) + def parse_action(action) -> Union[str, AgentScratchpadUnit.Action]: + action_name = None + action_input = None + if isinstance(action, str): + try: + action = json.loads(action, strict=False) + except json.JSONDecodeError: + return action or "" + + # cohere always returns a list + if isinstance(action, list) and len(action) == 1: + action = action[0] + + for key, value in action.items(): + if "input" in key.lower(): + action_input = value else: - return json_str or "" + action_name = value + + if action_name is not None and action_input is not None: + return AgentScratchpadUnit.Action( + action_name=action_name, + action_input=action_input, + ) + else: + return json.dumps(action) + + def extra_json_from_code_block(code_block) -> list[Union[list, dict]]: + blocks = re.findall(r"```[json]*\s*([\[{].*[]}])\s*```", code_block, re.DOTALL | re.IGNORECASE) + if not blocks: + return [] + try: + json_blocks = [] + for block in blocks: + json_text = re.sub(r"^[a-zA-Z]+\n", "", block.strip(), flags=re.MULTILINE) + json_blocks.append(json.loads(json_text, strict=False)) + return json_blocks except: - return json_str or "" - - def extra_json_from_code_block(code_block) -> Generator[Union[str, AgentScratchpadUnit.Action], None, None]: - code_blocks = re.findall(r"```(.*?)```", code_block, re.DOTALL) - if not code_blocks: - return - for block in code_blocks: - json_text = re.sub(r"^[a-zA-Z]+\n", "", block.strip(), flags=re.MULTILINE) - yield parse_action(json_text) + return [] code_block_cache = "" code_block_delimiter_count = 0 @@ -78,7 +84,7 @@ class CotAgentOutputParser: delta = response_content[index : index + steps] yield_delta = False - if delta == "`": + if not in_json and delta == "`": last_character = delta code_block_cache += delta code_block_delimiter_count += 1 @@ -159,8 +165,14 @@ class CotAgentOutputParser: if code_block_delimiter_count == 3: if in_code_block: last_character = delta - yield from extra_json_from_code_block(code_block_cache) - code_block_cache = "" + action_json_list = extra_json_from_code_block(code_block_cache) + if action_json_list: + for action_json in action_json_list: + yield parse_action(action_json) + code_block_cache = "" + else: + index += steps + continue in_code_block = not in_code_block code_block_delimiter_count = 0 diff --git a/api/core/app/app_config/easy_ui_based_app/model_config/converter.py b/api/core/app/app_config/easy_ui_based_app/model_config/converter.py index ecb045a30a..5beb09c2aa 100644 --- a/api/core/app/app_config/easy_ui_based_app/model_config/converter.py +++ b/api/core/app/app_config/easy_ui_based_app/model_config/converter.py @@ -16,7 +16,6 @@ class ModelConfigConverter: """ Convert app model config dict to entity. :param app_config: app config - :param skip_check: skip check :raises ProviderTokenNotInitError: provider token not init error :return: app orchestration config entity """ diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index 4fe22821f3..ef582d28e0 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -88,7 +88,7 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): :param user: account or end user :param args: request args :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is stream """ if not args.get("query"): raise ValueError("query is required") @@ -181,10 +181,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): :param app_model: App :param workflow: Workflow + :param node_id: the node id :param user: account or end user :param args: request args - :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is streamed """ if not node_id: raise ValueError("node_id is required") @@ -238,10 +238,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): :param app_model: App :param workflow: Workflow + :param node_id: the node id :param user: account or end user :param args: request args - :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is stream """ if not node_id: raise ValueError("node_id is required") diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 23abe41080..3ed436c07a 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -80,7 +80,7 @@ class AgentChatAppGenerator(MessageBasedAppGenerator): :param user: account or end user :param args: request args :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is stream """ if not streaming: raise ValueError("Agent Chat App does not support blocking mode") diff --git a/api/core/app/apps/base_app_runner.py b/api/core/app/apps/base_app_runner.py index 8c6b29731e..c813dbb9d1 100644 --- a/api/core/app/apps/base_app_runner.py +++ b/api/core/app/apps/base_app_runner.py @@ -157,6 +157,7 @@ class AppRunner: :param files: files :param query: query :param memory: memory + :param image_detail_config: the image quality config :return: """ # get prompt without memory and context diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index 5fc9ed55af..2d865795d8 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -76,7 +76,7 @@ class ChatAppGenerator(MessageBasedAppGenerator): :param user: account or end user :param args: request args :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is stream """ if not args.get("query"): raise ValueError("query is required") diff --git a/api/core/app/apps/completion/app_generator.py b/api/core/app/apps/completion/app_generator.py index ac9ad346eb..b1bc412616 100644 --- a/api/core/app/apps/completion/app_generator.py +++ b/api/core/app/apps/completion/app_generator.py @@ -74,7 +74,7 @@ class CompletionAppGenerator(MessageBasedAppGenerator): :param user: account or end user :param args: request args :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is stream """ query = args["query"] if not isinstance(query, str): diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index 0ee1ed5590..cc7bcdeee1 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -158,7 +158,7 @@ class WorkflowAppGenerator(BaseAppGenerator): :param user: account or end user :param application_generate_entity: application generate entity :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is stream :param workflow_thread_pool_id: workflow thread pool id """ # init queue manager @@ -208,10 +208,10 @@ class WorkflowAppGenerator(BaseAppGenerator): :param app_model: App :param workflow: Workflow + :param node_id: the node id :param user: account or end user :param args: request args - :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is streamed """ if not node_id: raise ValueError("node_id is required") @@ -264,10 +264,10 @@ class WorkflowAppGenerator(BaseAppGenerator): :param app_model: App :param workflow: Workflow + :param node_id: the node id :param user: account or end user :param args: request args - :param invoke_from: invoke from source - :param stream: is stream + :param streaming: is streamed """ if not node_id: raise ValueError("node_id is required") diff --git a/api/core/app/apps/workflow/app_runner.py b/api/core/app/apps/workflow/app_runner.py index 7bbf3612c9..b38ee18ac4 100644 --- a/api/core/app/apps/workflow/app_runner.py +++ b/api/core/app/apps/workflow/app_runner.py @@ -44,9 +44,6 @@ class WorkflowAppRunner(WorkflowBasedAppRunner): def run(self) -> None: """ Run application - :param application_generate_entity: application generate entity - :param queue_manager: application queue manager - :return: """ app_config = self.application_generate_entity.app_config app_config = cast(WorkflowAppConfig, app_config) diff --git a/api/core/app/task_pipeline/message_cycle_manage.py b/api/core/app/task_pipeline/message_cycle_manage.py index ef3a52442f..6223b33b67 100644 --- a/api/core/app/task_pipeline/message_cycle_manage.py +++ b/api/core/app/task_pipeline/message_cycle_manage.py @@ -48,7 +48,7 @@ class MessageCycleManage: def _generate_conversation_name(self, *, conversation_id: str, query: str) -> Optional[Thread]: """ Generate conversation name. - :param conversation: conversation + :param conversation_id: conversation id :param query: query :return: thread """ diff --git a/api/core/app/task_pipeline/workflow_cycle_manage.py b/api/core/app/task_pipeline/workflow_cycle_manage.py index 8204d0be85..4d629ca186 100644 --- a/api/core/app/task_pipeline/workflow_cycle_manage.py +++ b/api/core/app/task_pipeline/workflow_cycle_manage.py @@ -44,6 +44,7 @@ from core.app.entities.task_entities import ( WorkflowFinishStreamResponse, WorkflowStartStreamResponse, ) +from core.app.task_pipeline.exc import WorkflowRunNotFoundError from core.file import FILE_MODEL_IDENTITY, File from core.model_runtime.utils.encoders import jsonable_encoder from core.ops.entities.trace_entity import TraceTaskName @@ -66,8 +67,6 @@ from models.workflow import ( WorkflowRunStatus, ) -from .exc import WorkflowRunNotFoundError - class WorkflowCycleManage: def __init__( @@ -154,7 +153,7 @@ class WorkflowCycleManage: ) -> WorkflowRun: """ Workflow run success - :param workflow_run: workflow run + :param workflow_run_id: workflow run id :param start_at: start time :param total_tokens: total tokens :param total_steps: total steps @@ -166,7 +165,7 @@ class WorkflowCycleManage: outputs = WorkflowEntry.handle_special_values(outputs) - workflow_run.status = WorkflowRunStatus.SUCCEEDED.value + workflow_run.status = WorkflowRunStatus.SUCCEEDED workflow_run.outputs = json.dumps(outputs or {}) workflow_run.elapsed_time = time.perf_counter() - start_at workflow_run.total_tokens = total_tokens @@ -201,7 +200,7 @@ class WorkflowCycleManage: workflow_run = self._get_workflow_run(session=session, workflow_run_id=workflow_run_id) outputs = WorkflowEntry.handle_special_values(dict(outputs) if outputs else None) - workflow_run.status = WorkflowRunStatus.PARTIAL_SUCCESSED.value + workflow_run.status = WorkflowRunStatus.PARTIAL_SUCCEEDED.value workflow_run.outputs = json.dumps(outputs or {}) workflow_run.elapsed_time = time.perf_counter() - start_at workflow_run.total_tokens = total_tokens @@ -237,7 +236,7 @@ class WorkflowCycleManage: ) -> WorkflowRun: """ Workflow run failed - :param workflow_run: workflow run + :param workflow_run_id: workflow run id :param start_at: start time :param total_tokens: total tokens :param total_steps: total steps diff --git a/api/core/file/upload_file_parser.py b/api/core/file/upload_file_parser.py index 1f81d351b6..96b2884811 100644 --- a/api/core/file/upload_file_parser.py +++ b/api/core/file/upload_file_parser.py @@ -36,7 +36,7 @@ class UploadFileParser: """ get signed url from upload file - :param upload_file: UploadFile object + :param upload_file_id: the id of UploadFile object :return: """ base_url = dify_config.FILES_URL diff --git a/api/core/helper/code_executor/code_executor.py b/api/core/helper/code_executor/code_executor.py index 15b501780e..5bb045cce9 100644 --- a/api/core/helper/code_executor/code_executor.py +++ b/api/core/helper/code_executor/code_executor.py @@ -60,6 +60,7 @@ class CodeExecutor: """ Execute code :param language: code language + :param preload: the preload script :param code: code :return: """ diff --git a/api/core/helper/position_helper.py b/api/core/helper/position_helper.py index 3efdc8aa47..8def6fe4ed 100644 --- a/api/core/helper/position_helper.py +++ b/api/core/helper/position_helper.py @@ -53,7 +53,7 @@ def pin_position_map(original_position_map: dict[str, int], pin_list: list[str]) """ Pin the items in the pin list to the beginning of the position map. Overall logic: exclude > include > pin - :param position_map: the position map to be sorted and filtered + :param original_position_map: the position map to be sorted and filtered :param pin_list: the list of pins to be put at the beginning :return: the sorted position map """ diff --git a/api/core/helper/tool_parameter_cache.py b/api/core/helper/tool_parameter_cache.py index 3b67b3f848..918b3e9eee 100644 --- a/api/core/helper/tool_parameter_cache.py +++ b/api/core/helper/tool_parameter_cache.py @@ -38,12 +38,7 @@ class ToolParameterCache: return None def set(self, parameters: dict) -> None: - """ - Cache model provider credentials. - - :param credentials: provider credentials - :return: - """ + """Cache model provider credentials.""" redis_client.setex(self.cache_key, 86400, json.dumps(parameters)) def delete(self) -> None: diff --git a/api/core/model_runtime/model_providers/__base/tts_model.py b/api/core/model_runtime/model_providers/__base/tts_model.py index 4feaa6f042..1f248d11ac 100644 --- a/api/core/model_runtime/model_providers/__base/tts_model.py +++ b/api/core/model_runtime/model_providers/__base/tts_model.py @@ -38,7 +38,6 @@ class TTSModel(AIModel): :param credentials: model credentials :param voice: model timbre :param content_text: text content to be translated - :param streaming: output is streaming :param user: unique user id :return: translated audio file """ diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index 916509cd99..f388225cc4 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -8,6 +8,7 @@ from datetime import timedelta from typing import Any, Optional, Union from uuid import UUID, uuid4 +from cachetools import LRUCache from flask import current_app from sqlalchemy import select from sqlalchemy.orm import Session @@ -70,6 +71,8 @@ provider_config_map: dict[str, dict[str, Any]] = { class OpsTraceManager: + ops_trace_instances_cache: LRUCache = LRUCache(maxsize=128) + @classmethod def encrypt_tracing_config( cls, tenant_id: str, tracing_provider: str, tracing_config: dict, current_trace_config=None @@ -204,28 +207,32 @@ class OpsTraceManager: return None app_ops_trace_config = json.loads(app.tracing) if app.tracing else None - if app_ops_trace_config is None: return None + if not app_ops_trace_config.get("enabled"): + return None tracing_provider = app_ops_trace_config.get("tracing_provider") - if tracing_provider is None or tracing_provider not in provider_config_map: return None # decrypt_token decrypt_trace_config = cls.get_decrypted_tracing_config(app_id, tracing_provider) - if app_ops_trace_config.get("enabled"): - trace_instance, config_class = ( - provider_config_map[tracing_provider]["trace_instance"], - provider_config_map[tracing_provider]["config_class"], - ) - if not decrypt_trace_config: - return None - tracing_instance = trace_instance(config_class(**decrypt_trace_config)) - return tracing_instance + if not decrypt_trace_config: + return None - return None + trace_instance, config_class = ( + provider_config_map[tracing_provider]["trace_instance"], + provider_config_map[tracing_provider]["config_class"], + ) + decrypt_trace_config_key = str(decrypt_trace_config) + tracing_instance = cls.ops_trace_instances_cache.get(decrypt_trace_config_key) + if tracing_instance is None: + # create new tracing_instance and update the cache if it absent + tracing_instance = trace_instance(config_class(**decrypt_trace_config)) + cls.ops_trace_instances_cache[decrypt_trace_config_key] = tracing_instance + logging.info(f"new tracing_instance for app_id: {app_id}") + return tracing_instance @classmethod def get_app_config_through_message_id(cls, message_id: str): diff --git a/api/core/rag/datasource/vdb/opengauss/opengauss.py b/api/core/rag/datasource/vdb/opengauss/opengauss.py index 4d57a651d5..dae908f67d 100644 --- a/api/core/rag/datasource/vdb/opengauss/opengauss.py +++ b/api/core/rag/datasource/vdb/opengauss/opengauss.py @@ -177,7 +177,6 @@ class OpenGauss(BaseVector): Search the nearest neighbors to a vector. :param query_vector: The input vector to search for similar items. - :param top_k: The number of nearest neighbors to return, default is 5. :return: List of Documents that are nearest to the query vector. """ top_k = kwargs.get("top_k", 4) diff --git a/api/core/rag/datasource/vdb/oracle/oraclevector.py b/api/core/rag/datasource/vdb/oracle/oraclevector.py index 5888e04c71..143dfb3258 100644 --- a/api/core/rag/datasource/vdb/oracle/oraclevector.py +++ b/api/core/rag/datasource/vdb/oracle/oraclevector.py @@ -197,7 +197,6 @@ class OracleVector(BaseVector): Search the nearest neighbors to a vector. :param query_vector: The input vector to search for similar items. - :param top_k: The number of nearest neighbors to return, default is 5. :return: List of Documents that are nearest to the query vector. """ top_k = kwargs.get("top_k", 4) diff --git a/api/core/rag/datasource/vdb/pgvector/pgvector.py b/api/core/rag/datasource/vdb/pgvector/pgvector.py index 7c0d36ebe0..eab51ab01d 100644 --- a/api/core/rag/datasource/vdb/pgvector/pgvector.py +++ b/api/core/rag/datasource/vdb/pgvector/pgvector.py @@ -167,7 +167,6 @@ class PGVector(BaseVector): Search the nearest neighbors to a vector. :param query_vector: The input vector to search for similar items. - :param top_k: The number of nearest neighbors to return, default is 5. :return: List of Documents that are nearest to the query vector. """ top_k = kwargs.get("top_k", 4) diff --git a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py index 0a48c79511..3958280bd5 100644 --- a/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py +++ b/api/core/rag/datasource/vdb/tidb_on_qdrant/tidb_service.py @@ -22,7 +22,6 @@ class TidbService: :param iam_url: The URL of the TiDB Cloud IAM API (required). :param public_key: The public key for the API (required). :param private_key: The private key for the API (required). - :param display_name: The user-friendly display name of the cluster (required). :param region: The region where the cluster will be created (required). :return: The response from the API. @@ -149,13 +148,12 @@ class TidbService: ): """ Update the status of a new TiDB Serverless cluster. + :param tidb_serverless_list: The TiDB serverless list (required). :param project_id: The project ID of the TiDB Cloud project (required). :param api_url: The URL of the TiDB Cloud API (required). :param iam_url: The URL of the TiDB Cloud IAM API (required). :param public_key: The public key for the API (required). :param private_key: The private key for the API (required). - :param display_name: The user-friendly display name of the cluster (required). - :param region: The region where the cluster will be created (required). :return: The response from the API. """ @@ -186,12 +184,12 @@ class TidbService: ) -> list[dict]: """ Creates a new TiDB Serverless cluster. + :param batch_size: The batch size (required). :param project_id: The project ID of the TiDB Cloud project (required). :param api_url: The URL of the TiDB Cloud API (required). :param iam_url: The URL of the TiDB Cloud IAM API (required). :param public_key: The public key for the API (required). :param private_key: The private key for the API (required). - :param display_name: The user-friendly display name of the cluster (required). :param region: The region where the cluster will be created (required). :return: The response from the API. diff --git a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py index 01eaf947f1..8fe6199517 100644 --- a/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py +++ b/api/core/rag/datasource/vdb/weaviate/weaviate_vector.py @@ -226,7 +226,6 @@ class WeaviateVector(BaseVector): Args: query: Text to look up documents similar to. - k: Number of Documents to return. Defaults to 4. Returns: List of Documents most similar to the query. diff --git a/api/core/rag/extractor/firecrawl/firecrawl_web_extractor.py b/api/core/rag/extractor/firecrawl/firecrawl_web_extractor.py index 355a2fb204..4de8318881 100644 --- a/api/core/rag/extractor/firecrawl/firecrawl_web_extractor.py +++ b/api/core/rag/extractor/firecrawl/firecrawl_web_extractor.py @@ -7,11 +7,10 @@ class FirecrawlWebExtractor(BaseExtractor): """ Crawl and scrape websites and return content in clean llm-ready markdown. - Args: url: The URL to scrape. - api_key: The API key for Firecrawl. - base_url: The base URL for the Firecrawl API. Defaults to 'https://api.firecrawl.dev'. + job_id: The crawl job id. + tenant_id: The tenant id. mode: The mode of operation. Defaults to 'scrape'. Options are 'crawl', 'scrape' and 'crawl_return_urls'. only_main_content: Only return the main content of the page excluding headers, navs, footers, etc. """ diff --git a/api/core/rag/extractor/unstructured/unstructured_markdown_extractor.py b/api/core/rag/extractor/unstructured/unstructured_markdown_extractor.py index d5418e612a..0a0c8d3a1c 100644 --- a/api/core/rag/extractor/unstructured/unstructured_markdown_extractor.py +++ b/api/core/rag/extractor/unstructured/unstructured_markdown_extractor.py @@ -14,15 +14,6 @@ class UnstructuredMarkdownExtractor(BaseExtractor): Args: file_path: Path to the file to load. - remove_hyperlinks: Whether to remove hyperlinks from the text. - - remove_images: Whether to remove images from the text. - - encoding: File encoding to use. If `None`, the file will be loaded - with the default system encoding. - - autodetect_encoding: Whether to try to autodetect the file encoding - if the specified encoding fails. """ def __init__(self, file_path: str, api_url: Optional[str] = None, api_key: str = ""): diff --git a/api/core/rag/index_processor/constant/built_in_field.py b/api/core/rag/index_processor/constant/built_in_field.py index 09c5e949eb..c8ad53e3dd 100644 --- a/api/core/rag/index_processor/constant/built_in_field.py +++ b/api/core/rag/index_processor/constant/built_in_field.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import Enum, StrEnum -class BuiltInField(str, Enum): +class BuiltInField(StrEnum): document_name = "document_name" uploader = "uploader" upload_date = "upload_date" diff --git a/api/core/rag/index_processor/constant/index_type.py b/api/core/rag/index_processor/constant/index_type.py index 0845b58e25..659086e808 100644 --- a/api/core/rag/index_processor/constant/index_type.py +++ b/api/core/rag/index_processor/constant/index_type.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import StrEnum -class IndexType(str, Enum): +class IndexType(StrEnum): PARAGRAPH_INDEX = "text_model" QA_INDEX = "qa_model" PARENT_CHILD_INDEX = "hierarchical_model" diff --git a/api/core/rag/index_processor/processor/parent_child_index_processor.py b/api/core/rag/index_processor/processor/parent_child_index_processor.py index 894b85339a..1cde5e1c8f 100644 --- a/api/core/rag/index_processor/processor/parent_child_index_processor.py +++ b/api/core/rag/index_processor/processor/parent_child_index_processor.py @@ -39,6 +39,8 @@ class ParentChildIndexProcessor(BaseIndexProcessor): all_documents = [] # type: ignore if rules.parent_mode == ParentMode.PARAGRAPH: # Split the text documents into nodes. + if not rules.segmentation: + raise ValueError("No segmentation found in rules.") splitter = self._get_splitter( processing_rule_mode=process_rule.get("mode"), max_tokens=rules.segmentation.max_tokens, diff --git a/api/core/rag/retrieval/dataset_retrieval.py b/api/core/rag/retrieval/dataset_retrieval.py index 21c561f698..e00c989c9d 100644 --- a/api/core/rag/retrieval/dataset_retrieval.py +++ b/api/core/rag/retrieval/dataset_retrieval.py @@ -100,6 +100,7 @@ class DatasetRetrieval: :param hit_callback: hit callback :param message_id: message id :param memory: memory + :param inputs: inputs :return: """ dataset_ids = config.dataset_ids @@ -734,6 +735,7 @@ class DatasetRetrieval: Calculate keywords scores :param query: search query :param documents: documents for reranking + :param top_k: top k :return: """ @@ -1031,8 +1033,6 @@ class DatasetRetrieval: ) -> tuple[ModelInstance, ModelConfigWithCredentialsEntity]: """ Fetch model config - :param node_data: node data - :return: """ if model is None: raise ValueError("single_retrieval_config is required") diff --git a/api/core/rag/retrieval/router/multi_dataset_react_route.py b/api/core/rag/retrieval/router/multi_dataset_react_route.py index 05e8d043df..f0426ace1f 100644 --- a/api/core/rag/retrieval/router/multi_dataset_react_route.py +++ b/api/core/rag/retrieval/router/multi_dataset_react_route.py @@ -235,6 +235,7 @@ class ReactMultiDatasetRouter: tools: List of tools the agent will have access to, used to format the prompt. prefix: String to put before the list of tools. + format_instructions: The format instruction prompt. Returns: A PromptTemplate with the template assembled from the pieces here. """ diff --git a/api/core/tools/__base/tool.py b/api/core/tools/__base/tool.py index 63937f5f76..35e16b5c8f 100644 --- a/api/core/tools/__base/tool.py +++ b/api/core/tools/__base/tool.py @@ -29,9 +29,7 @@ class Tool(ABC): def fork_tool_runtime(self, runtime: ToolRuntime) -> "Tool": """ - fork a new tool with meta data - - :param meta: the meta data of a tool call processing, tenant_id is required + fork a new tool with metadata :return: the new tool """ return self.__class__( @@ -206,6 +204,7 @@ class Tool(ABC): create a blob message :param blob: the blob + :param meta: the meta info of blob object :return: the blob message """ return ToolInvokeMessage( diff --git a/api/core/tools/builtin_tool/provider.py b/api/core/tools/builtin_tool/provider.py index 037d86c89b..4f733f0ea1 100644 --- a/api/core/tools/builtin_tool/provider.py +++ b/api/core/tools/builtin_tool/provider.py @@ -153,7 +153,7 @@ class BuiltinToolProviderController(ToolProviderController): """ validate the credentials of the provider - :param tool_name: the name of the tool, defined in `get_tools` + :param user_id: use id :param credentials: the credentials of the tool """ # validate credentials format @@ -167,7 +167,7 @@ class BuiltinToolProviderController(ToolProviderController): """ validate the credentials of the provider - :param tool_name: the name of the tool, defined in `get_tools` + :param user_id: use id :param credentials: the credentials of the tool """ pass diff --git a/api/core/tools/builtin_tool/tool.py b/api/core/tools/builtin_tool/tool.py index e61cda5de5..7f37f98d0c 100644 --- a/api/core/tools/builtin_tool/tool.py +++ b/api/core/tools/builtin_tool/tool.py @@ -28,9 +28,7 @@ class BuiltinTool(Tool): def fork_tool_runtime(self, runtime: ToolRuntime) -> "BuiltinTool": """ - fork a new tool with meta data - - :param meta: the meta data of a tool call processing, tenant_id is required + fork a new tool with metadata :return: the new tool """ return self.__class__( @@ -43,7 +41,7 @@ class BuiltinTool(Tool): """ invoke model - :param model_config: the model config + :param user_id: the user id :param prompt_messages: the prompt messages :param stop: the stop words :return: the model result @@ -64,7 +62,6 @@ class BuiltinTool(Tool): """ get max tokens - :param model_config: the model config :return: the max tokens """ if self.runtime is None: diff --git a/api/core/tools/custom_tool/provider.py b/api/core/tools/custom_tool/provider.py index 7133535313..bfceb6679b 100644 --- a/api/core/tools/custom_tool/provider.py +++ b/api/core/tools/custom_tool/provider.py @@ -145,7 +145,6 @@ class ApiToolProviderController(ToolProviderController): """ fetch tools from database - :param user_id: the user id :param tenant_id: the tenant id :return: the tools """ diff --git a/api/core/tools/custom_tool/tool.py b/api/core/tools/custom_tool/tool.py index b1121fceec..2f2f1ebbdd 100644 --- a/api/core/tools/custom_tool/tool.py +++ b/api/core/tools/custom_tool/tool.py @@ -35,9 +35,7 @@ class ApiTool(Tool): def fork_tool_runtime(self, runtime: ToolRuntime): """ - fork a new tool with meta data - - :param meta: the meta data of a tool call processing, tenant_id is required + fork a new tool with metadata :return: the new tool """ if self.api_bundle is None: diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index 83e69d063d..d756763137 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -264,7 +264,7 @@ class ToolParameter(PluginParameter): :param name: the name of the parameter :param llm_description: the description presented to the LLM - :param type: the type of the parameter + :param typ: the type of the parameter :param required: if the parameter is required :param options: the options of the parameter """ diff --git a/api/core/tools/tool_engine.py b/api/core/tools/tool_engine.py index cf5411112d..997917f31c 100644 --- a/api/core/tools/tool_engine.py +++ b/api/core/tools/tool_engine.py @@ -313,7 +313,6 @@ class ToolEngine: """ Create message file - :param messages: messages :return: message file ids """ result = [] diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index 1caf02192d..f2d0b74f7c 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -161,8 +161,11 @@ class ToolManager: get the tool runtime :param provider_type: the type of the provider - :param provider_name: the name of the provider + :param provider_id: the id of the provider :param tool_name: the name of the tool + :param tenant_id: the tenant id + :param invoke_from: invoke from + :param tool_invoke_from: the tool invoke from :return: the tool """ @@ -427,8 +430,6 @@ class ToolManager: get the absolute path of the icon of the hardcoded provider :param provider: the name of the provider - :param tenant_id: the id of the tenant - :return: the absolute path of the icon, the mime type of the icon """ # get provider @@ -672,7 +673,8 @@ class ToolManager: """ get the api provider - :param provider_name: the name of the provider + :param tenant_id: the id of the tenant + :param provider_id: the id of the provider :return: the provider controller, the credentials """ diff --git a/api/core/tools/utils/model_invocation_utils.py b/api/core/tools/utils/model_invocation_utils.py index 245470ea49..3f59b3f472 100644 --- a/api/core/tools/utils/model_invocation_utils.py +++ b/api/core/tools/utils/model_invocation_utils.py @@ -84,12 +84,8 @@ class ModelInvocationUtils: :param user_id: user id :param tenant_id: tenant id, the tenant id of the creator of the tool - :param tool_provider: tool provider - :param tool_id: tool id + :param tool_type: tool type :param tool_name: tool name - :param provider: model provider - :param model: model name - :param model_parameters: model parameters :param prompt_messages: prompt messages :return: AssistantPromptMessage """ diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index 120c1fcfea..f72291783a 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -200,6 +200,8 @@ class ApiBasedToolSchemaParser: parse openapi yaml to tool bundle :param yaml: the yaml string + :param extra_info: the extra info + :param warning: the warning message :return: the tool bundle """ warning = warning if warning is not None else {} @@ -281,6 +283,8 @@ class ApiBasedToolSchemaParser: parse openapi plugin yaml to tool bundle :param json: the json string + :param extra_info: the extra info + :param warning: the warning message :return: the tool bundle """ warning = warning if warning is not None else {} @@ -315,6 +319,8 @@ class ApiBasedToolSchemaParser: auto parse to tool bundle :param content: the content + :param extra_info: the extra info + :param warning: the warning message :return: tools bundle, schema_type """ warning = warning if warning is not None else {} diff --git a/api/core/tools/workflow_as_tool/provider.py b/api/core/tools/workflow_as_tool/provider.py index 4777a019e4..7661e1e6a5 100644 --- a/api/core/tools/workflow_as_tool/provider.py +++ b/api/core/tools/workflow_as_tool/provider.py @@ -182,7 +182,6 @@ class WorkflowToolProviderController(ToolProviderController): """ fetch tools from database - :param user_id: the user id :param tenant_id: the tenant id :return: the tools """ diff --git a/api/core/tools/workflow_as_tool/tool.py b/api/core/tools/workflow_as_tool/tool.py index cf840880bf..241b4a94de 100644 --- a/api/core/tools/workflow_as_tool/tool.py +++ b/api/core/tools/workflow_as_tool/tool.py @@ -127,9 +127,8 @@ class WorkflowTool(Tool): def fork_tool_runtime(self, runtime: ToolRuntime) -> "WorkflowTool": """ - fork a new tool with meta data + fork a new tool with metadata - :param meta: the meta data of a tool call processing, tenant_id is required :return: the new tool """ return self.__class__( @@ -212,7 +211,6 @@ class WorkflowTool(Tool): """ extract files from the result - :param result: the result :return: the result, files """ files: list[File] = [] diff --git a/api/core/workflow/utils/condition/processor.py b/api/core/workflow/utils/condition/processor.py index c61b3d1861..9795387788 100644 --- a/api/core/workflow/utils/condition/processor.py +++ b/api/core/workflow/utils/condition/processor.py @@ -375,11 +375,25 @@ def _process_sub_conditions( for condition in sub_conditions: key = FileAttribute(condition.key) values = [file_manager.get_attr(file=file, attr=key) for file in files] + expected_value = condition.value + if key == FileAttribute.EXTENSION: + if not isinstance(expected_value, str): + raise TypeError("Expected value must be a string when key is FileAttribute.EXTENSION") + if expected_value and not expected_value.startswith("."): + expected_value = "." + expected_value + + normalized_values = [] + for value in values: + if value and isinstance(value, str): + if not value.startswith("."): + value = "." + value + normalized_values.append(value) + values = normalized_values sub_group_results = [ _evaluate_condition( value=value, operator=condition.comparison_operator, - expected=condition.value, + expected=expected_value, ) for value in values ] diff --git a/api/core/workflow/utils/variable_template_parser.py b/api/core/workflow/utils/variable_template_parser.py index 1d8fb38ebf..f86c54c50a 100644 --- a/api/core/workflow/utils/variable_template_parser.py +++ b/api/core/workflow/utils/variable_template_parser.py @@ -95,7 +95,6 @@ class VariableTemplateParser: Args: inputs: A dictionary containing the values for the template variables. - remove_template_variables: A boolean indicating whether to remove the template variables from the output. Returns: The formatted string with template variables replaced by their values. diff --git a/api/core/workflow/workflow_entry.py b/api/core/workflow/workflow_entry.py index 5a7d5373c1..50118a401c 100644 --- a/api/core/workflow/workflow_entry.py +++ b/api/core/workflow/workflow_entry.py @@ -204,6 +204,8 @@ class WorkflowEntry: NOTE: only parameter_extractor/question_classifier are supported :param node_data: node data + :param node_id: node id + :param tenant_id: tenant id :param user_id: user id :param user_inputs: user inputs :return: diff --git a/api/factories/file_factory.py b/api/factories/file_factory.py index 8c989e6b58..60de7a11b5 100644 --- a/api/factories/file_factory.py +++ b/api/factories/file_factory.py @@ -134,8 +134,9 @@ def _build_from_local_file( if row is None: raise ValueError("Invalid upload file") - file_type = FileType(mapping.get("type", "custom")) - file_type = _standardize_file_type(file_type, extension="." + row.extension, mime_type=row.mime_type) + file_type = _standardize_file_type(extension="." + row.extension, mime_type=row.mime_type) + if file_type.value != mapping.get("type", "custom"): + raise ValueError("Detected file type does not match the specified type. Please verify the file.") return File( id=mapping.get("id"), @@ -173,10 +174,9 @@ def _build_from_remote_url( if upload_file is None: raise ValueError("Invalid upload file") - file_type = FileType(mapping.get("type", "custom")) - file_type = _standardize_file_type( - file_type, extension="." + upload_file.extension, mime_type=upload_file.mime_type - ) + file_type = _standardize_file_type(extension="." + upload_file.extension, mime_type=upload_file.mime_type) + if file_type.value != mapping.get("type", "custom"): + raise ValueError("Detected file type does not match the specified type. Please verify the file.") return File( id=mapping.get("id"), @@ -196,10 +196,11 @@ def _build_from_remote_url( raise ValueError("Invalid file url") mime_type, filename, file_size = _get_remote_file_info(url) - extension = mimetypes.guess_extension(mime_type) or "." + filename.split(".")[-1] if "." in filename else ".bin" + extension = mimetypes.guess_extension(mime_type) or ("." + filename.split(".")[-1] if "." in filename else ".bin") - file_type = FileType(mapping.get("type", "custom")) - file_type = _standardize_file_type(file_type, extension=extension, mime_type=mime_type) + file_type = _standardize_file_type(extension=extension, mime_type=mime_type) + if file_type.value != mapping.get("type", "custom"): + raise ValueError("Detected file type does not match the specified type. Please verify the file.") return File( id=mapping.get("id"), @@ -250,8 +251,10 @@ def _build_from_tool_file( raise ValueError(f"ToolFile {mapping.get('tool_file_id')} not found") extension = "." + tool_file.file_key.split(".")[-1] if "." in tool_file.file_key else ".bin" - file_type = FileType(mapping.get("type", "custom")) - file_type = _standardize_file_type(file_type, extension=extension, mime_type=tool_file.mimetype) + + file_type = _standardize_file_type(extension=extension, mime_type=tool_file.mimetype) + if file_type.value != mapping.get("type", "custom"): + raise ValueError("Detected file type does not match the specified type. Please verify the file.") return File( id=mapping.get("id"), @@ -302,12 +305,10 @@ def _is_file_valid_with_config( return True -def _standardize_file_type(file_type: FileType, /, *, extension: str = "", mime_type: str = "") -> FileType: +def _standardize_file_type(*, extension: str = "", mime_type: str = "") -> FileType: """ - If custom type, try to guess the file type by extension and mime_type. + Infer the possible actual type of the file based on the extension and mime_type """ - if file_type != FileType.CUSTOM: - return FileType(file_type) guessed_type = None if extension: guessed_type = _get_file_type_by_extension(extension) diff --git a/api/models/model.py b/api/models/model.py index 05bc1fd301..8262109ba5 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -791,7 +791,7 @@ class Conversation(db.Model): # type: ignore[name-defined] WorkflowRunStatus.SUCCEEDED: 0, WorkflowRunStatus.FAILED: 0, WorkflowRunStatus.STOPPED: 0, - WorkflowRunStatus.PARTIAL_SUCCESSED: 0, + WorkflowRunStatus.PARTIAL_SUCCEEDED: 0, } for message in messages: @@ -802,7 +802,7 @@ class Conversation(db.Model): # type: ignore[name-defined] { "success": status_counts[WorkflowRunStatus.SUCCEEDED], "failed": status_counts[WorkflowRunStatus.FAILED], - "partial_success": status_counts[WorkflowRunStatus.PARTIAL_SUCCESSED], + "partial_success": status_counts[WorkflowRunStatus.PARTIAL_SUCCEEDED], } if messages else None diff --git a/api/models/workflow.py b/api/models/workflow.py index ed6820702c..dbcb859823 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -109,7 +109,7 @@ class Workflow(Base): tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False) app_id: Mapped[str] = mapped_column(StringUUID, nullable=False) type: Mapped[str] = mapped_column(db.String(255), nullable=False) - version: Mapped[str] + version: Mapped[str] = mapped_column(db.String(255), nullable=False) marked_name: Mapped[str] = mapped_column(default="", server_default="") marked_comment: Mapped[str] = mapped_column(default="", server_default="") graph: Mapped[str] = mapped_column(sa.Text) @@ -352,7 +352,7 @@ class WorkflowRunStatus(StrEnum): SUCCEEDED = "succeeded" FAILED = "failed" STOPPED = "stopped" - PARTIAL_SUCCESSED = "partial-succeeded" + PARTIAL_SUCCEEDED = "partial-succeeded" class WorkflowRun(Base): @@ -755,7 +755,8 @@ class WorkflowAppLog(Base): __tablename__ = "workflow_app_logs" __table_args__ = ( db.PrimaryKeyConstraint("id", name="workflow_app_log_pkey"), - db.Index("workflow_app_log_app_idx", "tenant_id", "app_id"), + db.Index("workflow_app_log_app_idx", "tenant_id", "app_id", "created_at"), + db.Index("workflow_app_log_workflow_run_idx", "workflow_run_id"), ) id: Mapped[str] = mapped_column(StringUUID, server_default=db.text("uuid_generate_v4()")) diff --git a/api/services/account_service.py b/api/services/account_service.py index 47730298b9..8329ef3645 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -913,6 +913,8 @@ class RegisterService: db.session.commit() except WorkSpaceNotAllowedCreateError: db.session.rollback() + logging.exception("Register failed") + raise AccountRegisterError("Workspace is not allowed to create.") except AccountRegisterError as are: db.session.rollback() logging.exception("Register failed") diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py index d33d277d4b..67ef4c319a 100644 --- a/api/services/app_dsl_service.py +++ b/api/services/app_dsl_service.py @@ -513,6 +513,7 @@ class AppDslService: """ Export app :param app_model: App instance + :param include_secret: Whether include secret variable :return: """ app_mode = AppMode.value_of(app_model.mode) diff --git a/api/services/entities/knowledge_entities/knowledge_entities.py b/api/services/entities/knowledge_entities/knowledge_entities.py index 6aa7b51d44..bb3be61f85 100644 --- a/api/services/entities/knowledge_entities/knowledge_entities.py +++ b/api/services/entities/knowledge_entities/knowledge_entities.py @@ -1,4 +1,4 @@ -from enum import Enum +from enum import StrEnum from typing import Literal, Optional from pydantic import BaseModel @@ -11,7 +11,7 @@ class SegmentUpdateEntity(BaseModel): enabled: Optional[bool] = None -class ParentMode(str, Enum): +class ParentMode(StrEnum): FULL_DOC = "full-doc" PARAGRAPH = "paragraph" diff --git a/api/services/tools/builtin_tools_manage_service.py b/api/services/tools/builtin_tools_manage_service.py index 51b56ab586..075c60842b 100644 --- a/api/services/tools/builtin_tools_manage_service.py +++ b/api/services/tools/builtin_tools_manage_service.py @@ -28,7 +28,6 @@ class BuiltinToolManageService: """ list builtin tool provider tools - :param user_id: the id of the user :param tenant_id: the id of the tenant :param provider: the name of the provider diff --git a/api/services/tools/tools_transform_service.py b/api/services/tools/tools_transform_service.py index d44151befa..367121125b 100644 --- a/api/services/tools/tools_transform_service.py +++ b/api/services/tools/tools_transform_service.py @@ -60,6 +60,7 @@ class ToolTransformService: """ repack provider + :param tenant_id: the tenant id :param provider: the provider dict """ if isinstance(provider, dict) and "icon" in provider: diff --git a/api/services/tools/workflow_tools_manage_service.py b/api/services/tools/workflow_tools_manage_service.py index e486ed7b8c..c6b205557a 100644 --- a/api/services/tools/workflow_tools_manage_service.py +++ b/api/services/tools/workflow_tools_manage_service.py @@ -222,7 +222,7 @@ class WorkflowToolManageService: Delete a workflow tool. :param user_id: the user id :param tenant_id: the tenant id - :param workflow_app_id: the workflow app id + :param workflow_tool_id: the workflow tool id """ db.session.query(WorkflowToolProvider).filter( WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id @@ -238,7 +238,7 @@ class WorkflowToolManageService: Get a workflow tool. :param user_id: the user id :param tenant_id: the tenant id - :param workflow_app_id: the workflow app id + :param workflow_tool_id: the workflow tool id :return: the tool """ db_tool: WorkflowToolProvider | None = ( @@ -313,7 +313,7 @@ class WorkflowToolManageService: List workflow tool provider tools. :param user_id: the user id :param tenant_id: the tenant id - :param workflow_app_id: the workflow app id + :param workflow_tool_id: the workflow tool id :return: the list of tools """ db_tool: WorkflowToolProvider | None = ( diff --git a/api/tasks/mail_account_deletion_task.py b/api/tasks/mail_account_deletion_task.py index 49a3a6d280..0c60ae53d5 100644 --- a/api/tasks/mail_account_deletion_task.py +++ b/api/tasks/mail_account_deletion_task.py @@ -10,11 +10,7 @@ from extensions.ext_mail import mail @shared_task(queue="mail") def send_deletion_success_task(to): - """Send email to user regarding account deletion. - - Args: - log (AccountDeletionLog): Account deletion log object - """ + """Send email to user regarding account deletion.""" if not mail.is_inited(): return diff --git a/api/tasks/ops_trace_task.py b/api/tasks/ops_trace_task.py index bb3b9e17ea..2b49e4bb23 100644 --- a/api/tasks/ops_trace_task.py +++ b/api/tasks/ops_trace_task.py @@ -17,8 +17,6 @@ from models.workflow import WorkflowRun def process_trace_tasks(file_info): """ Async process trace tasks - :param tasks_data: List of dictionaries containing task data - Usage: process_trace_tasks.delay(tasks_data) """ from core.ops.ops_trace_manager import OpsTraceManager diff --git a/api/tests/unit_tests/core/agent/output_parser/test_cot_output_parser.py b/api/tests/unit_tests/core/agent/output_parser/test_cot_output_parser.py new file mode 100644 index 0000000000..4a613e35b0 --- /dev/null +++ b/api/tests/unit_tests/core/agent/output_parser/test_cot_output_parser.py @@ -0,0 +1,70 @@ +import json +from collections.abc import Generator + +from core.agent.entities import AgentScratchpadUnit +from core.agent.output_parser.cot_output_parser import CotAgentOutputParser +from core.model_runtime.entities.llm_entities import AssistantPromptMessage, LLMResultChunk, LLMResultChunkDelta + + +def mock_llm_response(text) -> Generator[LLMResultChunk, None, None]: + for i in range(len(text)): + yield LLMResultChunk( + model="model", + prompt_messages=[], + delta=LLMResultChunkDelta(index=0, message=AssistantPromptMessage(content=text[i], tool_calls=[])), + ) + + +def test_cot_output_parser(): + test_cases = [ + { + "input": 'Through: abc\nAction: ```{"action": "Final Answer", "action_input": "```echarts\n {}\n```"}```', + "action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}, + "output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}', + }, + # code block with json + { + "input": 'Through: abc\nAction: ```json\n{"action": "Final Answer", "action_input": "```echarts\n {' + '}\n```"}```', + "action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}, + "output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}', + }, + # code block with JSON + { + "input": 'Through: abc\nAction: ```JSON\n{"action": "Final Answer", "action_input": "```echarts\n {' + '}\n```"}```', + "action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}, + "output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}', + }, + # list + { + "input": 'Through: abc\nAction: ```[{"action": "Final Answer", "action_input": "```echarts\n {}\n```"}]```', + "action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}, + "output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}', + }, + # no code block + { + "input": 'Through: abc\nAction: {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}', + "action": {"action": "Final Answer", "action_input": "```echarts\n {}\n```"}, + "output": 'Through: abc\n {"action": "Final Answer", "action_input": "```echarts\\n {}\\n```"}', + }, + # no code block and json + {"input": "Through: abc\nAction: efg", "action": {}, "output": "Through: abc\n efg"}, + ] + + parser = CotAgentOutputParser() + usage_dict = {} + for test_case in test_cases: + # mock llm_response as a generator by text + llm_response: Generator[LLMResultChunk, None, None] = mock_llm_response(test_case["input"]) + results = parser.handle_react_stream_output(llm_response, usage_dict) + output = "" + for result in results: + if isinstance(result, str): + output += result + elif isinstance(result, AgentScratchpadUnit.Action): + if test_case["action"]: + assert result.to_dict() == test_case["action"] + output += json.dumps(result.to_dict()) + if test_case["output"]: + assert output == test_case["output"] diff --git a/web/app/activate/activateForm.tsx b/web/app/activate/activateForm.tsx index aef5b5a290..782b24be6e 100644 --- a/web/app/activate/activateForm.tsx +++ b/web/app/activate/activateForm.tsx @@ -50,8 +50,8 @@ const ActivateForm = () => { {checkRes && !checkRes.is_valid && (
-
🤷‍♂️
-

{t('login.invalid')}

+
🤷‍♂️
+

{t('login.invalid')}

diff --git a/web/app/components/app/overview/embedded/index.tsx b/web/app/components/app/overview/embedded/index.tsx index 0d545aaf33..cb00c98355 100644 --- a/web/app/components/app/overview/embedded/index.tsx +++ b/web/app/components/app/overview/embedded/index.tsx @@ -44,7 +44,10 @@ const OPTION_MAP = { : ''}${IS_CE_EDITION ? `, baseUrl: '${url}'` - : ''} + : ''}, + systemVariables: { + // user_id: 'YOU CAN DEFINE USER ID HERE', + }, }