feat(core): Support higher-order operators (#1984)

Co-authored-by: 谨欣 <echo.cmy@antgroup.com>
This commit is contained in:
Fangyin Cheng
2024-09-09 10:15:37 +08:00
committed by GitHub
parent f6d5fc4595
commit 65c875db20
62 changed files with 6281 additions and 386 deletions

View File

@@ -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/

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -0,0 +1,4 @@
"""Operators package.
This package contains all higher-order operators that are used to build workflows.
"""

View 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)

View 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
View 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
View 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],
)

View File

@@ -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.

View File

@@ -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",

View File

@@ -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:

View File

@@ -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}")

View File

@@ -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",
)

View File

@@ -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,
)

View File

@@ -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]:

View File

@@ -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():

View File

@@ -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):

View File

@@ -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):

View File

@@ -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."""

View File

@@ -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, [])

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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(),

View File

@@ -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

View File

@@ -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)

View File

@@ -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(

View File

@@ -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]):

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

View 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

View File

@@ -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")

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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"""

View File

@@ -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

View File

@@ -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):

View File

@@ -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)

View File

@@ -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,
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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:

View File

@@ -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")

View File

@@ -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,
)

View 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(),
}

View 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

View File

@@ -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/../../

View File

@@ -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}",
)

Binary file not shown.

View 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 "算子的输出值。"

View File

@@ -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 "全文"

View File

@@ -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 "非流式任务的名称。"

View File

@@ -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 "获取系统中所有知识空间"

View File

@@ -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()