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", "type": "string",
"description": "LLM model to use", "description": "LLM model to use",
"default": "gpt-4", "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 "required": false
}, },
"model-size": { "model-size": {
@ -137,25 +154,36 @@ Flow classes define parameter metadata with type references, descriptions, and o
{ {
"flow_class": "document-analysis", "flow_class": "document-analysis",
"parameters": { "parameters": {
"model": { "llm-model": {
"type": "llm-model", "type": "llm-model",
"description": "LLM model to use for document analysis", "description": "Primary LLM model for text completion",
"order": 1 "order": 1
}, },
"size": { "llm-rag-model": {
"type": "model-size", "type": "llm-model",
"description": "Model size variant to use", "description": "LLM model for RAG operations",
"order": 2 "order": 2,
"advanced": true,
"controlled-by": "llm-model"
}, },
"temp": { "llm-temperature": {
"type": "temperature", "type": "temperature",
"description": "Generation temperature for creativity control", "description": "Generation temperature for creativity control",
"order": 3 "order": 3,
"advanced": true
}, },
"chunk": { "chunk-size": {
"type": "chunk-size", "type": "chunk-size",
"description": "Document chunk size for processing", "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": { "class": {
@ -163,8 +191,16 @@ Flow classes define parameter metadata with type references, descriptions, and o
"request": "non-persistent://tg/request/text-completion:{class}", "request": "non-persistent://tg/request/text-completion:{class}",
"response": "non-persistent://tg/response/text-completion:{class}", "response": "non-persistent://tg/response/text-completion:{class}",
"parameters": { "parameters": {
"model": "{model}", "model": "{llm-model}",
"temperature": "{temp}" "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}", "input": "persistent://tg/flow/chunk:{id}",
"output": "persistent://tg/flow/chunk-load:{id}", "output": "persistent://tg/flow/chunk-load:{id}",
"parameters": { "parameters": {
"chunk_size": "{chunk}", "chunk_size": "{chunk-size}",
"chunk_overlap": 100 "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") - `type`: Reference to centrally-defined parameter definition (e.g., "llm-model")
- `description`: Human-readable description for UI display - `description`: Human-readable description for UI display
- `order`: Display order for parameter forms (lower numbers appear first) - `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: This approach allows:
- Reusable parameter type definitions across multiple flow classes - Reusable parameter type definitions across multiple flow classes
@ -193,6 +231,8 @@ This approach allows:
- Enhanced UI experience with descriptive parameter forms - Enhanced UI experience with descriptive parameter forms
- Consistent parameter validation across flows - Consistent parameter validation across flows
- Easy addition of new standard parameter types - Easy addition of new standard parameter types
- Simplified UI with basic/advanced mode separation
- Parameter value inheritance for related settings
#### Flow Launch Request #### Flow Launch Request
@ -203,32 +243,62 @@ The flow launch API accepts parameters using the flow's parameter names:
"flow_class": "document-analysis", "flow_class": "document-analysis",
"flow_id": "customer-A-flow", "flow_id": "customer-A-flow",
"parameters": { "parameters": {
"model": "claude-3", "llm-model": "claude-3",
"size": "12b", "llm-temperature": 0.5,
"temp": 0.5, "chunk-size": 1024
"chunk": 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: The system will:
1. Extract parameter metadata from flow class definition 1. Extract parameter metadata from flow class definition
2. Map flow parameter names to their type definitions (e.g., `model``llm-model` type) 2. Map flow parameter names to their type definitions (e.g., `llm-model``llm-model` type)
3. Validate user-provided values against the parameter type definitions 3. Resolve controlled-by relationships (e.g., `llm-rag-model` inherits from `llm-model`)
4. Substitute validated values into processor parameters during flow instantiation 4. Validate user-provided and inherited values against the parameter type definitions
5. Substitute resolved values into processor parameters during flow instantiation
### Implementation Details ### Implementation Details
#### Parameter Resolution Process #### Parameter Resolution Process
1. **Flow Class Loading**: Load flow class and extract parameter metadata 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 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 4. **UI Form Generation**:
5. **Validation**: Validate user-provided parameters against type definitions - Use `description` and `order` fields to create ordered parameter forms
6. **Default Application**: Apply default values for missing parameters from type definitions - Parameters with `advanced: true` are hidden in basic mode or grouped in an "Advanced" section
7. **Template Substitution**: Replace parameter placeholders in processor parameters with validated values - Parameters with `controlled-by` may be hidden in simple mode if they inherit from their controller
8. **Processor Instantiation**: Create processors with substituted parameters 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 #### 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-documents = "trustgraph.cli.show_library_documents:main"
tg-show-library-processing = "trustgraph.cli.show_library_processing:main" tg-show-library-processing = "trustgraph.cli.show_library_processing:main"
tg-show-mcp-tools = "trustgraph.cli.show_mcp_tools: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-processor-state = "trustgraph.cli.show_processor_state:main"
tg-show-prompts = "trustgraph.cli.show_prompts:main" tg-show-prompts = "trustgraph.cli.show_prompts:main"
tg-show-token-costs = "trustgraph.cli.show_token_costs: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 argparse
import os import os
import tabulate import tabulate
from trustgraph.api import Api from trustgraph.api import Api, ConfigKey
import json import json
default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/') 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): 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: if len(class_names) == 0:
print("No flows.") print("No flow classes.")
return return
classes = []
for class_name in class_names: for class_name in class_names:
cls = api.get_class(class_name) cls = flow_api.get_class(class_name)
classes.append((
class_name, table = []
cls.get("description", ""), table.append(("name", class_name))
", ".join(cls.get("tags", [])), 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( print(tabulate.tabulate(
classes, table,
tablefmt="pretty", tablefmt="pretty",
maxcolwidths=[None, 40, 20],
stralign="left", stralign="left",
headers = ["flow class", "description", "tags"],
)) ))
print()
def main(): def main():

View file

@ -45,6 +45,89 @@ def describe_interfaces(intdefs, flow):
return "\n".join(lst) 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): def show_flows(url):
api = Api(url) api = Api(url)
@ -75,10 +158,23 @@ def show_flows(url):
table.append(("class", flow.get("class-name", ""))) table.append(("class", flow.get("class-name", "")))
table.append(("desc", flow.get("description", ""))) table.append(("desc", flow.get("description", "")))
# Display parameters if they exist # Display parameters with human-readable descriptions
parameters = flow.get("parameters", {}) parameters = flow.get("parameters", {})
if parameters: if parameters:
# 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) 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(("parameters", param_str))
table.append(("queue", describe_interfaces(interface_defs, flow))) 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 import argparse
@ -62,6 +67,12 @@ def main():
help='Path to JSON file containing flow parameters', 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() args = parser.parse_args()
try: try:
@ -73,6 +84,34 @@ def main():
parameters = json.load(f) parameters = json.load(f)
elif args.parameters: elif args.parameters:
parameters = json.loads(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( start_flow(
url = args.api_url, url = args.api_url,