From 8900221b57229094da25cd967c85d6b9e20b35fa Mon Sep 17 00:00:00 2001 From: Maximilian Winter Date: Sat, 25 May 2024 15:40:48 +0200 Subject: [PATCH] Added refactored websearch example and edited llm docs generation --- .gitignore | 1 + .../web_search.py | 101 ++++++++++++++++++ .../documentation_generation.py | 14 +-- 3 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 examples/03_Tools_And_Function_Calling/web_search.py diff --git a/.gitignore b/.gitignore index 250a8d7..8b6b925 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,4 @@ cython_debug/ /examples/05_Agents/data/ /examples/03_Tools_And_Function_Calling/experimental_llm_computer_interface/venv_agent/ +/examples/03_Tools_And_Function_Calling/llm_computer_interface/venv_agent diff --git a/examples/03_Tools_And_Function_Calling/web_search.py b/examples/03_Tools_And_Function_Calling/web_search.py new file mode 100644 index 0000000..691ea8c --- /dev/null +++ b/examples/03_Tools_And_Function_Calling/web_search.py @@ -0,0 +1,101 @@ +import json + +from duckduckgo_search import DDGS +from trafilatura import fetch_url, extract + +from llama_cpp_agent import LlamaCppAgent, MessagesFormatterType +from llama_cpp_agent.chat_history.messages import Roles +from llama_cpp_agent.llm_output_settings import LlmStructuredOutputSettings +from llama_cpp_agent.providers import LlamaCppServerProvider + +def send_message_to_user(message: str): + """ + Send a message to user. + Args: + message (str): Message to send. + """ + print(message) + +class DDGWebSearch: + + def __init__(self, llm_provider, message_formatter_type): + self.summarising_agent = LlamaCppAgent(llm_provider, debug_output=True, + system_prompt="You are a text summarization and information extraction specialist and you are able to summarize and filter out information relevant to a specific query.", + predefined_messages_formatter_type=message_formatter_type) + + def get_website_content_from_url(self, url: str) -> str: + """ + Get website content from a URL using Selenium and BeautifulSoup for improved content extraction and filtering. + + Args: + url (str): URL to get website content from. + + Returns: + str: Extracted content including title, main text, and tables. + """ + + try: + downloaded = fetch_url(url) + + result = extract(downloaded, include_formatting=True, include_links=True, output_format='json', url=url) + + if result: + result = json.loads(result) + return f'=========== Website Title: {result["title"]} ===========\n\n=========== Website URL: {url} ===========\n\n=========== Website Content ===========\n\n{result["raw_text"]}\n\n=========== Website Content End ===========\n\n' + else: + return "" + except Exception as e: + return f"An error occurred: {str(e)}" + + def search_web(self, search_query: str): + """ + Search the web for information. + Args: + search_query (str): Search query to search for. + """ + results = DDGS().text(search_query, region='wt-wt', safesearch='off', max_results=4) + result_string = '' + for res in results: + web_info = self.get_website_content_from_url(res['href']) + if web_info != "": + web_info = self.summarising_agent.get_chat_response( + f"Please summarize the following Website content and extract relevant information to this query:'{search_query}'.\n\n" + web_info, + add_response_to_chat_history=False, add_message_to_chat_history=False) + result_string += web_info + + res = result_string.strip() + return "Based on the following results, answer the previous user query:\nResults:\n\n" + res + + def get_tool(self): + return self.search_web + + +provider = LlamaCppServerProvider("http://hades.hq.solidrust.net:8084") +#provider = LlamaCppServerProvider("http://localhost:8080") +agent = LlamaCppAgent( + provider, + debug_output=True, + system_prompt="You are a helpful assistant. Use additional available information you have access to when giving a response. Always give detailed and long responses. Format your response, well structured in markdown format.", + predefined_messages_formatter_type=MessagesFormatterType.CHATML, + add_tools_and_structures_documentation_to_system_prompt=True, +) + +search_tool = DDGWebSearch(provider, MessagesFormatterType.CHATML) + +settings = provider.get_provider_default_settings() + +settings.temperature = 0.45 +settings.max_tokens = 1024 +output_settings = LlmStructuredOutputSettings.from_functions( + [search_tool.get_tool(), send_message_to_user]) +user = input(">") +result = agent.get_chat_response(user, + llm_sampling_settings=settings, structured_output_settings=output_settings) +while True: + if result[0]["function"] == "send_message_to_user": + user = input(">") + result = agent.get_chat_response(user, structured_output_settings=output_settings, + llm_sampling_settings=settings) + else: + result = agent.get_chat_response(result[0]["return_value"], role=Roles.tool, + structured_output_settings=output_settings, llm_sampling_settings=settings) diff --git a/src/llama_cpp_agent/llm_documentation/documentation_generation.py b/src/llama_cpp_agent/llm_documentation/documentation_generation.py index 5a620d5..016bc02 100644 --- a/src/llama_cpp_agent/llm_documentation/documentation_generation.py +++ b/src/llama_cpp_agent/llm_documentation/documentation_generation.py @@ -30,9 +30,9 @@ def generate_markdown_documentation( pyd_models = [(model, True) for model in pydantic_models] for model, add_prefix in pyd_models: if add_prefix: - documentation += f"### {model_prefix} {model.__name__}\n" + documentation += f"### {model_prefix} '{model.__name__}'\n" else: - documentation += f"### {model.__name__}\n" + documentation += f"### '{model.__name__}'\n" # Handling multi-line model description with proper indentation @@ -46,9 +46,9 @@ def generate_markdown_documentation( if add_prefix: # Indenting the fields section - documentation += f"{fields_prefix}:\n" + documentation += f"#### {fields_prefix}\n" else: - documentation += f"Fields:\n" + documentation += f"#### Fields\n" if isclass(model) and issubclass(model, BaseModel): count = 1 for name, field_type in model.__annotations__.items(): @@ -74,10 +74,10 @@ def generate_markdown_documentation( documentation_with_field_description=documentation_with_field_description, ) if add_prefix: - if documentation.endswith(f"{fields_prefix}:\n"): + if documentation.endswith(f"#### {fields_prefix}\n"): documentation += "none\n" else: - if documentation.endswith("Fields:\n"): + if documentation.endswith("#### Fields\n"): documentation += "none\n" documentation += "\n" @@ -147,7 +147,7 @@ def generate_field_markdown( elif issubclass(field_type, Enum): enum_values = [f"'{str(member.value)}'" for member in field_type] is_enum = True - field_text = f"{indent}{field_name} " + field_text = f"{indent}{field_name}" if field_description != "": field_text += ": " else: