Adding Custom Engines¶
OmniSerp's plugin architecture makes it easy to add new search engine backends.
Implementation Steps¶
1. Create Engine Package¶
Create a new package under client/:
// client/newengine/newengine.go
package newengine
import (
"context"
"fmt"
"os"
"github.com/plexusone/omniserp"
)
type Engine struct {
apiKey string
// other fields
}
func New() (*Engine, error) {
apiKey := os.Getenv("NEWENGINE_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("NEWENGINE_API_KEY required")
}
return &Engine{apiKey: apiKey}, nil
}
func (e *Engine) GetName() string { return "newengine" }
func (e *Engine) GetVersion() string { return "1.0.0" }
func (e *Engine) GetSupportedTools() []string {
return []string{
"google_search",
"google_search_news",
// ... list supported operations
}
}
2. Implement Search Methods¶
Implement all methods from the Engine interface:
func (e *Engine) Search(ctx context.Context, params omniserp.SearchParams) (*omniserp.SearchResult, error) {
// Make API request to your search engine
// Parse response
// Return normalized result
return &omniserp.SearchResult{
Data: result,
Raw: rawResponse,
}, nil
}
func (e *Engine) SearchNews(ctx context.Context, params omniserp.SearchParams) (*omniserp.SearchResult, error) {
// Implementation
}
// ... implement all other interface methods
// For unsupported operations, return an error:
func (e *Engine) SearchLens(ctx context.Context, params omniserp.SearchParams) (*omniserp.SearchResult, error) {
return nil, fmt.Errorf("google_search_lens is not supported by newengine")
}
3. Register in Your Application¶
// In your application code (e.g., cmd/yourapp/main.go)
import (
"github.com/plexusone/omniserp"
"github.com/plexusone/omniserp/client/newengine"
"github.com/plexusone/omniserp/client/serper"
)
func createRegistry() *omniserp.Registry {
registry := omniserp.NewRegistry()
// Register existing engines
if serperEngine, err := serper.New(); err == nil {
registry.Register(serperEngine)
}
// Register new engine
if newEng, err := newengine.New(); err == nil {
registry.Register(newEng)
}
return registry
}
4. Update CLI (Optional)¶
Add the new engine import and registration to cmd/omniserp/main.go.
Full Example¶
Here's a complete example for a hypothetical "CustomSearch" engine:
package customsearch
import (
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/plexusone/omniserp"
)
const (
baseURL = "https://api.customsearch.com"
engineName = "customsearch"
engineVersion = "1.0.0"
)
type Engine struct {
apiKey string
client *http.Client
}
func New() (*Engine, error) {
apiKey := os.Getenv("CUSTOMSEARCH_API_KEY")
if apiKey == "" {
return nil, fmt.Errorf("CUSTOMSEARCH_API_KEY environment variable is required")
}
return &Engine{
apiKey: apiKey,
client: &http.Client{},
}, nil
}
func (e *Engine) GetName() string { return engineName }
func (e *Engine) GetVersion() string { return engineVersion }
func (e *Engine) GetSupportedTools() []string {
return []string{
"google_search",
"google_search_news",
"google_search_images",
}
}
func (e *Engine) Search(ctx context.Context, params omniserp.SearchParams) (*omniserp.SearchResult, error) {
// Build request
url := fmt.Sprintf("%s/search?q=%s&key=%s", baseURL, params.Query, e.apiKey)
resp, err := e.client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &omniserp.SearchResult{
Data: result,
}, nil
}
// Implement other methods...
Best Practices¶
- Environment Variables: Use environment variables for API keys
- Error Messages: Provide clear error messages for missing configuration
- Supported Tools: Only list tools that are actually implemented
- Graceful Failures: Return descriptive errors for unsupported operations
- Thread Safety: Ensure your engine is safe for concurrent use