Commit b1f29cc2d7ff

Vincent Demeester <vincent@sbr.pm>
2026-01-14 17:14:56
feat(xmpp-research-bot): add model selection support
Users can now choose between Claude Opus 4.5 and Sonnet 4.5: Command formats: - /research <query> # Default: Sonnet 4.5 (faster) - /research opus: <query> # Use Opus 4.5 (most intelligent) - /research o: <query> # Shorthand for opus - /research sonnet: <query> # Explicit Sonnet 4.5 - /research s: <query> # Shorthand for sonnet Features: - Model name included in inbox.org properties (:MODEL:) - Updated /help text with model selection examples - Fallback to Sonnet if unknown model specified Models available: - Sonnet 4.5: claude-sonnet-4-5@20250929 (default) - Opus 4.5: claude-opus-4-5@20251101 Future: Could add Gemini support with gemini: prefix Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 3c6ce75
Changed files (1)
modules
xmpp-research-bot
modules/xmpp-research-bot/bot.py
@@ -69,22 +69,35 @@ class ResearchBot(slixmpp.ClientXMPP):
         if body.startswith("/research "):
             query = body[len("/research "):].strip()
             if not query:
-                msg.reply("Usage: /research <your research question>").send()
+                msg.reply("Usage: /research <your research question>\n\nModel selection:\n  opus: or o: - Use Claude Opus 4.5\n  sonnet: or s: - Use Claude Sonnet 4.5 (default)\n\nExample: /research opus: explain quantum computing").send()
                 return
 
+            # Parse model selection (format: "model: query")
+            model = "sonnet"  # default
+            model_name = "Sonnet 4.5"
+
+            if query.startswith("opus:") or query.startswith("o:"):
+                model = "opus"
+                model_name = "Opus 4.5"
+                query = query.split(":", 1)[1].strip()
+            elif query.startswith("sonnet:") or query.startswith("s:"):
+                model = "sonnet"
+                model_name = "Sonnet 4.5"
+                query = query.split(":", 1)[1].strip()
+
             # Acknowledge receipt
-            msg.reply(f"๐Ÿ” Researching: {query}").send()
+            msg.reply(f"๐Ÿ” Researching with {model_name}: {query}").send()
 
             try:
                 # Perform research
-                result = await self.research(query)
+                result = await self.research(query, model=model)
 
                 # Save to inbox
-                await self.save_to_inbox(query, result)
+                await self.save_to_inbox(query, result, model=model_name)
 
                 # Send confirmation
                 msg.reply(f"โœ… Research complete! Saved to inbox.org\n\nPreview:\n{result[:200]}...").send()
-                log.info(f"Research completed for: {query}")
+                log.info(f"Research completed for: {query} (model: {model})")
 
             except Exception as e:
                 log.error(f"Research failed: {e}", exc_info=True)
@@ -93,6 +106,12 @@ class ResearchBot(slixmpp.ClientXMPP):
         elif body == "/help":
             help_text = """Available commands:
 /research <question> - Perform research on a topic
+  Model selection:
+    opus: or o: - Use Claude Opus 4.5 (most intelligent)
+    sonnet: or s: - Use Claude Sonnet 4.5 (default, faster)
+  Examples:
+    /research how does XMPP work?
+    /research opus: analyze the complexity of distributed systems
 /help - Show this help message
 /ping - Check if bot is alive"""
             msg.reply(help_text).send()
@@ -100,13 +119,23 @@ class ResearchBot(slixmpp.ClientXMPP):
         elif body == "/ping":
             msg.reply("๐Ÿค– Pong! Bot is alive.").send()
 
-    async def research(self, query: str) -> str:
+    async def research(self, query: str, model: str = "sonnet") -> str:
         """
         Perform research using Claude API with prompt caching.
 
+        Args:
+            query: Research question to answer
+            model: Model to use - "sonnet" (default) or "opus"
+
         Uses cached system prompt for efficiency.
         """
-        log.info(f"Starting research for: {query}")
+        log.info(f"Starting research for: {query} (model: {model})")
+
+        # Map model names to Vertex AI model IDs
+        model_ids = {
+            "sonnet": "claude-sonnet-4-5@20250929",
+            "opus": "claude-opus-4-5@20251101",
+        }
 
         # System prompt (will be cached)
         system_prompt = [
@@ -130,7 +159,7 @@ Guidelines:
         # Call Claude API via Vertex AI
         # Note: Vertex AI uses different model ID format than direct API
         response = self.client.messages.create(
-            model="claude-sonnet-4-5@20250929",
+            model=model_ids.get(model, model_ids["sonnet"]),
             max_tokens=2000,
             system=system_prompt,
             messages=[{"role": "user", "content": query}],
@@ -149,13 +178,14 @@ Guidelines:
 
         return result
 
-    async def save_to_inbox(self, query: str, result: str):
+    async def save_to_inbox(self, query: str, result: str, model: str = "Sonnet 4.5"):
         """Save research result to inbox.org"""
         timestamp = datetime.now().strftime("%Y-%m-%d %a %H:%M")
 
         entry = f"""* TODO Review research: {query}
 :PROPERTIES:
 :CREATED: [{timestamp}]
+:MODEL: {model}
 :END:
 
 ** Query