From c13dc620656903e0cdbe973fd0164a0771ec4a39 Mon Sep 17 00:00:00 2001 From: Hanqing Zhao Date: Mon, 28 Oct 2024 16:31:58 +0800 Subject: [PATCH 01/29] Modify and add jp translation (#9930) --- web/i18n/ja-JP/app-annotation.ts | 2 ++ web/i18n/ja-JP/app-debug.ts | 57 ++++++++++++++++++++++++++++-- web/i18n/ja-JP/dataset-settings.ts | 2 +- web/i18n/ja-JP/dataset.ts | 2 +- web/i18n/ja-JP/workflow.ts | 38 ++++++++++---------- 5 files changed, 78 insertions(+), 23 deletions(-) diff --git a/web/i18n/ja-JP/app-annotation.ts b/web/i18n/ja-JP/app-annotation.ts index 6c6c98cdd0..f34d8d2acd 100644 --- a/web/i18n/ja-JP/app-annotation.ts +++ b/web/i18n/ja-JP/app-annotation.ts @@ -9,6 +9,8 @@ const translation = { table: { header: { question: '質問', + match: 'マッチ', + response: '応答', answer: '回答', createdAt: '作成日時', hits: 'ヒット数', diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index 0ba4c35b0d..620d9b2f55 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -150,7 +150,7 @@ const translation = { title: '会話履歴', description: '会話の役割に接頭辞名を設定します', tip: '会話履歴は有効になっていません。上記のプロンプトに を追加してください。', - learnMore: '詳細', + learnMore: '詳細を見る', editModal: { title: '会話役割名の編集', userPrefix: 'ユーザー接頭辞', @@ -163,6 +163,7 @@ const translation = { moderation: { title: 'コンテンツのモデレーション', description: 'モデレーションAPIを使用するか、機密語リストを維持することで、モデルの出力を安全にします。', + contentEnableLabel: 'モデレート・コンテンツを有効にする', allEnabled: '入力/出力コンテンツが有効になっています', inputEnabled: '入力コンテンツが有効になっています', outputEnabled: '出力コンテンツが有効になっています', @@ -198,6 +199,25 @@ const translation = { }, }, }, + fileUpload: { + title: 'ファイル アップロード', + description: 'チャットの入力ボックスは画像やドキュメントやその他のファイルのアップロードをサポートします。', + supportedTypes: 'サポートされるファイルのタイプ', + numberLimit: '最大アップロード数', + modalTitle: 'ファイル アップロード設置', + }, + imageUpload: { + title: '画像アップロード', + description: '画像アップロードをサポートする', + supportedTypes: 'サポートされるファイルのタイプ', + numberLimit: '最大アップロード数', + modalTitle: '画像アップロード設置', + }, + bar: { + empty: 'Webアプリのユーザーエクスペリアンスを強化させる機能を有効にする', + enableText: '有効な機能', + manage: '管理', + }, }, codegen: { title: 'コードジェネレーター', @@ -278,6 +298,10 @@ const translation = { waitForBatchResponse: 'バッチタスクへの応答が完了するまでお待ちください。', notSelectModel: 'モデルを選択してください', waitForImgUpload: '画像のアップロードが完了するまでお待ちください', + waitForFileUpload: 'ファイルのアップロードが完了するまでお待ちください', + }, + warningMessage: { + timeoutExceeded: 'タイムアウトのため結果が表示されません。完全な結果を手にいれるためには、ログを参照してください。', }, chatSubTitle: '手順', completionSubTitle: '接頭辞プロンプト', @@ -319,6 +343,8 @@ const translation = { 'paragraph': '段落', 'select': '選択', 'number': '数値', + 'single-file': '単一ファイル', + 'multi-files': 'ファイルリスト', 'notSet': '設定されていません。プレフィックスのプロンプトで {{input}} を入力してみてください。', 'stringTitle': 'フォームテキストボックスオプション', 'maxLength': '最大長', @@ -330,6 +356,31 @@ const translation = { 'inputPlaceholder': '入力してください', 'content': 'コンテンツ', 'required': '必須', + 'file': { + supportFileTypes: 'サッポトされたファイルタイプ', + image: { + name: '画像', + }, + audio: { + name: '音声', + }, + document: { + name: 'ドキュメント', + }, + video: { + name: '映像', + }, + custom: { + name: '他のファイルタイプ', + description: '他のファイルタイプを指定する。', + createPlaceholder: '+ 拡張子, 例:.doc', + }, + }, + 'uploadFileTypes': 'アップロードされたファイルのタイプ', + 'localUpload': 'ローカル アップロード', + 'both': '両方', + 'maxNumberOfUploads': 'アップロードの最大数', + 'maxNumberTip': 'ドキュメント < {{docLimit}}, 画像 < {{imgLimit}}, 音声 < {{audioLimit}}, 映像 < {{videoLimit}}', 'errorMsg': { varNameRequired: '変数名は必須です', labelNameRequired: 'ラベル名は必須です', @@ -341,6 +392,7 @@ const translation = { vision: { name: 'ビジョン', description: 'ビジョンを有効にすると、モデルが画像を受け取り、それに関する質問に答えることができます。', + onlySupportVisionModelTip: 'ビジョンモデルのみをサポート', settings: '設定', visionSettings: { title: 'ビジョン設定', @@ -369,7 +421,7 @@ const translation = { voice: '音声', autoPlay: '自動再生', autoPlayEnabled: '開ける', - autoPlayDisabled: '關閉', + autoPlayDisabled: '閉じる', }, }, openingStatement: { @@ -408,6 +460,7 @@ const translation = { run: '実行', }, result: '出力テキスト', + noResult: '出力はここに表示されます。', datasetConfig: { settingTitle: 'リトリーバル設定', knowledgeTip: 'ナレッジを追加するには「+」ボタンをクリックしてください', diff --git a/web/i18n/ja-JP/dataset-settings.ts b/web/i18n/ja-JP/dataset-settings.ts index 1eb3dabb74..f0b8c76a24 100644 --- a/web/i18n/ja-JP/dataset-settings.ts +++ b/web/i18n/ja-JP/dataset-settings.ts @@ -24,7 +24,7 @@ const translation = { embeddingModelTipLink: '設定', retrievalSetting: { title: '検索設定', - learnMore: '詳細を学ぶ', + learnMore: '詳細を見る', description: ' 検索方法についての詳細', longDescription: ' 検索方法についての詳細については、いつでもナレッジの設定で変更できます。', }, diff --git a/web/i18n/ja-JP/dataset.ts b/web/i18n/ja-JP/dataset.ts index d995509a3f..f15f0dfb1a 100644 --- a/web/i18n/ja-JP/dataset.ts +++ b/web/i18n/ja-JP/dataset.ts @@ -101,7 +101,7 @@ const translation = { end: '.次に、対応するナレッジIDを見つけて、左側のフォームに入力します。すべての情報が正しい場合は、接続ボタンをクリックした後、ナレッジベースの検索テストに自動的にジャンプします。', }, title: '外部ナレッジベースに接続する方法', - learnMore: '詳細情報', + learnMore: '詳細を見る', }, connectHelper: { helper2: '取得機能のみがサポートされています', diff --git a/web/i18n/ja-JP/workflow.ts b/web/i18n/ja-JP/workflow.ts index 632e5712e5..b6c7786081 100644 --- a/web/i18n/ja-JP/workflow.ts +++ b/web/i18n/ja-JP/workflow.ts @@ -19,6 +19,10 @@ const translation = { goBackToEdit: '編集に戻る', conversationLog: '会話ログ', features: '機能', + featuresDescription: 'Webアプリのユーザーエクスペリエンスを強化する', + ImageUploadLegacyTip: '開始フォームでファイルタイプ変数を作成できるようになりました。まもなく、画像アップロード機能のサポートは終了いたします。', + fileUploadTip: '画像アップロード機能がファイルのアップロード機能にアップグレードされました。', + featuresDocLink: '詳細を見る', debugAndPreview: 'プレビュー', restart: '再起動', currentDraft: '現在の下書き', @@ -55,7 +59,7 @@ const translation = { viewOnly: '表示のみ', showRunHistory: '実行履歴を表示', enableJinja: 'Jinjaテンプレートのサポートを有効にする', - learnMore: '詳細を学ぶ', + learnMore: '詳細を見る', copy: 'コピー', duplicate: '複製', addBlock: 'ブロックを追加', @@ -95,10 +99,6 @@ const translation = { addParallelNode: '並列ノードを追加', parallel: '並列', branch: 'ブランチ', - fileUploadTip: '画像のアップロード機能がファイルのアップロードにアップグレードされました。', - featuresDocLink: '詳細情報', - ImageUploadLegacyTip: 'これで、開始フォームでファイルタイプ変数を作成できるようになりました。今後、画像のアップロード機能のサポートは終了いたします。', - featuresDescription: 'Webアプリのユーザーエクスペリエンスを強化', }, env: { envPanelTitle: '環境変数', @@ -229,8 +229,8 @@ const translation = { 'iteration-start': 'イテレーション開始', 'iteration': 'イテレーション', 'parameter-extractor': 'パラメーター抽出', - 'document-extractor': 'ドキュメントエクストラクター', - 'list-operator': 'リスト演算子', + 'document-extractor': 'テキスト抽出ツール', + 'list-operator': 'リスト処理', }, blocksAbout: { 'start': 'ワークフローの開始に必要なパラメータを定義します', @@ -248,7 +248,7 @@ const translation = { 'variable-aggregator': '複数のブランチの変数を1つの変数に集約し、下流のノードに対して統一された設定を行います。', 'iteration': 'リストオブジェクトに対して複数のステップを実行し、すべての結果が出力されるまで繰り返します。', 'parameter-extractor': '自然言語からツールの呼び出しやHTTPリクエストのための構造化されたパラメーターを抽出するためにLLMを使用します。', - 'document-extractor': 'アップロードされたドキュメントを LLM で簡単に理解できるテキスト コンテンツに解析するために使用されます。', + 'document-extractor': 'アップロードされたドキュメントを LLM で簡単に理解できるテキストのコンテンツに解析するために使用されます。', 'list-operator': '配列のコンテンツをフィルタリングまたはソートするために使用されます。', }, operator: { @@ -405,7 +405,7 @@ const translation = { writeLabel: '書き込みタイムアウト', writePlaceholder: '書き込みタイムアウトを秒で入力', }, - type: '種類', + type: 'タイプ', binaryFileVariable: 'バイナリファイル変数', }, code: { @@ -443,21 +443,21 @@ const translation = { 'null': 'null', 'not null': 'nullでない', 'regex match': '正規表現マッチ', - 'in': 'で', - 'not exists': '存在しません', - 'exists': '存在', + 'in': '含まれている', 'not in': '含まれていない', 'all of': 'すべての', + 'exists': '存在します', + 'not exists': '存在しません', }, enterValue: '値を入力', addCondition: '条件を追加', conditionNotSetup: '条件が設定されていません', selectVariable: '変数を選択...', optionName: { - audio: 'オーディオ', + audio: '音声', localUpload: 'ローカルアップロード', image: '画像', - video: 'ビデオ', + video: '映像', doc: 'ドキュメント', url: 'URL', }, @@ -583,7 +583,7 @@ const translation = { text: '抽出されたテキスト', }, inputVar: '入力変数', - learnMore: '詳細情報', + learnMore: '詳細を見る', supportFileTypes: 'サポートするファイルタイプ: {{types}}。', }, listFilter: { @@ -593,13 +593,13 @@ const translation = { result: 'フィルター結果', }, limit: 'トップN', - asc: 'ASCの', + asc: 'ASC', filterCondition: 'フィルター条件', filterConditionKey: 'フィルター条件キー', - orderBy: '注文順', + orderBy: '並べる順番', filterConditionComparisonValue: 'フィルター条件の値', - selectVariableKeyPlaceholder: 'サブ変数キーの選択', - filterConditionComparisonOperator: 'フィルター条件比較演算子', + selectVariableKeyPlaceholder: 'サブ変数キーを選択する', + filterConditionComparisonOperator: 'フィルター条件を比較オペレーター', inputVar: '入力変数', desc: 'DESC', }, From 0ebd9856725e273621db5c7943a9e18e01991aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E7=A8=8B?= Date: Mon, 28 Oct 2024 16:52:12 +0800 Subject: [PATCH 02/29] feat: add models for gitee.ai (#9490) --- .../gitee_ai/_assets/Gitee-AI-Logo-full.svg | 6 + .../gitee_ai/_assets/Gitee-AI-Logo.svg | 3 + .../model_providers/gitee_ai/_common.py | 47 +++++++ .../model_providers/gitee_ai/gitee_ai.py | 25 ++++ .../model_providers/gitee_ai/gitee_ai.yaml | 35 +++++ .../gitee_ai/llm/Qwen2-72B-Instruct.yaml | 105 ++++++++++++++ .../gitee_ai/llm/Qwen2-7B-Instruct.yaml | 105 ++++++++++++++ .../gitee_ai/llm/Yi-1.5-34B-Chat.yaml | 105 ++++++++++++++ .../gitee_ai/llm/_position.yaml | 7 + .../gitee_ai/llm/codegeex4-all-9b.yaml | 105 ++++++++++++++ .../llm/deepseek-coder-33B-instruct-chat.yaml | 105 ++++++++++++++ ...epseek-coder-33B-instruct-completions.yaml | 91 ++++++++++++ .../gitee_ai/llm/glm-4-9b-chat.yaml | 105 ++++++++++++++ .../model_providers/gitee_ai/llm/llm.py | 47 +++++++ .../gitee_ai/rerank/__init__.py | 0 .../gitee_ai/rerank/_position.yaml | 1 + .../gitee_ai/rerank/bge-reranker-v2-m3.yaml | 4 + .../model_providers/gitee_ai/rerank/rerank.py | 128 +++++++++++++++++ .../gitee_ai/speech2text/__init__.py | 0 .../gitee_ai/speech2text/_position.yaml | 2 + .../gitee_ai/speech2text/speech2text.py | 53 +++++++ .../gitee_ai/speech2text/whisper-base.yaml | 5 + .../gitee_ai/speech2text/whisper-large.yaml | 5 + .../gitee_ai/text_embedding/_position.yaml | 3 + .../text_embedding/bge-large-zh-v1.5.yaml | 8 ++ .../gitee_ai/text_embedding/bge-m3.yaml | 8 ++ .../text_embedding/bge-small-zh-v1.5.yaml | 8 ++ .../gitee_ai/text_embedding/text_embedding.py | 31 ++++ .../model_providers/gitee_ai/tts/ChatTTS.yaml | 11 ++ .../tts/FunAudioLLM-CosyVoice-300M.yaml | 11 ++ .../model_providers/gitee_ai/tts/__init__.py | 0 .../gitee_ai/tts/_position.yaml | 4 + .../gitee_ai/tts/fish-speech-1.2-sft.yaml | 11 ++ .../gitee_ai/tts/speecht5_tts.yaml | 11 ++ .../model_providers/gitee_ai/tts/tts.py | 79 +++++++++++ api/pytest.ini | 1 + api/tests/integration_tests/.env.example | 3 + .../model_runtime/gitee_ai/__init__.py | 0 .../model_runtime/gitee_ai/test_llm.py | 132 ++++++++++++++++++ .../model_runtime/gitee_ai/test_provider.py | 15 ++ .../model_runtime/gitee_ai/test_rerank.py | 47 +++++++ .../gitee_ai/test_speech2text.py | 45 ++++++ .../gitee_ai/test_text_embedding.py | 46 ++++++ .../model_runtime/gitee_ai/test_tts.py | 23 +++ 44 files changed, 1586 insertions(+) create mode 100644 api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo-full.svg create mode 100644 api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo.svg create mode 100644 api/core/model_runtime/model_providers/gitee_ai/_common.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/gitee_ai.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/gitee_ai.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-72B-Instruct.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-7B-Instruct.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/Yi-1.5-34B-Chat.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/_position.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/codegeex4-all-9b.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-chat.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-completions.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/glm-4-9b-chat.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/llm/llm.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/rerank/__init__.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/rerank/_position.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/rerank/bge-reranker-v2-m3.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/rerank/rerank.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/speech2text/__init__.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/speech2text/_position.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/speech2text/speech2text.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-base.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-large.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/text_embedding/_position.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-large-zh-v1.5.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-m3.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-small-zh-v1.5.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/text_embedding/text_embedding.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/ChatTTS.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/FunAudioLLM-CosyVoice-300M.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/__init__.py create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/_position.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/fish-speech-1.2-sft.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/speecht5_tts.yaml create mode 100644 api/core/model_runtime/model_providers/gitee_ai/tts/tts.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/__init__.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/test_llm.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/test_provider.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/test_rerank.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/test_speech2text.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/test_text_embedding.py create mode 100644 api/tests/integration_tests/model_runtime/gitee_ai/test_tts.py diff --git a/api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo-full.svg b/api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo-full.svg new file mode 100644 index 0000000000..f9738b585b --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo-full.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo.svg b/api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo.svg new file mode 100644 index 0000000000..1f51187f19 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/_assets/Gitee-AI-Logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/api/core/model_runtime/model_providers/gitee_ai/_common.py b/api/core/model_runtime/model_providers/gitee_ai/_common.py new file mode 100644 index 0000000000..0750f3b75d --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/_common.py @@ -0,0 +1,47 @@ +from dashscope.common.error import ( + AuthenticationError, + InvalidParameter, + RequestFailure, + ServiceUnavailableError, + UnsupportedHTTPMethod, + UnsupportedModel, +) + +from core.model_runtime.errors.invoke import ( + InvokeAuthorizationError, + InvokeBadRequestError, + InvokeConnectionError, + InvokeError, + InvokeRateLimitError, + InvokeServerUnavailableError, +) + + +class _CommonGiteeAI: + @property + def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + Map model invoke error to unified error + The key is the error type thrown to the caller + The value is the error type thrown by the model, + which needs to be converted into a unified error type for the caller. + + :return: Invoke error mapping + """ + return { + InvokeConnectionError: [ + RequestFailure, + ], + InvokeServerUnavailableError: [ + ServiceUnavailableError, + ], + InvokeRateLimitError: [], + InvokeAuthorizationError: [ + AuthenticationError, + ], + InvokeBadRequestError: [ + InvalidParameter, + UnsupportedModel, + UnsupportedHTTPMethod, + ], + } diff --git a/api/core/model_runtime/model_providers/gitee_ai/gitee_ai.py b/api/core/model_runtime/model_providers/gitee_ai/gitee_ai.py new file mode 100644 index 0000000000..ca67594ce4 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/gitee_ai.py @@ -0,0 +1,25 @@ +import logging + +from core.model_runtime.entities.model_entities import ModelType +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.model_provider import ModelProvider + +logger = logging.getLogger(__name__) + + +class GiteeAIProvider(ModelProvider): + def validate_provider_credentials(self, credentials: dict) -> None: + """ + Validate provider credentials + if validate failed, raise exception + + :param credentials: provider credentials, credentials form defined in `provider_credential_schema`. + """ + try: + model_instance = self.get_model_instance(ModelType.LLM) + model_instance.validate_credentials(model="Qwen2-7B-Instruct", credentials=credentials) + except CredentialsValidateFailedError as ex: + raise ex + except Exception as ex: + logger.exception(f"{self.get_provider_schema().provider} credentials validate failed") + raise ex diff --git a/api/core/model_runtime/model_providers/gitee_ai/gitee_ai.yaml b/api/core/model_runtime/model_providers/gitee_ai/gitee_ai.yaml new file mode 100644 index 0000000000..7f7d0f2e53 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/gitee_ai.yaml @@ -0,0 +1,35 @@ +provider: gitee_ai +label: + en_US: Gitee AI + zh_Hans: Gitee AI +description: + en_US: 快速体验大模型,领先探索 AI 开源世界 + zh_Hans: 快速体验大模型,领先探索 AI 开源世界 +icon_small: + en_US: Gitee-AI-Logo.svg +icon_large: + en_US: Gitee-AI-Logo-full.svg +help: + title: + en_US: Get your token from Gitee AI + zh_Hans: 从 Gitee AI 获取 token + url: + en_US: https://ai.gitee.com/dashboard/settings/tokens +supported_model_types: + - llm + - text-embedding + - rerank + - speech2text + - tts +configurate_methods: + - predefined-model +provider_credential_schema: + credential_form_schemas: + - variable: api_key + label: + en_US: API Key + type: secret-input + required: true + placeholder: + zh_Hans: 在此输入您的 API Key + en_US: Enter your API Key diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-72B-Instruct.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-72B-Instruct.yaml new file mode 100644 index 0000000000..0348438a75 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-72B-Instruct.yaml @@ -0,0 +1,105 @@ +model: Qwen2-72B-Instruct +label: + zh_Hans: Qwen2-72B-Instruct + en_US: Qwen2-72B-Instruct +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 6400 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_k + use_template: top_k + label: + en_US: "Top K" + zh_Hans: "Top K" + type: int + default: 50 + min: 0 + max: 100 + required: true + help: + en_US: "The value range is [0,100], which limits the model to only select from the top k words with the highest probability when choosing the next word at each step. The larger the value, the more diverse text generation will be." + zh_Hans: "取值范围为 [0,100],限制模型在每一步选择下一个词时,只从概率最高的前 k 个词中选取。数值越大,文本生成越多样。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-7B-Instruct.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-7B-Instruct.yaml new file mode 100644 index 0000000000..ba1ad788f5 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/Qwen2-7B-Instruct.yaml @@ -0,0 +1,105 @@ +model: Qwen2-7B-Instruct +label: + zh_Hans: Qwen2-7B-Instruct + en_US: Qwen2-7B-Instruct +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 32768 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_k + use_template: top_k + label: + en_US: "Top K" + zh_Hans: "Top K" + type: int + default: 50 + min: 0 + max: 100 + required: true + help: + en_US: "The value range is [0,100], which limits the model to only select from the top k words with the highest probability when choosing the next word at each step. The larger the value, the more diverse text generation will be." + zh_Hans: "取值范围为 [0,100],限制模型在每一步选择下一个词时,只从概率最高的前 k 个词中选取。数值越大,文本生成越多样。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/Yi-1.5-34B-Chat.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/Yi-1.5-34B-Chat.yaml new file mode 100644 index 0000000000..f7260c987b --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/Yi-1.5-34B-Chat.yaml @@ -0,0 +1,105 @@ +model: Yi-1.5-34B-Chat +label: + zh_Hans: Yi-1.5-34B-Chat + en_US: Yi-1.5-34B-Chat +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 4096 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_k + use_template: top_k + label: + en_US: "Top K" + zh_Hans: "Top K" + type: int + default: 50 + min: 0 + max: 100 + required: true + help: + en_US: "The value range is [0,100], which limits the model to only select from the top k words with the highest probability when choosing the next word at each step. The larger the value, the more diverse text generation will be." + zh_Hans: "取值范围为 [0,100],限制模型在每一步选择下一个词时,只从概率最高的前 k 个词中选取。数值越大,文本生成越多样。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/_position.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/_position.yaml new file mode 100644 index 0000000000..21f6120742 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/_position.yaml @@ -0,0 +1,7 @@ +- Qwen2-7B-Instruct +- Qwen2-72B-Instruct +- Yi-1.5-34B-Chat +- glm-4-9b-chat +- deepseek-coder-33B-instruct-chat +- deepseek-coder-33B-instruct-completions +- codegeex4-all-9b diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/codegeex4-all-9b.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/codegeex4-all-9b.yaml new file mode 100644 index 0000000000..8632cd92ab --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/codegeex4-all-9b.yaml @@ -0,0 +1,105 @@ +model: codegeex4-all-9b +label: + zh_Hans: codegeex4-all-9b + en_US: codegeex4-all-9b +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 40960 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_k + use_template: top_k + label: + en_US: "Top K" + zh_Hans: "Top K" + type: int + default: 50 + min: 0 + max: 100 + required: true + help: + en_US: "The value range is [0,100], which limits the model to only select from the top k words with the highest probability when choosing the next word at each step. The larger the value, the more diverse text generation will be." + zh_Hans: "取值范围为 [0,100],限制模型在每一步选择下一个词时,只从概率最高的前 k 个词中选取。数值越大,文本生成越多样。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-chat.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-chat.yaml new file mode 100644 index 0000000000..2ac00761d5 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-chat.yaml @@ -0,0 +1,105 @@ +model: deepseek-coder-33B-instruct-chat +label: + zh_Hans: deepseek-coder-33B-instruct-chat + en_US: deepseek-coder-33B-instruct-chat +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 9000 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_k + use_template: top_k + label: + en_US: "Top K" + zh_Hans: "Top K" + type: int + default: 50 + min: 0 + max: 100 + required: true + help: + en_US: "The value range is [0,100], which limits the model to only select from the top k words with the highest probability when choosing the next word at each step. The larger the value, the more diverse text generation will be." + zh_Hans: "取值范围为 [0,100],限制模型在每一步选择下一个词时,只从概率最高的前 k 个词中选取。数值越大,文本生成越多样。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-completions.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-completions.yaml new file mode 100644 index 0000000000..7c364d89f7 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/deepseek-coder-33B-instruct-completions.yaml @@ -0,0 +1,91 @@ +model: deepseek-coder-33B-instruct-completions +label: + zh_Hans: deepseek-coder-33B-instruct-completions + en_US: deepseek-coder-33B-instruct-completions +model_type: llm +features: + - agent-thought +model_properties: + mode: completion + context_size: 9000 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/glm-4-9b-chat.yaml b/api/core/model_runtime/model_providers/gitee_ai/llm/glm-4-9b-chat.yaml new file mode 100644 index 0000000000..2afe1cf959 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/glm-4-9b-chat.yaml @@ -0,0 +1,105 @@ +model: glm-4-9b-chat +label: + zh_Hans: glm-4-9b-chat + en_US: glm-4-9b-chat +model_type: llm +features: + - agent-thought +model_properties: + mode: chat + context_size: 32768 +parameter_rules: + - name: stream + use_template: boolean + label: + en_US: "Stream" + zh_Hans: "流式" + type: boolean + default: true + required: true + help: + en_US: "Whether to return the results in batches through streaming. If set to true, the generated text will be pushed to the user in real time during the generation process." + zh_Hans: "是否通过流式分批返回结果。如果设置为 true,生成过程中实时地向用户推送每一部分生成的文本。" + + - name: max_tokens + use_template: max_tokens + label: + en_US: "Max Tokens" + zh_Hans: "最大Token数" + type: int + default: 512 + min: 1 + required: true + help: + en_US: "The maximum number of tokens that can be generated by the model varies depending on the model." + zh_Hans: "模型可生成的最大 token 个数,不同模型上限不同。" + + - name: temperature + use_template: temperature + label: + en_US: "Temperature" + zh_Hans: "采样温度" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The randomness of the sampling temperature control output. The temperature value is within the range of [0.0, 1.0]. The higher the value, the more random and creative the output; the lower the value, the more stable it is. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样温度控制输出的随机性。温度值在 [0.0, 1.0] 范围内,值越高,输出越随机和创造性;值越低,输出越稳定。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_p + use_template: top_p + label: + en_US: "Top P" + zh_Hans: "Top P" + type: float + default: 0.7 + min: 0.0 + max: 1.0 + precision: 1 + required: true + help: + en_US: "The value range of the sampling method is [0.0, 1.0]. The top_p value determines that the model selects tokens from the top p% of candidate words with the highest probability; when top_p is 0, this parameter is invalid. It is recommended to adjust either top_p or temperature parameters according to your needs to avoid adjusting both at the same time." + zh_Hans: "采样方法的取值范围为 [0.0,1.0]。top_p 值确定模型从概率最高的前p%的候选词中选取 tokens;当 top_p 为 0 时,此参数无效。建议根据需求调整 top_p 或 temperature 参数,避免同时调整两者。" + + - name: top_k + use_template: top_k + label: + en_US: "Top K" + zh_Hans: "Top K" + type: int + default: 50 + min: 0 + max: 100 + required: true + help: + en_US: "The value range is [0,100], which limits the model to only select from the top k words with the highest probability when choosing the next word at each step. The larger the value, the more diverse text generation will be." + zh_Hans: "取值范围为 [0,100],限制模型在每一步选择下一个词时,只从概率最高的前 k 个词中选取。数值越大,文本生成越多样。" + + - name: frequency_penalty + use_template: frequency_penalty + label: + en_US: "Frequency Penalty" + zh_Hans: "频率惩罚" + type: float + default: 0 + min: -1.0 + max: 1.0 + precision: 1 + required: false + help: + en_US: "Used to adjust the frequency of repeated content in automatically generated text. Positive numbers reduce repetition, while negative numbers increase repetition. After setting this parameter, if a word has already appeared in the text, the model will decrease the probability of choosing that word for subsequent generation." + zh_Hans: "用于调整自动生成文本中重复内容的频率。正数减少重复,负数增加重复。设置此参数后,如果一个词在文本中已经出现过,模型在后续生成中选择该词的概率会降低。" + + - name: user + use_template: text + label: + en_US: "User" + zh_Hans: "用户" + type: string + required: false + help: + en_US: "Used to track and differentiate conversation requests from different users." + zh_Hans: "用于追踪和区分不同用户的对话请求。" diff --git a/api/core/model_runtime/model_providers/gitee_ai/llm/llm.py b/api/core/model_runtime/model_providers/gitee_ai/llm/llm.py new file mode 100644 index 0000000000..b65db6f665 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/llm/llm.py @@ -0,0 +1,47 @@ +from collections.abc import Generator +from typing import Optional, Union + +from core.model_runtime.entities.llm_entities import LLMMode, LLMResult +from core.model_runtime.entities.message_entities import ( + PromptMessage, + PromptMessageTool, +) +from core.model_runtime.model_providers.openai_api_compatible.llm.llm import OAIAPICompatLargeLanguageModel + + +class GiteeAILargeLanguageModel(OAIAPICompatLargeLanguageModel): + MODEL_TO_IDENTITY: dict[str, str] = { + "Yi-1.5-34B-Chat": "Yi-34B-Chat", + "deepseek-coder-33B-instruct-completions": "deepseek-coder-33B-instruct", + "deepseek-coder-33B-instruct-chat": "deepseek-coder-33B-instruct", + } + + def _invoke( + self, + model: str, + credentials: dict, + prompt_messages: list[PromptMessage], + model_parameters: dict, + tools: Optional[list[PromptMessageTool]] = None, + stop: Optional[list[str]] = None, + stream: bool = True, + user: Optional[str] = None, + ) -> Union[LLMResult, Generator]: + self._add_custom_parameters(credentials, model, model_parameters) + return super()._invoke(model, credentials, prompt_messages, model_parameters, tools, stop, stream) + + def validate_credentials(self, model: str, credentials: dict) -> None: + self._add_custom_parameters(credentials, model, None) + super().validate_credentials(model, credentials) + + @staticmethod + def _add_custom_parameters(credentials: dict, model: str, model_parameters: dict) -> None: + if model is None: + model = "bge-large-zh-v1.5" + + model_identity = GiteeAILargeLanguageModel.MODEL_TO_IDENTITY.get(model, model) + credentials["endpoint_url"] = f"https://ai.gitee.com/api/serverless/{model_identity}/" + if model.endswith("completions"): + credentials["mode"] = LLMMode.COMPLETION.value + else: + credentials["mode"] = LLMMode.CHAT.value diff --git a/api/core/model_runtime/model_providers/gitee_ai/rerank/__init__.py b/api/core/model_runtime/model_providers/gitee_ai/rerank/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/gitee_ai/rerank/_position.yaml b/api/core/model_runtime/model_providers/gitee_ai/rerank/_position.yaml new file mode 100644 index 0000000000..83162fd338 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/rerank/_position.yaml @@ -0,0 +1 @@ +- bge-reranker-v2-m3 diff --git a/api/core/model_runtime/model_providers/gitee_ai/rerank/bge-reranker-v2-m3.yaml b/api/core/model_runtime/model_providers/gitee_ai/rerank/bge-reranker-v2-m3.yaml new file mode 100644 index 0000000000..f0681641e1 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/rerank/bge-reranker-v2-m3.yaml @@ -0,0 +1,4 @@ +model: bge-reranker-v2-m3 +model_type: rerank +model_properties: + context_size: 1024 diff --git a/api/core/model_runtime/model_providers/gitee_ai/rerank/rerank.py b/api/core/model_runtime/model_providers/gitee_ai/rerank/rerank.py new file mode 100644 index 0000000000..231345c2f4 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/rerank/rerank.py @@ -0,0 +1,128 @@ +from typing import Optional + +import httpx + +from core.model_runtime.entities.common_entities import I18nObject +from core.model_runtime.entities.model_entities import AIModelEntity, FetchFrom, ModelPropertyKey, ModelType +from core.model_runtime.entities.rerank_entities import RerankDocument, RerankResult +from core.model_runtime.errors.invoke import ( + InvokeAuthorizationError, + InvokeBadRequestError, + InvokeConnectionError, + InvokeError, + InvokeRateLimitError, + InvokeServerUnavailableError, +) +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.rerank_model import RerankModel + + +class GiteeAIRerankModel(RerankModel): + """ + Model class for rerank model. + """ + + def _invoke( + self, + model: str, + credentials: dict, + query: str, + docs: list[str], + score_threshold: Optional[float] = None, + top_n: Optional[int] = None, + user: Optional[str] = None, + ) -> RerankResult: + """ + Invoke rerank model + + :param model: model name + :param credentials: model credentials + :param query: search query + :param docs: docs for reranking + :param score_threshold: score threshold + :param top_n: top n documents to return + :param user: unique user id + :return: rerank result + """ + if len(docs) == 0: + return RerankResult(model=model, docs=[]) + + base_url = credentials.get("base_url", "https://ai.gitee.com/api/serverless") + base_url = base_url.removesuffix("/") + + try: + body = {"model": model, "query": query, "documents": docs} + if top_n is not None: + body["top_n"] = top_n + response = httpx.post( + f"{base_url}/{model}/rerank", + json=body, + headers={"Authorization": f"Bearer {credentials.get('api_key')}"}, + ) + + response.raise_for_status() + results = response.json() + + rerank_documents = [] + for result in results["results"]: + rerank_document = RerankDocument( + index=result["index"], + text=result["document"]["text"], + score=result["relevance_score"], + ) + if score_threshold is None or result["relevance_score"] >= score_threshold: + rerank_documents.append(rerank_document) + return RerankResult(model=model, docs=rerank_documents) + except httpx.HTTPStatusError as e: + raise InvokeServerUnavailableError(str(e)) + + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + Validate model credentials + + :param model: model name + :param credentials: model credentials + :return: + """ + try: + self._invoke( + model=model, + credentials=credentials, + query="What is the capital of the United States?", + docs=[ + "Carson City is the capital city of the American state of Nevada. At the 2010 United States " + "Census, Carson City had a population of 55,274.", + "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that " + "are a political division controlled by the United States. Its capital is Saipan.", + ], + score_threshold=0.01, + ) + except Exception as ex: + raise CredentialsValidateFailedError(str(ex)) + + @property + def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + Map model invoke error to unified error + """ + return { + InvokeConnectionError: [httpx.ConnectError], + InvokeServerUnavailableError: [httpx.RemoteProtocolError], + InvokeRateLimitError: [], + InvokeAuthorizationError: [httpx.HTTPStatusError], + InvokeBadRequestError: [httpx.RequestError], + } + + def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity: + """ + generate custom model entities from credentials + """ + entity = AIModelEntity( + model=model, + label=I18nObject(en_US=model), + model_type=ModelType.RERANK, + fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, + model_properties={ModelPropertyKey.CONTEXT_SIZE: int(credentials.get("context_size"))}, + ) + + return entity diff --git a/api/core/model_runtime/model_providers/gitee_ai/speech2text/__init__.py b/api/core/model_runtime/model_providers/gitee_ai/speech2text/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/gitee_ai/speech2text/_position.yaml b/api/core/model_runtime/model_providers/gitee_ai/speech2text/_position.yaml new file mode 100644 index 0000000000..8e9b47598b --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/speech2text/_position.yaml @@ -0,0 +1,2 @@ +- whisper-base +- whisper-large diff --git a/api/core/model_runtime/model_providers/gitee_ai/speech2text/speech2text.py b/api/core/model_runtime/model_providers/gitee_ai/speech2text/speech2text.py new file mode 100644 index 0000000000..5597f5b43e --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/speech2text/speech2text.py @@ -0,0 +1,53 @@ +import os +from typing import IO, Optional + +import requests + +from core.model_runtime.errors.invoke import InvokeBadRequestError +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.speech2text_model import Speech2TextModel +from core.model_runtime.model_providers.gitee_ai._common import _CommonGiteeAI + + +class GiteeAISpeech2TextModel(_CommonGiteeAI, Speech2TextModel): + """ + Model class for OpenAI Compatible Speech to text model. + """ + + def _invoke(self, model: str, credentials: dict, file: IO[bytes], user: Optional[str] = None) -> str: + """ + Invoke speech2text model + + :param model: model name + :param credentials: model credentials + :param file: audio file + :param user: unique user id + :return: text for given audio file + """ + # doc: https://ai.gitee.com/docs/openapi/serverless#tag/serverless/POST/{service}/speech-to-text + + endpoint_url = f"https://ai.gitee.com/api/serverless/{model}/speech-to-text" + files = [("file", file)] + _, file_ext = os.path.splitext(file.name) + headers = {"Content-Type": f"audio/{file_ext}", "Authorization": f"Bearer {credentials.get('api_key')}"} + response = requests.post(endpoint_url, headers=headers, files=files) + if response.status_code != 200: + raise InvokeBadRequestError(response.text) + response_data = response.json() + return response_data["text"] + + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + Validate model credentials + + :param model: model name + :param credentials: model credentials + :return: + """ + try: + audio_file_path = self._get_demo_file_path() + + with open(audio_file_path, "rb") as audio_file: + self._invoke(model, credentials, audio_file) + except Exception as ex: + raise CredentialsValidateFailedError(str(ex)) diff --git a/api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-base.yaml b/api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-base.yaml new file mode 100644 index 0000000000..a50bf5fc2d --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-base.yaml @@ -0,0 +1,5 @@ +model: whisper-base +model_type: speech2text +model_properties: + file_upload_limit: 1 + supported_file_extensions: flac,mp3,mp4,mpeg,mpga,m4a,ogg,wav,webm diff --git a/api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-large.yaml b/api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-large.yaml new file mode 100644 index 0000000000..1be7b1a391 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/speech2text/whisper-large.yaml @@ -0,0 +1,5 @@ +model: whisper-large +model_type: speech2text +model_properties: + file_upload_limit: 1 + supported_file_extensions: flac,mp3,mp4,mpeg,mpga,m4a,ogg,wav,webm diff --git a/api/core/model_runtime/model_providers/gitee_ai/text_embedding/_position.yaml b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/_position.yaml new file mode 100644 index 0000000000..e8abe6440d --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/_position.yaml @@ -0,0 +1,3 @@ +- bge-large-zh-v1.5 +- bge-small-zh-v1.5 +- bge-m3 diff --git a/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-large-zh-v1.5.yaml b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-large-zh-v1.5.yaml new file mode 100644 index 0000000000..9e3ca76e88 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-large-zh-v1.5.yaml @@ -0,0 +1,8 @@ +model: bge-large-zh-v1.5 +label: + zh_Hans: bge-large-zh-v1.5 + en_US: bge-large-zh-v1.5 +model_type: text-embedding +model_properties: + context_size: 200000 + max_chunks: 20 diff --git a/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-m3.yaml b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-m3.yaml new file mode 100644 index 0000000000..a7a99a98a3 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-m3.yaml @@ -0,0 +1,8 @@ +model: bge-m3 +label: + zh_Hans: bge-m3 + en_US: bge-m3 +model_type: text-embedding +model_properties: + context_size: 200000 + max_chunks: 20 diff --git a/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-small-zh-v1.5.yaml b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-small-zh-v1.5.yaml new file mode 100644 index 0000000000..bd760408fa --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/bge-small-zh-v1.5.yaml @@ -0,0 +1,8 @@ +model: bge-small-zh-v1.5 +label: + zh_Hans: bge-small-zh-v1.5 + en_US: bge-small-zh-v1.5 +model_type: text-embedding +model_properties: + context_size: 200000 + max_chunks: 20 diff --git a/api/core/model_runtime/model_providers/gitee_ai/text_embedding/text_embedding.py b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/text_embedding.py new file mode 100644 index 0000000000..b833c5652c --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/text_embedding/text_embedding.py @@ -0,0 +1,31 @@ +from typing import Optional + +from core.entities.embedding_type import EmbeddingInputType +from core.model_runtime.entities.text_embedding_entities import TextEmbeddingResult +from core.model_runtime.model_providers.openai_api_compatible.text_embedding.text_embedding import ( + OAICompatEmbeddingModel, +) + + +class GiteeAIEmbeddingModel(OAICompatEmbeddingModel): + def _invoke( + self, + model: str, + credentials: dict, + texts: list[str], + user: Optional[str] = None, + input_type: EmbeddingInputType = EmbeddingInputType.DOCUMENT, + ) -> TextEmbeddingResult: + self._add_custom_parameters(credentials, model) + return super()._invoke(model, credentials, texts, user, input_type) + + def validate_credentials(self, model: str, credentials: dict) -> None: + self._add_custom_parameters(credentials, None) + super().validate_credentials(model, credentials) + + @staticmethod + def _add_custom_parameters(credentials: dict, model: str) -> None: + if model is None: + model = "bge-m3" + + credentials["endpoint_url"] = f"https://ai.gitee.com/api/serverless/{model}/v1/" diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/ChatTTS.yaml b/api/core/model_runtime/model_providers/gitee_ai/tts/ChatTTS.yaml new file mode 100644 index 0000000000..940391dfab --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/tts/ChatTTS.yaml @@ -0,0 +1,11 @@ +model: ChatTTS +model_type: tts +model_properties: + default_voice: 'default' + voices: + - mode: 'default' + name: 'Default' + language: [ 'zh-Hans', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'th-TH', 'id-ID' ] + word_limit: 3500 + audio_type: 'mp3' + max_workers: 5 diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/FunAudioLLM-CosyVoice-300M.yaml b/api/core/model_runtime/model_providers/gitee_ai/tts/FunAudioLLM-CosyVoice-300M.yaml new file mode 100644 index 0000000000..8fc5734801 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/tts/FunAudioLLM-CosyVoice-300M.yaml @@ -0,0 +1,11 @@ +model: FunAudioLLM-CosyVoice-300M +model_type: tts +model_properties: + default_voice: 'default' + voices: + - mode: 'default' + name: 'Default' + language: [ 'zh-Hans', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'th-TH', 'id-ID' ] + word_limit: 3500 + audio_type: 'mp3' + max_workers: 5 diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/__init__.py b/api/core/model_runtime/model_providers/gitee_ai/tts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/_position.yaml b/api/core/model_runtime/model_providers/gitee_ai/tts/_position.yaml new file mode 100644 index 0000000000..13c6ec8454 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/tts/_position.yaml @@ -0,0 +1,4 @@ +- speecht5_tts +- ChatTTS +- fish-speech-1.2-sft +- FunAudioLLM-CosyVoice-300M diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/fish-speech-1.2-sft.yaml b/api/core/model_runtime/model_providers/gitee_ai/tts/fish-speech-1.2-sft.yaml new file mode 100644 index 0000000000..93cc28bc9d --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/tts/fish-speech-1.2-sft.yaml @@ -0,0 +1,11 @@ +model: fish-speech-1.2-sft +model_type: tts +model_properties: + default_voice: 'default' + voices: + - mode: 'default' + name: 'Default' + language: [ 'zh-Hans', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'th-TH', 'id-ID' ] + word_limit: 3500 + audio_type: 'mp3' + max_workers: 5 diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/speecht5_tts.yaml b/api/core/model_runtime/model_providers/gitee_ai/tts/speecht5_tts.yaml new file mode 100644 index 0000000000..f9c843bd41 --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/tts/speecht5_tts.yaml @@ -0,0 +1,11 @@ +model: speecht5_tts +model_type: tts +model_properties: + default_voice: 'default' + voices: + - mode: 'default' + name: 'Default' + language: [ 'zh-Hans', 'en-US', 'de-DE', 'fr-FR', 'es-ES', 'it-IT', 'th-TH', 'id-ID' ] + word_limit: 3500 + audio_type: 'mp3' + max_workers: 5 diff --git a/api/core/model_runtime/model_providers/gitee_ai/tts/tts.py b/api/core/model_runtime/model_providers/gitee_ai/tts/tts.py new file mode 100644 index 0000000000..ed2bd5b13d --- /dev/null +++ b/api/core/model_runtime/model_providers/gitee_ai/tts/tts.py @@ -0,0 +1,79 @@ +from typing import Optional + +import requests + +from core.model_runtime.errors.invoke import InvokeBadRequestError +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.__base.tts_model import TTSModel +from core.model_runtime.model_providers.gitee_ai._common import _CommonGiteeAI + + +class GiteeAIText2SpeechModel(_CommonGiteeAI, TTSModel): + """ + Model class for OpenAI Speech to text model. + """ + + def _invoke( + self, model: str, tenant_id: str, credentials: dict, content_text: str, voice: str, user: Optional[str] = None + ) -> any: + """ + _invoke text2speech model + + :param model: model name + :param tenant_id: user tenant id + :param credentials: model credentials + :param content_text: text content to be translated + :param voice: model timbre + :param user: unique user id + :return: text translated to audio file + """ + return self._tts_invoke_streaming(model=model, credentials=credentials, content_text=content_text, voice=voice) + + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + validate credentials text2speech model + + :param model: model name + :param credentials: model credentials + :return: text translated to audio file + """ + try: + self._tts_invoke_streaming( + model=model, + credentials=credentials, + content_text="Hello Dify!", + voice=self._get_model_default_voice(model, credentials), + ) + except Exception as ex: + raise CredentialsValidateFailedError(str(ex)) + + def _tts_invoke_streaming(self, model: str, credentials: dict, content_text: str, voice: str) -> any: + """ + _tts_invoke_streaming text2speech model + :param model: model name + :param credentials: model credentials + :param content_text: text content to be translated + :param voice: model timbre + :return: text translated to audio file + """ + try: + # doc: https://ai.gitee.com/docs/openapi/serverless#tag/serverless/POST/{service}/text-to-speech + endpoint_url = "https://ai.gitee.com/api/serverless/" + model + "/text-to-speech" + + headers = {"Content-Type": "application/json"} + api_key = credentials.get("api_key") + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + payload = {"inputs": content_text} + response = requests.post(endpoint_url, headers=headers, json=payload) + + if response.status_code != 200: + raise InvokeBadRequestError(response.text) + + data = response.content + + for i in range(0, len(data), 1024): + yield data[i : i + 1024] + except Exception as ex: + raise InvokeBadRequestError(str(ex)) diff --git a/api/pytest.ini b/api/pytest.ini index dcca08e2e5..a23a4b3f3d 100644 --- a/api/pytest.ini +++ b/api/pytest.ini @@ -27,3 +27,4 @@ env = XINFERENCE_GENERATION_MODEL_UID = generate XINFERENCE_RERANK_MODEL_UID = rerank XINFERENCE_SERVER_URL = http://a.abc.com:11451 + GITEE_AI_API_KEY = aaaaaaaaaaaaaaaaaaaa diff --git a/api/tests/integration_tests/.env.example b/api/tests/integration_tests/.env.example index 2d52399d29..6791cd891b 100644 --- a/api/tests/integration_tests/.env.example +++ b/api/tests/integration_tests/.env.example @@ -83,3 +83,6 @@ VOLC_EMBEDDING_ENDPOINT_ID= # 360 AI Credentials ZHINAO_API_KEY= + +# Gitee AI Credentials +GITEE_AI_API_KEY= diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/__init__.py b/api/tests/integration_tests/model_runtime/gitee_ai/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/test_llm.py b/api/tests/integration_tests/model_runtime/gitee_ai/test_llm.py new file mode 100644 index 0000000000..753c52ce31 --- /dev/null +++ b/api/tests/integration_tests/model_runtime/gitee_ai/test_llm.py @@ -0,0 +1,132 @@ +import os +from collections.abc import Generator + +import pytest + +from core.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta +from core.model_runtime.entities.message_entities import ( + AssistantPromptMessage, + PromptMessageTool, + SystemPromptMessage, + UserPromptMessage, +) +from core.model_runtime.entities.model_entities import AIModelEntity +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.gitee_ai.llm.llm import GiteeAILargeLanguageModel + + +def test_predefined_models(): + model = GiteeAILargeLanguageModel() + model_schemas = model.predefined_models() + + assert len(model_schemas) >= 1 + assert isinstance(model_schemas[0], AIModelEntity) + + +def test_validate_credentials_for_chat_model(): + model = GiteeAILargeLanguageModel() + + with pytest.raises(CredentialsValidateFailedError): + # model name to gpt-3.5-turbo because of mocking + model.validate_credentials(model="gpt-3.5-turbo", credentials={"api_key": "invalid_key"}) + + model.validate_credentials( + model="Qwen2-7B-Instruct", + credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, + ) + + +def test_invoke_chat_model(): + model = GiteeAILargeLanguageModel() + + result = model.invoke( + model="Qwen2-7B-Instruct", + credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, + prompt_messages=[ + SystemPromptMessage( + content="You are a helpful AI assistant.", + ), + UserPromptMessage(content="Hello World!"), + ], + model_parameters={ + "temperature": 0.0, + "top_p": 1.0, + "presence_penalty": 0.0, + "frequency_penalty": 0.0, + "max_tokens": 10, + "stream": False, + }, + stop=["How"], + stream=False, + user="foo", + ) + + assert isinstance(result, LLMResult) + assert len(result.message.content) > 0 + + +def test_invoke_stream_chat_model(): + model = GiteeAILargeLanguageModel() + + result = model.invoke( + model="Qwen2-7B-Instruct", + credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, + prompt_messages=[ + SystemPromptMessage( + content="You are a helpful AI assistant.", + ), + UserPromptMessage(content="Hello World!"), + ], + model_parameters={"temperature": 0.0, "max_tokens": 100, "stream": False}, + stream=True, + user="foo", + ) + + assert isinstance(result, Generator) + + for chunk in result: + assert isinstance(chunk, LLMResultChunk) + assert isinstance(chunk.delta, LLMResultChunkDelta) + assert isinstance(chunk.delta.message, AssistantPromptMessage) + assert len(chunk.delta.message.content) > 0 if chunk.delta.finish_reason is None else True + if chunk.delta.finish_reason is not None: + assert chunk.delta.usage is not None + + +def test_get_num_tokens(): + model = GiteeAILargeLanguageModel() + + num_tokens = model.get_num_tokens( + model="Qwen2-7B-Instruct", + credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, + prompt_messages=[UserPromptMessage(content="Hello World!")], + ) + + assert num_tokens == 10 + + num_tokens = model.get_num_tokens( + model="Qwen2-7B-Instruct", + credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, + prompt_messages=[ + SystemPromptMessage( + content="You are a helpful AI assistant.", + ), + UserPromptMessage(content="Hello World!"), + ], + tools=[ + PromptMessageTool( + name="get_weather", + description="Determine weather in my location", + parameters={ + "type": "object", + "properties": { + "location": {"type": "string", "description": "The city and state e.g. San Francisco, CA"}, + "unit": {"type": "string", "enum": ["c", "f"]}, + }, + "required": ["location"], + }, + ), + ], + ) + + assert num_tokens == 77 diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/test_provider.py b/api/tests/integration_tests/model_runtime/gitee_ai/test_provider.py new file mode 100644 index 0000000000..f12ed54a45 --- /dev/null +++ b/api/tests/integration_tests/model_runtime/gitee_ai/test_provider.py @@ -0,0 +1,15 @@ +import os + +import pytest + +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.gitee_ai.gitee_ai import GiteeAIProvider + + +def test_validate_provider_credentials(): + provider = GiteeAIProvider() + + with pytest.raises(CredentialsValidateFailedError): + provider.validate_provider_credentials(credentials={"api_key": "invalid_key"}) + + provider.validate_provider_credentials(credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}) diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/test_rerank.py b/api/tests/integration_tests/model_runtime/gitee_ai/test_rerank.py new file mode 100644 index 0000000000..0e5914a61f --- /dev/null +++ b/api/tests/integration_tests/model_runtime/gitee_ai/test_rerank.py @@ -0,0 +1,47 @@ +import os + +import pytest + +from core.model_runtime.entities.rerank_entities import RerankResult +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.gitee_ai.rerank.rerank import GiteeAIRerankModel + + +def test_validate_credentials(): + model = GiteeAIRerankModel() + + with pytest.raises(CredentialsValidateFailedError): + model.validate_credentials( + model="bge-reranker-v2-m3", + credentials={"api_key": "invalid_key"}, + ) + + model.validate_credentials( + model="bge-reranker-v2-m3", + credentials={ + "api_key": os.environ.get("GITEE_AI_API_KEY"), + }, + ) + + +def test_invoke_model(): + model = GiteeAIRerankModel() + result = model.invoke( + model="bge-reranker-v2-m3", + credentials={ + "api_key": os.environ.get("GITEE_AI_API_KEY"), + }, + query="What is the capital of the United States?", + docs=[ + "Carson City is the capital city of the American state of Nevada. At the 2010 United States " + "Census, Carson City had a population of 55,274.", + "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that " + "are a political division controlled by the United States. Its capital is Saipan.", + ], + top_n=1, + score_threshold=0.01, + ) + + assert isinstance(result, RerankResult) + assert len(result.docs) == 1 + assert result.docs[0].score >= 0.01 diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/test_speech2text.py b/api/tests/integration_tests/model_runtime/gitee_ai/test_speech2text.py new file mode 100644 index 0000000000..4a01453fdd --- /dev/null +++ b/api/tests/integration_tests/model_runtime/gitee_ai/test_speech2text.py @@ -0,0 +1,45 @@ +import os + +import pytest + +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.gitee_ai.speech2text.speech2text import GiteeAISpeech2TextModel + + +def test_validate_credentials(): + model = GiteeAISpeech2TextModel() + + with pytest.raises(CredentialsValidateFailedError): + model.validate_credentials( + model="whisper-base", + credentials={"api_key": "invalid_key"}, + ) + + model.validate_credentials( + model="whisper-base", + credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, + ) + + +def test_invoke_model(): + model = GiteeAISpeech2TextModel() + + # Get the directory of the current file + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # Get assets directory + assets_dir = os.path.join(os.path.dirname(current_dir), "assets") + + # Construct the path to the audio file + audio_file_path = os.path.join(assets_dir, "audio.mp3") + + # Open the file and get the file object + with open(audio_file_path, "rb") as audio_file: + file = audio_file + + result = model.invoke( + model="whisper-base", credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}, file=file + ) + + assert isinstance(result, str) + assert result == "1 2 3 4 5 6 7 8 9 10" diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/test_text_embedding.py b/api/tests/integration_tests/model_runtime/gitee_ai/test_text_embedding.py new file mode 100644 index 0000000000..34648f0bc8 --- /dev/null +++ b/api/tests/integration_tests/model_runtime/gitee_ai/test_text_embedding.py @@ -0,0 +1,46 @@ +import os + +import pytest + +from core.model_runtime.entities.text_embedding_entities import TextEmbeddingResult +from core.model_runtime.errors.validate import CredentialsValidateFailedError +from core.model_runtime.model_providers.gitee_ai.text_embedding.text_embedding import GiteeAIEmbeddingModel + + +def test_validate_credentials(): + model = GiteeAIEmbeddingModel() + + with pytest.raises(CredentialsValidateFailedError): + model.validate_credentials(model="bge-large-zh-v1.5", credentials={"api_key": "invalid_key"}) + + model.validate_credentials(model="bge-large-zh-v1.5", credentials={"api_key": os.environ.get("GITEE_AI_API_KEY")}) + + +def test_invoke_model(): + model = GiteeAIEmbeddingModel() + + result = model.invoke( + model="bge-large-zh-v1.5", + credentials={ + "api_key": os.environ.get("GITEE_AI_API_KEY"), + }, + texts=["hello", "world"], + user="user", + ) + + assert isinstance(result, TextEmbeddingResult) + assert len(result.embeddings) == 2 + + +def test_get_num_tokens(): + model = GiteeAIEmbeddingModel() + + num_tokens = model.get_num_tokens( + model="bge-large-zh-v1.5", + credentials={ + "api_key": os.environ.get("GITEE_AI_API_KEY"), + }, + texts=["hello", "world"], + ) + + assert num_tokens == 2 diff --git a/api/tests/integration_tests/model_runtime/gitee_ai/test_tts.py b/api/tests/integration_tests/model_runtime/gitee_ai/test_tts.py new file mode 100644 index 0000000000..9f18161a7b --- /dev/null +++ b/api/tests/integration_tests/model_runtime/gitee_ai/test_tts.py @@ -0,0 +1,23 @@ +import os + +from core.model_runtime.model_providers.gitee_ai.tts.tts import GiteeAIText2SpeechModel + + +def test_invoke_model(): + model = GiteeAIText2SpeechModel() + + result = model.invoke( + model="speecht5_tts", + tenant_id="test", + credentials={ + "api_key": os.environ.get("GITEE_AI_API_KEY"), + }, + content_text="Hello, world!", + voice="", + ) + + content = b"" + for chunk in result: + content += chunk + + assert content != b"" From ddb960ddfbd47bfa58fbdb92f6d781c55a3978d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Mon, 28 Oct 2024 16:52:57 +0800 Subject: [PATCH 03/29] feat: support Vectorizer can be used in workflow (#9932) --- api/core/agent/base_agent_runner.py | 12 +++++ api/core/file/file_manager.py | 12 ++++- .../builtin/vectorizer/tools/test_data.py | 1 - .../builtin/vectorizer/tools/vectorizer.py | 53 ++++++++++++------- .../builtin/vectorizer/tools/vectorizer.yaml | 15 +++--- .../provider/builtin/vectorizer/vectorizer.py | 10 +++- .../builtin/vectorizer/vectorizer.yaml | 8 --- api/core/tools/tool_manager.py | 14 +++-- 8 files changed, 82 insertions(+), 43 deletions(-) delete mode 100644 api/core/tools/provider/builtin/vectorizer/tools/test_data.py diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index 514dcfbd68..507455c176 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -165,6 +165,12 @@ class BaseAgentRunner(AppRunner): continue parameter_type = parameter.type.as_normal_type() + if parameter.type in { + ToolParameter.ToolParameterType.SYSTEM_FILES, + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + }: + continue enum = [] if parameter.type == ToolParameter.ToolParameterType.SELECT: enum = [option.value for option in parameter.options] @@ -250,6 +256,12 @@ class BaseAgentRunner(AppRunner): continue parameter_type = parameter.type.as_normal_type() + if parameter.type in { + ToolParameter.ToolParameterType.SYSTEM_FILES, + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + }: + continue enum = [] if parameter.type == ToolParameter.ToolParameterType.SELECT: enum = [option.value for option in parameter.options] diff --git a/api/core/file/file_manager.py b/api/core/file/file_manager.py index 0c6ce8ce75..b69d7a74c0 100644 --- a/api/core/file/file_manager.py +++ b/api/core/file/file_manager.py @@ -76,8 +76,16 @@ def to_prompt_message_content(f: File, /): def download(f: File, /): - upload_file = file_repository.get_upload_file(session=db.session(), file=f) - return _download_file_content(upload_file.key) + if f.transfer_method == FileTransferMethod.TOOL_FILE: + tool_file = file_repository.get_tool_file(session=db.session(), file=f) + return _download_file_content(tool_file.file_key) + elif f.transfer_method == FileTransferMethod.LOCAL_FILE: + upload_file = file_repository.get_upload_file(session=db.session(), file=f) + return _download_file_content(upload_file.key) + # remote file + response = ssrf_proxy.get(f.remote_url, follow_redirects=True) + response.raise_for_status() + return response.content def _download_file_content(path: str, /): diff --git a/api/core/tools/provider/builtin/vectorizer/tools/test_data.py b/api/core/tools/provider/builtin/vectorizer/tools/test_data.py deleted file mode 100644 index 8effa9818a..0000000000 --- a/api/core/tools/provider/builtin/vectorizer/tools/test_data.py +++ /dev/null @@ -1 +0,0 @@ -VECTORIZER_ICON_PNG = "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" # noqa: E501 diff --git a/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.py b/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.py index 4bd601c0bd..c722cd36c8 100644 --- a/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.py +++ b/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.py @@ -1,11 +1,12 @@ -from base64 import b64decode from typing import Any, Union from httpx import post +from core.file.enums import FileType +from core.file.file_manager import download +from core.tools.entities.common_entities import I18nObject from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter -from core.tools.errors import ToolProviderCredentialValidationError -from core.tools.provider.builtin.vectorizer.tools.test_data import VECTORIZER_ICON_PNG +from core.tools.errors import ToolParameterValidationError from core.tools.tool.builtin_tool import BuiltinTool @@ -16,30 +17,30 @@ class VectorizerTool(BuiltinTool): """ invoke tools """ - api_key_name = self.runtime.credentials.get("api_key_name", None) - api_key_value = self.runtime.credentials.get("api_key_value", None) + api_key_name = self.runtime.credentials.get("api_key_name") + api_key_value = self.runtime.credentials.get("api_key_value") mode = tool_parameters.get("mode", "test") - if mode == "production": - mode = "preview" - - if not api_key_name or not api_key_value: - raise ToolProviderCredentialValidationError("Please input api key name and value") + # image file for workflow mode + image = tool_parameters.get("image") + if image and image.type != FileType.IMAGE: + raise ToolParameterValidationError("Not a valid image") + # image_id for agent mode image_id = tool_parameters.get("image_id", "") - if not image_id: - return self.create_text_message("Please input image id") - if image_id.startswith("__test_"): - image_binary = b64decode(VECTORIZER_ICON_PNG) - else: + if image_id: image_binary = self.get_variable_file(self.VariableKey.IMAGE) if not image_binary: return self.create_text_message("Image not found, please request user to generate image firstly.") + elif image: + image_binary = download(image) + else: + raise ToolParameterValidationError("Please provide either image or image_id") response = post( "https://vectorizer.ai/api/v1/vectorize", + data={"mode": mode}, files={"image": image_binary}, - data={"mode": mode} if mode == "test" else {}, auth=(api_key_name, api_key_value), timeout=30, ) @@ -59,11 +60,23 @@ class VectorizerTool(BuiltinTool): return [ ToolParameter.get_simple_instance( name="image_id", - llm_description=f"the image id that you want to vectorize, \ - and the image id should be specified in \ + llm_description=f"the image_id that you want to vectorize, \ + and the image_id should be specified in \ {[i.name for i in self.list_default_image_variables()]}", type=ToolParameter.ToolParameterType.SELECT, - required=True, + required=False, options=[i.name for i in self.list_default_image_variables()], - ) + ), + ToolParameter( + name="image", + label=I18nObject(en_US="image", zh_Hans="image"), + human_description=I18nObject( + en_US="The image to be converted.", + zh_Hans="要转换的图片。", + ), + type=ToolParameter.ToolParameterType.FILE, + form=ToolParameter.ToolParameterForm.LLM, + llm_description="you should not input this parameter. just input the image_id.", + required=False, + ), ] diff --git a/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.yaml b/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.yaml index 4b4fb9e245..0afd1c201f 100644 --- a/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.yaml +++ b/api/core/tools/provider/builtin/vectorizer/tools/vectorizer.yaml @@ -4,14 +4,21 @@ identity: label: en_US: Vectorizer.AI zh_Hans: Vectorizer.AI - pt_BR: Vectorizer.AI description: human: en_US: Convert your PNG and JPG images to SVG vectors quickly and easily. Fully automatically. Using AI. zh_Hans: 一个将 PNG 和 JPG 图像快速轻松地转换为 SVG 矢量图的工具。 - pt_BR: Convert your PNG and JPG images to SVG vectors quickly and easily. Fully automatically. Using AI. llm: A tool for converting images to SVG vectors. you should input the image id as the input of this tool. the image id can be got from parameters. parameters: + - name: image + type: file + label: + en_US: image + human_description: + en_US: The image to be converted. + zh_Hans: 要转换的图片。 + llm_description: you should not input this parameter. just input the image_id. + form: llm - name: mode type: select required: true @@ -20,19 +27,15 @@ parameters: label: en_US: production zh_Hans: 生产模式 - pt_BR: production - value: test label: en_US: test zh_Hans: 测试模式 - pt_BR: test default: test label: en_US: Mode zh_Hans: 模式 - pt_BR: Mode human_description: en_US: It is free to integrate with and test out the API in test mode, no subscription required. zh_Hans: 在测试模式下,可以免费测试API。 - pt_BR: It is free to integrate with and test out the API in test mode, no subscription required. form: form diff --git a/api/core/tools/provider/builtin/vectorizer/vectorizer.py b/api/core/tools/provider/builtin/vectorizer/vectorizer.py index 3b868572f9..8140348723 100644 --- a/api/core/tools/provider/builtin/vectorizer/vectorizer.py +++ b/api/core/tools/provider/builtin/vectorizer/vectorizer.py @@ -1,5 +1,7 @@ from typing import Any +from core.file import File +from core.file.enums import FileTransferMethod, FileType from core.tools.errors import ToolProviderCredentialValidationError from core.tools.provider.builtin.vectorizer.tools.vectorizer import VectorizerTool from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController @@ -7,6 +9,12 @@ from core.tools.provider.builtin_tool_provider import BuiltinToolProviderControl class VectorizerProvider(BuiltinToolProviderController): def _validate_credentials(self, credentials: dict[str, Any]) -> None: + test_img = File( + tenant_id="__test_123", + remote_url="https://cloud.dify.ai/logo/logo-site.png", + type=FileType.IMAGE, + transfer_method=FileTransferMethod.REMOTE_URL, + ) try: VectorizerTool().fork_tool_runtime( runtime={ @@ -14,7 +22,7 @@ class VectorizerProvider(BuiltinToolProviderController): } ).invoke( user_id="", - tool_parameters={"mode": "test", "image_id": "__test_123"}, + tool_parameters={"mode": "test", "image": test_img}, ) except Exception as e: raise ToolProviderCredentialValidationError(str(e)) diff --git a/api/core/tools/provider/builtin/vectorizer/vectorizer.yaml b/api/core/tools/provider/builtin/vectorizer/vectorizer.yaml index 1257f8d285..94dae20876 100644 --- a/api/core/tools/provider/builtin/vectorizer/vectorizer.yaml +++ b/api/core/tools/provider/builtin/vectorizer/vectorizer.yaml @@ -4,11 +4,9 @@ identity: label: en_US: Vectorizer.AI zh_Hans: Vectorizer.AI - pt_BR: Vectorizer.AI description: en_US: Convert your PNG and JPG images to SVG vectors quickly and easily. Fully automatically. Using AI. zh_Hans: 一个将 PNG 和 JPG 图像快速轻松地转换为 SVG 矢量图的工具。 - pt_BR: Convert your PNG and JPG images to SVG vectors quickly and easily. Fully automatically. Using AI. icon: icon.png tags: - productivity @@ -20,15 +18,12 @@ credentials_for_provider: label: en_US: Vectorizer.AI API Key name zh_Hans: Vectorizer.AI API Key name - pt_BR: Vectorizer.AI API Key name placeholder: en_US: Please input your Vectorizer.AI ApiKey name zh_Hans: 请输入你的 Vectorizer.AI ApiKey name - pt_BR: Please input your Vectorizer.AI ApiKey name help: en_US: Get your Vectorizer.AI API Key from Vectorizer.AI. zh_Hans: 从 Vectorizer.AI 获取您的 Vectorizer.AI API Key。 - pt_BR: Get your Vectorizer.AI API Key from Vectorizer.AI. url: https://vectorizer.ai/api api_key_value: type: secret-input @@ -36,12 +31,9 @@ credentials_for_provider: label: en_US: Vectorizer.AI API Key zh_Hans: Vectorizer.AI API Key - pt_BR: Vectorizer.AI API Key placeholder: en_US: Please input your Vectorizer.AI ApiKey zh_Hans: 请输入你的 Vectorizer.AI ApiKey - pt_BR: Please input your Vectorizer.AI ApiKey help: en_US: Get your Vectorizer.AI API Key from Vectorizer.AI. zh_Hans: 从 Vectorizer.AI 获取您的 Vectorizer.AI API Key。 - pt_BR: Get your Vectorizer.AI API Key from Vectorizer.AI. diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index 9e984732b7..63f7775164 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -242,11 +242,15 @@ class ToolManager: parameters = tool_entity.get_all_runtime_parameters() for parameter in parameters: # check file types - if parameter.type in { - ToolParameter.ToolParameterType.SYSTEM_FILES, - ToolParameter.ToolParameterType.FILE, - ToolParameter.ToolParameterType.FILES, - }: + if ( + parameter.type + in { + ToolParameter.ToolParameterType.SYSTEM_FILES, + ToolParameter.ToolParameterType.FILE, + ToolParameter.ToolParameterType.FILES, + } + and parameter.required + ): raise ValueError(f"file type parameter {parameter.name} not supported in agent") if parameter.form == ToolParameter.ToolParameterForm.FORM: From 7056009b6adb60071b04d1441605cd0e886eb5e0 Mon Sep 17 00:00:00 2001 From: Xiao Ley Date: Mon, 28 Oct 2024 17:18:28 +0800 Subject: [PATCH 04/29] feat(tools): add Baidu translation tool (#9943) --- .../builtin/baidu_translate/_assets/icon.png | Bin 0 -> 16676 bytes .../_baidu_translate_tool_base.py | 11 + .../baidu_translate/baidu_translate.py | 17 ++ .../baidu_translate/baidu_translate.yaml | 39 +++ .../baidu_translate/tools/fieldtranslate.py | 78 +++++ .../baidu_translate/tools/fieldtranslate.yaml | 123 ++++++++ .../builtin/baidu_translate/tools/language.py | 95 ++++++ .../baidu_translate/tools/language.yaml | 43 +++ .../baidu_translate/tools/translate.py | 67 +++++ .../baidu_translate/tools/translate.yaml | 275 ++++++++++++++++++ 10 files changed, 748 insertions(+) create mode 100644 api/core/tools/provider/builtin/baidu_translate/_assets/icon.png create mode 100644 api/core/tools/provider/builtin/baidu_translate/_baidu_translate_tool_base.py create mode 100644 api/core/tools/provider/builtin/baidu_translate/baidu_translate.py create mode 100644 api/core/tools/provider/builtin/baidu_translate/baidu_translate.yaml create mode 100644 api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.py create mode 100644 api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.yaml create mode 100644 api/core/tools/provider/builtin/baidu_translate/tools/language.py create mode 100644 api/core/tools/provider/builtin/baidu_translate/tools/language.yaml create mode 100644 api/core/tools/provider/builtin/baidu_translate/tools/translate.py create mode 100644 api/core/tools/provider/builtin/baidu_translate/tools/translate.yaml diff --git a/api/core/tools/provider/builtin/baidu_translate/_assets/icon.png b/api/core/tools/provider/builtin/baidu_translate/_assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb8f21513ba7d45de8204bfe64aa3cc1fd7fc26 GIT binary patch literal 16676 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 literal 0 HcmV?d00001 diff --git a/api/core/tools/provider/builtin/baidu_translate/_baidu_translate_tool_base.py b/api/core/tools/provider/builtin/baidu_translate/_baidu_translate_tool_base.py new file mode 100644 index 0000000000..ce907c3c61 --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/_baidu_translate_tool_base.py @@ -0,0 +1,11 @@ +from hashlib import md5 + + +class BaiduTranslateToolBase: + def _get_sign(self, appid, secret, salt, query): + """ + get baidu translate sign + """ + # concatenate the string in the order of appid+q+salt+secret + str = appid + query + salt + secret + return md5(str.encode("utf-8")).hexdigest() diff --git a/api/core/tools/provider/builtin/baidu_translate/baidu_translate.py b/api/core/tools/provider/builtin/baidu_translate/baidu_translate.py new file mode 100644 index 0000000000..cccd2f8c8f --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/baidu_translate.py @@ -0,0 +1,17 @@ +from typing import Any + +from core.tools.errors import ToolProviderCredentialValidationError +from core.tools.provider.builtin.baidu_translate.tools.translate import BaiduTranslateTool +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + + +class BaiduTranslateProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict[str, Any]) -> None: + try: + BaiduTranslateTool().fork_tool_runtime( + runtime={ + "credentials": credentials, + } + ).invoke(user_id="", tool_parameters={"q": "这是一段测试文本", "from": "auto", "to": "en"}) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) diff --git a/api/core/tools/provider/builtin/baidu_translate/baidu_translate.yaml b/api/core/tools/provider/builtin/baidu_translate/baidu_translate.yaml new file mode 100644 index 0000000000..06dadeeefc --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/baidu_translate.yaml @@ -0,0 +1,39 @@ +identity: + author: Xiao Ley + name: baidu_translate + label: + en_US: Baidu Translate + zh_Hans: 百度翻译 + description: + en_US: Translate text using Baidu + zh_Hans: 使用百度进行翻译 + icon: icon.png + tags: + - utilities +credentials_for_provider: + appid: + type: secret-input + required: true + label: + en_US: Baidu translate appid + zh_Hans: Baidu translate appid + placeholder: + en_US: Please input your Baidu translate appid + zh_Hans: 请输入你的百度翻译 appid + help: + en_US: Get your Baidu translate appid from Baidu translate + zh_Hans: 从百度翻译开放平台获取你的 appid + url: https://api.fanyi.baidu.com + secret: + type: secret-input + required: true + label: + en_US: Baidu translate secret + zh_Hans: Baidu translate secret + placeholder: + en_US: Please input your Baidu translate secret + zh_Hans: 请输入你的百度翻译 secret + help: + en_US: Get your Baidu translate secret from Baidu translate + zh_Hans: 从百度翻译开放平台获取你的 secret + url: https://api.fanyi.baidu.com diff --git a/api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.py b/api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.py new file mode 100644 index 0000000000..bce259f31d --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.py @@ -0,0 +1,78 @@ +import random +from hashlib import md5 +from typing import Any, Union + +import requests + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.provider.builtin.baidu_translate._baidu_translate_tool_base import BaiduTranslateToolBase +from core.tools.tool.builtin_tool import BuiltinTool + + +class BaiduFieldTranslateTool(BuiltinTool, BaiduTranslateToolBase): + def _invoke( + self, + user_id: str, + tool_parameters: dict[str, Any], + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + """ + invoke tools + """ + BAIDU_FIELD_TRANSLATE_URL = "https://fanyi-api.baidu.com/api/trans/vip/fieldtranslate" + + appid = self.runtime.credentials.get("appid", "") + if not appid: + raise ValueError("invalid baidu translate appid") + + secret = self.runtime.credentials.get("secret", "") + if not secret: + raise ValueError("invalid baidu translate secret") + + q = tool_parameters.get("q", "") + if not q: + raise ValueError("Please input text to translate") + + from_ = tool_parameters.get("from", "") + if not from_: + raise ValueError("Please select source language") + + to = tool_parameters.get("to", "") + if not to: + raise ValueError("Please select destination language") + + domain = tool_parameters.get("domain", "") + if not domain: + raise ValueError("Please select domain") + + salt = str(random.randint(32768, 16777215)) + sign = self._get_sign(appid, secret, salt, q, domain) + + headers = {"Content-Type": "application/x-www-form-urlencoded"} + params = { + "q": q, + "from": from_, + "to": to, + "appid": appid, + "salt": salt, + "domain": domain, + "sign": sign, + "needIntervene": 1, + } + try: + response = requests.post(BAIDU_FIELD_TRANSLATE_URL, headers=headers, data=params) + result = response.json() + + if "trans_result" in result: + result_text = result["trans_result"][0]["dst"] + else: + result_text = f'{result["error_code"]}: {result["error_msg"]}' + + return self.create_text_message(str(result_text)) + except requests.RequestException as e: + raise ValueError(f"Translation service error: {e}") + except Exception: + raise ValueError("Translation service error, please check the network") + + def _get_sign(self, appid, secret, salt, query, domain): + str = appid + query + salt + domain + secret + return md5(str.encode("utf-8")).hexdigest() diff --git a/api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.yaml b/api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.yaml new file mode 100644 index 0000000000..de51fddbae --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/tools/fieldtranslate.yaml @@ -0,0 +1,123 @@ +identity: + name: field_translate + author: Xiao Ley + label: + en_US: Field translate + zh_Hans: 百度领域翻译 +description: + human: + en_US: A tool for Baidu Field translate (Currently, the fields of "novel" and "wiki" only support Chinese to English translation. If the language direction is set to English to Chinese, the default output will be a universal translation result). + zh_Hans: 百度领域翻译,提供多种领域的文本翻译(目前“网络文学领域”和“人文社科领域”仅支持中到英,如设置语言方向为英到中,则默认输出通用翻译结果) + llm: A tool for Baidu Field translate +parameters: + - name: q + type: string + required: true + label: + en_US: Text content + zh_Hans: 文本内容 + human_description: + en_US: Text content to be translated + zh_Hans: 需要翻译的文本内容 + llm_description: Text content to be translated + form: llm + - name: from + type: select + required: true + label: + en_US: source language + zh_Hans: 源语言 + human_description: + en_US: The source language of the input text + zh_Hans: 输入的文本的源语言 + default: auto + form: form + options: + - value: auto + label: + en_US: auto + zh_Hans: 自动检测 + - value: zh + label: + en_US: Chinese + zh_Hans: 中文 + - value: en + label: + en_US: English + zh_Hans: 英语 + - name: to + type: select + required: true + label: + en_US: destination language + zh_Hans: 目标语言 + human_description: + en_US: The destination language of the input text + zh_Hans: 输入文本的目标语言 + default: en + form: form + options: + - value: zh + label: + en_US: Chinese + zh_Hans: 中文 + - value: en + label: + en_US: English + zh_Hans: 英语 + - name: domain + type: select + required: true + label: + en_US: domain + zh_Hans: 领域 + human_description: + en_US: The domain of the input text + zh_Hans: 输入文本的领域 + default: novel + form: form + options: + - value: it + label: + en_US: it + zh_Hans: 信息技术领域 + - value: finance + label: + en_US: finance + zh_Hans: 金融财经领域 + - value: machinery + label: + en_US: machinery + zh_Hans: 机械制造领域 + - value: senimed + label: + en_US: senimed + zh_Hans: 生物医药领域 + - value: novel + label: + en_US: novel (only support Chinese to English translation) + zh_Hans: 网络文学领域(仅支持中到英) + - value: academic + label: + en_US: academic + zh_Hans: 学术论文领域 + - value: aerospace + label: + en_US: aerospace + zh_Hans: 航空航天领域 + - value: wiki + label: + en_US: wiki (only support Chinese to English translation) + zh_Hans: 人文社科领域(仅支持中到英) + - value: news + label: + en_US: news + zh_Hans: 新闻咨询领域 + - value: law + label: + en_US: law + zh_Hans: 法律法规领域 + - value: contract + label: + en_US: contract + zh_Hans: 合同领域 diff --git a/api/core/tools/provider/builtin/baidu_translate/tools/language.py b/api/core/tools/provider/builtin/baidu_translate/tools/language.py new file mode 100644 index 0000000000..3bbaee88b3 --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/tools/language.py @@ -0,0 +1,95 @@ +import random +from typing import Any, Union + +import requests + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.provider.builtin.baidu_translate._baidu_translate_tool_base import BaiduTranslateToolBase +from core.tools.tool.builtin_tool import BuiltinTool + + +class BaiduLanguageTool(BuiltinTool, BaiduTranslateToolBase): + def _invoke( + self, + user_id: str, + tool_parameters: dict[str, Any], + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + """ + invoke tools + """ + BAIDU_LANGUAGE_URL = "https://fanyi-api.baidu.com/api/trans/vip/language" + + appid = self.runtime.credentials.get("appid", "") + if not appid: + raise ValueError("invalid baidu translate appid") + + secret = self.runtime.credentials.get("secret", "") + if not secret: + raise ValueError("invalid baidu translate secret") + + q = tool_parameters.get("q", "") + if not q: + raise ValueError("Please input text to translate") + + description_language = tool_parameters.get("description_language", "English") + + salt = str(random.randint(32768, 16777215)) + sign = self._get_sign(appid, secret, salt, q) + + headers = {"Content-Type": "application/x-www-form-urlencoded"} + params = { + "q": q, + "appid": appid, + "salt": salt, + "sign": sign, + } + + try: + response = requests.post(BAIDU_LANGUAGE_URL, params=params, headers=headers) + result = response.json() + if "error_code" not in result: + raise ValueError("Translation service error, please check the network") + + result_text = "" + if result["error_code"] != 0: + result_text = f'{result["error_code"]}: {result["error_msg"]}' + else: + result_text = result["data"]["src"] + result_text = self.mapping_result(description_language, result_text) + + return self.create_text_message(result_text) + except requests.RequestException as e: + raise ValueError(f"Translation service error: {e}") + except Exception: + raise ValueError("Translation service error, please check the network") + + def mapping_result(self, description_language: str, result: str) -> str: + """ + mapping result + """ + mapping = { + "English": { + "zh": "Chinese", + "en": "English", + "jp": "Japanese", + "kor": "Korean", + "th": "Thai", + "vie": "Vietnamese", + "ru": "Russian", + }, + "Chinese": { + "zh": "中文", + "en": "英文", + "jp": "日文", + "kor": "韩文", + "th": "泰语", + "vie": "越南语", + "ru": "俄语", + }, + } + + language_mapping = mapping.get(description_language) + if not language_mapping: + return result + + return language_mapping.get(result, result) diff --git a/api/core/tools/provider/builtin/baidu_translate/tools/language.yaml b/api/core/tools/provider/builtin/baidu_translate/tools/language.yaml new file mode 100644 index 0000000000..60cca2e288 --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/tools/language.yaml @@ -0,0 +1,43 @@ +identity: + name: language + author: Xiao Ley + label: + en_US: Baidu Language + zh_Hans: 百度语种识别 +description: + human: + en_US: A tool for Baidu Language, support Chinese, English, Japanese, Korean, Thai, Vietnamese and Russian + zh_Hans: 使用百度进行语种识别,支持的语种:中文、英语、日语、韩语、泰语、越南语和俄语 + llm: A tool for Baidu Language +parameters: + - name: q + type: string + required: true + label: + en_US: Text content + zh_Hans: 文本内容 + human_description: + en_US: Text content to be recognized + zh_Hans: 需要识别语言的文本内容 + llm_description: Text content to be recognized + form: llm + - name: description_language + type: select + required: true + label: + en_US: Description language + zh_Hans: 描述语言 + human_description: + en_US: Describe the language used to identify the results + zh_Hans: 描述识别结果所用的语言 + default: Chinese + form: form + options: + - value: Chinese + label: + en_US: Chinese + zh_Hans: 中文 + - value: English + label: + en_US: English + zh_Hans: 英语 diff --git a/api/core/tools/provider/builtin/baidu_translate/tools/translate.py b/api/core/tools/provider/builtin/baidu_translate/tools/translate.py new file mode 100644 index 0000000000..7cd816a3bc --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/tools/translate.py @@ -0,0 +1,67 @@ +import random +from typing import Any, Union + +import requests + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.provider.builtin.baidu_translate._baidu_translate_tool_base import BaiduTranslateToolBase +from core.tools.tool.builtin_tool import BuiltinTool + + +class BaiduTranslateTool(BuiltinTool, BaiduTranslateToolBase): + def _invoke( + self, + user_id: str, + tool_parameters: dict[str, Any], + ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]: + """ + invoke tools + """ + BAIDU_TRANSLATE_URL = "https://fanyi-api.baidu.com/api/trans/vip/translate" + + appid = self.runtime.credentials.get("appid", "") + if not appid: + raise ValueError("invalid baidu translate appid") + + secret = self.runtime.credentials.get("secret", "") + if not secret: + raise ValueError("invalid baidu translate secret") + + q = tool_parameters.get("q", "") + if not q: + raise ValueError("Please input text to translate") + + from_ = tool_parameters.get("from", "") + if not from_: + raise ValueError("Please select source language") + + to = tool_parameters.get("to", "") + if not to: + raise ValueError("Please select destination language") + + salt = str(random.randint(32768, 16777215)) + sign = self._get_sign(appid, secret, salt, q) + + headers = {"Content-Type": "application/x-www-form-urlencoded"} + params = { + "q": q, + "from": from_, + "to": to, + "appid": appid, + "salt": salt, + "sign": sign, + } + try: + response = requests.post(BAIDU_TRANSLATE_URL, params=params, headers=headers) + result = response.json() + + if "trans_result" in result: + result_text = result["trans_result"][0]["dst"] + else: + result_text = f'{result["error_code"]}: {result["error_msg"]}' + + return self.create_text_message(str(result_text)) + except requests.RequestException as e: + raise ValueError(f"Translation service error: {e}") + except Exception: + raise ValueError("Translation service error, please check the network") diff --git a/api/core/tools/provider/builtin/baidu_translate/tools/translate.yaml b/api/core/tools/provider/builtin/baidu_translate/tools/translate.yaml new file mode 100644 index 0000000000..c8ff32cb6b --- /dev/null +++ b/api/core/tools/provider/builtin/baidu_translate/tools/translate.yaml @@ -0,0 +1,275 @@ +identity: + name: translate + author: Xiao Ley + label: + en_US: Translate + zh_Hans: 百度翻译 +description: + human: + en_US: A tool for Baidu Translate + zh_Hans: 百度翻译 + llm: A tool for Baidu Translate +parameters: + - name: q + type: string + required: true + label: + en_US: Text content + zh_Hans: 文本内容 + human_description: + en_US: Text content to be translated + zh_Hans: 需要翻译的文本内容 + llm_description: Text content to be translated + form: llm + - name: from + type: select + required: true + label: + en_US: source language + zh_Hans: 源语言 + human_description: + en_US: The source language of the input text + zh_Hans: 输入的文本的源语言 + default: auto + form: form + options: + - value: auto + label: + en_US: auto + zh_Hans: 自动检测 + - value: zh + label: + en_US: Chinese + zh_Hans: 中文 + - value: en + label: + en_US: English + zh_Hans: 英语 + - value: cht + label: + en_US: Traditional Chinese + zh_Hans: 繁体中文 + - value: yue + label: + en_US: Yue + zh_Hans: 粤语 + - value: wyw + label: + en_US: Wyw + zh_Hans: 文言文 + - value: jp + label: + en_US: Japanese + zh_Hans: 日语 + - value: kor + label: + en_US: Korean + zh_Hans: 韩语 + - value: fra + label: + en_US: French + zh_Hans: 法语 + - value: spa + label: + en_US: Spanish + zh_Hans: 西班牙语 + - value: th + label: + en_US: Thai + zh_Hans: 泰语 + - value: ara + label: + en_US: Arabic + zh_Hans: 阿拉伯语 + - value: ru + label: + en_US: Russian + zh_Hans: 俄语 + - value: pt + label: + en_US: Portuguese + zh_Hans: 葡萄牙语 + - value: de + label: + en_US: German + zh_Hans: 德语 + - value: it + label: + en_US: Italian + zh_Hans: 意大利语 + - value: el + label: + en_US: Greek + zh_Hans: 希腊语 + - value: nl + label: + en_US: Dutch + zh_Hans: 荷兰语 + - value: pl + label: + en_US: Polish + zh_Hans: 波兰语 + - value: bul + label: + en_US: Bulgarian + zh_Hans: 保加利亚语 + - value: est + label: + en_US: Estonian + zh_Hans: 爱沙尼亚语 + - value: dan + label: + en_US: Danish + zh_Hans: 丹麦语 + - value: fin + label: + en_US: Finnish + zh_Hans: 芬兰语 + - value: cs + label: + en_US: Czech + zh_Hans: 捷克语 + - value: rom + label: + en_US: Romanian + zh_Hans: 罗马尼亚语 + - value: slo + label: + en_US: Slovak + zh_Hans: 斯洛文尼亚语 + - value: swe + label: + en_US: Swedish + zh_Hans: 瑞典语 + - value: hu + label: + en_US: Hungarian + zh_Hans: 匈牙利语 + - value: vie + label: + en_US: Vietnamese + zh_Hans: 越南语 + - name: to + type: select + required: true + label: + en_US: destination language + zh_Hans: 目标语言 + human_description: + en_US: The destination language of the input text + zh_Hans: 输入文本的目标语言 + default: en + form: form + options: + - value: zh + label: + en_US: Chinese + zh_Hans: 中文 + - value: en + label: + en_US: English + zh_Hans: 英语 + - value: cht + label: + en_US: Traditional Chinese + zh_Hans: 繁体中文 + - value: yue + label: + en_US: Yue + zh_Hans: 粤语 + - value: wyw + label: + en_US: Wyw + zh_Hans: 文言文 + - value: jp + label: + en_US: Japanese + zh_Hans: 日语 + - value: kor + label: + en_US: Korean + zh_Hans: 韩语 + - value: fra + label: + en_US: French + zh_Hans: 法语 + - value: spa + label: + en_US: Spanish + zh_Hans: 西班牙语 + - value: th + label: + en_US: Thai + zh_Hans: 泰语 + - value: ara + label: + en_US: Arabic + zh_Hans: 阿拉伯语 + - value: ru + label: + en_US: Russian + zh_Hans: 俄语 + - value: pt + label: + en_US: Portuguese + zh_Hans: 葡萄牙语 + - value: de + label: + en_US: German + zh_Hans: 德语 + - value: it + label: + en_US: Italian + zh_Hans: 意大利语 + - value: el + label: + en_US: Greek + zh_Hans: 希腊语 + - value: nl + label: + en_US: Dutch + zh_Hans: 荷兰语 + - value: pl + label: + en_US: Polish + zh_Hans: 波兰语 + - value: bul + label: + en_US: Bulgarian + zh_Hans: 保加利亚语 + - value: est + label: + en_US: Estonian + zh_Hans: 爱沙尼亚语 + - value: dan + label: + en_US: Danish + zh_Hans: 丹麦语 + - value: fin + label: + en_US: Finnish + zh_Hans: 芬兰语 + - value: cs + label: + en_US: Czech + zh_Hans: 捷克语 + - value: rom + label: + en_US: Romanian + zh_Hans: 罗马尼亚语 + - value: slo + label: + en_US: Slovak + zh_Hans: 斯洛文尼亚语 + - value: swe + label: + en_US: Swedish + zh_Hans: 瑞典语 + - value: hu + label: + en_US: Hungarian + zh_Hans: 匈牙利语 + - value: vie + label: + en_US: Vietnamese + zh_Hans: 越南语 From 4da0b706948a7623403ddc71804d1884644659c4 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 28 Oct 2024 17:51:01 +0800 Subject: [PATCH 05/29] feat(http-request-executor): enhance file handling in HTTP requests (#9944) --- api/core/workflow/nodes/http_request/executor.py | 8 ++++++-- .../core/workflow/nodes/test_http_request_node.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/core/workflow/nodes/http_request/executor.py b/api/core/workflow/nodes/http_request/executor.py index 0270d7e0fd..6872478299 100644 --- a/api/core/workflow/nodes/http_request/executor.py +++ b/api/core/workflow/nodes/http_request/executor.py @@ -33,7 +33,7 @@ class Executor: params: Mapping[str, str] | None content: str | bytes | None data: Mapping[str, Any] | None - files: Mapping[str, bytes] | None + files: Mapping[str, tuple[str | None, bytes, str]] | None json: Any headers: dict[str, str] auth: HttpRequestNodeAuthorization @@ -141,7 +141,11 @@ class Executor: files = {k: self.variable_pool.get_file(selector) for k, selector in file_selectors.items()} files = {k: v for k, v in files.items() if v is not None} files = {k: variable.value for k, variable in files.items()} - files = {k: file_manager.download(v) for k, v in files.items() if v.related_id is not None} + files = { + k: (v.filename, file_manager.download(v), v.mime_type or "application/octet-stream") + for k, v in files.items() + if v.related_id is not None + } self.data = form_data self.files = files diff --git a/api/tests/unit_tests/core/workflow/nodes/test_http_request_node.py b/api/tests/unit_tests/core/workflow/nodes/test_http_request_node.py index 2a5fda48b1..720037d05f 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_http_request_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_http_request_node.py @@ -192,7 +192,7 @@ def test_http_request_node_form_with_file(monkeypatch): def attr_checker(*args, **kwargs): assert kwargs["data"] == {"name": "test"} - assert kwargs["files"] == {"file": b"test"} + assert kwargs["files"] == {"file": (None, b"test", "application/octet-stream")} return httpx.Response(200, content=b"") monkeypatch.setattr( From 81d4d8cea1d6c37eb4f2d4cf142ad12a667efc91 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 28 Oct 2024 18:01:33 +0800 Subject: [PATCH 06/29] fix: separator change add too many backslash (#9949) --- web/app/components/datasets/create/step-two/escape.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/datasets/create/step-two/escape.ts b/web/app/components/datasets/create/step-two/escape.ts index 098f43bc7f..2e1c3a9d73 100644 --- a/web/app/components/datasets/create/step-two/escape.ts +++ b/web/app/components/datasets/create/step-two/escape.ts @@ -3,7 +3,7 @@ function escape(input: string): string { return '' const res = input - .replaceAll('\\', '\\\\') + // .replaceAll('\\', '\\\\') // This would add too many backslashes .replaceAll('\0', '\\0') .replaceAll('\b', '\\b') .replaceAll('\f', '\\f') From adcd83f6a808e2d3a90a4844aca14441a8a9a49e Mon Sep 17 00:00:00 2001 From: AllenWriter Date: Mon, 28 Oct 2024 18:34:23 +0800 Subject: [PATCH 07/29] Docs: fix docs url (#9954) --- .../workflow/nodes/_base/hooks/use-node-help-link.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts index b5fe9554da..2ecdf101d2 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-node-help-link.ts @@ -23,8 +23,8 @@ export const useNodeHelpLink = (nodeType: BlockEnum) => { [BlockEnum.Code]: 'code', [BlockEnum.TemplateTransform]: 'template', [BlockEnum.VariableAssigner]: 'variable-assigner', - [BlockEnum.VariableAggregator]: 'variable-assigner', - [BlockEnum.Assigner]: 'variable-assignment', + [BlockEnum.VariableAggregator]: 'variable-aggregator', + [BlockEnum.Assigner]: 'variable-assigner', [BlockEnum.Iteration]: 'iteration', [BlockEnum.IterationStart]: 'iteration', [BlockEnum.ParameterExtractor]: 'parameter-extractor', @@ -46,8 +46,8 @@ export const useNodeHelpLink = (nodeType: BlockEnum) => { [BlockEnum.Code]: 'code', [BlockEnum.TemplateTransform]: 'template', [BlockEnum.VariableAssigner]: 'variable-assigner', - [BlockEnum.VariableAggregator]: 'variable-assigner', - [BlockEnum.Assigner]: 'variable-assignment', + [BlockEnum.VariableAggregator]: 'variable-aggregator', + [BlockEnum.Assigner]: 'variable-assigner', [BlockEnum.Iteration]: 'iteration', [BlockEnum.IterationStart]: 'iteration', [BlockEnum.ParameterExtractor]: 'parameter-extractor', From badf9baf9bbe87a56c4343cfbbbf70fab0d567db Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:37:35 +0800 Subject: [PATCH 08/29] Fix/external api update (#9955) --- api/services/external_knowledge_service.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/services/external_knowledge_service.py b/api/services/external_knowledge_service.py index 4efdf8d7db..094772d674 100644 --- a/api/services/external_knowledge_service.py +++ b/api/services/external_knowledge_service.py @@ -6,6 +6,8 @@ from typing import Any, Optional, Union import httpx import validators +from constants import HIDDEN_VALUE + # from tasks.external_document_indexing_task import external_document_indexing_task from core.helper import ssrf_proxy from extensions.ext_database import db @@ -92,6 +94,8 @@ class ExternalDatasetService: ).first() if external_knowledge_api is None: raise ValueError("api template not found") + if args.get("settings") and args.get("settings").get("api_key") == HIDDEN_VALUE: + args.get("settings")["api_key"] = external_knowledge_api.settings_dict.get("api_key") external_knowledge_api.name = args.get("name") external_knowledge_api.description = args.get("description", "") From de57af46c03d65f1f0c951e8f530516ea450656d Mon Sep 17 00:00:00 2001 From: -LAN- Date: Mon, 28 Oct 2024 18:47:45 +0800 Subject: [PATCH 09/29] chore: update version to 0.10.2 in packaging and docker configurations (#9924) --- api/configs/packaging/__init__.py | 2 +- docker-legacy/docker-compose.yaml | 6 +++--- docker/docker-compose.yaml | 6 +++--- web/package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index 389a64f53e..3dc87e3058 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings): CURRENT_VERSION: str = Field( description="Dify version", - default="0.10.1", + default="0.10.2", ) COMMIT_SHA: str = Field( diff --git a/docker-legacy/docker-compose.yaml b/docker-legacy/docker-compose.yaml index 17b788ff81..e3f1c3b761 100644 --- a/docker-legacy/docker-compose.yaml +++ b/docker-legacy/docker-compose.yaml @@ -2,7 +2,7 @@ version: '3' services: # API service api: - image: langgenius/dify-api:0.10.1 + image: langgenius/dify-api:0.10.2 restart: always environment: # Startup mode, 'api' starts the API server. @@ -227,7 +227,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.10.1 + image: langgenius/dify-api:0.10.2 restart: always environment: CONSOLE_WEB_URL: '' @@ -396,7 +396,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.10.1 + image: langgenius/dify-web:0.10.2 restart: always environment: # The base URL of console application api server, refers to the Console base URL of WEB service if console domain is diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index d43bd5e2d1..bf8a8b07e6 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -242,7 +242,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:0.10.1 + image: langgenius/dify-api:0.10.2 restart: always environment: # Use the shared environment variables. @@ -262,7 +262,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.10.1 + image: langgenius/dify-api:0.10.2 restart: always environment: # Use the shared environment variables. @@ -281,7 +281,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.10.1 + image: langgenius/dify-web:0.10.2 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} diff --git a/web/package.json b/web/package.json index e01603e8d4..04ef26afcd 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "0.10.1", + "version": "0.10.2", "private": true, "engines": { "node": ">=18.17.0" From de850262b8633a00eefe99333658dbd08156e4bb Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Mon, 28 Oct 2024 19:23:31 +0800 Subject: [PATCH 10/29] fix: button rendering when using streaming (#9957) --- web/app/components/base/markdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/base/markdown.tsx b/web/app/components/base/markdown.tsx index 33ff23d3b7..dde4bcf786 100644 --- a/web/app/components/base/markdown.tsx +++ b/web/app/components/base/markdown.tsx @@ -252,7 +252,7 @@ const MarkdownButton = ({ node }: any) => { className={cn('!h-8 !px-3 select-none')} onClick={() => onSend?.(message)} > - {node.children[0].value} + {node.children[0]?.value || ''} } MarkdownButton.displayName = 'MarkdownButton' From f47177ecb4c4b36df11c19f0ec866c2c5a162888 Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Mon, 28 Oct 2024 23:04:54 +0800 Subject: [PATCH 11/29] add top_k for es full text search (#9963) --- .../rag/datasource/vdb/elasticsearch/elasticsearch_vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py b/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py index 052a187225..c62042af80 100644 --- a/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py +++ b/api/core/rag/datasource/vdb/elasticsearch/elasticsearch_vector.py @@ -142,7 +142,7 @@ class ElasticSearchVector(BaseVector): def search_by_full_text(self, query: str, **kwargs: Any) -> list[Document]: query_str = {"match": {Field.CONTENT_KEY.value: query}} - results = self._client.search(index=self._collection_name, query=query_str) + results = self._client.search(index=self._collection_name, query=query_str, size=kwargs.get("top_k", 4)) docs = [] for hit in results["hits"]["hits"]: docs.append( From b6d045cebfd610de0d82c954fd75aefa9889a5e9 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:55:14 +0800 Subject: [PATCH 12/29] fix: Fix page logout issue due to refresh-token (#9970) --- web/hooks/use-refresh-token.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/hooks/use-refresh-token.ts b/web/hooks/use-refresh-token.ts index 293f3159de..53dc4faf00 100644 --- a/web/hooks/use-refresh-token.ts +++ b/web/hooks/use-refresh-token.ts @@ -41,6 +41,7 @@ const useRefreshToken = () => { return new Error('No access token or refresh token found') } if (localStorage?.getItem('is_refreshing') === '1') { + clearTimeout(timer.current) timer.current = setTimeout(() => { getNewAccessToken() }, 1000) @@ -61,12 +62,14 @@ const useRefreshToken = () => { localStorage?.setItem('console_token', access_token) localStorage?.setItem('refresh_token', refresh_token) const newTokenExpireTime = getExpireTime(access_token) + clearTimeout(timer.current) timer.current = setTimeout(() => { getNewAccessToken() }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp()) } else { const newTokenExpireTime = getExpireTime(currentAccessToken) + clearTimeout(timer.current) timer.current = setTimeout(() => { getNewAccessToken() }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp()) @@ -74,8 +77,15 @@ const useRefreshToken = () => { return null }, [getExpireTime, getCurrentTimeStamp, handleError]) + const handleVisibilityChange = useCallback(() => { + if (document.visibilityState === 'visible') + getNewAccessToken() + }, []) + useEffect(() => { + window.addEventListener('visibilitychange', handleVisibilityChange) return () => { + window.removeEventListener('visibilitychange', handleVisibilityChange) clearTimeout(timer.current) localStorage?.removeItem('is_refreshing') } From 61ff2fd0f3d5898db093fe31484751058cf5e553 Mon Sep 17 00:00:00 2001 From: ice yao Date: Tue, 29 Oct 2024 10:33:00 +0800 Subject: [PATCH 13/29] chore: Enable tencent cos test to run (#9971) --- .../unit_tests/oss/__mock/tencent_cos.py | 81 +++++++++++++++++++ .../unit_tests/oss/tencent_cos/__init__.py | 0 .../oss/tencent_cos/test_tencent_cos.py | 20 +++++ .../oss/volcengine_tos/test_volcengine_tos.py | 2 - 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 api/tests/unit_tests/oss/__mock/tencent_cos.py create mode 100644 api/tests/unit_tests/oss/tencent_cos/__init__.py create mode 100644 api/tests/unit_tests/oss/tencent_cos/test_tencent_cos.py diff --git a/api/tests/unit_tests/oss/__mock/tencent_cos.py b/api/tests/unit_tests/oss/__mock/tencent_cos.py new file mode 100644 index 0000000000..5189b68e87 --- /dev/null +++ b/api/tests/unit_tests/oss/__mock/tencent_cos.py @@ -0,0 +1,81 @@ +import os +from unittest.mock import MagicMock + +import pytest +from _pytest.monkeypatch import MonkeyPatch +from qcloud_cos import CosS3Client +from qcloud_cos.streambody import StreamBody + +from tests.unit_tests.oss.__mock.base import ( + get_example_bucket, + get_example_data, + get_example_filename, + get_example_filepath, +) + + +class MockTencentCosClass: + def __init__(self, conf, retry=1, session=None): + self.bucket_name = get_example_bucket() + self.key = get_example_filename() + self.content = get_example_data() + self.filepath = get_example_filepath() + self.resp = { + "ETag": "ee8de918d05640145b18f70f4c3aa602", + "Server": "tencent-cos", + "x-cos-hash-crc64ecma": 16749565679157681890, + "x-cos-request-id": "NWU5MDNkYzlfNjRiODJhMDlfMzFmYzhfMTFm****", + } + + def put_object(self, Bucket, Body, Key, EnableMD5=False, **kwargs): # noqa: N803 + assert Bucket == self.bucket_name + assert Key == self.key + assert Body == self.content + return self.resp + + def get_object(self, Bucket, Key, KeySimplifyCheck=True, **kwargs): # noqa: N803 + assert Bucket == self.bucket_name + assert Key == self.key + + mock_stream_body = MagicMock(StreamBody) + mock_raw_stream = MagicMock() + mock_stream_body.get_raw_stream.return_value = mock_raw_stream + mock_raw_stream.read.return_value = self.content + + mock_stream_body.get_stream_to_file = MagicMock() + + def chunk_generator(chunk_size=2): + for i in range(0, len(self.content), chunk_size): + yield self.content[i : i + chunk_size] + + mock_stream_body.get_stream.return_value = chunk_generator(chunk_size=4096) + return {"Body": mock_stream_body} + + def object_exists(self, Bucket, Key): # noqa: N803 + assert Bucket == self.bucket_name + assert Key == self.key + return True + + def delete_object(self, Bucket, Key, **kwargs): # noqa: N803 + assert Bucket == self.bucket_name + assert Key == self.key + self.resp.update({"x-cos-delete-marker": True}) + return self.resp + + +MOCK = os.getenv("MOCK_SWITCH", "false").lower() == "true" + + +@pytest.fixture +def setup_tencent_cos_mock(monkeypatch: MonkeyPatch): + if MOCK: + monkeypatch.setattr(CosS3Client, "__init__", MockTencentCosClass.__init__) + monkeypatch.setattr(CosS3Client, "put_object", MockTencentCosClass.put_object) + monkeypatch.setattr(CosS3Client, "get_object", MockTencentCosClass.get_object) + monkeypatch.setattr(CosS3Client, "object_exists", MockTencentCosClass.object_exists) + monkeypatch.setattr(CosS3Client, "delete_object", MockTencentCosClass.delete_object) + + yield + + if MOCK: + monkeypatch.undo() diff --git a/api/tests/unit_tests/oss/tencent_cos/__init__.py b/api/tests/unit_tests/oss/tencent_cos/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/api/tests/unit_tests/oss/tencent_cos/test_tencent_cos.py b/api/tests/unit_tests/oss/tencent_cos/test_tencent_cos.py new file mode 100644 index 0000000000..303f0493bd --- /dev/null +++ b/api/tests/unit_tests/oss/tencent_cos/test_tencent_cos.py @@ -0,0 +1,20 @@ +from unittest.mock import patch + +import pytest +from qcloud_cos import CosConfig + +from extensions.storage.tencent_cos_storage import TencentCosStorage +from tests.unit_tests.oss.__mock.base import ( + BaseStorageTest, + get_example_bucket, +) +from tests.unit_tests.oss.__mock.tencent_cos import setup_tencent_cos_mock + + +class TestTencentCos(BaseStorageTest): + @pytest.fixture(autouse=True) + def setup_method(self, setup_tencent_cos_mock): + """Executed before each test method.""" + with patch.object(CosConfig, "__init__", return_value=None): + self.storage = TencentCosStorage() + self.storage.bucket_name = get_example_bucket() diff --git a/api/tests/unit_tests/oss/volcengine_tos/test_volcengine_tos.py b/api/tests/unit_tests/oss/volcengine_tos/test_volcengine_tos.py index 9f8aa158a9..5afbc9e8b4 100644 --- a/api/tests/unit_tests/oss/volcengine_tos/test_volcengine_tos.py +++ b/api/tests/unit_tests/oss/volcengine_tos/test_volcengine_tos.py @@ -1,5 +1,3 @@ -from collections.abc import Generator - import pytest from tos import TosClientV2 From eb698963555be44d8812bd53e6189117550aeeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Tue, 29 Oct 2024 10:33:15 +0800 Subject: [PATCH 14/29] fix: allow external knowledge api use simple host (#9966) --- api/services/external_knowledge_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/services/external_knowledge_service.py b/api/services/external_knowledge_service.py index 094772d674..b49738c61c 100644 --- a/api/services/external_knowledge_service.py +++ b/api/services/external_knowledge_service.py @@ -70,7 +70,7 @@ class ExternalDatasetService: endpoint = f"{settings['endpoint']}/retrieval" api_key = settings["api_key"] - if not validators.url(endpoint): + if not validators.url(endpoint, simple_host=True): raise ValueError(f"invalid endpoint: {endpoint}") try: response = httpx.post(endpoint, headers={"Authorization": f"Bearer {api_key}"}) From fc37e654fc26304bed0a1a07bedc23a4e2c68b91 Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:32:50 +0800 Subject: [PATCH 15/29] Feat/support form in conversation (#9980) --- .../base/markdown-blocks/button.tsx | 22 +++ .../components/base/markdown-blocks/form.tsx | 137 ++++++++++++++++++ web/app/components/base/markdown.tsx | 20 +-- 3 files changed, 162 insertions(+), 17 deletions(-) create mode 100644 web/app/components/base/markdown-blocks/button.tsx create mode 100644 web/app/components/base/markdown-blocks/form.tsx diff --git a/web/app/components/base/markdown-blocks/button.tsx b/web/app/components/base/markdown-blocks/button.tsx new file mode 100644 index 0000000000..56647b3bbe --- /dev/null +++ b/web/app/components/base/markdown-blocks/button.tsx @@ -0,0 +1,22 @@ +import { useChatContext } from '@/app/components/base/chat/chat/context' +import Button from '@/app/components/base/button' +import cn from '@/utils/classnames' + +const MarkdownButton = ({ node }: any) => { + const { onSend } = useChatContext() + const variant = node.properties.dataVariant + const message = node.properties.dataMessage + const size = node.properties.dataSize + + return +} +MarkdownButton.displayName = 'MarkdownButton' + +export default MarkdownButton diff --git a/web/app/components/base/markdown-blocks/form.tsx b/web/app/components/base/markdown-blocks/form.tsx new file mode 100644 index 0000000000..f87f2dcd91 --- /dev/null +++ b/web/app/components/base/markdown-blocks/form.tsx @@ -0,0 +1,137 @@ +import Button from '@/app/components/base/button' +import Input from '@/app/components/base/input' +import Textarea from '@/app/components/base/textarea' +import { useChatContext } from '@/app/components/base/chat/chat/context' + +enum DATA_FORMAT { + TEXT = 'text', + JSON = 'json', +} +enum SUPPORTED_TAGS { + LABEL = 'label', + INPUT = 'input', + TEXTAREA = 'textarea', + BUTTON = 'button', +} +enum SUPPORTED_TYPES { + TEXT = 'text', + PASSWORD = 'password', + EMAIL = 'email', + NUMBER = 'number', +} +const MarkdownForm = ({ node }: any) => { + // const supportedTypes = ['text', 'password', 'email', 'number'] + //
+ // + // + // + // + // + // + // + //
+ const { onSend } = useChatContext() + + const getFormValues = (children: any) => { + const formValues: { [key: string]: any } = {} + children.forEach((child: any) => { + if (child.tagName === SUPPORTED_TAGS.INPUT) + formValues[child.properties.name] = child.properties.value + if (child.tagName === SUPPORTED_TAGS.TEXTAREA) + formValues[child.properties.name] = child.properties.value + }) + return formValues + } + const onSubmit = (e: any) => { + e.preventDefault() + const format = node.properties.dataFormat || DATA_FORMAT.TEXT + const result = getFormValues(node.children) + if (format === DATA_FORMAT.JSON) { + onSend?.(JSON.stringify(result)) + } + else { + const textResult = Object.entries(result) + .map(([key, value]) => `${key}: ${value}`) + .join('\n') + onSend?.(textResult) + } + } + return ( +
{ + e.preventDefault() + e.stopPropagation() + }} + > + {node.children.filter((i: any) => i.type === 'element').map((child: any, index: number) => { + if (child.tagName === SUPPORTED_TAGS.LABEL) { + return ( + + ) + } + if (child.tagName === SUPPORTED_TAGS.INPUT) { + if (Object.values(SUPPORTED_TYPES).includes(child.properties.type)) { + return ( + { + e.preventDefault() + child.properties.value = e.target.value + }} + /> + ) + } + else { + return

Unsupported input type: {child.properties.type}

+ } + } + if (child.tagName === SUPPORTED_TAGS.TEXTAREA) { + return ( +