Skip to content

Commit f929268

Browse files
huaaudioHua
andauthored
feat: Add Perplexity search provider integration (#138)
* feat: Add Perplexity search provider integration - Add PerplexityConfig struct to config package - Add PerplexitySearchProvider implementing SearchProvider interface - Update WebSearchTool to support Perplexity with priority system (Perplexity > Brave > DuckDuckGo) - Update agent loop to pass Perplexity config options - Update config.example.json with Perplexity configuration template - Uses Perplexity's 'sonar' model for web search capabilities * Edit config example * make fmt --------- Co-authored-by: Hua <zhangmikoto@gmail.com>
1 parent da79c20 commit f929268

4 files changed

Lines changed: 101 additions & 5 deletions

File tree

config/config.example.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"enabled": false,
1515
"token": "YOUR_TELEGRAM_BOT_TOKEN",
1616
"proxy": "",
17-
"allow_from": ["YOUR_USER_ID"]
17+
"allow_from": [
18+
"YOUR_USER_ID"
19+
]
1820
},
1921
"discord": {
2022
"enabled": false,
@@ -115,9 +117,15 @@
115117
},
116118
"tools": {
117119
"web": {
118-
"search": {
120+
"brave": {
121+
"enabled": false,
119122
"api_key": "YOUR_BRAVE_API_KEY",
120123
"max_results": 5
124+
},
125+
"perplexity": {
126+
"enabled": false,
127+
"api_key": "pplx-xxx",
128+
"max_results": 5
121129
}
122130
}
123131
},
@@ -133,4 +141,4 @@
133141
"host": "0.0.0.0",
134142
"port": 18790
135143
}
136-
}
144+
}

pkg/agent/loop.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ func createToolRegistry(workspace string, restrict bool, cfg *config.Config, msg
7979
BraveEnabled: cfg.Tools.Web.Brave.Enabled,
8080
DuckDuckGoMaxResults: cfg.Tools.Web.DuckDuckGo.MaxResults,
8181
DuckDuckGoEnabled: cfg.Tools.Web.DuckDuckGo.Enabled,
82+
PerplexityAPIKey: cfg.Tools.Web.Perplexity.APIKey,
83+
PerplexityMaxResults: cfg.Tools.Web.Perplexity.MaxResults,
84+
PerplexityEnabled: cfg.Tools.Web.Perplexity.Enabled,
8285
}); searchTool != nil {
8386
registry.Register(searchTool)
8487
}

pkg/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,16 @@ type DuckDuckGoConfig struct {
206206
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"`
207207
}
208208

209+
type PerplexityConfig struct {
210+
Enabled bool `json:"enabled" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_ENABLED"`
211+
APIKey string `json:"api_key" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_API_KEY"`
212+
MaxResults int `json:"max_results" env:"PICOCLAW_TOOLS_WEB_PERPLEXITY_MAX_RESULTS"`
213+
}
214+
209215
type WebToolsConfig struct {
210216
Brave BraveConfig `json:"brave"`
211217
DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"`
218+
Perplexity PerplexityConfig `json:"perplexity"`
212219
}
213220

214221
type ToolsConfig struct {
@@ -321,6 +328,11 @@ func DefaultConfig() *Config {
321328
Enabled: true,
322329
MaxResults: 5,
323330
},
331+
Perplexity: PerplexityConfig{
332+
Enabled: false,
333+
APIKey: "",
334+
MaxResults: 5,
335+
},
324336
},
325337
},
326338
Heartbeat: HeartbeatConfig{

pkg/tools/web.go

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,71 @@ func stripTags(content string) string {
176176
return re.ReplaceAllString(content, "")
177177
}
178178

179+
type PerplexitySearchProvider struct {
180+
apiKey string
181+
}
182+
183+
func (p *PerplexitySearchProvider) Search(ctx context.Context, query string, count int) (string, error) {
184+
searchURL := "https://api.perplexity.ai/chat/completions"
185+
186+
payload := map[string]interface{}{
187+
"model": "sonar",
188+
"messages": []map[string]string{
189+
{"role": "system", "content": "You are a search assistant. Provide concise search results with titles, URLs, and brief descriptions in the following format:\n1. Title\n URL\n Description\n\nDo not add extra commentary."},
190+
{"role": "user", "content": fmt.Sprintf("Search for: %s. Provide up to %d relevant results.", query, count)},
191+
},
192+
"max_tokens": 1000,
193+
}
194+
195+
payloadBytes, err := json.Marshal(payload)
196+
if err != nil {
197+
return "", fmt.Errorf("failed to marshal request: %w", err)
198+
}
199+
200+
req, err := http.NewRequestWithContext(ctx, "POST", searchURL, strings.NewReader(string(payloadBytes)))
201+
if err != nil {
202+
return "", fmt.Errorf("failed to create request: %w", err)
203+
}
204+
205+
req.Header.Set("Content-Type", "application/json")
206+
req.Header.Set("Authorization", "Bearer "+p.apiKey)
207+
req.Header.Set("User-Agent", userAgent)
208+
209+
client := &http.Client{Timeout: 30 * time.Second}
210+
resp, err := client.Do(req)
211+
if err != nil {
212+
return "", fmt.Errorf("request failed: %w", err)
213+
}
214+
defer resp.Body.Close()
215+
216+
body, err := io.ReadAll(resp.Body)
217+
if err != nil {
218+
return "", fmt.Errorf("failed to read response: %w", err)
219+
}
220+
221+
if resp.StatusCode != http.StatusOK {
222+
return "", fmt.Errorf("Perplexity API error: %s", string(body))
223+
}
224+
225+
var searchResp struct {
226+
Choices []struct {
227+
Message struct {
228+
Content string `json:"content"`
229+
} `json:"message"`
230+
} `json:"choices"`
231+
}
232+
233+
if err := json.Unmarshal(body, &searchResp); err != nil {
234+
return "", fmt.Errorf("failed to parse response: %w", err)
235+
}
236+
237+
if len(searchResp.Choices) == 0 {
238+
return fmt.Sprintf("No results for: %s", query), nil
239+
}
240+
241+
return fmt.Sprintf("Results for: %s (via Perplexity)\n%s", query, searchResp.Choices[0].Message.Content), nil
242+
}
243+
179244
type WebSearchTool struct {
180245
provider SearchProvider
181246
maxResults int
@@ -187,14 +252,22 @@ type WebSearchToolOptions struct {
187252
BraveEnabled bool
188253
DuckDuckGoMaxResults int
189254
DuckDuckGoEnabled bool
255+
PerplexityAPIKey string
256+
PerplexityMaxResults int
257+
PerplexityEnabled bool
190258
}
191259

192260
func NewWebSearchTool(opts WebSearchToolOptions) *WebSearchTool {
193261
var provider SearchProvider
194262
maxResults := 5
195263

196-
// Priority: Brave > DuckDuckGo
197-
if opts.BraveEnabled && opts.BraveAPIKey != "" {
264+
// Priority: Perplexity > Brave > DuckDuckGo
265+
if opts.PerplexityEnabled && opts.PerplexityAPIKey != "" {
266+
provider = &PerplexitySearchProvider{apiKey: opts.PerplexityAPIKey}
267+
if opts.PerplexityMaxResults > 0 {
268+
maxResults = opts.PerplexityMaxResults
269+
}
270+
} else if opts.BraveEnabled && opts.BraveAPIKey != "" {
198271
provider = &BraveSearchProvider{apiKey: opts.BraveAPIKey}
199272
if opts.BraveMaxResults > 0 {
200273
maxResults = opts.BraveMaxResults

0 commit comments

Comments
 (0)