Skip to main content

Tutorial 33: Slack Bot Integration with ADK

This tutorial has been verified against official Slack Bolt Python SDK (v1.26.0 - verified October 2025), Google ADK patterns, and production deployment best practices.

Estimated Reading Time: 50-60 minutes
Difficulty Level: Intermediate to Advanced
Prerequisites: Tutorial 1-3 (ADK Basics), Python 3.9+, Slack workspace admin access


Table of Contents​

  1. Why Slack + ADK? (Real-World Value)
  2. What You'll Learn
  3. Quick Start (15 Minutes)
  4. Key Mental Models
  5. Understanding the Architecture
  6. Building a Team Support Bot
  7. Advanced Features
  8. Production Deployment
  9. Common Pitfalls & How to Avoid Them
  10. Troubleshooting
  11. Next Steps

Why Slack + ADK? (Real-World Value)​

The Problem You're Solving​

Teams waste 3-4 hours per day switching between tools to answer questions:

  • "What's our vacation policy?"
  • "How do I reset my password?"
  • "Which project should I focus on?"

Developers waste context switching time. Support teams field repetitive questions. Knowledge lives in scattered places.

The ADK Solution​

With Slack + ADK, you build an intelligent bot that lives where your team already works:

Without Bot:
User β†’ Google Docs β†’ Notion β†’ Wiki β†’ Email support team β†’ Wait 4 hours

With Slack Bot:
User: @Support Bot help with expense reports
Bot: (instant response with the exact policy + ticket creation option)

Real-World Learning Gains​

By the end of this tutorial, you'll be able to:

  • βœ… Build intelligent Slack bots that understand context and respond in real-time
  • βœ… Integrate ADK agents with Slack Bolt for production-grade bots
  • βœ… Manage conversation state across threads and DMs
  • βœ… Deploy to Cloud Run safely with secrets and monitoring
  • βœ… Handle 100+ concurrent users without manual scaling
  • βœ… Create tools that execute real business logic (ticket creation, knowledge base search)

Who Should Use This?​

RoleWhy Slack + ADK?
Platform EngineersBuild internal developer tools that feel native to workflows
DevOps TeamsCreate incident response bots that execute runbooks in Slack
Product ManagersDeploy analytics dashboards and decision-making tools
Support TeamsAutomate FAQ responses and ticket triage
HR/People TeamsBuild onboarding bots and policy finders

Why Not Web UI?​

When to choose Slack vs Web UI (Tutorial 30):

FeatureSlack BotWeb UI
SetupEasy (in team's workflow)Requires URL sharing
AdoptionNative (9/10 usage)Low friction (2/10 usage)
ContextRich (user, channel, thread)Limited (just user)
PublicInternal team toolExternal customer-facing
MobileWorks on Slack MobileNeeds responsive design

Use Slack for internal team tools. Use Web UI for customer-facing apps.


What You'll Learn​

By completing this tutorial, you'll understand:

Concepts:

  • How Slack bots integrate with ADK agents
  • Socket Mode (development) vs HTTP Mode (production)
  • Session state and conversation threading
  • Tool integration and execution flows

Skills:

  • Configure Slack apps and OAuth scopes
  • Build event handlers for mentions and DMs
  • Create callable tools that agents execute
  • Deploy to Cloud Run with secrets
  • Monitor and troubleshoot production bots

Code:

  • Working Slack bot with 100+ lines of production code
  • Two callable tools (knowledge base search, ticket creation)
  • Complete test suite (50 tests)
  • Ready-to-deploy Docker configuration

Overview​

What You'll Build​

In this tutorial, you'll build a team support assistant Slack bot:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Team Support Bot (@support-bot) β”‚
β”‚ β”œβ”€ Intelligent responses β”‚
β”‚ β”œβ”€ Knowledge base search (tool) β”‚
β”‚ β”œβ”€ Support ticket creation (tool) β”‚
β”‚ β”œβ”€ Thread-aware conversations β”‚
β”‚ └─ Production deployment ready β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This bot will:

  1. Listen for mentions like @Support Bot how do I reset my password?
  2. Search your knowledge base for relevant articles
  3. Create support tickets when issues need human review
  4. Respond with formatted messages in Slack threads

Architecture: Three Layers​

Layer 1: Slack Events  (Mentions, DMs, Reactions)
↓
Layer 2: Slack Bolt (Routes to handlers, manages sessions)
↓
Layer 3: ADK Agent (LLM, tool calling, decision logic)
↓
Layer 4: Tools (Knowledge base, ticket system)

In this tutorial, you focus on Layers 2-4. We provide the Slack event handlers (Layer 1) as runnable code.


Key Mental Models​

Mental Model 1: Socket Mode vs HTTP Mode​

Understanding the connection model is crucial:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SOCKET MODE (Development) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ Your Server β†’ Slack (WebSocket Connection) β”‚
β”‚ (Keeps persistent connection open) β”‚
β”‚ β”‚
β”‚ βœ… No public URL needed
β”‚ βœ… Works on local machine
β”‚ βœ… Easy development
β”‚ ❌ Only one connection at a time
β”‚ ❌ Not suitable for production
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ HTTP MODE (Production) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ Slack β†’ Your Public HTTPS URL β”‚
β”‚ (HTTP webhooks, stateless) β”‚
β”‚ β”‚
β”‚ βœ… Scales horizontally
β”‚ βœ… Production-grade reliability
β”‚ βœ… Auto-load balancing in Cloud Run
β”‚ ❌ Needs public HTTPS URL
β”‚ ❌ More complex setup
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Decision Rule: Use Socket Mode while learning. Switch to HTTP Mode when deploying to production.

Mental Model 2: Agent Tool Execution​

How does the ADK agent use your tools?

User: "What's the vacation policy?"
↓
Bot Handler (receives @mention)
↓
Sends text to ADK Agent
↓
Agent (with system prompt): "I should use search_knowledge_base"
↓
Calls: search_knowledge_base("vacation policy")
↓
Tool returns: {"status": "success", "article": {...}}
↓
Agent writes response: "Our PTO policy is 15 days per year..."
↓
Bot sends response back to Slack

Key insight: Tools return structured dicts with status, report, and data fields. The agent reads these and decides what to do next.

Mental Model 3: Session State Management​

Conversation history needs to persist across messages:

Thread in Slack:
β”œβ”€ User: "What's our password policy?"
β”‚ Bot: "Here's the password reset guide..."
β”‚
β”œβ”€ User: "How do I request a reset?"
β”‚ Bot: "You need to request via IT..."
β”‚ (Bot remembers previous context!)
β”‚
└─ User: "Create a ticket for me"
Bot: "Done! Ticket TKT-ABC created"

Implementation: Use channel_id + thread_ts as unique session key. Store session state in memory (development) or database (production).


Prerequisites & Setup​

System Requirements​

# Python 3.9 or later
python --version # Should be >= 3.9

# pip (package manager)
pip --version

Required Accounts​

1. Google AI API Key

Get from Google AI Studio

2. Slack Workspace

  • Admin access to create apps
  • Or create a test workspace at slack.com

Quick Start (15 Minutes)​

Learning Approach

We provide a working implementation in tutorial_implementation/tutorial33/ that you can run immediately, then study to understand how it works.

Step 1: Get the Implementation​

cd tutorial_implementation/tutorial33
pwd # You should be in .../adk_training/tutorial_implementation/tutorial33

Step 2: Install and Test​

make setup   # Install dependencies and package
make test # Run 50 tests to verify everything works

Step 3: Configure Slack Tokens​

Go to api.slack.com/apps and create a new app:

  1. Click "Create New App" β†’ "From scratch"

  2. OAuth & Permissions: Add these scopes:

    • app_mentions:read (receive @mentions)
    • chat:write (send messages)
    • channels:history, groups:history, im:history (read messages)
  3. Install to Workspace: Get your Bot Token (starts with xoxb-)

  4. Socket Mode: Enable it and create app-level token (starts with xapp-)

Save these tokens to support_bot/.env:

cp support_bot/.env.example support_bot/.env
# Edit support_bot/.env with your tokens

Step 4: Run the Bot​

make slack-dev

You'll see: βœ… Bot is running! Listening for mentions...

Step 5: Test in Slack​

Try these in any Slack channel or DM:

  • @Support Bot what's the vacation policy?
  • @Support Bot how do I reset my password?
  • @Support Bot I need to file an expense report

The bot will:

  1. Search the knowledge base πŸ”
  2. Find matching articles πŸ“š
  3. Respond with formatted answers βœ…

πŸŽ‰ You're done with Quick Start!


Understanding the Architecture​

Component Diagram​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ SLACK WORKSPACE β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Channels & DMs β”‚ β”‚
β”‚ β”‚ β”œβ”€ @mention events β”‚ β”‚
β”‚ β”‚ β”œβ”€ Message events β”‚ β”‚
β”‚ β”‚ └─ Slash commands β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ Socket Mode (WebSocket) or HTTP Mode
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ BOT SERVER (Python Process) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Slack Bolt App β”‚ β”‚
β”‚ β”‚ β”œβ”€ Event handlers (@app.event) β”‚ β”‚
β”‚ β”‚ β”‚ β”œβ”€ app_mention β”‚ β”‚
β”‚ β”‚ β”‚ └─ message β”‚ β”‚
β”‚ β”‚ β”œβ”€ Command handlers (@app.command) β”‚ β”‚
β”‚ β”‚ └─ Session management β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ (In-Process Call) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Google ADK Agent β”‚ β”‚
β”‚ β”‚ β”œβ”€ Session per thread β”‚ β”‚
β”‚ β”‚ β”œβ”€ Tool calling β”‚ β”‚
β”‚ β”‚ └─ Response streaming β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”‚ HTTPS
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ GEMINI 2.0 FLASH API β”‚
β”‚ β”œβ”€ Conversation understanding β”‚
β”‚ β”œβ”€ Tool calling β”‚
β”‚ └─ Response generation β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Socket Mode vs HTTP Mode​

AspectSocket ModeHTTP Mode
ConnectionWebSocket (persistent)HTTP webhooks
SetupEasy (no public URL)Requires public endpoint
Use CaseDevelopmentProduction
LatencyLow (~50ms)Medium (~100ms)
ReliabilityReconnects automaticallyMust handle retries
DeploymentLocal or any serverCloud Run, Heroku, etc.

Request Flow​

1. User mentions bot: @Support Bot how do I reset my password?

2. Slack sends event to bot via Socket Mode/HTTP:

{
"type": "app_mention",
"user": "U12345",
"text": "<@UBOT123> how do I reset my password?",
"channel": "C67890",
"ts": "1234567890.123456",
"thread_ts": "1234567890.123456"
}

3. Bot handler processes event:

@app.event("app_mention")
def handle_mention(event, say):
# Extract message
text = remove_mention(event["text"])
thread_ts = event.get("thread_ts", event["ts"])

# Get/create session for this thread
session_id = f"{event['channel']}:{thread_ts}"
session = get_or_create_session(session_id)

# Send to ADK agent
response = send_to_agent(session, text)

# Reply in thread
say(text=response, thread_ts=thread_ts)

4. ADK agent processes:

System: You are a support assistant...
User: how do I reset my password?
Agent: To reset your password:
1. Go to account.company.com
2. Click "Forgot Password"
3. Check your email...

5. Response sent back to Slack thread!


Building a Team Support Bot​

Add a real knowledge base tool:

"""Enhanced bot with knowledge base search"""

from google.genai.types import Tool, FunctionDeclaration
import json

# Mock knowledge base (replace with real database/vector store)
KNOWLEDGE_BASE = {
"password_reset": {
"title": "How to Reset Your Password",
"content": """To reset your password:
1. Visit https://account.company.com
2. Click "Forgot Password"
3. Enter your work email
4. Check your email for reset link
5. Create a new strong password (8+ chars, mix of letters/numbers/symbols)

If you don't receive the email within 5 minutes, check your spam folder or contact IT at it-help@company.com.""",
"tags": ["password", "reset", "account", "login"]
},
"expense_report": {
"title": "Filing Expense Reports",
"content": """To file an expense report:
1. Log in to Expensify at https://expensify.company.com
2. Click "New Report"
3. Add expenses with receipts
4. Submit for manager approval
5. Reimbursement within 7 business days

Eligible expenses: Travel, meals (up to $50/day), software subscriptions (pre-approved).

Questions? Email finance@company.com""",
"tags": ["expense", "reimbursement", "finance", "expensify"]
},
"vacation_policy": {
"title": "Vacation and PTO Policy",
"content": """Our PTO policy:
β€’ 15 days PTO per year (prorated for first year)
β€’ 5 sick days per year
β€’ 10 company holidays
β€’ Unlimited unpaid time off (with manager approval)

To request time off:
1. Submit in BambooHR at https://bamboo.company.com
2. Get manager approval
3. Update your Slack status
4. Add to team calendar

Plan ahead for busy periods (Q4, product launches).""",
"tags": ["vacation", "pto", "time off", "leave", "holiday"]
},
"remote_work": {
"title": "Remote Work Policy",
"content": """Remote work options:
β€’ Hybrid: 3 days in office, 2 remote (standard)
β€’ Full remote: Available for approved roles
β€’ Temporary remote: For travel, emergencies (notify manager)

Requirements:
β€’ Reliable internet (50+ Mbps)
β€’ Quiet workspace
β€’ Available during core hours (10am-3pm local time)
β€’ Regular video presence in meetings

Equipment stipend: $500/year for home office setup.""",
"tags": ["remote", "work from home", "hybrid", "wfh"]
},
"it_support": {
"title": "IT Support Contacts",
"content": """IT Support channels:
β€’ Slack: #it-support (fastest, 9am-6pm ET)
β€’ Email: it-help@company.com (24h response)
β€’ Phone: 1-800-IT-HELPS (urgent issues only)
β€’ Portal: https://support.company.com

Common issues:
β€’ VPN: Use Cisco AnyConnect, credentials = AD login
β€’ Printer: Add via System Preferences β†’ Printers
β€’ Software installs: Request in #it-support

Emergency (P0): Call phone number for system outages.""",
"tags": ["IT", "support", "help", "technical", "vpn", "printer"]
}
}

def search_knowledge_base(query: str) -> dict:
"""
Search the company knowledge base.

Args:
query: Search query

Returns:
Dict with matching article or error
"""
query_lower = query.lower()

# Search by tags and content
matches = []
for key, article in KNOWLEDGE_BASE.items():
score = 0

# Check tags
for tag in article["tags"]:
if tag in query_lower:
score += 2

# Check title
if any(word in article["title"].lower() for word in query_lower.split()):
score += 1

# Check content
if any(word in article["content"].lower() for word in query_lower.split()):
score += 0.5

if score > 0:
matches.append((score, article))

if matches:
# Return best match
matches.sort(key=lambda x: x[0], reverse=True)
best_article = matches[0][1]
return {
"found": True,
"title": best_article["title"],
"content": best_article["content"]
}
else:
return {
"found": False,
"message": "I couldn't find a matching article. Try rephrasing or contact support@company.com"
}

# Create agent with knowledge base tool
from google.adk.agents import Agent

agent = Agent(
model="gemini-2.0-flash-exp",
name="support_bot",
instruction="""You are a helpful team support assistant.

Your responsibilities:
- Answer questions using the knowledge base
- Help with company policies and procedures
- Provide IT support guidance
- Be friendly, concise, and professional

Guidelines:
- ALWAYS use search_knowledge_base tool when users ask about:
* Company policies (PTO, remote work, expenses)
* IT support (passwords, VPN, printer, software)
* Procedures and processes
- Format responses clearly with bullet points
- Include relevant links from knowledge base
- Use Slack formatting (*bold*, `code`, > quotes)
- If you can't find info, admit it and suggest contacting the right team

Remember: You're helping employees be productive!""",
tools=[
Tool(
function_declarations=[
FunctionDeclaration(
name="search_knowledge_base",
description="Search the company knowledge base for policies, procedures, and IT support info",
parameters={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query describing what to look for"
}
},
"required": ["query"]
}
)
]
)
],
tool_config={
"function_calling_config": {
"mode": "AUTO"
}
}
)

# Tool execution mapping
TOOLS = {
"search_knowledge_base": search_knowledge_base
}

# Update handler to execute tools
@app.event("app_mention")
def handle_mention(event, say, logger):
"""Handle @mentions with tool calling."""
try:
user = event["user"]
text = event["text"]
channel = event["channel"]
thread_ts = event.get("thread_ts", event["ts"])

# Remove mention
text = re.sub(r'<@[A-Z0-9]+>', '', text).strip()

if not text:
say(text="Hi! How can I help you?", thread_ts=thread_ts)
return

# Call agent directly - ADK handles tool execution automatically
# Agent maintains conversation context and executes tools as needed
full_response = agent(text)

# Format and send
formatted_response = format_slack_message(full_response)
say(text=formatted_response, thread_ts=thread_ts)

except Exception as e:
logger.error(f"Error: {e}")
say(text="Sorry, I encountered an error!", thread_ts=thread_ts)

Test it:

@Support Bot how do I reset my password?

Bot will search the knowledge base and provide the full password reset guide! πŸ”


Feature 2: Rich Slack Blocks​

Use Slack's Block Kit for beautiful messages:

def create_article_blocks(title: str, content: str) -> list:
"""Create rich Slack blocks for knowledge base article."""
return [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"πŸ“š {title}",
"emoji": True
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": content
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "πŸ’‘ Need more help? Contact support@company.com"
}
]
}
]

def create_action_blocks(message: str, actions: list) -> list:
"""Create blocks with action buttons."""
blocks = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": message
}
}
]

if actions:
blocks.append({
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": action["label"],
"emoji": True
},
"value": action["value"],
"action_id": action["action_id"]
}
for action in actions
]
})

return blocks

# Enhanced knowledge base search with blocks
def search_knowledge_base_with_blocks(query: str) -> dict:
"""Search and return formatted Slack blocks."""
result = search_knowledge_base(query)

if result["found"]:
return {
"found": True,
"blocks": create_article_blocks(
result["title"],
result["content"]
)
}
else:
return {
"found": False,
"blocks": create_action_blocks(
result["message"],
actions=[
{
"label": "πŸ“§ Email Support",
"value": "email_support",
"action_id": "email_support"
},
{
"label": "πŸ’¬ Open Ticket",
"value": "open_ticket",
"action_id": "open_ticket"
}
]
)
}

# Update handler to use blocks
@app.event("app_mention")
def handle_mention(event, say, client, logger):
"""Handle mentions with rich blocks."""
# ... (same extraction logic)

# After getting response from agent
# Check if knowledge base was used
if "search_knowledge_base" in full_response: # Simplified check
# Extract query from response
# Call search_knowledge_base_with_blocks
# Send blocks instead of plain text

result = search_knowledge_base_with_blocks(text)

if result["found"]:
say(
blocks=result["blocks"],
thread_ts=thread_ts
)
else:
say(
blocks=result["blocks"],
thread_ts=thread_ts
)
else:
# Regular text response
say(text=formatted_response, thread_ts=thread_ts)

# Handle button clicks
@app.action("email_support")
def handle_email_support(ack, body, say):
"""Handle email support button click."""
ack()

say(
text="πŸ“§ You can email our support team at support@company.com\n\n" +
"We typically respond within 24 hours on business days.",
thread_ts=body["message"]["ts"]
)

@app.action("open_ticket")
def handle_open_ticket(ack, body, say):
"""Handle open ticket button click."""
ack()

# Show modal for ticket creation
client.views_open(
trigger_id=body["trigger_id"],
view={
"type": "modal",
"callback_id": "ticket_modal",
"title": {
"type": "plain_text",
"text": "Create Support Ticket"
},
"submit": {
"type": "plain_text",
"text": "Submit"
},
"blocks": [
{
"type": "input",
"block_id": "subject",
"label": {
"type": "plain_text",
"text": "Subject"
},
"element": {
"type": "plain_text_input",
"action_id": "subject_input"
}
},
{
"type": "input",
"block_id": "description",
"label": {
"type": "plain_text",
"text": "Description"
},
"element": {
"type": "plain_text_input",
"action_id": "description_input",
"multiline": True
}
},
{
"type": "input",
"block_id": "priority",
"label": {
"type": "plain_text",
"text": "Priority"
},
"element": {
"type": "static_select",
"action_id": "priority_select",
"options": [
{
"text": {"type": "plain_text", "text": "Low"},
"value": "low"
},
{
"text": {"type": "plain_text", "text": "Normal"},
"value": "normal"
},
{
"text": {"type": "plain_text", "text": "High"},
"value": "high"
},
{
"text": {"type": "plain_text", "text": "Urgent"},
"value": "urgent"
}
]
}
}
]
}
)

Now your bot sends beautiful formatted messages with buttons! 🎨


Feature 3: Create Support Tickets​

Add ticket creation tool:

import uuid
from datetime import datetime

def create_support_ticket(subject: str, description: str, priority: str = "normal") -> dict:
"""
Create a support ticket.

Args:
subject: Ticket subject
description: Detailed description
priority: Priority level (low, normal, high, urgent)

Returns:
Dict with ticket details
"""
ticket_id = f"TKT-{uuid.uuid4().hex[:8].upper()}"

# Mock ticket creation (replace with real ticketing system API)
ticket = {
"id": ticket_id,
"subject": subject,
"description": description,
"priority": priority,
"status": "Open",
"created_at": datetime.now().isoformat(),
"url": f"https://support.company.com/tickets/{ticket_id}"
}

return ticket

# Add to agent tools
FunctionDeclaration(
name="create_support_ticket",
description="Create a support ticket for issues that need human attention",
parameters={
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "Brief subject line for the ticket"
},
"description": {
"type": "string",
"description": "Detailed description of the issue"
},
"priority": {
"type": "string",
"description": "Priority level",
"enum": ["low", "normal", "high", "urgent"]
}
},
"required": ["subject", "description"]
}
)

# Update TOOLS mapping
TOOLS = {
"search_knowledge_base": search_knowledge_base,
"create_support_ticket": create_support_ticket
}

# Agent instruction update
instruction="""...

When creating tickets:
- Use create_support_ticket for complex issues
- Set priority based on urgency
- Summarize the issue clearly
- Confirm ticket creation with user

..."""

Test it:

@Support Bot my laptop won't connect to VPN, tried everything

Bot creates a ticket and responds:

I've created ticket TKT-A1B2C3D4 for your VPN issue. Our IT team will reach out within 4 hours.

Track it here: https://support.company.com/tickets/TKT-A1B2C3D4

🎫 Ticket created!


Advanced Features​

Feature 1: Context from Slack​

Enrich agent with Slack context:

def get_user_info(user_id: str, client) -> dict:
"""Get user information from Slack."""
try:
response = client.users_info(user=user_id)
user = response["user"]

return {
"name": user["real_name"],
"email": user["profile"].get("email"),
"title": user["profile"].get("title"),
"team": user["profile"].get("team")
}
except Exception:
return {}

def get_channel_info(channel_id: str, client) -> dict:
"""Get channel information."""
try:
response = client.conversations_info(channel=channel_id)
channel = response["channel"]

return {
"name": channel["name"],
"topic": channel.get("topic", {}).get("value"),
"purpose": channel.get("purpose", {}).get("value")
}
except Exception:
return {}

# Enhanced handler with context
@app.event("app_mention")
def handle_mention(event, say, client, logger):
"""Handle mentions with rich context."""
# Get Slack context
user_info = get_user_info(event["user"], client)
channel_info = get_channel_info(event["channel"], client)

# Add context to agent message
context = f"""User context:
- Name: {user_info.get('name', 'Unknown')}
- Email: {user_info.get('email', 'Unknown')}
- Title: {user_info.get('title', 'Unknown')}

Channel context:
- Channel: #{channel_info.get('name', 'Unknown')}
- Topic: {channel_info.get('topic', 'N/A')}

User question: {text}"""

# Send to agent with context - ADK Agent handles execution
response = agent(context)

# ... process response

Agent now knows who's asking and where! 🎯


Feature 2: Scheduled Messages​

Send proactive reminders:

import schedule
import time
from threading import Thread

def send_daily_tip():
"""Send daily productivity tip to #general."""
tips = [
"πŸ’‘ Tip: Use /support command for quick help without @mentioning me!",
"πŸ“š New knowledge base article: Check out our updated remote work policy!",
"⏰ Reminder: Submit your timesheets before end of day Friday!",
"πŸŽ‰ Feature update: I can now create support tickets directly from Slack!"
]

import random
tip = random.choice(tips)

app.client.chat_postMessage(
channel="#general",
text=tip
)

# Schedule daily tips
schedule.every().day.at("10:00").do(send_daily_tip)

def run_schedule():
"""Run scheduled tasks in background thread."""
while True:
schedule.run_pending()
time.sleep(60)

# Start scheduler
scheduler_thread = Thread(target=run_schedule, daemon=True)
scheduler_thread.start()

Feature 3: Analytics & Logging​

Track bot usage:

import logging
from collections import defaultdict
from datetime import datetime

# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("bot.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)

# Usage statistics
stats = defaultdict(int)

@app.event("app_mention")
def handle_mention(event, say, client, logger_obj):
"""Handle mentions with analytics."""
# Log event
logger.info(f"Mention from user {event['user']} in channel {event['channel']}")

# Track stats
stats["mentions"] += 1
stats[f"user_{event['user']}"] += 1
stats[f"channel_{event['channel']}"] += 1

# ... process mention

# Log response
logger.info(f"Responded with {len(full_response)} characters")
stats["responses"] += 1

# Stats command
@app.command("/support-stats")
def handle_stats_command(ack, say, command):
"""Show bot usage statistics."""
ack()

# Admin only
if command["user_id"] not in ADMIN_USERS:
say("Sorry, this command is for admins only!")
return

message = f"""πŸ“Š *Support Bot Statistics*

Total mentions: {stats['mentions']}
Total responses: {stats['responses']}
Active users: {len([k for k in stats.keys() if k.startswith('user_')])}
Active channels: {len([k for k in stats.keys() if k.startswith('channel_')])}

Top users:
{get_top_users(stats, 5)}

Top channels:
{get_top_channels(stats, 5)}
"""

say(text=message)

def get_top_users(stats, n=5):
"""Get top N users by interaction count."""
user_stats = {k: v for k, v in stats.items() if k.startswith("user_")}
sorted_users = sorted(user_stats.items(), key=lambda x: x[1], reverse=True)[:n]

return "\n".join([
f"{i+1}. <@{user.replace('user_', '')}> - {count} interactions"
for i, (user, count) in enumerate(sorted_users)
])

Production Deployment​

Step 1: Update Bot for HTTP Mode

"""Production bot with HTTP mode"""

import os
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler
from flask import Flask, request

# Initialize Slack app (no Socket Mode)
app = App(
token=os.environ.get("SLACK_BOT_TOKEN"),
signing_secret=os.environ.get("SLACK_SIGNING_SECRET")
)

# ... (all your handlers)

# Flask app for HTTP endpoint
flask_app = Flask(__name__)
handler = SlackRequestHandler(app)

@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
"""Handle Slack events via HTTP."""
return handler.handle(request)

@flask_app.route("/health", methods=["GET"])
def health():
"""Health check endpoint."""
return {"status": "healthy"}, 200

# Run Flask server
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
flask_app.run(host="0.0.0.0", port=port)

Step 2: Update Slack App Configuration

  1. Go to Event Subscriptions in Slack app settings
  2. Enable Events
  3. Set Request URL: https://your-app.run.app/slack/events
  4. Slack will verify the URL (make sure bot is running!)
  5. Subscribe to bot events (same as before)

Step 3: Deploy to Cloud Run

Create requirements.txt:

slack-bolt==1.20.0
google-genai==1.41.0
python-dotenv==1.0.0
Flask==3.0.0
schedule==1.2.0

Create Dockerfile:

FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy bot code
COPY bot.py .

# Expose port
EXPOSE 8080

# Health check
HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1

# Run bot
CMD ["python", "bot.py"]

Deploy:

# Deploy to Cloud Run
gcloud run deploy support-bot \
--source=. \
--region=us-central1 \
--allow-unauthenticated \
--set-env-vars="SLACK_BOT_TOKEN=xoxb-...,SLACK_SIGNING_SECRET=...,GOOGLE_API_KEY=..."

# Output:
# Service URL: https://support-bot-abc123.run.app

Step 4: Update Slack Event URL

Go back to Slack app settings β†’ Event Subscriptions β†’ Update URL:

https://support-bot-abc123.run.app/slack/events

βœ… Production bot is live!


Production Best Practices​

1. Rate Limiting

from collections import defaultdict
import time

class RateLimiter:
def __init__(self, max_requests=20, window=60):
self.max_requests = max_requests
self.window = window
self.requests = defaultdict(list)

def is_allowed(self, user_id):
now = time.time()
self.requests[user_id] = [
req_time for req_time in self.requests[user_id]
if now - req_time < self.window
]

if len(self.requests[user_id]) < self.max_requests:
self.requests[user_id].append(now)
return True
return False

rate_limiter = RateLimiter()

@app.event("app_mention")
def handle_mention(event, say):
user_id = event["user"]

if not rate_limiter.is_allowed(user_id):
say(
text="⚠️ You're sending too many requests. Please wait a minute!",
thread_ts=event.get("thread_ts", event["ts"])
)
return

# ... process normally

2. Error Recovery

from functools import wraps
import traceback

def retry_on_error(max_retries=3):
"""Retry decorator for Slack API calls."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed: {e}")
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff
return wrapper
return decorator

@retry_on_error(max_retries=3)
def send_message_with_retry(channel, text, thread_ts):
"""Send message with automatic retry."""
app.client.chat_postMessage(
channel=channel,
text=text,
thread_ts=thread_ts
)

3. Monitoring

from google.cloud import monitoring_v3

def log_metric(metric_name, value):
"""Log to Google Cloud Monitoring."""
if os.getenv("ENVIRONMENT") != "production":
return

client = monitoring_v3.MetricServiceClient()
project_name = f"projects/{os.getenv('GCP_PROJECT')}"

series = monitoring_v3.TimeSeries()
series.metric.type = f"custom.googleapis.com/slack_bot/{metric_name}"

# ... (same as previous tutorials)

client.create_time_series(name=project_name, time_series=[series])

@app.event("app_mention")
def handle_mention(event, say):
start_time = time.time()

# ... process mention

latency = time.time() - start_time
log_metric("response_latency", latency)
log_metric("mentions", 1)

4. Session Cleanup

from datetime import datetime, timedelta

# Clean up old sessions periodically
def cleanup_old_sessions():
"""Remove sessions older than 24 hours."""
cutoff = datetime.now() - timedelta(hours=24)

sessions_to_remove = []
for session_id, session_data in sessions.items():
if session_data.get("created_at", datetime.now()) < cutoff:
sessions_to_remove.append(session_id)

for session_id in sessions_to_remove:
del sessions[session_id]
logger.info(f"Cleaned up session: {session_id}")

# Run cleanup every hour
schedule.every().hour.do(cleanup_old_sessions)

Troubleshooting​

Common Issues​

Issue 1: Bot Not Responding

Symptoms:

  • Mention bot, no response
  • No errors in logs

Solutions:

# Check bot is running
curl https://your-bot.run.app/health

# Check Slack app config
# Event Subscriptions β†’ Request URL should be verified (βœ“)

# Check bot token scopes
# OAuth & Permissions β†’ Verify all scopes are added

# Check event subscriptions
# Event Subscriptions β†’ Verify app_mention, message.im are subscribed

Issue 2: "Verification Failed" Error

Symptoms:

  • Slack says request URL verification failed
  • Events not reaching bot

Solution:

# Make sure bot handles challenge request
@flask_app.route("/slack/events", methods=["POST"])
def slack_events():
# Slack sends challenge on initial setup
if request.json and "challenge" in request.json:
return {"challenge": request.json["challenge"]}

# Normal event handling
return handler.handle(request)

Issue 3: Rate Limit Errors

Symptoms:

  • ratelimited error from Slack API
  • Bot stops responding

Solution:

from slack_sdk.errors import SlackApiError
import time

def send_message_safely(channel, text, thread_ts=None):
"""Send message with rate limit handling."""
max_retries = 5

for attempt in range(max_retries):
try:
app.client.chat_postMessage(
channel=channel,
text=text,
thread_ts=thread_ts
)
return
except SlackApiError as e:
if e.response["error"] == "ratelimited":
# Get retry-after header
retry_after = int(e.response.headers.get("Retry-After", 1))
logger.warning(f"Rate limited, waiting {retry_after}s")
time.sleep(retry_after)
else:
raise

Issue 4: Tools Not Executing

Symptoms:

  • Agent doesn't call functions
  • Generic responses only

Solution:

from google.adk.agents import Agent

# Verify tool registration - pass functions directly
agent = Agent(
model="gemini-2.0-flash-exp",
name="support_bot",
instruction="...",
tools=[search_knowledge_base, create_ticket] # βœ… Functions passed directly
)

# ADK automatically enables AUTO mode for function calling

# Verify tool mapping
TOOLS = {
"search_knowledge_base": search_knowledge_base, # βœ… Name matches function name
"searchKnowledgeBase": search_knowledge_base, # ❌ Wrong name (use snake_case)
}

# Check tool execution logic
for event in response_stream:
if hasattr(event, 'function_calls'): # βœ… Check attribute exists
for fc in event.function_calls:
# Execute tool...

Issue 5: Session State Lost

Symptoms:

  • Bot forgets conversation context
  • Each message treated as new conversation

Solution:

# Use consistent session ID
def get_session_id(channel_id: str, thread_ts: str = None) -> str:
"""Generate consistent session ID."""
# Use thread_ts for threaded conversations
return f"{channel_id}:{thread_ts or 'main'}"

# Verify session is retrieved correctly
session_id = get_session_id(event["channel"], event.get("thread_ts"))

if session_id in sessions:
session = sessions[session_id] # βœ… Reuse session
else:
session = create_new_session() # Create new
sessions[session_id] = session

# Log for debugging
logger.info(f"Using session: {session_id}")

Common Pitfalls & How to Avoid Them​

❌ Pitfall 1: Forgetting to Enable Event Subscriptions​

The Problem: You create the Slack app, install it, but bot never responds to @mentions.

Root Cause: Events aren't subscribed in Slack app settings.

Solution:

Go to: OAuth & Permissions β†’ Event Subscriptions
β–‘ Enable Events
β–‘ Subscribe to bot events:
βœ“ app_mention
βœ“ message.channels
βœ“ message.im

❌ Pitfall 2: Using Wrong Token for Socket Mode​

The Problem:

Error: "invalid_auth"

Root Cause: You used SLACK_BOT_TOKEN instead of SLACK_APP_TOKEN for Socket Mode.

Solution:

  • Socket Mode needs SLACK_APP_TOKEN (starts with xapp-)
  • HTTP webhooks need SLACK_BOT_TOKEN (starts with xoxb-)
  • Both go in .env file

❌ Pitfall 3: Tool Functions Don't Match ADK Format​

The Problem:

Agent: "I should call search_knowledge_base"
Result: ERROR - Tool not found

Root Cause: Tool functions must return {'status': 'success', 'report': '...'} format.

Solution:

def my_tool(param: str) -> Dict[str, Any]:
try:
result = do_something(param)
return {
'status': 'success',
'report': 'Human-readable message',
'data': result # Optional
}
except Exception as e:
return {
'status': 'error',
'error': str(e),
'report': 'Error message for user'
}

❌ Pitfall 4: Session State Lost Between Messages​

The Problem:

User: "What's the vacation policy?"
Bot: "15 days PTO per year..."

User: "How do I request it?"
Bot: "I don't know what you're asking about" 😞

Root Cause: Each message creates a new session instead of reusing the thread session.

Solution:

# βœ… Use thread_ts as part of session key
session_id = f"{channel_id}:{thread_ts}"

# Store conversation in persistent storage
if session_id not in sessions:
sessions[session_id] = []

sessions[session_id].append({
"role": "user",
"content": message_text
})

❌ Pitfall 5: Agent Never Calls Tools​

The Problem:

User: "Search for password policy"
Agent: "I don't have information about password policies"

Root Cause:

  • Tools not properly registered
  • System prompt doesn't encourage tool use
  • Function names don't match tool names

Solution:

# βœ… Register tools correctly
root_agent = Agent(
name="support_bot",
model="gemini-2.5-flash",
tools=[
search_knowledge_base, # βœ… Pass function directly
create_support_ticket
]
)

# βœ… Encourage tool use in instructions
instruction="""
When users ask about policies, use search_knowledge_base.
When they report issues, use create_support_ticket.
Always use tools when relevant!
"""

❌ Pitfall 6: Credentials Leaked in Code​

The Problem:

SLACK_BOT_TOKEN = "xoxb-secret123"  # ❌ Don't do this!

Root Cause: Hardcoding secrets in source code exposes them to git history.

Solution:

# βœ… Always use environment variables
import os
from dotenv import load_dotenv

load_dotenv()
token = os.environ.get("SLACK_BOT_TOKEN")

# Add to .gitignore
echo ".env" >> .gitignore

βœ… Best Practice: Test Locally Before Deploying​

# 1. Test in Socket Mode locally
make slack-dev

# 2. Run full test suite
make slack-test

# 3. Only then deploy to production
make slack-deploy

Next Steps​

You've Mastered Slack + ADK! πŸŽ‰β€‹

You now know how to:

βœ… Build Slack bots with Google ADK
βœ… Handle mentions, DMs, and slash commands
βœ… Create rich Slack blocks and interactive buttons
βœ… Add knowledge base search and ticket creation
βœ… Deploy to production with HTTP mode
βœ… Implement rate limiting, monitoring, and error handling

Continue Learning​

Tutorial 34: Google Cloud Pub/Sub + Event-Driven Agents
Build scalable, event-driven agent architectures

Tutorial 35: AG-UI Deep Dive - Building Custom Components
Master advanced CopilotKit features for web UIs

Tutorial 29: UI Integration Overview
Compare all integration approaches (Slack, Web, Streamlit, etc.)

Additional Resources​


πŸš€ Ready to Code?​

View Working Implementation on GitHub β†’

A complete, tested implementation is available with:

  • βœ… Root agent with tools exported
  • βœ… Knowledge base search tool (with 5 company knowledge articles)
  • βœ… Support ticket creation tool
  • βœ… 50 comprehensive tests (100% passing)
  • βœ… Slack Bolt Socket Mode integration ready
  • βœ… Production-ready structure with Cloud Run deployment

Quick Start:

cd tutorial_implementation/tutorial33
make setup # Install dependencies and package
make test # Run 50 tests
make dev # Start ADK web interface at localhost:8000

Or clone and explore directly:

git clone https://github.com/raphaelmansuy/adk_training.git
cd adk_training/tutorial_implementation/tutorial33
make setup && make test

πŸŽ‰ Tutorial 33 Complete!

Next: Tutorial 34: Google Cloud Pub/Sub Integration


Questions or feedback? Open an issue on the ADK Training Repository.

πŸ’¬ Join the Discussion

Have questions or feedback? Discuss this tutorial with the community on GitHub Discussions.