Commit f232868e3a93

Vincent Demeester <vincent@sbr.pm>
2026-01-14 21:34:56
feat(xmpp-research-bot): use direct Gemini API instead of Vertex AI
Organization policy blocks Gemini 3 models on Vertex AI. Switched to using the direct Gemini Developer API with an API key instead. Changes: - Updated bot.py to accept gemini_api_key parameter - Initialize Gemini client with api_key instead of vertexai=True - Added geminiApiKeyFile option to NixOS module - Added GEMINI_API_KEY environment variable to bot script - Created aomi/gemini-api-key.age secret for the API key - Updated aomi configuration to use the secret This bypasses the organization policy constraint since the Gemini Developer API is a separate service from Vertex AI. Claude models continue using Vertex AI which works fine. Error fixed: "Organization Policy constraint constraints/vertexai.allowedModels violated attempting to use disallowed Gen AI model gemini-3-flash-preview" References: - https://googleapis.github.io/python-genai/ - https://github.com/googleapis/python-genai/issues/1404 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 702d035
Changed files (4)
modules
xmpp-research-bot
systems
modules/xmpp-research-bot/bot.py
@@ -53,9 +53,9 @@ Guidelines:
 
 
 class ResearchBot(slixmpp.ClientXMPP):
-    """XMPP bot that performs research using Claude/Gemini APIs via Vertex AI"""
+    """XMPP bot that performs research using Claude (Vertex AI) and Gemini (Developer API)"""
 
-    def __init__(self, jid, password, owner_jid, project_id, region, inbox_path, commands_path=None):
+    def __init__(self, jid, password, owner_jid, project_id, region, inbox_path, commands_path=None, gemini_api_key=None):
         super().__init__(jid, password)
         self.owner_jid = owner_jid
         self.inbox_path = Path(inbox_path)
@@ -63,22 +63,20 @@ class ResearchBot(slixmpp.ClientXMPP):
         self.region = region
         self.commands_path = commands_path
 
-        # Initialize Vertex AI clients
+        # Initialize Vertex AI client for Claude
         self.anthropic_client = AnthropicVertex(project_id=project_id, region=region)
 
         # Initialize Gemini client if available
-        # Note: Gemini 3 models require the global endpoint
+        # Uses direct Gemini Developer API with API key (not Vertex AI)
         self.gemini_client = None
-        if GEMINI_AVAILABLE:
+        if GEMINI_AVAILABLE and gemini_api_key:
             try:
-                self.gemini_client = genai.Client(
-                    vertexai=True,
-                    project=project_id,
-                    location='global'  # Gemini 3 models only available on global endpoint
-                )
-                log.info("Gemini client initialized successfully (global endpoint)")
+                self.gemini_client = genai.Client(api_key=gemini_api_key)
+                log.info("Gemini client initialized successfully (Developer API)")
             except Exception as e:
                 log.warning(f"Failed to initialize Gemini client: {e}")
+        elif GEMINI_AVAILABLE:
+            log.warning("Gemini API key not provided, Gemini support disabled")
 
         # Load commands configuration
         self.commands = self.load_commands()
@@ -408,6 +406,7 @@ async def main():
     region = os.getenv("VERTEX_REGION", "us-east5")
     inbox_path = os.getenv("INBOX_PATH", "/home/vincent/desktop/org/inbox.org")
     commands_path = os.getenv("COMMANDS_PATH")
+    gemini_api_key = os.getenv("GEMINI_API_KEY")
 
     if not all([jid, password, owner_jid, project_id]):
         log.error("Missing required environment variables:")
@@ -415,7 +414,7 @@ async def main():
         sys.exit(1)
 
     # Create and start bot
-    bot = ResearchBot(jid, password, owner_jid, project_id, region, inbox_path, commands_path)
+    bot = ResearchBot(jid, password, owner_jid, project_id, region, inbox_path, commands_path, gemini_api_key)
 
     log.info("Connecting to XMPP server...")
     bot.connect()
modules/xmpp-research-bot/default.nix
@@ -27,6 +27,9 @@ let
     ${lib.optionalString (cfg.commandsPath != null) ''
       export COMMANDS_PATH="${cfg.commandsPath}"
     ''}
+    ${lib.optionalString (cfg.geminiApiKeyFile != null) ''
+      export GEMINI_API_KEY="$(cat ${cfg.geminiApiKeyFile})"
+    ''}
 
     exec ${pythonEnv}/bin/python3 ${./bot.py}
   '';
@@ -86,6 +89,12 @@ in
       default = null;
       description = "Path to commands.yaml configuration file (optional)";
     };
+
+    geminiApiKeyFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = "Path to file containing Gemini API key (optional, for direct Gemini API)";
+    };
   };
 
   config = lib.mkIf cfg.enable {
systems/aomi/extra.nix
@@ -71,6 +71,12 @@
     owner = "vincent";
     group = "users";
   };
+  age.secrets."gemini-api-key" = {
+    file = ../../secrets/aomi/gemini-api-key.age;
+    mode = "400";
+    owner = "vincent";
+    group = "users";
+  };
 
   # TODO make it an option ? (otherwise I'll add it for all)
   users.users.vincent.linger = true;
@@ -116,12 +122,13 @@
     configFile = "/home/vincent/.config/nixpkgs-automation/branches.conf";
   };
 
-  # XMPP Research Bot (uses Vertex AI with Application Default Credentials)
+  # XMPP Research Bot (uses Vertex AI for Claude, direct API for Gemini)
   services.xmpp-research-bot = {
     enable = true;
     jid = "researchbot@xmpp.sbr.pm";
     ownerJid = "vincent@xmpp.sbr.pm";
     passwordFile = config.age.secrets."xmpp-research-bot-password".path;
+    geminiApiKeyFile = config.age.secrets."gemini-api-key".path;
     vertexProjectId = "itpc-gcp-pnd-pe-eng-claude";
     vertexRegion = "us-east5";
     inboxPath = "/home/vincent/desktop/org/inbox.org";
secrets.nix
@@ -144,6 +144,7 @@ in
   "secrets/demeter/mosquitto-homeassistant-password.age".publicKeys = users ++ [ demeter ];
   "secrets/aion/restic-aix-password.age".publicKeys = users ++ [ aion ];
   "secrets/aomi/xmpp-research-bot-password.age".publicKeys = users ++ [ aomi ];
+  "secrets/aomi/gemini-api-key.age".publicKeys = users ++ [ aomi ];
   "secrets/rhea/restic-aix-password.age".publicKeys = users ++ [ rhea ];
 
   # Harmonia binary cache signing keys