diff --git a/lollms/functions/analyze_code/__init__.py b/lollms/functions/analyze_code/__init__.py new file mode 100644 index 0000000..1872f84 --- /dev/null +++ b/lollms/functions/analyze_code/__init__.py @@ -0,0 +1,7 @@ +from lollms.functions.analyze_code.create_project_database import create_project_database, create_project_database_function +from lollms.functions.analyze_code.retreive_information_for_task import retrieve_information_for_task, retrieve_information_for_task_function +from lollms.functions.analyze_code.retrieve_classes_from_project import retrieve_classes_from_project, retrieve_classes_from_project_function +from lollms.functions.analyze_code.update_class_in_file import update_class_in_file, update_class_in_file_function +from lollms.functions.analyze_code.update_function_in_file import update_function_in_file, update_function_in_file_function +from lollms.functions.analyze_code.add_function_to_file import add_function_to_file, add_function_to_file_function + diff --git a/lollms/functions/analyze_code/add_function_to_file.py b/lollms/functions/analyze_code/add_function_to_file.py new file mode 100644 index 0000000..3571076 --- /dev/null +++ b/lollms/functions/analyze_code/add_function_to_file.py @@ -0,0 +1,58 @@ +# Lollms function call definition file +from functools import partial +from lollms.utilities import PackageManager +from ascii_colors import trace_exception +from typing import List, Union +from pathlib import Path +import ast + +def add_function_to_file(file_path: Union[str, Path], function_name: str, function_content: str) -> str: + """ + Adds a new function to the specified file. + + Args: + file_path (Union[str, Path]): The path to the Python file. + function_name (str): The name of the function to add. + function_content (str): The content of the function to add. + + Returns: + str: Success message or error message. + """ + try: + file_path = Path(file_path) + + # Validate the file path + if not file_path.exists() or not file_path.is_file(): + return "Invalid file path." + + with open(file_path, "r", encoding="utf-8") as file: + content = file.read() + tree = ast.parse(content) + + # Check if function already exists + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef) and node.name == function_name: + return f"Function {function_name} already exists in the file." + + # Add the new function content at the end of the file + with open(file_path, "a", encoding="utf-8") as file: + file.write("\n\n" + function_content) + + return f"Function {function_name} added successfully to file {file_path}." + + except Exception as e: + return trace_exception(e) + + +# Metadata function +def add_function_to_file_function(): + return { + "function_name": "add_function_to_file", # The function name in string + "function": add_function_to_file, # The function to be called + "function_description": "Adds a new function to the specified file.", # Description of the function + "function_parameters": [ # The set of parameters + {"name": "file_path", "type": "str"}, + {"name": "function_name", "type": "str"}, + {"name": "function_content", "type": "str"}, + ] + } \ No newline at end of file diff --git a/lollms/functions/analyze_code.py b/lollms/functions/analyze_code/create_project_database.py similarity index 58% rename from lollms/functions/analyze_code.py rename to lollms/functions/analyze_code/create_project_database.py index 4ddf8b4..74199a5 100644 --- a/lollms/functions/analyze_code.py +++ b/lollms/functions/analyze_code/create_project_database.py @@ -1,4 +1,8 @@ -# Lollms function call definition file +""" + +""" + + from functools import partial from typing import List, Dict, Union, Any from lollms.utilities import PackageManager @@ -129,102 +133,4 @@ def create_project_database_function(project_path, llm): "function": partial(create_project_database,project_path=project_path, llm=llm), "function_description": "Creates a database containing structured information about a Python project.", "function_parameters": [] - } - - - -# Lollms function call definition file -from functools import partial -from typing import List, Dict -from lollms.utilities import PackageManager -from ascii_colors import trace_exception -from pathlib import Path -import sqlite3 - -# Ensure required packages are installed -if not PackageManager.check_package_installed("sqlite3"): - PackageManager.install_package("sqlite3") - -def retrieve_information_for_task(project_path: str, task_description: str, llm: APScript) -> Union[str, Dict[str, str]]: - """ - Retrieves information from the database to perform a task given by the user. - - Args: - project_path (str): The path to the project directory. - task_description (str): The description of the task to perform. - llm (APScript): The language model instance for generating SQL queries. - - Returns: - Union[str, Dict[str, str]]: A string containing relevant information or an error message. - """ - try: - db_path = Path(project_path) / "project_info.db" - - # Validate the database path - if not db_path.exists() or not db_path.is_file(): - return "Invalid database path." - - conn = sqlite3.connect(db_path) - cursor = conn.cursor() - - # Retrieve the list of classes and their descriptions - cursor.execute("SELECT name, docstring FROM classes") - classes = cursor.fetchall() - - # Format the classes into a string - classes_text = "\n".join([f"Class: {cls[0]}, Description: {cls[1]}" for cls in classes]) - - # Ask the LLM which classes are needed for the task - prompt = f"{llm.personality.config.start_header_id_template}{llm.personality.config.system_message_template}{llm.personality.config.end_header_id_template}" \ - f"Given the following list of classes and their descriptions:\n" \ - f"{classes_text}\n\n" \ - f"Task description: {task_description}\n\n" \ - f"{llm.personality.config.start_header_id_template}instructions{llm.personality.config.end_header_id_template}" \ - f"Which classes are needed to perform the task? List the class names.\n" \ - f"Answer in form of a json list inside a json markdown tag.\n" \ - f"{llm.personality.config.start_header_id_template}assistant{llm.personality.config.end_header_id_template}" - - needed_classes = llm.fast_gen(prompt, callback=llm.sink).strip() - needed_classes = llm.extract_code_blocks(needed_classes) - if len(needed_classes)>0: - needed_classes = json.loads(needed_classes[0]["content"]) - # Retrieve the relevant information for the needed classes - class_info = {} - for class_name in needed_classes: - cursor.execute("SELECT * FROM classes WHERE name = ?", (class_name,)) - class_info[class_name] = cursor.fetchone() - - # Retrieve the project description and structure - cursor.execute("SELECT name, description FROM project_info") - project_info = cursor.fetchone() - - cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") - tables = cursor.fetchall() - - conn.close() - - # Format the results into a string - result_text = f"Project Name: {project_info[0]}\nProject Description: {project_info[1]}\n\n" - result_text += "Project Structure:\n" + "\n".join([table[0] for table in tables]) + "\n\n" - result_text += "Needed Classes Information:\n" - for class_name, info in class_info.items(): - result_text += f"Class: {class_name}\n" - result_text += f"Description: {info[2]}\n" - result_text += f"Methods: {info[4]}\n" - result_text += f"Static Methods: {info[5]}\n\n" - - return result_text.strip() - else: - return "Failed to ask the llm" - except Exception as e: - return str(e) - -def retrieve_information_for_task_function(project_path, llm): - return { - "function_name": "retrieve_information_for_task", - "function": partial(retrieve_information_for_task, project_path=project_path, llm=llm), - "function_description": "Retrieves information from the database to perform a task given by the user.", - "function_parameters": [ - {"name": "task_description", "type": "str", "description":"a description of "} - ] - } + } \ No newline at end of file diff --git a/lollms/functions/analyze_code/retreive_information_for_task.py b/lollms/functions/analyze_code/retreive_information_for_task.py new file mode 100644 index 0000000..4ed1643 --- /dev/null +++ b/lollms/functions/analyze_code/retreive_information_for_task.py @@ -0,0 +1,96 @@ +# Lollms function call definition file +from functools import partial +from typing import List, Dict, Union +from lollms.utilities import PackageManager +from lollms.personality import APScript +from ascii_colors import trace_exception +from pathlib import Path +import sqlite3 +import json +# Ensure required packages are installed +if not PackageManager.check_package_installed("sqlite3"): + PackageManager.install_package("sqlite3") + +def retrieve_information_for_task(project_path: str, task_description: str, llm: APScript) -> Union[str, Dict[str, str]]: + """ + Retrieves information from the database to perform a task given by the user. + + Args: + project_path (str): The path to the project directory. + task_description (str): The description of the task to perform. + llm (APScript): The language model instance for generating SQL queries. + + Returns: + Union[str, Dict[str, str]]: A string containing relevant information or an error message. + """ + try: + db_path = Path(project_path) / "project_info.db" + + # Validate the database path + if not db_path.exists() or not db_path.is_file(): + return "Invalid database path." + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # Retrieve the list of classes and their descriptions + cursor.execute("SELECT name, docstring FROM classes") + classes = cursor.fetchall() + + # Format the classes into a string + classes_text = "\n".join([f"Class: {cls[0]}, Description: {cls[1]}" for cls in classes]) + + # Ask the LLM which classes are needed for the task + prompt = f"{llm.personality.config.start_header_id_template}{llm.personality.config.system_message_template}{llm.personality.config.end_header_id_template}" \ + f"Given the following list of classes and their descriptions:\n" \ + f"{classes_text}\n\n" \ + f"Task description: {task_description}\n\n" \ + f"{llm.personality.config.start_header_id_template}instructions{llm.personality.config.end_header_id_template}" \ + f"Which classes are needed to perform the task? List the class names.\n" \ + f"Answer in form of a json list inside a json markdown tag.\n" \ + f"{llm.personality.config.start_header_id_template}assistant{llm.personality.config.end_header_id_template}" + + needed_classes = llm.fast_gen(prompt, callback=llm.sink).strip() + needed_classes = llm.extract_code_blocks(needed_classes) + if len(needed_classes)>0: + needed_classes = json.loads(needed_classes[0]["content"]) + # Retrieve the relevant information for the needed classes + class_info = {} + for class_name in needed_classes: + cursor.execute("SELECT * FROM classes WHERE name = ?", (class_name,)) + class_info[class_name] = cursor.fetchone() + + # Retrieve the project description and structure + cursor.execute("SELECT name, description FROM project_info") + project_info = cursor.fetchone() + + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = cursor.fetchall() + + conn.close() + + # Format the results into a string + result_text = f"Project Name: {project_info[0]}\nProject Description: {project_info[1]}\n\n" + result_text += "Project Structure:\n" + "\n".join([table[0] for table in tables]) + "\n\n" + result_text += "Needed Classes Information:\n" + for class_name, info in class_info.items(): + result_text += f"Class: {class_name}\n" + result_text += f"Description: {info[2]}\n" + result_text += f"Methods: {info[4]}\n" + result_text += f"Static Methods: {info[5]}\n\n" + + return result_text.strip() + else: + return "Failed to ask the llm" + except Exception as e: + return str(e) + +def retrieve_information_for_task_function(project_path, llm): + return { + "function_name": "retrieve_information_for_task", + "function": partial(retrieve_information_for_task, project_path=project_path, llm=llm), + "function_description": "Retrieves information from the database to perform a task given by the user.", + "function_parameters": [ + {"name": "task_description", "type": "str", "description":"a description of "} + ] + } \ No newline at end of file diff --git a/lollms/functions/analyze_code/retrieve_classes_from_project.py b/lollms/functions/analyze_code/retrieve_classes_from_project.py new file mode 100644 index 0000000..471ef30 --- /dev/null +++ b/lollms/functions/analyze_code/retrieve_classes_from_project.py @@ -0,0 +1,59 @@ +# Import necessary libraries +from pathlib import Path +from functools import partial + +from typing import List, Union +import ast + +# ascii_colors offers advanced console coloring and bug tracing +from ascii_colors import trace_exception + +# Function to retrieve classes from the project files +def retrieve_classes_from_project(class_names: List[str], project_path: Union[str, Path]) -> str: + """ + Retrieves the code of specified classes from the given project path. + + Args: + project_path (Union[str, Path]): The path to the Python project directory. + class_names (List[str]): List of class names to retrieve. + + Returns: + str: The code of the specified classes as a string. + """ + try: + project_path = Path(project_path) + + # Validate the project path + if not project_path.exists() or not project_path.is_dir(): + return "Invalid project path." + + class_code = "\n" + + # Traverse the project directory and extract class code + for py_file in project_path.rglob("*.py"): + with open(py_file, "r", encoding="utf-8") as file: + content = file.read() + tree = ast.parse(content) + + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef) and node.name in class_names: + class_code += f"\n\n# Class: {node.name} in file: {py_file.relative_to(project_path)}\n" + class_code += "```python\n" + class_code += "\n".join(content.split("\n")[node.lineno-1:node.end_lineno]) + class_code += "\n```\n" + + return class_code if class_code else "No specified classes found." + + except Exception as e: + return trace_exception(e) + +# Metadata function +def retrieve_classes_from_project_function(project_path:str): + return { + "function_name": "retrieve_classes_from_project", + "function": partial(retrieve_classes_from_project,project_path = project_path), + "function_description": "Retrieves the code of specified classes from the given project path.", + "function_parameters": [ + {"name": "class_names", "type": "list", "description": "List of class names to retrieve."} + ] + } \ No newline at end of file diff --git a/lollms/functions/analyze_code/update_class_in_file.py b/lollms/functions/analyze_code/update_class_in_file.py new file mode 100644 index 0000000..8d5e870 --- /dev/null +++ b/lollms/functions/analyze_code/update_class_in_file.py @@ -0,0 +1,85 @@ +# Lollms function call definition file +from functools import partial +from lollms.utilities import PackageManager +from ascii_colors import trace_exception +from typing import List, Union +from pathlib import Path +import ast +def update_class_in_file(class_name: str, new_content: str, method_name: Union[str, None] = None, project_path: Union[str, Path] = "") -> str: + """ + Updates the specified class or method with new content in the given project path. + + Args: + class_name (str): The name of the class to update. + new_content (str): The new content to replace the class or method with. + method_name (Union[str, None]): The name of the method to update. If None, updates the entire class. + project_path (Union[str, Path]): The path to the Python project directory. + + Returns: + str: Success message or error message. + """ + try: + project_path = Path(project_path) + + # Validate the project path + if not project_path.exists() or not project_path.is_dir(): + return "Invalid project path." + + # Traverse the project directory and update class or method content + for py_file in project_path.rglob("*.py"): + with open(py_file, "r", encoding="utf-8") as file: + content = file.read() + tree = ast.parse(content) + + class_found = False + method_found = False + updated_content = [] + lines = content.split("\n") + + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef) and node.name == class_name: + class_found = True + if method_name is None: + # Update entire class content + updated_content.extend(lines[:node.lineno-1]) + updated_content.append(new_content) + updated_content.extend(lines[node.end_lineno:]) + else: + # Update specific method content + for class_node in node.body: + if isinstance(class_node, ast.FunctionDef) and class_node.name == method_name: + method_found = True + # Calculate the indentation level + indent_level = len(lines[class_node.lineno-1]) - len(lines[class_node.lineno-1].lstrip()) + indented_new_content = "\n".join(" " * indent_level + line for line in new_content.split("\n")) + updated_content.extend(lines[:class_node.lineno-1]) + updated_content.append(indented_new_content) + updated_content.extend(lines[class_node.end_lineno:]) + break + if not method_found: + return f"Method {method_name} not found in class {class_name}." + break + + if class_found: + with open(py_file, "w", encoding="utf-8") as file: + file.write("\n".join(updated_content)) + return f"{'Method ' + method_name if method_name else 'Class ' + class_name} updated successfully in file {py_file.relative_to(project_path)}." + + return "Class not found in the specified project." + + except Exception as e: + return trace_exception(e) + + +# Metadata function +def update_class_in_file_function(project_path:str ): + return { + "function_name": "update_class_in_file", # The function name in string + "function": partial(update_class_in_file,project_path = project_path), # The function to be called + "function_description": "Updates the specified class with new content in the given project path.", # Description of the function + "function_parameters": [ # The set of parameters + {"name": "class_name", "type": "str"}, + {"name": "new_content", "type": "str"}, + {"name": "method_name", "type": "str", "description":"An optional method name, required only if you need to change a single method content. If you need to change the whole class, do not set this element"}, + ] + } \ No newline at end of file diff --git a/lollms/functions/analyze_code/update_function_in_file.py b/lollms/functions/analyze_code/update_function_in_file.py new file mode 100644 index 0000000..a0853f5 --- /dev/null +++ b/lollms/functions/analyze_code/update_function_in_file.py @@ -0,0 +1,70 @@ +# Lollms function call definition file +from functools import partial +from lollms.utilities import PackageManager +from ascii_colors import trace_exception +from typing import List, Union +from pathlib import Path +import ast + +def update_function_in_file(function_name: str, new_content: str, project_path: Union[str, Path] = "") -> str: + """ + Updates the specified function with new content in the given project path. + + Args: + function_name (str): The name of the function to update. + new_content (str): The new content to replace the function with. + project_path (Union[str, Path]): The path to the Python project directory. + + Returns: + str: Success message or error message. + """ + try: + project_path = Path(project_path) + + # Validate the project path + if not project_path.exists() or not project_path.is_dir(): + return "Invalid project path." + + # Traverse the project directory and update function content + for py_file in project_path.rglob("*.py"): + with open(py_file, "r", encoding="utf-8") as file: + content = file.read() + tree = ast.parse(content) + + function_found = False + updated_content = [] + lines = content.split("\n") + + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef) and node.name == function_name: + function_found = True + # Calculate the indentation level + indent_level = len(lines[node.lineno-1]) - len(lines[node.lineno-1].lstrip()) + indented_new_content = "\n".join(" " * indent_level + line for line in new_content.split("\n")) + updated_content.extend(lines[:node.lineno-1]) + updated_content.append(indented_new_content) + updated_content.extend(lines[node.end_lineno:]) + break + + if function_found: + with open(py_file, "w", encoding="utf-8") as file: + file.write("\n".join(updated_content)) + return f"Function {function_name} updated successfully in file {py_file.relative_to(project_path)}." + + return "Function not found in the specified project." + + except Exception as e: + return trace_exception(e) + + +# Metadata function +def update_function_in_file_function(project_path:str ): + return { + "function_name": "update_function_in_file", # The function name in string + "function": partial(update_function_in_file, project_path=project_path), # The function to be called + "function_description": "Updates the specified function with new content in the given project path.", # Description of the function + "function_parameters": [ # The set of parameters + {"name": "function_name", "type": "str"}, + {"name": "new_content", "type": "str"}, + ] + } diff --git a/lollms/functions/image_management/draw_bounding_boxes.py b/lollms/functions/image_management/draw_bounding_boxes.py new file mode 100644 index 0000000..8bd91d4 --- /dev/null +++ b/lollms/functions/image_management/draw_bounding_boxes.py @@ -0,0 +1,75 @@ +# Lollms function call definition file +# File Name: draw_bounding_boxes.py +# Author: Saif (ParisNeo) +# Description: This script defines a function that takes an image file path and a list of bounding boxes in the form of x, y, w, h in normalized mode along with the labels, and returns the bounding boxes placed on top of the images with the labels. + +# Lollms function call definition file +from functools import partial +from typing import List, Tuple +from lollms.utilities import PackageManager +from ascii_colors import trace_exception +from pathlib import Path + +# Ensure necessary packages are installed +if not PackageManager.check_package_installed("Pillow"): + PackageManager.install_package("Pillow") + +from PIL import Image, ImageDraw, ImageFont + +def draw_bounding_boxes(image_path: str, bounding_boxes: List[Tuple[float, float, float, float, str]]) -> str: + """ + Draws bounding boxes on an image and saves the result. + + Args: + image_path (str): Path to the input image file. + bounding_boxes (List[Tuple[float, float, float, float, str]]): List of bounding boxes in normalized coordinates (x, y, w, h) along with labels. + + Returns: + str: Path to the output image file with bounding boxes drawn. + """ + try: + image_path = Path(image_path) + if not image_path.exists(): + raise FileNotFoundError(f"Image file not found: {image_path}") + + # Load the image + image = Image.open(image_path) + draw = ImageDraw.Draw(image) + width, height = image.size + + # Iterate over bounding boxes and draw them + for bbox in bounding_boxes: + x, y, w, h, label = bbox + left = x * width + top = y * height + right = left + (w * width) + bottom = top + (h * height) + + # Draw rectangle + draw.rectangle([left, top, right, bottom], outline="red", width=2) + + # Draw label + font = ImageFont.load_default() + text_size = draw.textsize(label, font=font) + text_background = [left, top - text_size[1], left + text_size[0], top] + draw.rectangle(text_background, fill="red") + draw.text((left, top - text_size[1]), label, fill="white", font=font) + + # Save the output image + output_path = image_path.with_name(f"{image_path.stem}_with_boxes{image_path.suffix}") + image.save(output_path) + + return str(output_path) + except Exception as e: + return trace_exception(e) + +def draw_bounding_boxes_function(): + return { + "function_name": "draw_bounding_boxes", + "function": draw_bounding_boxes, + "function_description": "Draws bounding boxes on an image and saves the result.", + "function_parameters": [ + {"name": "image_path", "type": "str"}, + {"name": "bounding_boxes", "type": "List[Tuple[float, float, float, float, str]]"} + ] + } diff --git a/lollms/functions/timers.py b/lollms/functions/timers.py index c93f403..cfe8176 100644 --- a/lollms/functions/timers.py +++ b/lollms/functions/timers.py @@ -1,4 +1,8 @@ # Lollms function call definition file +# File Name: set_timer_with_alert.py +# Author: Saif (ParisNeo) +# Description: This function sets a non-blocking timer that shows a PyQt window with a message and makes noise after a specified duration. It works on any operating system by using the pyautogui library for the alert sound. + # Here you need to import any necessary imports depending on the function requested by the user # example import math @@ -6,7 +10,7 @@ from functools import partial # It is advised to import typing elements -# from typing import List +from typing import Any, Dict # Import PackageManager if there are potential libraries that need to be installed from lollms.utilities import PackageManager @@ -17,30 +21,35 @@ from ascii_colors import trace_exception # Here is an example of how we install a non installed library using PackageManager if not PackageManager.check_package_installed("PyQt5"): PackageManager.install_package("PyQt5") +if not PackageManager.check_package_installed("pyautogui"): + PackageManager.install_package("pyautogui") -# now we can import the library +# now we can import the libraries import threading import time import sys from PyQt5.QtWidgets import QApplication, QMessageBox -import winsound +import pyautogui # here is the core of the function to be built def set_timer_with_alert(duration: int, message: str) -> str: - def timer_callback(): - if type(duration)==str and len(duration)>0: - duration = float(duration.split()[0]) - time.sleep(duration) - winsound.Beep(1000, 1000) # Make noise when time is up + """ + Sets a non-blocking timer that shows a PyQt window with a message and makes noise after a specified duration. - app = QApplication(sys.argv) - msg_box = QMessageBox() - msg_box.setIcon(QMessageBox.Information) - msg_box.setWindowTitle("Timer Alert") - msg_box.setText(message) - msg_box.setStandardButtons(QMessageBox.Ok) - msg_box.buttonClicked.connect(app.quit) - msg_box.exec_() + Parameters: + duration (int): The duration for the timer in seconds. + message (str): The message to be displayed in the alert window. + + Returns: + str: A success message indicating the timer has been set. + """ + def timer_callback(): + try: + time.sleep(duration) + pyautogui.alert(text=message, title="Timer Alert", button='OK') + pyautogui.beep() + except Exception as e: + return trace_exception(e) try: # Start the timer in a new thread to make it non-blocking @@ -53,7 +62,7 @@ def set_timer_with_alert(duration: int, message: str) -> str: return trace_exception(e) # Here is the metadata function that should have the name in format function_name_function -def set_timer_with_alert_function(processor, client): +def set_timer_with_alert_function() -> Dict[str, Any]: return { "function_name": "set_timer_with_alert", # The function name in string "function": set_timer_with_alert, # The function to be called diff --git a/lollms/functions/tts/__init__.py b/lollms/functions/tts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lollms/functions/tts/read_text.py b/lollms/functions/tts/read_text.py new file mode 100644 index 0000000..37abe39 --- /dev/null +++ b/lollms/functions/tts/read_text.py @@ -0,0 +1,45 @@ +# Lollms function call definition file + +# Import necessary modules +from pathlib import Path +from functools import partial +from typing import Union +from lollms.utilities import PackageManager +from lollms.personality import APScript +from lollms.tts import LollmsTTS +from safe_store import GenericDataLoader +from ascii_colors import trace_exception + +# Here is the core of the function to be built +def read_text(text: str, tts_module:LollmsTTS, llm:APScript) -> str: + """ + This function takes a TTS module and a file path as input, reads the text from the file, + and uses the TTS module to generate audio from the text. + + Parameters: + tts_module: The text-to-speech module with a method tts_audio. + text: The text to be read. + + Returns: + str: The path to the generated audio file. + """ + try: + # Generate audio from the text + audio_file_path = tts_module.tts_audio(text) + llm.chunk(text) + llm.new_message("") + + # Return the path to the generated audio file + return str(audio_file_path) + except Exception as e: + return trace_exception(e) + + +# Metadata function +def read_text_function(file_path:str,tts_module:LollmsTTS): + return { + "function_name": "read_text_from_file", # The function name in string + "function": partial(read_text, file_path=file_path, tts_module=tts_module), # The function to be called + "function_description": "Reads text from a file and uses a TTS module to generate audio from the text.", # Description of the function + "function_parameters": [] # The set of parameters + } diff --git a/lollms/functions/tts/read_text_from_file.py b/lollms/functions/tts/read_text_from_file.py new file mode 100644 index 0000000..2383242 --- /dev/null +++ b/lollms/functions/tts/read_text_from_file.py @@ -0,0 +1,50 @@ +# Lollms function call definition file + +# Import necessary modules +from pathlib import Path +from functools import partial +from typing import Union +from lollms.utilities import PackageManager +from lollms.personality import APScript +from lollms.tts import LollmsTTS +from safe_store import GenericDataLoader +from ascii_colors import trace_exception + +# Here is the core of the function to be built +def read_text_from_file(file_path: Union[Path, str], tts_module:LollmsTTS, llm:APScript) -> str: + """ + This function takes a TTS module and a file path as input, reads the text from the file, + and uses the TTS module to generate audio from the text. + + Parameters: + tts_module: The text-to-speech module with a method tts_audio. + file_path: The path to the text file containing the text to be read. + + Returns: + str: The path to the generated audio file. + """ + try: + # Ensure file_path is of type Path + file_path = Path(file_path) + + # Read the text from the file + text = GenericDataLoader.read_file(file_path) + + # Generate audio from the text + audio_file_path = tts_module.tts_audio(text,use_threading=True) + llm.full(text) + + # Return the path to the generated audio file + return str(audio_file_path) + except Exception as e: + return trace_exception(e) + + +# Metadata function +def read_text_from_file_function(file_path:str,tts_module:LollmsTTS, llm:APScript): + return { + "function_name": "read_text_from_file", # The function name in string + "function": partial(read_text_from_file, file_path=file_path, tts_module=tts_module, llm=llm), # The function to be called + "function_description": "Reads text from the current file and uses a TTS module to generate audio from the text.", # Description of the function + "function_parameters": [] # The set of parameters + } diff --git a/lollms/functions/web/__init__.py b/lollms/functions/web/__init__.py new file mode 100644 index 0000000..3c0f9c3 --- /dev/null +++ b/lollms/functions/web/__init__.py @@ -0,0 +1 @@ +from lollms.functions.web.google_search import google_search, google_search_function \ No newline at end of file diff --git a/lollms/functions/web/google_search.py b/lollms/functions/web/google_search.py new file mode 100644 index 0000000..6801078 --- /dev/null +++ b/lollms/functions/web/google_search.py @@ -0,0 +1,46 @@ +# Lollms function call definition file + +# Import necessary libraries +from functools import partial +from typing import List +from lollms.utilities import PackageManager +from ascii_colors import trace_exception + +# Ensure the webbrowser package is available +if not PackageManager.check_package_installed("webbrowser"): + PackageManager.install_package("webbrowser") + +# Import the webbrowser library +import webbrowser + +# Define the function to perform a Google search +def google_search(query: str) -> str: + """ + Perform a Google search using the default web browser. + + Parameters: + - query (str): The search query. + + Returns: + - str: A message indicating the search was performed. + """ + try: + # Construct the Google search URL + search_url = f"https://www.google.com/search?q={query}" + + # Open the search URL in the default web browser + webbrowser.open(search_url) + + # Return a success message + return f"Performed Google search for query: {query}" + except Exception as e: + return trace_exception(e) + +# Define the metadata function +def google_search_function(): + return { + "function_name": "google_search", # The function name in string + "function": google_search, # The function to be called + "function_description": "Performs a Google search using the default web browser.", # Description of the function + "function_parameters": [{"name": "query", "type": "str"}] # The set of parameters + } diff --git a/lollms/personality.py b/lollms/personality.py index aecdb8e..71e4302 100644 --- a/lollms/personality.py +++ b/lollms/personality.py @@ -3394,6 +3394,7 @@ The AI should respond in this format using data from actions_list: result = function(**parameters) results.append(result) except TypeError as e: + trace_exception(e) # Handle cases where the function call fails due to incorrect parameters, etc. results.append(f"Error calling {function_name}: {e}") else: @@ -3521,6 +3522,20 @@ The AI should respond in this format using data from actions_list: return function_calls + def interact( + self, + context_details, + callback = None + ): + upgraded_prompt = self.build_prompt_from_context_details(context_details) + if len(self.personality.image_files)>0: + # Generate the initial text based on the upgraded prompt. + generated_text = self.fast_gen_with_images(upgraded_prompt, self.personality.image_files, callback=callback) + else: + generated_text = self.fast_gen(upgraded_prompt, callback=callback) + + return generated_text + def interact_with_function_call( self, context_details, @@ -3528,24 +3543,26 @@ The AI should respond in this format using data from actions_list: prompt_after_execution=True, callback = None, hide_function_call=False, - separate_output=False): + separate_output=False, + max_nested_function_calls=10): start_header_id_template = self.config.start_header_id_template end_header_id_template = self.config.end_header_id_template system_message_template = self.config.system_message_template separator_template = self.config.separator_template - - final_output = "" if len(self.personality.image_files)>0: out, function_calls = self.generate_with_function_calls_and_images(context_details, self.personality.image_files, function_definitions, callback=callback) else: out, function_calls = self.generate_with_function_calls(context_details, function_definitions, callback=callback) - if len(function_calls)>0: + nested_function_calls = 0 + while len(function_calls)>0 and nested_function_calls0: - outputs = self.execute_function_calls(function_calls,function_definitions) - final_output = "\n".join([str(o) if type(o)==str else str(o[0]) if (type(o)==tuple or type(0)==list) and len(o)>0 else "" for o in outputs]) - out += f"{separator_template}{start_header_id_template}function calls results{end_header_id_template}\n" + final_output - context_details["discussion_messages"] +=out else: final_output = out return final_output