mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-05-22 13:55:12 +02:00
Update flow parameter tech spec for advanced params (#537)
* Add advanced mode to tech spec, fix enum description in tech spec * Updated tech-spec for controlled-by relationship between parameters * Update tg-show-flows CLI * Update tg-show-flows, tg-show-flow-classes, tg-start-flow CLI * Add tg-show-parameter-types
This commit is contained in:
parent
8929a680a1
commit
8354ea1276
5 changed files with 311 additions and 50 deletions
|
|
@ -100,7 +100,24 @@ Parameter definitions are stored centrally in the schema and config system with
|
|||
"type": "string",
|
||||
"description": "LLM model to use",
|
||||
"default": "gpt-4",
|
||||
"enum": ["gpt-4", "gpt-3.5-turbo", "claude-3", "gemma3:8b"],
|
||||
"enum": [
|
||||
{
|
||||
"id": "gpt-4",
|
||||
"description": "OpenAI GPT-4 (Most Capable)"
|
||||
},
|
||||
{
|
||||
"id": "gpt-3.5-turbo",
|
||||
"description": "OpenAI GPT-3.5 Turbo (Fast & Efficient)"
|
||||
},
|
||||
{
|
||||
"id": "claude-3",
|
||||
"description": "Anthropic Claude 3 (Thoughtful & Safe)"
|
||||
},
|
||||
{
|
||||
"id": "gemma3:8b",
|
||||
"description": "Google Gemma 3 8B (Open Source)"
|
||||
}
|
||||
],
|
||||
"required": false
|
||||
},
|
||||
"model-size": {
|
||||
|
|
@ -137,25 +154,36 @@ Flow classes define parameter metadata with type references, descriptions, and o
|
|||
{
|
||||
"flow_class": "document-analysis",
|
||||
"parameters": {
|
||||
"model": {
|
||||
"llm-model": {
|
||||
"type": "llm-model",
|
||||
"description": "LLM model to use for document analysis",
|
||||
"description": "Primary LLM model for text completion",
|
||||
"order": 1
|
||||
},
|
||||
"size": {
|
||||
"type": "model-size",
|
||||
"description": "Model size variant to use",
|
||||
"order": 2
|
||||
"llm-rag-model": {
|
||||
"type": "llm-model",
|
||||
"description": "LLM model for RAG operations",
|
||||
"order": 2,
|
||||
"advanced": true,
|
||||
"controlled-by": "llm-model"
|
||||
},
|
||||
"temp": {
|
||||
"llm-temperature": {
|
||||
"type": "temperature",
|
||||
"description": "Generation temperature for creativity control",
|
||||
"order": 3
|
||||
"order": 3,
|
||||
"advanced": true
|
||||
},
|
||||
"chunk": {
|
||||
"chunk-size": {
|
||||
"type": "chunk-size",
|
||||
"description": "Document chunk size for processing",
|
||||
"order": 4
|
||||
"order": 4,
|
||||
"advanced": true
|
||||
},
|
||||
"chunk-overlap": {
|
||||
"type": "integer",
|
||||
"description": "Overlap between document chunks",
|
||||
"order": 5,
|
||||
"advanced": true,
|
||||
"controlled-by": "chunk-size"
|
||||
}
|
||||
},
|
||||
"class": {
|
||||
|
|
@ -163,8 +191,16 @@ Flow classes define parameter metadata with type references, descriptions, and o
|
|||
"request": "non-persistent://tg/request/text-completion:{class}",
|
||||
"response": "non-persistent://tg/response/text-completion:{class}",
|
||||
"parameters": {
|
||||
"model": "{model}",
|
||||
"temperature": "{temp}"
|
||||
"model": "{llm-model}",
|
||||
"temperature": "{llm-temperature}"
|
||||
}
|
||||
},
|
||||
"rag-completion:{class}": {
|
||||
"request": "non-persistent://tg/request/rag-completion:{class}",
|
||||
"response": "non-persistent://tg/response/rag-completion:{class}",
|
||||
"parameters": {
|
||||
"model": "{llm-rag-model}",
|
||||
"temperature": "{llm-temperature}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -173,8 +209,8 @@ Flow classes define parameter metadata with type references, descriptions, and o
|
|||
"input": "persistent://tg/flow/chunk:{id}",
|
||||
"output": "persistent://tg/flow/chunk-load:{id}",
|
||||
"parameters": {
|
||||
"chunk_size": "{chunk}",
|
||||
"chunk_overlap": 100
|
||||
"chunk_size": "{chunk-size}",
|
||||
"chunk_overlap": "{chunk-overlap}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,6 +221,8 @@ The `parameters` section maps flow-specific parameter names (keys) to parameter
|
|||
- `type`: Reference to centrally-defined parameter definition (e.g., "llm-model")
|
||||
- `description`: Human-readable description for UI display
|
||||
- `order`: Display order for parameter forms (lower numbers appear first)
|
||||
- `advanced` (optional): Boolean flag indicating if this is an advanced parameter (default: false). When set to true, the UI may hide this parameter by default or place it in an "Advanced" section
|
||||
- `controlled-by` (optional): Name of another parameter that controls this parameter's value when in simple mode. When specified, this parameter inherits its value from the controlling parameter unless explicitly overridden
|
||||
|
||||
This approach allows:
|
||||
- Reusable parameter type definitions across multiple flow classes
|
||||
|
|
@ -193,6 +231,8 @@ This approach allows:
|
|||
- Enhanced UI experience with descriptive parameter forms
|
||||
- Consistent parameter validation across flows
|
||||
- Easy addition of new standard parameter types
|
||||
- Simplified UI with basic/advanced mode separation
|
||||
- Parameter value inheritance for related settings
|
||||
|
||||
#### Flow Launch Request
|
||||
|
||||
|
|
@ -203,32 +243,62 @@ The flow launch API accepts parameters using the flow's parameter names:
|
|||
"flow_class": "document-analysis",
|
||||
"flow_id": "customer-A-flow",
|
||||
"parameters": {
|
||||
"model": "claude-3",
|
||||
"size": "12b",
|
||||
"temp": 0.5,
|
||||
"chunk": 1024
|
||||
"llm-model": "claude-3",
|
||||
"llm-temperature": 0.5,
|
||||
"chunk-size": 1024
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: In this example, `llm-rag-model` is not explicitly provided but will inherit the value "claude-3" from `llm-model` due to its `controlled-by` relationship. Similarly, `chunk-overlap` could inherit a calculated value based on `chunk-size`.
|
||||
|
||||
The system will:
|
||||
1. Extract parameter metadata from flow class definition
|
||||
2. Map flow parameter names to their type definitions (e.g., `model` → `llm-model` type)
|
||||
3. Validate user-provided values against the parameter type definitions
|
||||
4. Substitute validated values into processor parameters during flow instantiation
|
||||
2. Map flow parameter names to their type definitions (e.g., `llm-model` → `llm-model` type)
|
||||
3. Resolve controlled-by relationships (e.g., `llm-rag-model` inherits from `llm-model`)
|
||||
4. Validate user-provided and inherited values against the parameter type definitions
|
||||
5. Substitute resolved values into processor parameters during flow instantiation
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### Parameter Resolution Process
|
||||
|
||||
1. **Flow Class Loading**: Load flow class and extract parameter metadata
|
||||
2. **Metadata Extraction**: Extract `type`, `description`, and `order` for each parameter
|
||||
2. **Metadata Extraction**: Extract `type`, `description`, `order`, `advanced`, and `controlled-by` for each parameter
|
||||
3. **Type Definition Lookup**: Retrieve parameter type definitions from schema/config store using `type` field
|
||||
4. **UI Form Generation**: Use `description` and `order` fields to create ordered parameter forms
|
||||
5. **Validation**: Validate user-provided parameters against type definitions
|
||||
6. **Default Application**: Apply default values for missing parameters from type definitions
|
||||
7. **Template Substitution**: Replace parameter placeholders in processor parameters with validated values
|
||||
8. **Processor Instantiation**: Create processors with substituted parameters
|
||||
4. **UI Form Generation**:
|
||||
- Use `description` and `order` fields to create ordered parameter forms
|
||||
- Parameters with `advanced: true` are hidden in basic mode or grouped in an "Advanced" section
|
||||
- Parameters with `controlled-by` may be hidden in simple mode if they inherit from their controller
|
||||
5. **Parameter Inheritance Resolution**:
|
||||
- For parameters with `controlled-by` field, check if a value was explicitly provided
|
||||
- If no explicit value provided, inherit the value from the controlling parameter
|
||||
- If the controlling parameter also has no value, use the default from the type definition
|
||||
6. **Validation**: Validate user-provided and inherited parameters against type definitions
|
||||
7. **Default Application**: Apply default values for missing parameters from type definitions
|
||||
8. **Template Substitution**: Replace parameter placeholders in processor parameters with resolved values
|
||||
9. **Processor Instantiation**: Create processors with substituted parameters
|
||||
|
||||
#### Parameter Inheritance with controlled-by
|
||||
|
||||
The `controlled-by` field enables parameter value inheritance, particularly useful for simplifying user interfaces while maintaining flexibility:
|
||||
|
||||
**Example Scenario**:
|
||||
- `llm-model` parameter controls the primary LLM model
|
||||
- `llm-rag-model` parameter has `"controlled-by": "llm-model"`
|
||||
- In simple mode, setting `llm-model` to "gpt-4" automatically sets `llm-rag-model` to "gpt-4" as well
|
||||
- In advanced mode, users can override `llm-rag-model` with a different value
|
||||
|
||||
**Resolution Rules**:
|
||||
1. If a parameter has an explicitly provided value, use that value
|
||||
2. If no explicit value and `controlled-by` is set, use the controlling parameter's value
|
||||
3. If the controlling parameter has no value, fall back to the default from the type definition
|
||||
4. Circular dependencies in `controlled-by` relationships result in a validation error
|
||||
|
||||
**UI Behavior**:
|
||||
- In basic/simple mode: Parameters with `controlled-by` may be hidden or shown as read-only with inherited value
|
||||
- In advanced mode: All parameters are shown and can be individually configured
|
||||
- When a controlling parameter changes, dependent parameters update automatically unless explicitly overridden
|
||||
|
||||
#### Pulsar Integration
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ tg-show-kg-cores = "trustgraph.cli.show_kg_cores:main"
|
|||
tg-show-library-documents = "trustgraph.cli.show_library_documents:main"
|
||||
tg-show-library-processing = "trustgraph.cli.show_library_processing:main"
|
||||
tg-show-mcp-tools = "trustgraph.cli.show_mcp_tools:main"
|
||||
tg-show-parameter-types = "trustgraph.cli.show_parameter_types:main"
|
||||
tg-show-processor-state = "trustgraph.cli.show_processor_state:main"
|
||||
tg-show-prompts = "trustgraph.cli.show_prompts:main"
|
||||
tg-show-token-costs = "trustgraph.cli.show_token_costs:main"
|
||||
|
|
|
|||
|
|
@ -5,38 +5,93 @@ Shows all defined flow classes.
|
|||
import argparse
|
||||
import os
|
||||
import tabulate
|
||||
from trustgraph.api import Api
|
||||
from trustgraph.api import Api, ConfigKey
|
||||
import json
|
||||
|
||||
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/')
|
||||
|
||||
def format_parameters(params_metadata, config_api):
|
||||
"""
|
||||
Format parameter metadata for display
|
||||
|
||||
Args:
|
||||
params_metadata: Parameter definitions from flow class
|
||||
config_api: API client to get parameter type information
|
||||
|
||||
Returns:
|
||||
Formatted string describing parameters
|
||||
"""
|
||||
if not params_metadata:
|
||||
return "None"
|
||||
|
||||
param_list = []
|
||||
|
||||
# Sort parameters by order if available
|
||||
sorted_params = sorted(
|
||||
params_metadata.items(),
|
||||
key=lambda x: x[1].get("order", 999)
|
||||
)
|
||||
|
||||
for param_name, param_meta in sorted_params:
|
||||
description = param_meta.get("description", param_name)
|
||||
param_type = param_meta.get("type", "unknown")
|
||||
|
||||
# Get type information if available
|
||||
type_info = param_type
|
||||
if config_api:
|
||||
try:
|
||||
key = ConfigKey("parameter-types", param_type)
|
||||
type_def_value = config_api.get([key])[0].value
|
||||
param_type_def = json.loads(type_def_value)
|
||||
|
||||
# Add default value if available
|
||||
default = param_type_def.get("default")
|
||||
if default is not None:
|
||||
type_info = f"{param_type} (default: {default})"
|
||||
|
||||
except:
|
||||
# If we can't get type definition, just show the type name
|
||||
pass
|
||||
|
||||
param_list.append(f" {param_name}: {description} [{type_info}]")
|
||||
|
||||
return "\n".join(param_list)
|
||||
|
||||
def show_flow_classes(url):
|
||||
|
||||
api = Api(url).flow()
|
||||
api = Api(url)
|
||||
flow_api = api.flow()
|
||||
config_api = api.config()
|
||||
|
||||
class_names = api.list_classes()
|
||||
class_names = flow_api.list_classes()
|
||||
|
||||
if len(class_names) == 0:
|
||||
print("No flows.")
|
||||
print("No flow classes.")
|
||||
return
|
||||
|
||||
classes = []
|
||||
|
||||
for class_name in class_names:
|
||||
cls = api.get_class(class_name)
|
||||
classes.append((
|
||||
class_name,
|
||||
cls.get("description", ""),
|
||||
", ".join(cls.get("tags", [])),
|
||||
))
|
||||
cls = flow_api.get_class(class_name)
|
||||
|
||||
print(tabulate.tabulate(
|
||||
classes,
|
||||
tablefmt="pretty",
|
||||
maxcolwidths=[None, 40, 20],
|
||||
stralign="left",
|
||||
headers = ["flow class", "description", "tags"],
|
||||
))
|
||||
table = []
|
||||
table.append(("name", class_name))
|
||||
table.append(("description", cls.get("description", "")))
|
||||
|
||||
tags = cls.get("tags", [])
|
||||
if tags:
|
||||
table.append(("tags", ", ".join(tags)))
|
||||
|
||||
# Show parameters if they exist
|
||||
parameters = cls.get("parameters", {})
|
||||
if parameters:
|
||||
param_str = format_parameters(parameters, config_api)
|
||||
table.append(("parameters", param_str))
|
||||
|
||||
print(tabulate.tabulate(
|
||||
table,
|
||||
tablefmt="pretty",
|
||||
stralign="left",
|
||||
))
|
||||
print()
|
||||
|
||||
def main():
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,89 @@ def describe_interfaces(intdefs, flow):
|
|||
|
||||
return "\n".join(lst)
|
||||
|
||||
def get_enum_description(param_value, param_type_def):
|
||||
"""
|
||||
Get the human-readable description for an enum value
|
||||
|
||||
Args:
|
||||
param_value: The actual parameter value (e.g., "gpt-4")
|
||||
param_type_def: The parameter type definition containing enum objects
|
||||
|
||||
Returns:
|
||||
Human-readable description or the original value if not found
|
||||
"""
|
||||
enum_list = param_type_def.get("enum", [])
|
||||
|
||||
# Handle both old format (strings) and new format (objects with id/description)
|
||||
for enum_item in enum_list:
|
||||
if isinstance(enum_item, dict):
|
||||
if enum_item.get("id") == param_value:
|
||||
return enum_item.get("description", param_value)
|
||||
elif enum_item == param_value:
|
||||
return param_value
|
||||
|
||||
# If not found in enum, return original value
|
||||
return param_value
|
||||
|
||||
def format_parameters(flow_params, class_params_metadata, config_api):
|
||||
"""
|
||||
Format flow parameters with their human-readable descriptions
|
||||
|
||||
Args:
|
||||
flow_params: The actual parameter values used in the flow
|
||||
class_params_metadata: The parameter metadata from the flow class definition
|
||||
config_api: API client to retrieve parameter type definitions
|
||||
|
||||
Returns:
|
||||
Formatted string of parameters with descriptions
|
||||
"""
|
||||
if not flow_params:
|
||||
return "None"
|
||||
|
||||
param_list = []
|
||||
|
||||
# Sort parameters by order if available
|
||||
sorted_params = sorted(
|
||||
class_params_metadata.items(),
|
||||
key=lambda x: x[1].get("order", 999)
|
||||
)
|
||||
|
||||
for param_name, param_meta in sorted_params:
|
||||
if param_name in flow_params:
|
||||
value = flow_params[param_name]
|
||||
description = param_meta.get("description", param_name)
|
||||
param_type = param_meta.get("type", "")
|
||||
controlled_by = param_meta.get("controlled-by", None)
|
||||
|
||||
# Try to get enum description if this parameter has a type definition
|
||||
display_value = value
|
||||
if param_type and config_api:
|
||||
try:
|
||||
from trustgraph.api import ConfigKey
|
||||
key = ConfigKey("parameter-types", param_type)
|
||||
type_def_value = config_api.get([key])[0].value
|
||||
param_type_def = json.loads(type_def_value)
|
||||
display_value = get_enum_description(value, param_type_def)
|
||||
except:
|
||||
# If we can't get the type definition, just use the original value
|
||||
display_value = value
|
||||
|
||||
# Format the parameter line
|
||||
line = f"• {description}: {display_value}"
|
||||
|
||||
# Add controlled-by indicator if present
|
||||
if controlled_by:
|
||||
line += f" (controlled by {controlled_by})"
|
||||
|
||||
param_list.append(line)
|
||||
|
||||
# Add any parameters that aren't in the class metadata (shouldn't happen normally)
|
||||
for param_name, value in flow_params.items():
|
||||
if param_name not in class_params_metadata:
|
||||
param_list.append(f"• {param_name}: {value} (undefined)")
|
||||
|
||||
return "\n".join(param_list) if param_list else "None"
|
||||
|
||||
def show_flows(url):
|
||||
|
||||
api = Api(url)
|
||||
|
|
@ -75,10 +158,23 @@ def show_flows(url):
|
|||
table.append(("class", flow.get("class-name", "")))
|
||||
table.append(("desc", flow.get("description", "")))
|
||||
|
||||
# Display parameters if they exist
|
||||
# Display parameters with human-readable descriptions
|
||||
parameters = flow.get("parameters", {})
|
||||
if parameters:
|
||||
param_str = json.dumps(parameters, indent=2)
|
||||
# Try to get the flow class definition for parameter metadata
|
||||
class_name = flow.get("class-name", "")
|
||||
if class_name:
|
||||
try:
|
||||
flow_class = flow_api.get_class(class_name)
|
||||
class_params_metadata = flow_class.get("parameters", {})
|
||||
param_str = format_parameters(parameters, class_params_metadata, config_api)
|
||||
except Exception as e:
|
||||
# Fallback to JSON if we can't get the class definition
|
||||
param_str = json.dumps(parameters, indent=2)
|
||||
else:
|
||||
# No class name, fallback to JSON
|
||||
param_str = json.dumps(parameters, indent=2)
|
||||
|
||||
table.append(("parameters", param_str))
|
||||
|
||||
table.append(("queue", describe_interfaces(interface_defs, flow)))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
"""
|
||||
Starts a processing flow using a defined flow class
|
||||
Starts a processing flow using a defined flow class.
|
||||
|
||||
Parameters can be provided in three ways:
|
||||
1. As key=value pairs: --param model=gpt-4 --param temp=0.7
|
||||
2. As JSON string: -p '{"model": "gpt-4", "temp": 0.7}'
|
||||
3. As JSON file: --parameters-file params.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
|
@ -62,6 +67,12 @@ def main():
|
|||
help='Path to JSON file containing flow parameters',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--param',
|
||||
action='append',
|
||||
help='Flow parameter as key=value pair (can be used multiple times, e.g., --param model=gpt-4 --param temp=0.7)',
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
|
|
@ -73,6 +84,34 @@ def main():
|
|||
parameters = json.load(f)
|
||||
elif args.parameters:
|
||||
parameters = json.loads(args.parameters)
|
||||
elif args.param:
|
||||
# Parse key=value pairs
|
||||
parameters = {}
|
||||
for param in args.param:
|
||||
if '=' not in param:
|
||||
raise ValueError(f"Invalid parameter format: {param}. Expected key=value")
|
||||
|
||||
key, value = param.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
# Try to parse value as JSON first (for numbers, booleans, etc.)
|
||||
try:
|
||||
# Handle common cases where we want to preserve the string
|
||||
if value.lower() in ['true', 'false']:
|
||||
parameters[key] = value.lower() == 'true'
|
||||
elif value.replace('.', '').replace('-', '').isdigit():
|
||||
# Check if it's a number
|
||||
if '.' in value:
|
||||
parameters[key] = float(value)
|
||||
else:
|
||||
parameters[key] = int(value)
|
||||
else:
|
||||
# Keep as string
|
||||
parameters[key] = value
|
||||
except ValueError:
|
||||
# If JSON parsing fails, treat as string
|
||||
parameters[key] = value
|
||||
|
||||
start_flow(
|
||||
url = args.api_url,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue