From ae48e616ec6ed2a33cf4aa9cc5f7119fb1c6421d Mon Sep 17 00:00:00 2001 From: cybercoder Date: Tue, 27 Feb 2024 07:00:26 -0500 Subject: [PATCH 01/10] Create kagent.py add keras agent skeleton --- intelli/flow/agents/kagent.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 intelli/flow/agents/kagent.py diff --git a/intelli/flow/agents/kagent.py b/intelli/flow/agents/kagent.py new file mode 100644 index 0000000..005fe7d --- /dev/null +++ b/intelli/flow/agents/kagent.py @@ -0,0 +1,19 @@ +from intelli.flow.agents.agent import BasicAgent +from intelli.flow.input.agent_input import AgentInput, TextAgentInput, ImageAgentInput + +class KerasAgent(BasicAgent): + def __init__(self, agent_type, provider, mission, model_params, options=None): + super().__init__(agent_type, provider, mission, model_params, options) + try: + import keras_nlp + self.keras_nlp = keras_nlp + self.KERAS_AVAILABLE = True + except ImportError: + self.KERAS_AVAILABLE = False + + def execute(self, agent_input: AgentInput): + if self.KERAS_AVAILABLE: + # todo add the execute logic + pass + else: + raise Exception("keras_nlp is not available. This function cannot proceed.") \ No newline at end of file From 11f3289bf85fade5d189f878c7e25de48dd996c2 Mon Sep 17 00:00:00 2001 From: cybercoder Date: Tue, 27 Feb 2024 07:36:33 -0500 Subject: [PATCH 02/10] Update setup.py add optional requirements --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b260cfb..b3768cb 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,6 @@ "python-dotenv==1.0.1", "networkx==3.2.1", ], extras_require={ - 'visual': ["matplotlib==3.6.0"], + 'visual': ["matplotlib==3.6.0", "keras>=3", "keras-nlp"], } ) \ No newline at end of file From 1091336e2e1a9d6bce9d14f5b319133c9b4b0277 Mon Sep 17 00:00:00 2001 From: cybercoder Date: Tue, 27 Feb 2024 08:12:56 -0500 Subject: [PATCH 03/10] Add keras agent full logic --- instructions/run_integration_text.sh | 4 +- intelli/flow/agents/kagent.py | 61 +++++++++++++++++--- intelli/test/integration/test_keras_agent.py | 44 ++++++++++++++ setup.py | 2 +- 4 files changed, 100 insertions(+), 11 deletions(-) create mode 100644 intelli/test/integration/test_keras_agent.py diff --git a/instructions/run_integration_text.sh b/instructions/run_integration_text.sh index c5cf762..718bade 100644 --- a/instructions/run_integration_text.sh +++ b/instructions/run_integration_text.sh @@ -44,4 +44,6 @@ python3 -m unittest intelli.test.integration.test_chatbot_with_data # basic flow python3 -m unittest intelli.test.integration.test_flow_sequence # map flow -python3 -m unittest intelli.test.integration.test_flow_map \ No newline at end of file +python3 -m unittest intelli.test.integration.test_flow_map +# keras nlp +python3 -m unittest intelli.test.integration.test_keras_agent \ No newline at end of file diff --git a/intelli/flow/agents/kagent.py b/intelli/flow/agents/kagent.py index 005fe7d..a79a1cc 100644 --- a/intelli/flow/agents/kagent.py +++ b/intelli/flow/agents/kagent.py @@ -1,19 +1,62 @@ from intelli.flow.agents.agent import BasicAgent from intelli.flow.input.agent_input import AgentInput, TextAgentInput, ImageAgentInput +import os class KerasAgent(BasicAgent): - def __init__(self, agent_type, provider, mission, model_params, options=None): - super().__init__(agent_type, provider, mission, model_params, options) + def __init__(self, agent_type, provider="", mission="", model_params={}, options=None): + super().__init__() + + # set the parameters + self.agent_type = agent_type + self.provider = provider + self.mission = mission + self.model_params = model_params + self.options = options if options is not None else {} + + self.model = self.load_model() + + def load_model(self): + """ + Dynamically load a model based on `model_params`. + This example demonstrates loading Gemma models, but you should add similar logic for other models. + """ try: + # import keras import keras_nlp self.keras_nlp = keras_nlp - self.KERAS_AVAILABLE = True - except ImportError: - self.KERAS_AVAILABLE = False + model_param = self.model_params['model'] + + if "gemma" in model_param: + print('start gemma model') + from keras_nlp.models import GemmaCausalLM + + # set the username and password + if "KAGGLE_USERNAME" in self.model_params: + os.environ["KAGGLE_USERNAME"] = self.model_params["KAGGLE_USERNAME"] + os.environ["KAGGLE_KEY"] = self.model_params["KAGGLE_KEY"] + + return GemmaCausalLM.from_preset(model_param) + # ------------------------------------------------------------------ # + # Add similar conditions for models like Mistral, RoBERTa, or BERT # + # ------------------------------------------------------------------ # + else: + raise Exception("The received model not supported in this version.") + + except ImportError as e: + raise ImportError("keras_nlp is not installed or model is not supported.") from e def execute(self, agent_input: AgentInput): - if self.KERAS_AVAILABLE: - # todo add the execute logic - pass + """ + Execute the agent task based on input. + """ + + if not isinstance(agent_input, TextAgentInput): + raise ValueError("This agent requires a TextAgentInput.") + + max_length = self.model_params.get("max_length", 64) + model_input = agent_input.desc if not self.mission else self.mission + ": " + agent_input.desc + + if hasattr(self.model, 'generate'): + return self.model.generate(model_input, max_length=max_length) else: - raise Exception("keras_nlp is not available. This function cannot proceed.") \ No newline at end of file + raise NotImplementedError("Model does not support text generation.") diff --git a/intelli/test/integration/test_keras_agent.py b/intelli/test/integration/test_keras_agent.py new file mode 100644 index 0000000..7f8364b --- /dev/null +++ b/intelli/test/integration/test_keras_agent.py @@ -0,0 +1,44 @@ +import os +import base64 +import unittest +from intelli.flow.types import * +from intelli.flow.agents.agent import Agent +from intelli.flow.agents.kagent import KerasAgent +from intelli.flow.input.task_input import TextTaskInput, ImageTaskInput +from intelli.flow.processors.basic_processor import TextProcessor +from intelli.flow.sequence_flow import SequenceFlow +from intelli.flow.tasks.task import Task +from dotenv import load_dotenv + +load_dotenv() + +class TestKerasFlows(unittest.TestCase): + def setUp(self): + self.kaggle_username = os.getenv("KAGGLE_USERNAME") + self.kaggle_pass = os.getenv("GEMINI_API_KEY") + + def test_blog_post_flow(self): + print("---- start simple blog post flow ----") + + # Define agents + gemma_model_params = { + "model": "gemma_2b_en", + "max_length": 64 + } + gemma_agent = KerasAgent(agent_type="text", + mission="write blog posts", + model_params=gemma_model_params) + # Define tasks + task1 = Task( + TextTaskInput("blog post about electric cars"), gemma_agent, log=True + ) + + # Start SequenceFlow + flow = SequenceFlow([task1], log=True) + final_result = flow.start() + + print("Final result:", final_result) + + +if __name__ == "__main__": + unittest.main() diff --git a/setup.py b/setup.py index b3768cb..51dbcca 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="intelli", - version="0.1.6", + version="0.1.7", author="Intellinode", author_email="admin@intellinode.ai", description="Create chatbots and AI agent workflows. Intelli provide unified layer to connect your data with multiple AI models like OpenAI, Gemini, and Mistral.", From 1a2a61ba5080105d624270457ce44e38217ea1ea Mon Sep 17 00:00:00 2001 From: cybercoder Date: Tue, 27 Feb 2024 08:17:28 -0500 Subject: [PATCH 04/10] Update setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 51dbcca..e4de5bf 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ "python-dotenv==1.0.1", "networkx==3.2.1", ], extras_require={ - 'visual': ["matplotlib==3.6.0", "keras>=3", "keras-nlp"], + "visual": ["matplotlib==3.6.0"], + "offline": ["keras>=3", "keras-nlp"], } ) \ No newline at end of file From e5ef3bfbcd63bc643e7312d8487f47ee9f8d72fd Mon Sep 17 00:00:00 2001 From: cybercoder Date: Fri, 1 Mar 2024 07:33:34 -0500 Subject: [PATCH 05/10] Update keras agent - fix a bug in the agent type. - add mistral support. --- intelli/flow/agents/kagent.py | 28 +++++++++++++------- intelli/test/integration/test_keras_agent.py | 1 + setup.py | 4 +-- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/intelli/flow/agents/kagent.py b/intelli/flow/agents/kagent.py index a79a1cc..c8749eb 100644 --- a/intelli/flow/agents/kagent.py +++ b/intelli/flow/agents/kagent.py @@ -7,7 +7,7 @@ def __init__(self, agent_type, provider="", mission="", model_params={}, options super().__init__() # set the parameters - self.agent_type = agent_type + self.type = agent_type self.provider = provider self.mission = mission self.model_params = model_params @@ -15,6 +15,9 @@ def __init__(self, agent_type, provider="", mission="", model_params={}, options self.model = self.load_model() + def update_model_params(self, model_params): + self.model_params = model_params + def load_model(self): """ Dynamically load a model based on `model_params`. @@ -23,19 +26,24 @@ def load_model(self): try: # import keras import keras_nlp - self.keras_nlp = keras_nlp - model_param = self.model_params['model'] + os.environ["KERAS_BACKEND"] = "jax" + model_param = self.model_params["model"] + + # set the username and password + if "KAGGLE_USERNAME" in self.model_params: + os.environ["KAGGLE_USERNAME"] = self.model_params["KAGGLE_USERNAME"] + os.environ["KAGGLE_KEY"] = self.model_params["KAGGLE_KEY"] if "gemma" in model_param: - print('start gemma model') + print("start gemma model") from keras_nlp.models import GemmaCausalLM - # set the username and password - if "KAGGLE_USERNAME" in self.model_params: - os.environ["KAGGLE_USERNAME"] = self.model_params["KAGGLE_USERNAME"] - os.environ["KAGGLE_KEY"] = self.model_params["KAGGLE_KEY"] - return GemmaCausalLM.from_preset(model_param) + elif "mistral" in model_param: + print("start mistral model") + from keras_nlp.models import MistralCausalLM + + return MistralCausalLM.from_preset(model_param) # ------------------------------------------------------------------ # # Add similar conditions for models like Mistral, RoBERTa, or BERT # # ------------------------------------------------------------------ # @@ -56,7 +64,7 @@ def execute(self, agent_input: AgentInput): max_length = self.model_params.get("max_length", 64) model_input = agent_input.desc if not self.mission else self.mission + ": " + agent_input.desc - if hasattr(self.model, 'generate'): + if hasattr(self.model, "generate"): return self.model.generate(model_input, max_length=max_length) else: raise NotImplementedError("Model does not support text generation.") diff --git a/intelli/test/integration/test_keras_agent.py b/intelli/test/integration/test_keras_agent.py index 7f8364b..0d24151 100644 --- a/intelli/test/integration/test_keras_agent.py +++ b/intelli/test/integration/test_keras_agent.py @@ -28,6 +28,7 @@ def test_blog_post_flow(self): gemma_agent = KerasAgent(agent_type="text", mission="write blog posts", model_params=gemma_model_params) + # Define tasks task1 = Task( TextTaskInput("blog post about electric cars"), gemma_agent, log=True diff --git a/setup.py b/setup.py index e4de5bf..114ec16 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="intelli", - version="0.1.7", + version="0.1.10", author="Intellinode", author_email="admin@intellinode.ai", description="Create chatbots and AI agent workflows. Intelli provide unified layer to connect your data with multiple AI models like OpenAI, Gemini, and Mistral.", @@ -22,6 +22,6 @@ ], extras_require={ "visual": ["matplotlib==3.6.0"], - "offline": ["keras>=3", "keras-nlp"], + "offline": ["keras-nlp", "keras>=3"], } ) \ No newline at end of file From 827b0d43f728881508c66cd00764bc547f1d14f4 Mon Sep 17 00:00:00 2001 From: cybercoder Date: Fri, 1 Mar 2024 20:00:27 -0500 Subject: [PATCH 06/10] Update kagent.py - add fine tuning. - adjust the initial. - add a function to add externally fined tuned model. --- intelli/flow/agents/kagent.py | 131 +++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 33 deletions(-) diff --git a/intelli/flow/agents/kagent.py b/intelli/flow/agents/kagent.py index c8749eb..09c1fee 100644 --- a/intelli/flow/agents/kagent.py +++ b/intelli/flow/agents/kagent.py @@ -3,7 +3,7 @@ import os class KerasAgent(BasicAgent): - def __init__(self, agent_type, provider="", mission="", model_params={}, options=None): + def __init__(self, agent_type, provider="", mission="", model_params={}, options=None, log=False, external=False): super().__init__() # set the parameters @@ -12,10 +12,30 @@ def __init__(self, agent_type, provider="", mission="", model_params={}, options self.mission = mission self.model_params = model_params self.options = options if options is not None else {} + self.log = log + self.external = external + try: + import keras_nlp + import keras + self.nlp_manager = keras_nlp + self.keras_manager = keras + if not external: + self.model = self.load_model() + except ImportError as e: + raise ImportError("keras_nlp is not installed or model is not supported.") from e + + def set_keras_model(self, model, model_params): + if not self.external: + raise Exception("Initiate the agent with external flag to set the model.") - self.model = self.load_model() + if not hasattr(model, "generate"): + raise ValueError("The provided model does not have a 'generate' method, which is required for this agent.") + + self.model = model + self.model_params = model_params def update_model_params(self, model_params): + self.model_params = model_params def load_model(self): @@ -23,35 +43,27 @@ def load_model(self): Dynamically load a model based on `model_params`. This example demonstrates loading Gemma models, but you should add similar logic for other models. """ - try: - # import keras - import keras_nlp - os.environ["KERAS_BACKEND"] = "jax" - model_param = self.model_params["model"] - - # set the username and password - if "KAGGLE_USERNAME" in self.model_params: - os.environ["KAGGLE_USERNAME"] = self.model_params["KAGGLE_USERNAME"] - os.environ["KAGGLE_KEY"] = self.model_params["KAGGLE_KEY"] - - if "gemma" in model_param: - print("start gemma model") - from keras_nlp.models import GemmaCausalLM - - return GemmaCausalLM.from_preset(model_param) - elif "mistral" in model_param: - print("start mistral model") - from keras_nlp.models import MistralCausalLM - - return MistralCausalLM.from_preset(model_param) - # ------------------------------------------------------------------ # - # Add similar conditions for models like Mistral, RoBERTa, or BERT # - # ------------------------------------------------------------------ # - else: - raise Exception("The received model not supported in this version.") - - except ImportError as e: - raise ImportError("keras_nlp is not installed or model is not supported.") from e + + self.keras_manager = keras + os.environ["KERAS_BACKEND"] = "jax" + model_param = self.model_params["model"] + + # set the username and password + if "KAGGLE_USERNAME" in self.model_params: + os.environ["KAGGLE_USERNAME"] = self.model_params["KAGGLE_USERNAME"] + os.environ["KAGGLE_KEY"] = self.model_params["KAGGLE_KEY"] + + if "gemma" in model_param: + print("start gemma model") + return self.nlp_manager.models.GemmaCausalLM.from_preset(model_param) + elif "mistral" in model_param: + print("start mistral model") + return self.nlp_manager.models.MistralCausalLM.from_preset(model_param) + # ------------------------------------------------------------------ # + # Add similar conditions for models like Mistral, RoBERTa, or BERT # + # ------------------------------------------------------------------ # + else: + raise Exception("The received model not supported in this version.") def execute(self, agent_input: AgentInput): """ @@ -63,8 +75,61 @@ def execute(self, agent_input: AgentInput): max_length = self.model_params.get("max_length", 64) model_input = agent_input.desc if not self.mission else self.mission + ": " + agent_input.desc - + if hasattr(self.model, "generate"): - return self.model.generate(model_input, max_length=max_length) + if self.log: + print("Call the model generate with input: ", model_input) + + generated_output = self.model.generate(model_input, max_length=max_length) + + if isinstance(generated_output, str) and generated_output.startswith(model_input): + generated_output = generated_output.replace(model_input, "", 1).strip() + + return generated_output else: raise NotImplementedError("Model does not support text generation.") + + def fine_tune_model_with_lora(self, fine_tuning_config): + """ + Finetunes the model as per the provided config. + """ + print("Fine tuning model...") + + # rank=4 replaces the weights matrix of relevant layers with the product AxB of two matrices of rank 4, + # which reduces the number of trainable parameters. + lora_rank = fine_tuning_config.get("lora_rank", 4) + + # Enable lora for the model learning + self.model.backbone.enable_lora(rank=lora_rank) + + # Set the preprocessor sequence_length + self.model.preprocessor.sequence_length = fine_tuning_config.get("sequence_length", 512) + + # Use AdamW optimizer. + learning_rate = fine_tuning_config.get("learning_rate", 0.001) + weight_decay = fine_tuning_config.get("weight_decay", 0.004) + beta_1 = fine_tuning_config.get("beta_1", 0.9) + beta_2 = fine_tuning_config.get("beta_2", 0.999) + + optimizer = self.keras_manager.optimizers.AdamW( + learning_rate=learning_rate, + weight_decay=weight_decay, + beta_1=beta_1, + beta_2=beta_2 + ) + + # Exclude layernorm and bias terms from decay. + optimizer.exclude_from_weight_decay(var_names=["bias", "scale"]) + + # Compile the model + self.model.compile( + loss=self.keras_manager.losses.SparseCategoricalCrossentropy(from_logits=True), + optimizer=optimizer, + weighted_metrics=[self.keras_manager.metrics.SparseCategoricalAccuracy()], + ) + + # Fit using input dataset, epochs and batch size + dataset = fine_tuning_config.get('dataset') + epochs = fine_tuning_config.get('epochs', 3) + batch_size = fine_tuning_config.get('batch_size', 1) + self.model.fit(dataset, epochs=epochs, batch_size=batch_size) From 0710d14055f5c27463fa50456d18fb8875b47cba Mon Sep 17 00:00:00 2001 From: cybercoder Date: Fri, 1 Mar 2024 20:03:00 -0500 Subject: [PATCH 07/10] Update setup.py update the version. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 114ec16..0966d7e 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="intelli", - version="0.1.10", + version="0.1.11", author="Intellinode", author_email="admin@intellinode.ai", description="Create chatbots and AI agent workflows. Intelli provide unified layer to connect your data with multiple AI models like OpenAI, Gemini, and Mistral.", From a68166ccde297999c2d4967ae233a6215682138d Mon Sep 17 00:00:00 2001 From: cybercoder Date: Fri, 1 Mar 2024 20:38:04 -0500 Subject: [PATCH 08/10] update the version --- intelli/flow/agents/kagent.py | 4 +--- setup.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/intelli/flow/agents/kagent.py b/intelli/flow/agents/kagent.py index 09c1fee..6a3c072 100644 --- a/intelli/flow/agents/kagent.py +++ b/intelli/flow/agents/kagent.py @@ -43,9 +43,7 @@ def load_model(self): Dynamically load a model based on `model_params`. This example demonstrates loading Gemma models, but you should add similar logic for other models. """ - - self.keras_manager = keras - os.environ["KERAS_BACKEND"] = "jax" + model_param = self.model_params["model"] # set the username and password diff --git a/setup.py b/setup.py index 0966d7e..b3adef9 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="intelli", - version="0.1.11", + version="0.2.0", author="Intellinode", author_email="admin@intellinode.ai", description="Create chatbots and AI agent workflows. Intelli provide unified layer to connect your data with multiple AI models like OpenAI, Gemini, and Mistral.", From d7cfa7a529ef39a8e3439100b18485a0f8013558 Mon Sep 17 00:00:00 2001 From: cybercoder Date: Fri, 1 Mar 2024 20:44:23 -0500 Subject: [PATCH 09/10] Update test_keras_agent.py --- intelli/test/integration/test_keras_agent.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/intelli/test/integration/test_keras_agent.py b/intelli/test/integration/test_keras_agent.py index 0d24151..15021b3 100644 --- a/intelli/test/integration/test_keras_agent.py +++ b/intelli/test/integration/test_keras_agent.py @@ -1,29 +1,31 @@ import os -import base64 import unittest from intelli.flow.types import * from intelli.flow.agents.agent import Agent from intelli.flow.agents.kagent import KerasAgent -from intelli.flow.input.task_input import TextTaskInput, ImageTaskInput -from intelli.flow.processors.basic_processor import TextProcessor +from intelli.flow.input.task_input import TextTaskInput from intelli.flow.sequence_flow import SequenceFlow from intelli.flow.tasks.task import Task from dotenv import load_dotenv - +# set keras back +os.environ["KERAS_BACKEND"] = "jax" +# load env load_dotenv() class TestKerasFlows(unittest.TestCase): def setUp(self): self.kaggle_username = os.getenv("KAGGLE_USERNAME") - self.kaggle_pass = os.getenv("GEMINI_API_KEY") + self.kaggle_pass = os.getenv("KAGGLE_API_KEY") def test_blog_post_flow(self): print("---- start simple blog post flow ----") # Define agents gemma_model_params = { - "model": "gemma_2b_en", - "max_length": 64 + "model": "gemma_instruct_2b_en", + "max_length": 64, + "KAGGLE_USERNAME": self.kaggle_username, + "KAGGLE_KEY": self.kaggle_pass, } gemma_agent = KerasAgent(agent_type="text", mission="write blog posts", @@ -39,6 +41,7 @@ def test_blog_post_flow(self): final_result = flow.start() print("Final result:", final_result) + self.assertIsNotNone(final_result) if __name__ == "__main__": From a1caeb15c3c256ef432cea7fd2724834c7b5da7f Mon Sep 17 00:00:00 2001 From: cybercoder Date: Fri, 1 Mar 2024 20:48:03 -0500 Subject: [PATCH 10/10] update Keras instructions --- README.md | 5 ++++- intelli/.example.env | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 449b62d..462a67c 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,10 @@ wrapper = RemoteImageModel(your_api_key, provider) results = wrapper.generate_images(image_input) ``` -# The Repository Setup +## Keras Agent +Load gemma or mistral models offline using keras agent, [check the docs](https://docs.intellinode.ai/docs/python/flows/kagent). + +# Repository Setup 1. Install the requirements. ```shell pip install -r requirements.txt diff --git a/intelli/.example.env b/intelli/.example.env index 6420c26..b290449 100644 --- a/intelli/.example.env +++ b/intelli/.example.env @@ -7,3 +7,5 @@ REPLICATE_API_KEY= MISTRAL_API_KEY= INTELLI_ONE_KEY= GEMINI_API_KEY= +KAGGLE_USERNAME= +KAGGLE_API_KEY= \ No newline at end of file