Optimizely Opal's Custom Tools feature lets you extend the Opal AI agent's capabilities by connecting it to your own services and APIs. In this guide, we'll walk through creating a simple custom tool from scratch, deploying it to the cloud, and integrating it with Opal.
By the end of this tutorial, you'll have a working custom tool that responds to natural language queries in Opal chat. We'll keep it straightforward, no complex integrations, just the essentials you need to get started.
What We're Building
We'll create a simple "greeting" tool that demonstrates the core concepts:
Accepting parameters from Opal
Processing requests
Returning formatted responses
Proper tool registration and discovery
Once you understand these basics, you can extend the pattern to build more sophisticated tools.
Prerequisites
Python 3.12 or higher (critical - the Opal SDK requires 3.12+)
Basic Python knowledge
A Railway account (free tier works fine)
An Optimizely account with Opal access
Part 1: Setting Up Your Local Environment
Create Your Project Directory
Set Up a Virtual Environment
Python version matters here. The Opal Tools SDK uses syntax that requires Python 3.12+:
Install the Opal Tools SDK
Note: While you install it as optimizely-opal.opal-tools-sdk, you'll import it in code as opal_tools_sdk. Here we also install uvicorn which is the ASGI server needed to run FastAPI applications.
Create Your Requirements File
Part 2: Building the Python Script
Create a file called main.py:
Key Concepts in the Code
ToolsService: This automatically creates the /discovery endpoint that Opal uses to learn about your tool
@tool decorator: Registers your function as an Opal tool with a name and description
Pydantic BaseModel: Defines the parameters your tool accepts (with type validation)
Return value: Can be a dictionary, string, or more complex objects
Part 3: Testing Locally
Before deploying, let's make sure everything works.
Run Your Service
Test Your Tool Endpoint
Using curl or a tool like Postman:
If both tests pass, you're ready to deploy!
Part 4: Deploying to Railway
Railway makes deployment straightforward. Here's the process:
Create a Railway Account
Go to railway.com
Sign up (GitHub auth is easiest)
The free tier provides everything you need for testing
Install Railway CLI
You'll be prompted to:
Create a new project or select existing
Choose a name for your project
Configure for Deployment
Railway needs two configuration files to deploy your Python app properly.
1. Create runtime.txt
Create a file called runtime.txt in your project root to specify your Python version:
2. Create Procfile
Create a file called Procfile (no file extension) in your project root. Inside the Procfile, add this single line:
Your project structure should now look like this:
This tells Railway:
Which Python version to use (runtime.txt)
How to start your application (Procfile)
What dependencies to install (requirements.txt - Railway detects this automatically)
The $PORT environment variable in the Procfile is automatically provided by Railway.
Note: Both files must be named exactly as shown - Procfile with a capital P and runtime.txt in lowercase, both with no extra file extensions.
Deploy
That's it! Railway will:
Detect it's a Python project
Install dependencies from requirements.txt
Start your service using the Procfile
Provide you with a public URL
Get Your Service URL
This creates a public domain for your service. Copy this URL - you'll need it for Optimizely.
Your tool is now live at: https://your-project.railway.app
Part 5: Registering Your Tool in Optimizely
Navigate to Opal Settings
Log into Optimizely
Go to Opal → Tools
Click "Add Tool Registry"
Configure Your Tool
Tool Name: Give it a friendly name (e.g., "Greeting Tool")
Discovery URL: Your Railway URL + /discovery
Example: https://my-opal-tool.railway.app/discovery
Bearer Token (Optional): None (for this basic example)
Save and Sync
After saving, you'll see a "Sync" that can be found under Actions in the Registries list view. Click it. This is critical - Optimizely queries your /discovery endpoint to learn about your tool's capabilities.
Important: It doesn’t seem like the sync happens automatically so every time you redeploy your tool with changes, you should manually sync in Optimizely for it to pick up the updates.
Part 6: Critical Concept - Tool Sync
This is a common gotcha: Optimizely doesn't seem to automatically detect changes to your tool.
The Sync Workflow
When to sync:
✅ After initial deployment
✅ After changing tool parameters
✅ After adding new tools
✅ After modifying descriptions or names
✅ After fixing bugs that affect the tool's interface
You don't need to sync:
❌ For internal logic changes that don't affect the tool's signature
❌ For changes that only affect response content (not structure)
How to Sync
Go to Opal → Tools → Registries
Find your tool in the list
Click the … and then the "Sync" button
Wait for confirmation (usually a few seconds)
Think of sync as "hey Optimizely, go check what my tool can do now."
Part 7: Setting Up Your Custom Agent
Now let's make Opal aware of your tool.
Create a Custom Agent
Navigate to Opal → Agents
Click "Add Agent"
Choose “Specialized Agent”
Configure your agent:
Name: Something descriptive (e.g., "Greeting Assistant")
Id: This is a unique id for your agent
Description: What your agent does
Prompt Template: In the INPUT section, add instructions for how the agent should use your tool
Tools: Enable your "Greeting Tool"
Example System Prompt
Enable for Chat
In the Tools list, toggle "Enabled in Chat"
Part 8: Testing in Opal Chat
Go to Opal Chat and try it out:
User: "Hi, my name is Jason. Can you greet me enthusiastically?"
Opal will:
Understand the request
Call your greet_user tool with name="Jason" and style="enthusiastic"
Return the greeting in conversation
You should see something like:
OMG Jason!!! This is SO exciting!!! 🎉
Common Issues and Solutions
Issue: "Module not found" errors
Solution: Make sure you're using Python 3.12+. The SDK uses newer Python syntax.
Issue: Railway deployment fails
Solution: Check that your Procfile is in the root directory and requirements.txt includes all dependencies.
Issue: Optimizely shows "Tool unavailable"
Solution:
Verify your Railway app is running (railway status)
Check that your discovery endpoint is accessible
Click "Sync" in Optimizely
Issue: Changes not appearing in Opal
Solution: Did you sync? Always sync after redeployment.
Issue: Tool not being called by Opal
Solution:
Check your agent's system prompt - does it clearly explain when to use the tool?
Verify the tool is enabled in your agent's configuration
Try being more explicit in your prompt to Opal
Best Practices
1. Descriptive Tool Names and Descriptions
Opal uses these to decide when to call your tool. Be clear and specific:
2. Use Pydantic Validation
Let Pydantic handle parameter validation:
3. Meaningful Error Responses
Return helpful error messages:
4. Test Locally First
Always test with curl/Postman before deploying. It's much faster to debug locally.
5. Version Your Tools
Consider adding versioning to your tool names if you're planning updates:
This lets you run multiple versions simultaneously during migrations.
What's Next?
Now that you have the basics down, you can extend your tool:
Add authentication: Use @requires_auth for tools that need user credentials
Connect to APIs: Integrate with external services (databases, third-party APIs, etc.)
Return Islands: Use IslandResponse for interactive UI components
Handle complex data: Process files, work with structured data, generate reports
The pattern stays the same:
Define parameters (Pydantic)
Implement logic (Python function)
Return results (dictionary/object)
Deploy → Sync → Test
Conclusion
You've now built and deployed your first Optimizely Opal custom tool! The key steps were:
✅ Set up Python 3.12+ environment
✅ Install Opal Tools SDK
✅ Create tool with @tool decorator
✅ Test locally with discovery and tool endpoints
✅ Deploy to Railway
✅ Register tool in Optimizely
✅ Sync after deployment (don't forget!)
✅ Configure custom agent
✅ Enable and test in chat
The most important lessons:
Python 3.12+ is required for the SDK
Always sync in Optimizely after redeployment
Test locally before deploying to save time
Clear descriptions help Opal use your tool correctly
The possibilities from here are endless. You can connect Opal to your data, automate workflows, integrate with company systems, and build sophisticated AI agents that understand your business context.
Happy building!
Jason Thompson is the CEO and co-founder of 33 Sticks, a boutique analytics company focused on helping businesses make human-centered decisions through data. He regularly speaks on topics related to data literacy and ethical analytics practices and is the co-author of the analytics children’s book ‘A is for Analytics’
.png)

