mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 15:14:45 +02:00
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:
parent
94d27a634e
commit
6e2d956be6
7 changed files with 45 additions and 58 deletions
|
|
@ -58,6 +58,7 @@ llm:
|
||||||
You are a helpful assistant on a Meshtastic mesh network.
|
You are a helpful assistant on a Meshtastic mesh network.
|
||||||
Keep responses VERY brief - under 250 characters total.
|
Keep responses VERY brief - under 250 characters total.
|
||||||
Be concise but friendly. No markdown formatting.
|
Be concise but friendly. No markdown formatting.
|
||||||
|
google_grounding: false # Enable Google Search grounding (Gemini only, $35/1k queries)
|
||||||
|
|
||||||
# === WEATHER ===
|
# === WEATHER ===
|
||||||
weather:
|
weather:
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ llm:
|
||||||
You are a helpful assistant on a Meshtastic mesh network.
|
You are a helpful assistant on a Meshtastic mesh network.
|
||||||
Keep responses VERY brief - under 250 characters total.
|
Keep responses VERY brief - under 250 characters total.
|
||||||
Be concise but friendly. No markdown formatting.
|
Be concise but friendly. No markdown formatting.
|
||||||
|
google_grounding: false
|
||||||
EOF
|
EOF
|
||||||
echo "Default config created. Configure via http://localhost:7682"
|
echo "Default config created. Configure via http://localhost:7682"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -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
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import google.generativeai as genai
|
from google import genai
|
||||||
|
from google.genai import types
|
||||||
|
|
||||||
from ..config import LLMConfig
|
from ..config import LLMConfig
|
||||||
from ..memory import RollingSummaryMemory
|
from ..memory import RollingSummaryMemory
|
||||||
|
|
@ -23,7 +24,7 @@ Summary (2-3 sentences):"""
|
||||||
|
|
||||||
|
|
||||||
class GoogleBackend(LLMBackend):
|
class GoogleBackend(LLMBackend):
|
||||||
"""Google Gemini backend with rolling summary memory."""
|
"""Google Gemini backend with rolling summary memory and optional grounding."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -32,19 +33,9 @@ class GoogleBackend(LLMBackend):
|
||||||
window_size: int = 4,
|
window_size: int = 4,
|
||||||
summarize_threshold: int = 8,
|
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
|
self.config = config
|
||||||
genai.configure(api_key=api_key)
|
self._client = genai.Client(api_key=api_key)
|
||||||
self._model = genai.GenerativeModel(config.model)
|
|
||||||
|
|
||||||
# Initialize rolling summary memory with Gemini summarize function
|
|
||||||
self._memory = RollingSummaryMemory(
|
self._memory = RollingSummaryMemory(
|
||||||
summarize_fn=self._summarize_messages,
|
summarize_fn=self._summarize_messages,
|
||||||
window_size=window_size,
|
window_size=window_size,
|
||||||
|
|
@ -52,7 +43,7 @@ class GoogleBackend(LLMBackend):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _summarize_messages(self, messages: list[dict]) -> str:
|
async def _summarize_messages(self, messages: list[dict]) -> str:
|
||||||
"""Summarize messages using Google Gemini API."""
|
"""Summarize messages using Gemini."""
|
||||||
if not messages:
|
if not messages:
|
||||||
return "No previous conversation."
|
return "No previous conversation."
|
||||||
|
|
||||||
|
|
@ -62,9 +53,10 @@ class GoogleBackend(LLMBackend):
|
||||||
prompt = _SUMMARIZE_PROMPT.format(conversation=conversation)
|
prompt = _SUMMARIZE_PROMPT.format(conversation=conversation)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await self._model.generate_content_async(
|
response = await self._client.aio.models.generate_content(
|
||||||
prompt,
|
model=self.config.model,
|
||||||
generation_config=genai.types.GenerationConfig(
|
contents=prompt,
|
||||||
|
config=types.GenerateContentConfig(
|
||||||
max_output_tokens=150,
|
max_output_tokens=150,
|
||||||
temperature=0.3,
|
temperature=0.3,
|
||||||
),
|
),
|
||||||
|
|
@ -81,18 +73,7 @@ class GoogleBackend(LLMBackend):
|
||||||
max_tokens: int = 300,
|
max_tokens: int = 300,
|
||||||
user_id: Optional[str] = None,
|
user_id: Optional[str] = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Generate a response using Google Gemini API.
|
"""Generate a response using Google Gemini with optional grounding."""
|
||||||
|
|
||||||
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
|
|
||||||
enhanced_system = system_prompt
|
enhanced_system = system_prompt
|
||||||
final_messages = messages
|
final_messages = messages
|
||||||
|
|
||||||
|
|
@ -101,43 +82,40 @@ class GoogleBackend(LLMBackend):
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
full_history=messages,
|
full_history=messages,
|
||||||
)
|
)
|
||||||
|
|
||||||
if summary:
|
if summary:
|
||||||
enhanced_system = f"{system_prompt}\n\nPrevious conversation summary: {summary}"
|
enhanced_system = f"{system_prompt}\n\nPrevious conversation summary: {summary}"
|
||||||
final_messages = recent_messages
|
final_messages = recent_messages
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Using summary + {len(recent_messages)} recent messages "
|
f"Using summary + {len(recent_messages)} recent messages "
|
||||||
f"(total history: {len(messages)})"
|
f"(total history: {len(messages)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create model with system instruction for persistent system prompt
|
contents = []
|
||||||
model = genai.GenerativeModel(
|
for msg in final_messages:
|
||||||
self.config.model,
|
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,
|
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
|
response = await self._client.aio.models.generate_content(
|
||||||
# Gemini uses "user" and "model" roles
|
model=self.config.model,
|
||||||
history = []
|
contents=contents,
|
||||||
for msg in final_messages[:-1]: # All but last message
|
config=config,
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return response.text.strip() if response.text else ""
|
return response.text.strip() if response.text else ""
|
||||||
|
|
@ -147,9 +125,7 @@ class GoogleBackend(LLMBackend):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_memory(self) -> RollingSummaryMemory:
|
def get_memory(self) -> RollingSummaryMemory:
|
||||||
"""Get the memory manager instance."""
|
|
||||||
return self._memory
|
return self._memory
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
"""Clean up - nothing to close for Google client."""
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -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("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("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("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", "")
|
table.add_row("0", "Back", "")
|
||||||
|
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
@ -311,6 +312,13 @@ class Configurator:
|
||||||
elif choice == 7:
|
elif choice == 7:
|
||||||
self.config.llm.web_search = not self.config.llm.web_search
|
self.config.llm.web_search = not self.config.llm.web_search
|
||||||
self.modified = True
|
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:
|
def _weather_settings(self) -> None:
|
||||||
"""Weather settings submenu."""
|
"""Weather settings submenu."""
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class LLMConfig:
|
||||||
)
|
)
|
||||||
use_system_prompt: bool = True # Toggle to disable sending system prompt
|
use_system_prompt: bool = True # Toggle to disable sending system prompt
|
||||||
web_search: bool = False # Enable web search (Open WebUI feature)
|
web_search: bool = False # Enable web search (Open WebUI feature)
|
||||||
|
google_grounding: bool = False # Enable Google Search grounding (Gemini only)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ dependencies = [
|
||||||
"aiosqlite>=0.19.0",
|
"aiosqlite>=0.19.0",
|
||||||
"openai>=1.0.0",
|
"openai>=1.0.0",
|
||||||
"anthropic>=0.18.0",
|
"anthropic>=0.18.0",
|
||||||
"google-generativeai>=0.4.0",
|
"google-genai>=1.0.0",
|
||||||
"rich>=13.0.0",
|
"rich>=13.0.0",
|
||||||
"httpx>=0.25.0",
|
"httpx>=0.25.0",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ pyyaml>=6.0
|
||||||
aiosqlite>=0.19.0
|
aiosqlite>=0.19.0
|
||||||
openai>=1.0.0
|
openai>=1.0.0
|
||||||
anthropic>=0.18.0
|
anthropic>=0.18.0
|
||||||
google-generativeai>=0.4.0
|
google-genai>=1.0.0
|
||||||
rich>=13.0.0
|
rich>=13.0.0
|
||||||
httpx>=0.25.0
|
httpx>=0.25.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue