From 1bd1cd6938e1fa160364e1bffa81f24e903e9e2d Mon Sep 17 00:00:00 2001 From: takatost Date: Mon, 19 Feb 2024 16:04:52 +0800 Subject: [PATCH 01/17] fix: event handlers not registered globally (#2479) --- api/app.py | 5 +++-- api/pyproject.toml | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/app.py b/api/app.py index bcf3856c13..ac8bf27df1 100644 --- a/api/app.py +++ b/api/app.py @@ -38,10 +38,11 @@ from extensions import ( from extensions.ext_database import db from extensions.ext_login import login_manager from libs.passport import PassportService - -# DO NOT REMOVE BELOW from services.account_service import AccountService +# DO NOT REMOVE BELOW +from events import event_handlers +from models import account, dataset, model, source, task, tool, tools, web # DO NOT REMOVE ABOVE diff --git a/api/pyproject.toml b/api/pyproject.toml index a04246bb52..3ec759386b 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -3,6 +3,7 @@ requires-python = ">=3.10" [tool.ruff] exclude = [ + "app.py", "__init__.py", "tests/", ] From e031ec9359f32ea291db1a14ca856f7d8461e5e2 Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:00:46 +0800 Subject: [PATCH 02/17] remove: parameters in seeds (#2481) --- .../model_providers/openai/llm/gpt-4-0125-preview.yaml | 3 --- .../model_providers/openai/llm/gpt-4-1106-preview.yaml | 3 --- .../model_runtime/model_providers/openai/llm/gpt-4-32k.yaml | 3 --- .../model_providers/openai/llm/gpt-4-turbo-preview.yaml | 3 --- .../model_providers/openai/llm/gpt-4-vision-preview.yaml | 3 --- api/core/model_runtime/model_providers/openai/llm/gpt-4.yaml | 3 --- 6 files changed, 18 deletions(-) diff --git a/api/core/model_runtime/model_providers/openai/llm/gpt-4-0125-preview.yaml b/api/core/model_runtime/model_providers/openai/llm/gpt-4-0125-preview.yaml index 943a6de321..007cfed0f3 100644 --- a/api/core/model_runtime/model_providers/openai/llm/gpt-4-0125-preview.yaml +++ b/api/core/model_runtime/model_providers/openai/llm/gpt-4-0125-preview.yaml @@ -37,9 +37,6 @@ parameter_rules: the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. required: false - precision: 2 - min: 0 - max: 1 - name: response_format label: zh_Hans: 回复格式 diff --git a/api/core/model_runtime/model_providers/openai/llm/gpt-4-1106-preview.yaml b/api/core/model_runtime/model_providers/openai/llm/gpt-4-1106-preview.yaml index 7f3bdaeac1..f4fa6317af 100644 --- a/api/core/model_runtime/model_providers/openai/llm/gpt-4-1106-preview.yaml +++ b/api/core/model_runtime/model_providers/openai/llm/gpt-4-1106-preview.yaml @@ -37,9 +37,6 @@ parameter_rules: the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. required: false - precision: 2 - min: 0 - max: 1 - name: response_format label: zh_Hans: 回复格式 diff --git a/api/core/model_runtime/model_providers/openai/llm/gpt-4-32k.yaml b/api/core/model_runtime/model_providers/openai/llm/gpt-4-32k.yaml index b1e61317e9..f92173ccfd 100644 --- a/api/core/model_runtime/model_providers/openai/llm/gpt-4-32k.yaml +++ b/api/core/model_runtime/model_providers/openai/llm/gpt-4-32k.yaml @@ -37,9 +37,6 @@ parameter_rules: the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. required: false - precision: 2 - min: 0 - max: 1 - name: response_format label: zh_Hans: 回复格式 diff --git a/api/core/model_runtime/model_providers/openai/llm/gpt-4-turbo-preview.yaml b/api/core/model_runtime/model_providers/openai/llm/gpt-4-turbo-preview.yaml index b109cfc814..c0350ae2c6 100644 --- a/api/core/model_runtime/model_providers/openai/llm/gpt-4-turbo-preview.yaml +++ b/api/core/model_runtime/model_providers/openai/llm/gpt-4-turbo-preview.yaml @@ -37,9 +37,6 @@ parameter_rules: the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. required: false - precision: 2 - min: 0 - max: 1 - name: response_format label: zh_Hans: 回复格式 diff --git a/api/core/model_runtime/model_providers/openai/llm/gpt-4-vision-preview.yaml b/api/core/model_runtime/model_providers/openai/llm/gpt-4-vision-preview.yaml index b7044d2066..a63b608423 100644 --- a/api/core/model_runtime/model_providers/openai/llm/gpt-4-vision-preview.yaml +++ b/api/core/model_runtime/model_providers/openai/llm/gpt-4-vision-preview.yaml @@ -35,9 +35,6 @@ parameter_rules: the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. required: false - precision: 2 - min: 0 - max: 1 - name: response_format label: zh_Hans: 回复格式 diff --git a/api/core/model_runtime/model_providers/openai/llm/gpt-4.yaml b/api/core/model_runtime/model_providers/openai/llm/gpt-4.yaml index 48e8930608..a7a5bf3c86 100644 --- a/api/core/model_runtime/model_providers/openai/llm/gpt-4.yaml +++ b/api/core/model_runtime/model_providers/openai/llm/gpt-4.yaml @@ -37,9 +37,6 @@ parameter_rules: the same result. Determinism is not guaranteed, and you should refer to the system_fingerprint response parameter to monitor changes in the backend. required: false - precision: 2 - min: 0 - max: 1 - name: response_format label: zh_Hans: 回复格式 From 8b49e0ee2a310ba130cea5daa26ee572dc6b4572 Mon Sep 17 00:00:00 2001 From: takatost Date: Mon, 19 Feb 2024 17:13:55 +0800 Subject: [PATCH 03/17] bump version to 0.5.6 (#2482) --- api/config.py | 2 +- docker/docker-compose.yaml | 6 +++--- web/package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/config.py b/api/config.py index 1728a18d0b..b6a8ce1438 100644 --- a/api/config.py +++ b/api/config.py @@ -86,7 +86,7 @@ class Config: # ------------------------ # General Configurations. # ------------------------ - self.CURRENT_VERSION = "0.5.5" + self.CURRENT_VERSION = "0.5.6" self.COMMIT_SHA = get_env('COMMIT_SHA') self.EDITION = "SELF_HOSTED" self.DEPLOY_ENV = get_env('DEPLOY_ENV') diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 1d6f5d7f86..e3a7bdbbe2 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -2,7 +2,7 @@ version: '3.1' services: # API service api: - image: langgenius/dify-api:0.5.5 + image: langgenius/dify-api:0.5.6 restart: always environment: # Startup mode, 'api' starts the API server. @@ -135,7 +135,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:0.5.5 + image: langgenius/dify-api:0.5.6 restart: always environment: # Startup mode, 'worker' starts the Celery worker for processing the queue. @@ -206,7 +206,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:0.5.5 + image: langgenius/dify-web:0.5.6 restart: always environment: EDITION: SELF_HOSTED diff --git a/web/package.json b/web/package.json index 23caa31226..72cc5bc967 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "0.5.5", + "version": "0.5.6", "private": true, "scripts": { "dev": "next dev", From 5ff6b1da07c226a98819bd311299722c38552c87 Mon Sep 17 00:00:00 2001 From: kukuze <67894866+kukuze@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:03:20 +0000 Subject: [PATCH 04/17] =?UTF-8?q?Windows=20local=20deployment=20switch=20"?= =?UTF-8?q?tool=E2=80=9C=20interface=20failed=20(#2483)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/tools/provider/builtin_tool_provider.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/core/tools/provider/builtin_tool_provider.py b/api/core/tools/provider/builtin_tool_provider.py index 0dc29b8a04..93e7d5a39e 100644 --- a/api/core/tools/provider/builtin_tool_provider.py +++ b/api/core/tools/provider/builtin_tool_provider.py @@ -58,7 +58,7 @@ class BuiltinToolProviderController(ToolProviderController): tool_files = list(filter(lambda x: x.endswith(".yaml") and not x.startswith("__"), listdir(tool_path))) tools = [] for tool_file in tool_files: - with open(path.join(tool_path, tool_file)) as f: + with open(path.join(tool_path, tool_file), encoding='utf-8') as f: # get tool name tool_name = tool_file.split(".")[0] tool = load(f.read(), FullLoader) @@ -287,4 +287,4 @@ class BuiltinToolProviderController(ToolProviderController): :param tool_name: the name of the tool, defined in `get_tools` :param credentials: the credentials of the tool """ - pass \ No newline at end of file + pass From eedbe1b7707a58838505305da67777109c53ba0d Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 20 Feb 2024 11:24:27 +0800 Subject: [PATCH 05/17] fix: chat restart (#2488) --- web/app/components/base/chat/chat/hooks.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 896ef74369..00d62241c8 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -146,8 +146,9 @@ export const useChat = ( }, [stopChat, handleResponsing]) const handleRestart = useCallback(() => { - handleStop() connversationId.current = '' + taskIdRef.current = '' + handleStop() const newChatList = config?.opening_statement ? [{ id: `${Date.now()}`, From 297d0f1f3070533ff1c2f5d7c2f5f8ddbf02c664 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 20 Feb 2024 14:49:00 +0800 Subject: [PATCH 06/17] fix: code-based extension (#2490) --- .../app/configuration/config-var/index.tsx | 3 +- .../app/configuration/debug/index.tsx | 2 +- .../toolbox/moderation/form-generation.tsx | 2 +- web/app/components/base/chat/chat/hooks.ts | 2 +- web/utils/model-config.ts | 48 +++++++++---------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/web/app/components/app/configuration/config-var/index.tsx b/web/app/components/app/configuration/config-var/index.tsx index 671ff9746e..9aeb00de8f 100644 --- a/web/app/components/app/configuration/config-var/index.tsx +++ b/web/app/components/app/configuration/config-var/index.tsx @@ -147,6 +147,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar ) => { setShowExternalDataToolModal({ payload: { + type, variable: key, label: name, config, @@ -245,7 +246,7 @@ const ConfigVar: FC = ({ promptVariables, readonly, onPromptVar const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => { setCurrKey(key) - if (type === 'api') { + if (type !== 'string' && type !== 'paragraph' && type !== 'select') { handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables) return } diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx index 194bc1f8b5..68bbdacfad 100644 --- a/web/app/components/app/configuration/debug/index.tsx +++ b/web/app/components/app/configuration/debug/index.tsx @@ -149,7 +149,7 @@ const Debug: FC = ({ } let hasEmptyInput = '' const requiredVars = modelConfig.configs.prompt_variables.filter(({ key, name, required, type }) => { - if (type === 'api') + if (type !== 'string' && type !== 'paragraph' && type !== 'select') return false const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) return res diff --git a/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx b/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx index 15a237efee..daf964447b 100644 --- a/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx +++ b/web/app/components/app/configuration/toolbox/moderation/form-generation.tsx @@ -65,7 +65,7 @@ const FormGeneration: FC = ({ } })} onSelect={item => handleFormChange(form.variable, item.value as string)} - popupClassName='w-[576px]' + popupClassName='w-[576px] !z-[102]' /> ) } diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 00d62241c8..d870cfc79f 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -42,7 +42,7 @@ export const useCheckPromptVariables = () => { } = promptVariablesConfig let hasEmptyInput = '' const requiredVars = promptVariables.filter(({ key, name, required, type }) => { - if (type === 'api') + if (type !== 'string' && type !== 'paragraph' && type !== 'select') return false const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) return res diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts index c465b57dfc..66b8babb21 100644 --- a/web/utils/model-config.ts +++ b/web/utils/model-config.ts @@ -16,7 +16,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | return ['string', item['text-input']] if (item.external_data_tool) - return ['api', item.external_data_tool] + return [item.external_data_tool.type, item.external_data_tool] return ['select', item.select] })() @@ -33,7 +33,17 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | is_context_var, }) } - else if (type === 'api') { + else if (type === 'select') { + promptVariables.push({ + key: content.variable, + name: content.label, + required: content.required, + type: 'select', + options: content.options, + is_context_var, + }) + } + else { promptVariables.push({ key: content.variable, name: content.label, @@ -46,16 +56,6 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | is_context_var, }) } - else { - promptVariables.push({ - key: content.variable, - name: content.label, - required: content.required, - type: 'select', - options: content.options, - is_context_var, - }) - } }) return promptVariables } @@ -79,7 +79,18 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ }, } as any) } - else if (item.type === 'api') { + else if (item.type === 'select') { + userInputs.push({ + select: { + label: item.name, + variable: item.key, + required: item.required !== false, // default true + options: item.options, + default: '', + }, + } as any) + } + else { userInputs.push({ external_data_tool: { label: item.name, @@ -93,17 +104,6 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ }, } as any) } - else { - userInputs.push({ - select: { - label: item.name, - variable: item.key, - required: item.required !== false, // default true - options: item.options, - default: '', - }, - } as any) - } }) return userInputs } From 48bacd01cc6087b3954bc92758c942bec239a422 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:50:57 +0800 Subject: [PATCH 07/17] fix: incorrect tool name (#2489) --- api/core/tools/utils/parser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index 68a8adc11d..af0518d69e 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -115,7 +115,12 @@ class ApiBasedToolSchemaParser: # check if there is a operation id, use $path_$method as operation id if not if 'operationId' not in interface['operation']: - interface['operation']['operationId'] = f'{interface["path"]}_{interface["method"]}' + # remove special characters like / to ensure the operation id is valid ^[a-zA-Z0-9_-]{1,64}$ + path = interface['path'] + if interface['path'].startswith('/'): + path = interface['path'][1:] + path = path.replace('/', '_') + interface['operation']['operationId'] = f'{path}_{interface["method"]}' bundles.append(ApiBasedToolBundle( server_url=server_url + interface['path'], From 207080babcfa14fe53e787112ab95d14d004e967 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 20 Feb 2024 15:16:46 +0800 Subject: [PATCH 08/17] fix: audio to text (#2493) --- api/controllers/console/app/audio.py | 1 - api/services/audio_service.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/controllers/console/app/audio.py b/api/controllers/console/app/audio.py index ac90dfcc8d..d2c1891b65 100644 --- a/api/controllers/console/app/audio.py +++ b/api/controllers/console/app/audio.py @@ -47,7 +47,6 @@ class ChatMessageAudioApi(Resource): tenant_id=app_model.tenant_id, file=file, end_user=None, - promot=app_model.app_model_config.pre_prompt ) return response diff --git a/api/services/audio_service.py b/api/services/audio_service.py index 0161fde7bb..a9fe65df6f 100644 --- a/api/services/audio_service.py +++ b/api/services/audio_service.py @@ -20,7 +20,7 @@ ALLOWED_EXTENSIONS = ['mp3', 'mp4', 'mpeg', 'mpga', 'm4a', 'wav', 'webm', 'amr'] class AudioService: @classmethod - def transcript_asr(cls, tenant_id: str, file: FileStorage, promot: str, end_user: Optional[str] = None): + def transcript_asr(cls, tenant_id: str, file: FileStorage, end_user: Optional[str] = None): if file is None: raise NoAudioUploadedServiceError() From 20b932da9758838557a9b457be344e438a73e70c Mon Sep 17 00:00:00 2001 From: Jyong <76649700+JohnJyong@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:05:09 +0800 Subject: [PATCH 09/17] del doc support (#2494) Co-authored-by: jyong --- api/core/data_loader/file_extractor.py | 4 ++-- api/services/file_service.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/core/data_loader/file_extractor.py b/api/core/data_loader/file_extractor.py index 4a6eb3654d..4741014c96 100644 --- a/api/core/data_loader/file_extractor.py +++ b/api/core/data_loader/file_extractor.py @@ -69,7 +69,7 @@ class FileExtractor: else MarkdownLoader(file_path, autodetect_encoding=True) elif file_extension in ['.htm', '.html']: loader = HTMLLoader(file_path) - elif file_extension in ['.docx', '.doc']: + elif file_extension in ['.docx']: loader = Docx2txtLoader(file_path) elif file_extension == '.csv': loader = CSVLoader(file_path, autodetect_encoding=True) @@ -96,7 +96,7 @@ class FileExtractor: loader = MarkdownLoader(file_path, autodetect_encoding=True) elif file_extension in ['.htm', '.html']: loader = HTMLLoader(file_path) - elif file_extension in ['.docx', '.doc']: + elif file_extension in ['.docx']: loader = Docx2txtLoader(file_path) elif file_extension == '.csv': loader = CSVLoader(file_path, autodetect_encoding=True) diff --git a/api/services/file_service.py b/api/services/file_service.py index 3c56e6808e..215ccf688a 100644 --- a/api/services/file_service.py +++ b/api/services/file_service.py @@ -20,9 +20,9 @@ from services.errors.file import FileTooLargeError, UnsupportedFileTypeError IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'svg'] IMAGE_EXTENSIONS.extend([ext.upper() for ext in IMAGE_EXTENSIONS]) -ALLOWED_EXTENSIONS = ['txt', 'markdown', 'md', 'pdf', 'html', 'htm', 'xlsx', 'docx', 'doc', 'csv'] + IMAGE_EXTENSIONS +ALLOWED_EXTENSIONS = ['txt', 'markdown', 'md', 'pdf', 'html', 'htm', 'xlsx', 'docx', 'csv'] + IMAGE_EXTENSIONS UNSTRUSTURED_ALLOWED_EXTENSIONS = ['txt', 'markdown', 'md', 'pdf', 'html', 'htm', 'xlsx', - 'docx', 'doc', 'csv', 'eml', 'msg', 'pptx', 'ppt', 'xml'] + IMAGE_EXTENSIONS + 'docx', 'csv', 'eml', 'msg', 'pptx', 'ppt', 'xml'] + IMAGE_EXTENSIONS PREVIEW_WORDS_LIMIT = 3000 @@ -162,7 +162,7 @@ class FileService: generator = storage.load(upload_file.key, stream=True) return generator, upload_file.mime_type - + @staticmethod def get_public_image_preview(file_id: str) -> str: upload_file = db.session.query(UploadFile) \ From 25957d917a7c20817dd79be38223142ca345b881 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:07:43 +0800 Subject: [PATCH 10/17] Add default values for optional parameters in API tool and parser (#2491) --- api/core/tools/tool/api_tool.py | 8 ++++++++ api/core/tools/utils/parser.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/api/core/tools/tool/api_tool.py b/api/core/tools/tool/api_tool.py index 30e3f96afd..f6914d3473 100644 --- a/api/core/tools/tool/api_tool.py +++ b/api/core/tools/tool/api_tool.py @@ -127,6 +127,8 @@ class ApiTool(Tool): value = parameters[parameter['name']] elif parameter['required']: raise ToolProviderCredentialValidationError(f"Missing required parameter {parameter['name']}") + else: + value = (parameter.get('schema', {}) or {}).get('default', '') path_params[parameter['name']] = value elif parameter['in'] == 'query': @@ -135,6 +137,8 @@ class ApiTool(Tool): value = parameters[parameter['name']] elif parameter['required']: raise ToolProviderCredentialValidationError(f"Missing required parameter {parameter['name']}") + else: + value = (parameter.get('schema', {}) or {}).get('default', '') params[parameter['name']] = value elif parameter['in'] == 'cookie': @@ -143,6 +147,8 @@ class ApiTool(Tool): value = parameters[parameter['name']] elif parameter['required']: raise ToolProviderCredentialValidationError(f"Missing required parameter {parameter['name']}") + else: + value = (parameter.get('schema', {}) or {}).get('default', '') cookies[parameter['name']] = value elif parameter['in'] == 'header': @@ -151,6 +157,8 @@ class ApiTool(Tool): value = parameters[parameter['name']] elif parameter['required']: raise ToolProviderCredentialValidationError(f"Missing required parameter {parameter['name']}") + else: + value = (parameter.get('schema', {}) or {}).get('default', '') headers[parameter['name']] = value # check if there is a request body and handle it diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index af0518d69e..91c18be3f5 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -60,7 +60,7 @@ class ApiBasedToolSchemaParser: required=parameter.get('required', False), form=ToolParameter.ToolParameterForm.LLM, llm_description=parameter.get('description'), - default=parameter['default'] if 'default' in parameter else None, + default=parameter['schema']['default'] if 'schema' in parameter and 'default' in parameter['schema'] else None, )) # create tool bundle # check if there is a request body From 97e9f523315e1d60b1a43906227697cdb673b8b5 Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:08:01 +0800 Subject: [PATCH 11/17] doc: typo in chat (#2492) --- web/app/components/develop/template/template_chat.en.mdx | 2 +- web/app/components/develop/template/template_chat.zh.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/develop/template/template_chat.en.mdx b/web/app/components/develop/template/template_chat.en.mdx index f3891621f0..7963b38b84 100644 --- a/web/app/components/develop/template/template_chat.en.mdx +++ b/web/app/components/develop/template/template_chat.en.mdx @@ -167,7 +167,7 @@ Chat applications support session persistence, allowing previous chat history to - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/chat-messages' \ diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index 0f17006d3b..72e059eaa2 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -170,7 +170,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/chat-messages' \ From e6cd7b04670ece2732cac9ddc5c2653ddbb76709 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:03:10 +0800 Subject: [PATCH 12/17] feat: increase max tools (#2497) --- web/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/config/index.ts b/web/config/index.ts index 8baf6d9d25..b6b6eb3ee7 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -130,7 +130,7 @@ export const ANNOTATION_DEFAULT = { score_threshold: 0.9, } -export const MAX_TOOLS_NUM = 5 +export const MAX_TOOLS_NUM = 10 export const DEFAULT_AGENT_SETTING = { enabled: false, From ae3ad59b166c963429f42f6b858e77b12cae0fa6 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:03:43 +0800 Subject: [PATCH 13/17] =?UTF-8?q?Refactor=20agent=20history=20organization?= =?UTF-8?q?=20and=20initialization=20of=20agent=20scrat=E2=80=A6=20(#2495)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/core/features/assistant_base_runner.py | 77 ++++++++++++++++++---- api/core/features/assistant_cot_runner.py | 35 ++++++++++ 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/api/core/features/assistant_base_runner.py b/api/core/features/assistant_base_runner.py index c62028eaf0..c4a5767b04 100644 --- a/api/core/features/assistant_base_runner.py +++ b/api/core/features/assistant_base_runner.py @@ -1,5 +1,6 @@ import json import logging +import uuid from datetime import datetime from mimetypes import guess_extension from typing import Optional, Union, cast @@ -20,7 +21,14 @@ from core.file.message_file_parser import FileTransferMethod from core.memory.token_buffer_memory import TokenBufferMemory from core.model_manager import ModelInstance from core.model_runtime.entities.llm_entities import LLMUsage -from core.model_runtime.entities.message_entities import PromptMessage, PromptMessageTool +from core.model_runtime.entities.message_entities import ( + AssistantPromptMessage, + PromptMessage, + PromptMessageTool, + SystemPromptMessage, + ToolPromptMessage, + UserPromptMessage, +) from core.model_runtime.entities.model_entities import ModelFeature from core.model_runtime.model_providers.__base.large_language_model import LargeLanguageModel from core.model_runtime.utils.encoders import jsonable_encoder @@ -77,7 +85,9 @@ class BaseAssistantApplicationRunner(AppRunner): self.message = message self.user_id = user_id self.memory = memory - self.history_prompt_messages = prompt_messages + self.history_prompt_messages = self.organize_agent_history( + prompt_messages=prompt_messages or [] + ) self.variables_pool = variables_pool self.db_variables_pool = db_variables self.model_instance = model_instance @@ -504,17 +514,6 @@ class BaseAssistantApplicationRunner(AppRunner): agent_thought.tool_labels_str = json.dumps(labels) db.session.commit() - - def get_history_prompt_messages(self) -> list[PromptMessage]: - """ - Get history prompt messages - """ - if self.history_prompt_messages is None: - self.history_prompt_messages = db.session.query(PromptMessage).filter( - PromptMessage.message_id == self.message.id, - ).order_by(PromptMessage.position.asc()).all() - - return self.history_prompt_messages def transform_tool_invoke_messages(self, messages: list[ToolInvokeMessage]) -> list[ToolInvokeMessage]: """ @@ -589,4 +588,54 @@ class BaseAssistantApplicationRunner(AppRunner): """ db_variables.updated_at = datetime.utcnow() db_variables.variables_str = json.dumps(jsonable_encoder(tool_variables.pool)) - db.session.commit() \ No newline at end of file + db.session.commit() + + def organize_agent_history(self, prompt_messages: list[PromptMessage]) -> list[PromptMessage]: + """ + Organize agent history + """ + result = [] + # check if there is a system message in the beginning of the conversation + if prompt_messages and isinstance(prompt_messages[0], SystemPromptMessage): + result.append(prompt_messages[0]) + + messages: list[Message] = db.session.query(Message).filter( + Message.conversation_id == self.message.conversation_id, + ).order_by(Message.created_at.asc()).all() + + for message in messages: + result.append(UserPromptMessage(content=message.query)) + agent_thoughts: list[MessageAgentThought] = message.agent_thoughts + for agent_thought in agent_thoughts: + tools = agent_thought.tool + if tools: + tools = tools.split(';') + tool_calls: list[AssistantPromptMessage.ToolCall] = [] + tool_call_response: list[ToolPromptMessage] = [] + tool_inputs = json.loads(agent_thought.tool_input) + for tool in tools: + # generate a uuid for tool call + tool_call_id = str(uuid.uuid4()) + tool_calls.append(AssistantPromptMessage.ToolCall( + id=tool_call_id, + type='function', + function=AssistantPromptMessage.ToolCall.ToolCallFunction( + name=tool, + arguments=json.dumps(tool_inputs.get(tool, {})), + ) + )) + tool_call_response.append(ToolPromptMessage( + content=agent_thought.observation, + name=tool, + tool_call_id=tool_call_id, + )) + + result.extend([ + AssistantPromptMessage( + content=agent_thought.thought, + tool_calls=tool_calls, + ), + *tool_call_response + ]) + + return result \ No newline at end of file diff --git a/api/core/features/assistant_cot_runner.py b/api/core/features/assistant_cot_runner.py index b8d08bb5d3..c8477fb5d9 100644 --- a/api/core/features/assistant_cot_runner.py +++ b/api/core/features/assistant_cot_runner.py @@ -12,6 +12,7 @@ from core.model_runtime.entities.message_entities import ( PromptMessage, PromptMessageTool, SystemPromptMessage, + ToolPromptMessage, UserPromptMessage, ) from core.model_runtime.utils.encoders import jsonable_encoder @@ -39,6 +40,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): self._repack_app_orchestration_config(app_orchestration_config) agent_scratchpad: list[AgentScratchpadUnit] = [] + self._init_agent_scratchpad(agent_scratchpad, self.history_prompt_messages) # check model mode if self.app_orchestration_config.model_config.mode == "completion": @@ -327,6 +329,39 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): continue return instruction + + def _init_agent_scratchpad(self, + agent_scratchpad: list[AgentScratchpadUnit], + messages: list[PromptMessage] + ) -> list[AgentScratchpadUnit]: + """ + init agent scratchpad + """ + current_scratchpad: AgentScratchpadUnit = None + for message in messages: + if isinstance(message, AssistantPromptMessage): + current_scratchpad = AgentScratchpadUnit( + agent_response=message.content, + thought=message.content, + action_str='', + action=None, + observation=None + ) + if message.tool_calls: + try: + current_scratchpad.action = AgentScratchpadUnit.Action( + action_name=message.tool_calls[0].function.name, + action_input=json.loads(message.tool_calls[0].function.arguments) + ) + except: + pass + + agent_scratchpad.append(current_scratchpad) + elif isinstance(message, ToolPromptMessage): + if current_scratchpad: + current_scratchpad.observation = message.content + + return agent_scratchpad def _extract_response_scratchpad(self, content: str) -> AgentScratchpadUnit: """ From 5031d64e28cec579249a0c8efef6a642fdd011c3 Mon Sep 17 00:00:00 2001 From: Chenhe Gu Date: Wed, 21 Feb 2024 03:17:33 +0800 Subject: [PATCH 14/17] Chore/delete chunk decode error alert (#2500) --- .../model_providers/openai_api_compatible/llm/llm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py b/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py index cf90633aa6..d294fcaa9c 100644 --- a/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py +++ b/api/core/model_runtime/model_providers/openai_api_compatible/llm/llm.py @@ -376,7 +376,6 @@ class OAIAPICompatLargeLanguageModel(_CommonOAI_API_Compat, LargeLanguageModel): chunk_json = json.loads(decoded_chunk) # stream ended except json.JSONDecodeError as e: - logger.error(f"decoded_chunk error: {e}, delimiter={delimiter}, decoded_chunk={decoded_chunk}") yield create_final_llm_result_chunk( index=chunk_index + 1, message=AssistantPromptMessage(content=""), From adf2651d1fee2f6f365a8c313a07207e76bc8eae Mon Sep 17 00:00:00 2001 From: Yash_1124 <82636823+Yash-1511@users.noreply.github.com> Date: Wed, 21 Feb 2024 08:12:34 +0530 Subject: [PATCH 15/17] FEAT: Add DuckDuckGo Search Tool for Enhanced Privacy-Focused Search Functionality (#2499) --- .../builtin/duckduckgo/_assets/icon.svg | 1 + .../provider/builtin/duckduckgo/duckduckgo.py | 20 ++++++++++ .../builtin/duckduckgo/duckduckgo.yaml | 10 +++++ .../duckduckgo/tools/duckduckgo_search.py | 40 +++++++++++++++++++ .../duckduckgo/tools/duckduckgo_search.yaml | 23 +++++++++++ api/requirements.txt | 3 +- 6 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 api/core/tools/provider/builtin/duckduckgo/_assets/icon.svg create mode 100644 api/core/tools/provider/builtin/duckduckgo/duckduckgo.py create mode 100644 api/core/tools/provider/builtin/duckduckgo/duckduckgo.yaml create mode 100644 api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.py create mode 100644 api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.yaml diff --git a/api/core/tools/provider/builtin/duckduckgo/_assets/icon.svg b/api/core/tools/provider/builtin/duckduckgo/_assets/icon.svg new file mode 100644 index 0000000000..a816a6b49e --- /dev/null +++ b/api/core/tools/provider/builtin/duckduckgo/_assets/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/api/core/tools/provider/builtin/duckduckgo/duckduckgo.py b/api/core/tools/provider/builtin/duckduckgo/duckduckgo.py new file mode 100644 index 0000000000..3e9b57ece7 --- /dev/null +++ b/api/core/tools/provider/builtin/duckduckgo/duckduckgo.py @@ -0,0 +1,20 @@ +from core.tools.errors import ToolProviderCredentialValidationError +from core.tools.provider.builtin.duckduckgo.tools.duckduckgo_search import DuckDuckGoSearchTool +from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController + + +class DuckDuckGoProvider(BuiltinToolProviderController): + def _validate_credentials(self, credentials: dict) -> None: + try: + DuckDuckGoSearchTool().fork_tool_runtime( + meta={ + "credentials": credentials, + } + ).invoke( + user_id='', + tool_parameters={ + "query": "John Doe", + }, + ) + except Exception as e: + raise ToolProviderCredentialValidationError(str(e)) \ No newline at end of file diff --git a/api/core/tools/provider/builtin/duckduckgo/duckduckgo.yaml b/api/core/tools/provider/builtin/duckduckgo/duckduckgo.yaml new file mode 100644 index 0000000000..8778dde625 --- /dev/null +++ b/api/core/tools/provider/builtin/duckduckgo/duckduckgo.yaml @@ -0,0 +1,10 @@ +identity: + author: Yash Parmar + name: duckduckgo + label: + en_US: DuckDuckGo + zh_Hans: DuckDuckGo + description: + en_US: A privacy-focused search engine. + zh_Hans: 一个注重隐私的搜索引擎。 + icon: icon.svg diff --git a/api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.py b/api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.py new file mode 100644 index 0000000000..6046a18930 --- /dev/null +++ b/api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.py @@ -0,0 +1,40 @@ +from typing import Any + +from langchain.tools import DuckDuckGoSearchRun +from pydantic import BaseModel, Field + +from core.tools.entities.tool_entities import ToolInvokeMessage +from core.tools.tool.builtin_tool import BuiltinTool + + +class DuckDuckGoInput(BaseModel): + query: str = Field(..., description="Search query.") + + +class DuckDuckGoSearchTool(BuiltinTool): + """ + Tool for performing a search using DuckDuckGo search engine. + """ + + def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> ToolInvokeMessage | list[ToolInvokeMessage]: + """ + Invoke the DuckDuckGo search tool. + + Args: + user_id (str): The ID of the user invoking the tool. + tool_parameters (dict[str, Any]): The parameters for the tool invocation. + + Returns: + ToolInvokeMessage | list[ToolInvokeMessage]: The result of the tool invocation. + """ + query = tool_parameters.get('query', '') + + if not query: + return self.create_text_message('Please input query') + + tool = DuckDuckGoSearchRun(args_schema=DuckDuckGoInput) + + result = tool.run(query) + + return self.create_text_message(self.summary(user_id=user_id, content=result)) + \ No newline at end of file diff --git a/api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.yaml b/api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.yaml new file mode 100644 index 0000000000..93c857010c --- /dev/null +++ b/api/core/tools/provider/builtin/duckduckgo/tools/duckduckgo_search.yaml @@ -0,0 +1,23 @@ +identity: + name: duckduckgo_search + author: Yash Parmar + label: + en_US: DuckDuckGo Search + zh_Hans: DuckDuckGo 搜索 +description: + human: + en_US: Perform searches on DuckDuckGo and get results. + zh_Hans: 在 DuckDuckGo 上进行搜索并获取结果。 + llm: Perform searches on DuckDuckGo and get results. +parameters: + - name: query + type: string + required: true + label: + en_US: Query string + zh_Hans: 查询语句 + human_description: + en_US: The search query. + zh_Hans: 搜索查询语句。 + llm_description: Key words for searching + form: llm diff --git a/api/requirements.txt b/api/requirements.txt index af29654248..5881c99903 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -65,4 +65,5 @@ matplotlib~=3.8.2 yfinance~=0.2.35 pydub~=0.25.1 gmpy2~=2.1.5 -numexpr~=2.9.0 \ No newline at end of file +numexpr~=2.9.0 +duckduckgo-search==4.4.3 \ No newline at end of file From edb86f5f5a6f0471a79dece2c3f6e0efa240f793 Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:45:59 +0800 Subject: [PATCH 16/17] Feat/stream react (#2498) --- api/core/features/assistant_cot_runner.py | 309 +++++++++++----------- 1 file changed, 159 insertions(+), 150 deletions(-) diff --git a/api/core/features/assistant_cot_runner.py b/api/core/features/assistant_cot_runner.py index c8477fb5d9..aa4a6797cd 100644 --- a/api/core/features/assistant_cot_runner.py +++ b/api/core/features/assistant_cot_runner.py @@ -133,61 +133,95 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): # recale llm max tokens self.recale_llm_max_tokens(self.model_config, prompt_messages) # invoke model - llm_result: LLMResult = model_instance.invoke_llm( + chunks: Generator[LLMResultChunk, None, None] = model_instance.invoke_llm( prompt_messages=prompt_messages, model_parameters=app_orchestration_config.model_config.parameters, tools=[], stop=app_orchestration_config.model_config.stop, - stream=False, + stream=True, user=self.user_id, callbacks=[], ) # check llm result - if not llm_result: + if not chunks: raise ValueError("failed to invoke llm") - - # get scratchpad - scratchpad = self._extract_response_scratchpad(llm_result.message.content) - agent_scratchpad.append(scratchpad) - - # get llm usage - if llm_result.usage: - increase_usage(llm_usage, llm_result.usage) + usage_dict = {} + react_chunks = self._handle_stream_react(chunks, usage_dict) + scratchpad = AgentScratchpadUnit( + agent_response='', + thought='', + action_str='', + observation='', + action=None + ) + # publish agent thought if it's first iteration if iteration_step == 1: self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER) + for chunk in react_chunks: + if isinstance(chunk, dict): + scratchpad.agent_response += json.dumps(chunk) + try: + if scratchpad.action: + raise Exception("") + scratchpad.action_str = json.dumps(chunk) + scratchpad.action = AgentScratchpadUnit.Action( + action_name=chunk['action'], + action_input=chunk['action_input'] + ) + except: + scratchpad.thought += json.dumps(chunk) + yield LLMResultChunk( + model=self.model_config.model, + prompt_messages=prompt_messages, + system_fingerprint='', + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage( + content=json.dumps(chunk) + ), + usage=None + ) + ) + else: + scratchpad.agent_response += chunk + scratchpad.thought += chunk + yield LLMResultChunk( + model=self.model_config.model, + prompt_messages=prompt_messages, + system_fingerprint='', + delta=LLMResultChunkDelta( + index=0, + message=AssistantPromptMessage( + content=chunk + ), + usage=None + ) + ) + + agent_scratchpad.append(scratchpad) + + # get llm usage + if 'usage' in usage_dict: + increase_usage(llm_usage, usage_dict['usage']) + else: + usage_dict['usage'] = LLMUsage.empty_usage() + self.save_agent_thought(agent_thought=agent_thought, tool_name=scratchpad.action.action_name if scratchpad.action else '', tool_input=scratchpad.action.action_input if scratchpad.action else '', thought=scratchpad.thought, observation='', - answer=llm_result.message.content, + answer=scratchpad.agent_response, messages_ids=[], - llm_usage=llm_result.usage) + llm_usage=usage_dict['usage']) if scratchpad.action and scratchpad.action.action_name.lower() != "final answer": self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER) - # publish agent thought if it's not empty and there is a action - if scratchpad.thought and scratchpad.action: - # check if final answer - if not scratchpad.action.action_name.lower() == "final answer": - yield LLMResultChunk( - model=model_instance.model, - prompt_messages=prompt_messages, - delta=LLMResultChunkDelta( - index=0, - message=AssistantPromptMessage( - content=scratchpad.thought - ), - usage=llm_result.usage, - ), - system_fingerprint='' - ) - if not scratchpad.action: # failed to extract action, return final answer directly final_answer = scratchpad.agent_response or '' @@ -262,7 +296,6 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): # save scratchpad scratchpad.observation = observation - scratchpad.agent_response = llm_result.message.content # save agent thought self.save_agent_thought( @@ -271,7 +304,7 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): tool_input=tool_call_args, thought=None, observation=observation, - answer=llm_result.message.content, + answer=scratchpad.agent_response, messages_ids=message_file_ids, ) self.queue_manager.publish_agent_thought(agent_thought, PublishFrom.APPLICATION_MANAGER) @@ -318,6 +351,97 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): system_fingerprint='' ), PublishFrom.APPLICATION_MANAGER) + def _handle_stream_react(self, llm_response: Generator[LLMResultChunk, None, None], usage: dict) \ + -> Generator[Union[str, dict], None, None]: + def parse_json(json_str): + try: + return json.loads(json_str.strip()) + except: + return json_str + + def extra_json_from_code_block(code_block) -> Generator[Union[dict, str], None, None]: + code_blocks = re.findall(r'```(.*?)```', code_block, re.DOTALL) + if not code_blocks: + return + for block in code_blocks: + json_text = re.sub(r'^[a-zA-Z]+\n', '', block.strip(), flags=re.MULTILINE) + yield parse_json(json_text) + + code_block_cache = '' + code_block_delimiter_count = 0 + in_code_block = False + json_cache = '' + json_quote_count = 0 + in_json = False + got_json = False + + for response in llm_response: + response = response.delta.message.content + if not isinstance(response, str): + continue + + # stream + index = 0 + while index < len(response): + steps = 1 + delta = response[index:index+steps] + if delta == '`': + code_block_cache += delta + code_block_delimiter_count += 1 + else: + if not in_code_block: + if code_block_delimiter_count > 0: + yield code_block_cache + code_block_cache = '' + else: + code_block_cache += delta + code_block_delimiter_count = 0 + + if code_block_delimiter_count == 3: + if in_code_block: + yield from extra_json_from_code_block(code_block_cache) + code_block_cache = '' + + in_code_block = not in_code_block + code_block_delimiter_count = 0 + + if not in_code_block: + # handle single json + if delta == '{': + json_quote_count += 1 + in_json = True + json_cache += delta + elif delta == '}': + json_cache += delta + if json_quote_count > 0: + json_quote_count -= 1 + if json_quote_count == 0: + in_json = False + got_json = True + index += steps + continue + else: + if in_json: + json_cache += delta + + if got_json: + got_json = False + yield parse_json(json_cache) + json_cache = '' + json_quote_count = 0 + in_json = False + + if not in_code_block and not in_json: + yield delta.replace('`', '') + + index += steps + + if code_block_cache: + yield code_block_cache + + if json_cache: + yield parse_json(json_cache) + def _fill_in_inputs_from_external_data_tools(self, instruction: str, inputs: dict) -> str: """ fill in inputs from external data tools @@ -363,121 +487,6 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): return agent_scratchpad - def _extract_response_scratchpad(self, content: str) -> AgentScratchpadUnit: - """ - extract response from llm response - """ - def extra_quotes() -> AgentScratchpadUnit: - agent_response = content - # try to extract all quotes - pattern = re.compile(r'```(.*?)```', re.DOTALL) - quotes = pattern.findall(content) - - # try to extract action from end to start - for i in range(len(quotes) - 1, 0, -1): - """ - 1. use json load to parse action - 2. use plain text `Action: xxx` to parse action - """ - try: - action = json.loads(quotes[i].replace('```', '')) - action_name = action.get("action") - action_input = action.get("action_input") - agent_thought = agent_response.replace(quotes[i], '') - - if action_name and action_input: - return AgentScratchpadUnit( - agent_response=content, - thought=agent_thought, - action_str=quotes[i], - action=AgentScratchpadUnit.Action( - action_name=action_name, - action_input=action_input, - ) - ) - except: - # try to parse action from plain text - action_name = re.findall(r'action: (.*)', quotes[i], re.IGNORECASE) - action_input = re.findall(r'action input: (.*)', quotes[i], re.IGNORECASE) - # delete action from agent response - agent_thought = agent_response.replace(quotes[i], '') - # remove extra quotes - agent_thought = re.sub(r'```(json)*\n*```', '', agent_thought, flags=re.DOTALL) - # remove Action: xxx from agent thought - agent_thought = re.sub(r'Action:.*', '', agent_thought, flags=re.IGNORECASE) - - if action_name and action_input: - return AgentScratchpadUnit( - agent_response=content, - thought=agent_thought, - action_str=quotes[i], - action=AgentScratchpadUnit.Action( - action_name=action_name[0], - action_input=action_input[0], - ) - ) - - def extra_json(): - agent_response = content - # try to extract all json - structures, pair_match_stack = [], [] - started_at, end_at = 0, 0 - for i in range(len(content)): - if content[i] == '{': - pair_match_stack.append(i) - if len(pair_match_stack) == 1: - started_at = i - elif content[i] == '}': - begin = pair_match_stack.pop() - if not pair_match_stack: - end_at = i + 1 - structures.append((content[begin:i+1], (started_at, end_at))) - - # handle the last character - if pair_match_stack: - end_at = len(content) - structures.append((content[pair_match_stack[0]:], (started_at, end_at))) - - for i in range(len(structures), 0, -1): - try: - json_content, (started_at, end_at) = structures[i - 1] - action = json.loads(json_content) - action_name = action.get("action") - action_input = action.get("action_input") - # delete json content from agent response - agent_thought = agent_response[:started_at] + agent_response[end_at:] - # remove extra quotes like ```(json)*\n\n``` - agent_thought = re.sub(r'```(json)*\n*```', '', agent_thought, flags=re.DOTALL) - # remove Action: xxx from agent thought - agent_thought = re.sub(r'Action:.*', '', agent_thought, flags=re.IGNORECASE) - - if action_name and action_input is not None: - return AgentScratchpadUnit( - agent_response=content, - thought=agent_thought, - action_str=json_content, - action=AgentScratchpadUnit.Action( - action_name=action_name, - action_input=action_input, - ) - ) - except: - pass - - agent_scratchpad = extra_quotes() - if agent_scratchpad: - return agent_scratchpad - agent_scratchpad = extra_json() - if agent_scratchpad: - return agent_scratchpad - - return AgentScratchpadUnit( - agent_response=content, - thought=content, - action_str='', - action=None - ) - def _check_cot_prompt_messages(self, mode: Literal["completion", "chat"], agent_prompt_message: AgentPromptEntity, ): @@ -591,15 +600,15 @@ class AssistantCotApplicationRunner(BaseAssistantApplicationRunner): # organize prompt messages if mode == "chat": # override system message - overrided = False + overridden = False prompt_messages = prompt_messages.copy() for prompt_message in prompt_messages: if isinstance(prompt_message, SystemPromptMessage): prompt_message.content = system_message - overrided = True + overridden = True break - if not overrided: + if not overridden: prompt_messages.insert(0, SystemPromptMessage( content=system_message, )) From e15359e589d597fc7ee6c79ec7da1a50f4890fc1 Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 21 Feb 2024 12:03:48 +0800 Subject: [PATCH 17/17] fix: api doc example error (#2505) --- web/app/components/develop/template/template_chat.zh.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/develop/template/template_chat.zh.mdx b/web/app/components/develop/template/template_chat.zh.mdx index 72e059eaa2..71e101e208 100644 --- a/web/app/components/develop/template/template_chat.zh.mdx +++ b/web/app/components/develop/template/template_chat.zh.mdx @@ -170,7 +170,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx' - + ```bash {{ title: 'cURL' }} curl -X POST '${props.appDetail.api_base_url}/chat-messages' \