From 65c875db200970ae083fba64f2851a7cd58d15e7 Mon Sep 17 00:00:00 2001 From: Fangyin Cheng Date: Mon, 9 Sep 2024 10:15:37 +0800 Subject: [PATCH] feat(core): Support higher-order operators (#1984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 谨欣 --- .dockerignore | 5 + dbgpt/_private/config.py | 4 + dbgpt/app/component_configs.py | 14 +- dbgpt/app/initialization/scheduler.py | 4 +- dbgpt/app/operators/__init__.py | 4 + dbgpt/app/operators/converter.py | 186 +++ dbgpt/app/operators/datasource.py | 363 ++++++ dbgpt/app/operators/llm.py | 453 +++++++ dbgpt/app/operators/rag.py | 210 ++++ dbgpt/core/awel/dag/base.py | 52 +- dbgpt/core/awel/flow/__init__.py | 2 + dbgpt/core/awel/flow/base.py | 188 ++- dbgpt/core/awel/flow/flow_factory.py | 131 +- dbgpt/core/awel/flow/ui.py | 39 +- dbgpt/core/awel/operators/base.py | 39 +- dbgpt/core/awel/operators/common_operator.py | 17 + dbgpt/core/awel/tests/conftest.py | 8 +- dbgpt/core/awel/trigger/http_trigger.py | 74 +- dbgpt/core/awel/trigger/trigger_manager.py | 9 +- dbgpt/core/interface/llm.py | 3 + dbgpt/core/interface/message.py | 27 +- .../core/interface/operators/llm_operator.py | 33 +- .../interface/operators/prompt_operator.py | 31 +- dbgpt/core/interface/output_parser.py | 89 +- dbgpt/core/interface/prompt.py | 12 + dbgpt/core/interface/variables.py | 72 +- dbgpt/model/cluster/client.py | 10 +- dbgpt/model/operators/llm_operator.py | 27 +- dbgpt/model/proxy/base.py | 11 + dbgpt/model/utils/chatgpt_utils.py | 9 +- dbgpt/rag/summary/db_summary_client.py | 3 +- dbgpt/serve/agent/resource/datasource.py | 67 +- dbgpt/serve/agent/resource/knowledge.py | 1 + dbgpt/serve/dbgpts/__init__.py | 0 dbgpt/serve/file/api/endpoints.py | 77 +- dbgpt/serve/file/api/schemas.py | 48 +- dbgpt/serve/file/service/service.py | 39 +- dbgpt/serve/flow/api/endpoints.py | 113 +- dbgpt/serve/flow/api/schemas.py | 13 +- dbgpt/serve/flow/api/variables_provider.py | 134 ++ dbgpt/serve/flow/serve.py | 31 +- dbgpt/serve/flow/service/service.py | 73 +- dbgpt/serve/flow/service/variables_service.py | 224 +++- .../en/rag-chat-awel-flow-template.json | 1088 +++++++++++++++++ .../zh/rag-chat-awel-flow-template.json | 1088 +++++++++++++++++ dbgpt/serve/rag/operators/knowledge_space.py | 2 +- dbgpt/storage/metadata/_base_dao.py | 3 + dbgpt/util/net_utils.py | 32 + dbgpt/util/pagination_utils.py | 26 + dbgpt/util/serialization/check.py | 87 ++ dbgpt/util/tests/test_pagination_utils.py | 84 ++ docker/base/build_image.sh | 21 +- examples/awel/awel_flow_ui_components.py | 159 ++- i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.mo | Bin 0 -> 1197 bytes i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.po | 66 + i18n/locales/zh_CN/LC_MESSAGES/dbgpt_app.mo | Bin 382 -> 9596 bytes i18n/locales/zh_CN/LC_MESSAGES/dbgpt_app.po | 445 ++++++- i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.mo | Bin 15271 -> 16041 bytes i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.po | 449 ++++--- i18n/locales/zh_CN/LC_MESSAGES/dbgpt_serve.mo | Bin 2607 -> 4572 bytes i18n/locales/zh_CN/LC_MESSAGES/dbgpt_serve.po | 135 +- setup.py | 33 +- 62 files changed, 6281 insertions(+), 386 deletions(-) create mode 100644 dbgpt/app/operators/__init__.py create mode 100644 dbgpt/app/operators/converter.py create mode 100644 dbgpt/app/operators/datasource.py create mode 100644 dbgpt/app/operators/llm.py create mode 100644 dbgpt/app/operators/rag.py create mode 100644 dbgpt/serve/dbgpts/__init__.py create mode 100644 dbgpt/serve/flow/templates/en/rag-chat-awel-flow-template.json create mode 100644 dbgpt/serve/flow/templates/zh/rag-chat-awel-flow-template.json create mode 100644 dbgpt/util/serialization/check.py create mode 100644 dbgpt/util/tests/test_pagination_utils.py create mode 100644 i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.mo create mode 100644 i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.po diff --git a/.dockerignore b/.dockerignore index 823dcbd59..7e4266596 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,6 @@ +.env +.git/ +./.mypy_cache/ models/ plugins/ pilot/data @@ -5,6 +8,8 @@ pilot/message logs/ venv/ web/node_modules/ +web/.next/ +web/.env docs/node_modules/ build/ docs/build/ diff --git a/dbgpt/_private/config.py b/dbgpt/_private/config.py index 4a9f975f2..35c424029 100644 --- a/dbgpt/_private/config.py +++ b/dbgpt/_private/config.py @@ -335,6 +335,10 @@ class Config(metaclass=Singleton): os.getenv("MULTI_INSTANCE", "False").lower() == "true" ) + self.SCHEDULER_ENABLED = ( + os.getenv("SCHEDULER_ENABLED", "True").lower() == "true" + ) + @property def local_db_manager(self) -> "ConnectorManager": from dbgpt.datasource.manages import ConnectorManager diff --git a/dbgpt/app/component_configs.py b/dbgpt/app/component_configs.py index a8a0f24d1..e50ec5679 100644 --- a/dbgpt/app/component_configs.py +++ b/dbgpt/app/component_configs.py @@ -36,7 +36,7 @@ def initialize_components( system_app.register( DefaultExecutorFactory, max_workers=param.default_thread_pool_size ) - system_app.register(DefaultScheduler) + system_app.register(DefaultScheduler, scheduler_enable=CFG.SCHEDULER_ENABLED) system_app.register_instance(controller) system_app.register(ConnectorManager) @@ -60,6 +60,7 @@ def initialize_components( _initialize_openapi(system_app) # Register serve apps register_serve_apps(system_app, CFG, param.port) + _initialize_operators() def _initialize_model_cache(system_app: SystemApp, port: int): @@ -128,3 +129,14 @@ def _initialize_openapi(system_app: SystemApp): from dbgpt.app.openapi.api_v1.editor.service import EditorService system_app.register(EditorService) + + +def _initialize_operators(): + from dbgpt.app.operators.converter import StringToInteger + from dbgpt.app.operators.datasource import ( + HODatasourceExecutorOperator, + HODatasourceRetrieverOperator, + ) + from dbgpt.app.operators.llm import HOLLMOperator, HOStreamingLLMOperator + from dbgpt.app.operators.rag import HOKnowledgeOperator + from dbgpt.serve.agent.resource.datasource import DatasourceResource diff --git a/dbgpt/app/initialization/scheduler.py b/dbgpt/app/initialization/scheduler.py index 70b7bb71a..36a3107db 100644 --- a/dbgpt/app/initialization/scheduler.py +++ b/dbgpt/app/initialization/scheduler.py @@ -19,12 +19,14 @@ class DefaultScheduler(BaseComponent): system_app: SystemApp, scheduler_delay_ms: int = 5000, scheduler_interval_ms: int = 1000, + scheduler_enable: bool = True, ): super().__init__(system_app) self.system_app = system_app self._scheduler_interval_ms = scheduler_interval_ms self._scheduler_delay_ms = scheduler_delay_ms self._stop_event = threading.Event() + self._scheduler_enable = scheduler_enable def init_app(self, system_app: SystemApp): self.system_app = system_app @@ -39,7 +41,7 @@ class DefaultScheduler(BaseComponent): def _scheduler(self): time.sleep(self._scheduler_delay_ms / 1000) - while not self._stop_event.is_set(): + while self._scheduler_enable and not self._stop_event.is_set(): try: schedule.run_pending() except Exception as e: diff --git a/dbgpt/app/operators/__init__.py b/dbgpt/app/operators/__init__.py new file mode 100644 index 000000000..353336a34 --- /dev/null +++ b/dbgpt/app/operators/__init__.py @@ -0,0 +1,4 @@ +"""Operators package. + +This package contains all higher-order operators that are used to build workflows. +""" diff --git a/dbgpt/app/operators/converter.py b/dbgpt/app/operators/converter.py new file mode 100644 index 000000000..1115e0de4 --- /dev/null +++ b/dbgpt/app/operators/converter.py @@ -0,0 +1,186 @@ +"""Type Converter Operators.""" + +from dbgpt.core.awel import MapOperator +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + IOField, + OperatorCategory, + Parameter, + ViewMetadata, +) +from dbgpt.util.i18n_utils import _ + +_INPUTS_STRING = IOField.build_from( + _("String"), + "string", + str, + description=_("The string to be converted to other types."), +) +_INPUTS_INTEGER = IOField.build_from( + _("Integer"), + "integer", + int, + description=_("The integer to be converted to other types."), +) +_INPUTS_FLOAT = IOField.build_from( + _("Float"), + "float", + float, + description=_("The float to be converted to other types."), +) +_INPUTS_BOOLEAN = IOField.build_from( + _("Boolean"), + "boolean", + bool, + description=_("The boolean to be converted to other types."), +) + +_OUTPUTS_STRING = IOField.build_from( + _("String"), + "string", + str, + description=_("The string converted from other types."), +) +_OUTPUTS_INTEGER = IOField.build_from( + _("Integer"), + "integer", + int, + description=_("The integer converted from other types."), +) +_OUTPUTS_FLOAT = IOField.build_from( + _("Float"), + "float", + float, + description=_("The float converted from other types."), +) +_OUTPUTS_BOOLEAN = IOField.build_from( + _("Boolean"), + "boolean", + bool, + description=_("The boolean converted from other types."), +) + + +class StringToInteger(MapOperator[str, int]): + """Converts a string to an integer.""" + + metadata = ViewMetadata( + label=_("String to Integer"), + name="default_converter_string_to_integer", + description=_("Converts a string to an integer."), + category=OperatorCategory.TYPE_CONVERTER, + parameters=[], + inputs=[_INPUTS_STRING], + outputs=[_OUTPUTS_INTEGER], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + """Create a new StringToInteger operator.""" + super().__init__(map_function=lambda x: int(x), **kwargs) + + +class StringToFloat(MapOperator[str, float]): + """Converts a string to a float.""" + + metadata = ViewMetadata( + label=_("String to Float"), + name="default_converter_string_to_float", + description=_("Converts a string to a float."), + category=OperatorCategory.TYPE_CONVERTER, + parameters=[], + inputs=[_INPUTS_STRING], + outputs=[_OUTPUTS_FLOAT], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + """Create a new StringToFloat operator.""" + super().__init__(map_function=lambda x: float(x), **kwargs) + + +class StringToBoolean(MapOperator[str, bool]): + """Converts a string to a boolean.""" + + metadata = ViewMetadata( + label=_("String to Boolean"), + name="default_converter_string_to_boolean", + description=_("Converts a string to a boolean, true: 'true', '1', 'y'"), + category=OperatorCategory.TYPE_CONVERTER, + parameters=[ + Parameter.build_from( + _("True Values"), + "true_values", + str, + optional=True, + default="true,1,y", + description=_("Comma-separated values that should be treated as True."), + ) + ], + inputs=[_INPUTS_STRING], + outputs=[_OUTPUTS_BOOLEAN], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, true_values: str = "true,1,y", **kwargs): + """Create a new StringToBoolean operator.""" + true_values_list = true_values.split(",") + true_values_list = [x.strip().lower() for x in true_values_list] + super().__init__(map_function=lambda x: x.lower() in true_values_list, **kwargs) + + +class IntegerToString(MapOperator[int, str]): + """Converts an integer to a string.""" + + metadata = ViewMetadata( + label=_("Integer to String"), + name="default_converter_integer_to_string", + description=_("Converts an integer to a string."), + category=OperatorCategory.TYPE_CONVERTER, + parameters=[], + inputs=[_INPUTS_INTEGER], + outputs=[_OUTPUTS_STRING], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + """Create a new IntegerToString operator.""" + super().__init__(map_function=lambda x: str(x), **kwargs) + + +class FloatToString(MapOperator[float, str]): + """Converts a float to a string.""" + + metadata = ViewMetadata( + label=_("Float to String"), + name="default_converter_float_to_string", + description=_("Converts a float to a string."), + category=OperatorCategory.TYPE_CONVERTER, + parameters=[], + inputs=[_INPUTS_FLOAT], + outputs=[_OUTPUTS_STRING], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + """Create a new FloatToString operator.""" + super().__init__(map_function=lambda x: str(x), **kwargs) + + +class BooleanToString(MapOperator[bool, str]): + """Converts a boolean to a string.""" + + metadata = ViewMetadata( + label=_("Boolean to String"), + name="default_converter_boolean_to_string", + description=_("Converts a boolean to a string."), + category=OperatorCategory.TYPE_CONVERTER, + parameters=[], + inputs=[_INPUTS_BOOLEAN], + outputs=[_OUTPUTS_STRING], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + """Create a new BooleanToString operator.""" + super().__init__(map_function=lambda x: str(x), **kwargs) diff --git a/dbgpt/app/operators/datasource.py b/dbgpt/app/operators/datasource.py new file mode 100644 index 000000000..320df8d22 --- /dev/null +++ b/dbgpt/app/operators/datasource.py @@ -0,0 +1,363 @@ +import json +import logging +from typing import List, Optional + +from dbgpt._private.config import Config +from dbgpt.agent.resource.database import DBResource +from dbgpt.core import Chunk +from dbgpt.core.awel import DAGContext, MapOperator +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + IOField, + OperatorCategory, + Parameter, + ViewMetadata, + ui, +) +from dbgpt.core.operators import BaseLLM +from dbgpt.util.i18n_utils import _ +from dbgpt.vis.tags.vis_chart import default_chart_type_prompt + +from .llm import HOContextBody + +logger = logging.getLogger(__name__) + +CFG = Config() + +_DEFAULT_CHART_TYPE = default_chart_type_prompt() + +_DEFAULT_TEMPLATE_EN = """You are a database expert. +Please answer the user's question based on the database selected by the user and some \ +of the available table structure definitions of the database. +Database name: + {db_name} +Table structure definition: + {table_info} + +Constraint: + 1.Please understand the user's intention based on the user's question, and use the \ + given table structure definition to create a grammatically correct {dialect} sql. \ + If sql is not required, answer the user's question directly.. + 2.Always limit the query to a maximum of {max_num_results} results unless the user \ + specifies in the question the specific number of rows of data he wishes to obtain. + 3.You can only use the tables provided in the table structure information to \ + generate sql. If you cannot generate sql based on the provided table structure, \ + please say: "The table structure information provided is not enough to generate \ + sql queries." It is prohibited to fabricate information at will. + 4.Please be careful not to mistake the relationship between tables and columns \ + when generating SQL. + 5.Please check the correctness of the SQL and ensure that the query performance is \ + optimized under correct conditions. + 6.Please choose the best one from the display methods given below for data \ + rendering, and put the type name into the name parameter value that returns the \ + required format. If you cannot find the most suitable one, use 'Table' as the \ + display method. , the available data display methods are as follows: {display_type} + +User Question: + {user_input} +Please think step by step and respond according to the following JSON format: + {response} +Ensure the response is correct json and can be parsed by Python json.loads. +""" + +_DEFAULT_TEMPLATE_ZH = """你是一个数据库专家. +请根据用户选择的数据库和该库的部分可用表结构定义来回答用户问题. +数据库名: + {db_name} +表结构定义: + {table_info} + +约束: + 1. 请根据用户问题理解用户意图,使用给出表结构定义创建一个语法正确的 {dialect} sql,如果不需要 \ + sql,则直接回答用户问题。 + 2. 除非用户在问题中指定了他希望获得的具体数据行数,否则始终将查询限制为最多 {max_num_results} \ + 个结果。 + 3. 只能使用表结构信息中提供的表来生成 sql,如果无法根据提供的表结构中生成 sql ,请说:\ + “提供的表结构信息不足以生成 sql 查询。” 禁止随意捏造信息。 + 4. 请注意生成SQL时不要弄错表和列的关系 + 5. 请检查SQL的正确性,并保证正确的情况下优化查询性能 + 6.请从如下给出的展示方式种选择最优的一种用以进行数据渲染,将类型名称放入返回要求格式的name参数值种\ + ,如果找不到最合适的则使用'Table'作为展示方式,可用数据展示方式如下: {display_type} +用户问题: + {user_input} +请一步步思考并按照以下JSON格式回复: + {response} +确保返回正确的json并且可以被Python json.loads方法解析. +""" +_DEFAULT_TEMPLATE = ( + _DEFAULT_TEMPLATE_EN if CFG.LANGUAGE == "en" else _DEFAULT_TEMPLATE_ZH +) + +_DEFAULT_RESPONSE = json.dumps( + { + "thoughts": "thoughts summary to say to user", + "sql": "SQL Query to run", + "display_type": "Data display method", + }, + ensure_ascii=False, + indent=4, +) + +_PARAMETER_DATASOURCE = Parameter.build_from( + _("Datasource"), + "datasource", + type=DBResource, + description=_("The datasource to retrieve the context"), +) +_PARAMETER_PROMPT_TEMPLATE = Parameter.build_from( + _("Prompt Template"), + "prompt_template", + type=str, + optional=True, + default=_DEFAULT_TEMPLATE, + description=_("The prompt template to build a database prompt"), + ui=ui.DefaultUITextArea(), +) +_PARAMETER_DISPLAY_TYPE = Parameter.build_from( + _("Display Type"), + "display_type", + type=str, + optional=True, + default=_DEFAULT_CHART_TYPE, + description=_("The display type for the data"), + ui=ui.DefaultUITextArea(), +) +_PARAMETER_MAX_NUM_RESULTS = Parameter.build_from( + _("Max Number of Results"), + "max_num_results", + type=int, + optional=True, + default=50, + description=_("The maximum number of results to return"), +) +_PARAMETER_RESPONSE_FORMAT = Parameter.build_from( + _("Response Format"), + "response_format", + type=str, + optional=True, + default=_DEFAULT_RESPONSE, + description=_("The response format, default is a JSON format"), + ui=ui.DefaultUITextArea(), +) + +_PARAMETER_CONTEXT_KEY = Parameter.build_from( + _("Context Key"), + "context_key", + type=str, + optional=True, + default="context", + description=_("The key of the context, it will be used in building the prompt"), +) +_INPUTS_QUESTION = IOField.build_from( + _("User question"), + "query", + str, + description=_("The user question to retrieve table schemas from the datasource"), +) +_OUTPUTS_CONTEXT = IOField.build_from( + _("Retrieved context"), + "context", + HOContextBody, + description=_("The retrieved context from the datasource"), +) + +_INPUTS_SQL_DICT = IOField.build_from( + _("SQL dict"), + "sql_dict", + dict, + description=_("The SQL to be executed wrapped in a dictionary, generated by LLM"), +) +_OUTPUTS_SQL_RESULT = IOField.build_from( + _("SQL result"), + "sql_result", + str, + description=_("The result of the SQL execution"), +) + +_INPUTS_SQL_DICT_LIST = IOField.build_from( + _("SQL dict list"), + "sql_dict_list", + dict, + description=_( + "The SQL list to be executed wrapped in a dictionary, generated by LLM" + ), + is_list=True, +) + + +class GPTVisMixin: + async def save_view_message(self, dag_ctx: DAGContext, view: str): + """Save the view message.""" + await dag_ctx.save_to_share_data(BaseLLM.SHARE_DATA_KEY_MODEL_OUTPUT_VIEW, view) + + +class HODatasourceRetrieverOperator(MapOperator[str, HOContextBody]): + """Retrieve the table schemas from the datasource.""" + + _share_data_key = "__datasource_retriever_chunks__" + + class ChunkMapper(MapOperator[HOContextBody, List[Chunk]]): + async def map(self, context: HOContextBody) -> List[Chunk]: + schema_info = await self.current_dag_context.get_from_share_data( + HODatasourceRetrieverOperator._share_data_key + ) + if isinstance(schema_info, list): + chunks = [Chunk(content=table_info) for table_info in schema_info] + else: + chunks = [Chunk(content=schema_info)] + return chunks + + metadata = ViewMetadata( + label=_("Datasource Retriever Operator"), + name="higher_order_datasource_retriever_operator", + description=_("Retrieve the table schemas from the datasource."), + category=OperatorCategory.DATABASE, + parameters=[ + _PARAMETER_DATASOURCE.new(), + _PARAMETER_PROMPT_TEMPLATE.new(), + _PARAMETER_DISPLAY_TYPE.new(), + _PARAMETER_MAX_NUM_RESULTS.new(), + _PARAMETER_RESPONSE_FORMAT.new(), + _PARAMETER_CONTEXT_KEY.new(), + ], + inputs=[_INPUTS_QUESTION.new()], + outputs=[ + _OUTPUTS_CONTEXT.new(), + IOField.build_from( + _("Retrieved schema chunks"), + "chunks", + Chunk, + is_list=True, + description=_("The retrieved schema chunks from the datasource"), + mappers=[ChunkMapper], + ), + ], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__( + self, + datasource: DBResource, + prompt_template: str = _DEFAULT_TEMPLATE, + display_type: str = _DEFAULT_CHART_TYPE, + max_num_results: int = 50, + response_format: str = _DEFAULT_RESPONSE, + context_key: Optional[str] = "context", + **kwargs, + ): + """Initialize the operator.""" + super().__init__(**kwargs) + self._datasource = datasource + self._prompt_template = prompt_template + self._display_type = display_type + self._max_num_results = max_num_results + self._response_format = response_format + self._context_key = context_key + + async def map(self, question: str) -> HOContextBody: + """Retrieve the context from the datasource.""" + db_name = self._datasource._db_name + dialect = self._datasource.dialect + schema_info = await self.blocking_func_to_async( + self._datasource.get_schema_link, + db=db_name, + question=question, + ) + await self.current_dag_context.save_to_share_data( + self._share_data_key, schema_info + ) + context = self._prompt_template.format( + db_name=db_name, + table_info=schema_info, + dialect=dialect, + max_num_results=self._max_num_results, + display_type=self._display_type, + user_input=question, + response=self._response_format, + ) + + return HOContextBody( + context_key=self._context_key, + context=context, + ) + + +class HODatasourceExecutorOperator(GPTVisMixin, MapOperator[dict, str]): + """Execute the context from the datasource.""" + + metadata = ViewMetadata( + label=_("Datasource Executor Operator"), + name="higher_order_datasource_executor_operator", + description=_("Execute the context from the datasource."), + category=OperatorCategory.DATABASE, + parameters=[_PARAMETER_DATASOURCE.new()], + inputs=[_INPUTS_SQL_DICT.new()], + outputs=[_OUTPUTS_SQL_RESULT.new()], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, datasource: DBResource, **kwargs): + """Initialize the operator.""" + MapOperator.__init__(self, **kwargs) + self._datasource = datasource + + async def map(self, sql_dict: dict) -> str: + """Execute the context from the datasource.""" + from dbgpt.vis.tags.vis_chart import VisChart + + if not isinstance(sql_dict, dict): + raise ValueError( + "The input value of datasource executor should be a dictionary." + ) + vis = VisChart() + sql = sql_dict.get("sql") + if not sql: + return sql_dict.get("thoughts", "No SQL found in the input dictionary.") + data_df = await self._datasource.query_to_df(sql) + view = await vis.display(chart=sql_dict, data_df=data_df) + await self.save_view_message(self.current_dag_context, view) + return view + + +class HODatasourceDashboardOperator(GPTVisMixin, MapOperator[dict, str]): + """Execute the context from the datasource.""" + + metadata = ViewMetadata( + label=_("Datasource Dashboard Operator"), + name="higher_order_datasource_dashboard_operator", + description=_("Execute the context from the datasource."), + category=OperatorCategory.DATABASE, + parameters=[_PARAMETER_DATASOURCE.new()], + inputs=[_INPUTS_SQL_DICT_LIST.new()], + outputs=[_OUTPUTS_SQL_RESULT.new()], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, datasource: DBResource, **kwargs): + """Initialize the operator.""" + MapOperator.__init__(self, **kwargs) + self._datasource = datasource + + async def map(self, sql_dict_list: List[dict]) -> str: + """Execute the context from the datasource.""" + from dbgpt.vis.tags.vis_dashboard import VisDashboard + + if not isinstance(sql_dict_list, list): + raise ValueError( + "The input value of datasource executor should be a list of dictionaries." + ) + vis = VisDashboard() + chart_params = [] + for chart_item in sql_dict_list: + chart_dict = {k: v for k, v in chart_item.items()} + sql = chart_item.get("sql") + try: + data_df = await self._datasource.query_to_df(sql) + chart_dict["data"] = data_df + except Exception as e: + logger.warning(f"Sql execute failed!{str(e)}") + chart_dict["err_msg"] = str(e) + chart_params.append(chart_dict) + view = await vis.display(charts=chart_params) + await self.save_view_message(self.current_dag_context, view) + return view diff --git a/dbgpt/app/operators/llm.py b/dbgpt/app/operators/llm.py new file mode 100644 index 000000000..7ba44cb32 --- /dev/null +++ b/dbgpt/app/operators/llm.py @@ -0,0 +1,453 @@ +from typing import List, Literal, Optional, Tuple, Union, cast + +from dbgpt._private.pydantic import BaseModel, Field +from dbgpt.core import ( + BaseMessage, + ChatPromptTemplate, + LLMClient, + ModelOutput, + ModelRequest, + StorageConversation, +) +from dbgpt.core.awel import ( + DAG, + BaseOperator, + CommonLLMHttpRequestBody, + DAGContext, + DefaultInputContext, + InputOperator, + JoinOperator, + MapOperator, + SimpleCallDataInputSource, + TaskOutput, +) +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + IOField, + OperatorCategory, + OptionValue, + Parameter, + ViewMetadata, + ui, +) +from dbgpt.core.interface.operators.message_operator import ( + BaseConversationOperator, + BufferedConversationMapperOperator, + TokenBufferedConversationMapperOperator, +) +from dbgpt.core.interface.operators.prompt_operator import HistoryPromptBuilderOperator +from dbgpt.model.operators import LLMOperator, StreamingLLMOperator +from dbgpt.serve.conversation.serve import Serve as ConversationServe +from dbgpt.util.i18n_utils import _ +from dbgpt.util.tracer import root_tracer + + +class HOContextBody(BaseModel): + """Higher-order context body.""" + + context_key: str = Field( + "context", + description=_("The context key can be used as the key for formatting prompt."), + ) + context: Union[str, List[str]] = Field( + ..., + description=_("The context."), + ) + + +class BaseHOLLMOperator( + BaseConversationOperator, + JoinOperator[ModelRequest], + LLMOperator, + StreamingLLMOperator, +): + """Higher-order model request builder operator.""" + + def __init__( + self, + prompt_template: ChatPromptTemplate, + model: str = None, + llm_client: Optional[LLMClient] = None, + history_merge_mode: Literal["none", "window", "token"] = "window", + user_message_key: str = "user_input", + history_key: Optional[str] = None, + keep_start_rounds: Optional[int] = None, + keep_end_rounds: Optional[int] = None, + max_token_limit: int = 2048, + **kwargs, + ): + JoinOperator.__init__(self, combine_function=self._join_func, **kwargs) + LLMOperator.__init__(self, llm_client=llm_client, **kwargs) + StreamingLLMOperator.__init__(self, llm_client=llm_client, **kwargs) + + # User must select a history merge mode + self._history_merge_mode = history_merge_mode + self._user_message_key = user_message_key + self._has_history = history_merge_mode != "none" + self._prompt_template = prompt_template + self._model = model + self._history_key = history_key + self._str_history = False + self._keep_start_rounds = keep_start_rounds if self._has_history else 0 + self._keep_end_rounds = keep_end_rounds if self._has_history else 0 + self._max_token_limit = max_token_limit + self._sub_compose_dag: Optional[DAG] = None + + async def _do_run(self, dag_ctx: DAGContext) -> TaskOutput[ModelOutput]: + conv_serve = ConversationServe.get_instance(self.system_app) + self._storage = conv_serve.conv_storage + self._message_storage = conv_serve.message_storage + + _: TaskOutput[ModelRequest] = await JoinOperator._do_run(self, dag_ctx) + dag_ctx.current_task_context.set_task_input( + DefaultInputContext([dag_ctx.current_task_context]) + ) + if dag_ctx.streaming_call: + task_output = await StreamingLLMOperator._do_run(self, dag_ctx) + else: + task_output = await LLMOperator._do_run(self, dag_ctx) + + return task_output + + async def after_dag_end(self, event_loop_task_id: int): + model_output: Optional[ + ModelOutput + ] = await self.current_dag_context.get_from_share_data( + LLMOperator.SHARE_DATA_KEY_MODEL_OUTPUT + ) + model_output_view: Optional[ + str + ] = await self.current_dag_context.get_from_share_data( + LLMOperator.SHARE_DATA_KEY_MODEL_OUTPUT_VIEW + ) + storage_conv = await self.get_storage_conversation() + end_current_round: bool = False + if model_output and storage_conv: + # Save model output message to storage + storage_conv.add_ai_message(model_output.text) + end_current_round = True + if model_output_view and storage_conv: + # Save model output view to storage + storage_conv.add_view_message(model_output_view) + end_current_round = True + if end_current_round: + # End current conversation round and flush to storage + storage_conv.end_current_round() + + async def _join_func(self, req: CommonLLMHttpRequestBody, *args): + dynamic_inputs = [] + for arg in args: + if isinstance(arg, HOContextBody): + dynamic_inputs.append(arg) + # Load and store chat history, default use InMemoryStorage. + storage_conv, history_messages = await self.blocking_func_to_async( + self._build_storage, req + ) + # Save the storage conversation to share data, for the child operators + await self.current_dag_context.save_to_share_data( + self.SHARE_DATA_KEY_STORAGE_CONVERSATION, storage_conv + ) + + user_input = ( + req.messages[-1] if isinstance(req.messages, list) else req.messages + ) + prompt_dict = { + self._user_message_key: user_input, + } + for dynamic_input in dynamic_inputs: + if dynamic_input.context_key in prompt_dict: + raise ValueError( + f"Duplicate context key '{dynamic_input.context_key}' in upstream " + f"operators." + ) + prompt_dict[dynamic_input.context_key] = dynamic_input.context + + call_data = { + "messages": history_messages, + "prompt_dict": prompt_dict, + } + end_node: BaseOperator = cast(BaseOperator, self.sub_compose_dag.leaf_nodes[0]) + # Sub dag, use the same dag context in the parent dag + messages = await end_node.call(call_data, dag_ctx=self.current_dag_context) + model_request = ModelRequest.build_request( + model=req.model, + messages=messages, + context=req.context, + temperature=req.temperature, + max_new_tokens=req.max_new_tokens, + span_id=root_tracer.get_current_span_id(), + echo=False, + ) + if storage_conv: + # Start new round + storage_conv.start_new_round() + storage_conv.add_user_message(user_input) + return model_request + + @property + def sub_compose_dag(self) -> DAG: + if not self._sub_compose_dag: + self._sub_compose_dag = self._build_conversation_composer_dag() + return self._sub_compose_dag + + def _build_storage( + self, req: CommonLLMHttpRequestBody + ) -> Tuple[StorageConversation, List[BaseMessage]]: + # Create a new storage conversation, this will load the conversation from + # storage, so we must do this async + storage_conv: StorageConversation = StorageConversation( + conv_uid=req.conv_uid, + chat_mode=req.chat_mode, + user_name=req.user_name, + sys_code=req.sys_code, + conv_storage=self.storage, + message_storage=self.message_storage, + param_type="", + param_value=req.chat_param, + ) + # Get history messages from storage + history_messages: List[BaseMessage] = storage_conv.get_history_message( + include_system_message=False + ) + + return storage_conv, history_messages + + def _build_conversation_composer_dag(self) -> DAG: + default_dag_variables = self.dag._default_dag_variables if self.dag else None + with DAG( + "dbgpt_awel_app_chat_history_prompt_composer", + default_dag_variables=default_dag_variables, + ) as composer_dag: + input_task = InputOperator(input_source=SimpleCallDataInputSource()) + # History transform task + if self._history_merge_mode == "token": + history_transform_task = TokenBufferedConversationMapperOperator( + model=self._model, + llm_client=self.llm_client, + max_token_limit=self._max_token_limit, + ) + else: + history_transform_task = BufferedConversationMapperOperator( + keep_start_rounds=self._keep_start_rounds, + keep_end_rounds=self._keep_end_rounds, + ) + if self._history_key: + history_key = self._history_key + else: + placeholders = self._prompt_template.get_placeholders() + if not placeholders or len(placeholders) != 1: + raise ValueError( + "The prompt template must have exactly one placeholder if " + "history_key is not provided." + ) + history_key = placeholders[0] + history_prompt_build_task = HistoryPromptBuilderOperator( + prompt=self._prompt_template, + history_key=history_key, + check_storage=False, + save_to_storage=False, + str_history=self._str_history, + ) + # Build composer dag + ( + input_task + >> MapOperator(lambda x: x["messages"]) + >> history_transform_task + >> history_prompt_build_task + ) + ( + input_task + >> MapOperator(lambda x: x["prompt_dict"]) + >> history_prompt_build_task + ) + + return composer_dag + + +_PARAMETER_PROMPT_TEMPLATE = Parameter.build_from( + _("Prompt Template"), + "prompt_template", + ChatPromptTemplate, + description=_("The prompt template for the conversation."), +) +_PARAMETER_MODEL = Parameter.build_from( + _("Model Name"), + "model", + str, + optional=True, + default=None, + description=_("The model name."), +) + +_PARAMETER_LLM_CLIENT = Parameter.build_from( + _("LLM Client"), + "llm_client", + LLMClient, + optional=True, + default=None, + description=_( + "The LLM Client, how to connect to the LLM model, if not provided, it will use" + " the default client deployed by DB-GPT." + ), +) +_PARAMETER_HISTORY_MERGE_MODE = Parameter.build_from( + _("History Message Merge Mode"), + "history_merge_mode", + str, + optional=True, + default="none", + options=[ + OptionValue(label="No History", name="none", value="none"), + OptionValue(label="Message Window", name="window", value="window"), + OptionValue(label="Token Length", name="token", value="token"), + ], + description=_( + "The history merge mode, supports 'none', 'window' and 'token'." + " 'none': no history merge, 'window': merge by conversation window, 'token': " + "merge by token length." + ), + ui=ui.UISelect(), +) +_PARAMETER_USER_MESSAGE_KEY = Parameter.build_from( + _("User Message Key"), + "user_message_key", + str, + optional=True, + default="user_input", + description=_( + "The key of the user message in your prompt, default is 'user_input'." + ), +) +_PARAMETER_HISTORY_KEY = Parameter.build_from( + _("History Key"), + "history_key", + str, + optional=True, + default=None, + description=_( + "The chat history key, with chat history message pass to prompt template, " + "if not provided, it will parse the prompt template to get the key." + ), +) +_PARAMETER_KEEP_START_ROUNDS = Parameter.build_from( + _("Keep Start Rounds"), + "keep_start_rounds", + int, + optional=True, + default=None, + description=_("The start rounds to keep in the chat history."), +) +_PARAMETER_KEEP_END_ROUNDS = Parameter.build_from( + _("Keep End Rounds"), + "keep_end_rounds", + int, + optional=True, + default=None, + description=_("The end rounds to keep in the chat history."), +) +_PARAMETER_MAX_TOKEN_LIMIT = Parameter.build_from( + _("Max Token Limit"), + "max_token_limit", + int, + optional=True, + default=2048, + description=_("The max token limit to keep in the chat history."), +) + +_INPUTS_COMMON_LLM_REQUEST_BODY = IOField.build_from( + _("Common LLM Request Body"), + "common_llm_request_body", + CommonLLMHttpRequestBody, + _("The common LLM request body."), +) +_INPUTS_EXTRA_CONTEXT = IOField.build_from( + _("Extra Context"), + "extra_context", + HOContextBody, + _( + "Extra context for building prompt(Knowledge context, database " + "schema, etc), you can add multiple context." + ), + dynamic=True, +) +_OUTPUTS_MODEL_OUTPUT = IOField.build_from( + _("Model Output"), + "model_output", + ModelOutput, + description=_("The model output."), +) +_OUTPUTS_STREAMING_MODEL_OUTPUT = IOField.build_from( + _("Streaming Model Output"), + "streaming_model_output", + ModelOutput, + is_list=True, + description=_("The streaming model output."), +) + + +class HOLLMOperator(BaseHOLLMOperator): + metadata = ViewMetadata( + label=_("LLM Operator"), + name="higher_order_llm_operator", + category=OperatorCategory.LLM, + description=_( + "High-level LLM operator, supports multi-round conversation " + "(conversation window, token length and no multi-round)." + ), + parameters=[ + _PARAMETER_PROMPT_TEMPLATE.new(), + _PARAMETER_MODEL.new(), + _PARAMETER_LLM_CLIENT.new(), + _PARAMETER_HISTORY_MERGE_MODE.new(), + _PARAMETER_USER_MESSAGE_KEY.new(), + _PARAMETER_HISTORY_KEY.new(), + _PARAMETER_KEEP_START_ROUNDS.new(), + _PARAMETER_KEEP_END_ROUNDS.new(), + _PARAMETER_MAX_TOKEN_LIMIT.new(), + ], + inputs=[ + _INPUTS_COMMON_LLM_REQUEST_BODY.new(), + _INPUTS_EXTRA_CONTEXT.new(), + ], + outputs=[ + _OUTPUTS_MODEL_OUTPUT.new(), + ], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class HOStreamingLLMOperator(BaseHOLLMOperator): + metadata = ViewMetadata( + label=_("Streaming LLM Operator"), + name="higher_order_streaming_llm_operator", + category=OperatorCategory.LLM, + description=_( + "High-level streaming LLM operator, supports multi-round conversation " + "(conversation window, token length and no multi-round)." + ), + parameters=[ + _PARAMETER_PROMPT_TEMPLATE.new(), + _PARAMETER_MODEL.new(), + _PARAMETER_LLM_CLIENT.new(), + _PARAMETER_HISTORY_MERGE_MODE.new(), + _PARAMETER_USER_MESSAGE_KEY.new(), + _PARAMETER_HISTORY_KEY.new(), + _PARAMETER_KEEP_START_ROUNDS.new(), + _PARAMETER_KEEP_END_ROUNDS.new(), + _PARAMETER_MAX_TOKEN_LIMIT.new(), + ], + inputs=[ + _INPUTS_COMMON_LLM_REQUEST_BODY.new(), + _INPUTS_EXTRA_CONTEXT.new(), + ], + outputs=[ + _OUTPUTS_STREAMING_MODEL_OUTPUT.new(), + ], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, **kwargs): + super().__init__(**kwargs) diff --git a/dbgpt/app/operators/rag.py b/dbgpt/app/operators/rag.py new file mode 100644 index 000000000..d7fa75b24 --- /dev/null +++ b/dbgpt/app/operators/rag.py @@ -0,0 +1,210 @@ +from typing import List, Optional + +from dbgpt._private.config import Config +from dbgpt.core import Chunk +from dbgpt.core.awel import MapOperator +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + FunctionDynamicOptions, + IOField, + OperatorCategory, + OptionValue, + Parameter, + ViewMetadata, + ui, +) +from dbgpt.serve.rag.retriever.knowledge_space import KnowledgeSpaceRetriever +from dbgpt.util.i18n_utils import _ + +from .llm import HOContextBody + +CFG = Config() + + +def _load_space_name() -> List[OptionValue]: + from dbgpt.serve.rag.models.models import KnowledgeSpaceDao, KnowledgeSpaceEntity + + spaces = KnowledgeSpaceDao().get_knowledge_space(KnowledgeSpaceEntity()) + return [ + OptionValue(label=space.name, name=space.name, value=space.name) + for space in spaces + ] + + +_PARAMETER_CONTEXT_KEY = Parameter.build_from( + _("Context Key"), + "context", + type=str, + optional=True, + default="context", + description=_("The key of the context, it will be used in building the prompt"), +) +_PARAMETER_TOP_K = Parameter.build_from( + _("Top K"), + "top_k", + type=int, + optional=True, + default=5, + description=_("The number of chunks to retrieve"), +) +_PARAMETER_SCORE_THRESHOLD = Parameter.build_from( + _("Minimum Match Score"), + "score_threshold", + type=float, + optional=True, + default=0.3, + description=_( + _( + "The minimum match score for the retrieved chunks, it will be dropped if " + "the match score is less than the threshold" + ) + ), + ui=ui.UISlider(attr=ui.UISlider.UIAttribute(min=0.0, max=1.0, step=0.1)), +) + +_PARAMETER_RE_RANKER_ENABLED = Parameter.build_from( + _("Reranker Enabled"), + "reranker_enabled", + type=bool, + optional=True, + default=None, + description=_("Whether to enable the reranker"), +) +_PARAMETER_RE_RANKER_TOP_K = Parameter.build_from( + _("Reranker Top K"), + "reranker_top_k", + type=int, + optional=True, + default=3, + description=_("The top k for the reranker"), +) + +_INPUTS_QUESTION = IOField.build_from( + _("User question"), + "query", + str, + description=_("The user question to retrieve the knowledge"), +) +_OUTPUTS_CONTEXT = IOField.build_from( + _("Retrieved context"), + "context", + HOContextBody, + description=_("The retrieved context from the knowledge space"), +) + + +class HOKnowledgeOperator(MapOperator[str, HOContextBody]): + _share_data_key = "_higher_order_knowledge_operator_retriever_chunks" + + class ChunkMapper(MapOperator[HOContextBody, List[Chunk]]): + async def map(self, context: HOContextBody) -> List[Chunk]: + chunks = await self.current_dag_context.get_from_share_data( + HOKnowledgeOperator._share_data_key + ) + return chunks + + metadata = ViewMetadata( + label=_("Knowledge Operator"), + name="higher_order_knowledge_operator", + category=OperatorCategory.RAG, + description=_( + _( + "Knowledge Operator, retrieve your knowledge(documents) from knowledge" + " space" + ) + ), + parameters=[ + Parameter.build_from( + _("Knowledge Space Name"), + "knowledge_space", + type=str, + options=FunctionDynamicOptions(func=_load_space_name), + description=_("The name of the knowledge space"), + ), + _PARAMETER_CONTEXT_KEY.new(), + _PARAMETER_TOP_K.new(), + _PARAMETER_SCORE_THRESHOLD.new(), + _PARAMETER_RE_RANKER_ENABLED.new(), + _PARAMETER_RE_RANKER_TOP_K.new(), + ], + inputs=[ + _INPUTS_QUESTION.new(), + ], + outputs=[ + _OUTPUTS_CONTEXT.new(), + IOField.build_from( + _("Chunks"), + "chunks", + Chunk, + is_list=True, + description=_("The retrieved chunks from the knowledge space"), + mappers=[ChunkMapper], + ), + ], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__( + self, + knowledge_space: str, + context_key: Optional[str] = "context", + top_k: Optional[int] = None, + score_threshold: Optional[float] = None, + reranker_enabled: Optional[bool] = None, + reranker_top_k: Optional[int] = None, + **kwargs, + ): + super().__init__(**kwargs) + self._knowledge_space = knowledge_space + self._context_key = context_key + self._top_k = top_k + self._score_threshold = score_threshold + self._reranker_enabled = reranker_enabled + self._reranker_top_k = reranker_top_k + + from dbgpt.rag.embedding.embedding_factory import RerankEmbeddingFactory + from dbgpt.rag.retriever.rerank import RerankEmbeddingsRanker + from dbgpt.serve.rag.models.models import ( + KnowledgeSpaceDao, + KnowledgeSpaceEntity, + ) + + spaces = KnowledgeSpaceDao().get_knowledge_space( + KnowledgeSpaceEntity(name=knowledge_space) + ) + if len(spaces) != 1: + raise Exception(f"invalid space name: {knowledge_space}") + space = spaces[0] + + reranker: Optional[RerankEmbeddingsRanker] = None + + if CFG.RERANK_MODEL and self._reranker_enabled: + reranker_top_k = ( + self._reranker_top_k + if self._reranker_top_k is not None + else CFG.RERANK_TOP_K + ) + rerank_embeddings = RerankEmbeddingFactory.get_instance( + CFG.SYSTEM_APP + ).create() + reranker = RerankEmbeddingsRanker(rerank_embeddings, topk=reranker_top_k) + if self._top_k < reranker_top_k or self._top_k < 20: + # We use reranker, so if the top_k is less than 20, + # we need to set it to 20 + self._top_k = max(reranker_top_k, 20) + + self._space_retriever = KnowledgeSpaceRetriever( + space_id=space.id, + top_k=self._top_k, + rerank=reranker, + ) + + async def map(self, query: str) -> HOContextBody: + chunks = await self._space_retriever.aretrieve_with_scores( + query, self._score_threshold + ) + await self.current_dag_context.save_to_share_data(self._share_data_key, chunks) + return HOContextBody( + context_key=self._context_key, + context=[chunk.content for chunk in chunks], + ) diff --git a/dbgpt/core/awel/dag/base.py b/dbgpt/core/awel/dag/base.py index ffe6a7b0e..2c12b3bad 100644 --- a/dbgpt/core/awel/dag/base.py +++ b/dbgpt/core/awel/dag/base.py @@ -145,6 +145,9 @@ class DAGVar: _executor: Optional[Executor] = None _variables_provider: Optional["VariablesProvider"] = None + # Whether check serializable for AWEL, it will be set to True when running AWEL + # operator in remote environment + _check_serializable: Optional[bool] = None @classmethod def enter_dag(cls, dag) -> None: @@ -257,6 +260,24 @@ class DAGVar: """ cls._variables_provider = variables_provider + @classmethod + def get_check_serializable(cls) -> Optional[bool]: + """Get the check serializable flag. + + Returns: + Optional[bool]: The check serializable flag + """ + return cls._check_serializable + + @classmethod + def set_check_serializable(cls, check_serializable: bool) -> None: + """Set the check serializable flag. + + Args: + check_serializable (bool): The check serializable flag to set + """ + cls._check_serializable = check_serializable + class DAGLifecycle: """The lifecycle of DAG.""" @@ -286,6 +307,7 @@ class DAGNode(DAGLifecycle, DependencyMixin, ViewMixin, ABC): node_name: Optional[str] = None, system_app: Optional[SystemApp] = None, executor: Optional[Executor] = None, + check_serializable: Optional[bool] = None, **kwargs, ) -> None: """Initialize a DAGNode. @@ -311,6 +333,7 @@ class DAGNode(DAGLifecycle, DependencyMixin, ViewMixin, ABC): node_id = self._dag._new_node_id() self._node_id: Optional[str] = node_id self._node_name: Optional[str] = node_name + self._check_serializable = check_serializable if self._dag: self._dag._append_node(self) @@ -486,6 +509,20 @@ class DAGNode(DAGLifecycle, DependencyMixin, ViewMixin, ABC): """Return the string of current DAGNode.""" return self.__repr__() + @classmethod + def _do_check_serializable(cls, obj: Any, obj_name: str = "Object"): + """Check whether the current DAGNode is serializable.""" + from dbgpt.util.serialization.check import check_serializable + + check_serializable(obj, obj_name) + + @property + def check_serializable(self) -> bool: + """Whether check serializable for current DAGNode.""" + if self._check_serializable is not None: + return self._check_serializable or False + return DAGVar.get_check_serializable() or False + def _build_task_key(task_name: str, key: str) -> str: return f"{task_name}___$$$$$$___{key}" @@ -619,6 +656,7 @@ class DAGContext: self._node_name_to_ids: Dict[str, str] = node_name_to_ids self._event_loop_task_id = event_loop_task_id self._dag_variables = dag_variables + self._share_data_lock = asyncio.Lock() @property def _task_outputs(self) -> Dict[str, TaskContext]: @@ -680,8 +718,9 @@ class DAGContext: Returns: Any: The share data, you can cast it to the real type """ - logger.debug(f"Get share data by key {key} from {id(self._share_data)}") - return self._share_data.get(key) + async with self._share_data_lock: + logger.debug(f"Get share data by key {key} from {id(self._share_data)}") + return self._share_data.get(key) async def save_to_share_data( self, key: str, data: Any, overwrite: bool = False @@ -694,10 +733,11 @@ class DAGContext: overwrite (bool): Whether overwrite the share data if the key already exists. Defaults to None. """ - if key in self._share_data and not overwrite: - raise ValueError(f"Share data key {key} already exists") - logger.debug(f"Save share data by key {key} to {id(self._share_data)}") - self._share_data[key] = data + async with self._share_data_lock: + if key in self._share_data and not overwrite: + raise ValueError(f"Share data key {key} already exists") + logger.debug(f"Save share data by key {key} to {id(self._share_data)}") + self._share_data[key] = data async def get_task_share_data(self, task_name: str, key: str) -> Any: """Get share data by task name and key. diff --git a/dbgpt/core/awel/flow/__init__.py b/dbgpt/core/awel/flow/__init__.py index 80db5b7e6..0d4e268c2 100644 --- a/dbgpt/core/awel/flow/__init__.py +++ b/dbgpt/core/awel/flow/__init__.py @@ -10,6 +10,7 @@ from ..util.parameter_util import ( # noqa: F401 VariablesDynamicOptions, ) from .base import ( # noqa: F401 + TAGS_ORDER_HIGH, IOField, OperatorCategory, OperatorType, @@ -33,6 +34,7 @@ __ALL__ = [ "ResourceCategory", "ResourceType", "OperatorType", + "TAGS_ORDER_HIGH", "IOField", "BaseDynamicOptions", "FunctionDynamicOptions", diff --git a/dbgpt/core/awel/flow/base.py b/dbgpt/core/awel/flow/base.py index 314cb2171..99aa77c8b 100644 --- a/dbgpt/core/awel/flow/base.py +++ b/dbgpt/core/awel/flow/base.py @@ -36,10 +36,15 @@ _ALLOWED_TYPES: Dict[str, Type] = { } _BASIC_TYPES = [str, int, float, bool, dict, list, set] +_DYNAMIC_PARAMETER_TYPES = [str, int, float, bool] +DefaultParameterType = Union[str, int, float, bool, None] T = TypeVar("T", bound="ViewMixin") TM = TypeVar("TM", bound="TypeMetadata") +TAGS_ORDER_HIGH = "higher-order" +TAGS_ORDER_FIRST = "first-order" + def _get_type_name(type_: Type[Any]) -> str: """Get the type name of the type. @@ -143,6 +148,8 @@ _OPERATOR_CATEGORY_DETAIL = { "agent": _CategoryDetail("Agent", "The agent operator"), "rag": _CategoryDetail("RAG", "The RAG operator"), "experimental": _CategoryDetail("EXPERIMENTAL", "EXPERIMENTAL operator"), + "database": _CategoryDetail("Database", "Interact with the database"), + "type_converter": _CategoryDetail("Type Converter", "Convert the type"), "example": _CategoryDetail("Example", "Example operator"), } @@ -159,6 +166,8 @@ class OperatorCategory(str, Enum): AGENT = "agent" RAG = "rag" EXPERIMENTAL = "experimental" + DATABASE = "database" + TYPE_CONVERTER = "type_converter" EXAMPLE = "example" def label(self) -> str: @@ -202,6 +211,7 @@ _RESOURCE_CATEGORY_DETAIL = { "embeddings": _CategoryDetail("Embeddings", "The embeddings resource"), "rag": _CategoryDetail("RAG", "The resource"), "vector_store": _CategoryDetail("Vector Store", "The vector store resource"), + "database": _CategoryDetail("Database", "Interact with the database"), "example": _CategoryDetail("Example", "The example resource"), } @@ -219,6 +229,7 @@ class ResourceCategory(str, Enum): EMBEDDINGS = "embeddings" RAG = "rag" VECTOR_STORE = "vector_store" + DATABASE = "database" EXAMPLE = "example" def label(self) -> str: @@ -283,9 +294,6 @@ class ParameterCategory(str, Enum): return cls.RESOURCER -DefaultParameterType = Union[str, int, float, bool, None] - - class TypeMetadata(BaseModel): """The metadata of the type.""" @@ -304,7 +312,23 @@ class TypeMetadata(BaseModel): return self.__class__(**self.model_dump(exclude_defaults=True)) -class Parameter(TypeMetadata, Serializable): +class BaseDynamic(BaseModel): + """The base dynamic field.""" + + dynamic: bool = Field( + default=False, + description="Whether current field is dynamic", + examples=[True, False], + ) + dynamic_minimum: int = Field( + default=0, + description="The minimum count of the dynamic field, only valid when dynamic is" + " True", + examples=[0, 1, 2], + ) + + +class Parameter(BaseDynamic, TypeMetadata, Serializable): """Parameter for build operator.""" label: str = Field( @@ -323,11 +347,6 @@ class Parameter(TypeMetadata, Serializable): description="The category of the parameter", examples=["common", "resource"], ) - # resource_category: Optional[str] = Field( - # default=None, - # description="The category of the resource, just for resource type", - # examples=["llm_client", "common"], - # ) resource_type: ResourceType = Field( default=ResourceType.INSTANCE, description="The type of the resource, just for resource type", @@ -372,32 +391,52 @@ class Parameter(TypeMetadata, Serializable): "value": values.get("value"), "default": values.get("default"), } + is_list = values.get("is_list") or False if type_cls: for k, v in to_handle_values.items(): if v: - handled_v = cls._covert_to_real_type(type_cls, v) + handled_v = cls._covert_to_real_type(type_cls, v, is_list) values[k] = handled_v return values + @model_validator(mode="after") + def check_parameters(self) -> "Parameter": + """Check the parameters.""" + if self.dynamic and not self.is_list: + raise FlowMetadataException("Dynamic parameter must be list.") + if self.dynamic and self.dynamic_minimum < 0: + raise FlowMetadataException( + "Dynamic minimum must be greater then or equal to 0." + ) + return self + @classmethod - def _covert_to_real_type(cls, type_cls: str, v: Any) -> Any: - if type_cls and v is not None: - typed_value: Any = v + def _covert_to_real_type(cls, type_cls: str, v: Any, is_list: bool) -> Any: + def _parse_single_value(vv: Any) -> Any: + typed_value: Any = vv try: # Try to convert the value to the type. if type_cls == "builtins.str": - typed_value = str(v) + typed_value = str(vv) elif type_cls == "builtins.int": - typed_value = int(v) + typed_value = int(vv) elif type_cls == "builtins.float": - typed_value = float(v) + typed_value = float(vv) elif type_cls == "builtins.bool": - if str(v).lower() in ["false", "0", "", "no", "off"]: + if str(vv).lower() in ["false", "0", "", "no", "off"]: return False - typed_value = bool(v) + typed_value = bool(vv) return typed_value except ValueError: - raise ValidationError(f"Value '{v}' is not valid for type {type_cls}") + raise ValidationError(f"Value '{vv}' is not valid for type {type_cls}") + + if type_cls and v is not None: + if not is_list: + _parse_single_value(v) + else: + if not isinstance(v, list): + raise ValidationError(f"Value '{v}' is not a list.") + return [_parse_single_value(vv) for vv in v] return v def get_typed_value(self) -> Any: @@ -413,11 +452,11 @@ class Parameter(TypeMetadata, Serializable): if is_variables and self.value is not None and isinstance(self.value, str): return VariablesPlaceHolder(self.name, self.value) else: - return self._covert_to_real_type(self.type_cls, self.value) + return self._covert_to_real_type(self.type_cls, self.value, self.is_list) def get_typed_default(self) -> Any: """Get the typed default.""" - return self._covert_to_real_type(self.type_cls, self.default) + return self._covert_to_real_type(self.type_cls, self.default, self.is_list) @classmethod def build_from( @@ -432,6 +471,8 @@ class Parameter(TypeMetadata, Serializable): description: Optional[str] = None, options: Optional[Union[BaseDynamicOptions, List[OptionValue]]] = None, resource_type: ResourceType = ResourceType.INSTANCE, + dynamic: bool = False, + dynamic_minimum: int = 0, alias: Optional[List[str]] = None, ui: Optional[UIComponent] = None, ): @@ -443,6 +484,8 @@ class Parameter(TypeMetadata, Serializable): raise ValueError(f"Default value is missing for optional parameter {name}.") if not optional: default = None + if dynamic and type not in _DYNAMIC_PARAMETER_TYPES: + raise ValueError("Dynamic parameter must be str, int, float or bool.") return cls( label=label, name=name, @@ -456,6 +499,8 @@ class Parameter(TypeMetadata, Serializable): placeholder=placeholder, description=description or label, options=options, + dynamic=dynamic, + dynamic_minimum=dynamic_minimum, alias=alias, ui=ui, ) @@ -499,7 +544,10 @@ class Parameter(TypeMetadata, Serializable): values = self.options.option_values() dict_value["options"] = [value.to_dict() for value in values] else: - dict_value["options"] = [value.to_dict() for value in self.options] + dict_value["options"] = [ + value.to_dict() if not isinstance(value, dict) else value + for value in self.options + ] if self.ui: dict_value["ui"] = self.ui.to_dict() @@ -594,6 +642,17 @@ class Parameter(TypeMetadata, Serializable): value = view_value return {self.name: value} + def new(self: TM) -> TM: + """Copy the metadata.""" + new_obj = self.__class__( + **self.model_dump(exclude_defaults=True, exclude={"ui", "options"}) + ) + if self.ui: + new_obj.ui = self.ui + if self.options: + new_obj.options = self.options + return new_obj + class BaseResource(Serializable, BaseModel): """The base resource.""" @@ -603,6 +662,11 @@ class BaseResource(Serializable, BaseModel): description="The label to display in UI", examples=["LLM Operator", "OpenAI LLM Client"], ) + custom_label: Optional[str] = Field( + None, + description="The custom label to display in UI", + examples=["LLM Operator", "OpenAI LLM Client"], + ) name: str = Field( ..., description="The name of the operator", @@ -636,7 +700,7 @@ class IOFiledType(str, Enum): LIST = "list" -class IOField(Resource): +class IOField(BaseDynamic, Resource): """The input or output field of the operator.""" is_list: bool = Field( @@ -644,6 +708,10 @@ class IOField(Resource): description="Whether current field is list", examples=[True, False], ) + mappers: Optional[List[str]] = Field( + default=None, + description="The mappers of the field, transform the field to the target type", + ) @classmethod def build_from( @@ -653,10 +721,18 @@ class IOField(Resource): type: Type, description: Optional[str] = None, is_list: bool = False, + dynamic: bool = False, + dynamic_minimum: int = 0, + mappers: Optional[Union[Type, List[Type]]] = None, ): """Build the resource from the type.""" type_name = type.__qualname__ type_cls = _get_type_name(type) + # TODO: Check the mapper instance can be created without required + # parameters. + if mappers and not isinstance(mappers, list): + mappers = [mappers] + mappers_cls = [_get_type_name(m) for m in mappers] if mappers else None return cls( label=label, name=name, @@ -664,6 +740,9 @@ class IOField(Resource): type_cls=type_cls, is_list=is_list, description=description or label, + dynamic=dynamic, + dynamic_minimum=dynamic_minimum, + mappers=mappers_cls, ) @@ -808,9 +887,40 @@ class BaseMetadata(BaseResource): split_ids = self.id.split("_") return "_".join(split_ids[:-1]) + def _parse_ui_size(self) -> Optional[str]: + """Parse the ui size.""" + if not self.parameters: + return None + parameters_size = set() + for parameter in self.parameters: + if parameter.ui and parameter.ui.size: + parameters_size.add(parameter.ui.size) + for size in ["large", "middle", "small"]: + if size in parameters_size: + return size + return None + def to_dict(self) -> Dict: """Convert current metadata to json dict.""" + from .ui import _size_to_order + dict_value = model_to_dict(self, exclude={"parameters"}) + tags = dict_value.get("tags") + if not tags: + tags = {"ui_version": "flow2.0"} + elif isinstance(tags, dict) and "ui_version" not in tags: + tags["ui_version"] = "flow2.0" + + parsed_ui_size = self._parse_ui_size() + if parsed_ui_size: + exist_size = tags.get("ui_size") + if not exist_size or _size_to_order(parsed_ui_size) > _size_to_order( + exist_size + ): + # Use the higher order size as current size. + tags["ui_size"] = parsed_ui_size + + dict_value["tags"] = tags dict_value["parameters"] = [ parameter.to_dict() for parameter in self.parameters ] @@ -1036,6 +1146,38 @@ class ViewMetadata(BaseMetadata): values["outputs"] = new_outputs return values + @model_validator(mode="after") + def check_metadata(self) -> "ViewMetadata": + """Check the metadata.""" + if self.inputs: + for field in self.inputs: + if field.mappers: + raise ValueError("Input field can't have mappers.") + dyn_cnt, is_last_field_dynamic = 0, False + for field in self.inputs: + if field.dynamic: + dyn_cnt += 1 + is_last_field_dynamic = True + else: + if is_last_field_dynamic: + raise ValueError("Dynamic field input must be the last field.") + is_last_field_dynamic = False + if dyn_cnt > 1: + raise ValueError("Only one dynamic input field is allowed.") + if self.outputs: + dyn_cnt, is_last_field_dynamic = 0, False + for field in self.outputs: + if field.dynamic: + dyn_cnt += 1 + is_last_field_dynamic = True + else: + if is_last_field_dynamic: + raise ValueError("Dynamic field output must be the last field.") + is_last_field_dynamic = False + if dyn_cnt > 1: + raise ValueError("Only one dynamic output field is allowed.") + return self + def get_operator_key(self) -> str: """Get the operator key.""" if not self.flow_type: diff --git a/dbgpt/core/awel/flow/flow_factory.py b/dbgpt/core/awel/flow/flow_factory.py index 87b828971..9d157a998 100644 --- a/dbgpt/core/awel/flow/flow_factory.py +++ b/dbgpt/core/awel/flow/flow_factory.py @@ -1,10 +1,11 @@ """Build AWEL DAGs from serialized data.""" +import dataclasses import logging import uuid from contextlib import suppress from enum import Enum -from typing import Any, Dict, List, Literal, Optional, Tuple, Type, Union, cast +from typing import Any, Dict, List, Literal, Optional, Type, Union, cast from typing_extensions import Annotated @@ -97,6 +98,12 @@ class FlowNodeData(BaseModel): return ResourceMetadata(**value) raise ValueError("Unable to infer the type for `data`") + def to_dict(self) -> Dict[str, Any]: + """Convert to dict.""" + dict_value = model_to_dict(self, exclude={"data"}) + dict_value["data"] = self.data.to_dict() + return dict_value + class FlowEdgeData(BaseModel): """Edge data in a flow.""" @@ -166,6 +173,12 @@ class FlowData(BaseModel): edges: List[FlowEdgeData] = Field(..., description="Edges in the flow") viewport: FlowPositionData = Field(..., description="Viewport of the flow") + def to_dict(self) -> Dict[str, Any]: + """Convert to dict.""" + dict_value = model_to_dict(self, exclude={"nodes"}) + dict_value["nodes"] = [n.to_dict() for n in self.nodes] + return dict_value + class _VariablesRequestBase(BaseModel): key: str = Field( @@ -518,9 +531,24 @@ class FlowPanel(BaseModel): values["name"] = name return values + def model_dump(self, **kwargs): + """Override the model dump method.""" + exclude = kwargs.get("exclude", set()) + if "flow_dag" not in exclude: + exclude.add("flow_dag") + if "flow_data" not in exclude: + exclude.add("flow_data") + kwargs["exclude"] = exclude + common_dict = super().model_dump(**kwargs) + if self.flow_dag: + common_dict["flow_dag"] = None + if self.flow_data: + common_dict["flow_data"] = self.flow_data.to_dict() + return common_dict + def to_dict(self) -> Dict[str, Any]: """Convert to dict.""" - return model_to_dict(self, exclude={"flow_dag"}) + return model_to_dict(self, exclude={"flow_dag", "flow_data"}) def get_variables_dict(self) -> List[Dict[str, Any]]: """Get the variables dict.""" @@ -538,6 +566,17 @@ class FlowPanel(BaseModel): return [FlowVariables(**v) for v in variables] +@dataclasses.dataclass +class _KeyToNodeItem: + """Key to node item.""" + + key: str + source_order: int + target_order: int + mappers: List[str] + edge_index: int + + class FlowFactory: """Flow factory.""" @@ -553,8 +592,10 @@ class FlowFactory: key_to_operator_nodes: Dict[str, FlowNodeData] = {} key_to_resource_nodes: Dict[str, FlowNodeData] = {} key_to_resource: Dict[str, ResourceMetadata] = {} - key_to_downstream: Dict[str, List[Tuple[str, int, int]]] = {} - key_to_upstream: Dict[str, List[Tuple[str, int, int]]] = {} + # Record current node's downstream + key_to_downstream: Dict[str, List[_KeyToNodeItem]] = {} + # Record current node's upstream + key_to_upstream: Dict[str, List[_KeyToNodeItem]] = {} key_to_upstream_node: Dict[str, List[FlowNodeData]] = {} for node in flow_data.nodes: key = node.id @@ -568,7 +609,12 @@ class FlowFactory: key_to_resource_nodes[key] = node key_to_resource[key] = node.data - for edge in flow_data.edges: + if not key_to_operator_nodes and not key_to_resource_nodes: + raise FlowMetadataException( + "No operator or resource nodes found in the flow." + ) + + for edge_index, edge in enumerate(flow_data.edges): source_key = edge.source target_key = edge.target source_node: FlowNodeData | None = key_to_operator_nodes.get( @@ -588,12 +634,37 @@ class FlowFactory: if source_node.data.is_operator and target_node.data.is_operator: # Operator to operator. + mappers = [] + for i, out in enumerate(source_node.data.outputs): + if i != edge.source_order: + continue + if out.mappers: + # Current edge is a mapper edge, find the mappers. + mappers = out.mappers + # Note: Not support mappers in the inputs of the target node now. + downstream = key_to_downstream.get(source_key, []) - downstream.append((target_key, edge.source_order, edge.target_order)) + downstream.append( + _KeyToNodeItem( + key=target_key, + source_order=edge.source_order, + target_order=edge.target_order, + mappers=mappers, + edge_index=edge_index, + ) + ) key_to_downstream[source_key] = downstream upstream = key_to_upstream.get(target_key, []) - upstream.append((source_key, edge.source_order, edge.target_order)) + upstream.append( + _KeyToNodeItem( + key=source_key, + source_order=edge.source_order, + target_order=edge.target_order, + mappers=mappers, + edge_index=edge_index, + ) + ) key_to_upstream[target_key] = upstream elif not source_node.data.is_operator and target_node.data.is_operator: # Resource to operator. @@ -651,10 +722,10 @@ class FlowFactory: # Sort the keys by the order of the nodes. for key, value in key_to_downstream.items(): # Sort by source_order. - key_to_downstream[key] = sorted(value, key=lambda x: x[1]) + key_to_downstream[key] = sorted(value, key=lambda x: x.source_order) for key, value in key_to_upstream.items(): # Sort by target_order. - key_to_upstream[key] = sorted(value, key=lambda x: x[2]) + key_to_upstream[key] = sorted(value, key=lambda x: x.target_order) sorted_key_to_resource_nodes = list(key_to_resource_nodes.values()) sorted_key_to_resource_nodes = sorted( @@ -752,8 +823,8 @@ class FlowFactory: self, flow_panel: FlowPanel, key_to_tasks: Dict[str, DAGNode], - key_to_downstream: Dict[str, List[Tuple[str, int, int]]], - key_to_upstream: Dict[str, List[Tuple[str, int, int]]], + key_to_downstream: Dict[str, List[_KeyToNodeItem]], + key_to_upstream: Dict[str, List[_KeyToNodeItem]], dag_id: Optional[str] = None, ) -> DAG: """Build the DAG.""" @@ -800,7 +871,8 @@ class FlowFactory: # This upstream has been sorted according to the order in the downstream # So we just need to connect the task to the upstream. - for upstream_key, _, _ in upstream: + for up_item in upstream: + upstream_key = up_item.key # Just one direction. upstream_task = key_to_tasks.get(upstream_key) if not upstream_task: @@ -811,7 +883,13 @@ class FlowFactory: upstream_task.set_node_id(dag._new_node_id()) if upstream_task is None: raise ValueError("Unable to find upstream task.") - upstream_task >> task + tasks = _build_mapper_operators(dag, up_item.mappers) + tasks.append(task) + last_task = upstream_task + for t in tasks: + # Connect the task to the upstream task. + last_task >> t + last_task = t return dag def pre_load_requirements(self, flow_panel: FlowPanel): @@ -918,6 +996,23 @@ def _topological_sort( return key_to_order +def _build_mapper_operators(dag: DAG, mappers: List[str]) -> List[DAGNode]: + from .base import _get_type_cls + + tasks = [] + for mapper in mappers: + try: + mapper_cls = _get_type_cls(mapper) + task = mapper_cls() + if not task._node_id: + task.set_node_id(dag._new_node_id()) + tasks.append(task) + except Exception as e: + err_msg = f"Unable to build mapper task: {mapper}, error: {e}" + raise FlowMetadataException(err_msg) + return tasks + + def fill_flow_panel(flow_panel: FlowPanel): """Fill the flow panel with the latest metadata. @@ -943,11 +1038,19 @@ def fill_flow_panel(flow_panel: FlowPanel): new_param = input_parameters[i.name] i.label = new_param.label i.description = new_param.description + i.dynamic = new_param.dynamic + i.is_list = new_param.is_list + i.dynamic_minimum = new_param.dynamic_minimum + i.mappers = new_param.mappers for i in node.data.outputs: if i.name in output_parameters: new_param = output_parameters[i.name] i.label = new_param.label i.description = new_param.description + i.dynamic = new_param.dynamic + i.is_list = new_param.is_list + i.dynamic_minimum = new_param.dynamic_minimum + i.mappers = new_param.mappers else: data = cast(ResourceMetadata, node.data) key = data.get_origin_id() @@ -972,6 +1075,8 @@ def fill_flow_panel(flow_panel: FlowPanel): param.options = new_param.get_dict_options() # type: ignore param.default = new_param.default param.placeholder = new_param.placeholder + param.alias = new_param.alias + param.ui = new_param.ui except (FlowException, ValueError) as e: logger.warning(f"Unable to fill the flow panel: {e}") diff --git a/dbgpt/core/awel/flow/ui.py b/dbgpt/core/awel/flow/ui.py index 928755a20..efe3d05e0 100644 --- a/dbgpt/core/awel/flow/ui.py +++ b/dbgpt/core/awel/flow/ui.py @@ -2,7 +2,7 @@ from typing import Any, Dict, List, Literal, Optional, Union -from dbgpt._private.pydantic import BaseModel, Field, model_to_dict +from dbgpt._private.pydantic import BaseModel, Field, model_to_dict, model_validator from dbgpt.core.interface.serialization import Serializable from .exceptions import FlowUIComponentException @@ -25,6 +25,16 @@ _UI_TYPE = Literal[ "code_editor", ] +_UI_SIZE_TYPE = Literal["large", "middle", "small"] +_SIZE_ORDER = {"large": 6, "middle": 4, "small": 2} + + +def _size_to_order(size: str) -> int: + """Convert size to order.""" + if size not in _SIZE_ORDER: + return -1 + return _SIZE_ORDER[size] + class RefreshableMixin(BaseModel): """Refreshable mixin.""" @@ -81,6 +91,10 @@ class UIComponent(RefreshableMixin, Serializable, BaseModel): ) ui_type: _UI_TYPE = Field(..., description="UI component type") + size: Optional[_UI_SIZE_TYPE] = Field( + None, + description="The size of the component(small, middle, large)", + ) attr: Optional[UIAttribute] = Field( None, @@ -266,6 +280,27 @@ class UITextArea(PanelEditorMixin, UIInput): description="The attributes of the component", ) + @model_validator(mode="after") + def check_size(self) -> "UITextArea": + """Check the size. + + Automatically set the size to large if the max_rows is greater than 10. + """ + attr = self.attr + auto_size = attr.auto_size if attr else None + if not attr or not auto_size or isinstance(auto_size, bool): + return self + max_rows = ( + auto_size.max_rows + if isinstance(auto_size, self.UIAttribute.AutoSize) + else None + ) + size = self.size + if not size and max_rows and max_rows > 10: + # Automatically set the size to large if the max_rows is greater than 10 + self.size = "large" + return self + class UIAutoComplete(UIInput): """Auto complete component.""" @@ -450,7 +485,7 @@ class DefaultUITextArea(UITextArea): attr: Optional[UITextArea.UIAttribute] = Field( default_factory=lambda: UITextArea.UIAttribute( - auto_size=UITextArea.UIAttribute.AutoSize(min_rows=2, max_rows=40) + auto_size=UITextArea.UIAttribute.AutoSize(min_rows=2, max_rows=20) ), description="The attributes of the component", ) diff --git a/dbgpt/core/awel/operators/base.py b/dbgpt/core/awel/operators/base.py index da82d2856..aa4940f35 100644 --- a/dbgpt/core/awel/operators/base.py +++ b/dbgpt/core/awel/operators/base.py @@ -193,12 +193,29 @@ class BaseOperator(DAGNode, ABC, Generic[OUT], metaclass=BaseOperatorMeta): self.incremental_output = bool(kwargs["incremental_output"]) if "output_format" in kwargs: self.output_format = kwargs["output_format"] - self._runner: WorkflowRunner = runner self._dag_ctx: Optional[DAGContext] = None self._can_skip_in_branch = can_skip_in_branch self._variables_provider = variables_provider + def __getstate__(self): + """Customize the pickling process.""" + state = self.__dict__.copy() + if "_runner" in state: + del state["_runner"] + if "_executor" in state: + del state["_executor"] + if "_system_app" in state: + del state["_system_app"] + return state + + def __setstate__(self, state): + """Customize the unpickling process.""" + self.__dict__.update(state) + self._runner = default_runner + self._system_app = DAGVar.get_current_system_app() + self._executor = DAGVar.get_executor() + @property def current_dag_context(self) -> DAGContext: """Return the current DAG context.""" @@ -404,7 +421,11 @@ class BaseOperator(DAGNode, ABC, Generic[OUT], metaclass=BaseOperatorMeta): Args: dag_ctx (DAGContext): The context of the DAG when this node is run. """ - from ...interface.variables import VariablesIdentifier, VariablesPlaceHolder + from ...interface.variables import ( + VariablesIdentifier, + VariablesPlaceHolder, + is_variable_string, + ) if not self._variables_provider: return @@ -415,11 +436,13 @@ class BaseOperator(DAGNode, ABC, Generic[OUT], metaclass=BaseOperatorMeta): resolve_items = [] for item in dag_ctx._dag_variables.items: # TODO: Resolve variables just once? + if not item.value: + continue + if isinstance(item.value, str) and is_variable_string(item.value): + item.value = VariablesPlaceHolder(item.name, item.value) if isinstance(item.value, VariablesPlaceHolder): resolve_tasks.append( - self.blocking_func_to_async( - item.value.parse, self._variables_provider - ) + item.value.async_parse(self._variables_provider) ) resolve_items.append(item) resolved_values = await asyncio.gather(*resolve_tasks) @@ -445,15 +468,13 @@ class BaseOperator(DAGNode, ABC, Generic[OUT], metaclass=BaseOperatorMeta): if dag_provider: # First try to resolve the variable with the DAG variables - resolved_value = await self.blocking_func_to_async( - value.parse, + resolved_value = await value.async_parse( dag_provider, ignore_not_found_error=True, default_identifier_map=default_identifier_map, ) if resolved_value is None: - resolved_value = await self.blocking_func_to_async( - value.parse, + resolved_value = await value.async_parse( self._variables_provider, default_identifier_map=default_identifier_map, ) diff --git a/dbgpt/core/awel/operators/common_operator.py b/dbgpt/core/awel/operators/common_operator.py index f8bc25370..763992323 100644 --- a/dbgpt/core/awel/operators/common_operator.py +++ b/dbgpt/core/awel/operators/common_operator.py @@ -41,6 +41,12 @@ class JoinOperator(BaseOperator, Generic[OUT]): super().__init__(can_skip_in_branch=can_skip_in_branch, **kwargs) if not callable(combine_function): raise ValueError("combine_function must be callable") + + if self.check_serializable: + super()._do_check_serializable( + combine_function, + f"JoinOperator: {self}, combine_function: {combine_function}", + ) self.combine_function = combine_function async def _do_run(self, dag_ctx: DAGContext) -> TaskOutput[OUT]: @@ -83,6 +89,11 @@ class ReduceStreamOperator(BaseOperator, Generic[IN, OUT]): super().__init__(**kwargs) if reduce_function and not callable(reduce_function): raise ValueError("reduce_function must be callable") + if reduce_function and self.check_serializable: + super()._do_check_serializable( + reduce_function, f"Operator: {self}, reduce_function: {reduce_function}" + ) + self.reduce_function = reduce_function async def _do_run(self, dag_ctx: DAGContext) -> TaskOutput[OUT]: @@ -133,6 +144,12 @@ class MapOperator(BaseOperator, Generic[IN, OUT]): super().__init__(**kwargs) if map_function and not callable(map_function): raise ValueError("map_function must be callable") + + if map_function and self.check_serializable: + super()._do_check_serializable( + map_function, f"Operator: {self}, map_function: {map_function}" + ) + self.map_function = map_function async def _do_run(self, dag_ctx: DAGContext) -> TaskOutput[OUT]: diff --git a/dbgpt/core/awel/tests/conftest.py b/dbgpt/core/awel/tests/conftest.py index 607783028..2341b6602 100644 --- a/dbgpt/core/awel/tests/conftest.py +++ b/dbgpt/core/awel/tests/conftest.py @@ -4,12 +4,14 @@ from typing import AsyncIterator, List import pytest import pytest_asyncio +from dbgpt.component import SystemApp + from ...interface.variables import ( StorageVariables, StorageVariablesProvider, VariablesIdentifier, ) -from .. import DefaultWorkflowRunner, InputOperator, SimpleInputSource +from .. import DAGVar, DefaultWorkflowRunner, InputOperator, SimpleInputSource from ..task.task_impl import _is_async_iterator @@ -104,7 +106,9 @@ async def stream_input_nodes(request): @asynccontextmanager async def _create_variables(**kwargs): - vp = StorageVariablesProvider() + sys_app = SystemApp() + DAGVar.set_current_system_app(sys_app) + vp = StorageVariablesProvider(system_app=sys_app) vars = kwargs.get("vars") if vars and isinstance(vars, dict): for param_key, param_var in vars.items(): diff --git a/dbgpt/core/awel/trigger/http_trigger.py b/dbgpt/core/awel/trigger/http_trigger.py index 8f0298297..fd503f566 100644 --- a/dbgpt/core/awel/trigger/http_trigger.py +++ b/dbgpt/core/awel/trigger/http_trigger.py @@ -29,6 +29,7 @@ from dbgpt.util.tracer import root_tracer from ..dag.base import DAG from ..flow import ( + TAGS_ORDER_HIGH, IOField, OperatorCategory, OperatorType, @@ -57,6 +58,8 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) +ENDPOINT_PLACEHOLDER_DAG_ID = "{dag_id}" + class AWELHttpError(RuntimeError): """AWEL Http Error.""" @@ -464,14 +467,11 @@ class HttpTrigger(Trigger): router (APIRouter): The router to mount the trigger. global_prefix (Optional[str], optional): The global prefix of the router. """ - path = ( - join_paths(global_prefix, self._endpoint) - if global_prefix - else self._endpoint - ) + endpoint = self._resolved_endpoint() + path = join_paths(global_prefix, endpoint) if global_prefix else endpoint dynamic_route_function = self._create_route_func() router.api_route( - self._endpoint, + endpoint, methods=self._methods, response_model=self._response_model, status_code=self._status_code, @@ -497,11 +497,9 @@ class HttpTrigger(Trigger): """ from dbgpt.util.fastapi import PriorityAPIRouter - path = ( - join_paths(global_prefix, self._endpoint) - if global_prefix - else self._endpoint - ) + endpoint = self._resolved_endpoint() + + path = join_paths(global_prefix, endpoint) if global_prefix else endpoint dynamic_route_function = self._create_route_func() router = cast(PriorityAPIRouter, app.router) router.add_api_route( @@ -532,17 +530,28 @@ class HttpTrigger(Trigger): """ from fastapi import APIRouter - path = ( - join_paths(global_prefix, self._endpoint) - if global_prefix - else self._endpoint - ) + endpoint = self._resolved_endpoint() + + path = join_paths(global_prefix, endpoint) if global_prefix else endpoint app_router = cast(APIRouter, app.router) for i, r in enumerate(app_router.routes): if r.path_format == path: # type: ignore # TODO, remove with path and methods del app_router.routes[i] + def _resolved_endpoint(self) -> str: + """Get the resolved endpoint. + + Replace the placeholder {dag_id} with the real dag_id. + """ + endpoint = self._endpoint + if ENDPOINT_PLACEHOLDER_DAG_ID not in endpoint: + return endpoint + if not self.dag: + raise AWELHttpError("DAG is not set") + dag_id = self.dag.dag_id + return endpoint.replace(ENDPOINT_PLACEHOLDER_DAG_ID, dag_id) + def _trigger_mode(self) -> str: if ( self._req_body @@ -936,6 +945,16 @@ class StringHttpTrigger(HttpTrigger): class CommonLLMHttpTrigger(HttpTrigger): """Common LLM http trigger for AWEL.""" + class MessagesOutputMapper(MapOperator[CommonLLMHttpRequestBody, str]): + """Messages output mapper.""" + + async def map(self, request_body: CommonLLMHttpRequestBody) -> str: + """Map the request body to messages.""" + if isinstance(request_body.messages, str): + return request_body.messages + else: + raise ValueError("Messages to be transformed is not a string") + metadata = ViewMetadata( label=_("Common LLM Http Trigger"), name="common_llm_http_trigger", @@ -956,20 +975,38 @@ class CommonLLMHttpTrigger(HttpTrigger): "LLM http body" ), ), + IOField.build_from( + _("Request String Messages"), + "request_string_messages", + str, + description=_( + "The request string messages of the API endpoint, parsed from " + "'messages' field of the request body" + ), + mappers=[MessagesOutputMapper], + ), ], parameters=[ - _PARAMETER_ENDPOINT.new(), + Parameter.build_from( + _("API Endpoint"), + "endpoint", + str, + optional=True, + default="/example/" + ENDPOINT_PLACEHOLDER_DAG_ID, + description=_("The API endpoint"), + ), _PARAMETER_METHODS_POST_PUT.new(), _PARAMETER_STREAMING_RESPONSE.new(), _PARAMETER_RESPONSE_BODY.new(), _PARAMETER_MEDIA_TYPE.new(), _PARAMETER_STATUS_CODE.new(), ], + tags={"order": TAGS_ORDER_HIGH}, ) def __init__( self, - endpoint: str, + endpoint: str = "/example/" + ENDPOINT_PLACEHOLDER_DAG_ID, methods: Optional[Union[str, List[str]]] = "POST", streaming_response: bool = False, http_response_body: Optional[Type[BaseHttpBody]] = None, @@ -1203,6 +1240,7 @@ class RequestedParsedOperator(MapOperator[CommonLLMHttpRequestBody, str]): "User input parsed operator, parse the user input from request body and " "return as a string" ), + tags={"order": TAGS_ORDER_HIGH}, ) def __init__(self, key: str = "user_input", **kwargs): diff --git a/dbgpt/core/awel/trigger/trigger_manager.py b/dbgpt/core/awel/trigger/trigger_manager.py index 45b040147..94563226e 100644 --- a/dbgpt/core/awel/trigger/trigger_manager.py +++ b/dbgpt/core/awel/trigger/trigger_manager.py @@ -81,7 +81,8 @@ class HttpTriggerManager(TriggerManager): raise ValueError(f"Current trigger {trigger} not an object of HttpTrigger") trigger_id = trigger.node_id if trigger_id not in self._trigger_map: - path = join_paths(self._router_prefix, trigger._endpoint) + real_endpoint = trigger._resolved_endpoint() + path = join_paths(self._router_prefix, real_endpoint) methods = trigger._methods # Check whether the route is already registered self._register_route_tables(path, methods) @@ -116,9 +117,9 @@ class HttpTriggerManager(TriggerManager): if not app: raise ValueError("System app not initialized") trigger.remove_from_app(app, self._router_prefix) - self._unregister_route_tables( - join_paths(self._router_prefix, trigger._endpoint), trigger._methods - ) + real_endpoint = trigger._resolved_endpoint() + path = join_paths(self._router_prefix, real_endpoint) + self._unregister_route_tables(path, trigger._methods) del self._trigger_map[trigger_id] def _init_app(self, system_app: SystemApp): diff --git a/dbgpt/core/interface/llm.py b/dbgpt/core/interface/llm.py index e6a5d24d4..94de92a03 100644 --- a/dbgpt/core/interface/llm.py +++ b/dbgpt/core/interface/llm.py @@ -195,6 +195,9 @@ class ModelRequest: temperature: Optional[float] = None """The temperature of the model inference.""" + top_p: Optional[float] = None + """The top p of the model inference.""" + max_new_tokens: Optional[int] = None """The maximum number of tokens to generate.""" diff --git a/dbgpt/core/interface/message.py b/dbgpt/core/interface/message.py index 50a7b39e5..f67b83cb8 100755 --- a/dbgpt/core/interface/message.py +++ b/dbgpt/core/interface/message.py @@ -317,6 +317,25 @@ class ModelMessage(BaseModel): """ return _messages_to_str(messages, human_prefix, ai_prefix, system_prefix) + @staticmethod + def parse_user_message(messages: List[ModelMessage]) -> str: + """Parse user message from messages. + + Args: + messages (List[ModelMessage]): The all messages in the conversation. + + Returns: + str: The user message + """ + lass_user_message = None + for message in messages[::-1]: + if message.role == ModelMessageRoleType.HUMAN: + lass_user_message = message.content + break + if not lass_user_message: + raise ValueError("No user message") + return lass_user_message + _SingleRoundMessage = List[BaseMessage] _MultiRoundMessageMapper = Callable[[List[_SingleRoundMessage]], List[BaseMessage]] @@ -1244,9 +1263,11 @@ def _append_view_messages(messages: List[BaseMessage]) -> List[BaseMessage]: content=ai_message.content, index=ai_message.index, round_index=ai_message.round_index, - additional_kwargs=ai_message.additional_kwargs.copy() - if ai_message.additional_kwargs - else {}, + additional_kwargs=( + ai_message.additional_kwargs.copy() + if ai_message.additional_kwargs + else {} + ), ) current_round.append(view_message) return sum(messages_by_round, []) diff --git a/dbgpt/core/interface/operators/llm_operator.py b/dbgpt/core/interface/operators/llm_operator.py index 45863d0a9..628c2f59f 100644 --- a/dbgpt/core/interface/operators/llm_operator.py +++ b/dbgpt/core/interface/operators/llm_operator.py @@ -246,10 +246,16 @@ class BaseLLM: SHARE_DATA_KEY_MODEL_NAME = "share_data_key_model_name" SHARE_DATA_KEY_MODEL_OUTPUT = "share_data_key_model_output" + SHARE_DATA_KEY_MODEL_OUTPUT_VIEW = "share_data_key_model_output_view" - def __init__(self, llm_client: Optional[LLMClient] = None): + def __init__( + self, + llm_client: Optional[LLMClient] = None, + save_model_output: bool = True, + ): """Create a new LLM operator.""" self._llm_client = llm_client + self._save_model_output = save_model_output @property def llm_client(self) -> LLMClient: @@ -262,9 +268,10 @@ class BaseLLM: self, current_dag_context: DAGContext, model_output: ModelOutput ) -> None: """Save the model output to the share data.""" - await current_dag_context.save_to_share_data( - self.SHARE_DATA_KEY_MODEL_OUTPUT, model_output - ) + if self._save_model_output: + await current_dag_context.save_to_share_data( + self.SHARE_DATA_KEY_MODEL_OUTPUT, model_output + ) class BaseLLMOperator(BaseLLM, MapOperator[ModelRequest, ModelOutput], ABC): @@ -276,9 +283,14 @@ class BaseLLMOperator(BaseLLM, MapOperator[ModelRequest, ModelOutput], ABC): This operator will generate a no streaming response. """ - def __init__(self, llm_client: Optional[LLMClient] = None, **kwargs): + def __init__( + self, + llm_client: Optional[LLMClient] = None, + save_model_output: bool = True, + **kwargs, + ): """Create a new LLM operator.""" - super().__init__(llm_client=llm_client) + super().__init__(llm_client=llm_client, save_model_output=save_model_output) MapOperator.__init__(self, **kwargs) async def map(self, request: ModelRequest) -> ModelOutput: @@ -309,13 +321,18 @@ class BaseStreamingLLMOperator( This operator will generate streaming response. """ - def __init__(self, llm_client: Optional[LLMClient] = None, **kwargs): + def __init__( + self, + llm_client: Optional[LLMClient] = None, + save_model_output: bool = True, + **kwargs, + ): """Create a streaming operator for a LLM. Args: llm_client (LLMClient, optional): The LLM client. Defaults to None. """ - super().__init__(llm_client=llm_client) + super().__init__(llm_client=llm_client, save_model_output=save_model_output) BaseOperator.__init__(self, **kwargs) async def streamify( # type: ignore diff --git a/dbgpt/core/interface/operators/prompt_operator.py b/dbgpt/core/interface/operators/prompt_operator.py index 7d97230ac..241d8915f 100644 --- a/dbgpt/core/interface/operators/prompt_operator.py +++ b/dbgpt/core/interface/operators/prompt_operator.py @@ -4,14 +4,10 @@ from abc import ABC from typing import Any, Dict, List, Optional, Union from dbgpt._private.pydantic import model_validator -from dbgpt.core import ( - ModelMessage, - ModelMessageRoleType, - ModelOutput, - StorageConversation, -) +from dbgpt.core import ModelMessage, ModelOutput, StorageConversation from dbgpt.core.awel import JoinOperator, MapOperator from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, IOField, OperatorCategory, OperatorType, @@ -42,6 +38,7 @@ from dbgpt.util.i18n_utils import _ name="common_chat_prompt_template", category=ResourceCategory.PROMPT, description=_("The operator to build the prompt with static prompt."), + tags={"order": TAGS_ORDER_HIGH}, parameters=[ Parameter.build_from( label=_("System Message"), @@ -101,9 +98,10 @@ class CommonChatPromptTemplate(ChatPromptTemplate): class BasePromptBuilderOperator(BaseConversationOperator, ABC): """The base prompt builder operator.""" - def __init__(self, check_storage: bool, **kwargs): + def __init__(self, check_storage: bool, save_to_storage: bool = True, **kwargs): """Create a new prompt builder operator.""" super().__init__(check_storage=check_storage, **kwargs) + self._save_to_storage = save_to_storage async def format_prompt( self, prompt: ChatPromptTemplate, prompt_dict: Dict[str, Any] @@ -122,8 +120,9 @@ class BasePromptBuilderOperator(BaseConversationOperator, ABC): pass_kwargs = {k: v for k, v in kwargs.items() if k in prompt.input_variables} messages = prompt.format_messages(**pass_kwargs) model_messages = ModelMessage.from_base_messages(messages) - # Start new round conversation, and save user message to storage - await self.start_new_round_conv(model_messages) + if self._save_to_storage: + # Start new round conversation, and save user message to storage + await self.start_new_round_conv(model_messages) return model_messages async def start_new_round_conv(self, messages: List[ModelMessage]) -> None: @@ -132,13 +131,7 @@ class BasePromptBuilderOperator(BaseConversationOperator, ABC): Args: messages (List[ModelMessage]): The messages. """ - lass_user_message = None - for message in messages[::-1]: - if message.role == ModelMessageRoleType.HUMAN: - lass_user_message = message.content - break - if not lass_user_message: - raise ValueError("No user message") + lass_user_message = ModelMessage.parse_user_message(messages) storage_conv: Optional[ StorageConversation ] = await self.get_storage_conversation() @@ -150,6 +143,8 @@ class BasePromptBuilderOperator(BaseConversationOperator, ABC): async def after_dag_end(self, event_loop_task_id: int): """Execute after the DAG finished.""" + if not self._save_to_storage: + return # Save the storage conversation to storage after the whole DAG finished storage_conv: Optional[ StorageConversation @@ -422,7 +417,7 @@ class HistoryPromptBuilderOperator( self._prompt = prompt self._history_key = history_key self._str_history = str_history - BasePromptBuilderOperator.__init__(self, check_storage=check_storage) + BasePromptBuilderOperator.__init__(self, check_storage=check_storage, **kwargs) JoinOperator.__init__(self, combine_function=self.merge_history, **kwargs) @rearrange_args_by_type @@ -455,7 +450,7 @@ class HistoryDynamicPromptBuilderOperator( """Create a new history dynamic prompt builder operator.""" self._history_key = history_key self._str_history = str_history - BasePromptBuilderOperator.__init__(self, check_storage=check_storage) + BasePromptBuilderOperator.__init__(self, check_storage=check_storage, **kwargs) JoinOperator.__init__(self, combine_function=self.merge_history, **kwargs) @rearrange_args_by_type diff --git a/dbgpt/core/interface/output_parser.py b/dbgpt/core/interface/output_parser.py index faf29bfff..31e91b9f3 100644 --- a/dbgpt/core/interface/output_parser.py +++ b/dbgpt/core/interface/output_parser.py @@ -13,7 +13,13 @@ from typing import Any, TypeVar, Union from dbgpt.core import ModelOutput from dbgpt.core.awel import MapOperator -from dbgpt.core.awel.flow import IOField, OperatorCategory, OperatorType, ViewMetadata +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + IOField, + OperatorCategory, + OperatorType, + ViewMetadata, +) from dbgpt.util.i18n_utils import _ T = TypeVar("T") @@ -271,7 +277,7 @@ class BaseOutputParser(MapOperator[ModelOutput, Any], ABC): if self.current_dag_context.streaming_call: return self.parse_model_stream_resp_ex(input_value, 0) else: - return self.parse_model_nostream_resp(input_value, "###") + return self.parse_model_nostream_resp(input_value, "#####################") def _parse_model_response(response: ResponseTye): @@ -293,6 +299,31 @@ def _parse_model_response(response: ResponseTye): class SQLOutputParser(BaseOutputParser): """Parse the SQL output of an LLM call.""" + metadata = ViewMetadata( + label=_("SQL Output Parser"), + name="default_sql_output_parser", + category=OperatorCategory.OUTPUT_PARSER, + description=_("Parse the SQL output of an LLM call."), + parameters=[], + inputs=[ + IOField.build_from( + _("Model Output"), + "model_output", + ModelOutput, + description=_("The model output of upstream."), + ) + ], + outputs=[ + IOField.build_from( + _("Dict SQL Output"), + "dict", + dict, + description=_("The dict output after parsing."), + ) + ], + tags={"order": TAGS_ORDER_HIGH}, + ) + def __init__(self, is_stream_out: bool = False, **kwargs): """Create a new SQL output parser.""" super().__init__(is_stream_out=is_stream_out, **kwargs) @@ -302,3 +333,57 @@ class SQLOutputParser(BaseOutputParser): model_out_text = super().parse_model_nostream_resp(response, sep) clean_str = super().parse_prompt_response(model_out_text) return json.loads(clean_str, strict=True) + + +class SQLListOutputParser(BaseOutputParser): + """Parse the SQL list output of an LLM call.""" + + metadata = ViewMetadata( + label=_("SQL List Output Parser"), + name="default_sql_list_output_parser", + category=OperatorCategory.OUTPUT_PARSER, + description=_( + "Parse the SQL list output of an LLM call, mostly used for dashboard." + ), + parameters=[], + inputs=[ + IOField.build_from( + _("Model Output"), + "model_output", + ModelOutput, + description=_("The model output of upstream."), + ) + ], + outputs=[ + IOField.build_from( + _("List SQL Output"), + "list", + dict, + is_list=True, + description=_("The list output after parsing."), + ) + ], + tags={"order": TAGS_ORDER_HIGH}, + ) + + def __init__(self, is_stream_out: bool = False, **kwargs): + """Create a new SQL list output parser.""" + super().__init__(is_stream_out=is_stream_out, **kwargs) + + def parse_model_nostream_resp(self, response: ResponseTye, sep: str): + """Parse the output of an LLM call.""" + from dbgpt.util.json_utils import find_json_objects + + model_out_text = super().parse_model_nostream_resp(response, sep) + json_objects = find_json_objects(model_out_text) + json_count = len(json_objects) + if json_count < 1: + raise ValueError("Unable to obtain valid output.") + + parsed_json_list = json_objects[0] + if not isinstance(parsed_json_list, list): + if isinstance(parsed_json_list, dict): + return [parsed_json_list] + else: + raise ValueError("Invalid output format.") + return parsed_json_list diff --git a/dbgpt/core/interface/prompt.py b/dbgpt/core/interface/prompt.py index 99c4b9b10..d1d025d0a 100644 --- a/dbgpt/core/interface/prompt.py +++ b/dbgpt/core/interface/prompt.py @@ -254,6 +254,18 @@ class ChatPromptTemplate(BasePromptTemplate): values["input_variables"] = sorted(input_variables) return values + def get_placeholders(self) -> List[str]: + """Get all placeholders in the prompt template. + + Returns: + List[str]: The placeholders. + """ + placeholders = set() + for message in self.messages: + if isinstance(message, MessagesPlaceholder): + placeholders.add(message.variable_name) + return sorted(placeholders) + @dataclasses.dataclass class PromptTemplateIdentifier(ResourceIdentifier): diff --git a/dbgpt/core/interface/variables.py b/dbgpt/core/interface/variables.py index 22e035d52..b932298e8 100644 --- a/dbgpt/core/interface/variables.py +++ b/dbgpt/core/interface/variables.py @@ -31,6 +31,7 @@ BUILTIN_VARIABLES_CORE_VARIABLES = "dbgpt.core.variables" BUILTIN_VARIABLES_CORE_SECRETS = "dbgpt.core.secrets" BUILTIN_VARIABLES_CORE_LLMS = "dbgpt.core.model.llms" BUILTIN_VARIABLES_CORE_EMBEDDINGS = "dbgpt.core.model.embeddings" +# Not implemented yet BUILTIN_VARIABLES_CORE_RERANKERS = "dbgpt.core.model.rerankers" BUILTIN_VARIABLES_CORE_DATASOURCES = "dbgpt.core.datasources" BUILTIN_VARIABLES_CORE_AGENTS = "dbgpt.core.agent.agents" @@ -373,6 +374,15 @@ class VariablesProvider(BaseComponent, ABC): ) -> Any: """Query variables from storage.""" + async def async_get( + self, + full_key: str, + default_value: Optional[str] = _EMPTY_DEFAULT_VALUE, + default_identifier_map: Optional[Dict[str, str]] = None, + ) -> Any: + """Query variables from storage async.""" + raise NotImplementedError("Current variables provider does not support async.") + @abstractmethod def save(self, variables_item: StorageVariables) -> None: """Save variables to storage.""" @@ -456,6 +466,24 @@ class VariablesPlaceHolder: return None raise e + async def async_parse( + self, + variables_provider: VariablesProvider, + ignore_not_found_error: bool = False, + default_identifier_map: Optional[Dict[str, str]] = None, + ): + """Parse the variables async.""" + try: + return await variables_provider.async_get( + self.full_key, + self.default_value, + default_identifier_map=default_identifier_map, + ) + except ValueError as e: + if ignore_not_found_error: + return None + raise e + def __repr__(self): """Return the representation of the variables place holder.""" return f"" @@ -507,6 +535,42 @@ class StorageVariablesProvider(VariablesProvider): variable.value = self.encryption.decrypt(variable.value, variable.salt) return self._convert_to_value_type(variable) + async def async_get( + self, + full_key: str, + default_value: Optional[str] = _EMPTY_DEFAULT_VALUE, + default_identifier_map: Optional[Dict[str, str]] = None, + ) -> Any: + """Query variables from storage async.""" + # Try to get variables from storage + value = await blocking_func_to_async_no_executor( + self.get, + full_key, + default_value=None, + default_identifier_map=default_identifier_map, + ) + if value is not None: + return value + key = VariablesIdentifier.from_str_identifier(full_key, default_identifier_map) + # Get all builtin variables + variables = await self.async_get_variables( + key=key.key, + scope=key.scope, + scope_key=key.scope_key, + sys_code=key.sys_code, + user_name=key.user_name, + ) + values = [v for v in variables if v.name == key.name] + if not values: + if default_value == _EMPTY_DEFAULT_VALUE: + raise ValueError(f"Variable {full_key} not found") + return default_value + if len(values) > 1: + raise ValueError(f"Multiple variables found for {full_key}") + + variable = values[0] + return self._convert_to_value_type(variable) + def save(self, variables_item: StorageVariables) -> None: """Save variables to storage.""" if variables_item.category == "secret": @@ -576,9 +640,11 @@ class StorageVariablesProvider(VariablesProvider): ) if is_builtin: return builtin_variables - executor_factory: Optional[ - DefaultExecutorFactory - ] = DefaultExecutorFactory.get_instance(self.system_app, default_component=None) + executor_factory: Optional[DefaultExecutorFactory] = None + if self.system_app: + executor_factory = DefaultExecutorFactory.get_instance( + self.system_app, default_component=None + ) if executor_factory: return await blocking_func_to_async( executor_factory.create(), diff --git a/dbgpt/model/cluster/client.py b/dbgpt/model/cluster/client.py index 7e0aa0214..f4141cc23 100644 --- a/dbgpt/model/cluster/client.py +++ b/dbgpt/model/cluster/client.py @@ -27,7 +27,7 @@ from dbgpt.util.i18n_utils import _ name="auto_convert_message", type=bool, optional=True, - default=False, + default=True, description=_( "Whether to auto convert the messages that are not supported " "by the LLM to a compatible format" @@ -42,13 +42,13 @@ class DefaultLLMClient(LLMClient): Args: worker_manager (WorkerManager): worker manager instance. - auto_convert_message (bool, optional): auto convert the message to ModelRequest. Defaults to False. + auto_convert_message (bool, optional): auto convert the message to ModelRequest. Defaults to True. """ def __init__( self, worker_manager: Optional[WorkerManager] = None, - auto_convert_message: bool = False, + auto_convert_message: bool = True, ): self._worker_manager = worker_manager self._auto_covert_message = auto_convert_message @@ -128,7 +128,7 @@ class DefaultLLMClient(LLMClient): name="auto_convert_message", type=bool, optional=True, - default=False, + default=True, description=_( "Whether to auto convert the messages that are not supported " "by the LLM to a compatible format" @@ -158,7 +158,7 @@ class RemoteLLMClient(DefaultLLMClient): def __init__( self, controller_address: str = "http://127.0.0.1:8000", - auto_convert_message: bool = False, + auto_convert_message: bool = True, ): """Initialize the RemoteLLMClient.""" from dbgpt.model.cluster import ModelRegistryClient, RemoteWorkerManager diff --git a/dbgpt/model/operators/llm_operator.py b/dbgpt/model/operators/llm_operator.py index 56eee1e3e..02f14fe73 100644 --- a/dbgpt/model/operators/llm_operator.py +++ b/dbgpt/model/operators/llm_operator.py @@ -24,8 +24,13 @@ class MixinLLMOperator(BaseLLM, BaseOperator, ABC): This class extends BaseOperator by adding LLM capabilities. """ - def __init__(self, default_client: Optional[LLMClient] = None, **kwargs): - super().__init__(default_client) + def __init__( + self, + default_client: Optional[LLMClient] = None, + save_model_output: bool = True, + **kwargs, + ): + super().__init__(default_client, save_model_output=save_model_output) @property def llm_client(self) -> LLMClient: @@ -95,8 +100,13 @@ class LLMOperator(MixinLLMOperator, BaseLLMOperator): ], ) - def __init__(self, llm_client: Optional[LLMClient] = None, **kwargs): - super().__init__(llm_client) + def __init__( + self, + llm_client: Optional[LLMClient] = None, + save_model_output: bool = True, + **kwargs, + ): + super().__init__(llm_client, save_model_output=save_model_output) BaseLLMOperator.__init__(self, llm_client, **kwargs) @@ -144,6 +154,11 @@ class StreamingLLMOperator(MixinLLMOperator, BaseStreamingLLMOperator): ], ) - def __init__(self, llm_client: Optional[LLMClient] = None, **kwargs): - super().__init__(llm_client) + def __init__( + self, + llm_client: Optional[LLMClient] = None, + save_model_output: bool = True, + **kwargs, + ): + super().__init__(llm_client, save_model_output=save_model_output) BaseStreamingLLMOperator.__init__(self, llm_client, **kwargs) diff --git a/dbgpt/model/proxy/base.py b/dbgpt/model/proxy/base.py index 129dcf11e..2a1a3b6b8 100644 --- a/dbgpt/model/proxy/base.py +++ b/dbgpt/model/proxy/base.py @@ -94,6 +94,17 @@ class ProxyLLMClient(LLMClient): self.executor = executor or ThreadPoolExecutor() self.proxy_tokenizer = proxy_tokenizer or TiktokenProxyTokenizer() + def __getstate__(self): + """Customize the serialization of the object""" + state = self.__dict__.copy() + state.pop("executor") + return state + + def __setstate__(self, state): + """Customize the deserialization of the object""" + self.__dict__.update(state) + self.executor = ThreadPoolExecutor() + @classmethod @abstractmethod def new_client( diff --git a/dbgpt/model/utils/chatgpt_utils.py b/dbgpt/model/utils/chatgpt_utils.py index 057a04bf5..51c0fcae3 100644 --- a/dbgpt/model/utils/chatgpt_utils.py +++ b/dbgpt/model/utils/chatgpt_utils.py @@ -16,7 +16,13 @@ from typing import ( from dbgpt._private.pydantic import model_to_json from dbgpt.core.awel import TransformStreamAbsOperator -from dbgpt.core.awel.flow import IOField, OperatorCategory, OperatorType, ViewMetadata +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + IOField, + OperatorCategory, + OperatorType, + ViewMetadata, +) from dbgpt.core.interface.llm import ModelOutput from dbgpt.core.operators import BaseLLM from dbgpt.util.i18n_utils import _ @@ -184,6 +190,7 @@ class OpenAIStreamingOutputOperator(TransformStreamAbsOperator[ModelOutput, str] ), ) ], + tags={"order": TAGS_ORDER_HIGH}, ) async def transform_stream(self, model_output: AsyncIterator[ModelOutput]): diff --git a/dbgpt/rag/summary/db_summary_client.py b/dbgpt/rag/summary/db_summary_client.py index de5ee83ff..8ce9a79e6 100644 --- a/dbgpt/rag/summary/db_summary_client.py +++ b/dbgpt/rag/summary/db_summary_client.py @@ -2,6 +2,7 @@ import logging import traceback +from typing import List from dbgpt._private.config import Config from dbgpt.component import SystemApp @@ -46,7 +47,7 @@ class DBSummaryClient: logger.info("db summary embedding success") - def get_db_summary(self, dbname, query, topk): + def get_db_summary(self, dbname, query, topk) -> List[str]: """Get user query related tables info.""" from dbgpt.serve.rag.connector import VectorStoreConnector from dbgpt.storage.vector_store.base import VectorStoreConfig diff --git a/dbgpt/serve/agent/resource/datasource.py b/dbgpt/serve/agent/resource/datasource.py index 5e37cdd0c..0be2127dd 100644 --- a/dbgpt/serve/agent/resource/datasource.py +++ b/dbgpt/serve/agent/resource/datasource.py @@ -3,14 +3,41 @@ import logging from typing import Any, List, Optional, Type, Union, cast from dbgpt._private.config import Config -from dbgpt.agent.resource.database import DBParameters, RDBMSConnectorResource +from dbgpt.agent.resource.database import ( + _DEFAULT_PROMPT_TEMPLATE, + _DEFAULT_PROMPT_TEMPLATE_ZH, + DBParameters, + RDBMSConnectorResource, +) +from dbgpt.core.awel.flow import ( + TAGS_ORDER_HIGH, + FunctionDynamicOptions, + OptionValue, + Parameter, + ResourceCategory, + register_resource, +) from dbgpt.util import ParameterDescription +from dbgpt.util.i18n_utils import _ CFG = Config() logger = logging.getLogger(__name__) +def _load_datasource() -> List[OptionValue]: + dbs = CFG.local_db_manager.get_db_list() + results = [ + OptionValue( + label="[" + db["db_type"] + "]" + db["db_name"], + name=db["db_name"], + value=db["db_name"], + ) + for db in dbs + ] + return results + + @dataclasses.dataclass class DatasourceDBParameters(DBParameters): """The DB parameters for the datasource.""" @@ -57,6 +84,44 @@ class DatasourceDBParameters(DBParameters): return super().from_dict(copied_data, ignore_extra_fields=ignore_extra_fields) +@register_resource( + _("Datasource Resource"), + "datasource", + category=ResourceCategory.DATABASE, + description=_( + "Connect to a datasource(retrieve table schemas and execute SQL to fetch data)." + ), + tags={"order": TAGS_ORDER_HIGH}, + parameters=[ + Parameter.build_from( + _("Datasource Name"), + "name", + str, + optional=True, + default="datasource", + description=_("The name of the datasource, default is 'datasource'."), + ), + Parameter.build_from( + _("DB Name"), + "db_name", + str, + description=_("The name of the database."), + options=FunctionDynamicOptions(func=_load_datasource), + ), + Parameter.build_from( + _("Prompt Template"), + "prompt_template", + str, + optional=True, + default=( + _DEFAULT_PROMPT_TEMPLATE_ZH + if CFG.LANGUAGE == "zh" + else _DEFAULT_PROMPT_TEMPLATE + ), + description=_("The prompt template to build a database prompt."), + ), + ], +) class DatasourceResource(RDBMSConnectorResource): def __init__(self, name: str, db_name: Optional[str] = None, **kwargs): conn = CFG.local_db_manager.get_connector(db_name) diff --git a/dbgpt/serve/agent/resource/knowledge.py b/dbgpt/serve/agent/resource/knowledge.py index 90359be4c..65c062415 100644 --- a/dbgpt/serve/agent/resource/knowledge.py +++ b/dbgpt/serve/agent/resource/knowledge.py @@ -64,6 +64,7 @@ class KnowledgeSpaceRetrieverResource(RetrieverResource): """Knowledge Space retriever resource.""" def __init__(self, name: str, space_name: str, context: Optional[dict] = None): + # TODO: Build the retriever in a thread pool, it will block the event loop retriever = KnowledgeSpaceRetriever( space_id=space_name, top_k=context.get("top_k", None) if context else 4, diff --git a/dbgpt/serve/dbgpts/__init__.py b/dbgpt/serve/dbgpts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dbgpt/serve/file/api/endpoints.py b/dbgpt/serve/file/api/endpoints.py index 26bbb9673..d5b65bc54 100644 --- a/dbgpt/serve/file/api/endpoints.py +++ b/dbgpt/serve/file/api/endpoints.py @@ -1,3 +1,4 @@ +import asyncio import logging from functools import cache from typing import List, Optional @@ -13,7 +14,13 @@ from dbgpt.util import PaginationResult from ..config import APP_NAME, SERVE_APP_NAME, SERVE_SERVICE_COMPONENT_NAME, ServeConfig from ..service.service import Service -from .schemas import ServeRequest, ServerResponse, UploadFileResponse +from .schemas import ( + FileMetadataBatchRequest, + FileMetadataResponse, + ServeRequest, + ServerResponse, + UploadFileResponse, +) router = APIRouter() logger = logging.getLogger(__name__) @@ -162,6 +169,74 @@ async def delete_file( return Result.succ(None) +@router.get( + "/files/metadata", + response_model=Result[FileMetadataResponse], + dependencies=[Depends(check_api_key)], +) +async def get_file_metadata( + uri: Optional[str] = Query(None, description="File URI"), + bucket: Optional[str] = Query(None, description="Bucket name"), + file_id: Optional[str] = Query(None, description="File ID"), + service: Service = Depends(get_service), +) -> Result[FileMetadataResponse]: + """Get file metadata by URI or by bucket and file_id.""" + if not uri and not (bucket and file_id): + raise HTTPException( + status_code=400, + detail="Either uri or (bucket and file_id) must be provided", + ) + + metadata = await blocking_func_to_async( + global_system_app, service.get_file_metadata, uri, bucket, file_id + ) + return Result.succ(metadata) + + +@router.post( + "/files/metadata/batch", + response_model=Result[List[FileMetadataResponse]], + dependencies=[Depends(check_api_key)], +) +async def get_files_metadata_batch( + request: FileMetadataBatchRequest, service: Service = Depends(get_service) +) -> Result[List[FileMetadataResponse]]: + """Get metadata for multiple files by URIs or bucket and file_id pairs.""" + if not request.uris and not request.bucket_file_pairs: + raise HTTPException( + status_code=400, + detail="Either uris or bucket_file_pairs must be provided", + ) + + batch_req = [] + if request.uris: + for uri in request.uris: + batch_req.append((uri, None, None)) + elif request.bucket_file_pairs: + for pair in request.bucket_file_pairs: + batch_req.append((None, pair.bucket, pair.file_id)) + else: + raise HTTPException( + status_code=400, + detail="Either uris or bucket_file_pairs must be provided", + ) + + batch_req_tasks = [ + blocking_func_to_async( + global_system_app, service.get_file_metadata, uri, bucket, file_id + ) + for uri, bucket, file_id in batch_req + ] + + metadata_list = await asyncio.gather(*batch_req_tasks) + if not metadata_list: + raise HTTPException( + status_code=404, + detail="File metadata not found", + ) + return Result.succ(metadata_list) + + def init_endpoints(system_app: SystemApp) -> None: """Initialize the endpoints""" global global_system_app diff --git a/dbgpt/serve/file/api/schemas.py b/dbgpt/serve/file/api/schemas.py index 911f71db3..bd8b3bbf2 100644 --- a/dbgpt/serve/file/api/schemas.py +++ b/dbgpt/serve/file/api/schemas.py @@ -1,7 +1,13 @@ # Define your Pydantic schemas here -from typing import Any, Dict +from typing import Any, Dict, List, Optional -from dbgpt._private.pydantic import BaseModel, ConfigDict, Field, model_to_dict +from dbgpt._private.pydantic import ( + BaseModel, + ConfigDict, + Field, + model_to_dict, + model_validator, +) from ..config import SERVE_APP_NAME_HUMP @@ -41,3 +47,41 @@ class UploadFileResponse(BaseModel): def to_dict(self, **kwargs) -> Dict[str, Any]: """Convert the model to a dictionary""" return model_to_dict(self, **kwargs) + + +class _BucketFilePair(BaseModel): + """Bucket file pair model""" + + bucket: str = Field(..., title="The bucket of the file") + file_id: str = Field(..., title="The ID of the file") + + +class FileMetadataBatchRequest(BaseModel): + """File metadata batch request model""" + + uris: Optional[List[str]] = Field(None, title="The URIs of the files") + bucket_file_pairs: Optional[List[_BucketFilePair]] = Field( + None, title="The bucket file pairs" + ) + + @model_validator(mode="after") + def check_uris_or_bucket_file_pairs(self): + # Check if either uris or bucket_file_pairs is provided + if not (self.uris or self.bucket_file_pairs): + raise ValueError("Either uris or bucket_file_pairs must be provided") + # Check only one of uris or bucket_file_pairs is provided + if self.uris and self.bucket_file_pairs: + raise ValueError("Only one of uris or bucket_file_pairs can be provided") + return self + + +class FileMetadataResponse(BaseModel): + """File metadata model""" + + file_name: str = Field(..., title="The name of the file") + file_id: str = Field(..., title="The ID of the file") + bucket: str = Field(..., title="The bucket of the file") + uri: str = Field(..., title="The URI of the file") + file_size: int = Field(..., title="The size of the file") + user_name: Optional[str] = Field(None, title="The user name") + sys_code: Optional[str] = Field(None, title="The system code") diff --git a/dbgpt/serve/file/service/service.py b/dbgpt/serve/file/service/service.py index 13e8b6225..85940ed35 100644 --- a/dbgpt/serve/file/service/service.py +++ b/dbgpt/serve/file/service/service.py @@ -1,7 +1,7 @@ import logging from typing import BinaryIO, List, Optional, Tuple -from fastapi import UploadFile +from fastapi import HTTPException, UploadFile from dbgpt.component import BaseComponent, SystemApp from dbgpt.core.interface.file import FileMetadata, FileStorageClient, FileStorageURI @@ -10,7 +10,12 @@ from dbgpt.storage.metadata import BaseDao from dbgpt.util.pagination_utils import PaginationResult from dbgpt.util.tracer import root_tracer, trace -from ..api.schemas import ServeRequest, ServerResponse, UploadFileResponse +from ..api.schemas import ( + FileMetadataResponse, + ServeRequest, + ServerResponse, + UploadFileResponse, +) from ..config import SERVE_CONFIG_KEY_PREFIX, SERVE_SERVICE_COMPONENT_NAME, ServeConfig from ..models.models import ServeDao, ServeEntity @@ -117,3 +122,33 @@ class Service(BaseService[ServeEntity, ServeRequest, ServerResponse]): def delete_file(self, bucket: str, file_id: str) -> None: """Delete a file by file_id.""" self.file_storage_client.delete_file_by_id(bucket, file_id) + + def get_file_metadata( + self, + uri: Optional[str] = None, + bucket: Optional[str] = None, + file_id: Optional[str] = None, + ) -> Optional[FileMetadataResponse]: + """Get the metadata of a file by file_id.""" + if uri: + parsed_uri = FileStorageURI.parse(uri) + bucket, file_id = parsed_uri.bucket, parsed_uri.file_id + if not (bucket and file_id): + raise ValueError("Either uri or bucket and file_id must be provided.") + metadata = self.file_storage_client.storage_system.get_file_metadata( + bucket, file_id + ) + if not metadata: + raise HTTPException( + status_code=404, + detail=f"File metadata not found: bucket={bucket}, file_id={file_id}, uri={uri}", + ) + return FileMetadataResponse( + file_name=metadata.file_name, + file_id=metadata.file_id, + bucket=metadata.bucket, + uri=metadata.uri, + file_size=metadata.file_size, + user_name=metadata.user_name, + sys_code=metadata.sys_code, + ) diff --git a/dbgpt/serve/flow/api/endpoints.py b/dbgpt/serve/flow/api/endpoints.py index 936e0ff0f..68e3a815e 100644 --- a/dbgpt/serve/flow/api/endpoints.py +++ b/dbgpt/serve/flow/api/endpoints.py @@ -14,13 +14,14 @@ from dbgpt.serve.core import Result, blocking_func_to_async from dbgpt.util import PaginationResult from ..config import APP_NAME, SERVE_SERVICE_COMPONENT_NAME, ServeConfig -from ..service.service import Service +from ..service.service import Service, _parse_flow_template_from_json from ..service.variables_service import VariablesService from .schemas import ( FlowDebugRequest, RefreshNodeRequest, ServeRequest, ServerResponse, + VariablesKeyResponse, VariablesRequest, VariablesResponse, ) @@ -133,7 +134,10 @@ async def create( Returns: ServerResponse: The response """ - return Result.succ(service.create_and_save_dag(request)) + res = await blocking_func_to_async( + global_system_app, service.create_and_save_dag, request + ) + return Result.succ(res) @router.put( @@ -154,7 +158,10 @@ async def update( ServerResponse: The response """ try: - return Result.succ(service.update_flow(request)) + res = await blocking_func_to_async( + global_system_app, service.update_flow, request + ) + return Result.succ(res) except Exception as e: return Result.failed(msg=str(e)) @@ -176,9 +183,7 @@ async def delete( @router.get("/flows/{uid}") -async def get_flows( - uid: str, service: Service = Depends(get_service) -) -> Result[ServerResponse]: +async def get_flows(uid: str, service: Service = Depends(get_service)): """Get a Flow entity by uid Args: @@ -191,7 +196,7 @@ async def get_flows( flow = service.get({"uid": uid}) if not flow: raise HTTPException(status_code=404, detail=f"Flow {uid} not found") - return Result.succ(flow) + return Result.succ(flow.model_dump()) @router.get( @@ -360,6 +365,62 @@ async def update_variables( return Result.succ(res) +@router.get( + "/variables", + response_model=Result[PaginationResult[VariablesResponse]], + dependencies=[Depends(check_api_key)], +) +async def get_variables_by_keys( + key: str = Query(..., description="variable key"), + scope: Optional[str] = Query(default=None, description="scope"), + scope_key: Optional[str] = Query(default=None, description="scope key"), + user_name: Optional[str] = Query(default=None, description="user name"), + sys_code: Optional[str] = Query(default=None, description="system code"), + page: int = Query(default=1, description="current page"), + page_size: int = Query(default=20, description="page size"), +) -> Result[PaginationResult[VariablesResponse]]: + """Get the variables by keys + + Returns: + VariablesResponse: The response + """ + res = await get_variable_service().get_list_by_page( + key, + scope, + scope_key, + user_name, + sys_code, + page, + page_size, + ) + return Result.succ(res) + + +@router.get( + "/variables/keys", + response_model=Result[List[VariablesKeyResponse]], + dependencies=[Depends(check_api_key)], +) +async def get_variables_keys( + user_name: Optional[str] = Query(default=None, description="user name"), + sys_code: Optional[str] = Query(default=None, description="system code"), + category: Optional[str] = Query(default=None, description="category"), +) -> Result[List[VariablesKeyResponse]]: + """Get the variable keys + + Returns: + VariablesKeyResponse: The response + """ + res = await blocking_func_to_async( + global_system_app, + get_variable_service().list_keys, + user_name, + sys_code, + category, + ) + return Result.succ(res) + + @router.post("/flow/debug", dependencies=[Depends(check_api_key)]) async def debug_flow( flow_debug_request: FlowDebugRequest, service: Service = Depends(get_service) @@ -456,7 +517,7 @@ async def import_flow( raise HTTPException( status_code=400, detail="invalid json file, missing 'flow' key" ) - flow = ServeRequest.parse_obj(json_dict["flow"]) + flow = _parse_flow_template_from_json(json_dict) elif file_extension == "zip": from ..service.share_utils import _parse_flow_from_zip_file @@ -467,18 +528,49 @@ async def import_flow( status_code=400, detail=f"invalid file extension {file_extension}" ) if save_flow: - return Result.succ(service.create_and_save_dag(flow)) + res = await blocking_func_to_async( + global_system_app, service.create_and_save_dag, flow + ) + return Result.succ(res) else: return Result.succ(flow) +@router.get( + "/flow/templates", + response_model=Result[PaginationResult[ServerResponse]], + dependencies=[Depends(check_api_key)], +) +async def query_flow_templates( + user_name: Optional[str] = Query(default=None, description="user name"), + sys_code: Optional[str] = Query(default=None, description="system code"), + page: int = Query(default=1, description="current page"), + page_size: int = Query(default=20, description="page size"), + service: Service = Depends(get_service), +) -> Result[PaginationResult[ServerResponse]]: + """Query Flow templates.""" + + res = await blocking_func_to_async( + global_system_app, + service.get_flow_templates, + user_name, + sys_code, + page, + page_size, + ) + return Result.succ(res) + + def init_endpoints(system_app: SystemApp) -> None: """Initialize the endpoints""" from .variables_provider import ( + BuiltinAgentsVariablesProvider, BuiltinAllSecretVariablesProvider, BuiltinAllVariablesProvider, + BuiltinDatasourceVariablesProvider, BuiltinEmbeddingsVariablesProvider, BuiltinFlowVariablesProvider, + BuiltinKnowledgeSpacesVariablesProvider, BuiltinLLMVariablesProvider, BuiltinNodeVariablesProvider, ) @@ -492,4 +584,7 @@ def init_endpoints(system_app: SystemApp) -> None: system_app.register(BuiltinAllSecretVariablesProvider) system_app.register(BuiltinLLMVariablesProvider) system_app.register(BuiltinEmbeddingsVariablesProvider) + system_app.register(BuiltinDatasourceVariablesProvider) + system_app.register(BuiltinAgentsVariablesProvider) + system_app.register(BuiltinKnowledgeSpacesVariablesProvider) global_system_app = system_app diff --git a/dbgpt/serve/flow/api/schemas.py b/dbgpt/serve/flow/api/schemas.py index cf82de982..6053dd885 100644 --- a/dbgpt/serve/flow/api/schemas.py +++ b/dbgpt/serve/flow/api/schemas.py @@ -2,7 +2,11 @@ from typing import Any, Dict, List, Literal, Optional, Union from dbgpt._private.pydantic import BaseModel, ConfigDict, Field from dbgpt.core.awel import CommonLLMHttpRequestBody -from dbgpt.core.awel.flow.flow_factory import FlowPanel, VariablesRequest +from dbgpt.core.awel.flow.flow_factory import ( + FlowPanel, + VariablesRequest, + _VariablesRequestBase, +) from dbgpt.core.awel.util.parameter_util import RefreshOptionRequest from ..config import SERVE_APP_NAME_HUMP @@ -28,6 +32,13 @@ class VariablesResponse(VariablesRequest): ) +class VariablesKeyResponse(_VariablesRequestBase): + """Variables Key response model. + + Just include the key, for select options in the frontend. + """ + + class RefreshNodeRequest(BaseModel): """Flow response model""" diff --git a/dbgpt/serve/flow/api/variables_provider.py b/dbgpt/serve/flow/api/variables_provider.py index 4728f80e6..10bb8a656 100644 --- a/dbgpt/serve/flow/api/variables_provider.py +++ b/dbgpt/serve/flow/api/variables_provider.py @@ -1,9 +1,12 @@ from typing import List, Literal, Optional from dbgpt.core.interface.variables import ( + BUILTIN_VARIABLES_CORE_AGENTS, + BUILTIN_VARIABLES_CORE_DATASOURCES, BUILTIN_VARIABLES_CORE_EMBEDDINGS, BUILTIN_VARIABLES_CORE_FLOW_NODES, BUILTIN_VARIABLES_CORE_FLOWS, + BUILTIN_VARIABLES_CORE_KNOWLEDGE_SPACES, BUILTIN_VARIABLES_CORE_LLMS, BUILTIN_VARIABLES_CORE_SECRETS, BUILTIN_VARIABLES_CORE_VARIABLES, @@ -54,6 +57,7 @@ class BuiltinFlowVariablesProvider(BuiltinVariablesProvider): scope_key=scope_key, sys_code=sys_code, user_name=user_name, + description=flow.description, ) ) return variables @@ -91,6 +95,7 @@ class BuiltinNodeVariablesProvider(BuiltinVariablesProvider): scope_key=scope_key, sys_code=sys_code, user_name=user_name, + description=metadata.get("description"), ) ) return variables @@ -122,10 +127,14 @@ class BuiltinAllVariablesProvider(BuiltinVariablesProvider): name=var.name, label=var.label, value=var.value, + category=var.category, + value_type=var.value_type, scope=scope, scope_key=scope_key, sys_code=sys_code, user_name=user_name, + enabled=1 if var.enabled else 0, + description=var.description, ) ) return variables @@ -258,3 +267,128 @@ class BuiltinEmbeddingsVariablesProvider(BuiltinLLMVariablesProvider): return await self._get_models( key, scope, scope_key, sys_code, user_name, "text2vec" ) + + +class BuiltinDatasourceVariablesProvider(BuiltinVariablesProvider): + """Builtin datasource variables provider. + + Provide all datasource variables by variables "${dbgpt.core.datasource}" + """ + + name = BUILTIN_VARIABLES_CORE_DATASOURCES + + def get_variables( + self, + key: str, + scope: str = "global", + scope_key: Optional[str] = None, + sys_code: Optional[str] = None, + user_name: Optional[str] = None, + ) -> List[StorageVariables]: + """Get the builtin variables.""" + from dbgpt.serve.datasource.service.service import ( + DatasourceServeResponse, + Service, + ) + + all_datasource: List[DatasourceServeResponse] = Service.get_instance( + self.system_app + ).list() + + variables = [] + for datasource in all_datasource: + label = f"[{datasource.db_type}]{datasource.db_name}" + variables.append( + StorageVariables( + key=key, + name=datasource.db_name, + label=label, + value=datasource.db_name, + scope=scope, + scope_key=scope_key, + sys_code=sys_code, + user_name=user_name, + description=datasource.comment, + ) + ) + return variables + + +class BuiltinAgentsVariablesProvider(BuiltinVariablesProvider): + """Builtin agents variables provider. + + Provide all agents variables by variables "${dbgpt.core.agent.agents}" + """ + + name = BUILTIN_VARIABLES_CORE_AGENTS + + def get_variables( + self, + key: str, + scope: str = "global", + scope_key: Optional[str] = None, + sys_code: Optional[str] = None, + user_name: Optional[str] = None, + ) -> List[StorageVariables]: + """Get the builtin variables.""" + from dbgpt.agent.core.agent_manage import get_agent_manager + + agent_manager = get_agent_manager(self.system_app) + agents = agent_manager.list_agents() + variables = [] + for agent in agents: + variables.append( + StorageVariables( + key=key, + name=agent["name"], + label=agent["name"], + value=agent["name"], + scope=scope, + scope_key=scope_key, + sys_code=sys_code, + user_name=user_name, + description=agent["desc"], + ) + ) + return variables + + +class BuiltinKnowledgeSpacesVariablesProvider(BuiltinVariablesProvider): + """Builtin knowledge variables provider. + + Provide all knowledge variables by variables "${dbgpt.core.knowledge_spaces}" + """ + + name = BUILTIN_VARIABLES_CORE_KNOWLEDGE_SPACES + + def get_variables( + self, + key: str, + scope: str = "global", + scope_key: Optional[str] = None, + sys_code: Optional[str] = None, + user_name: Optional[str] = None, + ) -> List[StorageVariables]: + """Get the builtin variables.""" + from dbgpt.serve.rag.service.service import Service, SpaceServeRequest + + # TODO: Query with user_name and sys_code + knowledge_list = Service.get_instance(self.system_app).get_list( + SpaceServeRequest() + ) + variables = [] + for k in knowledge_list: + variables.append( + StorageVariables( + key=key, + name=k.name, + label=k.name, + value=k.name, + scope=scope, + scope_key=scope_key, + sys_code=sys_code, + user_name=user_name, + description=k.desc, + ) + ) + return variables diff --git a/dbgpt/serve/flow/serve.py b/dbgpt/serve/flow/serve.py index a27e3d28f..a8d0161f9 100644 --- a/dbgpt/serve/flow/serve.py +++ b/dbgpt/serve/flow/serve.py @@ -4,7 +4,11 @@ from typing import List, Optional, Union from sqlalchemy import URL from dbgpt.component import SystemApp -from dbgpt.core.interface.variables import VariablesProvider +from dbgpt.core.interface.variables import ( + FernetEncryption, + StorageVariablesProvider, + VariablesProvider, +) from dbgpt.serve.core import BaseServe from dbgpt.storage.metadata import DatabaseManager @@ -33,6 +37,7 @@ class Serve(BaseServe): db_url_or_db: Union[str, URL, DatabaseManager] = None, try_create_tables: Optional[bool] = False, ): + if api_prefix is None: api_prefix = [f"/api/v1/serve/awel", "/api/v2/serve/awel"] if api_tags is None: @@ -41,8 +46,15 @@ class Serve(BaseServe): system_app, api_prefix, api_tags, db_url_or_db, try_create_tables ) self._db_manager: Optional[DatabaseManager] = None - self._variables_provider: Optional[VariablesProvider] = None - self._serve_config: Optional[ServeConfig] = None + self._serve_config = ServeConfig.from_app_config( + system_app.config, SERVE_CONFIG_KEY_PREFIX + ) + self._variables_provider: StorageVariablesProvider = StorageVariablesProvider( + storage=None, + encryption=FernetEncryption(self._serve_config.encrypt_key), + system_app=system_app, + ) + system_app.register_instance(self._variables_provider) def init_app(self, system_app: SystemApp): if self._app_has_initiated: @@ -65,10 +77,6 @@ class Serve(BaseServe): def before_start(self): """Called before the start of the application.""" - from dbgpt.core.interface.variables import ( - FernetEncryption, - StorageVariablesProvider, - ) from dbgpt.storage.metadata.db_storage import SQLAlchemyStorage from dbgpt.util.serialization.json_serialization import JsonSerializer @@ -76,9 +84,6 @@ class Serve(BaseServe): from .models.variables_adapter import VariablesAdapter self._db_manager = self.create_or_get_db_manager() - self._serve_config = ServeConfig.from_app_config( - self._system_app.config, SERVE_CONFIG_KEY_PREFIX - ) self._db_manager = self.create_or_get_db_manager() storage_adapter = VariablesAdapter() @@ -89,11 +94,7 @@ class Serve(BaseServe): storage_adapter, serializer, ) - self._variables_provider = StorageVariablesProvider( - storage=storage, - encryption=FernetEncryption(self._serve_config.encrypt_key), - system_app=self._system_app, - ) + self._variables_provider.storage = storage @property def variables_provider(self): diff --git a/dbgpt/serve/flow/service/service.py b/dbgpt/serve/flow/service/service.py index 15c9d5ceb..54d07d49a 100644 --- a/dbgpt/serve/flow/service/service.py +++ b/dbgpt/serve/flow/service/service.py @@ -1,5 +1,6 @@ import json import logging +import os from typing import AsyncIterator, List, Optional, cast import schedule @@ -27,7 +28,7 @@ from dbgpt.core.schema.api import ( ChatCompletionStreamResponse, DeltaMessage, ) -from dbgpt.serve.core import BaseService +from dbgpt.serve.core import BaseService, blocking_func_to_async from dbgpt.storage.metadata import BaseDao from dbgpt.storage.metadata._base_dao import QUERY_SPEC from dbgpt.util.dbgpts.loader import DBGPTsLoader @@ -230,7 +231,7 @@ class Service(BaseService[ServeEntity, ServeRequest, ServerResponse]): continue # Set state to DEPLOYED flow.state = State.DEPLOYED - exist_inst = self.get({"name": flow.name}) + exist_inst = self.dao.get_one({"name": flow.name}) if not exist_inst: self.create_and_save_dag(flow, save_failed_flow=True) elif is_first_load or exist_inst.state != State.RUNNING: @@ -388,7 +389,9 @@ class Service(BaseService[ServeEntity, ServeRequest, ServerResponse]): Returns: List[ServerResponse]: The response """ - page_result = self.dao.get_list_page(request, page, page_size) + page_result = self.dao.get_list_page( + request, page, page_size, desc_order_column=ServeEntity.gmt_modified.name + ) for item in page_result.items: metadata = self.dag_manager.get_dag_metadata( item.dag_id, alias_name=item.uid @@ -397,6 +400,47 @@ class Service(BaseService[ServeEntity, ServeRequest, ServerResponse]): item.metadata = metadata.to_dict() return page_result + def get_flow_templates( + self, + user_name: Optional[str] = None, + sys_code: Optional[str] = None, + page: int = 1, + page_size: int = 20, + ) -> PaginationResult[ServerResponse]: + """Get a list of Flow templates + + Args: + user_name (Optional[str]): The user name + sys_code (Optional[str]): The system code + page (int): The page number + page_size (int): The page size + Returns: + List[ServerResponse]: The response + """ + local_file_templates = self._get_flow_templates_from_files() + return PaginationResult.build_from_all(local_file_templates, page, page_size) + + def _get_flow_templates_from_files(self) -> List[ServerResponse]: + """Get a list of Flow templates from files""" + user_lang = self._system_app.config.get_current_lang(default="en") + # List files in current directory + parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + template_dir = os.path.join(parent_dir, "templates", user_lang) + default_template_dir = os.path.join(parent_dir, "templates", "en") + if not os.path.exists(template_dir): + template_dir = default_template_dir + templates = [] + for root, _, files in os.walk(template_dir): + for file in files: + if file.endswith(".json"): + try: + with open(os.path.join(root, file), "r") as f: + data = json.load(f) + templates.append(_parse_flow_template_from_json(data)) + except Exception as e: + logger.warning(f"Load template {file} error: {str(e)}") + return templates + async def chat_stream_flow_str( self, flow_uid: str, request: CommonLLMHttpRequestBody ) -> AsyncIterator[str]: @@ -590,7 +634,11 @@ class Service(BaseService[ServeEntity, ServeRequest, ServerResponse]): """ from dbgpt.core.awel.dag.dag_manager import DAGMetadata, _parse_metadata - dag = self._flow_factory.build(request.flow) + dag = await blocking_func_to_async( + self._system_app, + self._flow_factory.build, + request.flow, + ) leaf_nodes = dag.leaf_nodes if len(leaf_nodes) != 1: raise ValueError("Chat Flow just support one leaf node in dag") @@ -632,3 +680,20 @@ class Service(BaseService[ServeEntity, ServeRequest, ServerResponse]): break else: yield f"data:{text}\n\n" + + +def _parse_flow_template_from_json(json_dict: dict) -> ServerResponse: + """Parse the flow from json + + Args: + json_dict (dict): The json dict + + Returns: + ServerResponse: The flow + """ + flow_json = json_dict["flow"] + flow_json["editable"] = False + del flow_json["uid"] + flow_json["state"] = State.INITIALIZING + flow_json["dag_id"] = None + return ServerResponse(**flow_json) diff --git a/dbgpt/serve/flow/service/variables_service.py b/dbgpt/serve/flow/service/variables_service.py index 09e2a16b0..fbb4cc9b9 100644 --- a/dbgpt/serve/flow/service/variables_service.py +++ b/dbgpt/serve/flow/service/variables_service.py @@ -1,10 +1,25 @@ from typing import List, Optional from dbgpt import SystemApp -from dbgpt.core.interface.variables import StorageVariables, VariablesProvider -from dbgpt.serve.core import BaseService +from dbgpt.core.interface.variables import ( + BUILTIN_VARIABLES_CORE_AGENTS, + BUILTIN_VARIABLES_CORE_DATASOURCES, + BUILTIN_VARIABLES_CORE_EMBEDDINGS, + BUILTIN_VARIABLES_CORE_FLOW_NODES, + BUILTIN_VARIABLES_CORE_FLOWS, + BUILTIN_VARIABLES_CORE_KNOWLEDGE_SPACES, + BUILTIN_VARIABLES_CORE_LLMS, + BUILTIN_VARIABLES_CORE_RERANKERS, + BUILTIN_VARIABLES_CORE_SECRETS, + BUILTIN_VARIABLES_CORE_VARIABLES, + StorageVariables, + VariablesProvider, +) +from dbgpt.serve.core import BaseService, blocking_func_to_async +from dbgpt.util import PaginationResult +from dbgpt.util.i18n_utils import _ -from ..api.schemas import VariablesRequest, VariablesResponse +from ..api.schemas import VariablesKeyResponse, VariablesRequest, VariablesResponse from ..config import ( SERVE_CONFIG_KEY_PREFIX, SERVE_VARIABLES_SERVICE_COMPONENT_NAME, @@ -12,6 +27,93 @@ from ..config import ( ) from ..models.models import VariablesDao, VariablesEntity +BUILTIN_VARIABLES = [ + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_FLOWS, + label=_("All AWEL Flows"), + description=_("Fetch all AWEL flows in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_FLOW_NODES, + label=_("All AWEL Flow Nodes"), + description=_("Fetch all AWEL flow nodes in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_VARIABLES, + label=_("All Variables"), + description=_("Fetch all variables in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_SECRETS, + label=_("All Secrets"), + description=_("Fetch all secrets in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_LLMS, + label=_("All LLMs"), + description=_("Fetch all LLMs in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_EMBEDDINGS, + label=_("All Embeddings"), + description=_("Fetch all embeddings models in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_RERANKERS, + label=_("All Rerankers"), + description=_("Fetch all rerankers in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_DATASOURCES, + label=_("All Data Sources"), + description=_("Fetch all data sources in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_AGENTS, + label=_("All Agents"), + description=_("Fetch all agents in the system"), + value_type="str", + category="common", + scope="global", + ), + VariablesKeyResponse( + key=BUILTIN_VARIABLES_CORE_KNOWLEDGE_SPACES, + label=_("All Knowledge Spaces"), + description=_("Fetch all knowledge spaces in the system"), + value_type="str", + category="common", + scope="global", + ), +] + + +def _is_builtin_variable(key: str) -> bool: + return key in [v.key for v in BUILTIN_VARIABLES] + class VariablesService( BaseService[VariablesEntity, VariablesRequest, VariablesResponse] @@ -148,5 +250,119 @@ class VariablesService( return self.dao.get_one(query) def list_all_variables(self, category: str = "common") -> List[VariablesResponse]: - """List all variables.""" + """List all variables. + + Please note that this method will return all variables in the system, it may + be a large list. + """ return self.dao.get_list({"enabled": True, "category": category}) + + def list_keys( + self, + user_name: Optional[str] = None, + sys_code: Optional[str] = None, + category: Optional[str] = None, + ) -> List[VariablesKeyResponse]: + """List all keys.""" + results = [] + + # TODO: More high performance way to get the keys + all_db_variables = self.dao.get_list( + { + "enabled": True, + "category": category, + "user_name": user_name, + "sys_code": sys_code, + } + ) + if not user_name: + # Only return the keys that are not user specific + all_db_variables = [v for v in all_db_variables if not v.user_name] + if not sys_code: + # Only return the keys that are not system specific + all_db_variables = [v for v in all_db_variables if not v.sys_code] + key_to_db_variable = {} + for db_variable in all_db_variables: + key = db_variable.key + if key not in key_to_db_variable: + key_to_db_variable[key] = db_variable + + # Append all builtin variables to the results + results.extend(BUILTIN_VARIABLES) + + # Append all db variables to the results + for key, db_variable in key_to_db_variable.items(): + results.append( + VariablesKeyResponse( + key=key, + label=db_variable.label, + description=db_variable.description, + value_type=db_variable.value_type, + category=db_variable.category, + scope=db_variable.scope, + scope_key=db_variable.scope_key, + ) + ) + return results + + async def get_list_by_page( + self, + key: str, + scope: Optional[str] = None, + scope_key: Optional[str] = None, + user_name: Optional[str] = None, + sys_code: Optional[str] = None, + page: int = 1, + page_size: int = 20, + ) -> PaginationResult[VariablesResponse]: + """Get a list of variables by page.""" + if not _is_builtin_variable(key): + query = { + "key": key, + "scope": scope, + "scope_key": scope_key, + "user_name": user_name, + "sys_code": sys_code, + } + return await blocking_func_to_async( + self._system_app, + self.dao.get_list_page, + query, + page, + page_size, + desc_order_column="gmt_modified", + ) + else: + variables: List[ + StorageVariables + ] = await self.variables_provider.async_get_variables( + key=key, + scope=scope, + scope_key=scope_key, + sys_code=sys_code, + user_name=user_name, + ) + result_variables = [] + for entity in variables: + result_variables.append( + VariablesResponse( + id=-1, + key=entity.key, + name=entity.name, + label=entity.label, + value=entity.value, + value_type=entity.value_type, + category=entity.category, + scope=entity.scope, + scope_key=entity.scope_key, + enabled=True if entity.enabled == 1 else False, + user_name=entity.user_name, + sys_code=entity.sys_code, + description=entity.description, + ) + ) + return PaginationResult.build_from_all( + result_variables, + page, + page_size, + ) diff --git a/dbgpt/serve/flow/templates/en/rag-chat-awel-flow-template.json b/dbgpt/serve/flow/templates/en/rag-chat-awel-flow-template.json new file mode 100644 index 000000000..60ff5c911 --- /dev/null +++ b/dbgpt/serve/flow/templates/en/rag-chat-awel-flow-template.json @@ -0,0 +1,1088 @@ +{ + "flow": { + "uid": "21eb87d5-b63a-4f41-b2aa-28d01033344d", + "label": "RAG Chat AWEL flow template", + "name": "rag_chat_awel_flow_template", + "flow_category": "chat_flow", + "description": "An example of a RAG chat AWEL flow.", + "state": "running", + "error_message": "", + "source": "DBGPT-WEB", + "source_url": null, + "version": "0.1.1", + "define_type": "json", + "editable": true, + "user_name": null, + "sys_code": null, + "dag_id": "flow_dag_rag_chat_awel_flow_template_21eb87d5-b63a-4f41-b2aa-28d01033344d", + "gmt_created": "2024-08-30 10:48:56", + "gmt_modified": "2024-08-30 10:48:56", + "metadata": { + "sse_output": true, + "streaming_output": true, + "tags": {}, + "triggers": [ + { + "trigger_type": "http", + "path": "/api/v1/awel/trigger/templates/flow_dag_rag_chat_awel_flow_template_21eb87d5-b63a-4f41-b2aa-28d01033344d", + "methods": [ + "POST" + ], + "trigger_mode": "chat" + } + ] + }, + "variables": null, + "authors": null, + "flow_data": { + "edges": [ + { + "source": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "source_order": 0, + "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "target_order": 0, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_handle": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|outputs|0", + "target_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|inputs|0", + "type": "buttonedge" + }, + { + "source": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "source_order": 1, + "target": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "target_order": 0, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "source_handle": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|outputs|1", + "target_handle": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0|inputs|0", + "type": "buttonedge" + }, + { + "source": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "source_order": 0, + "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "target_order": 1, + "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0|operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_handle": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0|outputs|0", + "target_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|inputs|1", + "type": "buttonedge" + }, + { + "source": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "source_order": 0, + "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "target_order": 0, + "id": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0|operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_handle": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0|outputs|0", + "target_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|parameters|0", + "type": "buttonedge" + }, + { + "source": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_order": 0, + "target": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "target_order": 0, + "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "source_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|outputs|0", + "target_handle": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0|inputs|0", + "type": "buttonedge" + } + ], + "viewport": { + "x": 900.5986504747431, + "y": 420.90015979869725, + "zoom": 0.6903331247004052 + }, + "nodes": [ + { + "width": 320, + "height": 632, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "position": { + "x": -1164.0000230376968, + "y": -501.9869760888273, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": -1164.0000230376968, + "y": -501.9869760888273, + "zoom": 0.0 + }, + "data": { + "label": "Common LLM Http Trigger", + "custom_label": null, + "name": "common_llm_http_trigger", + "description": "Trigger your workflow by http request, and parse the request body as a common LLM http body", + "category": "trigger", + "category_label": "Trigger", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "input", + "inputs": [], + "outputs": [ + { + "type_name": "CommonLLMHttpRequestBody", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpRequestBody", + "label": "Request Body", + "custom_label": null, + "name": "request_body", + "description": "The request body of the API endpoint, parse as a common LLM http body", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "label": "Request String Messages", + "custom_label": null, + "name": "request_string_messages", + "description": "The request string messages of the API endpoint, parsed from 'messages' field of the request body", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": [ + "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpTrigger.MessagesOutputMapper" + ] + } + ], + "version": "v1", + "type_name": "CommonLLMHttpTrigger", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpTrigger", + "parameters": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "API Endpoint", + "name": "endpoint", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "/example/{dag_id}", + "placeholder": null, + "description": "The API endpoint", + "value": "/templates/{dag_id}", + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Http Methods", + "name": "methods", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "POST", + "placeholder": null, + "description": "The methods of the API endpoint", + "value": null, + "options": [ + { + "label": "HTTP Method PUT", + "name": "http_put", + "value": "PUT", + "children": null + }, + { + "label": "HTTP Method POST", + "name": "http_post", + "value": "POST", + "children": null + } + ] + }, + { + "type_name": "bool", + "type_cls": "builtins.bool", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Streaming Response", + "name": "streaming_response", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": false, + "placeholder": null, + "description": "Whether the response is streaming", + "value": false, + "options": null + }, + { + "type_name": "BaseHttpBody", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.BaseHttpBody", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Http Response Body", + "name": "http_response_body", + "is_list": false, + "category": "resource", + "resource_type": "class", + "optional": true, + "default": null, + "placeholder": null, + "description": "The response body of the API endpoint", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Response Media Type", + "name": "response_media_type", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "The response media type", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Http Status Code", + "name": "status_code", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 200, + "placeholder": null, + "description": "The http status code", + "value": null, + "options": null + } + ] + } + }, + { + "width": 320, + "height": 910, + "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "position": { + "x": 661.094354143159, + "y": -368.93541722528227, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": 661.094354143159, + "y": -368.93541722528227, + "zoom": 0.0 + }, + "data": { + "label": "Streaming LLM Operator", + "custom_label": null, + "name": "higher_order_streaming_llm_operator", + "description": "High-level streaming LLM operator, supports multi-round conversation (conversation window, token length and no multi-round).", + "category": "llm", + "category_label": "LLM", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "map", + "inputs": [ + { + "type_name": "CommonLLMHttpRequestBody", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpRequestBody", + "label": "Common LLM Request Body", + "custom_label": null, + "name": "common_llm_request_body", + "description": "The common LLM request body.", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + }, + { + "type_name": "HOContextBody", + "type_cls": "dbgpt.app.operators.llm.HOContextBody", + "label": "Extra Context", + "custom_label": null, + "name": "extra_context", + "description": "Extra context for building prompt(Knowledge context, database schema, etc), you can add multiple context.", + "dynamic": true, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + } + ], + "outputs": [ + { + "type_name": "ModelOutput", + "type_cls": "dbgpt.core.interface.llm.ModelOutput", + "label": "Streaming Model Output", + "custom_label": null, + "name": "streaming_model_output", + "description": "The streaming model output.", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": null + } + ], + "version": "v1", + "type_name": "HOStreamingLLMOperator", + "type_cls": "dbgpt.app.operators.llm.HOStreamingLLMOperator", + "parameters": [ + { + "type_name": "ChatPromptTemplate", + "type_cls": "dbgpt.core.interface.prompt.ChatPromptTemplate", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Prompt Template", + "name": "prompt_template", + "is_list": false, + "category": "resource", + "resource_type": "instance", + "optional": false, + "default": null, + "placeholder": null, + "description": "The prompt template for the conversation.", + "value": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Model Name", + "name": "model", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "The model name.", + "value": null, + "options": null + }, + { + "type_name": "LLMClient", + "type_cls": "dbgpt.core.interface.llm.LLMClient", + "dynamic": false, + "dynamic_minimum": 0, + "label": "LLM Client", + "name": "llm_client", + "is_list": false, + "category": "resource", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "The LLM Client, how to connect to the LLM model, if not provided, it will use the default client deployed by DB-GPT.", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "History Message Merge Mode", + "name": "history_merge_mode", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "none", + "placeholder": null, + "description": "The history merge mode, supports 'none', 'window' and 'token'. 'none': no history merge, 'window': merge by conversation window, 'token': merge by token length.", + "value": "window", + "options": [ + { + "label": "No History", + "name": "none", + "value": "none", + "children": null + }, + { + "label": "Message Window", + "name": "window", + "value": "window", + "children": null + }, + { + "label": "Token Length", + "name": "token", + "value": "token", + "children": null + } + ], + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "select", + "size": null, + "attr": null + } + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "User Message Key", + "name": "user_message_key", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "user_input", + "placeholder": null, + "description": "The key of the user message in your prompt, default is 'user_input'.", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "History Key", + "name": "history_key", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "The chat history key, with chat history message pass to prompt template, if not provided, it will parse the prompt template to get the key.", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Keep Start Rounds", + "name": "keep_start_rounds", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "The start rounds to keep in the chat history.", + "value": 0, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Keep End Rounds", + "name": "keep_end_rounds", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "The end rounds to keep in the chat history.", + "value": 10, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Max Token Limit", + "name": "max_token_limit", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 2048, + "placeholder": null, + "description": "The max token limit to keep in the chat history.", + "value": null, + "options": null + } + ] + } + }, + { + "width": 320, + "height": 774, + "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "position": { + "x": -781.3390803520426, + "y": 112.87665693387501, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": -781.3390803520426, + "y": 112.87665693387501, + "zoom": 0.0 + }, + "data": { + "label": "Knowledge Operator", + "custom_label": null, + "name": "higher_order_knowledge_operator", + "description": "Knowledge Operator, retrieve your knowledge(documents) from knowledge space", + "category": "rag", + "category_label": "RAG", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "map", + "inputs": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "label": "User question", + "custom_label": null, + "name": "query", + "description": "The user question to retrieve the knowledge", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + } + ], + "outputs": [ + { + "type_name": "HOContextBody", + "type_cls": "dbgpt.app.operators.llm.HOContextBody", + "label": "Retrieved context", + "custom_label": null, + "name": "context", + "description": "The retrieved context from the knowledge space", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + }, + { + "type_name": "Chunk", + "type_cls": "dbgpt.core.interface.knowledge.Chunk", + "label": "Chunks", + "custom_label": null, + "name": "chunks", + "description": "The retrieved chunks from the knowledge space", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": [ + "dbgpt.app.operators.rag.HOKnowledgeOperator.ChunkMapper" + ] + } + ], + "version": "v1", + "type_name": "HOKnowledgeOperator", + "type_cls": "dbgpt.app.operators.rag.HOKnowledgeOperator", + "parameters": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Knowledge Space Name", + "name": "knowledge_space", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": false, + "default": null, + "placeholder": null, + "description": "The name of the knowledge space", + "value": "k_cmd2", + "options": [ + { + "label": "k_cmd2", + "name": "k_cmd2", + "value": "k_cmd2", + "children": null + }, + { + "label": "f5", + "name": "f5", + "value": "f5", + "children": null + }, + { + "label": "f4", + "name": "f4", + "value": "f4", + "children": null + }, + { + "label": "t333", + "name": "t333", + "value": "t333", + "children": null + }, + { + "label": "f3", + "name": "f3", + "value": "f3", + "children": null + }, + { + "label": "f1", + "name": "f1", + "value": "f1", + "children": null + }, + { + "label": "sdf", + "name": "sdf", + "value": "sdf", + "children": null + }, + { + "label": "sfsd", + "name": "sfsd", + "value": "sfsd", + "children": null + }, + { + "label": "hello", + "name": "hello", + "value": "hello", + "children": null + }, + { + "label": "k1", + "name": "k1", + "value": "k1", + "children": null + }, + { + "label": "f2", + "name": "f2", + "value": "f2", + "children": null + }, + { + "label": "test_f1", + "name": "test_f1", + "value": "test_f1", + "children": null + }, + { + "label": "SMMF", + "name": "SMMF", + "value": "SMMF", + "children": null + }, + { + "label": "docker_xxx", + "name": "docker_xxx", + "value": "docker_xxx", + "children": null + }, + { + "label": "t2", + "name": "t2", + "value": "t2", + "children": null + }, + { + "label": "t1", + "name": "t1", + "value": "t1", + "children": null + }, + { + "label": "test_graph", + "name": "test_graph", + "value": "test_graph", + "children": null + }, + { + "label": "small", + "name": "small", + "value": "small", + "children": null + }, + { + "label": "ttt", + "name": "ttt", + "value": "ttt", + "children": null + }, + { + "label": "bf", + "name": "bf", + "value": "bf", + "children": null + }, + { + "label": "new_big_file", + "name": "new_big_file", + "value": "new_big_file", + "children": null + }, + { + "label": "test_big_fild", + "name": "test_big_fild", + "value": "test_big_fild", + "children": null + }, + { + "label": "Greenplum", + "name": "Greenplum", + "value": "Greenplum", + "children": null + }, + { + "label": "Mytest", + "name": "Mytest", + "value": "Mytest", + "children": null + }, + { + "label": "dba", + "name": "dba", + "value": "dba", + "children": null + } + ] + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Context Key", + "name": "context", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "context", + "placeholder": null, + "description": "The key of the context, it will be used in building the prompt", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Top K", + "name": "top_k", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 5, + "placeholder": null, + "description": "The number of chunks to retrieve", + "value": null, + "options": null + }, + { + "type_name": "float", + "type_cls": "builtins.float", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Minimum Match Score", + "name": "score_threshold", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 0.3, + "placeholder": null, + "description": "The minimum match score for the retrieved chunks, it will be dropped if the match score is less than the threshold", + "value": null, + "options": null, + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "slider", + "size": null, + "attr": { + "disabled": false, + "min": 0.0, + "max": 1.0, + "step": 0.1 + }, + "show_input": false + } + }, + { + "type_name": "bool", + "type_cls": "builtins.bool", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Reranker Enabled", + "name": "reranker_enabled", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "Whether to enable the reranker", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Reranker Top K", + "name": "reranker_top_k", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 3, + "placeholder": null, + "description": "The top k for the reranker", + "value": null, + "options": null + } + ] + } + }, + { + "width": 320, + "height": 884, + "id": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "position": { + "x": 195.5602050169747, + "y": 175.41495969060128, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": 195.5602050169747, + "y": 175.41495969060128, + "zoom": 0.0 + }, + "data": { + "type_name": "CommonChatPromptTemplate", + "type_cls": "dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate", + "label": "Common Chat Prompt Template", + "custom_label": null, + "name": "common_chat_prompt_template", + "description": "The operator to build the prompt with static prompt.", + "category": "prompt", + "category_label": "Prompt", + "flow_type": "resource", + "icon": null, + "documentation_url": null, + "id": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0", + "ui_size": "large" + }, + "resource_type": "instance", + "parent_cls": [ + "dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate", + "dbgpt.core.interface.prompt.ChatPromptTemplate", + "dbgpt.core.interface.prompt.BasePromptTemplate", + "pydantic.main.BaseModel" + ], + "parameters": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "System Message", + "name": "system_message", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "You are a helpful AI Assistant.", + "placeholder": null, + "description": "The system message.", + "value": "You are a helpful AI assistant.\nBased on the known information below, provide users with professional and concise answers to their questions.\nconstraints:\n 1.Ensure to include original markdown formatting elements such as images, links, tables, or code blocks without alteration in the response if they are present in the provided information.\n For example, image format should be ![image.png](xxx), link format [xxx](xxx), table format should be represented with |xxx|xxx|xxx|, and code format with xxx.\n 2.If the information available in the knowledge base is insufficient to answer the question, state clearly: \"The content provided in the knowledge base is not enough to answer this question,\" and avoid making up answers.\n 3.When responding, it is best to summarize the points in the order of 1, 2, 3, And displayed in markdwon format.\n\nknown information: \n{context}\n\nuser question:\n{user_input}\n\nwhen answering, use the same language as the \"user\".", + "options": null, + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "text_area", + "size": "large", + "attr": { + "disabled": false, + "status": null, + "prefix": null, + "suffix": null, + "show_count": null, + "max_length": null, + "auto_size": { + "min_rows": 2, + "max_rows": 20 + } + }, + "editor": { + "width": 800, + "height": 400 + } + } + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Message placeholder", + "name": "message_placeholder", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "chat_history", + "placeholder": null, + "description": "The chat history message placeholder.", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "Human Message", + "name": "human_message", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "{user_input}", + "placeholder": "{user_input}", + "description": "The human message.", + "value": null, + "options": null, + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "text_area", + "size": "large", + "attr": { + "disabled": false, + "status": null, + "prefix": null, + "suffix": null, + "show_count": null, + "max_length": null, + "auto_size": { + "min_rows": 2, + "max_rows": 20 + } + }, + "editor": { + "width": 800, + "height": 400 + } + } + } + ] + } + }, + { + "width": 320, + "height": 235, + "id": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "position": { + "x": 1087.8490700167088, + "y": 389.9348086323575, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": 1087.8490700167088, + "y": 389.9348086323575, + "zoom": 0.0 + }, + "data": { + "label": "OpenAI Streaming Output Operator", + "custom_label": null, + "name": "openai_streaming_output_operator", + "description": "The OpenAI streaming LLM operator.", + "category": "output_parser", + "category_label": "Output Parser", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "transform_stream", + "inputs": [ + { + "type_name": "ModelOutput", + "type_cls": "dbgpt.core.interface.llm.ModelOutput", + "label": "Upstream Model Output", + "custom_label": null, + "name": "model_output", + "description": "The model output of upstream.", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": null + } + ], + "outputs": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "label": "Model Output", + "custom_label": null, + "name": "model_output", + "description": "The model output after transformed to openai stream format.", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": null + } + ], + "version": "v1", + "type_name": "OpenAIStreamingOutputOperator", + "type_cls": "dbgpt.model.utils.chatgpt_utils.OpenAIStreamingOutputOperator", + "parameters": [] + } + } + ] + } + } +} \ No newline at end of file diff --git a/dbgpt/serve/flow/templates/zh/rag-chat-awel-flow-template.json b/dbgpt/serve/flow/templates/zh/rag-chat-awel-flow-template.json new file mode 100644 index 000000000..d9eb0bba4 --- /dev/null +++ b/dbgpt/serve/flow/templates/zh/rag-chat-awel-flow-template.json @@ -0,0 +1,1088 @@ +{ + "flow": { + "uid": "f947dc6d-a6e6-4f02-b794-14989944173d", + "label": "RAG Chat AWEL flow template", + "name": "rag_chat_awel_flow_template_zh", + "flow_category": "chat_flow", + "description": "知识库对话的 AWEL flow 模板", + "state": "running", + "error_message": "", + "source": "DBGPT-WEB", + "source_url": null, + "version": "0.1.1", + "define_type": "json", + "editable": true, + "user_name": null, + "sys_code": null, + "dag_id": "flow_dag_rag_chat_awel_flow_template_zh_f947dc6d-a6e6-4f02-b794-14989944173d", + "gmt_created": "2024-09-06 17:12:50", + "gmt_modified": "2024-09-06 17:12:50", + "metadata": { + "sse_output": true, + "streaming_output": true, + "tags": {}, + "triggers": [ + { + "trigger_type": "http", + "path": "/api/v1/awel/trigger/example/flow_dag_rag_chat_awel_flow_template_zh_f947dc6d-a6e6-4f02-b794-14989944173d", + "methods": [ + "POST" + ], + "trigger_mode": "chat" + } + ] + }, + "variables": null, + "authors": null, + "flow_data": { + "edges": [ + { + "source": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "source_order": 0, + "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "target_order": 0, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_handle": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|outputs|0", + "target_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|inputs|0", + "type": "buttonedge" + }, + { + "source": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "source_order": 1, + "target": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "target_order": 0, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "source_handle": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0|outputs|1", + "target_handle": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0|inputs|0", + "type": "buttonedge" + }, + { + "source": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "source_order": 0, + "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "target_order": 1, + "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0|operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_handle": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0|outputs|0", + "target_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|inputs|1", + "type": "buttonedge" + }, + { + "source": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "source_order": 0, + "target": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "target_order": 0, + "id": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0|operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_handle": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0|outputs|0", + "target_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|parameters|0", + "type": "buttonedge" + }, + { + "source": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "source_order": 0, + "target": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "target_order": 0, + "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "source_handle": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0|outputs|0", + "target_handle": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0|inputs|0", + "type": "buttonedge" + } + ], + "viewport": { + "x": 669.8345476335454, + "y": 80.57987544808663, + "zoom": 0.7037219281316303 + }, + "nodes": [ + { + "width": 320, + "height": 632, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "position": { + "x": -859.6394981135705, + "y": 19.187128710000025, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": -859.6394981135705, + "y": 19.187128710000025, + "zoom": 0.0 + }, + "data": { + "label": "常见 LLM Http 触发器", + "custom_label": null, + "name": "common_llm_http_trigger", + "description": "通过 HTTP 请求触发您的工作流,并将请求主体解析为常见的 LLM HTTP 主体", + "category": "trigger", + "category_label": "Trigger", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_common_llm_http_trigger___$$___trigger___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "input", + "inputs": [], + "outputs": [ + { + "type_name": "CommonLLMHttpRequestBody", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpRequestBody", + "label": "请求体", + "custom_label": null, + "name": "request_body", + "description": "API 端点的请求主体,解析为常见的 LLM HTTP 主体", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "label": "Request String Messages", + "custom_label": null, + "name": "request_string_messages", + "description": "The request string messages of the API endpoint, parsed from 'messages' field of the request body", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": [ + "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpTrigger.MessagesOutputMapper" + ] + } + ], + "version": "v1", + "type_name": "CommonLLMHttpTrigger", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpTrigger", + "parameters": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "API 端点", + "name": "endpoint", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "/example/{dag_id}", + "placeholder": null, + "description": "API 端点", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "HTTP 方法", + "name": "methods", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "POST", + "placeholder": null, + "description": "API 端点的方法", + "value": null, + "options": [ + { + "label": "HTTP PUT 方法", + "name": "http_put", + "value": "PUT", + "children": null + }, + { + "label": "HTTP POST 方法", + "name": "http_post", + "value": "POST", + "children": null + } + ] + }, + { + "type_name": "bool", + "type_cls": "builtins.bool", + "dynamic": false, + "dynamic_minimum": 0, + "label": "流式响应", + "name": "streaming_response", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": false, + "placeholder": null, + "description": "响应是否为流式传输", + "value": false, + "options": null + }, + { + "type_name": "BaseHttpBody", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.BaseHttpBody", + "dynamic": false, + "dynamic_minimum": 0, + "label": "HTTP 响应主体", + "name": "http_response_body", + "is_list": false, + "category": "resource", + "resource_type": "class", + "optional": true, + "default": null, + "placeholder": null, + "description": "API 端点的响应主体", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "响应媒体类型", + "name": "response_media_type", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "响应的媒体类型", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "HTTP 状态码", + "name": "status_code", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 200, + "placeholder": null, + "description": "HTTP 状态码", + "value": null, + "options": null + } + ] + } + }, + { + "width": 320, + "height": 910, + "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "position": { + "x": 894.6387897331704, + "y": 11.20538256124324, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": 894.6387897331704, + "y": 11.20538256124324, + "zoom": 0.0 + }, + "data": { + "label": "流式 LLM 算子", + "custom_label": null, + "name": "higher_order_streaming_llm_operator", + "description": "高级流式 LLM 算子,支持多轮对话(对话窗口、Token 长度和无多轮)。", + "category": "llm", + "category_label": "LLM", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_higher_order_streaming_llm_operator___$$___llm___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "map", + "inputs": [ + { + "type_name": "CommonLLMHttpRequestBody", + "type_cls": "dbgpt.core.awel.trigger.http_trigger.CommonLLMHttpRequestBody", + "label": "通用 LLM 请求体", + "custom_label": null, + "name": "common_llm_request_body", + "description": "通用 LLM 请求体。", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + }, + { + "type_name": "HOContextBody", + "type_cls": "dbgpt.app.operators.llm.HOContextBody", + "label": "额外上下文", + "custom_label": null, + "name": "extra_context", + "description": "用于构建提示的额外上下文(知识上下文、数据库架构等),您可以添加多个上下文。", + "dynamic": true, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + } + ], + "outputs": [ + { + "type_name": "ModelOutput", + "type_cls": "dbgpt.core.interface.llm.ModelOutput", + "label": "流式模型输出", + "custom_label": null, + "name": "streaming_model_output", + "description": "流式模型输出。", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": null + } + ], + "version": "v1", + "type_name": "HOStreamingLLMOperator", + "type_cls": "dbgpt.app.operators.llm.HOStreamingLLMOperator", + "parameters": [ + { + "type_name": "ChatPromptTemplate", + "type_cls": "dbgpt.core.interface.prompt.ChatPromptTemplate", + "dynamic": false, + "dynamic_minimum": 0, + "label": "提示模板", + "name": "prompt_template", + "is_list": false, + "category": "resource", + "resource_type": "instance", + "optional": false, + "default": null, + "placeholder": null, + "description": "对话的提示模板。", + "value": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "模型名称", + "name": "model", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "模型名称。", + "value": null, + "options": null + }, + { + "type_name": "LLMClient", + "type_cls": "dbgpt.core.interface.llm.LLMClient", + "dynamic": false, + "dynamic_minimum": 0, + "label": "LLM 客户端", + "name": "llm_client", + "is_list": false, + "category": "resource", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "LLM 客户端,如何连接到 LLM 模型,如果未提供,将使用 DB-GPT 部署的默认客户端。", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "历史消息合并模式", + "name": "history_merge_mode", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "none", + "placeholder": null, + "description": "历史合并模式,支持 'none','window' 和 'token'。'none':不合并历史,'window':按对话窗口合并,'token':按 Token 长度合并。", + "value": null, + "options": [ + { + "label": "No History", + "name": "none", + "value": "none", + "children": null + }, + { + "label": "Message Window", + "name": "window", + "value": "window", + "children": null + }, + { + "label": "Token Length", + "name": "token", + "value": "token", + "children": null + } + ], + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "select", + "size": null, + "attr": null + } + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "用户消息键", + "name": "user_message_key", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "user_input", + "placeholder": null, + "description": "提示中用户消息的键,默认为 'user_input'。", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "历史键", + "name": "history_key", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "聊天历史键,将聊天历史消息传递给提示模板,如果未提供,它将解析提示模板以获取键。", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "保留起始轮数", + "name": "keep_start_rounds", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "在聊天历史中保留的起始轮数。", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "保留结束轮数", + "name": "keep_end_rounds", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "在聊天历史中保留的结束轮数。", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "最大 Token 限制", + "name": "max_token_limit", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 2048, + "placeholder": null, + "description": "在聊天历史中保留的最大 Token 限制。", + "value": null, + "options": null + } + ] + } + }, + { + "width": 320, + "height": 774, + "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "position": { + "x": -490.28264249580883, + "y": 867.094350868951, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": -490.28264249580883, + "y": 867.094350868951, + "zoom": 0.0 + }, + "data": { + "label": "知识算子", + "custom_label": null, + "name": "higher_order_knowledge_operator", + "description": "知识算子,从知识空间检索您的知识(文档)", + "category": "rag", + "category_label": "RAG", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_higher_order_knowledge_operator___$$___rag___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "map", + "inputs": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "label": "用户问题", + "custom_label": null, + "name": "query", + "description": "用于检索知识的用户问题", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + } + ], + "outputs": [ + { + "type_name": "HOContextBody", + "type_cls": "dbgpt.app.operators.llm.HOContextBody", + "label": "检索到的上下文", + "custom_label": null, + "name": "context", + "description": "从知识空间检索到的上下文", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": false, + "mappers": null + }, + { + "type_name": "Chunk", + "type_cls": "dbgpt.core.interface.knowledge.Chunk", + "label": "块", + "custom_label": null, + "name": "chunks", + "description": "从知识空间检索到的块", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": [ + "dbgpt.app.operators.rag.HOKnowledgeOperator.ChunkMapper" + ] + } + ], + "version": "v1", + "type_name": "HOKnowledgeOperator", + "type_cls": "dbgpt.app.operators.rag.HOKnowledgeOperator", + "parameters": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "知识空间名称", + "name": "knowledge_space", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": false, + "default": null, + "placeholder": null, + "description": "知识空间的名称", + "value": "k_cmd2", + "options": [ + { + "label": "k_cmd2", + "name": "k_cmd2", + "value": "k_cmd2", + "children": null + }, + { + "label": "f5", + "name": "f5", + "value": "f5", + "children": null + }, + { + "label": "f4", + "name": "f4", + "value": "f4", + "children": null + }, + { + "label": "t333", + "name": "t333", + "value": "t333", + "children": null + }, + { + "label": "f3", + "name": "f3", + "value": "f3", + "children": null + }, + { + "label": "f1", + "name": "f1", + "value": "f1", + "children": null + }, + { + "label": "sdf", + "name": "sdf", + "value": "sdf", + "children": null + }, + { + "label": "sfsd", + "name": "sfsd", + "value": "sfsd", + "children": null + }, + { + "label": "hello", + "name": "hello", + "value": "hello", + "children": null + }, + { + "label": "k1", + "name": "k1", + "value": "k1", + "children": null + }, + { + "label": "f2", + "name": "f2", + "value": "f2", + "children": null + }, + { + "label": "test_f1", + "name": "test_f1", + "value": "test_f1", + "children": null + }, + { + "label": "SMMF", + "name": "SMMF", + "value": "SMMF", + "children": null + }, + { + "label": "docker_xxx", + "name": "docker_xxx", + "value": "docker_xxx", + "children": null + }, + { + "label": "t2", + "name": "t2", + "value": "t2", + "children": null + }, + { + "label": "t1", + "name": "t1", + "value": "t1", + "children": null + }, + { + "label": "test_graph", + "name": "test_graph", + "value": "test_graph", + "children": null + }, + { + "label": "small", + "name": "small", + "value": "small", + "children": null + }, + { + "label": "ttt", + "name": "ttt", + "value": "ttt", + "children": null + }, + { + "label": "bf", + "name": "bf", + "value": "bf", + "children": null + }, + { + "label": "new_big_file", + "name": "new_big_file", + "value": "new_big_file", + "children": null + }, + { + "label": "test_big_fild", + "name": "test_big_fild", + "value": "test_big_fild", + "children": null + }, + { + "label": "Greenplum", + "name": "Greenplum", + "value": "Greenplum", + "children": null + }, + { + "label": "Mytest", + "name": "Mytest", + "value": "Mytest", + "children": null + }, + { + "label": "dba", + "name": "dba", + "value": "dba", + "children": null + } + ] + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "上下文键", + "name": "context", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "context", + "placeholder": null, + "description": "上下文的键,将用于构建提示", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "前 K", + "name": "top_k", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 5, + "placeholder": null, + "description": "要检索的块数", + "value": null, + "options": null + }, + { + "type_name": "float", + "type_cls": "builtins.float", + "dynamic": false, + "dynamic_minimum": 0, + "label": "最低匹配分数", + "name": "score_threshold", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 0.3, + "placeholder": null, + "description": "检索到的块的最低匹配分数,如果匹配分数低于阈值,将被丢弃", + "value": null, + "options": null, + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "slider", + "size": null, + "attr": { + "disabled": false, + "min": 0.0, + "max": 1.0, + "step": 0.1 + }, + "show_input": false + } + }, + { + "type_name": "bool", + "type_cls": "builtins.bool", + "dynamic": false, + "dynamic_minimum": 0, + "label": "启用重新排序器", + "name": "reranker_enabled", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": null, + "placeholder": null, + "description": "是否启用重新排序器", + "value": null, + "options": null + }, + { + "type_name": "int", + "type_cls": "builtins.int", + "dynamic": false, + "dynamic_minimum": 0, + "label": "重新排序器前 K", + "name": "reranker_top_k", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": 3, + "placeholder": null, + "description": "重新排序器的前 K", + "value": null, + "options": null + } + ] + } + }, + { + "width": 530, + "height": 774, + "id": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "position": { + "x": 215.72827911579407, + "y": 823.7825153084143, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": 215.72827911579407, + "y": 823.7825153084143, + "zoom": 0.0 + }, + "data": { + "type_name": "CommonChatPromptTemplate", + "type_cls": "dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate", + "label": "常见聊天提示模板", + "custom_label": null, + "name": "common_chat_prompt_template", + "description": "用静态提示构建提示的原子。", + "category": "prompt", + "category_label": "Prompt", + "flow_type": "resource", + "icon": null, + "documentation_url": null, + "id": "resource_dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0", + "ui_size": "large" + }, + "resource_type": "instance", + "parent_cls": [ + "dbgpt.core.interface.operators.prompt_operator.CommonChatPromptTemplate", + "dbgpt.core.interface.prompt.ChatPromptTemplate", + "dbgpt.core.interface.prompt.BasePromptTemplate", + "pydantic.main.BaseModel" + ], + "parameters": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "系统消息", + "name": "system_message", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "You are a helpful AI Assistant.", + "placeholder": null, + "description": "系统消息。", + "value": "基于以下给出的已知信息, 准守规范约束,专业、简要回答用户的问题.\n规范约束:\n 1.如果已知信息包含的图片、链接、表格、代码块等特殊markdown标签格式的信息,确保在答案中包含原文这些图片、链接、表格和代码标签,不要丢弃不要修改,如:图片格式:![image.png](xxx), 链接格式:[xxx](xxx), 表格格式:|xxx|xxx|xxx|, 代码格式:```xxx```.\n 2.如果无法从提供的内容中获取答案, 请说: \"知识库中提供的内容不足以回答此问题\" 禁止胡乱编造.\n 3.回答的时候最好按照1.2.3.点进行总结, 并以markdwon格式显示.\n\n已知内容: \n{context}\n问题:\n{user_input}, 请使用和用户相同的语言进行回答.\n", + "options": null, + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "text_area", + "size": "large", + "attr": { + "disabled": false, + "status": null, + "prefix": null, + "suffix": null, + "show_count": null, + "max_length": null, + "auto_size": { + "min_rows": 2, + "max_rows": 20 + } + }, + "editor": { + "width": 800, + "height": 400 + } + } + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "消息占位符", + "name": "message_placeholder", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "chat_history", + "placeholder": null, + "description": "聊天历史消息占位符。", + "value": null, + "options": null + }, + { + "type_name": "str", + "type_cls": "builtins.str", + "dynamic": false, + "dynamic_minimum": 0, + "label": "用户消息", + "name": "human_message", + "is_list": false, + "category": "common", + "resource_type": "instance", + "optional": true, + "default": "{user_input}", + "placeholder": "{user_input}", + "description": "用户消息。", + "value": null, + "options": null, + "ui": { + "refresh": false, + "refresh_depends": null, + "ui_type": "text_area", + "size": "large", + "attr": { + "disabled": false, + "status": null, + "prefix": null, + "suffix": null, + "show_count": null, + "max_length": null, + "auto_size": { + "min_rows": 2, + "max_rows": 20 + } + }, + "editor": { + "width": 800, + "height": 400 + } + } + } + ] + } + }, + { + "width": 320, + "height": 235, + "id": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "position": { + "x": 1364.8014471050712, + "y": 723.8593282560083, + "zoom": 0.0 + }, + "type": "customNode", + "position_absolute": { + "x": 1364.8014471050712, + "y": 723.8593282560083, + "zoom": 0.0 + }, + "data": { + "label": "OpenAI 流式输出算子", + "custom_label": null, + "name": "openai_streaming_output_operator", + "description": "OpenAI 流式 LLM 算子。", + "category": "output_parser", + "category_label": "Output Parser", + "flow_type": "operator", + "icon": null, + "documentation_url": null, + "id": "operator_openai_streaming_output_operator___$$___output_parser___$$___v1_0", + "tags": { + "order": "higher-order", + "ui_version": "flow2.0" + }, + "operator_type": "transform_stream", + "inputs": [ + { + "type_name": "ModelOutput", + "type_cls": "dbgpt.core.interface.llm.ModelOutput", + "label": "上游模型输出", + "custom_label": null, + "name": "model_output", + "description": "上游模型的输出。", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": null + } + ], + "outputs": [ + { + "type_name": "str", + "type_cls": "builtins.str", + "label": "模型输出", + "custom_label": null, + "name": "model_output", + "description": "转换为 OpenAI 流格式后的模型输出。", + "dynamic": false, + "dynamic_minimum": 0, + "is_list": true, + "mappers": null + } + ], + "version": "v1", + "type_name": "OpenAIStreamingOutputOperator", + "type_cls": "dbgpt.model.utils.chatgpt_utils.OpenAIStreamingOutputOperator", + "parameters": [] + } + } + ] + } + } +} \ No newline at end of file diff --git a/dbgpt/serve/rag/operators/knowledge_space.py b/dbgpt/serve/rag/operators/knowledge_space.py index c37495ed5..3d2e1d846 100644 --- a/dbgpt/serve/rag/operators/knowledge_space.py +++ b/dbgpt/serve/rag/operators/knowledge_space.py @@ -223,7 +223,7 @@ class KnowledgeSpacePromptBuilderOperator( self._prompt = prompt self._history_key = history_key self._str_history = str_history - BasePromptBuilderOperator.__init__(self, check_storage=check_storage) + BasePromptBuilderOperator.__init__(self, check_storage=check_storage, **kwargs) JoinOperator.__init__(self, combine_function=self.merge_context, **kwargs) @rearrange_args_by_type diff --git a/dbgpt/storage/metadata/_base_dao.py b/dbgpt/storage/metadata/_base_dao.py index 04b74e81c..199b3eabc 100644 --- a/dbgpt/storage/metadata/_base_dao.py +++ b/dbgpt/storage/metadata/_base_dao.py @@ -285,6 +285,9 @@ class BaseDao(Generic[T, REQ, RES]): else model_to_dict(query_request) ) for key, value in query_dict.items(): + if value and isinstance(value, (list, tuple, dict, set)): + # Skip the list, tuple, dict, set + continue if value is not None and hasattr(model_cls, key): if isinstance(value, list): if len(value) > 0: diff --git a/dbgpt/util/net_utils.py b/dbgpt/util/net_utils.py index fc9fb3f86..ce41ba781 100644 --- a/dbgpt/util/net_utils.py +++ b/dbgpt/util/net_utils.py @@ -1,5 +1,6 @@ import errno import socket +from typing import Set, Tuple def _get_ip_address(address: str = "10.254.254.254:1") -> str: @@ -22,3 +23,34 @@ def _get_ip_address(address: str = "10.254.254.254:1") -> str: finally: s.close() return curr_address + + +async def _async_get_free_port( + port_range: Tuple[int, int], timeout: int, used_ports: Set[int] +): + import asyncio + + loop = asyncio.get_running_loop() + return await loop.run_in_executor( + None, _get_free_port, port_range, timeout, used_ports + ) + + +def _get_free_port(port_range: Tuple[int, int], timeout: int, used_ports: Set[int]): + import random + + available_ports = set(range(port_range[0], port_range[1] + 1)) - used_ports + if not available_ports: + raise RuntimeError("No available ports in the specified range") + + while available_ports: + port = random.choice(list(available_ports)) + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", port)) + used_ports.add(port) + return port + except OSError: + available_ports.remove(port) + + raise RuntimeError("No available ports in the specified range") diff --git a/dbgpt/util/pagination_utils.py b/dbgpt/util/pagination_utils.py index f8c20ccd9..5b67333c6 100644 --- a/dbgpt/util/pagination_utils.py +++ b/dbgpt/util/pagination_utils.py @@ -15,3 +15,29 @@ class PaginationResult(BaseModel, Generic[T]): total_pages: int = Field(..., description="total number of pages") page: int = Field(..., description="Current page number") page_size: int = Field(..., description="Number of items per page") + + @classmethod + def build_from_all( + cls, all_items: List[T], page: int, page_size: int + ) -> "PaginationResult[T]": + """Build a pagination result from all items""" + if page < 1: + page = 1 + if page_size < 1: + page_size = 1 + total_count = len(all_items) + total_pages = ( + (total_count + page_size - 1) // page_size if total_count > 0 else 0 + ) + page = max(1, min(page, total_pages)) if total_pages > 0 else 0 + start_index = (page - 1) * page_size if page > 0 else 0 + end_index = min(start_index + page_size, total_count) + items = all_items[start_index:end_index] + + return cls( + items=items, + total_count=total_count, + total_pages=total_pages, + page=page, + page_size=page_size, + ) diff --git a/dbgpt/util/serialization/check.py b/dbgpt/util/serialization/check.py new file mode 100644 index 000000000..d10d308b7 --- /dev/null +++ b/dbgpt/util/serialization/check.py @@ -0,0 +1,87 @@ +import inspect +from io import StringIO +from typing import Any, Dict, Optional, TextIO + + +def check_serializable( + obj: Any, obj_name: str = "Object", error_msg: str = "Object is not serializable" +): + import cloudpickle + + try: + cloudpickle.dumps(obj) + except Exception as e: + inspect_info = inspect_serializability(obj, obj_name) + msg = f"{error_msg}\n{inspect_info['report']}" + raise TypeError(msg) from e + + +class SerializabilityInspector: + def __init__(self, stream: Optional[TextIO] = None): + self.stream = stream or StringIO() + self.failures = {} + self.indent_level = 0 + + def log(self, message: str): + indent = " " * self.indent_level + self.stream.write(f"{indent}{message}\n") + + def inspect(self, obj: Any, name: str, depth: int = 3) -> bool: + import cloudpickle + + self.log(f"Inspecting '{name}'") + self.indent_level += 1 + + try: + cloudpickle.dumps(obj) + self.indent_level -= 1 + return True + except Exception as e: + self.failures[name] = str(e) + self.log(f"Failure: {str(e)}") + + if depth > 0: + if inspect.isfunction(obj) or inspect.ismethod(obj): + self._inspect_function(obj, depth - 1) + elif hasattr(obj, "__dict__"): + self._inspect_object(obj, depth - 1) + + self.indent_level -= 1 + return False + + def _inspect_function(self, func, depth): + closure = inspect.getclosurevars(func) + for name, value in closure.nonlocals.items(): + self.inspect(value, f"{func.__name__}.{name}", depth) + for name, value in closure.globals.items(): + self.inspect(value, f"global:{name}", depth) + + def _inspect_object(self, obj, depth): + for name, value in inspect.getmembers(obj): + if not name.startswith("__"): + self.inspect(value, f"{type(obj).__name__}.{name}", depth) + + def get_report(self) -> str: + summary = "\nSummary of Serialization Failures:\n" + if not self.failures: + summary += "All components are serializable.\n" + else: + for name, error in self.failures.items(): + summary += f" - {name}: {error}\n" + + return self.stream.getvalue() + summary + + +def inspect_serializability( + obj: Any, + name: Optional[str] = None, + depth: int = 5, + stream: Optional[TextIO] = None, +) -> Dict[str, Any]: + inspector = SerializabilityInspector(stream) + success = inspector.inspect(obj, name or type(obj).__name__, depth) + return { + "success": success, + "failures": inspector.failures, + "report": inspector.get_report(), + } diff --git a/dbgpt/util/tests/test_pagination_utils.py b/dbgpt/util/tests/test_pagination_utils.py new file mode 100644 index 000000000..d0d2132c5 --- /dev/null +++ b/dbgpt/util/tests/test_pagination_utils.py @@ -0,0 +1,84 @@ +from dbgpt.util.pagination_utils import PaginationResult + + +def test_build_from_all_normal_case(): + items = list(range(100)) + result = PaginationResult.build_from_all(items, page=2, page_size=20) + + assert len(result.items) == 20 + assert result.items == list(range(20, 40)) + assert result.total_count == 100 + assert result.total_pages == 5 + assert result.page == 2 + assert result.page_size == 20 + + +def test_build_from_all_empty_list(): + items = [] + result = PaginationResult.build_from_all(items, page=1, page_size=5) + + assert result.items == [] + assert result.total_count == 0 + assert result.total_pages == 0 + assert result.page == 0 + assert result.page_size == 5 + + +def test_build_from_all_last_page(): + items = list(range(95)) + result = PaginationResult.build_from_all(items, page=5, page_size=20) + + assert len(result.items) == 15 + assert result.items == list(range(80, 95)) + assert result.total_count == 95 + assert result.total_pages == 5 + assert result.page == 5 + assert result.page_size == 20 + + +def test_build_from_all_page_out_of_range(): + items = list(range(50)) + result = PaginationResult.build_from_all(items, page=10, page_size=10) + + assert len(result.items) == 10 + assert result.items == list(range(40, 50)) + assert result.total_count == 50 + assert result.total_pages == 5 + assert result.page == 5 + assert result.page_size == 10 + + +def test_build_from_all_page_zero(): + items = list(range(50)) + result = PaginationResult.build_from_all(items, page=0, page_size=10) + + assert len(result.items) == 10 + assert result.items == list(range(0, 10)) + assert result.total_count == 50 + assert result.total_pages == 5 + assert result.page == 1 + assert result.page_size == 10 + + +def test_build_from_all_negative_page(): + items = list(range(50)) + result = PaginationResult.build_from_all(items, page=-1, page_size=10) + + assert len(result.items) == 10 + assert result.items == list(range(0, 10)) + assert result.total_count == 50 + assert result.total_pages == 5 + assert result.page == 1 + assert result.page_size == 10 + + +def test_build_from_all_page_size_larger_than_total(): + items = list(range(50)) + result = PaginationResult.build_from_all(items, page=1, page_size=100) + + assert len(result.items) == 50 + assert result.items == list(range(50)) + assert result.total_count == 50 + assert result.total_pages == 1 + assert result.page == 1 + assert result.page_size == 100 diff --git a/docker/base/build_image.sh b/docker/base/build_image.sh index 028dcc809..08cd0b549 100755 --- a/docker/base/build_image.sh +++ b/docker/base/build_image.sh @@ -20,16 +20,21 @@ LOAD_EXAMPLES="true" BUILD_NETWORK="" DB_GPT_INSTALL_MODEL="default" +DOCKERFILE="Dockerfile" +IMAGE_NAME_SUFFIX="" + usage () { echo "USAGE: $0 [--base-image nvidia/cuda:12.1.0-runtime-ubuntu22.04] [--image-name db-gpt]" echo " [-b|--base-image base image name] Base image name" echo " [-n|--image-name image name] Current image name, default: db-gpt" + echo " [--image-name-suffix image name suffix] Image name suffix" echo " [-i|--pip-index-url pip index url] Pip index url, default: https://pypi.org/simple" echo " [--language en or zh] You language, default: en" echo " [--build-local-code true or false] Whether to use the local project code to package the image, default: true" echo " [--load-examples true or false] Whether to load examples to default database default: true" echo " [--network network name] The network of docker build" echo " [--install-mode mode name] Installation mode name, default: default, If you completely use openai's service, you can set the mode name to 'openai'" + echo " [-f|--dockerfile dockerfile] Dockerfile name, default: Dockerfile" echo " [-h|--help] Usage message" } @@ -46,6 +51,11 @@ while [[ $# -gt 0 ]]; do shift # past argument shift # past value ;; + --image-name-suffix) + IMAGE_NAME_SUFFIX="$2" + shift # past argument + shift # past value + ;; -i|--pip-index-url) PIP_INDEX_URL="$2" shift @@ -80,6 +90,11 @@ while [[ $# -gt 0 ]]; do shift # past argument shift # past value ;; + -f|--dockerfile) + DOCKERFILE="$2" + shift # past argument + shift # past value + ;; -h|--help) help="true" shift @@ -111,6 +126,10 @@ else BASE_IMAGE=$IMAGE_NAME_ARGS fi +if [ -n "$IMAGE_NAME_SUFFIX" ]; then + IMAGE_NAME="$IMAGE_NAME-$IMAGE_NAME_SUFFIX" +fi + echo "Begin build docker image, base image: ${BASE_IMAGE}, target image name: ${IMAGE_NAME}" docker build $BUILD_NETWORK \ @@ -120,5 +139,5 @@ docker build $BUILD_NETWORK \ --build-arg BUILD_LOCAL_CODE=$BUILD_LOCAL_CODE \ --build-arg LOAD_EXAMPLES=$LOAD_EXAMPLES \ --build-arg DB_GPT_INSTALL_MODEL=$DB_GPT_INSTALL_MODEL \ - -f Dockerfile \ + -f $DOCKERFILE \ -t $IMAGE_NAME $WORK_DIR/../../ diff --git a/examples/awel/awel_flow_ui_components.py b/examples/awel/awel_flow_ui_components.py index db92ca09d..ce411a79d 100644 --- a/examples/awel/awel_flow_ui_components.py +++ b/examples/awel/awel_flow_ui_components.py @@ -4,7 +4,7 @@ import json import logging from typing import Any, Dict, List, Optional -from dbgpt.core.awel import MapOperator +from dbgpt.core.awel import JoinOperator, MapOperator from dbgpt.core.awel.flow import ( FunctionDynamicOptions, IOField, @@ -852,7 +852,7 @@ class ExampleFlowUploadOperator(MapOperator[str, str]): ui=ui.UIUpload( max_file_size=1024 * 1024 * 100, up_event="button_click", - file_types=["image/*", "*.pdf"], + file_types=["image/*", ".pdf"], drag=True, attr=ui.UIUpload.UIAttribute(max_count=5), ), @@ -897,7 +897,7 @@ class ExampleFlowUploadOperator(MapOperator[str, str]): files_metadata = await self.blocking_func_to_async( self._parse_files_metadata, fsc ) - files_metadata_str = json.dumps(files_metadata, ensure_ascii=False) + files_metadata_str = json.dumps(files_metadata, ensure_ascii=False, indent=4) return "Your name is %s, and you files are %s." % ( user_name, files_metadata_str, @@ -1243,3 +1243,156 @@ class ExampleFlowCodeEditorOperator(MapOperator[str, str]): if exitcode != 0: return exitcode, logs_all return exitcode, logs_all + + +class ExampleFlowDynamicParametersOperator(MapOperator[str, str]): + """An example flow operator that includes dynamic parameters.""" + + metadata = ViewMetadata( + label="Example Dynamic Parameters Operator", + name="example_dynamic_parameters_operator", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes dynamic parameters.", + parameters=[ + Parameter.build_from( + "Dynamic String", + "dynamic_1", + type=str, + is_list=True, + placeholder="Please input the dynamic parameter", + description="The dynamic parameter you want to use, you can add more, " + "at least 1 parameter.", + dynamic=True, + dynamic_minimum=1, + ui=ui.UIInput(), + ), + Parameter.build_from( + "Dynamic Integer", + "dynamic_2", + type=int, + is_list=True, + placeholder="Please input the dynamic parameter", + description="The dynamic parameter you want to use, you can add more, " + "at least 0 parameter.", + dynamic=True, + dynamic_minimum=0, + ), + ], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ), + ], + outputs=[ + IOField.build_from( + "Dynamic", + "dynamic", + str, + description="User's selected dynamic.", + ), + ], + ) + + def __init__(self, dynamic_1: List[str], dynamic_2: List[int], **kwargs): + super().__init__(**kwargs) + if not dynamic_1: + raise ValueError("The dynamic string is empty.") + self.dynamic_1 = dynamic_1 + self.dynamic_2 = dynamic_2 + + async def map(self, user_name: str) -> str: + """Map the user name to the dynamic.""" + return "Your name is %s, and your dynamic is %s." % ( + user_name, + f"dynamic_1: {self.dynamic_1}, dynamic_2: {self.dynamic_2}", + ) + + +class ExampleFlowDynamicOutputsOperator(MapOperator[str, str]): + """An example flow operator that includes dynamic outputs.""" + + metadata = ViewMetadata( + label="Example Dynamic Outputs Operator", + name="example_dynamic_outputs_operator", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes dynamic outputs.", + parameters=[], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ), + ], + outputs=[ + IOField.build_from( + "Dynamic", + "dynamic", + str, + description="User's selected dynamic.", + dynamic=True, + dynamic_minimum=1, + ), + ], + ) + + async def map(self, user_name: str) -> str: + """Map the user name to the dynamic.""" + return "Your name is %s, this operator has dynamic outputs." % user_name + + +class ExampleFlowDynamicInputsOperator(JoinOperator[str]): + """An example flow operator that includes dynamic inputs.""" + + metadata = ViewMetadata( + label="Example Dynamic Inputs Operator", + name="example_dynamic_inputs_operator", + category=OperatorCategory.EXAMPLE, + description="An example flow operator that includes dynamic inputs.", + parameters=[], + inputs=[ + IOField.build_from( + "User Name", + "user_name", + str, + description="The name of the user.", + ), + IOField.build_from( + "Other Inputs", + "other_inputs", + str, + description="Other inputs.", + dynamic=True, + dynamic_minimum=0, + ), + ], + outputs=[ + IOField.build_from( + "Dynamic", + "dynamic", + str, + description="User's selected dynamic.", + ), + ], + ) + + def __init__(self, **kwargs): + super().__init__(combine_function=self.join, **kwargs) + + async def join(self, user_name: str, *other_inputs: str) -> str: + """Map the user name to the dynamic.""" + if not other_inputs: + dyn_inputs = ["You have no other inputs."] + else: + dyn_inputs = [ + f"Input {i}: {input_data}" for i, input_data in enumerate(other_inputs) + ] + dyn_str = "\n".join(dyn_inputs) + return "Your name is %s, and your dynamic is %s." % ( + user_name, + f"other_inputs:\n{dyn_str}", + ) diff --git a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.mo b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.mo new file mode 100644 index 0000000000000000000000000000000000000000..20fc41aca2f8f1cd05cb159a746ff6c95b542624 GIT binary patch literal 1197 zcmZ{i%}*0S6u?)(uT^6-@uD7vM5BorEQAnT^-!>yqEM`rD_PoUySCj~cW3!f4@E@~ zAtFc!MvNXBmGBV{N@=WKO+0%uUhHn`(f`0V-K9m;NnU>YzTcbK?+0pU1UP#@j)2?- z*$?s!gdem;5Do$BfCqtRfjl+{+zae+nF0ntzXd!7`~u{4egTgHYqmly;0a(I&<_;B zmjE6Hy|2cpKLy+e`fHb2An*4Xcm?;Mr#c7tpK z;RoF1y3oFIcEZ>8iY5rV__sGkREAo0Sq;ZgJ5^K^(1f1W^@nt6Xj^hnYfEooG~NNF@V?M&tz14Md~mDBUSu zFSMHmLkU8(vN@{gpsG~GB&D%IM9Tx!3n3`%?6lG1v~o)!M%~`)OF3zZ3M-*LIcbvh z=G46lC%~!rzlbiKUMFFO{Sn*^^TSdS>hiW-@?P|zZeQ@KKhP-#%WL8eBc?=fs~IzJ zh)PK83g95=Q+N(OFEb*cMo(iC_MFC^X4KFuH8h>{w0Jz?W!Ydj1aD;|`3p(NYciUW znG%+h$w377$)AoyAW)5&urv-4IVJIHA{Cp2ZpM4sI>iouhp#$mL%m0Aqbh^H0fz=P z7=e)ic1laiih2fxGVoPaNJ!n5X?hZ90#C&VUDpuqZW<2!t}d%fLD;;K@e zKpaEt*bv7y!FC`|6Nv3lNE|+F=&dfL8%=e!4pDsLeJHyj}b`_d^24ghv(=Xr$4?l;ovw_zFzYLrOq^UcBw*eOb zF9S9L&jr2!yaw0@{3>t)NObS3{)HvriGI1l2Z3KgKLx}e+lU`p*9N5XysrB10?$L= z1-qR z-ckJ%s(&^%yAb1-0ha-91YQhW3&c^_79dDj7w{6`ZXoeF3cLvT9+2q%2qe0knm+-& z0R6MhXAFPrQvA@m>w$BCw*u*0I*|0d4mbnY4#ZN{10=rtfR_RfDSQ`5cKS1r*1JIB z`w!KhfsT#-6~IbhJMcc>Z-J%2OL3^1fcF5m0G|euJ^l{78+Z|zk{k~LZvZ|4B)e?_ z(z@Ni>w!N9&H|nQz5=`kN+kY201{mecn|PmC}|O}23QX40TSJNz!dNkAdbc&P&!yK z8%TcE4CKIlK-%ZWz}di0fFU4GAhK+V3ME$@woJ@t*-^gjWM; z{uW>Za0&2k;P-*I0Y3qfKhK0S60QWU2d)Rg71$?0^80hRl%oPj_Fe)c{xKl=;rD>F z{(!XFoX!oJfa}OFvdm4>$z!PY6hTwCH z>eK+~Jgd;|RAU5Zs`s0Kq|e7yA7*3o(MSjMP;P#TUhrsWcZfIX6ZBe!b}ia0v2JxD+78))Q<5MAU>vM1^4n`rbPbcL}<2p*@KfL8F+X2W}u9qz_?#lnu5-4v9KIF-1=lEr#|8b@1FELHwSf-X8!`3{;}6 zM5{+5pCG&4g+}(I=XA6>@y6IvAo(l#7(I`oEks*E9X!<%WG%w`&1jFQvE{(C(BOjN zS&ZKY(dMDui&lb0&qHWuqlM8Z#vewbhx~Akc*D;E(~Rqy;UTw8USp?XMm<{?O&bjs zTWBT{TFBCqT1vC^2w$tk)4Ij&sAh93YNq26UZ-G9v+_3faHeaTT_qBS;wz$UYSYN{4!MC?+r)vyySGk=HgRbtV_X zBvGA*mA2QU%!C+-_@|i-LOZ2#Wf0Z#7hv+)C(>~{mW=z0X0yd)OZiKxtC8-uI317kTDS(g zKUNXzwxl2<~mRiqJ%iGGc(rEGu1H{FtymW;B3DSh`ld8mx1l&g_JQ@p`rGBaX^(+Sv?rLQ3r=M*C1I~a2SixXsxi@ z@>n8fv#OX8OQaLLO0&aJUK2J`IwQ5jC3E?@B5fzrHd`tD70#4ERMfphJfm$=km!WGVO#@!M*i0qhnqGv7h=GH{d8L0_Qe29xU?6$)!Wk*M`5Yo@ArH%B zTJzv?9*Ko*=DqMZ3@&;pAvi-c1l-S8Qen7AQWZNA$RoUTUKo+lR_J?fR0|;rCMn}l za~&~202z8%q*@Y_C=#URG9FulxUnf2tc^wV2nKAvE{32@LvunU`Wg+k<6&WB9?_F= zvk?`eu93^6IGfR?Bsxh=yPio;!@XtqO2T72rA)U~f`ZVNi=i4LiV!5svb$ znD8~I0B8x#whJ|&XtnGV0)dXwvg%d%v0xkL{8O^g-LZn)9_CTd6HB3WXA7m{l73HH;6qM50 zT+UWdJt^XXn}(vV;F2w3K%PPsDz7vtRsphLv62F-yGKBbsYUkaK9A63cx2mvl?w6f zIRh-d#tbCrQYlvgDPBoSDW2SC zddp*hr~!swEo9OZP6N+NrwnmARBu6-5VgXhTH(8#=b*^dA|)4z9Z8uo1xd{W*F%Ch zgoMU$uOV&NQEVPH;}N-~C`AV9oRUrSO;L~J2+esapIk3%!LJ~cJXkQA#1@l-o&r?a z&%Xf4ttL~FL(pB0@~U@X3m6k}$nqz{y~t`Bf1qYXwMqoy7*ge)B$Ui$21mA5^YVJ? zDY#HFr;<8gj#FP%C-a?5IXz=px|O*Lrzzwu!QK+rrO%s#EWb*N;>d%B#vCQgLeiN! zS{2#n3051ol!kx@%iD}NwunCQc}3S>F!L13Kl9%5S?l4GzG9iYDAA`KUoAcW*+Uff z-Zga@zUIV$cNDf7mk2m$4Ssa?a8!q1$)OCbS55gbF^d zeBLdg@|#2RZsv36R?M4sQ~4d`aOjI>{P66{ z%)~a98F@Z4vc>J%obMcD&TzB88}@M9c7dh86Y&6r`}SaV<573}@F^+$6$J`UhL6Wv z>FfLB@g4Fj9DRI-(=WcmX?ssBf2qHn+I|X(?Qh{U-swcgU+q)VwE7Nr#+%&EW8zC3 z>)f_MceI1~Z<(XU&22v5bBASojnk3xyZW3xUH%Y*fik08-Tr5tu~E0ZgSf$##Y7*Ew`6zj%?-ic z%61<%ZIC-Squbp6!&sg@_}s_i_-l1H4mj;YnX$d@kum4_SDZaNG9w293pX-f!G$-d z&sQ9CHVwc%SpM~%?C5^sJyKd?-Z|v9ZUE2R#Go^DG&j_Tv&on20r=l;987+(<+mSq zM)x@{wz}PP2GHYpLIHxU`}PKBy#0U9+}Yafw7)4lTfDi4TigvpPDhJ#^sqbdDz=2( z$$1nM$(=dgm)-tSc5Iv5w-e_jDmj#U`-roDiyD%aa9%!^8$QHjM>#{V16^gtwyKfW zM)Tcoxx#N?9oScnLv_%z`|56J#^WSNosPEb{$WPnUC!Vxx8+Fo^&!#oyso#&*|VPx z+TSZ=5qcV9xntX$&7({@9)=JF z%;^|{Q|33fxn0BV))$@8cITx5HmUDC*M^T~XImQ*k^9OxDHif#7a3hrSPCoQoMd-7 zt2W@d|B=+C&@Uy2cX)_v6pFNxH&U%obs{CO8I97vJ-Do zw2$@V2KV@uCS_$i56Rf*-08M-fW~Rvl-s?@=^c=2$&4JtU~b7Z{Y$nHhg2-}GX3 z?4_U=POZX06vpP<{@reWN01oFF1PiF)82)^7b#|nx<})F76hHlk(7NWdZhz9tzD8S zYf-l|BW;p{U>99Zi`JNVB?xcXZU9V?G zb~)qEFqw8_W~beQ36T21I)oin383^LFEbeG>!uQ+Y>8uws|fg@A_xw_Yl_J7bo21; zD?#d_qEMt&@fE3x1!Z`ZyF?kIIJvk?t|Zei@ZIB-Yzn1k8dC?a0Yb~7 z{7lyMbXn9ZK(u-q$M#DS(M>A6WIj=QrF);O7r*F&Qo4D*doaJN$Gd4ti;L^zWD3Pt ld{Gd&A0mh;)mOZnju-Nq+ns|w&c*@e_TYxKZ|ZBqzX236wBrB( delta 125 zcmez4^^Ymxo)F7a1|VPpVi_RT0b*7lwgF-g2moS!APxj#E=C51L?A5)#5q7V5WxXR nKNz?;>ADAmDEJqo<|#M^cy3-I&ci4K6@0p<;n|dZAYldooZ1sW diff --git a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_app.po b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_app.po index 5505757d1..a2655e159 100644 --- a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_app.po +++ b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_app.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-17 00:05+0800\n" +"POT-Creation-Date: 2024-09-06 16:14+0800\n" "PO-Revision-Date: 2024-03-23 11:22+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,6 +17,447 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: ../dbgpt/app/dbgpt_server.py:53 ../dbgpt/app/dbgpt_server.py:54 +#: ../dbgpt/app/operators/converter.py:14 +#: ../dbgpt/app/operators/converter.py:39 +msgid "String" +msgstr "字符串" + +#: ../dbgpt/app/operators/converter.py:17 +msgid "The string to be converted to other types." +msgstr "要转换为其他类型的字符串。" + +#: ../dbgpt/app/operators/converter.py:20 +#: ../dbgpt/app/operators/converter.py:45 +msgid "Integer" +msgstr "整数" + +#: ../dbgpt/app/operators/converter.py:23 +msgid "The integer to be converted to other types." +msgstr "要转换为其他类型的整数。" + +#: ../dbgpt/app/operators/converter.py:26 +#: ../dbgpt/app/operators/converter.py:51 +msgid "Float" +msgstr "浮点数" + +#: ../dbgpt/app/operators/converter.py:29 +msgid "The float to be converted to other types." +msgstr "要转换为其他类型的浮点数。" + +#: ../dbgpt/app/operators/converter.py:32 +#: ../dbgpt/app/operators/converter.py:57 +msgid "Boolean" +msgstr "布尔值" + +#: ../dbgpt/app/operators/converter.py:35 +msgid "The boolean to be converted to other types." +msgstr "要转换为其他类型的布尔值。" + +#: ../dbgpt/app/operators/converter.py:42 +msgid "The string converted from other types." +msgstr "从其他类型转换来的字符串。" + +#: ../dbgpt/app/operators/converter.py:48 +msgid "The integer converted from other types." +msgstr "从其他类型转换来的整数。" + +#: ../dbgpt/app/operators/converter.py:54 +msgid "The float converted from other types." +msgstr "从其他类型转换来的浮点数。" + +#: ../dbgpt/app/operators/converter.py:60 +msgid "The boolean converted from other types." +msgstr "从其他类型转换来的布尔值。" + +#: ../dbgpt/app/operators/converter.py:68 +msgid "String to Integer" +msgstr "字符串转整数" + +#: ../dbgpt/app/operators/converter.py:70 +msgid "Converts a string to an integer." +msgstr "将字符串转换为整数。" + +#: ../dbgpt/app/operators/converter.py:87 +msgid "String to Float" +msgstr "字符串转浮点数" + +#: ../dbgpt/app/operators/converter.py:89 +msgid "Converts a string to a float." +msgstr "将字符串转换为浮点数。" + +#: ../dbgpt/app/operators/converter.py:106 +msgid "String to Boolean" +msgstr "字符串转布尔值" + +#: ../dbgpt/app/operators/converter.py:108 +msgid "Converts a string to a boolean, true: 'true', '1', 'y'" +msgstr "将字符串转换为布尔值,true:'true','1','y'" + +#: ../dbgpt/app/operators/converter.py:112 +msgid "True Values" +msgstr "真值" + +#: ../dbgpt/app/operators/converter.py:117 +msgid "Comma-separated values that should be treated as True." +msgstr "应视为真值的逗号分隔值。" + +#: ../dbgpt/app/operators/converter.py:136 +msgid "Integer to String" +msgstr "整数转字符串" + +#: ../dbgpt/app/operators/converter.py:138 +msgid "Converts an integer to a string." +msgstr "将整数转换为字符串。" + +#: ../dbgpt/app/operators/converter.py:155 +msgid "Float to String" +msgstr "浮点数转字符串" + +#: ../dbgpt/app/operators/converter.py:157 +msgid "Converts a float to a string." +msgstr "将浮点数转换为字符串。" + +#: ../dbgpt/app/operators/converter.py:174 +msgid "Boolean to String" +msgstr "布尔值转字符串" + +#: ../dbgpt/app/operators/converter.py:176 +msgid "Converts a boolean to a string." +msgstr "将布尔值转换为字符串。" + +#: ../dbgpt/app/operators/llm.py:50 +msgid "The context key can be used as the key for formatting prompt." +msgstr "上下文键可以用作格式化提示的键。" + +#: ../dbgpt/app/operators/llm.py:54 +msgid "The context." +msgstr "上下文。" + +#: ../dbgpt/app/operators/llm.py:268 ../dbgpt/app/operators/datasource.py:108 +msgid "Prompt Template" +msgstr "提示模板" + +#: ../dbgpt/app/operators/llm.py:271 +msgid "The prompt template for the conversation." +msgstr "对话的提示模板。" + +#: ../dbgpt/app/operators/llm.py:274 +msgid "Model Name" +msgstr "模型名称" + +#: ../dbgpt/app/operators/llm.py:279 +msgid "The model name." +msgstr "模型名称。" + +#: ../dbgpt/app/operators/llm.py:283 +msgid "LLM Client" +msgstr "LLM 客户端" + +#: ../dbgpt/app/operators/llm.py:289 +msgid "" +"The LLM Client, how to connect to the LLM model, if not provided, it will " +"use the default client deployed by DB-GPT." +msgstr "LLM 客户端,如何连接到 LLM 模型,如果未提供,将使用 DB-GPT 部署的默认客户端。" + +#: ../dbgpt/app/operators/llm.py:294 +msgid "History Message Merge Mode" +msgstr "历史消息合并模式" + +#: ../dbgpt/app/operators/llm.py:305 +msgid "" +"The history merge mode, supports 'none', 'window' and 'token'. 'none': no " +"history merge, 'window': merge by conversation window, 'token': merge by " +"token length." +msgstr "历史合并模式,支持 'none','window' 和 'token'。'none':不合并历史,'window':按对话窗口合并,'token':按 Token 长度合并。" + +#: ../dbgpt/app/operators/llm.py:312 +msgid "User Message Key" +msgstr "用户消息键" + +#: ../dbgpt/app/operators/llm.py:318 +msgid "The key of the user message in your prompt, default is 'user_input'." +msgstr "提示中用户消息的键,默认为 'user_input'。" + +#: ../dbgpt/app/operators/llm.py:322 +msgid "History Key" +msgstr "历史键" + +#: ../dbgpt/app/operators/llm.py:328 +msgid "" +"The chat history key, with chat history message pass to prompt template, if " +"not provided, it will parse the prompt template to get the key." +msgstr "聊天历史键,将聊天历史消息传递给提示模板,如果未提供,它将解析提示模板以获取键。" + +#: ../dbgpt/app/operators/llm.py:333 +msgid "Keep Start Rounds" +msgstr "保留起始轮数" + +#: ../dbgpt/app/operators/llm.py:338 +msgid "The start rounds to keep in the chat history." +msgstr "在聊天历史中保留的起始轮数。" + +#: ../dbgpt/app/operators/llm.py:341 +msgid "Keep End Rounds" +msgstr "保留结束轮数" + +#: ../dbgpt/app/operators/llm.py:346 +msgid "The end rounds to keep in the chat history." +msgstr "在聊天历史中保留的结束轮数。" + +#: ../dbgpt/app/operators/llm.py:349 +msgid "Max Token Limit" +msgstr "最大 Token 限制" + +#: ../dbgpt/app/operators/llm.py:354 +msgid "The max token limit to keep in the chat history." +msgstr "在聊天历史中保留的最大 Token 限制。" + +#: ../dbgpt/app/operators/llm.py:358 +msgid "Common LLM Request Body" +msgstr "通用 LLM 请求体" + +#: ../dbgpt/app/operators/llm.py:361 +msgid "The common LLM request body." +msgstr "通用 LLM 请求体。" + +#: ../dbgpt/app/operators/llm.py:364 +msgid "Extra Context" +msgstr "额外上下文" + +#: ../dbgpt/app/operators/llm.py:368 +msgid "" +"Extra context for building prompt(Knowledge context, database schema, etc), " +"you can add multiple context." +msgstr "用于构建提示的额外上下文(知识上下文、数据库架构等),您可以添加多个上下文。" + +#: ../dbgpt/app/operators/llm.py:374 +msgid "Model Output" +msgstr "模型输出" + +#: ../dbgpt/app/operators/llm.py:377 +msgid "The model output." +msgstr "模型输出。" + +#: ../dbgpt/app/operators/llm.py:380 +msgid "Streaming Model Output" +msgstr "流式模型输出" + +#: ../dbgpt/app/operators/llm.py:384 +msgid "The streaming model output." +msgstr "流式模型输出。" + +#: ../dbgpt/app/operators/llm.py:390 +msgid "LLM Operator" +msgstr "LLM 算子" + +#: ../dbgpt/app/operators/llm.py:394 +msgid "" +"High-level LLM operator, supports multi-round conversation (conversation " +"window, token length and no multi-round)." +msgstr "高级 LLM 算子,支持多轮对话(对话窗口、Token 长度和无多轮)。" + +#: ../dbgpt/app/operators/llm.py:424 +msgid "Streaming LLM Operator" +msgstr "流式 LLM 算子" + +#: ../dbgpt/app/operators/llm.py:428 +msgid "" +"High-level streaming LLM operator, supports multi-round conversation " +"(conversation window, token length and no multi-round)." +msgstr "高级流式 LLM 算子,支持多轮对话(对话窗口、Token 长度和无多轮)。" + +#: ../dbgpt/app/operators/datasource.py:102 +msgid "Datasource" +msgstr "数据源" + +#: ../dbgpt/app/operators/datasource.py:105 +msgid "The datasource to retrieve the context" +msgstr "用于检索上下文的数据源" + +#: ../dbgpt/app/operators/datasource.py:113 +msgid "The prompt template to build a database prompt" +msgstr "用于构建数据库提示的提示模板" + +#: ../dbgpt/app/operators/datasource.py:117 +msgid "Display Type" +msgstr "显示类型" + +#: ../dbgpt/app/operators/datasource.py:122 +msgid "The display type for the data" +msgstr "数据的显示类型" + +#: ../dbgpt/app/operators/datasource.py:126 +msgid "Max Number of Results" +msgstr "最大结果数" + +#: ../dbgpt/app/operators/datasource.py:131 +msgid "The maximum number of results to return" +msgstr "返回的最大结果数" + +#: ../dbgpt/app/operators/datasource.py:134 +msgid "Response Format" +msgstr "响应格式" + +#: ../dbgpt/app/operators/datasource.py:139 +msgid "The response format, default is a JSON format" +msgstr "响应格式,默认为 JSON 格式" + +#: ../dbgpt/app/operators/datasource.py:144 ../dbgpt/app/operators/rag.py:35 +msgid "Context Key" +msgstr "上下文键" + +#: ../dbgpt/app/operators/datasource.py:149 ../dbgpt/app/operators/rag.py:40 +msgid "The key of the context, it will be used in building the prompt" +msgstr "上下文的键,将用于构建提示" + +#: ../dbgpt/app/operators/datasource.py:152 ../dbgpt/app/operators/rag.py:83 +msgid "User question" +msgstr "用户问题" + +#: ../dbgpt/app/operators/datasource.py:155 +msgid "The user question to retrieve table schemas from the datasource" +msgstr "用于从数据源检索表架构的用户问题" + +#: ../dbgpt/app/operators/datasource.py:158 ../dbgpt/app/operators/rag.py:89 +msgid "Retrieved context" +msgstr "检索到的上下文" + +#: ../dbgpt/app/operators/datasource.py:161 +msgid "The retrieved context from the datasource" +msgstr "从数据源检索到的上下文" + +#: ../dbgpt/app/operators/datasource.py:165 +msgid "SQL dict" +msgstr "SQL 字典" + +#: ../dbgpt/app/operators/datasource.py:168 +msgid "The SQL to be executed wrapped in a dictionary, generated by LLM" +msgstr "由 LLM 生成的包装在字典中的要执行的 SQL" + +#: ../dbgpt/app/operators/datasource.py:171 +msgid "SQL result" +msgstr "SQL 结果" + +#: ../dbgpt/app/operators/datasource.py:174 +msgid "The result of the SQL execution" +msgstr "SQL 执行结果" + +#: ../dbgpt/app/operators/datasource.py:178 +msgid "SQL dict list" +msgstr "SQL 字典列表" + +#: ../dbgpt/app/operators/datasource.py:182 +msgid "The SQL list to be executed wrapped in a dictionary, generated by LLM" +msgstr "由 LLM 生成的包装在字典中的要执行的 SQL 列表" + +#: ../dbgpt/app/operators/datasource.py:211 +msgid "Datasource Retriever Operator" +msgstr "数据源检索算子" + +#: ../dbgpt/app/operators/datasource.py:213 +msgid "Retrieve the table schemas from the datasource." +msgstr "从数据源检索表架构。" + +#: ../dbgpt/app/operators/datasource.py:227 +msgid "Retrieved schema chunks" +msgstr "检索到的架构块" + +#: ../dbgpt/app/operators/datasource.py:231 +msgid "The retrieved schema chunks from the datasource" +msgstr "从数据源检索到的架构块" + +#: ../dbgpt/app/operators/datasource.py:289 +msgid "Datasource Executor Operator" +msgstr "数据源执行算子" + +#: ../dbgpt/app/operators/datasource.py:291 +#: ../dbgpt/app/operators/datasource.py:328 +msgid "Execute the context from the datasource." +msgstr "执行来自数据源的上下文。" + +#: ../dbgpt/app/operators/datasource.py:326 +msgid "Datasource Dashboard Operator" +msgstr "数据源仪表板算子" + +#: ../dbgpt/app/operators/rag.py:43 +msgid "Top K" +msgstr "前 K" + +#: ../dbgpt/app/operators/rag.py:48 +msgid "The number of chunks to retrieve" +msgstr "要检索的块数" + +#: ../dbgpt/app/operators/rag.py:51 +msgid "Minimum Match Score" +msgstr "最低匹配分数" + +#: ../dbgpt/app/operators/rag.py:58 +msgid "" +"The minimum match score for the retrieved chunks, it will be dropped if the " +"match score is less than the threshold" +msgstr "检索到的块的最低匹配分数,如果匹配分数低于阈值,将被丢弃" + +#: ../dbgpt/app/operators/rag.py:66 +msgid "Reranker Enabled" +msgstr "启用重新排序器" + +#: ../dbgpt/app/operators/rag.py:71 +msgid "Whether to enable the reranker" +msgstr "是否启用重新排序器" + +#: ../dbgpt/app/operators/rag.py:74 +msgid "Reranker Top K" +msgstr "重新排序器前 K" + +#: ../dbgpt/app/operators/rag.py:79 +msgid "The top k for the reranker" +msgstr "重新排序器的前 K" + +#: ../dbgpt/app/operators/rag.py:86 +msgid "The user question to retrieve the knowledge" +msgstr "用于检索知识的用户问题" + +#: ../dbgpt/app/operators/rag.py:92 +msgid "The retrieved context from the knowledge space" +msgstr "从知识空间检索到的上下文" + +#: ../dbgpt/app/operators/rag.py:107 +msgid "Knowledge Operator" +msgstr "知识算子" + +#: ../dbgpt/app/operators/rag.py:112 +msgid "" +"Knowledge Operator, retrieve your knowledge(documents) from knowledge space" +msgstr "知识算子,从知识空间检索您的知识(文档)" + +#: ../dbgpt/app/operators/rag.py:118 +msgid "Knowledge Space Name" +msgstr "知识空间名称" + +#: ../dbgpt/app/operators/rag.py:122 +msgid "The name of the knowledge space" +msgstr "知识空间的名称" + +#: ../dbgpt/app/operators/rag.py:136 +msgid "Chunks" +msgstr "块" + +#: ../dbgpt/app/operators/rag.py:140 +msgid "The retrieved chunks from the knowledge space" +msgstr "从知识空间检索到的块" + +#: ../dbgpt/app/dbgpt_server.py:56 ../dbgpt/app/dbgpt_server.py:57 msgid "DB-GPT Open API" msgstr "DB-GPT 开放 API" + +#: ../dbgpt/app/knowledge/api.py:266 +msgid "Vector Store" +msgstr "向量存储" + +#: ../dbgpt/app/knowledge/api.py:274 +msgid "Knowledge Graph" +msgstr "知识图谱" + +#: ../dbgpt/app/knowledge/api.py:282 +msgid "Full Text" +msgstr "全文" \ No newline at end of file diff --git a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.mo b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.mo index 8e58f044d62495711a0f6303b4f32843be12c263..f5e246fcd8180007ccec11f39b20f48d6472e446 100644 GIT binary patch delta 5067 zcmZ{md2p508HY~@O8_B2NJtRj6O8Oh6eF8}ARrhPX=n*Vse#-`Gz;9^XoKQq2Ne*! zL_jv9$esw^0x`JYjH6R(T{{;3u`c%}LF`beh^ zKCmSCX_uCjhVlyPfg-Jp`2iko&xf)#-k5T@4Nivfos78#&W2rK0lW=90ux{@%!Kd4 z8(@ph#teadVRx7dHSZyKtuaCKBo%GE4_*h~g5BU3Py_!19T?xmn2s<5_Jm`g`sYIa znKC}=Uk#aJDj`0beUOdJ38?WO!$jB!+p@p;nTjHc!+okI(Xt=Z0wW>8m@I4G1KVI1 zK_w{wd%>kp>pTbLKsD5QZ$Qoe2zG#9+Wv1~D*KyQ+|ow4yM3kP^o<%-UPpb%0v&sRAvW4Wp)xA3=7~HxEThuU~!VS!4jwlHbHs13u>Xm zP#c_tO64b(7h!Acj-1zcmo7r z(u508_D@h7#u2tACPO)x4&~sT)}8~E0Y6k`Dxfk}33cXspd2^`wf>)M|KDtXeUM6D zIxa(P(2G?j!J#k}E`{=RFT5U}f=b;NP8Q+AmXTd5s8~z&-q{+rxWuP3ki!o+5%*Q^+`2|N)iKkb$ei~Hdv!OioLtT=c zP!S!0I-*Y@YnY!P_t2y=SmVY*&7TRCsba{zGpnGE@Odc5Uxga??f~+yhA0hM;1bj= zjpG8yPPFU?75PZ0vz-C8z&zXkh~);T`Bm1gfm;7fYkz3%2-G^?4kZ5?&}xwPjuWBm z9#D}Dg6-iXs1(n%EP#6Fe}LD(HBj?5L#2EVQ~+;Vo`td4UqfyDPs^Afc~wIfsFe1B zS|AH@4NNg4DduT-4SWqM^3za9@g>x_7HRyUfqkLIH9$G^t+iXGd+nVqdqQP2IM7zc zTF!#4=_rO{VF2;~%yGzC<}B1}iOcXV)dV;WyA-PbW!M&e4zcD70JC&8CVWAa1YcoIsp~gpKbdYs7rO;_P4s(+aLvM+%Ty56Re#B_RZ$pmTT!va8HPdr+BqFku=8h}&bCa4Af4i(V_m;gHt^~U#xY9DFsIZ%$5 zS$mh|3ETdGwSRzmU0sK9{)#ALn0NctUuGtpQy2tA4JKo1}l{fLE;(%lB_N54a5XdY^b64~GVPeTrd&iB`)Ck^{-;{mwb z)(c@dx(?~0@t5>Ud661_vE9;&@3TmEX_?#N{f~IOt^ZN^*BkYtg|@LBbkJUF7s1DE zUAWoSC&R;NK6=EqHDR@_=y^{^K6DhVK_R49p%U!C=TWp2C8I^?AnK2fpu3Qs%tlm# zcB2>2(@13rT7iCxUO|tcrjkwNMr*tbC!pu7E#&I_RTiL9#H(;iHQ!!Hy;W|@)z7_D z`=9`dL%&B&Wfzr~&_p!GHr@}XpmAs=nuSK8&dA*%9vpKG@5keA>IG;n%0yd{%4`qj z8CZ;VSX<9=fUQ@+bTk!pM+s;#dKf*ARPI47&?YoTm*+mDasxVnhN5+7Em9fo!ToOG zyC)p&*d;c8eBu0nlRaaKGrc^pusjg{uJhEG)Vs>d0?vaz-$Ey_z~>Yc`Y}sNO9uOj z7X~VvQg>upXk5a~_$h^SHJe_Uu%boKTgu#>=P&cQD`-HGMtW17(gG*1#F;W>sxv>Y zsA!N=Tv`?=s&LB7eECj6so%-ZD_bzHG|!)(_Nzty=lte_{Juryq0f>!hTd`#!wZrw z#`xIUb<%Unah>v;t1e$@$$QA>RFsx$_duRgSmG4>ic9?!X(rn@zqBOZ$?{b+RkH*B z!jk*3{DpZ%g^PXu(4yp$(7%#v66WO*vI4_XXQ2|17T%H4HMV>6@bd}+KEK<`Drw}FH`r8$!eIFf)<<_iq+T73n1_*E~7tEx8drQV2l61!c{Lm8=MkeKe%m7Q((K(&QEazZ?=c$NOaqN_d0lUueL>x ztgf#-Qop{2c%#)@qdWFTSFfTHsjXI+kEi!;U-uL)N7p^W5}^a>1)<&<;cn60bq!TZ zz3ANZdMk!M%s3U3)I7Bc#Af~A&EJHU-I5aCKIGMy(8A2Np~}oVT1PkR=0w7uXWrK0 zUM~sJru{s>NZXYbJYc=(-sSbXcGT}U*jV#=!)rC`|uO3(|aI O=$D&P!=r~4#{L&N@n<&x delta 4228 zcmZYBe^k}=9mnwxP!ec>h=3S!x%eaUQiviHYK3#CgiXO8kq0jIUi?YK_<;ik=#r`#rFXPK+BlRIZ_?fJU*L)zK*@cKSJpYQj6 zKi|*y{r!ILc8s4+hCAq}1QCMILtKw}ax3-!V2=)wia9L!1_ zh8yhuBN$8nH+KI;%+X@sE{6LB%}XIfa@ zkL@@FPoqkB3CG|+Q56}<<*9<%s0z-)46MOFV+ZPl4O~8lm{!z;x1&!p+fPFSoj{%7 z461ZjthZ3NBr)EYewc}wn1gw^2zA0eI1$gICK$s(k(iG6wC8#VD9)D=xZov74quRx7gi_FEeAVZjKs0no1{%;eh zzXrI>23^@dF!0czDl&+=>mIsMpBsxhaRF-oY`eb#N71jxskjqIU@!7#22j>i9EqyT zRMf3)^3lkq@fvD?OE?-sn1u$&K+U`uRhgxz3N)gwqzzrT3&-KRI3B;U{R|!qH~n%{ zMc1HCd>9MRcY%fmOs3AdCt0YcdXDuO)O*}+``wsJ|3l=@#PKfbbViKL?x2_L~jsM7bMDsU6iG1?WJ(8H)J{vl352Pfbq z%*S{J<1U&~R3&SzTT$bk!cltvFVfJ{`(NZiHy-Mxem?4+*PsSijSt{K+rNOiW&g%- zOyhFr<7h0zR-A`_#3J-iMvdQudN?;?66ZI&Xz1zg#<}=Dmf{G`%AAZ3b>+J;GK9AR zE9hV3hEBpfW}&CQ33bJ5P!sJy-HNlQ3%ZO{-9+*L&@}1j<2f-iY3PH?Py?()m1rCC zc$iMqy*r1R`8Cw%{$=|EnVpVLMLneBZ2xg<3F^wLP{%df?d|E*Uk4swgAP1tcbr9? z=(6p9X8T{Go{4BKPy1b{*EZAkb5K`UfclEAL{+xdx*GM`wxiCs_W|m!f!<()Ch}|R z1q`Eq4RylLtp7okE{aE8w`M$Yx6C|bmuW@~ydQO?Z=xo24)wX~cp76p!Oxxd(a;1w zwi|9(Z&@R_aVlNB)nm=K&O=q88Xv@Z)LXCzDVF&Q=3{?$YX1yY5!j5Hps$sNu6!42 zrheN$j|1rUpbq#Fbp^LjSDyM%@VLpSw`30L^GodZ2HRh2`!8cO`w!ZFS5TjMpN0;+ zf*Sa`{XleP@WgJ^E%0D8PDE9t2z3PuQO7-x8m9vz@et~QPT-?>!S)C9{j1~0VUphe z=`=W}@uCLWg1VP)p$q?t9Baa|g0Imy)BxqE$~0mWwxA}s2{p0ZI2OCCpQFZcvr&~S zMmOg-jWqO{ZO0;f8#PcrzTR{N@i+*xQ3Fr2+skc#6>7q-*!~&oM|S&7+jnONf86p= z4b+92VGj<*NPbcDqvB$viOofAuR;yfjQW0PM^*9|PQok5xlKw= z@c0tc=hj*G=1_laIL!tPcnLL;8>mtx=LSzO1NGWfp(e1wZhsY3p|`CcU<&=OP|rgA z!~8yA8Wv&;&cbsz15+QN{;@P__{EVgU?R4oCUg|_!814$} zEzBpd@Cbct(QVN(Fo^jvszSadR!fPOXqils$U(B6Xn7^bH|q*oF7i701$lymlD=gv zjlF~)K0~Dg|9E^4?_8$P;g7QaK**uE&R?dcdp zxc!0WM4Ok9!(_=*B zf4+4LMv-A;5h*4^$qu4#(>fBU>ks@}qGQASZ^b?l;-tqF``5>{ggO`FqhdU#ow(TT|X;`Vr($Nb&N%}RAD zvnpc$|5)d2)^Y#Vu_r@@1W(y};>F%oNB_36#oV6IlI$=1b>mNlI0?CKCpmY)?XmqW Nxf4SDDS2zd{s%024tf9p diff --git a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.po b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.po index f6db05771..3e412add9 100644 --- a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.po +++ b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_core.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-06-17 00:05+0800\n" +"POT-Creation-Date: 2024-09-06 16:14+0800\n" "PO-Revision-Date: 2024-03-23 16:45+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -17,19 +17,19 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: ../dbgpt/core/interface/operators/prompt_operator.py:39 +#: ../dbgpt/core/interface/operators/prompt_operator.py:37 msgid "Common Chat Prompt Template" msgstr "常见聊天提示模板" -#: ../dbgpt/core/interface/operators/prompt_operator.py:42 +#: ../dbgpt/core/interface/operators/prompt_operator.py:40 msgid "The operator to build the prompt with static prompt." -msgstr "用静态提示构建提示的操作员。" +msgstr "用静态提示构建提示的原子。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:45 +#: ../dbgpt/core/interface/operators/prompt_operator.py:44 msgid "System Message" msgstr "系统消息" -#: ../dbgpt/core/interface/operators/prompt_operator.py:50 +#: ../dbgpt/core/interface/operators/prompt_operator.py:49 msgid "The system message." msgstr "系统消息。" @@ -43,80 +43,80 @@ msgstr "聊天历史消息占位符。" #: ../dbgpt/core/interface/operators/prompt_operator.py:61 msgid "Human Message" -msgstr "人类消息" +msgstr "用户消息" #: ../dbgpt/core/interface/operators/prompt_operator.py:67 msgid "The human message." -msgstr "人类消息。" +msgstr "用户消息。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:258 +#: ../dbgpt/core/interface/operators/prompt_operator.py:257 msgid "Prompt Builder Operator" -msgstr "提示构建器操作员" +msgstr "提示构建器算子" -#: ../dbgpt/core/interface/operators/prompt_operator.py:260 +#: ../dbgpt/core/interface/operators/prompt_operator.py:259 msgid "Build messages from prompt template." msgstr "从提示模板构建消息。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:264 -#: ../dbgpt/core/interface/operators/prompt_operator.py:351 +#: ../dbgpt/core/interface/operators/prompt_operator.py:263 +#: ../dbgpt/core/interface/operators/prompt_operator.py:350 msgid "Chat Prompt Template" msgstr "聊天提示模板" -#: ../dbgpt/core/interface/operators/prompt_operator.py:267 -#: ../dbgpt/core/interface/operators/prompt_operator.py:354 +#: ../dbgpt/core/interface/operators/prompt_operator.py:266 +#: ../dbgpt/core/interface/operators/prompt_operator.py:353 msgid "The chat prompt template." msgstr "聊天提示模板。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:272 -#: ../dbgpt/core/interface/operators/prompt_operator.py:382 +#: ../dbgpt/core/interface/operators/prompt_operator.py:271 +#: ../dbgpt/core/interface/operators/prompt_operator.py:381 msgid "Prompt Input Dict" msgstr "提示输入字典" -#: ../dbgpt/core/interface/operators/prompt_operator.py:275 -#: ../dbgpt/core/interface/operators/prompt_operator.py:385 +#: ../dbgpt/core/interface/operators/prompt_operator.py:274 +#: ../dbgpt/core/interface/operators/prompt_operator.py:384 msgid "The prompt dict." msgstr "提示字典。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:280 -#: ../dbgpt/core/interface/operators/prompt_operator.py:390 +#: ../dbgpt/core/interface/operators/prompt_operator.py:279 +#: ../dbgpt/core/interface/operators/prompt_operator.py:389 msgid "Formatted Messages" msgstr "格式化消息" -#: ../dbgpt/core/interface/operators/prompt_operator.py:284 -#: ../dbgpt/core/interface/operators/prompt_operator.py:394 +#: ../dbgpt/core/interface/operators/prompt_operator.py:283 +#: ../dbgpt/core/interface/operators/prompt_operator.py:393 msgid "The formatted messages." msgstr "格式化的消息。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:344 +#: ../dbgpt/core/interface/operators/prompt_operator.py:343 msgid "History Prompt Builder Operator" -msgstr "历史提示构建器操作员" +msgstr "历史提示构建器算子" -#: ../dbgpt/core/interface/operators/prompt_operator.py:346 +#: ../dbgpt/core/interface/operators/prompt_operator.py:345 msgid "Build messages from prompt template and chat history." msgstr "从提示模板和聊天历史构建消息。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:357 +#: ../dbgpt/core/interface/operators/prompt_operator.py:356 #: ../dbgpt/core/operators/flow/composer_operator.py:65 msgid "History Key" msgstr "历史关键字" -#: ../dbgpt/core/interface/operators/prompt_operator.py:362 +#: ../dbgpt/core/interface/operators/prompt_operator.py:361 msgid "The key of history in prompt dict." msgstr "提示字典中的历史关键字。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:365 +#: ../dbgpt/core/interface/operators/prompt_operator.py:364 msgid "String History" msgstr "字符串历史" -#: ../dbgpt/core/interface/operators/prompt_operator.py:370 +#: ../dbgpt/core/interface/operators/prompt_operator.py:369 msgid "Whether to convert the history to string." msgstr "是否将历史转换为字符串。" -#: ../dbgpt/core/interface/operators/prompt_operator.py:375 +#: ../dbgpt/core/interface/operators/prompt_operator.py:374 msgid "History" msgstr "历史" -#: ../dbgpt/core/interface/operators/prompt_operator.py:379 +#: ../dbgpt/core/interface/operators/prompt_operator.py:378 msgid "The history." msgstr "历史。" @@ -151,10 +151,10 @@ msgid "" msgstr "" #: ../dbgpt/core/interface/operators/message_operator.py:152 -#: ../dbgpt/core/interface/operators/llm_operator.py:100 -#: ../dbgpt/core/interface/operators/llm_operator.py:203 -#: ../dbgpt/core/interface/operators/llm_operator.py:218 -#: ../dbgpt/core/interface/operators/llm_operator.py:364 +#: ../dbgpt/core/interface/operators/llm_operator.py:105 +#: ../dbgpt/core/interface/operators/llm_operator.py:208 +#: ../dbgpt/core/interface/operators/llm_operator.py:223 +#: ../dbgpt/core/interface/operators/llm_operator.py:369 #: ../dbgpt/core/operators/flow/composer_operator.py:118 msgid "Model Request" msgstr "模型请求" @@ -171,260 +171,281 @@ msgstr "存储的消息" msgid "The messages stored in the storage." msgstr "存储在存储中的消息。" -#: ../dbgpt/core/interface/operators/llm_operator.py:52 +#: ../dbgpt/core/interface/operators/llm_operator.py:53 msgid "Build Model Request" msgstr "构建模型请求" -#: ../dbgpt/core/interface/operators/llm_operator.py:55 +#: ../dbgpt/core/interface/operators/llm_operator.py:56 msgid "Build the model request from the http request body." msgstr "从 HTTP 请求体构建模型请求。" -#: ../dbgpt/core/interface/operators/llm_operator.py:58 +#: ../dbgpt/core/interface/operators/llm_operator.py:59 msgid "Default Model Name" msgstr "默认模型名称" -#: ../dbgpt/core/interface/operators/llm_operator.py:63 +#: ../dbgpt/core/interface/operators/llm_operator.py:64 msgid "The model name of the model request." msgstr "模型请求的模型名称。" -#: ../dbgpt/core/interface/operators/llm_operator.py:66 +#: ../dbgpt/core/interface/operators/llm_operator.py:67 msgid "Temperature" msgstr "" -#: ../dbgpt/core/interface/operators/llm_operator.py:71 +#: ../dbgpt/core/interface/operators/llm_operator.py:72 msgid "The temperature of the model request." msgstr "模型请求的温度。" -#: ../dbgpt/core/interface/operators/llm_operator.py:74 +#: ../dbgpt/core/interface/operators/llm_operator.py:79 msgid "Max New Tokens" msgstr "" -#: ../dbgpt/core/interface/operators/llm_operator.py:79 +#: ../dbgpt/core/interface/operators/llm_operator.py:84 msgid "The max new tokens of the model request." msgstr "最大新令牌数(token)。" -#: ../dbgpt/core/interface/operators/llm_operator.py:82 +#: ../dbgpt/core/interface/operators/llm_operator.py:87 msgid "Context Length" msgstr "上下文长度" -#: ../dbgpt/core/interface/operators/llm_operator.py:87 +#: ../dbgpt/core/interface/operators/llm_operator.py:92 msgid "The context length of the model request." msgstr "模型请求的上下文长度。" -#: ../dbgpt/core/interface/operators/llm_operator.py:92 +#: ../dbgpt/core/interface/operators/llm_operator.py:97 #: ../dbgpt/core/awel/trigger/ext_http_trigger.py:41 #: ../dbgpt/core/awel/trigger/ext_http_trigger.py:98 -#: ../dbgpt/core/awel/trigger/http_trigger.py:766 -#: ../dbgpt/core/awel/trigger/http_trigger.py:825 -#: ../dbgpt/core/awel/trigger/http_trigger.py:886 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1017 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1074 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1123 +#: ../dbgpt/core/awel/trigger/http_trigger.py:840 +#: ../dbgpt/core/awel/trigger/http_trigger.py:899 +#: ../dbgpt/core/awel/trigger/http_trigger.py:970 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1119 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1176 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1225 msgid "Request Body" msgstr "请求体" -#: ../dbgpt/core/interface/operators/llm_operator.py:95 -#: ../dbgpt/core/interface/operators/llm_operator.py:367 -#: ../dbgpt/core/interface/operators/llm_operator.py:441 -#: ../dbgpt/core/interface/operators/llm_operator.py:534 -#: ../dbgpt/core/interface/operators/llm_operator.py:542 +#: ../dbgpt/core/interface/operators/llm_operator.py:100 +#: ../dbgpt/core/interface/operators/llm_operator.py:372 +#: ../dbgpt/core/interface/operators/llm_operator.py:458 +#: ../dbgpt/core/interface/operators/llm_operator.py:551 +#: ../dbgpt/core/interface/operators/llm_operator.py:559 msgid "The input value of the operator." -msgstr "操作员的输入值。" +msgstr "算子的输入值。" -#: ../dbgpt/core/interface/operators/llm_operator.py:103 -#: ../dbgpt/core/interface/operators/llm_operator.py:221 -#: ../dbgpt/core/interface/operators/llm_operator.py:449 -#: ../dbgpt/core/interface/operators/llm_operator.py:594 -#: ../dbgpt/core/interface/operators/llm_operator.py:639 +#: ../dbgpt/core/interface/operators/llm_operator.py:108 +#: ../dbgpt/core/interface/operators/llm_operator.py:226 +#: ../dbgpt/core/interface/operators/llm_operator.py:466 +#: ../dbgpt/core/interface/operators/llm_operator.py:611 +#: ../dbgpt/core/interface/operators/llm_operator.py:656 msgid "The output value of the operator." msgstr "算子的输出值。" -#: ../dbgpt/core/interface/operators/llm_operator.py:196 +#: ../dbgpt/core/interface/operators/llm_operator.py:201 msgid "Merge Model Request Messages" msgstr "合并模型请求消息" -#: ../dbgpt/core/interface/operators/llm_operator.py:199 +#: ../dbgpt/core/interface/operators/llm_operator.py:204 msgid "Merge the model request from the input value." msgstr "从输入值中合并模型请求。" -#: ../dbgpt/core/interface/operators/llm_operator.py:206 +#: ../dbgpt/core/interface/operators/llm_operator.py:211 msgid "The model request of upstream." msgstr "上游的模型请求。" -#: ../dbgpt/core/interface/operators/llm_operator.py:209 +#: ../dbgpt/core/interface/operators/llm_operator.py:214 msgid "Model messages" msgstr "模型消息" -#: ../dbgpt/core/interface/operators/llm_operator.py:212 +#: ../dbgpt/core/interface/operators/llm_operator.py:217 msgid "The model messages of upstream." msgstr "上游的模型消息。" -#: ../dbgpt/core/interface/operators/llm_operator.py:339 +#: ../dbgpt/core/interface/operators/llm_operator.py:361 msgid "LLM Branch Operator" msgstr "LLM 分支算子" -#: ../dbgpt/core/interface/operators/llm_operator.py:343 +#: ../dbgpt/core/interface/operators/llm_operator.py:365 msgid "Branch the workflow based on the stream flag of the request." msgstr "根据请求的流标志分支工作流。" -#: ../dbgpt/core/interface/operators/llm_operator.py:346 -msgid "Streaming Task Name" -msgstr "流式任务名称" - -#: ../dbgpt/core/interface/operators/llm_operator.py:351 -msgid "The name of the streaming task." -msgstr "流式任务的名称。" - -#: ../dbgpt/core/interface/operators/llm_operator.py:354 -msgid "Non-Streaming Task Name" -msgstr "非流式任务名称" - -#: ../dbgpt/core/interface/operators/llm_operator.py:359 -msgid "The name of the non-streaming task." -msgstr "非流式任务的名称。" - -#: ../dbgpt/core/interface/operators/llm_operator.py:372 +#: ../dbgpt/core/interface/operators/llm_operator.py:377 msgid "Streaming Model Request" msgstr "流式模型请求" -#: ../dbgpt/core/interface/operators/llm_operator.py:375 +#: ../dbgpt/core/interface/operators/llm_operator.py:380 msgid "The streaming request, to streaming Operator." msgstr "流式请求,发送至流式算子。" -#: ../dbgpt/core/interface/operators/llm_operator.py:378 +#: ../dbgpt/core/interface/operators/llm_operator.py:383 msgid "Non-Streaming Model Request" msgstr "非流式模型请求" -#: ../dbgpt/core/interface/operators/llm_operator.py:381 +#: ../dbgpt/core/interface/operators/llm_operator.py:386 msgid "The non-streaming request, to non-streaming Operator." msgstr "非流式请求,发送至非流式算子。" -#: ../dbgpt/core/interface/operators/llm_operator.py:431 +#: ../dbgpt/core/interface/operators/llm_operator.py:448 msgid "Map Model Output to Common Response Body" msgstr "将模型输出映射到通用响应体" -#: ../dbgpt/core/interface/operators/llm_operator.py:434 +#: ../dbgpt/core/interface/operators/llm_operator.py:451 msgid "Map the model output to the common response body." msgstr "将模型输出映射到通用响应体。" -#: ../dbgpt/core/interface/operators/llm_operator.py:438 -#: ../dbgpt/core/interface/operators/llm_operator.py:494 -#: ../dbgpt/core/interface/operators/llm_operator.py:539 -#: ../dbgpt/core/interface/operators/llm_operator.py:590 -#: ../dbgpt/core/interface/output_parser.py:40 -#: ../dbgpt/core/interface/output_parser.py:49 +#: ../dbgpt/core/interface/operators/llm_operator.py:455 +#: ../dbgpt/core/interface/operators/llm_operator.py:511 +#: ../dbgpt/core/interface/operators/llm_operator.py:556 +#: ../dbgpt/core/interface/operators/llm_operator.py:607 +#: ../dbgpt/core/interface/output_parser.py:46 +#: ../dbgpt/core/interface/output_parser.py:55 +#: ../dbgpt/core/interface/output_parser.py:310 +#: ../dbgpt/core/interface/output_parser.py:351 msgid "Model Output" msgstr "模型输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:446 +#: ../dbgpt/core/interface/operators/llm_operator.py:463 msgid "Common Response Body" msgstr "通用响应体" -#: ../dbgpt/core/interface/operators/llm_operator.py:477 +#: ../dbgpt/core/interface/operators/llm_operator.py:494 msgid "Common Streaming Output Operator" msgstr "通用流式输出算子" -#: ../dbgpt/core/interface/operators/llm_operator.py:481 +#: ../dbgpt/core/interface/operators/llm_operator.py:498 msgid "The common streaming LLM operator, for chat flow." msgstr "用于聊天流程的通用流式 LLM 算子。" -#: ../dbgpt/core/interface/operators/llm_operator.py:485 +#: ../dbgpt/core/interface/operators/llm_operator.py:502 msgid "Upstream Model Output" msgstr "上游模型输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:489 -#: ../dbgpt/core/interface/output_parser.py:44 +#: ../dbgpt/core/interface/operators/llm_operator.py:506 +#: ../dbgpt/core/interface/output_parser.py:50 +#: ../dbgpt/core/interface/output_parser.py:313 +#: ../dbgpt/core/interface/output_parser.py:354 msgid "The model output of upstream." msgstr "上游的模型输出。" -#: ../dbgpt/core/interface/operators/llm_operator.py:499 +#: ../dbgpt/core/interface/operators/llm_operator.py:516 msgid "The model output after transform to common stream format" msgstr "转换为通用流格式后的模型输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:524 +#: ../dbgpt/core/interface/operators/llm_operator.py:541 msgid "Map String to ModelOutput" msgstr "将字符串映射到模型输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:527 +#: ../dbgpt/core/interface/operators/llm_operator.py:544 msgid "Map String to ModelOutput." msgstr "将字符串映射到模型输出。" -#: ../dbgpt/core/interface/operators/llm_operator.py:531 +#: ../dbgpt/core/interface/operators/llm_operator.py:548 msgid "String" msgstr "字符串" -#: ../dbgpt/core/interface/operators/llm_operator.py:567 +#: ../dbgpt/core/interface/operators/llm_operator.py:584 msgid "LLM Branch Join Operator" msgstr "LLM 分支算子" -#: ../dbgpt/core/interface/operators/llm_operator.py:571 -#: ../dbgpt/core/interface/operators/llm_operator.py:616 +#: ../dbgpt/core/interface/operators/llm_operator.py:588 +#: ../dbgpt/core/interface/operators/llm_operator.py:633 msgid "Just keep the first non-empty output." -msgstr "" +msgstr "仅保留第一个非空输出。" -#: ../dbgpt/core/interface/operators/llm_operator.py:575 +#: ../dbgpt/core/interface/operators/llm_operator.py:592 msgid "Streaming Model Output" msgstr "模型流式输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:579 -#: ../dbgpt/core/interface/operators/llm_operator.py:624 +#: ../dbgpt/core/interface/operators/llm_operator.py:596 +#: ../dbgpt/core/interface/operators/llm_operator.py:641 msgid "The streaming output." msgstr "上游模型流式输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:582 +#: ../dbgpt/core/interface/operators/llm_operator.py:599 msgid "Non-Streaming Model Output" msgstr "非流式模型请求" -#: ../dbgpt/core/interface/operators/llm_operator.py:585 -#: ../dbgpt/core/interface/operators/llm_operator.py:630 +#: ../dbgpt/core/interface/operators/llm_operator.py:602 +#: ../dbgpt/core/interface/operators/llm_operator.py:647 msgid "The non-streaming output." msgstr "非流式任务的名称。" -#: ../dbgpt/core/interface/operators/llm_operator.py:612 +#: ../dbgpt/core/interface/operators/llm_operator.py:629 msgid "String Branch Join Operator" msgstr "LLM 分支算子" -#: ../dbgpt/core/interface/operators/llm_operator.py:620 +#: ../dbgpt/core/interface/operators/llm_operator.py:637 msgid "Streaming String Output" msgstr "将字符串映射到模型输出" -#: ../dbgpt/core/interface/operators/llm_operator.py:627 +#: ../dbgpt/core/interface/operators/llm_operator.py:644 msgid "Non-Streaming String Output" msgstr "非流式模型请求" -#: ../dbgpt/core/interface/operators/llm_operator.py:635 +#: ../dbgpt/core/interface/operators/llm_operator.py:652 msgid "String Output" msgstr "将字符串映射到模型输出" -#: ../dbgpt/core/interface/output_parser.py:32 +#: ../dbgpt/core/interface/output_parser.py:38 msgid "Base Output Operator" msgstr "通用流式输出算子" -#: ../dbgpt/core/interface/output_parser.py:36 +#: ../dbgpt/core/interface/output_parser.py:42 msgid "The base LLM out parse." -msgstr "" +msgstr "基础 LLM 输出解析。" -#: ../dbgpt/core/interface/output_parser.py:53 +#: ../dbgpt/core/interface/output_parser.py:59 msgid "The model output after parsing." msgstr "上游的模型输出。" -#: ../dbgpt/core/interface/storage.py:390 +#: ../dbgpt/core/interface/output_parser.py:303 +msgid "SQL Output Parser" +msgstr "SQL 输出解析器" + +#: ../dbgpt/core/interface/output_parser.py:306 +msgid "Parse the SQL output of an LLM call." +msgstr "解析 LLM 调用的 SQL 输出。" + +#: ../dbgpt/core/interface/output_parser.py:318 +msgid "Dict SQL Output" +msgstr "SQL 字典输出" + +#: ../dbgpt/core/interface/output_parser.py:321 +#, fuzzy +msgid "The dict output after parsing." +msgstr "上游的模型输出。" + +#: ../dbgpt/core/interface/output_parser.py:342 +msgid "SQL List Output Parser" +msgstr "SQL 列表输出解析器" + +#: ../dbgpt/core/interface/output_parser.py:346 +msgid "Parse the SQL list output of an LLM call, mostly used for dashboard." +msgstr "解析 LLM 调用的 SQL 列表输出,主要用于 dashboard。" + +#: ../dbgpt/core/interface/output_parser.py:359 +msgid "List SQL Output" +msgstr "SQL 列表输出" + +#: ../dbgpt/core/interface/output_parser.py:363 +msgid "The list output after parsing." +msgstr "上游的模型输出。" + +#: ../dbgpt/core/interface/storage.py:391 msgid "Memory Storage" msgstr "消息存储" -#: ../dbgpt/core/interface/storage.py:393 +#: ../dbgpt/core/interface/storage.py:394 msgid "Save your data in memory." -msgstr "" +msgstr "将数据保存在内存中。" -#: ../dbgpt/core/interface/storage.py:396 +#: ../dbgpt/core/interface/storage.py:397 msgid "Serializer" -msgstr "" +msgstr "序列化器" -#: ../dbgpt/core/interface/storage.py:402 +#: ../dbgpt/core/interface/storage.py:403 msgid "" "The serializer for serializing the data. If not set, the default JSON " "serializer will be used." -msgstr "" +msgstr "用于序列化数据的序列化器。如果未设置,将使用默认的 JSON 序列化器。" #: ../dbgpt/core/operators/flow/composer_operator.py:42 msgid "Conversation Composer Operator" @@ -488,7 +509,7 @@ msgstr "消息存储。" #: ../dbgpt/core/operators/flow/composer_operator.py:110 #: ../dbgpt/core/operators/flow/composer_operator.py:226 -#: ../dbgpt/core/awel/trigger/http_trigger.py:209 +#: ../dbgpt/core/awel/trigger/http_trigger.py:227 msgid "Common LLM Http Request Body" msgstr "通用LLM HTTP请求体" @@ -598,8 +619,8 @@ msgid "The request body to send" msgstr "API 端点的请求主体" #: ../dbgpt/core/awel/trigger/ext_http_trigger.py:106 -#: ../dbgpt/core/awel/trigger/http_trigger.py:974 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1025 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1076 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1127 msgid "Response Body" msgstr "响应主体" @@ -643,227 +664,253 @@ msgstr "" msgid "The cookies to use for the HTTP request" msgstr "发送 HTTP 请求的 cookies。" -#: ../dbgpt/core/awel/trigger/http_trigger.py:117 +#: ../dbgpt/core/awel/trigger/http_trigger.py:135 msgid "Dict Http Body" msgstr "字典 HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:121 +#: ../dbgpt/core/awel/trigger/http_trigger.py:139 msgid "Parse the request body as a dict or response body as a dict" msgstr "将请求主体解析为字典或响应主体解析为字典" -#: ../dbgpt/core/awel/trigger/http_trigger.py:147 +#: ../dbgpt/core/awel/trigger/http_trigger.py:165 msgid "String Http Body" msgstr "字符串 HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:151 +#: ../dbgpt/core/awel/trigger/http_trigger.py:169 msgid "Parse the request body as a string or response body as string" msgstr "将请求主体解析为字符串或响应主体解析为字符串" -#: ../dbgpt/core/awel/trigger/http_trigger.py:177 +#: ../dbgpt/core/awel/trigger/http_trigger.py:195 msgid "Request Http Body" msgstr "请求 HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:181 +#: ../dbgpt/core/awel/trigger/http_trigger.py:199 msgid "Parse the request body as a starlette Request" msgstr "将请求主体解析为 starlette 请求" -#: ../dbgpt/core/awel/trigger/http_trigger.py:213 +#: ../dbgpt/core/awel/trigger/http_trigger.py:231 msgid "Parse the request body as a common LLM http body" msgstr "将请求主体解析为通用 LLM HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:289 +#: ../dbgpt/core/awel/trigger/http_trigger.py:307 msgid "Common LLM Http Response Body" msgstr "通用 LLM HTTP 响应主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:293 +#: ../dbgpt/core/awel/trigger/http_trigger.py:311 msgid "Parse the response body as a common LLM http body" msgstr "将响应主体解析为通用 LLM HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:685 +#: ../dbgpt/core/awel/trigger/http_trigger.py:759 +#: ../dbgpt/core/awel/trigger/http_trigger.py:991 msgid "API Endpoint" msgstr "API 端点" -#: ../dbgpt/core/awel/trigger/http_trigger.py:685 +#: ../dbgpt/core/awel/trigger/http_trigger.py:759 +#: ../dbgpt/core/awel/trigger/http_trigger.py:996 msgid "The API endpoint" msgstr "API 端点" -#: ../dbgpt/core/awel/trigger/http_trigger.py:688 -#: ../dbgpt/core/awel/trigger/http_trigger.py:700 +#: ../dbgpt/core/awel/trigger/http_trigger.py:762 +#: ../dbgpt/core/awel/trigger/http_trigger.py:774 msgid "Http Methods" msgstr "HTTP 方法" -#: ../dbgpt/core/awel/trigger/http_trigger.py:693 -#: ../dbgpt/core/awel/trigger/http_trigger.py:705 +#: ../dbgpt/core/awel/trigger/http_trigger.py:767 +#: ../dbgpt/core/awel/trigger/http_trigger.py:779 msgid "The methods of the API endpoint" msgstr "API 端点的方法" -#: ../dbgpt/core/awel/trigger/http_trigger.py:695 -#: ../dbgpt/core/awel/trigger/http_trigger.py:709 +#: ../dbgpt/core/awel/trigger/http_trigger.py:769 +#: ../dbgpt/core/awel/trigger/http_trigger.py:783 msgid "HTTP Method PUT" msgstr "HTTP PUT 方法" -#: ../dbgpt/core/awel/trigger/http_trigger.py:696 -#: ../dbgpt/core/awel/trigger/http_trigger.py:710 +#: ../dbgpt/core/awel/trigger/http_trigger.py:770 +#: ../dbgpt/core/awel/trigger/http_trigger.py:784 msgid "HTTP Method POST" msgstr "HTTP POST 方法" -#: ../dbgpt/core/awel/trigger/http_trigger.py:707 +#: ../dbgpt/core/awel/trigger/http_trigger.py:781 msgid "HTTP Method GET" msgstr "HTTP GET 方法" -#: ../dbgpt/core/awel/trigger/http_trigger.py:708 +#: ../dbgpt/core/awel/trigger/http_trigger.py:782 msgid "HTTP Method DELETE" msgstr "HTTP DELETE 方法" -#: ../dbgpt/core/awel/trigger/http_trigger.py:714 +#: ../dbgpt/core/awel/trigger/http_trigger.py:788 msgid "Streaming Response" msgstr "流式响应" -#: ../dbgpt/core/awel/trigger/http_trigger.py:719 +#: ../dbgpt/core/awel/trigger/http_trigger.py:793 msgid "Whether the response is streaming" msgstr "响应是否为流式传输" -#: ../dbgpt/core/awel/trigger/http_trigger.py:722 +#: ../dbgpt/core/awel/trigger/http_trigger.py:796 msgid "Http Response Body" msgstr "HTTP 响应主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:727 -#: ../dbgpt/core/awel/trigger/http_trigger.py:977 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1028 +#: ../dbgpt/core/awel/trigger/http_trigger.py:801 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1079 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1130 msgid "The response body of the API endpoint" msgstr "API 端点的响应主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:731 +#: ../dbgpt/core/awel/trigger/http_trigger.py:805 msgid "Response Media Type" msgstr "响应媒体类型" -#: ../dbgpt/core/awel/trigger/http_trigger.py:736 +#: ../dbgpt/core/awel/trigger/http_trigger.py:810 msgid "The response media type" msgstr "响应的媒体类型" -#: ../dbgpt/core/awel/trigger/http_trigger.py:739 +#: ../dbgpt/core/awel/trigger/http_trigger.py:813 msgid "Http Status Code" msgstr "HTTP 状态码" -#: ../dbgpt/core/awel/trigger/http_trigger.py:744 +#: ../dbgpt/core/awel/trigger/http_trigger.py:818 msgid "The http status code" msgstr "HTTP 状态码" -#: ../dbgpt/core/awel/trigger/http_trigger.py:755 +#: ../dbgpt/core/awel/trigger/http_trigger.py:829 msgid "Dict Http Trigger" msgstr "字典 HTTP 触发器" -#: ../dbgpt/core/awel/trigger/http_trigger.py:760 +#: ../dbgpt/core/awel/trigger/http_trigger.py:834 msgid "" "Trigger your workflow by http request, and parse the request body as a dict" msgstr "通过 HTTP 请求触发您的工作流,并将请求主体解析为字典" -#: ../dbgpt/core/awel/trigger/http_trigger.py:769 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1020 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1077 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1126 +#: ../dbgpt/core/awel/trigger/http_trigger.py:843 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1122 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1179 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1228 msgid "The request body of the API endpoint" msgstr "API 端点的请求主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:814 +#: ../dbgpt/core/awel/trigger/http_trigger.py:888 msgid "String Http Trigger" msgstr "字符串 HTTP 触发器" -#: ../dbgpt/core/awel/trigger/http_trigger.py:819 +#: ../dbgpt/core/awel/trigger/http_trigger.py:893 msgid "" "Trigger your workflow by http request, and parse the request body as a string" msgstr "通过 HTTP 请求触发您的工作流,并将请求主体解析为字符串" -#: ../dbgpt/core/awel/trigger/http_trigger.py:829 +#: ../dbgpt/core/awel/trigger/http_trigger.py:903 msgid "The request body of the API endpoint, parse as a json string" msgstr "API 端点的请求主体,解析为 JSON 字符串" -#: ../dbgpt/core/awel/trigger/http_trigger.py:875 +#: ../dbgpt/core/awel/trigger/http_trigger.py:959 msgid "Common LLM Http Trigger" msgstr "常见 LLM Http 触发器" -#: ../dbgpt/core/awel/trigger/http_trigger.py:880 +#: ../dbgpt/core/awel/trigger/http_trigger.py:964 msgid "" "Trigger your workflow by http request, and parse the request body as a " "common LLM http body" msgstr "通过 HTTP 请求触发您的工作流,并将请求主体解析为常见的 LLM HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:890 +#: ../dbgpt/core/awel/trigger/http_trigger.py:974 msgid "The request body of the API endpoint, parse as a common LLM http body" msgstr "API 端点的请求主体,解析为常见的 LLM HTTP 主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:934 +#: ../dbgpt/core/awel/trigger/http_trigger.py:979 +#, fuzzy +msgid "Request String Messages" +msgstr "请求HTTP触发器" + +#: ../dbgpt/core/awel/trigger/http_trigger.py:983 +#, fuzzy +msgid "" +"The request string messages of the API endpoint, parsed from 'messages' " +"field of the request body" +msgstr "API 端点的请求主体,解析为 starlette 请求" + +#: ../dbgpt/core/awel/trigger/http_trigger.py:1036 msgid "Example Http Response" msgstr "示例 HTTP 响应" -#: ../dbgpt/core/awel/trigger/http_trigger.py:938 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1040 msgid "Example Http Request" msgstr "示例 HTTP 请求" -#: ../dbgpt/core/awel/trigger/http_trigger.py:960 -#: ../dbgpt/core/awel/trigger/http_trigger.py:980 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1062 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1082 msgid "Example Http Hello Operator" msgstr "示例 HTTP Hello 算子" -#: ../dbgpt/core/awel/trigger/http_trigger.py:966 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1068 msgid "Http Request Body" msgstr "HTTP 请求主体" -#: ../dbgpt/core/awel/trigger/http_trigger.py:969 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1071 msgid "The request body of the API endpoint(Dict[str, Any])" msgstr "API 端点的请求主体(字典[str, Any])" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1000 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1102 msgid "Request Body To Dict Operator" msgstr "请求主体转换为字典算子" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1005 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1107 msgid "Prefix Key" msgstr "前缀键" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1011 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1113 msgid "The prefix key of the dict, link 'message' or 'extra.info'" msgstr "字典的前缀键,链接 'message' 或 'extra.info'" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1059 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1161 msgid "User Input Parsed Operator" msgstr "用户输入解析算子" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1064 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1113 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1166 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1215 msgid "Key" msgstr "键" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1069 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1118 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1171 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1220 msgid "The key of the dict, link 'user_input'" msgstr "字典的键,链接 'user_input'" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1082 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1184 msgid "User Input Dict" msgstr "用户输入字典" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1085 -#: ../dbgpt/core/awel/trigger/http_trigger.py:1134 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1187 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1236 msgid "The user input dict of the API endpoint" msgstr "API 端点的用户输入字典" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1089 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1191 msgid "" "User input parsed operator, parse the user input from request body and " "return as a dict" msgstr "用户输入解析算子,从请求主体解析用户输入并返回为字典" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1108 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1210 msgid "Request Body Parsed To String Operator" msgstr "请求主体解析为字符串算子" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1131 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1233 msgid "User Input String" msgstr "用户输入字符串" -#: ../dbgpt/core/awel/trigger/http_trigger.py:1138 +#: ../dbgpt/core/awel/trigger/http_trigger.py:1240 msgid "" "User input parsed operator, parse the user input from request body and " "return as a string" msgstr "用户输入解析算子,从请求主体解析用户输入并返回为字符串" + +#~ msgid "Streaming Task Name" +#~ msgstr "流式任务名称" + +#~ msgid "The name of the streaming task." +#~ msgstr "流式任务的名称。" + +#~ msgid "Non-Streaming Task Name" +#~ msgstr "非流式任务名称" + +#~ msgid "The name of the non-streaming task." +#~ msgstr "非流式任务的名称。" diff --git a/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_serve.mo b/i18n/locales/zh_CN/LC_MESSAGES/dbgpt_serve.mo index 6046d24e6c62706e5e1137f4e23d1d57e2ab6076..748605fc54ce4855e70e40e7d856d9e7a5701afa 100644 GIT binary patch literal 4572 zcma)-UvN}c9mkKAT8PCz^&i^S9$Pg)*(Ib@a2e|a2sA?yAlbr`BR9Jz$x8O#c<vQcoU>}+ylQ1z7Kv0{44k*_z_5cJh6(g7r^x(y}uSDc>~-G z{s`O%PU-cBAjkY8@QdKncAwzs0sIX}em&Ilzk~Q=k71Fx znur{)0ZH!*FqY1J7bL%4(-a`to6`H8dOi%|kKNL|4~8)RElBeJ0(XN?B6y@Psu>3< zZ!WkV8~`bQ_ccGz`zsMtk}Cx%kGnLRHM>Ewdrb3vklvF4Q3~us5TRrL2Faesu}S*Y zfcwEMAWURkAjzNC^GhK9*v~c7pp2*HLy+|T6C{09{KV%#EU{NWlCK5BU>ppA10dDc zFEsxIlH5N)vTqfVN_+;Scx(j8zo=#_Ncwv;FM@P_3?x5(srh>lX_dYDEiiZxR#|6c zJ3XX}9;&T~{I&3JBYskCeg%VkdkKRcs^c<@O&DY&)z=D)!iRciEyl}w5$?&lq*!dg zARmK=@{jhA{Y*Vd59Jyy$mrVHia`&@pjaT4GKPTc0rH!A@XFwRy!fu1b69`+=(@dq~S=d;)sZ)@P_*R)pV>;xUpt=bX}P3+|Fx_q+sN((!}e8 z`oVUJMkCeYa%sn_$|_!M8F9Y16;8uQwoB?Vg6K9Qnc(gMT9H+mlqQGRPrQ@ZCPIOuxY*CBH~SOq}4zrEc;$NsDfoB990?1?zaWi zvQ)ydlZNYxIIq$5!*nhJ@;#!R?a5nOPnIsEXz7M7C|Q!iUXYWCEi6D)rZS00HR8^# zMwL@xqt0R|U&}-?!c{I$IC3YhD~1YKAK1A)w0mEa)li&KT0cw)$7TCd!ft2EP+5NU zI8f+eJ&M_E;(?=4RFX7pNz;TG?KI{i6m*zc&o>eY9Tx3mzh0vlYhCd~%vJKiX-?+D znxKT0Gg?OaVJkn>l8OEaLPO=(U^t@qj;hNIJO~q_R=Uu_rRvZU7pY?#DDqr7*TfwrG?P{lRg(q;$h$PZwxCzS)vja+82A=Cn1r5DuxoXjw#d}e_mK|&s zs1KWLj9F%zuyHRT)8UVVvcQ9@%;uK9XTr=DbVw+9v(kSdur5Us6D;D8f8xn2_^Sh4v7rWHQ(Yrg6nY-xUK9=k5$adT-zNc6H3+ddC z=Q48x!Tj8me|o}C_v^WLyT>~*;*VbOhTja9uHDE@j^{>?E?S*Gb=vPe4&2P<&4nhX3{~Wv=JE%05#Cp)r4Uf)(1KlTf&%iX?mXvftUSw^3^x_Xa+4*_q>+ znLF8^9QVeDypB;MOeIsLH#afm&7FHR+mm~6!5^CB>RZPlu8Ke%l0_r?AUAo}zl92- zz+7c2GP;VCC{6}mj1OkVu4ks-(eC_rK})rP@A-qYs*cEg*)iVupm$=LY?r-4=AE68 zg3>AP;%MHxMS4`5Dx;+~SrMpwNGY=yR#Ex+RRl!&PcbR{z>#?N>}V$4tNS%jsB-}C z3tC)S*#8!qKOdaztr=V=MK-*N;cV~J(hc46xL3RaJxX@blG4jyuh)Own>wiim75#$ zPhZVUrx8Gv4Cz2YS;OVbba!U@P5=C0bHhfqRZz$L58AZ delta 857 zcmX}q-%FEG9LMpqHD}h;xn@7i=8uUHG^C5J62*w3h%UX_u7kV?f^NJS4i#Ns!9!#h z#R_2+xKRVWh*cm#6yYCW=!HG&>89{byU^aBXGgzq_B!Wz&d&2a-*bK>%kk`bEcDnA zy|hl+`G7G4I2h!I*v5;vgK-=ibID#5(9fLeG7wcsb~hJC+_I)XTpWD52FtEfcM*1MSP z<3Wy&N>#M3pc2@y{zWaYhf1)WvdCVfAbzOiT{Inqj!k!IKPc=$=qOmmUmvnY_e|%m z;3)kMb!$NA!l;zGcna-CJK!$)M{@?LqB%xm(+AN*Pe-ZhL}}Vw2W>z6 Tuple[OSType, AVXType]: print("Current platform is windows, use avx2 as default cpu architecture") elif system == "Linux": os_type = OSType.LINUX - result = subprocess.run( - ["lscpu"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - output = result.stdout.decode() + if os.path.exists("/etc/alpine-release"): + # For Alpine, we'll check /proc/cpuinfo directly + with open("/proc/cpuinfo", "r") as f: + output = f.read() + else: + result = subprocess.run( + ["lscpu"], stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + output = result.stdout.decode() elif system == "Darwin": os_type = OSType.DARWIN result = subprocess.run( @@ -443,6 +448,7 @@ def core_requires(): "termcolor", # https://github.com/eosphoros-ai/DB-GPT/issues/551 # TODO: remove pandas dependency + # alpine can't install pandas by default "pandas==2.0.3", # numpy should less than 2.0.0 "numpy>=1.21.0,<2.0.0", @@ -459,6 +465,8 @@ def core_requires(): "SQLAlchemy>=2.0.25,<2.0.29", # for cache "msgpack", + # for AWEL operator serialization + "cloudpickle", # for cache # TODO: pympler has not been updated for a long time and needs to # find a new toolkit. @@ -500,6 +508,22 @@ def core_requires(): "graphviz", # For security "cryptography", + # For high performance RPC communication in code execution + "pyzmq", + ] + + +def code_execution_requires(): + """ + pip install "dbgpt[code]" + + Code execution dependencies. For building a docker image. + """ + setup_spec.extras["code"] = setup_spec.extras["core"] + [ + "pyzmq", + "msgpack", + # for AWEL operator serialization + "cloudpickle", ] @@ -720,6 +744,7 @@ def init_install_requires(): core_requires() +code_execution_requires() torch_requires() llama_cpp_requires() quantization_requires()