From 6e2d956be696145e3e586f2a7262e29626f7adba Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 24 Feb 2026 04:44:44 +0000 Subject: [PATCH] Migrate Google backend from deprecated google-generativeai to google-genai SDK with grounding support Co-Authored-By: Claude Opus 4.6 --- config.example.yaml | 1 + docker-entrypoint.sh | 1 + meshai/backends/google_backend.py | 90 ++++++++++++------------------- meshai/cli/configurator.py | 8 +++ meshai/config.py | 1 + pyproject.toml | 2 +- requirements.txt | 2 +- 7 files changed, 46 insertions(+), 59 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index d632654..165cfe3 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -58,6 +58,7 @@ llm: You are a helpful assistant on a Meshtastic mesh network. Keep responses VERY brief - under 250 characters total. Be concise but friendly. No markdown formatting. + google_grounding: false # Enable Google Search grounding (Gemini only, $35/1k queries) # === WEATHER === weather: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 6234cdb..15dbab4 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -59,6 +59,7 @@ llm: You are a helpful assistant on a Meshtastic mesh network. Keep responses VERY brief - under 250 characters total. Be concise but friendly. No markdown formatting. + google_grounding: false EOF echo "Default config created. Configure via http://localhost:7682" fi diff --git a/meshai/backends/google_backend.py b/meshai/backends/google_backend.py index a0ab0f4..23dbd1c 100644 --- a/meshai/backends/google_backend.py +++ b/meshai/backends/google_backend.py @@ -1,9 +1,10 @@ -"""Google Gemini LLM backend with rolling summary memory.""" +"""Google Gemini LLM backend with rolling summary memory and Google Search grounding.""" import logging from typing import Optional -import google.generativeai as genai +from google import genai +from google.genai import types from ..config import LLMConfig from ..memory import RollingSummaryMemory @@ -23,7 +24,7 @@ Summary (2-3 sentences):""" class GoogleBackend(LLMBackend): - """Google Gemini backend with rolling summary memory.""" + """Google Gemini backend with rolling summary memory and optional grounding.""" def __init__( self, @@ -32,19 +33,9 @@ class GoogleBackend(LLMBackend): window_size: int = 4, summarize_threshold: int = 8, ): - """Initialize Google backend. - - Args: - config: LLM configuration - api_key: Google API key - window_size: Recent message pairs to keep in full - summarize_threshold: Messages before re-summarizing - """ self.config = config - genai.configure(api_key=api_key) - self._model = genai.GenerativeModel(config.model) + self._client = genai.Client(api_key=api_key) - # Initialize rolling summary memory with Gemini summarize function self._memory = RollingSummaryMemory( summarize_fn=self._summarize_messages, window_size=window_size, @@ -52,7 +43,7 @@ class GoogleBackend(LLMBackend): ) async def _summarize_messages(self, messages: list[dict]) -> str: - """Summarize messages using Google Gemini API.""" + """Summarize messages using Gemini.""" if not messages: return "No previous conversation." @@ -62,9 +53,10 @@ class GoogleBackend(LLMBackend): prompt = _SUMMARIZE_PROMPT.format(conversation=conversation) try: - response = await self._model.generate_content_async( - prompt, - generation_config=genai.types.GenerationConfig( + response = await self._client.aio.models.generate_content( + model=self.config.model, + contents=prompt, + config=types.GenerateContentConfig( max_output_tokens=150, temperature=0.3, ), @@ -81,18 +73,7 @@ class GoogleBackend(LLMBackend): max_tokens: int = 300, user_id: Optional[str] = None, ) -> str: - """Generate a response using Google Gemini API. - - Args: - messages: Conversation history - system_prompt: System prompt - max_tokens: Maximum tokens to generate - user_id: User identifier (enables memory optimization) - - Returns: - Generated response - """ - # Use memory manager to optimize context if user_id provided + """Generate a response using Google Gemini with optional grounding.""" enhanced_system = system_prompt final_messages = messages @@ -101,43 +82,40 @@ class GoogleBackend(LLMBackend): user_id=user_id, full_history=messages, ) - if summary: enhanced_system = f"{system_prompt}\n\nPrevious conversation summary: {summary}" final_messages = recent_messages - logger.debug( f"Using summary + {len(recent_messages)} recent messages " f"(total history: {len(messages)})" ) try: - # Create model with system instruction for persistent system prompt - model = genai.GenerativeModel( - self.config.model, + contents = [] + for msg in final_messages: + role = "model" if msg["role"] == "assistant" else "user" + contents.append( + types.Content( + role=role, + parts=[types.Part.from_text(text=msg["content"])], + ) + ) + + tools = [] + if self.config.google_grounding: + tools.append(types.Tool(google_search=types.GoogleSearch())) + + config = types.GenerateContentConfig( system_instruction=enhanced_system if enhanced_system else None, + max_output_tokens=max_tokens, + temperature=0.7, + tools=tools if tools else None, ) - # Convert messages to Gemini format - # Gemini uses "user" and "model" roles - history = [] - for msg in final_messages[:-1]: # All but last message - role = "model" if msg["role"] == "assistant" else "user" - history.append({"role": role, "parts": [msg["content"]]}) - - # Start chat with history - chat = model.start_chat(history=history) - - # Get the last user message - last_message = final_messages[-1]["content"] if final_messages else "" - - # Generate response - response = await chat.send_message_async( - last_message, - generation_config=genai.types.GenerationConfig( - max_output_tokens=max_tokens, - temperature=0.7, - ), + response = await self._client.aio.models.generate_content( + model=self.config.model, + contents=contents, + config=config, ) return response.text.strip() if response.text else "" @@ -147,9 +125,7 @@ class GoogleBackend(LLMBackend): raise def get_memory(self) -> RollingSummaryMemory: - """Get the memory manager instance.""" return self._memory async def close(self) -> None: - """Clean up - nothing to close for Google client.""" pass diff --git a/meshai/cli/configurator.py b/meshai/cli/configurator.py index e573d64..66e2354 100644 --- a/meshai/cli/configurator.py +++ b/meshai/cli/configurator.py @@ -261,6 +261,7 @@ class Configurator: table.add_row("5", "System Prompt", f"[dim]{len(self.config.llm.system_prompt)} chars[/dim]") table.add_row("6", "Use System Prompt", self._status_icon(self.config.llm.use_system_prompt)) table.add_row("7", "Web Search", self._status_icon(self.config.llm.web_search)) + table.add_row("8", "Google Grounding", self._status_icon(self.config.llm.google_grounding)) table.add_row("0", "Back", "") console.print(table) @@ -311,6 +312,13 @@ class Configurator: elif choice == 7: self.config.llm.web_search = not self.config.llm.web_search self.modified = True + elif choice == 8: + if self.config.llm.backend == "google": + self.config.llm.google_grounding = not self.config.llm.google_grounding + self.modified = True + else: + console.print("[yellow]Google grounding is only available with the google backend.[/yellow]") + input("Press Enter to continue...") def _weather_settings(self) -> None: """Weather settings submenu.""" diff --git a/meshai/config.py b/meshai/config.py index 7ce007a..999d9df 100644 --- a/meshai/config.py +++ b/meshai/config.py @@ -90,6 +90,7 @@ class LLMConfig: ) use_system_prompt: bool = True # Toggle to disable sending system prompt web_search: bool = False # Enable web search (Open WebUI feature) + google_grounding: bool = False # Enable Google Search grounding (Gemini only) @dataclass diff --git a/pyproject.toml b/pyproject.toml index 080e948..837dcee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ dependencies = [ "aiosqlite>=0.19.0", "openai>=1.0.0", "anthropic>=0.18.0", - "google-generativeai>=0.4.0", + "google-genai>=1.0.0", "rich>=13.0.0", "httpx>=0.25.0", ] diff --git a/requirements.txt b/requirements.txt index b29c1a2..5469ec4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,6 @@ pyyaml>=6.0 aiosqlite>=0.19.0 openai>=1.0.0 anthropic>=0.18.0 -google-generativeai>=0.4.0 +google-genai>=1.0.0 rich>=13.0.0 httpx>=0.25.0