mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-08-20 17:24:15 +00:00
feat: Support dynamic parameters
This commit is contained in:
parent
0219f5733b
commit
e9155f0c31
@ -36,6 +36,8 @@ _ALLOWED_TYPES: Dict[str, Type] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_BASIC_TYPES = [str, int, float, bool, dict, list, set]
|
_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")
|
T = TypeVar("T", bound="ViewMixin")
|
||||||
TM = TypeVar("TM", bound="TypeMetadata")
|
TM = TypeVar("TM", bound="TypeMetadata")
|
||||||
@ -292,9 +294,6 @@ class ParameterCategory(str, Enum):
|
|||||||
return cls.RESOURCER
|
return cls.RESOURCER
|
||||||
|
|
||||||
|
|
||||||
DefaultParameterType = Union[str, int, float, bool, None]
|
|
||||||
|
|
||||||
|
|
||||||
class TypeMetadata(BaseModel):
|
class TypeMetadata(BaseModel):
|
||||||
"""The metadata of the type."""
|
"""The metadata of the type."""
|
||||||
|
|
||||||
@ -313,7 +312,23 @@ class TypeMetadata(BaseModel):
|
|||||||
return self.__class__(**self.model_dump(exclude_defaults=True))
|
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."""
|
"""Parameter for build operator."""
|
||||||
|
|
||||||
label: str = Field(
|
label: str = Field(
|
||||||
@ -332,11 +347,6 @@ class Parameter(TypeMetadata, Serializable):
|
|||||||
description="The category of the parameter",
|
description="The category of the parameter",
|
||||||
examples=["common", "resource"],
|
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(
|
resource_type: ResourceType = Field(
|
||||||
default=ResourceType.INSTANCE,
|
default=ResourceType.INSTANCE,
|
||||||
description="The type of the resource, just for resource type",
|
description="The type of the resource, just for resource type",
|
||||||
@ -389,6 +399,17 @@ class Parameter(TypeMetadata, Serializable):
|
|||||||
values[k] = handled_v
|
values[k] = handled_v
|
||||||
return values
|
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
|
@classmethod
|
||||||
def _covert_to_real_type(cls, type_cls: str, v: Any, is_list: bool) -> Any:
|
def _covert_to_real_type(cls, type_cls: str, v: Any, is_list: bool) -> Any:
|
||||||
def _parse_single_value(vv: Any) -> Any:
|
def _parse_single_value(vv: Any) -> Any:
|
||||||
@ -450,6 +471,8 @@ class Parameter(TypeMetadata, Serializable):
|
|||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
options: Optional[Union[BaseDynamicOptions, List[OptionValue]]] = None,
|
options: Optional[Union[BaseDynamicOptions, List[OptionValue]]] = None,
|
||||||
resource_type: ResourceType = ResourceType.INSTANCE,
|
resource_type: ResourceType = ResourceType.INSTANCE,
|
||||||
|
dynamic: bool = False,
|
||||||
|
dynamic_minimum: int = 0,
|
||||||
alias: Optional[List[str]] = None,
|
alias: Optional[List[str]] = None,
|
||||||
ui: Optional[UIComponent] = None,
|
ui: Optional[UIComponent] = None,
|
||||||
):
|
):
|
||||||
@ -461,6 +484,8 @@ class Parameter(TypeMetadata, Serializable):
|
|||||||
raise ValueError(f"Default value is missing for optional parameter {name}.")
|
raise ValueError(f"Default value is missing for optional parameter {name}.")
|
||||||
if not optional:
|
if not optional:
|
||||||
default = None
|
default = None
|
||||||
|
if dynamic and type not in _DYNAMIC_PARAMETER_TYPES:
|
||||||
|
raise ValueError("Dynamic parameter must be str, int, float or bool.")
|
||||||
return cls(
|
return cls(
|
||||||
label=label,
|
label=label,
|
||||||
name=name,
|
name=name,
|
||||||
@ -474,6 +499,8 @@ class Parameter(TypeMetadata, Serializable):
|
|||||||
placeholder=placeholder,
|
placeholder=placeholder,
|
||||||
description=description or label,
|
description=description or label,
|
||||||
options=options,
|
options=options,
|
||||||
|
dynamic=dynamic,
|
||||||
|
dynamic_minimum=dynamic_minimum,
|
||||||
alias=alias,
|
alias=alias,
|
||||||
ui=ui,
|
ui=ui,
|
||||||
)
|
)
|
||||||
@ -635,6 +662,11 @@ class BaseResource(Serializable, BaseModel):
|
|||||||
description="The label to display in UI",
|
description="The label to display in UI",
|
||||||
examples=["LLM Operator", "OpenAI LLM Client"],
|
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(
|
name: str = Field(
|
||||||
...,
|
...,
|
||||||
description="The name of the operator",
|
description="The name of the operator",
|
||||||
@ -668,7 +700,7 @@ class IOFiledType(str, Enum):
|
|||||||
LIST = "list"
|
LIST = "list"
|
||||||
|
|
||||||
|
|
||||||
class IOField(Resource):
|
class IOField(BaseDynamic, Resource):
|
||||||
"""The input or output field of the operator."""
|
"""The input or output field of the operator."""
|
||||||
|
|
||||||
is_list: bool = Field(
|
is_list: bool = Field(
|
||||||
@ -676,17 +708,6 @@ class IOField(Resource):
|
|||||||
description="Whether current field is list",
|
description="Whether current field is list",
|
||||||
examples=[True, False],
|
examples=[True, False],
|
||||||
)
|
)
|
||||||
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],
|
|
||||||
)
|
|
||||||
mappers: Optional[List[str]] = Field(
|
mappers: Optional[List[str]] = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="The mappers of the field, transform the field to the target type",
|
description="The mappers of the field, transform the field to the target type",
|
||||||
@ -724,18 +745,6 @@ class IOField(Resource):
|
|||||||
mappers=mappers_cls,
|
mappers=mappers_cls,
|
||||||
)
|
)
|
||||||
|
|
||||||
@model_validator(mode="before")
|
|
||||||
@classmethod
|
|
||||||
def base_pre_fill(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""Pre fill the metadata."""
|
|
||||||
if not isinstance(values, dict):
|
|
||||||
return values
|
|
||||||
if "dynamic" not in values:
|
|
||||||
values["dynamic"] = False
|
|
||||||
if "dynamic_minimum" not in values:
|
|
||||||
values["dynamic_minimum"] = 0
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMetadata(BaseResource):
|
class BaseMetadata(BaseResource):
|
||||||
"""The base metadata."""
|
"""The base metadata."""
|
||||||
@ -1137,6 +1146,38 @@ class ViewMetadata(BaseMetadata):
|
|||||||
values["outputs"] = new_outputs
|
values["outputs"] = new_outputs
|
||||||
return values
|
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:
|
def get_operator_key(self) -> str:
|
||||||
"""Get the operator key."""
|
"""Get the operator key."""
|
||||||
if not self.flow_type:
|
if not self.flow_type:
|
||||||
|
@ -4,7 +4,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, List, Optional
|
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 (
|
from dbgpt.core.awel.flow import (
|
||||||
FunctionDynamicOptions,
|
FunctionDynamicOptions,
|
||||||
IOField,
|
IOField,
|
||||||
@ -1243,3 +1243,156 @@ class ExampleFlowCodeEditorOperator(MapOperator[str, str]):
|
|||||||
if exitcode != 0:
|
if exitcode != 0:
|
||||||
return exitcode, logs_all
|
return exitcode, logs_all
|
||||||
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}",
|
||||||
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user