meta-prompt / tests /meta_prompt_graph_test.py
yaleh's picture
Updated unit test. Updated UI.
0e80df8
import unittest
from unittest.mock import MagicMock, Mock
import functools
import pprint
from langchain_core.language_models import BaseLanguageModel
from langchain_openai import ChatOpenAI
from meta_prompt import *
from meta_prompt.consts import NODE_ACCEPTANCE_CRITERIA_DEVELOPER
from langgraph.graph import END
import os
class TestMetaPromptGraph(unittest.TestCase):
def setUp(self):
# logging.basicConfig(level=logging.DEBUG)
pass
def test_prompt_node(self):
"""
Test the _prompt_node method of MetaPromptGraph.
This test case sets up a mock language model that returns a response content
and verifies that the updated state has the output attribute updated with
the mocked response content.
"""
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
llm.invoke = lambda x, y=None: "Mocked response content"
llms = {
NODE_PROMPT_INITIAL_DEVELOPER: llm
}
graph = MetaPromptGraph(llms=llms)
state = AgentState(
user_message="Test message", expected_output="Expected output"
)
updated_state = graph._prompt_node(
NODE_PROMPT_INITIAL_DEVELOPER, "output", state
)
assert (
updated_state['output'] == "Mocked response content"
), "The output attribute should be updated with the mocked response content"
def test_output_history_analyzer(self):
"""
Test the _output_history_analyzer method of MetaPromptGraph.
This test case sets up a mock language model that returns an analysis
response and verifies that the updated state has the best output, best
system message, and best output age updated correctly.
"""
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
llm.invoke = lambda x, y: "{\"closerOutputID\": 2, \"analysis\": \"The output should use the `reverse()` method.\"}"
prompts = {}
meta_prompt_graph = MetaPromptGraph(llms=llm, prompts=prompts)
state = AgentState(
user_message="How do I reverse a list in Python?",
expected_output="Use the `[::-1]` slicing technique or the `list.reverse()` method.",
output="To reverse a list in Python, you can use the `[::-1]` slicing.",
system_message="To reverse a list, use slicing or the reverse method.",
best_output="To reverse a list in Python, use the `reverse()` method.",
best_system_message="To reverse a list, use the `reverse()` method.",
acceptance_criteria="The output should correctly describe how to reverse a list in Python.",
)
updated_state = meta_prompt_graph._output_history_analyzer(state)
assert (
updated_state['best_output'] == state['output']
), "Best output should be updated to the current output."
assert (
updated_state['best_system_message'] == state['system_message']
), "Best system message should be updated to the current system message."
assert (
updated_state['best_output_age'] == 0
), "Best output age should be reset to 0."
def test_prompt_analyzer_accept(self):
"""
Test the _prompt_analyzer method of MetaPromptGraph when the prompt analyzer
accepts the output.
This test case sets up a mock language model that returns an acceptance
response and verifies that the updated state has the accepted attribute
set to True.
"""
# llms = {
# NODE_PROMPT_ANALYZER: lambda prompt: "{\"Accept\": \"Yes\"}"
# }
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
llm.invoke = lambda x, y: "{\"Accept\": \"Yes\"}"
meta_prompt_graph = MetaPromptGraph(llms=llm)
state = AgentState(
output="Test output", expected_output="Expected output",
acceptance_criteria="Acceptance criteria: ...",
system_message="System message: ...",
max_output_age=2
)
updated_state = meta_prompt_graph._prompt_analyzer(state)
assert updated_state['accepted'] is True
def test_get_node_names(self):
"""
Test the get_node_names method of MetaPromptGraph.
This test case verifies that the get_node_names method returns the
correct list of node names.
"""
graph = MetaPromptGraph()
node_names = graph.get_node_names()
self.assertEqual(node_names, META_PROMPT_NODES)
def test_workflow_execution(self):
"""
Test the workflow execution of the MetaPromptGraph.
This test case sets up a MetaPromptGraph with a single language model and
executes it with a given input state. It then verifies that the output
state contains the expected keys and values.
"""
model_name = os.getenv("TEST_MODEL_NAME_EXECUTOR")
raw_llm = ChatOpenAI(model_name=model_name)
llms = {
NODE_PROMPT_INITIAL_DEVELOPER: raw_llm,
NODE_ACCEPTANCE_CRITERIA_DEVELOPER: raw_llm,
NODE_PROMPT_DEVELOPER: raw_llm,
NODE_PROMPT_EXECUTOR: raw_llm,
NODE_OUTPUT_HISTORY_ANALYZER: raw_llm,
NODE_PROMPT_ANALYZER: raw_llm,
NODE_PROMPT_SUGGESTER: raw_llm,
}
meta_prompt_graph = MetaPromptGraph(llms=llms)
input_state = AgentState(
user_message="How do I reverse a list in Python?",
expected_output="Use the `[::-1]` slicing technique or the "
"`list.reverse()` method.",
acceptance_criteria="Similar in meaning, text length and style.",
max_output_age=2
)
output_state = meta_prompt_graph(input_state, recursion_limit=25)
pprint.pp(output_state)
assert (
"best_system_message" in output_state
), "The output state should contain the key 'best_system_message'"
assert (
output_state["best_system_message"] is not None
), "The best system message should not be None"
if (
"best_system_message" in output_state
and output_state["best_system_message"] is not None
):
print(output_state["best_system_message"])
user_message = "How can I create a list of numbers in Python?"
messages = [("system", output_state["best_system_message"]), ("human", user_message)]
result = raw_llm.invoke(messages)
assert hasattr(result, "content"), "The result should have the attribute 'content'"
print(result.content)
def test_workflow_execution_with_llms(self):
"""
Test the workflow execution of the MetaPromptGraph with multiple LLMs.
This test case sets up a MetaPromptGraph with multiple language models and
executes it with a given input state. It then verifies that the output
state contains the expected keys and values.
"""
optimizer_llm = ChatOpenAI(
model_name=os.getenv("TEST_MODEL_NAME_OPTIMIZER"), temperature=0.5
)
executor_llm = ChatOpenAI(
model_name=os.getenv("TEST_MODEL_NAME_EXECUTOR"), temperature=0.01
)
llms = {
NODE_PROMPT_INITIAL_DEVELOPER: optimizer_llm,
NODE_ACCEPTANCE_CRITERIA_DEVELOPER: optimizer_llm,
NODE_PROMPT_DEVELOPER: optimizer_llm,
NODE_PROMPT_EXECUTOR: executor_llm,
NODE_OUTPUT_HISTORY_ANALYZER: optimizer_llm,
NODE_PROMPT_ANALYZER: optimizer_llm.bind(response_format={"type": "json_object"}),
NODE_PROMPT_SUGGESTER: optimizer_llm,
}
meta_prompt_graph = MetaPromptGraph(llms=llms)
input_state = AgentState(
max_output_age=2,
user_message="How do I reverse a list in Python?",
expected_output="Use the `[::-1]` slicing technique or the "
"`list.reverse()` method.",
# acceptance_criteria="Similar in meaning, text length and style."
)
output_state = meta_prompt_graph(input_state, recursion_limit=25)
pprint.pp(output_state)
assert (
"best_system_message" in output_state
), "The output state should contain the key 'best_system_message'"
assert (
output_state["best_system_message"] is not None
), "The best system message should not be None"
if (
"best_system_message" in output_state
and output_state["best_system_message"] is not None
):
print(output_state["best_system_message"])
user_message = "How can I create a list of numbers in Python?"
messages = [("system", output_state["best_system_message"]), ("human", user_message)]
result = executor_llm.invoke(messages)
assert hasattr(result, "content"), "The result should have the attribute 'content'"
print(result.content)
def test_simple_workflow_execution(self):
"""
Test the simple workflow execution of the MetaPromptGraph.
This test case sets up a MetaPromptGraph with a mock LLM and executes it
with a given input state. It then verifies that the output state contains
the expected keys and values.
"""
# Create a mock LLM that returns predefined responses based on the input messages
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
responses = [
"Explain how to reverse a list in Python.", # NODE_PROMPT_INITIAL_DEVELOPER
"Here's one way: `my_list[::-1]`", # NODE_PROMPT_EXECUTOR
"{\"Accept\": \"Yes\"}", # NODE_PPROMPT_ANALYZER
]
# everytime llm.invoke was called, it returns a item in responses
llm.invoke = lambda x, y=None: responses.pop(0)
meta_prompt_graph = MetaPromptGraph(llms=llm)
input_state = AgentState(
user_message="How do I reverse a list in Python?",
expected_output="The output should use the `reverse()` method.",
acceptance_criteria="The output should be correct and efficient.",
max_output_age=2
)
output_state = meta_prompt_graph(input_state)
self.assertIsNotNone(output_state['best_system_message'])
self.assertIsNotNone(output_state['best_output'])
pprint.pp(output_state["best_output"])
def test_iterated_workflow_execution(self):
"""
Test the iterated workflow execution of the MetaPromptGraph.
This test case sets up a MetaPromptGraph with a mock LLM and executes it
with a given input state. It then verifies that the output state contains
the expected keys and values. The test case simulates an iterated workflow
where the LLM provides multiple responses based on the input messages.
"""
# Create a mock LLM that returns predefined responses based on the input messages
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
responses = [
"Explain how to reverse a list in Python.", # NODE_PROMPT_INITIAL_DEVELOPER
"Here's one way: `my_list[::-1]`", # NODE_PROMPT_EXECUTOR
"{\"Accept\": \"No\"}", # NODE_PPROMPT_ANALYZER
"Try using the `reverse()` method instead.", # NODE_PROMPT_SUGGESTER
"Explain how to reverse a list in Python. Output in a Markdown List.", # NODE_PROMPT_DEVELOPER
"Here's one way: `my_list.reverse()`", # NODE_PROMPT_EXECUTOR
"{\"closerOutputID\": 2, \"analysis\": \"The output should use the `reverse()` method.\"}", # NODE_OUTPUT_HISTORY_ANALYZER
"{\"Accept\": \"Yes\"}", # NODE_PPROMPT_ANALYZER
]
llm.invoke = lambda x, y = None: responses.pop(0)
meta_prompt_graph = MetaPromptGraph(llms=llm)
input_state = AgentState(
user_message="How do I reverse a list in Python?",
expected_output="The output should use the `reverse()` method.",
acceptance_criteria="The output should be correct and efficient.",
max_output_age=2
)
output_state = meta_prompt_graph(input_state)
self.assertIsNotNone(output_state['best_system_message'])
self.assertIsNotNone(output_state['best_output'])
pprint.pp(output_state["best_output"])
def test_create_acceptance_criteria_workflow(self):
"""
Test the _create_acceptance_criteria_workflow method of MetaPromptGraph.
This test case verifies that the workflow created by the _create_acceptance_criteria_workflow method
contains the correct node and edge.
"""
llms = {
NODE_ACCEPTANCE_CRITERIA_DEVELOPER: ChatOpenAI(model_name=os.getenv("TEST_MODEL_NAME_ACCEPTANCE_CRITERIA_DEVELOPER"))
}
meta_prompt_graph = MetaPromptGraph(llms=llms)
workflow = meta_prompt_graph._create_workflow_for_node(NODE_ACCEPTANCE_CRITERIA_DEVELOPER)
# Check if the workflow contains the correct node
self.assertIn(NODE_ACCEPTANCE_CRITERIA_DEVELOPER, workflow.nodes)
# Check if the workflow contains the correct edge
self.assertIn((NODE_ACCEPTANCE_CRITERIA_DEVELOPER, END), workflow.edges)
# compile the workflow
graph = workflow.compile()
print(graph)
# invoke the workflow
state = AgentState(
user_message="How do I reverse a list in Python?",
expected_output="The output should use the `reverse()` method.",
# system_message="Create acceptance criteria for the task of reversing a list in Python."
)
output_state = graph.invoke(state)
# check if the output state contains the acceptance criteria
self.assertIsNotNone(output_state['acceptance_criteria'])
# check if the acceptance criteria includes string '`reverse()`'
self.assertIn('`reverse()`', output_state['acceptance_criteria'])
pprint.pp(output_state["acceptance_criteria"])
def test_run_acceptance_criteria_graph(self):
"""Test the run_acceptance_criteria_graph method of MetaPromptGraph.
This test case verifies that the run_acceptance_criteria_graph method
returns a state with acceptance criteria.
"""
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
llm.invoke = lambda x, y: "{\"Acceptance criteria\": \"Acceptance criteria: ...\"}"
meta_prompt_graph = MetaPromptGraph(llms=llm)
state = AgentState(
user_message="How do I reverse a list in Python?",
expected_output="The output should use the `reverse()` method.",
)
output_state = meta_prompt_graph.run_node_graph(NODE_ACCEPTANCE_CRITERIA_DEVELOPER, state)
# Check if the output state contains the acceptance criteria
self.assertIsNotNone(output_state["acceptance_criteria"])
# Check if the acceptance criteria includes the expected content
self.assertIn("Acceptance criteria: ...", output_state["acceptance_criteria"])
def test_run_prompt_initial_developer_graph(self):
"""Test the run_prompt_initial_developer_graph method of MetaPromptGraph.
This test case verifies that the run_prompt_initial_developer_graph method
returns a state with an initial developer prompt.
"""
llm = Mock(spec=BaseLanguageModel)
llm.config_specs = []
llm.invoke = lambda x, y: "{\"Initial developer prompt\": \"Initial developer prompt: ...\"}"
meta_prompt_graph = MetaPromptGraph(llms=llm)
state = AgentState(user_message="How do I reverse a list in Python?")
output_state = meta_prompt_graph.run_node_graph(NODE_PROMPT_INITIAL_DEVELOPER, state)
# Check if the output state contains the initial developer prompt
self.assertIsNotNone(output_state['system_message'])
# Check if the initial developer prompt includes the expected content
self.assertIn("Initial developer prompt: ...", output_state['system_message'])
if __name__ == '__main__':
unittest.main()