@apmantza/greedysearch-pi 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +222 -177
- package/package.json +1 -1
- package/results/2026-03-21_11-35-35_test-query-for-file-writing.json +6 -0
- package/search.mjs +11 -1
- package/skills/greedy-search/SKILL.md +72 -38
package/index.ts
CHANGED
|
@@ -1,177 +1,222 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GreedySearch Pi Extension
|
|
3
|
-
*
|
|
4
|
-
* Adds a `greedy_search` tool to Pi that fans out queries to Perplexity,
|
|
5
|
-
* Bing Copilot, and Google AI in parallel, returning synthesized AI answers.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
1
|
+
/**
|
|
2
|
+
* GreedySearch Pi Extension
|
|
3
|
+
*
|
|
4
|
+
* Adds a `greedy_search` tool to Pi that fans out queries to Perplexity,
|
|
5
|
+
* Bing Copilot, and Google AI in parallel, returning synthesized AI answers.
|
|
6
|
+
*
|
|
7
|
+
* Reports streaming progress as each engine completes.
|
|
8
|
+
* Requires Chrome to be running (or it auto-launches a dedicated instance).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import { Type } from "@sinclair/typebox";
|
|
17
|
+
|
|
18
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
const ALL_ENGINES = ["perplexity", "bing", "google"] as const;
|
|
21
|
+
|
|
22
|
+
function cdpAvailable(): boolean {
|
|
23
|
+
return existsSync(join(__dir, "cdp.mjs"));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runSearch(
|
|
27
|
+
engine: string,
|
|
28
|
+
query: string,
|
|
29
|
+
flags: string[] = [],
|
|
30
|
+
signal?: AbortSignal,
|
|
31
|
+
onProgress?: (engine: string, status: "done" | "error") => void,
|
|
32
|
+
): Promise<Record<string, unknown>> {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const proc = spawn("node", [__dir + "/search.mjs", engine, "--inline", ...flags, query], {
|
|
35
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
36
|
+
});
|
|
37
|
+
let out = "";
|
|
38
|
+
let err = "";
|
|
39
|
+
|
|
40
|
+
const onAbort = () => { proc.kill("SIGTERM"); reject(new Error("Aborted")); };
|
|
41
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
42
|
+
|
|
43
|
+
// Watch stderr for progress events (PROGRESS:engine:done|error)
|
|
44
|
+
proc.stderr.on("data", (d: Buffer) => {
|
|
45
|
+
err += d;
|
|
46
|
+
const lines = d.toString().split("\n");
|
|
47
|
+
for (const line of lines) {
|
|
48
|
+
const match = line.match(/^PROGRESS:(\w+):(done|error)$/);
|
|
49
|
+
if (match && onProgress) {
|
|
50
|
+
onProgress(match[1], match[2] as "done" | "error");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
proc.stdout.on("data", (d: Buffer) => (out += d));
|
|
56
|
+
proc.on("close", (code: number) => {
|
|
57
|
+
signal?.removeEventListener("abort", onAbort);
|
|
58
|
+
if (code !== 0) {
|
|
59
|
+
reject(new Error(err.trim() || `search.mjs exited with code ${code}`));
|
|
60
|
+
} else {
|
|
61
|
+
try {
|
|
62
|
+
resolve(JSON.parse(out.trim()));
|
|
63
|
+
} catch {
|
|
64
|
+
reject(new Error(`Invalid JSON from search.mjs: ${out.slice(0, 200)}`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatResults(engine: string, data: Record<string, unknown>): string {
|
|
72
|
+
const lines: string[] = [];
|
|
73
|
+
|
|
74
|
+
if (engine === "all") {
|
|
75
|
+
// Synthesized output: prefer _synthesis + _sources
|
|
76
|
+
const synthesis = data._synthesis as Record<string, unknown> | undefined;
|
|
77
|
+
const dedupedSources = data._sources as Array<Record<string, unknown>> | undefined;
|
|
78
|
+
if (synthesis?.answer) {
|
|
79
|
+
lines.push("## Synthesis");
|
|
80
|
+
lines.push(String(synthesis.answer));
|
|
81
|
+
if (dedupedSources?.length) {
|
|
82
|
+
lines.push("\n**Top sources by consensus:**");
|
|
83
|
+
for (const s of dedupedSources.slice(0, 6)) {
|
|
84
|
+
const engines = (s.engines as string[]) || [];
|
|
85
|
+
lines.push(`- [${s.title || s.url}](${s.url}) [${engines.length}/3]`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
lines.push("\n---\n*Synthesized from Perplexity, Bing Copilot, and Google AI*");
|
|
89
|
+
return lines.join("\n").trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Standard output: per-engine answers
|
|
93
|
+
for (const [eng, result] of Object.entries(data)) {
|
|
94
|
+
if (eng.startsWith("_")) continue;
|
|
95
|
+
lines.push(`\n## ${eng.charAt(0).toUpperCase() + eng.slice(1)}`);
|
|
96
|
+
const r = result as Record<string, unknown>;
|
|
97
|
+
if (r.error) {
|
|
98
|
+
lines.push(`Error: ${r.error}`);
|
|
99
|
+
} else {
|
|
100
|
+
if (r.answer) lines.push(String(r.answer));
|
|
101
|
+
if (Array.isArray(r.sources) && r.sources.length > 0) {
|
|
102
|
+
lines.push("\nSources:");
|
|
103
|
+
for (const s of r.sources.slice(0, 3)) {
|
|
104
|
+
const src = s as Record<string, string>;
|
|
105
|
+
lines.push(`- [${src.title || src.url}](${src.url})`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
if (data.error) {
|
|
112
|
+
lines.push(`Error: ${data.error}`);
|
|
113
|
+
} else {
|
|
114
|
+
if (data.answer) lines.push(String(data.answer));
|
|
115
|
+
if (Array.isArray(data.sources) && data.sources.length > 0) {
|
|
116
|
+
lines.push("\nSources:");
|
|
117
|
+
for (const s of data.sources.slice(0, 5)) {
|
|
118
|
+
const src = s as Record<string, string>;
|
|
119
|
+
lines.push(`- [${src.title || src.url}](${src.url})`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return lines.join("\n").trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
129
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
130
|
+
if (!cdpAvailable()) {
|
|
131
|
+
ctx.ui.notify(
|
|
132
|
+
"GreedySearch: cdp.mjs missing from package directory — try reinstalling: pi install git:github.com/apmantza/GreedySearch-pi",
|
|
133
|
+
"warning",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
pi.registerTool({
|
|
139
|
+
name: "greedy_search",
|
|
140
|
+
label: "Greedy Search",
|
|
141
|
+
description:
|
|
142
|
+
"Search the web using AI-powered engines (Perplexity, Bing Copilot, Google AI) in parallel. " +
|
|
143
|
+
"Optionally synthesize results with Gemini — deduplicates sources by consensus and returns one grounded answer. " +
|
|
144
|
+
"Reports streaming progress as each engine completes. " +
|
|
145
|
+
"Use for current information, library docs, error messages, best practices, or any question where training data may be stale.",
|
|
146
|
+
promptSnippet: "Multi-engine AI web search with streaming progress",
|
|
147
|
+
parameters: Type.Object({
|
|
148
|
+
query: Type.String({ description: "The search query" }),
|
|
149
|
+
engine: Type.Union(
|
|
150
|
+
[
|
|
151
|
+
Type.Literal("all"),
|
|
152
|
+
Type.Literal("perplexity"),
|
|
153
|
+
Type.Literal("bing"),
|
|
154
|
+
Type.Literal("google"),
|
|
155
|
+
Type.Literal("gemini"),
|
|
156
|
+
Type.Literal("gem"),
|
|
157
|
+
],
|
|
158
|
+
{
|
|
159
|
+
description: 'Engine to use. "all" fans out to Perplexity, Bing, and Google in parallel (default).',
|
|
160
|
+
default: "all",
|
|
161
|
+
},
|
|
162
|
+
),
|
|
163
|
+
synthesize: Type.Optional(Type.Boolean({
|
|
164
|
+
description: 'When true and engine is "all", deduplicates sources across engines and feeds them to Gemini for a single grounded synthesis. Adds ~30s but saves tokens and improves answer quality.',
|
|
165
|
+
default: false,
|
|
166
|
+
})),
|
|
167
|
+
fullAnswer: Type.Optional(Type.Boolean({
|
|
168
|
+
description: 'When true, returns the complete answer instead of a truncated preview (default: false, answers are shortened to ~300 chars to save tokens).',
|
|
169
|
+
default: false,
|
|
170
|
+
})),
|
|
171
|
+
}),
|
|
172
|
+
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
173
|
+
const { query, engine = "all", synthesize = false, fullAnswer = false } = params as {
|
|
174
|
+
query: string; engine: string; synthesize?: boolean; fullAnswer?: boolean;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (!cdpAvailable()) {
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: "cdp.mjs missing — try reinstalling: pi install git:github.com/apmantza/GreedySearch-pi" }],
|
|
180
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const flags: string[] = [];
|
|
185
|
+
if (fullAnswer) flags.push("--full");
|
|
186
|
+
if (synthesize && engine === "all") flags.push("--synthesize");
|
|
187
|
+
|
|
188
|
+
// Track progress for "all" engine mode
|
|
189
|
+
const completed = new Set<string>();
|
|
190
|
+
|
|
191
|
+
const onProgress = (eng: string, status: "done" | "error") => {
|
|
192
|
+
completed.add(eng);
|
|
193
|
+
const parts: string[] = [];
|
|
194
|
+
for (const e of ALL_ENGINES) {
|
|
195
|
+
if (completed.has(e)) parts.push(`✅ ${e} done`);
|
|
196
|
+
else parts.push(`⏳ ${e}`);
|
|
197
|
+
}
|
|
198
|
+
if (synthesize && completed.size >= 3) parts.push("🔄 synthesizing");
|
|
199
|
+
|
|
200
|
+
onUpdate?.({
|
|
201
|
+
content: [{ type: "text", text: `**Searching...** ${parts.join(" · ")}` }],
|
|
202
|
+
details: { _progress: true },
|
|
203
|
+
} as any);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const data = await runSearch(engine, query, flags, signal, engine === "all" ? onProgress : undefined);
|
|
208
|
+
const text = formatResults(engine, data);
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: "text", text: text || "No results returned." }],
|
|
211
|
+
details: { raw: data },
|
|
212
|
+
};
|
|
213
|
+
} catch (e) {
|
|
214
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text", text: `Search failed: ${msg}` }],
|
|
217
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apmantza/greedysearch-pi",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Pi extension: search Perplexity, Bing Copilot, and Google AI in parallel with optional Gemini synthesis — grounded AI answers, not just links",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"query": "test query for file writing",
|
|
3
|
+
"url": "https://www.perplexity.ai/search/test-query-for-file-writing-.FobTp2uSWSg70txH6RthQ",
|
|
4
|
+
"answer": "This message is just a test query and does not trigger any actual file‑writing action on your system. \n\nIf you want, you can describe what kind of file you’d like to write (for example: a text file, Python script, CSV, etc.), and I can give you a concrete example in code that you can run…",
|
|
5
|
+
"sources": []
|
|
6
|
+
}
|
package/search.mjs
CHANGED
|
@@ -405,7 +405,15 @@ async function main() {
|
|
|
405
405
|
// All tabs assigned — run extractors in parallel
|
|
406
406
|
const results = await Promise.allSettled(
|
|
407
407
|
ALL_ENGINES.map((e, i) =>
|
|
408
|
-
runExtractor(ENGINES[e], query, tabs[i], short)
|
|
408
|
+
runExtractor(ENGINES[e], query, tabs[i], short)
|
|
409
|
+
.then(r => {
|
|
410
|
+
process.stderr.write(`PROGRESS:${e}:done\n`);
|
|
411
|
+
return { engine: e, ...r };
|
|
412
|
+
})
|
|
413
|
+
.catch(err => {
|
|
414
|
+
process.stderr.write(`PROGRESS:${e}:error\n`);
|
|
415
|
+
throw err;
|
|
416
|
+
})
|
|
409
417
|
)
|
|
410
418
|
);
|
|
411
419
|
|
|
@@ -424,6 +432,7 @@ async function main() {
|
|
|
424
432
|
|
|
425
433
|
// Synthesize with Gemini if requested
|
|
426
434
|
if (synthesize) {
|
|
435
|
+
process.stderr.write('PROGRESS:synthesis:start\n');
|
|
427
436
|
process.stderr.write('[greedysearch] Synthesizing results with Gemini...\n');
|
|
428
437
|
try {
|
|
429
438
|
const synthesis = await synthesizeWithGemini(query, out);
|
|
@@ -432,6 +441,7 @@ async function main() {
|
|
|
432
441
|
sources: synthesis.sources || [],
|
|
433
442
|
synthesized: true,
|
|
434
443
|
};
|
|
444
|
+
process.stderr.write('PROGRESS:synthesis:done\n');
|
|
435
445
|
} catch (e) {
|
|
436
446
|
process.stderr.write(`[greedysearch] Synthesis failed: ${e.message}\n`);
|
|
437
447
|
out._synthesis = { error: e.message, synthesized: false };
|
|
@@ -1,38 +1,72 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: greedy-search
|
|
3
|
-
description: Multi-engine AI web search — Perplexity, Bing Copilot, Google AI in parallel
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Greedy Search
|
|
7
|
-
|
|
8
|
-
Use
|
|
9
|
-
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
---
|
|
2
|
+
name: greedy-search
|
|
3
|
+
description: Multi-engine AI web search — Perplexity, Bing Copilot, Google AI in parallel with optional Gemini synthesis. Use for high-quality research where training data may be stale or single-engine results are insufficient.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Greedy Search
|
|
7
|
+
|
|
8
|
+
Use `greedy_search` when you need high-quality, multi-perspective answers from the web.
|
|
9
|
+
|
|
10
|
+
## Greedy Search vs Built-in Web Search
|
|
11
|
+
|
|
12
|
+
| | `web_search` | `greedy_search` |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Speed | Instant (~2s) | 15-60s (one engine) / 30-90s (all engines) |
|
|
15
|
+
| Quality | Good for simple lookups | Higher — 3 AI engines cross-verify |
|
|
16
|
+
| Synthesis | Single engine answer | Optional Gemini synthesis (cleanest answer) |
|
|
17
|
+
| Use for | Quick facts, simple questions | Research, decisions, complex topics |
|
|
18
|
+
|
|
19
|
+
**Rule of thumb:** Use `web_search` for quick facts. Use `greedy_search` when the answer matters — architecture decisions, comparing libraries, understanding new releases, debugging tricky errors.
|
|
20
|
+
|
|
21
|
+
## When to Use
|
|
22
|
+
|
|
23
|
+
- **Version-specific changes** — "What changed in React 19?" / "Breaking changes in FastAPI 0.100"
|
|
24
|
+
- **Choosing between tools** — "Prisma vs Drizzle in 2026" / "Best auth library for Next.js 15"
|
|
25
|
+
- **Debugging** — User pastes an error message or stack trace
|
|
26
|
+
- **Research tasks** — When you need to synthesize information from multiple sources
|
|
27
|
+
- **Best practices** — "How to structure a monorepo" / "Auth patterns for SaaS"
|
|
28
|
+
- **Anything where training data might be stale** — 2025+, 2026+, "latest", "current", "still maintained"
|
|
29
|
+
|
|
30
|
+
## Engine Selection
|
|
31
|
+
|
|
32
|
+
```greedy_search({ query: "what changed in React 19", engine: "all" })```
|
|
33
|
+
|
|
34
|
+
| Engine | Latency | Best for |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `all` (default) | 30-90s | Highest confidence — all 3 engines in parallel |
|
|
37
|
+
| `perplexity` | 15-30s | Technical Q&A, code explanations, documentation |
|
|
38
|
+
| `bing` | 15-30s | Recent news, Microsoft ecosystem |
|
|
39
|
+
| `google` | 15-30s | Broad coverage, multiple perspectives |
|
|
40
|
+
| `gemini` | 15-30s | Google's perspective, different training data |
|
|
41
|
+
|
|
42
|
+
Use a single engine when speed matters and the question isn't contentious.
|
|
43
|
+
|
|
44
|
+
## Synthesis Mode
|
|
45
|
+
|
|
46
|
+
For complex research questions, use `synthesize: true` with `engine: "all"`:
|
|
47
|
+
|
|
48
|
+
```greedy_search({ query: "best auth patterns for SaaS in 2026", engine: "all", synthesize: true })```
|
|
49
|
+
|
|
50
|
+
This deduplicates sources across engines and feeds them to Gemini for one clean, synthesized answer. Adds ~30s but produces the highest quality output — ideal for research tasks where you'd otherwise need to parse 3 separate answers.
|
|
51
|
+
|
|
52
|
+
Use synthesis when:
|
|
53
|
+
- You need one definitive answer, not multiple perspectives
|
|
54
|
+
- You're researching a topic to write about or make a decision
|
|
55
|
+
- The question has a lot of noise and you want the signal
|
|
56
|
+
|
|
57
|
+
Skip synthesis when:
|
|
58
|
+
- You want to see where engines disagree (useful for controversial topics)
|
|
59
|
+
- Speed matters
|
|
60
|
+
|
|
61
|
+
## Full vs Short Answers
|
|
62
|
+
|
|
63
|
+
Default mode returns ~300 char summaries to save tokens. Use `fullAnswer: true` when you need the complete response:
|
|
64
|
+
|
|
65
|
+
```greedy_search({ query: "explain the React compiler", engine: "perplexity", fullAnswer: true })```
|
|
66
|
+
|
|
67
|
+
## Interpreting Results
|
|
68
|
+
|
|
69
|
+
- **All 3 agree** → High confidence, present as fact
|
|
70
|
+
- **2 agree, 1 differs** → Likely correct but note the dissent
|
|
71
|
+
- **All differ** → Present the different perspectives to the user
|
|
72
|
+
- **Sources with `[3/3]` or `[2/3]`** → Cited by multiple engines, higher confidence
|