mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 23:24:44 +02:00
Fix bugs: MeshMessage position field, summary loading, Google system prompt, import placement, cleanup timer
- 1a: Declare _position as proper dataclass field with field(default=None, init=False) so hasattr() check isn't needed and the attribute always exists - 1b: Load persisted conversation summaries from DB into memory cache on startup via new _load_summaries() method called after backend creation - 1c: Use Gemini's system_instruction parameter on GenerativeModel instead of only prepending to first message, so system prompt persists across all turns - 1d: Move 'import os' from line 198 to top of main.py with other imports - 1e: Replace unreliable modulo-based cleanup timer with _last_cleanup timestamp comparison that won't miss hours due to async sleep jitter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bc3e85a6fb
commit
c1f2c48494
3 changed files with 53 additions and 13 deletions
|
|
@ -162,6 +162,12 @@ class GoogleBackend(LLMBackend):
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Create model with system instruction for persistent system prompt
|
||||||
|
model = genai.GenerativeModel(
|
||||||
|
self.config.model,
|
||||||
|
system_instruction=enhanced_system if enhanced_system else None,
|
||||||
|
)
|
||||||
|
|
||||||
# Convert messages to Gemini format
|
# Convert messages to Gemini format
|
||||||
# Gemini uses "user" and "model" roles
|
# Gemini uses "user" and "model" roles
|
||||||
history = []
|
history = []
|
||||||
|
|
@ -170,15 +176,11 @@ class GoogleBackend(LLMBackend):
|
||||||
history.append({"role": role, "parts": [msg["content"]]})
|
history.append({"role": role, "parts": [msg["content"]]})
|
||||||
|
|
||||||
# Start chat with history
|
# Start chat with history
|
||||||
chat = self._model.start_chat(history=history)
|
chat = model.start_chat(history=history)
|
||||||
|
|
||||||
# Get the last user message
|
# Get the last user message
|
||||||
last_message = final_messages[-1]["content"] if final_messages else ""
|
last_message = final_messages[-1]["content"] if final_messages else ""
|
||||||
|
|
||||||
# Prepend system prompt to first message if needed
|
|
||||||
if enhanced_system and not history:
|
|
||||||
last_message = f"{enhanced_system}\n\n{last_message}"
|
|
||||||
|
|
||||||
# Generate response
|
# Generate response
|
||||||
response = await chat.send_message_async(
|
response = await chat.send_message_async(
|
||||||
last_message,
|
last_message,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
|
|
||||||
import meshtastic
|
import meshtastic
|
||||||
|
|
@ -26,13 +26,12 @@ class MeshMessage:
|
||||||
channel: int # Channel index
|
channel: int # Channel index
|
||||||
is_dm: bool # True if direct message to us
|
is_dm: bool # True if direct message to us
|
||||||
packet: dict # Raw packet for additional data
|
packet: dict # Raw packet for additional data
|
||||||
|
_position: Optional[tuple[float, float]] = field(default=None, repr=False, init=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sender_position(self) -> Optional[tuple[float, float]]:
|
def sender_position(self) -> Optional[tuple[float, float]]:
|
||||||
"""Get sender's GPS position if available (lat, lon)."""
|
"""Get sender's GPS position if available (lat, lon)."""
|
||||||
# Position comes from node info, not the message itself
|
return self._position
|
||||||
# This will be populated by the connector if available
|
|
||||||
return self._position if hasattr(self, "_position") else None
|
|
||||||
|
|
||||||
|
|
||||||
class MeshConnector:
|
class MeshConnector:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
@ -18,6 +19,7 @@ from .commands.status import set_start_time
|
||||||
from .config import Config, load_config
|
from .config import Config, load_config
|
||||||
from .connector import MeshConnector, MeshMessage
|
from .connector import MeshConnector, MeshMessage
|
||||||
from .history import ConversationHistory
|
from .history import ConversationHistory
|
||||||
|
from .memory import ConversationSummary
|
||||||
from .responder import Responder
|
from .responder import Responder
|
||||||
from .router import MessageRouter, RouteType
|
from .router import MessageRouter, RouteType
|
||||||
|
|
||||||
|
|
@ -37,6 +39,7 @@ class MeshAI:
|
||||||
self.responder: Optional[Responder] = None
|
self.responder: Optional[Responder] = None
|
||||||
self._running = False
|
self._running = False
|
||||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
||||||
|
self._last_cleanup: float = 0.0
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
"""Start the bot."""
|
"""Start the bot."""
|
||||||
|
|
@ -52,6 +55,7 @@ class MeshAI:
|
||||||
|
|
||||||
self._running = True
|
self._running = True
|
||||||
self._loop = asyncio.get_event_loop()
|
self._loop = asyncio.get_event_loop()
|
||||||
|
self._last_cleanup = time.time()
|
||||||
|
|
||||||
# Write PID file
|
# Write PID file
|
||||||
self._write_pid()
|
self._write_pid()
|
||||||
|
|
@ -63,8 +67,9 @@ class MeshAI:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
# Periodic cleanup
|
# Periodic cleanup
|
||||||
if int(time.time()) % 3600 == 0: # Every hour
|
if time.time() - self._last_cleanup >= 3600:
|
||||||
await self.history.cleanup_expired()
|
await self.history.cleanup_expired()
|
||||||
|
self._last_cleanup = time.time()
|
||||||
|
|
||||||
async def stop(self) -> None:
|
async def stop(self) -> None:
|
||||||
"""Stop the bot."""
|
"""Stop the bot."""
|
||||||
|
|
@ -121,6 +126,9 @@ class MeshAI:
|
||||||
self.config.llm, api_key, window_size, summarize_threshold
|
self.config.llm, api_key, window_size, summarize_threshold
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Load persisted summaries into memory cache
|
||||||
|
await self._load_summaries()
|
||||||
|
|
||||||
# Meshtastic connector
|
# Meshtastic connector
|
||||||
self.connector = MeshConnector(self.config.connection)
|
self.connector = MeshConnector(self.config.connection)
|
||||||
|
|
||||||
|
|
@ -183,6 +191,40 @@ class MeshAI:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error handling message: {e}", exc_info=True)
|
logger.error(f"Error handling message: {e}", exc_info=True)
|
||||||
|
|
||||||
|
async def _load_summaries(self) -> None:
|
||||||
|
"""Load persisted summaries from database into memory cache."""
|
||||||
|
memory = self.llm.get_memory()
|
||||||
|
if not memory:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.history or not self.history._db:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with self.history._lock:
|
||||||
|
cursor = await self.history._db.execute(
|
||||||
|
"SELECT user_id, summary, message_count, updated_at "
|
||||||
|
"FROM conversation_summaries"
|
||||||
|
)
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
|
||||||
|
loaded = 0
|
||||||
|
for row in rows:
|
||||||
|
user_id, summary_text, message_count, updated_at = row
|
||||||
|
summary = ConversationSummary(
|
||||||
|
summary=summary_text,
|
||||||
|
last_updated=updated_at,
|
||||||
|
message_count=message_count,
|
||||||
|
)
|
||||||
|
memory.load_summary(user_id, summary)
|
||||||
|
loaded += 1
|
||||||
|
|
||||||
|
if loaded:
|
||||||
|
logger.info(f"Loaded {loaded} conversation summaries from database")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to load summaries from database: {e}")
|
||||||
|
|
||||||
def _write_pid(self) -> None:
|
def _write_pid(self) -> None:
|
||||||
"""Write PID file."""
|
"""Write PID file."""
|
||||||
pid_file = Path("/tmp/meshai.pid")
|
pid_file = Path("/tmp/meshai.pid")
|
||||||
|
|
@ -195,9 +237,6 @@ class MeshAI:
|
||||||
pid_file.unlink()
|
pid_file.unlink()
|
||||||
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(verbose: bool = False) -> None:
|
def setup_logging(verbose: bool = False) -> None:
|
||||||
"""Configure logging."""
|
"""Configure logging."""
|
||||||
level = logging.DEBUG if verbose else logging.INFO
|
level = logging.DEBUG if verbose else logging.INFO
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue