mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-03 10:05:13 +00:00
feat(core): Support higher-order operators (#1984)
Co-authored-by: 谨欣 <echo.cmy@antgroup.com>
This commit is contained in:
@@ -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/
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
4
dbgpt/app/operators/__init__.py
Normal file
4
dbgpt/app/operators/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
"""Operators package.
|
||||
|
||||
This package contains all higher-order operators that are used to build workflows.
|
||||
"""
|
186
dbgpt/app/operators/converter.py
Normal file
186
dbgpt/app/operators/converter.py
Normal file
@@ -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)
|
363
dbgpt/app/operators/datasource.py
Normal file
363
dbgpt/app/operators/datasource.py
Normal file
@@ -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
|
453
dbgpt/app/operators/llm.py
Normal file
453
dbgpt/app/operators/llm.py
Normal file
@@ -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)
|
210
dbgpt/app/operators/rag.py
Normal file
210
dbgpt/app/operators/rag.py
Normal file
@@ -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],
|
||||
)
|
@@ -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.
|
||||
|
@@ -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",
|
||||
|
@@ -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:
|
||||
|
@@ -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}")
|
||||
|
@@ -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",
|
||||
)
|
||||
|
@@ -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,
|
||||
)
|
||||
|
@@ -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]:
|
||||
|
@@ -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():
|
||||
|
@@ -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):
|
||||
|
@@ -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):
|
||||
|
@@ -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."""
|
||||
|
||||
|
@@ -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, [])
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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):
|
||||
|
@@ -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"<VariablesPlaceHolder " f"{self.param_name} {self.full_key}>"
|
||||
@@ -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(),
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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(
|
||||
|
@@ -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]):
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
|
0
dbgpt/serve/dbgpts/__init__.py
Normal file
0
dbgpt/serve/dbgpts/__init__.py
Normal file
@@ -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
|
||||
|
@@ -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")
|
||||
|
@@ -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,
|
||||
)
|
||||
|
@@ -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
|
||||
|
@@ -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"""
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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):
|
||||
|
@@ -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)
|
||||
|
@@ -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,
|
||||
)
|
||||
|
1088
dbgpt/serve/flow/templates/en/rag-chat-awel-flow-template.json
Normal file
1088
dbgpt/serve/flow/templates/en/rag-chat-awel-flow-template.json
Normal file
File diff suppressed because it is too large
Load Diff
1088
dbgpt/serve/flow/templates/zh/rag-chat-awel-flow-template.json
Normal file
1088
dbgpt/serve/flow/templates/zh/rag-chat-awel-flow-template.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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")
|
||||
|
@@ -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,
|
||||
)
|
||||
|
87
dbgpt/util/serialization/check.py
Normal file
87
dbgpt/util/serialization/check.py
Normal file
@@ -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(),
|
||||
}
|
84
dbgpt/util/tests/test_pagination_utils.py
Normal file
84
dbgpt/util/tests/test_pagination_utils.py
Normal file
@@ -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
|
@@ -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/../../
|
||||
|
@@ -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}",
|
||||
)
|
||||
|
BIN
i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.mo
Normal file
BIN
i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.mo
Normal file
Binary file not shown.
66
i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.po
Normal file
66
i18n/locales/zh_CN/LC_MESSAGES/dbgpt_agent.po
Normal file
@@ -0,0 +1,66 @@
|
||||
# Chinese translations for PACKAGE package
|
||||
# PACKAGE 软件包的简体中文翻译.
|
||||
# Copyright (C) 2024 THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# Automatically generated, 2024.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-09-06 16:14+0800\n"
|
||||
"PO-Revision-Date: 2024-09-06 16:14+0800\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:353
|
||||
msgid "Agent Branch Operator"
|
||||
msgstr "智能体分支算子"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:358
|
||||
msgid ""
|
||||
"Branch the workflow based on the agent actionreport nexspeakers of the "
|
||||
"request."
|
||||
msgstr "根据智能体的操作报告和请求的下一步执行者来分支工作流。"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:363
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:371
|
||||
msgid "Agent Request"
|
||||
msgstr "智能体请求"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:366
|
||||
msgid "The input value of the operator."
|
||||
msgstr "算子的输入值。"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:374
|
||||
msgid "The agent request to agent Operator."
|
||||
msgstr "智能体请求智能体算子。"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:418
|
||||
msgid "Agent Branch Join Operator"
|
||||
msgstr "智能体分支合并算子"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:422
|
||||
msgid "Just keep the first non-empty output."
|
||||
msgstr "只保留第一个非空输出。"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:426
|
||||
msgid "Agent Output"
|
||||
msgstr "智能体输出"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:429
|
||||
msgid "The Agent output."
|
||||
msgstr "智能体的输出。"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:434
|
||||
msgid "Branch Output"
|
||||
msgstr "分支输出"
|
||||
|
||||
#: ../dbgpt/agent/core/plan/awel/agent_operator.py:437
|
||||
msgid "The output value of the operator."
|
||||
msgstr "算子的输出值。"
|
Binary file not shown.
@@ -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 "全文"
|
Binary file not shown.
@@ -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 "非流式任务的名称。"
|
||||
|
Binary file not shown.
@@ -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-24 11:24+0800\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
@@ -17,6 +17,42 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:88
|
||||
msgid "Datasource Resource"
|
||||
msgstr "数据源资源"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:92
|
||||
msgid ""
|
||||
"Connect to a datasource(retrieve table schemas and execute SQL to fetch "
|
||||
"data)."
|
||||
msgstr "连接到数据源(检索表模式并执行 SQL 以获取数据)。"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:97
|
||||
#, fuzzy
|
||||
msgid "Datasource Name"
|
||||
msgstr "数据源名称"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:102
|
||||
msgid "The name of the datasource, default is 'datasource'."
|
||||
msgstr "数据源的名称,默认是 'datasource'。"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:105
|
||||
msgid "DB Name"
|
||||
msgstr "数据库名称"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:108
|
||||
msgid "The name of the database."
|
||||
msgstr "数据库的名称。"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:112
|
||||
#, fuzzy
|
||||
msgid "Prompt Template"
|
||||
msgstr "提示模板"
|
||||
|
||||
#: ../dbgpt/serve/agent/resource/datasource.py:121
|
||||
msgid "The prompt template to build a database prompt."
|
||||
msgstr "用于构建数据库提示的提示模板。"
|
||||
|
||||
#: ../dbgpt/serve/rag/operators/knowledge_space.py:47
|
||||
msgid "Knowledge Space Operator"
|
||||
msgstr "知识空间算子"
|
||||
@@ -107,23 +143,23 @@ msgstr "格式化消息"
|
||||
msgid "The formatted messages."
|
||||
msgstr "格式化的消息。"
|
||||
|
||||
#: ../dbgpt/serve/rag/connector.py:39
|
||||
#: ../dbgpt/serve/rag/connector.py:40
|
||||
msgid "Vector Store Connector"
|
||||
msgstr "向量存储连接器"
|
||||
|
||||
#: ../dbgpt/serve/rag/connector.py:44
|
||||
#: ../dbgpt/serve/rag/connector.py:45
|
||||
msgid "Vector Store Type"
|
||||
msgstr "向量存储类型"
|
||||
|
||||
#: ../dbgpt/serve/rag/connector.py:47
|
||||
#: ../dbgpt/serve/rag/connector.py:48
|
||||
msgid "The type of vector store."
|
||||
msgstr "向量存储的类型。"
|
||||
|
||||
#: ../dbgpt/serve/rag/connector.py:51
|
||||
#: ../dbgpt/serve/rag/connector.py:52
|
||||
msgid "Vector Store Implementation"
|
||||
msgstr "向量存储实现"
|
||||
|
||||
#: ../dbgpt/serve/rag/connector.py:54
|
||||
#: ../dbgpt/serve/rag/connector.py:55
|
||||
msgid "The vector store implementation."
|
||||
msgstr "向量存储的实现。"
|
||||
|
||||
@@ -135,7 +171,7 @@ msgstr "默认聊天历史加载算子"
|
||||
msgid ""
|
||||
"Load chat history from the storage of the serve component.It is the default "
|
||||
"storage of DB-GPT"
|
||||
msgstr "从 serve 组件的存储中加载聊天记录,这是 DB-GPT 的默认存储"
|
||||
msgstr "从 serve 组件的存储中加载聊天记录,这是 DB-GPT 的默认存储。"
|
||||
|
||||
#: ../dbgpt/serve/conversation/operators.py:97
|
||||
msgid "Model Request"
|
||||
@@ -143,7 +179,7 @@ msgstr "模型请求"
|
||||
|
||||
#: ../dbgpt/serve/conversation/operators.py:100
|
||||
msgid "The model request."
|
||||
msgstr "这是模型请求。"
|
||||
msgstr "模型请求。"
|
||||
|
||||
#: ../dbgpt/serve/conversation/operators.py:105
|
||||
msgid "Stored Messages"
|
||||
@@ -151,4 +187,85 @@ msgstr "存储的消息"
|
||||
|
||||
#: ../dbgpt/serve/conversation/operators.py:108
|
||||
msgid "The messages stored in the storage."
|
||||
msgstr "存储在存储中的消息。"
|
||||
msgstr "存储在存储中的消息。"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:33
|
||||
msgid "All AWEL Flows"
|
||||
msgstr "所有 AWEL 工作流"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:34
|
||||
msgid "Fetch all AWEL flows in the system"
|
||||
msgstr "获取系统中所有 AWEL 工作流"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:41
|
||||
msgid "All AWEL Flow Nodes"
|
||||
msgstr "所有 AWEL 工作流节点"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:42
|
||||
msgid "Fetch all AWEL flow nodes in the system"
|
||||
msgstr "获取系统中所有 AWEL 工作流节点"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:49
|
||||
msgid "All Variables"
|
||||
msgstr "所有变量"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:50
|
||||
msgid "Fetch all variables in the system"
|
||||
msgstr "获取系统中所有变量"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:57
|
||||
msgid "All Secrets"
|
||||
msgstr "所有密钥"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:58
|
||||
msgid "Fetch all secrets in the system"
|
||||
msgstr "获取系统中所有密钥"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:65
|
||||
msgid "All LLMs"
|
||||
msgstr "所有大语言模型"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:66
|
||||
msgid "Fetch all LLMs in the system"
|
||||
msgstr "获取系统中所有大语言模型"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:73
|
||||
msgid "All Embeddings"
|
||||
msgstr "所有嵌入模型"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:74
|
||||
msgid "Fetch all embeddings models in the system"
|
||||
msgstr "获取系统中所有嵌入模型"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:81
|
||||
msgid "All Rerankers"
|
||||
msgstr "所有重排序器"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:82
|
||||
msgid "Fetch all rerankers in the system"
|
||||
msgstr "获取系统中所有重排序器"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:89
|
||||
msgid "All Data Sources"
|
||||
msgstr "所有数据源"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:90
|
||||
msgid "Fetch all data sources in the system"
|
||||
msgstr "获取系统中所有数据源"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:97
|
||||
msgid "All Agents"
|
||||
msgstr "所有智能体"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:98
|
||||
msgid "Fetch all agents in the system"
|
||||
msgstr "获取系统中所有智能体"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:105
|
||||
#, fuzzy
|
||||
msgid "All Knowledge Spaces"
|
||||
msgstr "所有知识空间"
|
||||
|
||||
#: ../dbgpt/serve/flow/service/variables_service.py:106
|
||||
msgid "Fetch all knowledge spaces in the system"
|
||||
msgstr "获取系统中所有知识空间"
|
33
setup.py
33
setup.py
@@ -190,10 +190,15 @@ def get_cpu_avx_support() -> 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()
|
||||
|
Reference in New Issue
Block a user