From 91f7ea072f4c5b0369441718d8cedb4eea52143b Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 12:39:37 -0700 Subject: [PATCH 01/17] ragConcept --- modules/llm.py | 19 ++++++++++++++++--- requirements.txt | 3 +-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 2dbf281..753d9ab 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -6,9 +6,10 @@ from modules.log import * # Ollama Client # https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server +import ollama # pip install ollama from ollama import Client as OllamaClient -from langchain_ollama import OllamaEmbeddings # pip install ollama langchain-ollama from googlesearch import search # pip install googlesearch-python +import chromadb # pip install chromadb # LLM System Variables OllamaClient(host=ollamaHostName) @@ -19,9 +20,12 @@ googleSearchResults = 3 # number of google search results to include in the cont antiFloodLLM = [] llmChat_history = {} trap_list_llm = ("ask:", "askai") -embedding_model = OllamaEmbeddings(model=llmModel) ragDEV = False +chromaClient = chromadb.Client() +collection = chromaClient.create_collection("meshBotAI") + + meshBotAI = """ FROM {llmModel} SYSTEM @@ -74,7 +78,16 @@ def llm_readTextFiles(): def embed_text(text): try: - return embedding_model.embed_documents(text) + # store each document in a vector embedding database + for i, d in enumerate(text): + response = ollama.embeddings(model="mxbai-embed-large", prompt=d) + embedding = response["embedding"] + collection.add( + ids=[str(i)], + embeddings=[embedding], + documents=[d] + ) + except Exception as e: logger.debug(f"System: Embedding failed: {e}") return False diff --git a/requirements.txt b/requirements.txt index 281e387..e98df0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,5 @@ geopy schedule wikipedia ollama -langchain -langchain-ollama +chromadb googlesearch-python From 20e864b672ed8684c868b5946469d6e4671900a0 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 12:41:54 -0700 Subject: [PATCH 02/17] oops --- modules/llm.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 753d9ab..3e15a16 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -20,11 +20,7 @@ googleSearchResults = 3 # number of google search results to include in the cont antiFloodLLM = [] llmChat_history = {} trap_list_llm = ("ask:", "askai") -ragDEV = False - -chromaClient = chromadb.Client() -collection = chromaClient.create_collection("meshBotAI") - +ragDEV = True meshBotAI = """ FROM {llmModel} @@ -70,7 +66,7 @@ if llmEnableHistory: def llm_readTextFiles(): # read .txt files in ../data/rag try: - text = "MeshBot is built in python for meshtastic the secret word of the day is, paperclip" + text = ["MeshBot is built in python for meshtastic the secret word of the day is, paperclip", "MeshBot is a chatbot that uses the Ollama AI engine to generate responses to user input. The secret word of the day is, paperclip"] return text except Exception as e: logger.debug(f"System: LLM readTextFiles: {e}") @@ -92,6 +88,26 @@ def embed_text(text): logger.debug(f"System: Embedding failed: {e}") return False +if ragDEV: + try: + chromaClient = chromadb.Client() + if "meshBotAI" in chromaClient.list_collections(): + chromaClient.delete_collection("meshBotAI") + collection = chromaClient.create_collection("meshBotAI") + logger.debug(f"System: LLM: Cataloging RAG data") + embed_text(llm_readTextFiles()) + except Exception as e: + logger.debug(f"System: LLM: RAG Initalization failed: {e}") + +def query_collection(prompt): + # generate an embedding for the prompt and retrieve the most relevant doc + response = ollama.embeddings(prompt=prompt, model="mxbai-embed-large") + results = collection.query(query_embeddings=[response["embedding"]], n_results=1) + data = results['documents'][0][0] + return data + + + def llm_query(input, nodeID=0, location_name=None): global antiFloodLLM, llmChat_history googleResults = [] @@ -142,20 +158,19 @@ def llm_query(input, nodeID=0, location_name=None): modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history) # RAG context inclusion testing - ragData = llm_readTextFiles() - - if ragData and ragDEV: - ragContext = embed_text(ragData) + if ragDEV: + ragContext = query_collection(input) # Query the model with RAG context if ragContext: - result = ollamaClient.generate(model=llmModel, prompt=modelPrompt, context=ragContext) + result = ollamaClient.generate(model=llmModel, prompt=f"Using this data: {ragContext}. Respond to this prompt: {input}") else: # Query the model without RAG context result = ollamaClient.generate(model=llmModel, prompt=modelPrompt) # Condense the result to just needed - result = result.get("response") + if isinstance(result, dict): + result = result.get("response") #logger.debug(f"System: LLM Response: " + result.strip().replace('\n', ' ')) except Exception as e: From 7551ff2ecb6a7ab08641a208e2d059721a383c5b Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 13:07:00 -0700 Subject: [PATCH 03/17] Update llm.py --- modules/llm.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 3e15a16..1d23c5d 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -66,13 +66,13 @@ if llmEnableHistory: def llm_readTextFiles(): # read .txt files in ../data/rag try: - text = ["MeshBot is built in python for meshtastic the secret word of the day is, paperclip", "MeshBot is a chatbot that uses the Ollama AI engine to generate responses to user input. The secret word of the day is, paperclip"] + text = ["MeshBot is a meshtastic radio bot it was hatched in 2024 the goal is to help people enjoy meshing-around", "MeshBot is a chatbot that uses the Ollama AI engine to generate responses to user input.", "The secret word of the day is, paperclip","This file is about tacos who likes tacos everyone and meshbot was written while enjoying tacos for lunch some days"] return text except Exception as e: logger.debug(f"System: LLM readTextFiles: {e}") return False -def embed_text(text): +def store_text_embedding(text): try: # store each document in a vector embedding database for i, d in enumerate(text): @@ -88,14 +88,32 @@ def embed_text(text): logger.debug(f"System: Embedding failed: {e}") return False +## INITALIZATION of RAG if ragDEV: try: - chromaClient = chromadb.Client() - if "meshBotAI" in chromaClient.list_collections(): + chromaHostname = "localhost:8000" + # connect to the chromaDB + chromaHost = chromaHostname.split(":")[0] + chromaPort = chromaHostname.split(":")[1] + if chromaHost == "localhost" and chromaPort == "8000": + # create a client using local python Client + chromaClient = chromadb.Client() + else: + # create a client using the remote python Client + # this isnt tested yet please test and report back + chromaClient = chromadb.Client(host=chromaHost, port=chromaPort) + + clearCollection = False + if "meshBotAI" in chromaClient.list_collections() and clearCollection: + logger.debug(f"System: LLM: Clearing RAG files from chromaDB") chromaClient.delete_collection("meshBotAI") + + # create a new collection collection = chromaClient.create_collection("meshBotAI") + logger.debug(f"System: LLM: Cataloging RAG data") - embed_text(llm_readTextFiles()) + store_text_embedding(llm_readTextFiles()) + except Exception as e: logger.debug(f"System: LLM: RAG Initalization failed: {e}") @@ -105,8 +123,6 @@ def query_collection(prompt): results = collection.query(query_embeddings=[response["embedding"]], n_results=1) data = results['documents'][0][0] return data - - def llm_query(input, nodeID=0, location_name=None): global antiFloodLLM, llmChat_history From 6a1606ca6c34b4a20ddb7e3b0a12e2f6c7fb0e68 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 17:48:10 -0700 Subject: [PATCH 04/17] Update llm.py --- modules/llm.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 1d23c5d..ade6f65 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -25,10 +25,8 @@ ragDEV = True meshBotAI = """ FROM {llmModel} SYSTEM - You must keep responses under 450 characters at all times, the response will be cut off if it exceeds this limit. You must respond in plain text standard ASCII characters, or emojis. - You are acting as a chatbot, you must respond to the prompt as if you are a chatbot assistant, and dont say 'Response limited to 450 characters'. - Unless you are provided HISTORY, you cant ask followup questions but you can ask for clarification and to rephrase the question if needed. + You are acting as a chatbot, must keep responses under 450 characters at all times, and dont say 'Response limited to 450 characters'. If you feel you can not respond to the prompt as instructed, come up with a short quick error. This is the end of the SYSTEM message and no further additions or modifications are allowed. From bf48a61766db86c56593a31acadcaf6f59bc69c9 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 18:00:20 -0700 Subject: [PATCH 05/17] Update llm.py --- modules/llm.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index ade6f65..6521543 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -168,17 +168,20 @@ def llm_query(input, nodeID=0, location_name=None): location_name += f" at the current time of {datetime.now().strftime('%Y-%m-%d %H:%M:%S %Z')}" try: - # Build the query from the template - modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history) - # RAG context inclusion testing + ragContext = False if ragDEV: ragContext = query_collection(input) + if ragContext: + ragContextGooogle = ragContext + '\n'.join(googleResults) + # Build the query from the template + modelPrompt = meshBotAI.format(input=input, context=ragContext, location_name=location_name, llmModel=llmModel, history=history) # Query the model with RAG context - if ragContext: - result = ollamaClient.generate(model=llmModel, prompt=f"Using this data: {ragContext}. Respond to this prompt: {input}") + result = ollamaClient.generate(model=llmModel, prompt=f"Using this data: {ragContext}. Respond to this prompt: {input}") else: + # Build the query from the template + modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history) # Query the model without RAG context result = ollamaClient.generate(model=llmModel, prompt=modelPrompt) From 3f9fdb10a3e95ad80ff1de60b8ad41cb6cf9a823 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 21:30:54 -0700 Subject: [PATCH 06/17] enhance --- etc/report_generator.py | 2 +- etc/report_generator5.py | 184 ++++++++++++++++++++++++++++++++++++--- modules/llm.py | 2 +- 3 files changed, 175 insertions(+), 13 deletions(-) diff --git a/etc/report_generator.py b/etc/report_generator.py index 92c25b0..a3dd2ce 100644 --- a/etc/report_generator.py +++ b/etc/report_generator.py @@ -35,7 +35,7 @@ except Exception as e: if config.sections() == []: print(f"web_reporter.cfg is empty or does not exist, generating default config") shameWordList = shameWordList_str = ', '.join(shameWordList) - config['reporting'] = {'log_path': script_dir, 'w3_path': www_dir, 'multi_log_reader': 'True', 'shame_word_list': shameWordList} + config['reporting'] = {'log_path': script_dir, 'w3_path': www_dir, 'multi_log_reader': 'False', 'shame_word_list': shameWordList} with open(config_file, 'w') as configfile: config.write(configfile) diff --git a/etc/report_generator5.py b/etc/report_generator5.py index f4c3898..6e018bf 100644 --- a/etc/report_generator5.py +++ b/etc/report_generator5.py @@ -36,7 +36,7 @@ except Exception as e: if config.sections() == []: print(f"web_reporter.cfg is empty or does not exist, generating default config") shameWordList = shameWordList_str = ', '.join(shameWordList) - config['reporting'] = {'log_path': script_dir, 'w3_path': www_dir, 'multi_log_reader': 'True', 'shame_word_list': shameWordList} + config['reporting'] = {'log_path': script_dir, 'w3_path': www_dir, 'multi_log_reader': 'False', 'shame_word_list': shameWordList} with open(config_file, 'w') as configfile: config.write(configfile) @@ -103,7 +103,11 @@ def parse_log_file(file_path): 'node2_name': "N/A", 'node1_ID': "N/A", 'node2_ID': "N/A", - 'shameList': [] + 'shameList': [], + 'channel_util': defaultdict(int), + 'packet_errors': defaultdict(int), + 'nodeCount1': defaultdict(int), + 'rx1': defaultdict(int), } for line in lines: @@ -208,17 +212,17 @@ def parse_log_file(file_path): if interface_number == '1': log_data['firmware1_version'] = firmware_version log_data['node1_uptime'] = data - log_data['nodeCount1'] = totalNodes - log_data['nodeCountOnline1'] = online - log_data['tx1'] = numPacketsTx - log_data['rx1'] = numPacketsRx + log_data['nodeCount1'] = {timestamp.isoformat(): f'{totalNodes}'} + log_data['nodeCountOnline1'] = {timestamp.isoformat(): f'{online}'} + log_data['tx1'] = {timestamp.isoformat(): f'{numPacketsTx}'} + log_data['rx1'] = {timestamp.isoformat(): f'{numPacketsRx}'} elif interface_number == '2': log_data['firmware2_version'] = firmware_version log_data['node2_uptime'] = data - log_data['nodeCount2'] = totalNodes - log_data['nodeCountOnline2'] = online - log_data['tx2'] = numPacketsTx - log_data['rx2'] = numPacketsRx + log_data['nodeCount2'] = {timestamp.isoformat(): f'{totalNodes}'} + log_data['nodeCountOnline2'] = {timestamp.isoformat(): f'{online}'} + log_data['tx2'] = {timestamp.isoformat(): f'{numPacketsTx}'} + log_data['rx2'] = {timestamp.isoformat(): f'{numPacketsRx}'} # get name and nodeID for devices if 'Autoresponder Started for Device' in line: @@ -793,6 +797,30 @@ def generate_main_html(log_data, system_info): +
+

Packet Counts

+
+ +
+
+
+

Channel Util

+
+ +
+
+
+

Packet Errors

+
+ +
+
+
+

Node Count

+
+ +
+

Command Usage

@@ -854,6 +882,10 @@ def generate_main_html(log_data, system_info): const commandData = ${command_data}; const messageData = ${message_data}; const activityData = ${activity_data}; + const packet1rxData = ${packet1rx_data}; + const utilData = ${util_data}; + const errorData = ${error_data}; + const node1Data = ${node1_data}; const messageCountData = { labels: ['BBSdm Messages', 'BBSdb Messages', 'Channel Messages'], datasets: [{ @@ -905,7 +937,7 @@ def generate_main_html(log_data, system_info): fill: false }] }, -options: { + options: { ...chartOptions, scales: { x: { @@ -932,6 +964,132 @@ options: { } }); + new Chart(document.getElementById('packetChart'), { + type: 'line', + data: { + labels: Object.keys(packet1rxData), + datasets: [{ + label: 'TX/RX Count', + data: Object.entries(packet1rxData).map(([time, count]) => ({x: new Date(time), y: count})), + borderColor: 'rgba(153, 102, 255, 1)', + fill: false + }] + }, + options: { + ...chartOptions, + scales: { + x: { + type: 'time', + time: { + unit: 'hour', + displayFormats: { + hour: 'MMM d, HH:mm' + } + }, + title: { + display: true, + text: 'Time' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'TX/RX Packet Count' + } + } + } + } + }); + + new Chart(document.getElementById('utilChart'), { + type: 'line', + data: { + labels: Object.keys(utilData), + datasets: [{ + label: 'Hourly Activity', + data: Object.entries(utilData).map(([time, count]) => ({x: new Date(time), y: count})), + borderColor: 'rgba(153, 102, 255, 1)', + fill: false + }] + }, + options: { + ...chartOptions, + scales: { + x: { + type: 'time', + time: { + unit: 'hour', + displayFormats: { + hour: 'MMM d, HH:mm' + } + }, + title: { + display: true, + text: 'Time' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Activity %' + } + } + } + } + }); + + new Chart(document.getElementById('errorChart'), { + type: 'line', + data: { + labels: Object.keys(errorData), + datasets: [{ + label: 'Hourly Activity', + data: Object.entries(errorData).map(([time, count]) => ({x: new Date(time), y: count})), + borderColor: 'rgba(153, 102, 255, 1)', + fill: false + }] + }, + options: { + ...chartOptions, + scales: { + x: { + type: 'time', + time: { + unit: 'hour', + displayFormats: { + hour: 'MMM d, HH:mm' + } + }, + title: { + display: true, + text: 'Time' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Error Count' + } + } + } + } + }); + + new Chart(document.getElementById('nodeChart'), { + type: 'pie', + data: { + labels: Object.keys(node1Data), + datasets: [{ + data: Object.values(node1Data), + backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)'] + }] + }, + options: chartOptions + }); + new Chart(document.getElementById('messageCountChart'), { type: 'bar', data: messageCountData, @@ -1040,6 +1198,10 @@ options: { command_data=json.dumps(log_data['command_counts']), message_data=json.dumps(log_data['message_types']), activity_data=json.dumps(log_data['hourly_activity']), + packet1rx_data=(log_data['rx1']), + util_data=json.dumps(log_data['channel_util']), + error_data=json.dumps(log_data['packet_errors']), + node1_data=json.dumps(log_data['nodeCount1']), bbs_messages=log_data['bbs_messages'], messages_waiting=log_data['messages_waiting'], total_messages=log_data['total_messages'], diff --git a/modules/llm.py b/modules/llm.py index 6521543..7b59065 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -20,7 +20,7 @@ googleSearchResults = 3 # number of google search results to include in the cont antiFloodLLM = [] llmChat_history = {} trap_list_llm = ("ask:", "askai") -ragDEV = True +ragDEV = False meshBotAI = """ FROM {llmModel} From 957619933df5de4fd7da83319b04ba7028922f5c Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 14 Oct 2024 21:38:40 -0700 Subject: [PATCH 07/17] Update report_generator5.py --- etc/report_generator5.py | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/report_generator5.py b/etc/report_generator5.py index 6e018bf..7aac746 100644 --- a/etc/report_generator5.py +++ b/etc/report_generator5.py @@ -272,6 +272,7 @@ def get_system_info(): pass # get Meshtastic CLI version on local try: + cli_local = "N/A" if "importlib.metadata" in sys.modules: cli_local = version("meshtastic") except: From 1df2fd3486583e90c0dcf3ffdcde3973717528aa Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 15 Oct 2024 13:25:53 -0700 Subject: [PATCH 08/17] channelFix --- mesh_bot.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index feb4f20..52086be 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -847,12 +847,13 @@ def onReceive(packet, interface): elif interface2_enabled and interface2_type == 'ble': rxNode = 2 + # check if the packet has a channel flag use it + if packet.get('channel'): + channel_number = packet.get('channel', 0) + # BBS DM MAIL CHECKER if bbs_enabled and 'decoded' in packet: message_from_id = packet['from'] - - if packet.get('channel'): - channel_number = packet['channel'] msg = bbs_check_dm(message_from_id) if msg: @@ -875,10 +876,6 @@ def onReceive(packet, interface): snr = packet.get('rxSnr', 0) rssi = packet.get('rxRssi', 0) - # check if the packet has a channel flag use it - if packet.get('channel'): - channel_number = packet.get('channel', 0) - # check if the packet has a publicKey flag use it if packet.get('publicKey'): pkiStatus = (packet.get('pkiEncrypted', False), packet.get('publicKey', 'ABC')) From b5fb7e997c3599ebdedceb4973454badbb509d36 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 15 Oct 2024 19:31:17 -0700 Subject: [PATCH 09/17] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 93878ae..44444b3 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,9 @@ schedule.every().day.at("08:00").do(lambda: send_message(handle_wxc(0, 1, 'wx'), schedule.every().wednesday.at("19:00").do(lambda: send_message("Net Starting Now", 2, 0, 1)) ``` +### MQTT Notes +There is no direct support for MQTT in the code, however, reports from Discord are that using [meshtasticd](https://meshtastic.org/docs/hardware/devices/linux-native-hardware/) with no radio and attaching the bot to the software node, which is MQTT-linked, allows routing. There also seems to be a quicker way to enable MQTT by having your bot node with the enabled [serial](https://meshtastic.org/docs/configuration/module/serial/) module with echo enabled and MQTT uplink and downlink. These two methods have been mentioned as allowing MQTT routing for the project. + ### Requirements Python 3.8? or later is needed (dev on latest). The following can be installed with `pip install -r requirements.txt` or using the [install.sh](install.sh) script for venv and automation: From 1c7840a2035affa5bb9900eb0087bbdc64573c3d Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 15 Oct 2024 19:32:29 -0700 Subject: [PATCH 10/17] Update llm.py --- modules/llm.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 7b59065..fce550e 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -6,10 +6,8 @@ from modules.log import * # Ollama Client # https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server -import ollama # pip install ollama from ollama import Client as OllamaClient from googlesearch import search # pip install googlesearch-python -import chromadb # pip install chromadb # LLM System Variables OllamaClient(host=ollamaHostName) @@ -22,6 +20,10 @@ llmChat_history = {} trap_list_llm = ("ask:", "askai") ragDEV = False +if ragDEV: + import ollama # pip install ollama + import chromadb # pip install chromadb + meshBotAI = """ FROM {llmModel} SYSTEM From 38bfcc1581e3376f98736549bb86b370c23f5282 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 15 Oct 2024 19:33:04 -0700 Subject: [PATCH 11/17] Update llm.py --- modules/llm.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index fce550e..145e09a 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -9,6 +9,12 @@ from modules.log import * from ollama import Client as OllamaClient from googlesearch import search # pip install googlesearch-python +ragDEV = False + +if ragDEV: + import ollama # pip install ollama + import chromadb # pip install chromadb + # LLM System Variables OllamaClient(host=ollamaHostName) ollamaClient = OllamaClient() @@ -18,11 +24,7 @@ googleSearchResults = 3 # number of google search results to include in the cont antiFloodLLM = [] llmChat_history = {} trap_list_llm = ("ask:", "askai") -ragDEV = False -if ragDEV: - import ollama # pip install ollama - import chromadb # pip install chromadb meshBotAI = """ FROM {llmModel} From ab8eb4185377f79bac1480e10273098045bc697d Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 15 Oct 2024 20:11:35 -0700 Subject: [PATCH 12/17] Update golfsim.py --- modules/games/golfsim.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/games/golfsim.py b/modules/games/golfsim.py index 42ebe66..33a08ca 100644 --- a/modules/games/golfsim.py +++ b/modules/games/golfsim.py @@ -375,7 +375,6 @@ def playGolf(nodeID, message, finishedHole=False): # Scorecard reset hole_to_par = 0 - total_to_par = 0 hole_strokes = 0 hole_shots = 0 From 5a5b394a1793c687167950ad4fee186ff82ecfd6 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 16 Oct 2024 13:27:59 -0700 Subject: [PATCH 13/17] Update mesh_bot.py --- mesh_bot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mesh_bot.py b/mesh_bot.py index 52086be..f656dda 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -53,6 +53,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "moon": lambda: handle_moon(message_from_id, deviceID, channel_number), "motd": lambda: handle_motd(message, message_from_id, isDM), "ping": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), + "pinging": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM), "playuno": lambda: handleUno(message, message_from_id, deviceID), "pong": lambda: "🏓PING!!🛜", "rlist": lambda: handle_repeaterQuery(message_from_id, deviceID, channel_number), From dd8ba14bbe0b99124ec751ad4202ff4fbfbb3f6d Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 18 Oct 2024 10:21:28 -0700 Subject: [PATCH 14/17] Update mesh_bot.py --- mesh_bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesh_bot.py b/mesh_bot.py index f656dda..22b3cd8 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -213,7 +213,8 @@ def handle_wxalert(message_from_id, deviceID, message): weatherAlert = getActiveWeatherAlertsDetail(str(location[0]), str(location[1])) else: weatherAlert = getWeatherAlerts(str(location[0]), str(location[1])) - + + weatherAlert = weatherAlert[0] return weatherAlert def handle_wiki(message, isDM): From a4837cf3379c6d7864832484752493cbc17a0bf9 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 18 Oct 2024 10:26:52 -0700 Subject: [PATCH 15/17] mainline --- etc/report_generator5.py | 183 +++------------------------------------ modules/llm.py | 89 +++++-------------- 2 files changed, 30 insertions(+), 242 deletions(-) diff --git a/etc/report_generator5.py b/etc/report_generator5.py index 7aac746..1e9e8f9 100644 --- a/etc/report_generator5.py +++ b/etc/report_generator5.py @@ -103,11 +103,7 @@ def parse_log_file(file_path): 'node2_name': "N/A", 'node1_ID': "N/A", 'node2_ID': "N/A", - 'shameList': [], - 'channel_util': defaultdict(int), - 'packet_errors': defaultdict(int), - 'nodeCount1': defaultdict(int), - 'rx1': defaultdict(int), + 'shameList': [] } for line in lines: @@ -212,17 +208,17 @@ def parse_log_file(file_path): if interface_number == '1': log_data['firmware1_version'] = firmware_version log_data['node1_uptime'] = data - log_data['nodeCount1'] = {timestamp.isoformat(): f'{totalNodes}'} - log_data['nodeCountOnline1'] = {timestamp.isoformat(): f'{online}'} - log_data['tx1'] = {timestamp.isoformat(): f'{numPacketsTx}'} - log_data['rx1'] = {timestamp.isoformat(): f'{numPacketsRx}'} + log_data['nodeCount1'] = totalNodes + log_data['nodeCountOnline1'] = online + log_data['tx1'] = numPacketsTx + log_data['rx1'] = numPacketsRx elif interface_number == '2': log_data['firmware2_version'] = firmware_version log_data['node2_uptime'] = data - log_data['nodeCount2'] = {timestamp.isoformat(): f'{totalNodes}'} - log_data['nodeCountOnline2'] = {timestamp.isoformat(): f'{online}'} - log_data['tx2'] = {timestamp.isoformat(): f'{numPacketsTx}'} - log_data['rx2'] = {timestamp.isoformat(): f'{numPacketsRx}'} + log_data['nodeCount2'] = totalNodes + log_data['nodeCountOnline2'] = online + log_data['tx2'] = numPacketsTx + log_data['rx2'] = numPacketsRx # get name and nodeID for devices if 'Autoresponder Started for Device' in line: @@ -272,7 +268,6 @@ def get_system_info(): pass # get Meshtastic CLI version on local try: - cli_local = "N/A" if "importlib.metadata" in sys.modules: cli_local = version("meshtastic") except: @@ -798,30 +793,6 @@ def generate_main_html(log_data, system_info):
-
-

Packet Counts

-
- -
-
-
-

Channel Util

-
- -
-
-
-

Packet Errors

-
- -
-
-
-

Node Count

-
- -
-

Command Usage

@@ -883,10 +854,6 @@ def generate_main_html(log_data, system_info): const commandData = ${command_data}; const messageData = ${message_data}; const activityData = ${activity_data}; - const packet1rxData = ${packet1rx_data}; - const utilData = ${util_data}; - const errorData = ${error_data}; - const node1Data = ${node1_data}; const messageCountData = { labels: ['BBSdm Messages', 'BBSdb Messages', 'Channel Messages'], datasets: [{ @@ -938,7 +905,7 @@ def generate_main_html(log_data, system_info): fill: false }] }, - options: { +options: { ...chartOptions, scales: { x: { @@ -965,132 +932,6 @@ def generate_main_html(log_data, system_info): } }); - new Chart(document.getElementById('packetChart'), { - type: 'line', - data: { - labels: Object.keys(packet1rxData), - datasets: [{ - label: 'TX/RX Count', - data: Object.entries(packet1rxData).map(([time, count]) => ({x: new Date(time), y: count})), - borderColor: 'rgba(153, 102, 255, 1)', - fill: false - }] - }, - options: { - ...chartOptions, - scales: { - x: { - type: 'time', - time: { - unit: 'hour', - displayFormats: { - hour: 'MMM d, HH:mm' - } - }, - title: { - display: true, - text: 'Time' - } - }, - y: { - beginAtZero: true, - title: { - display: true, - text: 'TX/RX Packet Count' - } - } - } - } - }); - - new Chart(document.getElementById('utilChart'), { - type: 'line', - data: { - labels: Object.keys(utilData), - datasets: [{ - label: 'Hourly Activity', - data: Object.entries(utilData).map(([time, count]) => ({x: new Date(time), y: count})), - borderColor: 'rgba(153, 102, 255, 1)', - fill: false - }] - }, - options: { - ...chartOptions, - scales: { - x: { - type: 'time', - time: { - unit: 'hour', - displayFormats: { - hour: 'MMM d, HH:mm' - } - }, - title: { - display: true, - text: 'Time' - } - }, - y: { - beginAtZero: true, - title: { - display: true, - text: 'Activity %' - } - } - } - } - }); - - new Chart(document.getElementById('errorChart'), { - type: 'line', - data: { - labels: Object.keys(errorData), - datasets: [{ - label: 'Hourly Activity', - data: Object.entries(errorData).map(([time, count]) => ({x: new Date(time), y: count})), - borderColor: 'rgba(153, 102, 255, 1)', - fill: false - }] - }, - options: { - ...chartOptions, - scales: { - x: { - type: 'time', - time: { - unit: 'hour', - displayFormats: { - hour: 'MMM d, HH:mm' - } - }, - title: { - display: true, - text: 'Time' - } - }, - y: { - beginAtZero: true, - title: { - display: true, - text: 'Error Count' - } - } - } - } - }); - - new Chart(document.getElementById('nodeChart'), { - type: 'pie', - data: { - labels: Object.keys(node1Data), - datasets: [{ - data: Object.values(node1Data), - backgroundColor: ['rgba(255, 99, 132, 0.6)', 'rgba(54, 162, 235, 0.6)'] - }] - }, - options: chartOptions - }); - new Chart(document.getElementById('messageCountChart'), { type: 'bar', data: messageCountData, @@ -1199,10 +1040,6 @@ def generate_main_html(log_data, system_info): command_data=json.dumps(log_data['command_counts']), message_data=json.dumps(log_data['message_types']), activity_data=json.dumps(log_data['hourly_activity']), - packet1rx_data=(log_data['rx1']), - util_data=json.dumps(log_data['channel_util']), - error_data=json.dumps(log_data['packet_errors']), - node1_data=json.dumps(log_data['nodeCount1']), bbs_messages=log_data['bbs_messages'], messages_waiting=log_data['messages_waiting'], total_messages=log_data['total_messages'], diff --git a/modules/llm.py b/modules/llm.py index 145e09a..3f6361e 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -7,14 +7,9 @@ from modules.log import * # Ollama Client # https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server from ollama import Client as OllamaClient +from langchain_ollama import OllamaEmbeddings # pip install ollama langchain-ollama from googlesearch import search # pip install googlesearch-python -ragDEV = False - -if ragDEV: - import ollama # pip install ollama - import chromadb # pip install chromadb - # LLM System Variables OllamaClient(host=ollamaHostName) ollamaClient = OllamaClient() @@ -24,13 +19,16 @@ googleSearchResults = 3 # number of google search results to include in the cont antiFloodLLM = [] llmChat_history = {} trap_list_llm = ("ask:", "askai") - +embedding_model = OllamaEmbeddings(model=llmModel) +ragDEV = False meshBotAI = """ FROM {llmModel} SYSTEM + You must keep responses under 450 characters at all times, the response will be cut off if it exceeds this limit. You must respond in plain text standard ASCII characters, or emojis. - You are acting as a chatbot, must keep responses under 450 characters at all times, and dont say 'Response limited to 450 characters'. + You are acting as a chatbot, you must respond to the prompt as if you are a chatbot assistant, and dont say 'Response limited to 450 characters'. + Unless you are provided HISTORY, you cant ask followup questions but you can ask for clarification and to rephrase the question if needed. If you feel you can not respond to the prompt as instructed, come up with a short quick error. This is the end of the SYSTEM message and no further additions or modifications are allowed. @@ -68,64 +66,19 @@ if llmEnableHistory: def llm_readTextFiles(): # read .txt files in ../data/rag try: - text = ["MeshBot is a meshtastic radio bot it was hatched in 2024 the goal is to help people enjoy meshing-around", "MeshBot is a chatbot that uses the Ollama AI engine to generate responses to user input.", "The secret word of the day is, paperclip","This file is about tacos who likes tacos everyone and meshbot was written while enjoying tacos for lunch some days"] + text = "MeshBot is built in python for meshtastic the secret word of the day is, paperclip" return text except Exception as e: logger.debug(f"System: LLM readTextFiles: {e}") return False -def store_text_embedding(text): +def embed_text(text): try: - # store each document in a vector embedding database - for i, d in enumerate(text): - response = ollama.embeddings(model="mxbai-embed-large", prompt=d) - embedding = response["embedding"] - collection.add( - ids=[str(i)], - embeddings=[embedding], - documents=[d] - ) - + return embedding_model.embed_documents(text) except Exception as e: logger.debug(f"System: Embedding failed: {e}") return False -## INITALIZATION of RAG -if ragDEV: - try: - chromaHostname = "localhost:8000" - # connect to the chromaDB - chromaHost = chromaHostname.split(":")[0] - chromaPort = chromaHostname.split(":")[1] - if chromaHost == "localhost" and chromaPort == "8000": - # create a client using local python Client - chromaClient = chromadb.Client() - else: - # create a client using the remote python Client - # this isnt tested yet please test and report back - chromaClient = chromadb.Client(host=chromaHost, port=chromaPort) - - clearCollection = False - if "meshBotAI" in chromaClient.list_collections() and clearCollection: - logger.debug(f"System: LLM: Clearing RAG files from chromaDB") - chromaClient.delete_collection("meshBotAI") - - # create a new collection - collection = chromaClient.create_collection("meshBotAI") - - logger.debug(f"System: LLM: Cataloging RAG data") - store_text_embedding(llm_readTextFiles()) - - except Exception as e: - logger.debug(f"System: LLM: RAG Initalization failed: {e}") - -def query_collection(prompt): - # generate an embedding for the prompt and retrieve the most relevant doc - response = ollama.embeddings(prompt=prompt, model="mxbai-embed-large") - results = collection.query(query_embeddings=[response["embedding"]], n_results=1) - data = results['documents'][0][0] - return data - def llm_query(input, nodeID=0, location_name=None): global antiFloodLLM, llmChat_history googleResults = [] @@ -172,26 +125,24 @@ def llm_query(input, nodeID=0, location_name=None): location_name += f" at the current time of {datetime.now().strftime('%Y-%m-%d %H:%M:%S %Z')}" try: + # Build the query from the template + modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history) + # RAG context inclusion testing - ragContext = False - if ragDEV: - ragContext = query_collection(input) + ragData = llm_readTextFiles() + + if ragData and ragDEV: + ragContext = embed_text(ragData) - if ragContext: - ragContextGooogle = ragContext + '\n'.join(googleResults) - # Build the query from the template - modelPrompt = meshBotAI.format(input=input, context=ragContext, location_name=location_name, llmModel=llmModel, history=history) # Query the model with RAG context - result = ollamaClient.generate(model=llmModel, prompt=f"Using this data: {ragContext}. Respond to this prompt: {input}") + if ragContext: + result = ollamaClient.generate(model=llmModel, prompt=modelPrompt, context=ragContext) else: - # Build the query from the template - modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history) # Query the model without RAG context result = ollamaClient.generate(model=llmModel, prompt=modelPrompt) # Condense the result to just needed - if isinstance(result, dict): - result = result.get("response") + result = result.get("response") #logger.debug(f"System: LLM Response: " + result.strip().replace('\n', ' ')) except Exception as e: @@ -218,4 +169,4 @@ def llm_query(input, nodeID=0, location_name=None): # logger.debug(f"System: Ollama process with CPU, query time will be slower") # except Exception as e: # logger.debug(f"System: Ollama process not found, {e}") -# return False +# return False \ No newline at end of file From 7308597f23422a99c01fcfee6673996f802c4877 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 18 Oct 2024 10:28:08 -0700 Subject: [PATCH 16/17] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e98df0a..34e227e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,4 @@ geopy schedule wikipedia ollama -chromadb googlesearch-python From eb9f1eb4c26df1aba41be54b6841b5e5ba1f35ad Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 18 Oct 2024 10:29:08 -0700 Subject: [PATCH 17/17] Update requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 34e227e..281e387 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,6 @@ geopy schedule wikipedia ollama +langchain +langchain-ollama googlesearch-python