From 626ac59b4edd4d1ca79f39df0b56792c47cc34b7 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 15 Aug 2025 21:11:09 -0700 Subject: [PATCH 01/17] gemma3 LLM rewrite, the removal of RAG to keep things clean. This default changes as well as puts input direct to the LLM further testing is needed, new LLM prompting is different. --- config.template | 4 +- install.sh | 10 ++-- modules/llm.py | 131 ++++++++------------------------------------ modules/settings.py | 2 +- 4 files changed, 31 insertions(+), 116 deletions(-) diff --git a/config.template b/config.template index 524a377..efe9409 100644 --- a/config.template +++ b/config.template @@ -56,8 +56,8 @@ wikipedia = True # Enable ollama LLM see more at https://ollama.com ollama = False -# Ollama model to use (defaults to gemma2:2b) -# ollamaModel = llama3.1 +# Ollama model to use (defaults to gemma3:270m) +# ollamaModel = gemma3:latest # server instance to use (defaults to local machine install) ollamaHostName = http://localhost:11434 # Produce LLM replies to messages that aren't commands? diff --git a/install.sh b/install.sh index 4c44c2f..7c49cce 100755 --- a/install.sh +++ b/install.sh @@ -250,7 +250,7 @@ if [[ $(echo "${embedded}" | grep -i "^n") ]]; then printf "\nOptionally if you want to install the multi gig LLM Ollama compnents we will execute the following commands\n" printf "\ncurl -fsSL https://ollama.com/install.sh | sh\n" - printf "ollama pull gemma2:2b\n" + printf "ollama pull gemma3:latest\n" printf "Total download is multi GB, recomend pi5/8GB or better for this\n" # ask if the user wants to install the LLM Ollama components printf "\nDo you want to install the LLM Ollama components? (y/n)" @@ -258,12 +258,12 @@ if [[ $(echo "${embedded}" | grep -i "^n") ]]; then if [[ $(echo "${ollama}" | grep -i "^y") ]]; then curl -fsSL https://ollama.com/install.sh | sh - # ask if want to install gemma2:2b - printf "\n Ollama install done now we can install the Gemma2:2b components\n" - echo "Do you want to install the Gemma2:2b components? (y/n)" + # ask if want to install gemma3:latest + printf "\n Ollama install done now we can install the gemma3:latest components\n" + echo "Do you want to install the gemma3:latest components? (y/n)" read gemma if [[ $(echo "${gemma}" | grep -i "^y") ]]; then - ollama pull gemma2:2b + ollama pull gemma3:latest fi fi diff --git a/modules/llm.py b/modules/llm.py index b0c36fb..60bb055 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -10,25 +10,16 @@ import requests import json from googlesearch import search # pip install googlesearch-python -# This is my attempt at a simple RAG implementation it will require some setup -# you will need to have the RAG data in a folder named rag in the data directory (../data/rag) -# This is lighter weight and can be used in a standalone environment, needs chromadb -# "chat with a file" is the use concept here, the file is the RAG data -# is anyone using this please let me know if you are Dec62024 -kelly -ragDEV = False - -if ragDEV: - import os - import ollama # pip install ollama - import chromadb # pip install chromadb - from ollama import Client as OllamaClient - ollamaClient = OllamaClient(host=ollamaHostName) - # LLM System Variables ollamaAPI = ollamaHostName + "/api/generate" +rawQuery = True # if True, the input is sent raw to the LLM, if False, it is processed by the meshBotAI template + openaiAPI = "https://api.openai.com/v1/completions" # not used, if you do push a enhancement! + +# Used in the meshBotAI template llmEnableHistory = True # enable last message history for the LLM model llmContext_fromGoogle = True # enable context from google search results adds to compute time but really helps with responses accuracy + googleSearchResults = 3 # number of google search results to include in the context more results = more compute time antiFloodLLM = [] llmChat_history = {} @@ -74,73 +65,6 @@ if llmEnableHistory: """ -def llm_readTextFiles(): - # read .txt files in ../data/rag - try: - text = [] - directory = "../data/rag" - for filename in os.listdir(directory): - if filename.endswith(".txt"): - filepath = os.path.join(directory, filename) - with open(filepath, 'r') as f: - text.append(f.read()) - return text - except Exception as e: - logger.debug(f"System: LLM readTextFiles: {e}") - return False - -def store_text_embedding(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] - ) - - 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 = [] @@ -162,7 +86,7 @@ def llm_query(input, nodeID=0, location_name=None): else: antiFloodLLM.append(nodeID) - if llmContext_fromGoogle: + if llmContext_fromGoogle and not rawQuery: # grab some context from the internet using google search hits (if available) # localization details at https://pypi.org/project/googlesearch-python/ @@ -193,36 +117,27 @@ 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: - # 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 - result = ollamaClient.generate(model=llmModel, prompt=modelPrompt) - # Condense the result to just needed - if isinstance(result, dict): - result = result.get("response") + if rawQuery: + # sanitize the input to remove tool call syntax + input = input.replace('```', '').replace('```bash', '').replace('```python', '') + modelPrompt = 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) - llmQuery = {"model": llmModel, "prompt": modelPrompt, "stream": False} - # Query the model via Ollama web API - result = requests.post(ollamaAPI, data=json.dumps(llmQuery)) - # Condense the result to just needed - if result.status_code == 200: - result_json = result.json() - result = result_json.get("response", "") + + llmQuery = {"model": llmModel, "prompt": modelPrompt, "stream": False} + # Query the model via Ollama web API + result = requests.post(ollamaAPI, data=json.dumps(llmQuery)) + # Condense the result to just needed + if result.status_code == 200: + result_json = result.json() + result = result_json.get("response", "") - # deepseek-r1 has added tags to the response - if "" in result: - result = result.split("")[1] - else: - raise Exception(f"HTTP Error: {result.status_code}") + # deepseek-r1 has added tags to the response + if "" in result: + result = result.split("")[1] + else: + raise Exception(f"HTTP Error: {result.status_code}") #logger.debug(f"System: LLM Response: " + result.strip().replace('\n', ' ')) except Exception as e: diff --git a/modules/settings.py b/modules/settings.py index 50e871d..74ae700 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -219,7 +219,7 @@ try: solar_conditions_enabled = config['general'].getboolean('spaceWeather', True) wikipedia_enabled = config['general'].getboolean('wikipedia', False) llm_enabled = config['general'].getboolean('ollama', False) # https://ollama.com - llmModel = config['general'].get('ollamaModel', 'gemma2:2b') # default gemma2:2b + llmModel = config['general'].get('ollamaModel', 'gemma3:270m') # default gemma3:270m ollamaHostName = config['general'].get('ollamaHostName', 'http://localhost:11434') # default localhost llmReplyToNonCommands = config['general'].getboolean('llmReplyToNonCommands', True) dont_retry_disconnect = config['general'].getboolean('dont_retry_disconnect', False) # default False, retry on disconnect From d1a87f161bab262eab1b25934709ac14223b96a5 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 15 Aug 2025 21:17:58 -0700 Subject: [PATCH 02/17] updateOllama remember to update to latest ollama bin's --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9143d63..0630b68 100644 --- a/README.md +++ b/README.md @@ -347,12 +347,12 @@ repeater_channels = [2, 3] ``` ### Ollama (LLM/AI) Settings -For Ollama to work, the command line `ollama run 'model'` needs to work properly. Ensure you have enough RAM and your GPU is working as expected. The default model for this project is set to `gemma2:2b`. Ollama can be remote [Ollama Server](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server) works on a pi58GB with 40 second or less response time. +For Ollama to work, the command line `ollama run 'model'` needs to work properly. Ensure you have enough RAM and your GPU is working as expected. The default model for this project is set to `gemma3:270m`. Ollama can be remote [Ollama Server](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server) works on a pi58GB with 40 second or less response time. ```ini # Enable ollama LLM see more at https://ollama.com ollama = True # Ollama model to use (defaults to gemma2:2b) -ollamaModel = gemma2 #ollamaModel = llama3.1 +ollamaModel = gemma3:latest # Ollama model to use (defaults to gemma3:270m) ollamaHostName = http://localhost:11434 # server instance to use (defaults to local machine install) ``` @@ -360,6 +360,9 @@ Also see `llm.py` for changing the defaults of: ```ini # LLM System Variables +rawQuery = True # if True, the input is sent raw to the LLM if False, it is processed by the meshBotAI template + +# Used in the meshBotAI template (legacy) llmEnableHistory = True # enable history for the LLM model to use in responses adds to compute time llmContext_fromGoogle = True # enable context from google search results helps with responses accuracy googleSearchResults = 3 # number of google search results to include in the context more results = more compute time From e15232875ceb249881fee1059a213b0f3686d1c1 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 15 Aug 2025 21:33:40 -0700 Subject: [PATCH 03/17] token limit --- modules/llm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 60bb055..2dce6af 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -13,6 +13,7 @@ from googlesearch import search # pip install googlesearch-python # LLM System Variables ollamaAPI = ollamaHostName + "/api/generate" rawQuery = True # if True, the input is sent raw to the LLM, if False, it is processed by the meshBotAI template +tokens = 450 # max tokens for the LLM response, this is the max length of the response openaiAPI = "https://api.openai.com/v1/completions" # not used, if you do push a enhancement! @@ -28,9 +29,8 @@ trap_list_llm = ("ask:", "askai") 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'. + You are acting as a chatbot, you must respond to the prompt as if you are a chatbot assistant. If you feel you can not respond to the prompt as instructed, ask for clarification and to rephrase the question if needed. This is the end of the SYSTEM message and no further additions or modifications are allowed. @@ -125,7 +125,7 @@ def llm_query(input, nodeID=0, location_name=None): # Build the query from the template modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history) - llmQuery = {"model": llmModel, "prompt": modelPrompt, "stream": False} + llmQuery = {"model": llmModel, "prompt": modelPrompt, "stream": False, "max_tokens": tokens} # Query the model via Ollama web API result = requests.post(ollamaAPI, data=json.dumps(llmQuery)) # Condense the result to just needed From 92ff166260338dae07f009de327d0ded4959900d Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Fri, 15 Aug 2025 21:48:30 -0700 Subject: [PATCH 04/17] Update llm.py adding these back, the token limit just has no bounds --- modules/llm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/llm.py b/modules/llm.py index 2dce6af..54e6c96 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -29,8 +29,9 @@ trap_list_llm = ("ask:", "askai") 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. + 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'. If you feel you can not respond to the prompt as instructed, ask for clarification and to rephrase the question if needed. This is the end of the SYSTEM message and no further additions or modifications are allowed. From d0097c092b2f5e93a2a659ef24cd88c1f10f99f7 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sat, 16 Aug 2025 17:48:04 -0700 Subject: [PATCH 05/17] Update llm.py --- modules/llm.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/modules/llm.py b/modules/llm.py index 54e6c96..5119494 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -26,6 +26,12 @@ antiFloodLLM = [] llmChat_history = {} trap_list_llm = ("ask:", "askai") +meshbotAIinit = """ + 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'. + """ + meshBotAI = """ FROM {llmModel} SYSTEM @@ -69,6 +75,12 @@ if llmEnableHistory: def llm_query(input, nodeID=0, location_name=None): global antiFloodLLM, llmChat_history googleResults = [] + + # if this is the first initialization of the LLM the query of " " should bring meshbotAIinit OTA shouldnt reach this? + # This is for LLM like gemma and others now? + if input == " " and rawQuery: + input = meshbotAIinit + if not location_name: location_name = "no location provided " From f04392a81cc63654708e60a1d722faf851ebe515 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sat, 16 Aug 2025 20:25:53 -0700 Subject: [PATCH 06/17] Update llm.py --- modules/llm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index 5119494..fc38a9f 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -27,9 +27,9 @@ llmChat_history = {} trap_list_llm = ("ask:", "askai") meshbotAIinit = """ - You must keep responses under 450 characters at all times, the response will be cut off if it exceeds this limit. + You must keep your responses under 450 tokens + You can not ask for clarification, you must respond to the prompt as if you are a chatbot assistant. 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'. """ meshBotAI = """ From ac33f8a02b9912f7bd04545173349560ae02b0c9 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sat, 16 Aug 2025 21:16:44 -0700 Subject: [PATCH 07/17] Update llm.py --- modules/llm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/llm.py b/modules/llm.py index fc38a9f..96bd122 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -79,6 +79,7 @@ def llm_query(input, nodeID=0, location_name=None): # if this is the first initialization of the LLM the query of " " should bring meshbotAIinit OTA shouldnt reach this? # This is for LLM like gemma and others now? if input == " " and rawQuery: + logger.warning("System: These LLM models lack a traditional system prompt, they can be verbose and not very helpful be advised.") input = meshbotAIinit if not location_name: @@ -132,6 +133,8 @@ def llm_query(input, nodeID=0, location_name=None): try: if rawQuery: # sanitize the input to remove tool call syntax + if '```' in input: + logger.warning("System: LLM Query: Code markdown detected, removing for raw query") input = input.replace('```', '').replace('```bash', '').replace('```python', '') modelPrompt = input else: From 388d862fc986ddb219f8e0c4c3c11f5b110ffc50 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sat, 16 Aug 2025 21:24:46 -0700 Subject: [PATCH 08/17] Update llm.py --- modules/llm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/llm.py b/modules/llm.py index 96bd122..ff214af 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -135,7 +135,7 @@ def llm_query(input, nodeID=0, location_name=None): # sanitize the input to remove tool call syntax if '```' in input: logger.warning("System: LLM Query: Code markdown detected, removing for raw query") - input = input.replace('```', '').replace('```bash', '').replace('```python', '') + input = input.replace('```bash', '').replace('```python', '').replace('```', '') modelPrompt = input else: # Build the query from the template From 9272218815f6eed91b210f0d2377fe1e28bb6b62 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 17 Aug 2025 20:05:41 -0700 Subject: [PATCH 09/17] Update system.py i declare --- modules/system.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/system.py b/modules/system.py index ab6f81e..31f1451 100644 --- a/modules/system.py +++ b/modules/system.py @@ -268,6 +268,7 @@ if ble_count > 1: logger.debug(f"System: Initializing Interfaces") interface1 = interface2 = interface3 = interface4 = interface5 = interface6 = interface7 = interface8 = interface9 = None retry_int1 = retry_int2 = retry_int3 = retry_int4 = retry_int5 = retry_int6 = retry_int7 = retry_int8 = retry_int9 = False +myNodeNum1 = myNodeNum2 = myNodeNum3 = myNodeNum4 = myNodeNum5 = myNodeNum6 = myNodeNum7 = myNodeNum8 = myNodeNum9 = 777 max_retry_count1 = max_retry_count2 = max_retry_count3 = max_retry_count4 = max_retry_count5 = max_retry_count6 = max_retry_count7 = max_retry_count8 = max_retry_count9 = interface_retry_count for i in range(1, 10): interface_type = globals().get(f'interface{i}_type') From 99944465102f6317de389d61a8e592802df0606a Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 17 Aug 2025 20:24:09 -0700 Subject: [PATCH 10/17] better MQTT handler --- mesh_bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesh_bot.py b/mesh_bot.py index 1adeac1..226b17d 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1162,6 +1162,7 @@ def onReceive(packet, interface): if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP': message_bytes = packet['decoded']['payload'] message_string = message_bytes.decode('utf-8') + via_mqtt = packet['decoded'].get('viaMqtt', False) # check if the packet is from us if message_from_id in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]: @@ -1207,7 +1208,7 @@ def onReceive(packet, interface): if hop_start == hop_limit: hop = "Direct" hop_count = 0 - elif hop_start == 0 and hop_limit > 0: + elif hop_start == 0 and hop_limit > 0 or via_mqtt: hop = "MQTT" hop_count = 0 else: From df6a1cfb668c264d93b67ae04100bb87f14c6894 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 17 Aug 2025 20:25:40 -0700 Subject: [PATCH 11/17] Update pong_bot.py better MQTT handler --- pong_bot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pong_bot.py b/pong_bot.py index a703a7f..7faafc3 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -254,6 +254,7 @@ def onReceive(packet, interface): if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP': message_bytes = packet['decoded']['payload'] message_string = message_bytes.decode('utf-8') + via_mqtt = packet['decoded'].get('viaMqtt', False) # check if the packet is from us if message_from_id == myNodeNum1 or message_from_id == myNodeNum2: @@ -286,7 +287,7 @@ def onReceive(packet, interface): if hop_start == hop_limit: hop = "Direct" hop_count = 0 - elif hop_start == 0 and hop_limit > 0: + elif hop_start == 0 and hop_limit > 0 or via_mqtt: hop = "MQTT" hop_count = 0 else: From 4c7fe55b430efaf9c640c6e7d25c851a82040952 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 18 Aug 2025 18:54:02 -0700 Subject: [PATCH 12/17] hopFix --- mesh_bot.py | 11 +++++++++-- pong_bot.py | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 226b17d..2f8134c 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1163,6 +1163,11 @@ def onReceive(packet, interface): message_bytes = packet['decoded']['payload'] message_string = message_bytes.decode('utf-8') via_mqtt = packet['decoded'].get('viaMqtt', False) + rx_time = packet['decoded'].get('rxTime', time.time()) + + # ignore packets received during soft startup + logger.warning(f"System: Soft Startup: Ignoring packet from {message_from_id} with time {rx_time}") + return # check if the packet is from us if message_from_id in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]: @@ -1202,8 +1207,10 @@ def onReceive(packet, interface): if enableHopLogs: logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start}") - if hop_away == 0 and hop_limit == 0 and hop_start == 0: - logger.debug(f"System: Packet HopDebugger: No hop count found in PACKET {packet} END PACKET") + + if hop_away == 0 and hop_limit == 0 and hop_start == 0: + hop = "Last Hop" + hop_count = 0 if hop_start == hop_limit: hop = "Direct" diff --git a/pong_bot.py b/pong_bot.py index 7faafc3..4d54829 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -284,6 +284,13 @@ def onReceive(packet, interface): else: hop_start = 0 + if enableHopLogs: + logger.debug(f"System: Packet HopDebugger: hop_away:{hop_away} hop_limit:{hop_limit} hop_start:{hop_start}") + + if hop_away == 0 and hop_limit == 0 and hop_start == 0: + hop = "Last Hop" + hop_count = 0 + if hop_start == hop_limit: hop = "Direct" hop_count = 0 From c1adca7db0545f9d8d156b720847ca8ed1577ed4 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Mon, 18 Aug 2025 18:55:23 -0700 Subject: [PATCH 13/17] Update mesh_bot.py --- mesh_bot.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 2f8134c..a76db89 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1165,10 +1165,6 @@ def onReceive(packet, interface): via_mqtt = packet['decoded'].get('viaMqtt', False) rx_time = packet['decoded'].get('rxTime', time.time()) - # ignore packets received during soft startup - logger.warning(f"System: Soft Startup: Ignoring packet from {message_from_id} with time {rx_time}") - return - # check if the packet is from us if message_from_id in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]: logger.warning(f"System: Packet from self {message_from_id} loop or traffic replay detected") From 56af59345d1e74ad0d7f3eab5cb557f2fd580e9a Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 19 Aug 2025 06:19:07 -0700 Subject: [PATCH 14/17] Update settings.py --- modules/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/settings.py b/modules/settings.py index 74ae700..2c2948a 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -361,7 +361,7 @@ try: splitDelay = config['messagingSettings'].getfloat('splitDelay', 0) # default 0 MESSAGE_CHUNK_SIZE = config['messagingSettings'].getint('MESSAGE_CHUNK_SIZE', 160) # default 160 wantAck = config['messagingSettings'].getboolean('wantAck', False) # default False - maxBuffer = config['messagingSettings'].getint('maxBuffer', 220) # default 220 + maxBuffer = config['messagingSettings'].getint('maxBuffer', 200) # default 200 enableHopLogs = config['messagingSettings'].getboolean('enableHopLogs', False) # default False except KeyError as e: From d311832a92f2d8f00d4e25ad5607aff497a44961 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Wed, 20 Aug 2025 11:55:44 -0700 Subject: [PATCH 15/17] truncation --- modules/llm.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/modules/llm.py b/modules/llm.py index ff214af..cc9f034 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -13,7 +13,8 @@ from googlesearch import search # pip install googlesearch-python # LLM System Variables ollamaAPI = ollamaHostName + "/api/generate" rawQuery = True # if True, the input is sent raw to the LLM, if False, it is processed by the meshBotAI template -tokens = 450 # max tokens for the LLM response, this is the max length of the response +tokens = 450 # max charcters for the LLM response, this is the max length of the response also in prompts +requestTruncation = True # if True, the LLM "will" truncate the response openaiAPI = "https://api.openai.com/v1/completions" # not used, if you do push a enhancement! @@ -27,11 +28,12 @@ llmChat_history = {} trap_list_llm = ("ask:", "askai") meshbotAIinit = """ - You must keep your responses under 450 tokens - You can not ask for clarification, you must respond to the prompt as if you are a chatbot assistant. - You must respond in plain text standard ASCII characters, or emojis. + keep responses as short as possible. chatbot assistant no followuyp questions, no asking for clarification. + You must respond in plain text standard ASCII characters or emojis. """ +truncatePrompt = f"truncate this as short as possible:\n" + meshBotAI = """ FROM {llmModel} SYSTEM @@ -162,6 +164,23 @@ def llm_query(input, nodeID=0, location_name=None): # cleanup for message output response = result.strip().replace('\n', ' ') + + if rawQuery and requestTruncation and len(response) > 450: + #retryy loop to truncate the response + logger.warning(f"System: LLM Query: Response exceeded {tokens} characters, requesting truncation") + truncateQuery = {"model": llmModel, "prompt": truncatePrompt + response, "stream": False, "max_tokens": tokens} + truncateResult = requests.post(ollamaAPI, data=json.dumps(truncateQuery)) + if truncateResult.status_code == 200: + truncate_json = truncateResult.json() + result = truncate_json.get("response", "") + + else: + #use the original result if truncation fails + logger.warning("System: LLM Query: Truncation failed, using original response") + + # cleanup for message output + response = result.strip().replace('\n', ' ') + # done with the query, remove the user from the anti flood list antiFloodLLM.remove(nodeID) From 80897f7a82275fc0edc2e5e641d4900d482616db Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 26 Aug 2025 10:26:33 -0700 Subject: [PATCH 16/17] defaults to gemma3 raw input this is a change as its looking to remove google lookups with the python module. if this changes is impacting please let me know in [general] add `rawLLMQuery = False` to reverse --- config.template | 2 ++ modules/llm.py | 14 ++++++++------ modules/settings.py | 3 ++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/config.template b/config.template index efe9409..a9ef433 100644 --- a/config.template +++ b/config.template @@ -63,6 +63,8 @@ ollamaHostName = http://localhost:11434 # Produce LLM replies to messages that aren't commands? # If False, the LLM only replies to the "ask:" and "askai" commands. llmReplyToNonCommands = True +# if True, the input is sent raw to the LLM, if False uses legacy template query +rawLLMQuery = True # StoreForward Enabled and Limits StoreForward = True diff --git a/modules/llm.py b/modules/llm.py index cc9f034..d18e5ac 100644 --- a/modules/llm.py +++ b/modules/llm.py @@ -8,11 +8,13 @@ from modules.log import * # https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server import requests import json -from googlesearch import search # pip install googlesearch-python + +if not rawLLMQuery: + # this may be removed in the future + from googlesearch import search # pip install googlesearch-python # LLM System Variables ollamaAPI = ollamaHostName + "/api/generate" -rawQuery = True # if True, the input is sent raw to the LLM, if False, it is processed by the meshBotAI template tokens = 450 # max charcters for the LLM response, this is the max length of the response also in prompts requestTruncation = True # if True, the LLM "will" truncate the response @@ -80,7 +82,7 @@ def llm_query(input, nodeID=0, location_name=None): # if this is the first initialization of the LLM the query of " " should bring meshbotAIinit OTA shouldnt reach this? # This is for LLM like gemma and others now? - if input == " " and rawQuery: + if input == " " and rawLLMQuery: logger.warning("System: These LLM models lack a traditional system prompt, they can be verbose and not very helpful be advised.") input = meshbotAIinit @@ -102,7 +104,7 @@ def llm_query(input, nodeID=0, location_name=None): else: antiFloodLLM.append(nodeID) - if llmContext_fromGoogle and not rawQuery: + if llmContext_fromGoogle and not rawLLMQuery: # grab some context from the internet using google search hits (if available) # localization details at https://pypi.org/project/googlesearch-python/ @@ -133,7 +135,7 @@ 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: - if rawQuery: + if rawLLMQuery: # sanitize the input to remove tool call syntax if '```' in input: logger.warning("System: LLM Query: Code markdown detected, removing for raw query") @@ -165,7 +167,7 @@ def llm_query(input, nodeID=0, location_name=None): # cleanup for message output response = result.strip().replace('\n', ' ') - if rawQuery and requestTruncation and len(response) > 450: + if rawLLMQuery and requestTruncation and len(response) > 450: #retryy loop to truncate the response logger.warning(f"System: LLM Query: Response exceeded {tokens} characters, requesting truncation") truncateQuery = {"model": llmModel, "prompt": truncatePrompt + response, "stream": False, "max_tokens": tokens} diff --git a/modules/settings.py b/modules/settings.py index 2c2948a..08ab3b7 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -219,8 +219,9 @@ try: solar_conditions_enabled = config['general'].getboolean('spaceWeather', True) wikipedia_enabled = config['general'].getboolean('wikipedia', False) llm_enabled = config['general'].getboolean('ollama', False) # https://ollama.com - llmModel = config['general'].get('ollamaModel', 'gemma3:270m') # default gemma3:270m ollamaHostName = config['general'].get('ollamaHostName', 'http://localhost:11434') # default localhost + llmModel = config['general'].get('ollamaModel', 'gemma3:270m') # default gemma3:270m + rawLLMQuery = config['general'].getboolean('rawLLMQuery', True) #default True llmReplyToNonCommands = config['general'].getboolean('llmReplyToNonCommands', True) dont_retry_disconnect = config['general'].getboolean('dont_retry_disconnect', False) # default False, retry on disconnect # emergency response From 50c3249edc49e93a71332f51f1c4a291abb9ec21 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 26 Aug 2025 10:44:30 -0700 Subject: [PATCH 17/17] explicitCmd i think @NomDeTom mentioned this a long time ago and well .. here is a change to help the phases of the moon and tide. --- README.md | 1 + config.template | 2 ++ modules/settings.py | 1 + modules/system.py | 23 ++++++++++++++++++----- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0630b68..b12e658 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,7 @@ defaultChannel = 0 ignoreDefaultChannel = False # ignoreDefaultChannel, the bot will ignore the default channel set above ignoreChannels = # ignoreChannels is a comma separated list of channels to ignore, e.g. 4,5 cmdBang = False # require ! to be the first character in a command +explicitCmd = True # require explicit command, the message will only be processed if it starts with a command word disable to get more activity ``` ### Location Settings diff --git a/config.template b/config.template index a9ef433..6e106bf 100644 --- a/config.template +++ b/config.template @@ -36,6 +36,8 @@ ignoreDefaultChannel = False ignoreChannels = # require ! to be the first character in a command cmdBang = False +# require explicit command, the message will only be processed if it starts with a command word +explicitCmd = True # motd is reset to this value on boot motd = Thanks for using MeshBOT! Have a good day! diff --git a/modules/settings.py b/modules/settings.py index 08ab3b7..f31007d 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -197,6 +197,7 @@ try: ignoreChannels = config['general'].get('ignoreChannels', '').split(',') # ignore these channels ignoreDefaultChannel = config['general'].getboolean('ignoreDefaultChannel', False) cmdBang = config['general'].getboolean('cmdBang', False) # default off + explicitCmd = config['general'].getboolean('explicitCmd', True) # default on zuluTime = config['general'].getboolean('zuluTime', False) # aka 24 hour time log_messages_to_file = config['general'].getboolean('LogMessagesToFile', False) # default off log_backup_count = config['general'].getint('LogBackupCount', 32) # default 32 days diff --git a/modules/system.py b/modules/system.py index 31f1451..5b1ad4c 100644 --- a/modules/system.py +++ b/modules/system.py @@ -687,11 +687,24 @@ def messageTrap(msg): message_list=msg.split(" ") for m in message_list: for t in trap_list: - # if word in message is in the trap list, return True - if t.lower() == m.lower(): - return True - if cmdBang and m.startswith("!"): - return True + if not explicitCmd: + # if word in message is in the trap list, return True + if t.lower() == m.lower(): + if cmdBang: + if m.startswith('!'): + return True + else: + continue + return True + else: + # if the index 0 of the message is a word in the trap list, return True + if t.lower() == m.lower() and message_list.index(m) == 0: + if cmdBang: + if m.startswith('!'): + return True + else: + continue + return True # if no trap words found, run a search for near misses like ping? or cmd? for m in message_list: for t in range(len(trap_list)):