Migrate Google backend from deprecated google-generativeai to google-genai SDK with grounding support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ubuntu 2026-02-24 04:44:44 +00:00
commit 6e2d956be6
7 changed files with 45 additions and 58 deletions

View file

@ -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:

View file

@ -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

View file

@ -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,
system_instruction=enhanced_system if enhanced_system else None,
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"])],
)
)
# 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"]]})
tools = []
if self.config.google_grounding:
tools.append(types.Tool(google_search=types.GoogleSearch()))
# 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(
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,
)
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

View file

@ -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."""

View file

@ -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

View file

@ -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",
]

View file

@ -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