Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LLM C++ test and doc updates #1379

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion morpheus/_lib/include/morpheus/llm/llm_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class MORPHEUS_EXPORT LLMContext : public std::enable_shared_from_this<LLMContex
std::string full_name() const;

/**
* @brief Create new context from this context with provided name and input mappings.
* @brief Create new context from this context using provided name and input mappings.
*
* @param name name of new context
* @param inputs input mappings for new context
Expand Down
6 changes: 3 additions & 3 deletions morpheus/_lib/include/morpheus/llm/llm_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ namespace morpheus::llm {
/**
* @brief A subclass of LLMNode that acts as container for components required in processing LLM service
* requests. For example, a prompt generator and LLM service can each be implemented as an LLMNode and added to
* the engine. Input mappings are used to match node's input to outputs of parent of sibling nodes. Task handlers
* can also be added to the engine to process the outputs of an LLM service to feed back to prompt generator, for
* example, or exit the engine.
* the engine. Input mappings are used to match node's input to outputs of parent or sibling nodes. Task handlers
* can also be added to the engine to perform additional processing on the outputs stored in the LLMContext of the
* engine.
*/
class MORPHEUS_EXPORT LLMEngine : public LLMNode
{
Expand Down
2 changes: 1 addition & 1 deletion morpheus/_lib/include/morpheus/llm/llm_task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace morpheus::llm {

/**
* @brief Holds information about LLM task. This information is extracted by LLMEngine from
* input control message and saved to context for use by task handler(s).
* input control message and saved to the context for use by task handler(s).
*/
struct MORPHEUS_EXPORT LLMTask
{
Expand Down
2 changes: 1 addition & 1 deletion morpheus/_lib/include/morpheus/llm/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ bool MORPHEUS_EXPORT is_valid_node_name(std::string_view name);

/**
* @brief Resolves user input mappings to final mappings. Handles replacement of placeholders in inputs with
* with actual internal input names provided in input_names.
* actual internal input names provided in input_names.
*
* @param user_inputs user input mappings
* @param input_names internal input names for context
Expand Down
188 changes: 147 additions & 41 deletions morpheus/_lib/tests/llm/test_llm_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,101 @@ TEST_F(TestLLMContext, Initialization)
ASSERT_EQ(ctx_5.full_name(), "/child");
}

TEST_F(TestLLMContext, InitWithLLMTask)
{
nlohmann::json task_dict;
task_dict = {
{"task_type", "dictionary"},
{"model_name", "test"},
};

llm::LLMContext ctx{llm::LLMTask{"template", task_dict}, nullptr};
ASSERT_EQ(ctx.task().get("task_type"), "dictionary");
ASSERT_EQ(ctx.task().get("model_name"), "test");
}

TEST_F(TestLLMContext, InitWithControlMessageTask)
{
nlohmann::json task_dict;
task_dict = {
{"task_type", "dictionary"},
{"model_name", "test"},
};

nlohmann::json msg_config;
msg_config["tasks"] = {{{"type", "llm_engine"}, {"properties", {{"type", "template"}, {"properties", task_dict}}}}};

auto msg = std::make_shared<ControlMessage>(msg_config);

llm::LLMContext ctx{llm::LLMTask{}, msg};
ASSERT_EQ(ctx.message()->has_task("llm_engine"), true);
}

TEST_F(TestLLMContext, InitWithLLMTaskAndControlMessageTask)
{
nlohmann::json task_dict;
task_dict = {
{"task_type", "dictionary"},
{"model_name", "test"},
};

nlohmann::json msg_config;
msg_config["tasks"] = {{{"type", "llm_engine"}, {"properties", {{"type", "template"}, {"properties", task_dict}}}}};

auto msg = std::make_shared<ControlMessage>(msg_config);

llm::LLMContext ctx{llm::LLMTask{"template", task_dict}, msg};
ASSERT_EQ(ctx.message()->has_task("llm_engine"), true);
ASSERT_EQ(ctx.task().get("task_type"), "dictionary");
ASSERT_EQ(ctx.task().get("model_name"), "test");
}

TEST_F(TestLLMContext, InitWithParentContext)
{
nlohmann::json task_dict;
task_dict = {
{"task_type", "dictionary"},
{"model_name", "test"},
};

nlohmann::json msg_config;
msg_config["tasks"] = {{{"type", "llm_engine"}, {"properties", {{"type", "template"}, {"properties", task_dict}}}}};

auto msg = std::make_shared<ControlMessage>(msg_config);

auto parent_ctx = std::make_shared<llm::LLMContext>(llm::LLMTask{"template", task_dict}, msg);
auto inputs = llm::input_mappings_t{{"/ext1", "input1"}};
llm::LLMContext ctx{parent_ctx, "child", inputs};
ASSERT_EQ(ctx.input_map()[0].external_name, "/ext1");
ASSERT_EQ(ctx.input_map()[0].internal_name, "input1");
ASSERT_EQ(ctx.parent()->message()->has_task("llm_engine"), true);
ASSERT_EQ(ctx.parent()->task().get("task_type"), "dictionary");
ASSERT_EQ(ctx.parent()->task().get("model_name"), "test");
ASSERT_EQ(ctx.name(), "child");
ASSERT_EQ(ctx.full_name(), "/child");
}

TEST_F(TestLLMContext, SetOutput)
{
llm::LLMContext ctx_1{llm::LLMTask{}, nullptr};
llm::LLMContext ctx{llm::LLMTask{}, nullptr};
nlohmann::json outputs;
outputs = {{"key1", "val1"}, {"key2", "val2"}};
ctx_1.set_output(outputs);
ctx.set_output(outputs);

ASSERT_EQ(ctx.all_outputs().size(), 2);
ASSERT_EQ(ctx.all_outputs()["key1"], "val1");
ASSERT_EQ(ctx.all_outputs()["key2"], "val2");
}

ASSERT_EQ(ctx_1.all_outputs()["key1"], "val1");
ASSERT_EQ(ctx_1.all_outputs()["key2"], "val2");
TEST_F(TestLLMContext, SetOutputDict)
{
llm::LLMContext ctx{llm::LLMTask{}, nullptr};
nlohmann::json outputs;
outputs = {{"key1", "val1"}, {"key2", "val2"}};

llm::LLMContext ctx_2{llm::LLMTask{}, nullptr};
ctx_2.set_output("output", outputs);
ASSERT_EQ(ctx_2.all_outputs()["output"]["key1"], "val1");
ASSERT_EQ(ctx_2.all_outputs()["output"]["key2"], "val2");
ctx.set_output("output", outputs);
ASSERT_EQ(ctx.all_outputs()["output"]["key1"], "val1");
ASSERT_EQ(ctx.all_outputs()["output"]["key2"], "val2");
}

TEST_F(TestLLMContext, PushPop)
Expand Down Expand Up @@ -188,51 +269,76 @@ TEST_F(TestLLMContext, PopSelectMultipleOutputs)
ASSERT_EQ(child_ctx->parent()->all_outputs()["child"]["key3"], "val3");
}

TEST_F(TestLLMContext, SingleInputMapping)
TEST_F(TestLLMContext, SingleInputMappingValid)
{
auto parent_ctx = std::make_shared<llm::LLMContext>(llm::LLMTask{}, nullptr);
nlohmann::json outputs;
outputs = {{"parent_out", "val1"}};
parent_ctx->set_output(outputs);

auto inputs_1 = llm::input_mappings_t{{"/parent_out", "input1"}};
llm::LLMContext child_ctx_1{parent_ctx, "child", inputs_1};
ASSERT_EQ(child_ctx_1.get_input(), "val1");
ASSERT_EQ(child_ctx_1.get_input("input1"), "val1");
ASSERT_EQ(child_ctx_1.get_inputs()["input1"], "val1");
ASSERT_THROW(child_ctx_1.get_input("input2"), std::runtime_error);

auto inputs_2 = llm::input_mappings_t{{"/invalid", "input1"}};
llm::LLMContext child_ctx_2{parent_ctx, "child", inputs_2};
ASSERT_THROW(child_ctx_2.get_input(), std::runtime_error);
ASSERT_THROW(child_ctx_2.get_input("input1"), std::runtime_error);
ASSERT_THROW(child_ctx_2.get_inputs()["input1"], std::runtime_error);
auto inputs = llm::input_mappings_t{{"/parent_out", "input1"}};
llm::LLMContext child_ctx{parent_ctx, "child", inputs};
ASSERT_EQ(child_ctx.get_input(), "val1");
ASSERT_EQ(child_ctx.get_input("input1"), "val1");
ASSERT_EQ(child_ctx.get_inputs()["input1"], "val1");
ASSERT_THROW(child_ctx.get_input("input2"), std::runtime_error);
}

TEST_F(TestLLMContext, MultipleInputMappings)
TEST_F(TestLLMContext, SingleInputMappingInvalid)
{
auto parent_ctx = std::make_shared<llm::LLMContext>(llm::LLMTask{}, nullptr);
nlohmann::json outputs;
outputs = {{"parent_out", "val1"}};
parent_ctx->set_output(outputs);

auto inputs = llm::input_mappings_t{{"/invalid", "input1"}};
llm::LLMContext child_ctx{parent_ctx, "child", inputs};
ASSERT_THROW(child_ctx.get_input(), std::runtime_error);
ASSERT_THROW(child_ctx.get_input("input1"), std::runtime_error);
ASSERT_THROW(child_ctx.get_inputs()["input1"], std::runtime_error);
}

TEST_F(TestLLMContext, MultipleInputMappingsValid)
{
auto parent_ctx = std::make_shared<llm::LLMContext>(llm::LLMTask{}, nullptr);
nlohmann::json outputs;
outputs = {{"parent_out1", "val1"}, {"parent_out2", "val2"}};
parent_ctx->set_output(outputs);

auto inputs_1 = llm::input_mappings_t{{"/parent_out1", "input1"}, {"/parent_out2", "input2"}};
llm::LLMContext child_ctx_1{parent_ctx, "child", inputs_1};
ASSERT_EQ(child_ctx_1.get_input("input1"), "val1");
ASSERT_EQ(child_ctx_1.get_input("input2"), "val2");
ASSERT_EQ(child_ctx_1.get_inputs()["input1"], "val1");
ASSERT_EQ(child_ctx_1.get_inputs()["input2"], "val2");
ASSERT_THROW(child_ctx_1.get_input(), std::runtime_error);
ASSERT_THROW(child_ctx_1.get_input("input3"), std::runtime_error);

auto inputs_2 = llm::input_mappings_t{{"/invalid", "input1"}};
llm::LLMContext child_ctx_2{parent_ctx, "child", inputs_2};
ASSERT_THROW(child_ctx_2.get_input(), std::runtime_error);
ASSERT_THROW(child_ctx_2.get_input("input1"), std::runtime_error);
ASSERT_THROW(child_ctx_2.get_inputs()["input1"], std::runtime_error);

auto inputs_3 = llm::input_mappings_t{{"/parent_out1", "input1"}, {"/invalid", "input2"}};
llm::LLMContext child_ctx_3{parent_ctx, "child", inputs_2};
ASSERT_EQ(child_ctx_1.get_input("input1"), "val1");
ASSERT_THROW(child_ctx_2.get_input("input2"), std::runtime_error);
auto inputs = llm::input_mappings_t{{"/parent_out1", "input1"}, {"/parent_out2", "input2"}};
llm::LLMContext child_ctx{parent_ctx, "child", inputs};
ASSERT_EQ(child_ctx.get_input("input1"), "val1");
ASSERT_EQ(child_ctx.get_input("input2"), "val2");
ASSERT_EQ(child_ctx.get_inputs()["input1"], "val1");
ASSERT_EQ(child_ctx.get_inputs()["input2"], "val2");
ASSERT_THROW(child_ctx.get_input(), std::runtime_error);
ASSERT_THROW(child_ctx.get_input("input3"), std::runtime_error);
}

TEST_F(TestLLMContext, MultipleInputMappingsSingleInvalid)
{
auto parent_ctx = std::make_shared<llm::LLMContext>(llm::LLMTask{}, nullptr);
nlohmann::json outputs;
outputs = {{"parent_out1", "val1"}, {"parent_out2", "val2"}};
parent_ctx->set_output(outputs);

auto inputs = llm::input_mappings_t{{"/parent_out1", "input1"}, {"/invalid", "input2"}};
llm::LLMContext child_ctx{parent_ctx, "child", inputs};
ASSERT_EQ(child_ctx.get_input("input1"), "val1");
ASSERT_THROW(child_ctx.get_input("input2"), std::runtime_error);
ASSERT_THROW(child_ctx.get_inputs(), std::runtime_error);
}

TEST_F(TestLLMContext, MultipleInputMappingsBothInvalid)
{
auto parent_ctx = std::make_shared<llm::LLMContext>(llm::LLMTask{}, nullptr);
nlohmann::json outputs;
outputs = {{"parent_out1", "val1"}, {"parent_out2", "val2"}};
parent_ctx->set_output(outputs);

auto inputs = llm::input_mappings_t{{"/invalid1", "input1"}, {"/invalid2", "input2"}};
llm::LLMContext child_ctx{parent_ctx, "child", inputs};
ASSERT_THROW(child_ctx.get_input("input1"), std::runtime_error);
ASSERT_THROW(child_ctx.get_input("input2"), std::runtime_error);
ASSERT_THROW(child_ctx.get_inputs(), std::runtime_error);
}
Loading