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:
cybermaggedon 2025-09-26 10:55:10 +01:00 committed by GitHub
parent 8929a680a1
commit 8354ea1276
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 311 additions and 50 deletions

View file

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

View file

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

View file

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

View file

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

View file

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