Repository: lastmile-ai/aiconfig (language: python)

Issue:
Create set_description endpoint
Create set_description endpoint


TSIA

## Test Plan
Follow dev README to setup the local editor: https://github.com/lastmile-ai/aiconfig/tree/main/python/src/aiconfig/editor#dev, then run this command
```
curl http://localhost:8080/api/set_description -d '{"description":"yo this is a cool new description get rekt dont @ me bro"}' -X POST -H 'Content-Type: application/json'
```

https://github.com/lastmile-ai/aiconfig/assets/151060367/0db278b3-8273-4178-8010-380538dcd0ca

---
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/lastmile-ai/aiconfig/pull/693).
* __->__ #693
* #692
* #691
* #690
* #688
* #670
* #668


Discussion / hints:

Fix patch:
diff --git a/python/src/aiconfig/editor/server/server.py b/python/src/aiconfig/editor/server/server.py
index 12b9bc35e..7bd6004cc 100644
--- a/python/src/aiconfig/editor/server/server.py
+++ b/python/src/aiconfig/editor/server/server.py
@@ -1,5 +1,5 @@
 import logging
-from typing import Any, Type
+from typing import Any, Dict, Type, Union
 
 import lastmile_utils.lib.core.api as core_utils
 import result
@@ -226,3 +226,75 @@ def delete_prompt() -> FlaskResponse:
 
     operation = make_op_run_method(method_name)
     return run_aiconfig_operation_with_request_json(aiconfig, request_json, f"method_{method_name}", operation, signature)
+
+
+@app.route("/api/update_model", methods=["POST"])
+def update_model() -> FlaskResponse:
+    state = get_server_state(app)
+    aiconfig = state.aiconfig
+    request_json = request.get_json()
+
+    model_name: str | None = request_json.get("model_name")
+    settings: Dict[str, Any] | None = request_json.get("settings")
+    prompt_name: str | None = request_json.get("prompt_name")
+
+    operation = make_op_run_method(MethodName("update_model"))
+    operation_args: Result[OpArgs, str] = result.Ok(OpArgs({"model_name": model_name, "settings": settings, "prompt_name": prompt_name}))
+    return run_aiconfig_operation_with_op_args(aiconfig, "update_model", operation, operation_args)
+
+
+@app.route("/api/set_parameter", methods=["POST"])
+def set_parameter() -> FlaskResponse:
+    state = get_server_state(app)
+    aiconfig = state.aiconfig
+    request_json = request.get_json()
+
+    parameter_name: str | None = request_json.get("parameter_name")
+    parameter_value: Union[str, Dict[str, Any]] | None = request_json.get("parameter_value")
+    prompt_name: str | None = request_json.get("prompt_name")
+
+    operation = make_op_run_method(MethodName("set_parameter"))
+    operation_args: Result[OpArgs, str] = result.Ok(
+        OpArgs({"parameter_name": parameter_name, "parameter_value": parameter_value, "prompt_name": prompt_name})
+    )
+    return run_aiconfig_operation_with_op_args(aiconfig, "set_parameter", operation, operation_args)
+
+
+@app.route("/api/delete_parameter", methods=["POST"])
+def delete_parameter() -> FlaskResponse:
+    state = get_server_state(app)
+    aiconfig = state.aiconfig
+    request_json = request.get_json()
+
+    parameter_name: str | None = request_json.get("parameter_name")
+    prompt_name: str | None = request_json.get("prompt_name")
+
+    operation = make_op_run_method(MethodName("delete_parameter"))
+    operation_args: Result[OpArgs, str] = result.Ok(OpArgs({"parameter_name": parameter_name, "prompt_name": prompt_name}))
+    return run_aiconfig_operation_with_op_args(aiconfig, "delete_parameter", operation, operation_args)
+
+
+@app.route("/api/set_name", methods=["POST"])
+def set_name() -> FlaskResponse:
+    state = get_server_state(app)
+    aiconfig = state.aiconfig
+    request_json = request.get_json()
+
+    name: str | None = request_json.get("name")
+
+    operation = make_op_run_method(MethodName("set_name"))
+    operation_args: Result[OpArgs, str] = result.Ok(OpArgs({"name": name}))
+    return run_aiconfig_operation_with_op_args(aiconfig, "set_name", operation, operation_args)
+
+
+@app.route("/api/set_description", methods=["POST"])
+def set_description() -> FlaskResponse:
+    state = get_server_state(app)
+    aiconfig = state.aiconfig
+    request_json = request.get_json()
+
+    description: str | None = request_json.get("description")
+
+    operation = make_op_run_method(MethodName("set_description"))
+    operation_args: Result[OpArgs, str] = result.Ok(OpArgs({"description": description}))
+    return run_aiconfig_operation_with_op_args(aiconfig, "set_description", operation, operation_args)
diff --git a/python/src/aiconfig/editor/server/server_utils.py b/python/src/aiconfig/editor/server/server_utils.py
index 28dc313d3..8754b2c52 100644
--- a/python/src/aiconfig/editor/server/server_utils.py
+++ b/python/src/aiconfig/editor/server/server_utils.py
@@ -8,16 +8,17 @@
 from enum import Enum
 from types import ModuleType
 from typing import Any, Callable, NewType, Optional, Type, TypeVar, cast
-from aiconfig.registry import ModelParserRegistry
-from aiconfig.schema import Prompt, PromptMetadata
 
 import lastmile_utils.lib.core.api as core_utils
 import result
 from aiconfig.Config import AIConfigRuntime
+from aiconfig.registry import ModelParserRegistry
 from flask import Flask
 from pydantic import field_validator
 from result import Err, Ok, Result
 
+from aiconfig.schema import Prompt, PromptMetadata
+
 MethodName = NewType("MethodName", str)
 
 logging.getLogger("werkzeug").disabled = True
diff --git a/python/src/aiconfig/schema.py b/python/src/aiconfig/schema.py
index df23faa43..5eaf5583d 100644
--- a/python/src/aiconfig/schema.py
+++ b/python/src/aiconfig/schema.py
@@ -335,25 +335,136 @@ def get_metadata(self, prompt_name: Optional[str] = None):
         else:
             return self.metadata
 
-    def set_parameter(self, parameter_name: str, parameter_value, prompt_name: Optional[str] = None):
+
+    def get_parameters(
+        self,
+        prompt_or_prompt_name: Optional[str | Prompt] = None,
+    ) -> JSONObject:
+        """
+        Get the parameters for a prompt, using the global parameters if 
+        needed.
+        
+        Args:
+            prompt_or_prompt_name Optional[str | Prompt]: The name of the 
+                prompt or the prompt object. If not specified, use the 
+                global parameters.
+        """
+        prompt = prompt_or_prompt_name
+        if isinstance(prompt_or_prompt_name, str):
+            if prompt_or_prompt_name not in self.prompt_index:
+                raise IndexError(f"Prompt '{prompt_or_prompt_name}' not found in config, available prompts are:\n {list(self.prompt_index.keys())}")
+            prompt = self.prompt_index[prompt_or_prompt_name]
+
+        assert prompt is None or isinstance(prompt, Prompt)
+        if prompt is None or not prompt.metadata or not prompt.metadata.parameters:
+            return self.get_global_parameters()
+
+        return self.get_prompt_parameters(prompt)
+
+    # pylint: disable=W0102
+    def get_global_parameters(
+        self,
+        default_return_value: JSONObject = {},
+    ) -> JSONObject:
+        """
+        Get the global parameters for the AIConfig. If they're not defined,
+        return a default value ({} unless overridden)
+        
+        Args:
+            default_return_value JSONObject - Default value to return if 
+                global parameters are not defined.
+        """
+        return self._get_global_parameters_exact() or default_return_value
+    # pylint: enable=W0102
+
+    def _get_global_parameters_exact(self) -> JSONObject | None:
+        """
+        Get the global parameters for the AIConfig. This should be the
+        the explicit value (ie: if parameters is None, return None, not {})
+        """
+        return self.metadata.parameters
+
+    # pylint: disable=W0102
+    def get_prompt_parameters(
+        self,
+        prompt: Prompt,
+        default_return_value: JSONObject = {},
+    ) -> JSONObject:
+        """
+        Get the prompt's local parameters. If they're not defined,
+        return a default value ({} unless overridden)
+        
+        Args:
+            default_return_value JSONObject - Default value to return if 
+                prompt parameters are not defined.
+        """
+        return self._get_prompt_parameters_exact(prompt) \
+            or default_return_value
+    # pylint: enable=W0102
+
+    def _get_prompt_parameters_exact(
+        self,
+        prompt: Prompt,
+    ) -> JSONObject | None:
+        """
+        Get the global parameters for the AIConfig. This should be the
+        the explicit value (ie: if parameters is None, return None, not {})
+        """
+        if not prompt.metadata:
+            return prompt.metadata
+        return prompt.metadata.parameters
+
+    def set_parameter(
+        self,
+        parameter_name: str,
+        parameter_value : Union[str, JSONObject],
+        prompt_name: Optional[str] = None):
         """
-        Sets a parameter in the AI configuration metadata. If a prompt_name is specified, it adds the parameter to
-        a specific prompt's metadata in the AI configuration. Otherwise, it adds the parameter to the global metadata.
+        Sets a parameter in the AI configuration metadata. If a prompt_name 
+        is specified, it adds the parameter to a specific prompt's metadata 
+        in the AI configuration. Otherwise, it adds the parameter to the 
+        global metadata.
 
         Args:
             parameter_name (str): The name of the parameter.
-            parameter_value: The value of the parameter. It can be more than just a string. It can be a string or a JSON object. For example:
-                {
-                person: {
-                    firstname: "john",
-                    lastname: "smith",
-                    },
-                }
-                Using the parameter in a prompt with handlebars syntax would look like this:
-                "{{person.firstname}} {{person.lastname}}"
-            prompt_name (str, optional): The name of the prompt to add the parameter to. Defaults to None.
+            parameter_value: The value of the parameter. It can be more than
+                just a string. It can be a string or a JSON object. For 
+                example:
+                    {
+                    person: {
+                        firstname: "john",
+                        lastname: "smith",
+                        },
+                    }
+                Using the parameter in a prompt with handlebars syntax would 
+                look like this:
+                    "{{person.firstname}} {{person.lastname}}"
+            prompt_name (str, optional): The name of the prompt to add the 
+                parameter to. Defaults to None.
         """
         target_metadata = self.get_metadata(prompt_name)
+        if not target_metadata:
+            # Technically this check is not needed since the metadata is a 
+            # required field in Config while it is not required in Prompt.
+            # Therefore, if it's not defined, we can infer that it should
+            # be a PromptMetadata type, but this is just good robustness
+            # in case we ever change our schema in the future
+            if prompt_name:
+                prompt = self.get_prompt(prompt_name)
+                # check next line not needed since it's already assumed
+                # we got here because target_metadata is None, just being 
+                # extra safe
+                if not prompt.metadata:
+                    target_metadata = PromptMetadata(parameters={})
+                    prompt.metadata = target_metadata
+            else:
+                if not self.metadata:
+                    target_metadata = ConfigMetadata()
+                    self.metadata = target_metadata
+
+        assert target_metadata is not None
+        if target_metadata.parameters is None:
+            target_metadata.parameters = {}
         target_metadata.parameters[parameter_name] = parameter_value
 
     def update_parameter(
@@ -733,14 +844,6 @@ def get_output_text(self, prompt: str | Prompt):
             prompt (str|Prompt): The name of the prompt or the prompt object.
         """
 
-    def get_prompt_parameters(self, prompt: Prompt):
-        """
-        Gets the prompt's local parameters for a prompt.
-        """
-        if not prompt.metadata:
-            return {}
-        return prompt.metadata.parameters
-
     """
     Library Helpers
     """
diff --git a/python/src/aiconfig/util/params.py b/python/src/aiconfig/util/params.py
index d81ba4bfe..8fcc72c1e 100644
--- a/python/src/aiconfig/util/params.py
+++ b/python/src/aiconfig/util/params.py
@@ -310,7 +310,7 @@ def resolve_prompt_string(
     augmented_params = collect_prompt_references(current_prompt, ai_config)
 
     # augment params with config-level params
-    augmented_params.update(ai_config.metadata.parameters)
+    augmented_params.update(ai_config.get_global_parameters())
 
     # augment params with prompt level params
     augmented_params.update(ai_config.get_prompt_parameters(current_prompt))


Test patch:
diff --git a/python/tests/test_parameter_api.py b/python/tests/test_parameter_api.py
new file mode 100644
index 000000000..a0acd5578
--- /dev/null
+++ b/python/tests/test_parameter_api.py
@@ -0,0 +1,478 @@
+import pytest
+from aiconfig.Config import AIConfigRuntime
+from aiconfig.util.config_utils import extract_override_settings
+
+from aiconfig.schema import (
+    AIConfig,
+    ConfigMetadata,
+    ExecuteResult,
+    ModelMetadata,
+    Prompt,
+    PromptMetadata,
+)
+
+
+@pytest.fixture
+def ai_config_runtime():
+    runtime = AIConfigRuntime.create("Untitled AIConfig")
+    return runtime
+
+@pytest.fixture
+def ai_config():
+    config = AIConfig(
+        name="Untitled AIConfig",
+        schema_version="latest",
+        metadata=ConfigMetadata(),
+        prompts=[],
+    )
+    return config
+
+def test_delete_nonexistent_parameter(ai_config_runtime: AIConfigRuntime):
+    """
+    Test deleting a nonexistent parameter.
+    """
+    config = ai_config_runtime
+    parameter_name_to_delete = "param1"
+    config.add_prompt(
+        "prompt1",
+        Prompt(
+            name="prompt_name",
+            input="This is a prompt",
+            metadata=PromptMetadata(model="fakemodel"),
+        ),
+    )
+
+    # Ensure deleting a nonexistent parameter raises a KeyError
+    with pytest.raises(
+        KeyError, match=f"Parameter '{parameter_name_to_delete}' does not exist."
+    ):
+        config.delete_parameter(parameter_name_to_delete)
+
+
+def test_set_parameter_for_aiconfig_empty_params(ai_config: AIConfig):
+    """
+    Test setting a global parameter when there are no global params set before.
+    We should create a new set of params.
+    """
+    prompt_name = "prompt1"
+    prompt_parameter_name = "prompt_param"
+    prompt_parameter_value = "prompt_value"
+    prompt = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(
+            model="fakemodel",
+            parameters= {
+                prompt_parameter_name: prompt_parameter_value,
+            }
+        ),
+    )
+    ai_config.add_prompt(prompt_name, prompt)
+
+    assert ai_config.metadata.parameters == {}
+    aiconfig_parameter_name = "aiconfig_param"
+    aiconfig_parameter_value = "aiconfig_value"
+    ai_config.set_parameter(aiconfig_parameter_name, aiconfig_parameter_value)
+
+    # Ensure the global parameter is set correctly
+    assert prompt.metadata is not None
+    assert prompt.metadata.parameters == {
+        prompt_parameter_name: prompt_parameter_value,
+    }
+    assert ai_config.metadata.parameters is not None
+    assert ai_config.metadata.parameters == {
+        aiconfig_parameter_name: aiconfig_parameter_value,
+    }
+
+def test_set_parameter_for_aiconfig_has_parameters(ai_config: AIConfig):
+    """
+    Test setting a global parameter when it already has parameters.
+    It should overwrite the value for key that is the same and keep
+    the others unchanged.
+    """
+    prompt_name = "prompt1"
+    prompt_parameter_name = "prompt_param"
+    prompt_parameter_value = "prompt_value"
+    prompt = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(
+            model="fakemodel",
+            parameters= {
+                prompt_parameter_name: prompt_parameter_value,
+            }
+        ),
+    )
+    ai_config.add_prompt(prompt_name, prompt)
+
+    aiconfig_parameter_name = "aiconfig_param"
+    ai_config.metadata = ConfigMetadata(
+        parameters= {
+            "random_key": "keep this parameter",
+            aiconfig_parameter_name: "should update this value",
+        }
+    )
+    aiconfig_parameter_value = "aiconfig_value"
+    ai_config.set_parameter(aiconfig_parameter_name, aiconfig_parameter_value)
+
+    # Ensure the global parameter is set correctly
+    assert prompt.metadata is not None
+    assert prompt.metadata.parameters == {
+        prompt_parameter_name: prompt_parameter_value,
+    }
+    assert ai_config.metadata.parameters is not None
+    assert ai_config.metadata.parameters == {
+        "random_key": "keep this parameter",
+        aiconfig_parameter_name: aiconfig_parameter_value,
+    }
+
+def test_set_parameter_for_prompt_no_metadata(ai_config: AIConfig):
+    """
+    Test setting a prompt parameter when there is no prompt metadata.
+    """
+    prompt_name = "prompt1"
+    prompt = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+    )
+    ai_config.add_prompt(prompt_name, prompt)
+
+    aiconfig_parameter_name = "aiconfig_param"
+    aiconfig_parameter_value = "aiconfig_value"
+    ai_config.metadata = ConfigMetadata(
+        parameters= {
+            aiconfig_parameter_name: aiconfig_parameter_value,
+        }
+    )
+
+    assert prompt.metadata is None
+    prompt_parameter_name = "prompt_param"
+    prompt_parameter_value = "prompt_value"
+    ai_config.set_parameter(prompt_parameter_name, prompt_parameter_value, prompt_name)
+
+    # Ensure the prompt parameter is set correctly
+    assert prompt.metadata is not None
+    assert prompt.metadata.parameters == {
+        prompt_parameter_name: prompt_parameter_value,
+    }
+    assert ai_config.metadata.parameters is not None
+    assert ai_config.metadata.parameters == {
+        aiconfig_parameter_name: aiconfig_parameter_value,
+    }
+    
+def test_set_parameter_for_prompt_no_parameters(ai_config: AIConfig):
+    """
+    Test setting a prompt parameter when there are no prompt parameters.
+    """
+    prompt_name = "prompt1"
+    prompt = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel"),
+    )
+    ai_config.add_prompt(prompt_name, prompt)
+
+    aiconfig_parameter_name = "aiconfig_param"
+    aiconfig_parameter_value = "aiconfig_value"
+    ai_config.metadata = ConfigMetadata(
+        parameters= {
+            aiconfig_parameter_name: aiconfig_parameter_value,
+        }
+    )
+
+    assert prompt.metadata is not None
+    assert prompt.metadata.parameters == {}
+    prompt_parameter_name = "prompt_param"
+    prompt_parameter_value = "prompt_value"
+    ai_config.set_parameter(prompt_parameter_name, prompt_parameter_value, prompt_name)
+
+    # Ensure the prompt parameter is set correctly
+    assert prompt.metadata is not None
+    assert prompt.metadata.parameters == {
+        prompt_parameter_name: prompt_parameter_value,
+    }
+    assert ai_config.metadata.parameters is not None
+    assert ai_config.metadata.parameters == {
+        aiconfig_parameter_name: aiconfig_parameter_value,
+    }
+
+def test_set_parameter_for_prompt_has_parameters(ai_config: AIConfig):
+    """
+    Test setting a prompt parameter when it already has parameters.
+    It should overwrite the value for key that is the same and keep
+    the others unchanged.
+    """
+    prompt_name = "prompt1"
+    prompt_parameter_name = "prompt_param"
+    prompt = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(
+            model="fakemodel",
+            parameters= {
+                "random_key": "keep this parameter",
+                prompt_parameter_name: "should update this value",
+            }
+        ),
+    )
+    ai_config.add_prompt(prompt_name, prompt)
+
+    aiconfig_parameter_name = "aiconfig_param"
+    aiconfig_parameter_value = "aiconfig_value"
+    ai_config.metadata = ConfigMetadata(
+        parameters= {
+            aiconfig_parameter_name: aiconfig_parameter_value,
+        }
+    )
+
+    prompt_parameter_value = "prompt_value"
+    ai_config.set_parameter(prompt_parameter_name, prompt_parameter_value, prompt_name)
+
+    # Ensure the prompt parameter is set correctly
+    assert prompt.metadata is not None
+    assert prompt.metadata.parameters == {
+        "random_key": "keep this parameter",
+        prompt_parameter_name: prompt_parameter_value,
+    }
+    assert ai_config.metadata.parameters is not None
+    assert ai_config.metadata.parameters == {
+        aiconfig_parameter_name: aiconfig_parameter_value,
+    }
+
+
+def test_update_existing_parameter(ai_config: AIConfig):
+    """
+    Test updating an existing parameter.
+    """
+    parameter_name = "existing_param"
+    initial_value = "initial_value"
+    updated_value = "updated_value"
+
+    ai_config.set_parameter(parameter_name, initial_value, prompt_name=None)
+    ai_config.update_parameter(parameter_name, updated_value, prompt_name=None)
+
+    assert ai_config.metadata.parameters is not None
+    assert ai_config.metadata.parameters[parameter_name] == updated_value
+
+
+def test_delete_existing_parameter(ai_config: AIConfig):
+    """
+    Test deleting an existing parameter.
+    """
+    parameter_name_to_delete = "param_to_delete"
+    parameter_value = "param_value"
+
+    ai_config.set_parameter(parameter_name_to_delete, parameter_value, prompt_name=None)
+    ai_config.delete_parameter(parameter_name_to_delete, prompt_name=None)
+
+    assert ai_config.metadata.parameters is not None
+    assert parameter_name_to_delete not in ai_config.metadata.parameters
+
+
+# | With both local and global (should use local override) 
+# | Without AIConfig but local is `{}` |
+def test_get_parameter_prompt_has_parameters(ai_config: AIConfig):
+    """
+    Test getting a parameter for a prompt
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel"),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    parameter_name = "param1"
+    parameter_value = "param_value"
+    ai_config.set_parameter(
+        parameter_name,
+        parameter_value,
+        prompt_name=prompt_name,
+    )
+
+    ai_config.set_parameter(
+        "this value",
+        "does not matter",
+        prompt_name=None,
+    )
+
+    parameters = ai_config.get_parameters(prompt_name)
+    assert ai_config.prompt_index["prompt1"].metadata is not None
+    assert parameters == ai_config.prompt_index["prompt1"].metadata.parameters
+
+
+def test_get_parameter_prompt_has_no_metadata(
+    ai_config: AIConfig,
+):
+    """
+    Test getting a parameter when only aiconfig param is set.
+    Prompt does not have metadata.
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    parameter_name = "param1"
+    parameter_value = "param_value"
+    ai_config.set_parameter(
+        parameter_name,
+        parameter_value,
+        prompt_name=None,
+    )
+
+    parameters = ai_config.get_parameters(prompt_name)
+    assert parameters == ai_config.metadata.parameters
+
+
+def test_get_parameter_prompt_has_metadata_no_parameters(
+    ai_config: AIConfig
+):
+    """
+    Test getting a parameter when only aiconfig param is set.
+    Prompt has metadata but no parameters.
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel"),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    parameter_name = "param1"
+    parameter_value = "param_value"
+    ai_config.set_parameter(
+        parameter_name,
+        parameter_value,
+        prompt_name=None,
+    )
+
+    parameters = ai_config.get_parameters(prompt_name)
+    assert parameters == ai_config.metadata.parameters
+
+
+def test_get_parameter_prompt_has_empty_parameters(
+    ai_config: AIConfig
+):
+    """
+    Test getting a parameter when only aiconfig param is set.
+    Prompt has empty parameters.
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel", parameters={}),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    parameter_name = "param1"
+    parameter_value = "param_value"
+    ai_config.set_parameter(
+        parameter_name,
+        parameter_value,
+        prompt_name=None,
+    )
+
+    parameters = ai_config.get_parameters(prompt_name)
+    assert parameters == ai_config.metadata.parameters
+
+
+def test_get_parameter_prompt_has_empty_parameters(
+    ai_config: AIConfig
+):
+    """
+    Test getting a parameter when only aiconfig param is set.
+    Prompt has empty parameters.
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel", parameters={}),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    parameter_name = "param1"
+    parameter_value = "param_value"
+    ai_config.set_parameter(
+        parameter_name,
+        parameter_value,
+        prompt_name=None,
+    )
+
+    parameters = ai_config.get_parameters(prompt_name)
+    assert parameters == ai_config.metadata.parameters
+
+def test_get_parameter_aiconfig_has_parameters(ai_config: AIConfig):
+    """
+    Test getting a parameter for an aiconfig
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel"),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    ai_config.set_parameter(
+        "this does",
+        "this matter",
+        prompt_name=prompt_name,
+    )
+
+    parameter_name = "param1"
+    parameter_value = "param_value"
+    ai_config.set_parameter(
+        parameter_name,
+        parameter_value,
+        prompt_name=None,
+    )
+
+    parameters = ai_config.get_parameters()
+    assert parameters == ai_config.metadata.parameters
+
+
+def test_get_parameter_aiconfig_no_parameters(ai_config: AIConfig):
+    """
+    Test getting a parameter for an aiconfig when no parameters are set
+    on the aiconfig
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel"),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    ai_config.set_parameter(
+        "this does",
+        "this matter",
+        prompt_name=prompt_name,
+    )
+
+    parameters = ai_config.get_parameters()
+    assert parameters == {}
+
+
+def test_get_parameter_prompt_no_parameters(ai_config: AIConfig):
+    """
+    Test getting a parameter for a prompt when no parameters are set
+    on either the prompt of the aiconfig
+    """
+    prompt_name = "prompt1"
+    prompt_data = Prompt(
+        name=prompt_name,
+        input="This is a prompt",
+        metadata=PromptMetadata(model="fakemodel"),
+    )
+    ai_config.add_prompt(prompt_name, prompt_data)
+
+    parameters = ai_config.get_parameters()
+    assert parameters == {}
diff --git a/python/tests/test_programmatically_create_an_AIConfig.py b/python/tests/test_programmatically_create_an_AIConfig.py
index 43bb8624b..09bd8729d 100644
--- a/python/tests/test_programmatically_create_an_AIConfig.py
+++ b/python/tests/test_programmatically_create_an_AIConfig.py
@@ -238,28 +238,6 @@ def test_get_metadata_with_nonexistent_prompt(ai_config_runtime: AIConfigRuntime
         config.get_metadata(prompt_name)
 
 
-def test_delete_nonexistent_parameter(ai_config_runtime: AIConfigRuntime):
-    """
-    Test deleting a nonexistent parameter.
-    """
-    config = ai_config_runtime
-    parameter_name_to_delete = "param1"
-    config.add_prompt(
-        "prompt1",
-        Prompt(
-            name="prompt_name",
-            input="This is a prompt",
-            metadata=PromptMetadata(model="fakemodel"),
-        ),
-    )
-
-    # Ensure deleting a nonexistent parameter raises a KeyError
-    with pytest.raises(
-        KeyError, match=f"Parameter '{parameter_name_to_delete}' does not exist."
-    ):
-        config.delete_parameter(parameter_name_to_delete)
-
-
 @pytest.fixture
 def ai_config():
     config = AIConfig(
@@ -271,74 +249,6 @@ def ai_config():
     return config
 
 
-def test_set_global_parameter(ai_config: AIConfig):
-    """
-    Test setting a global parameter.
-    """
-    parameter_name = "global_param"
-    parameter_value = "global_value"
-
-    ai_config.set_parameter(parameter_name, parameter_value, prompt_name=None)
-
-    # Ensure the global parameter is set correctly
-    assert ai_config.metadata.parameters[parameter_name] == parameter_value
-
-
-def test_set_parameter_for_prompt(ai_config: AIConfig):
-    """
-    Test setting a parameter for a specific prompt.
-    """
-    prompt_name = "prompt1"
-    parameter_name = "prompt_param"
-    parameter_value = "prompt_value"
-
-    # Create a sample prompt for testing
-    prompt_data = Prompt(
-        name=prompt_name,
-        input="This is a prompt",
-        metadata=PromptMetadata(model="fakemodel"),
-    )
-    ai_config.add_prompt(prompt_name, prompt_data)
-
-    ai_config.set_parameter(parameter_name, parameter_value, prompt_name=prompt_name)
-
-    # Ensure the parameter is set for the specific prompt
-    assert (
-        ai_config.prompt_index[prompt_name].metadata.parameters[parameter_name]
-        == parameter_value
-    )
-    assert ai_config.prompts[0].metadata.parameters[parameter_name] == parameter_value
-
-
-def test_update_existing_parameter(ai_config: AIConfig):
-    """
-    Test updating an existing parameter.
-    """
-    parameter_name = "existing_param"
-    initial_value = "initial_value"
-    updated_value = "updated_value"
-
-    ai_config.set_parameter(parameter_name, initial_value, prompt_name=None)
-    ai_config.update_parameter(parameter_name, updated_value, prompt_name=None)
-
-    # Ensure the existing parameter is updated correctly
-    assert ai_config.metadata.parameters[parameter_name] == updated_value
-
-
-def test_delete_existing_parameter(ai_config: AIConfig):
-    """
-    Test deleting an existing parameter.
-    """
-    parameter_name_to_delete = "param_to_delete"
-    parameter_value = "param_value"
-
-    ai_config.set_parameter(parameter_name_to_delete, parameter_value, prompt_name=None)
-    ai_config.delete_parameter(parameter_name_to_delete, prompt_name=None)
-
-    # Ensure the existing parameter is deleted correctly
-    assert parameter_name_to_delete not in ai_config.metadata.parameters
-
-
 def test_load_saved_config(tmp_path):
     """
     Test loading a saved AIConfig from a JSON file.
@@ -368,6 +278,7 @@ def test_load_saved_config(tmp_path):
     assert loaded_config.name == "My AIConfig"
     assert loaded_config.metadata.parameters == {"config_param": "config_value"}
     assert "prompt1" in loaded_config.prompt_index
+    assert loaded_config.prompt_index["prompt1"].metadata is not None
     assert loaded_config.prompt_index["prompt1"].metadata.parameters == {
         "prompt_param": "prompt_value"
     }