feature/pi-refactor
1/**
2 * Research Tool - Search the web and synthesize results
3 *
4 * This tool combines web search with LLM synthesis to provide
5 * comprehensive research answers with sources.
6 */
7
8import { Type } from "@sinclair/typebox";
9import type { AgentTool } from "@mariozechner/pi-agent-core";
10import { webSearchTool } from "./websearch.js";
11import { getDefaultModel } from "../config.js";
12import { completeSimple } from "@mariozechner/pi-ai";
13
14export const researchTool: AgentTool = {
15 name: "research",
16 label: "Research",
17 description: "Research a topic by searching the web and synthesizing the results into a comprehensive answer with sources.",
18 parameters: Type.Object({
19 topic: Type.String({ description: "The research topic or question" }),
20 }),
21 execute: async (_toolCallId, params, signal, _onUpdate) => {
22 const { topic } = params as { topic: string };
23
24 if (!topic || topic.trim().length === 0) {
25 return {
26 content: [
27 {
28 type: "text" as const,
29 text: "Error: Research topic cannot be empty.",
30 },
31 ],
32 details: { error: "empty_topic" },
33 };
34 }
35
36 try {
37 // Step 1: Search the web
38 const searchResult = await webSearchTool.execute(
39 "research-search",
40 { query: topic, maxResults: 5 },
41 signal || new AbortController().signal,
42 () => {}
43 );
44
45 // Extract search results text
46 const searchText = searchResult.content
47 .filter((c: any) => c.type === "text")
48 .map((c: any) => c.text)
49 .join("\n\n");
50
51 // Check if we got any results
52 if (searchText.includes("No results found")) {
53 return {
54 content: [
55 {
56 type: "text" as const,
57 text: `I couldn't find relevant web results for "${topic}". The topic might be too specific, or there might be connectivity issues. Please try rephrasing your question or being more general.`,
58 },
59 ],
60 details: {
61 topic,
62 searchResults: 0,
63 },
64 };
65 }
66
67 // Step 2: Synthesize with LLM
68 const model = getDefaultModel();
69
70 const synthesisPrompt = `You are a research assistant. Based on the following web search results, provide a comprehensive and well-structured answer about: "${topic}"
71
72Search Results:
73${searchText}
74
75Please:
761. Synthesize the information into a clear, comprehensive answer
772. Include the most important facts and details
783. Cite sources by mentioning the URLs where relevant
794. Structure your answer with clear sections if appropriate
805. Be objective and factual
81
82Provide your research summary:`;
83
84 const synthesis = await completeSimple(model, {
85 messages: [
86 {
87 role: "user",
88 content: synthesisPrompt,
89 timestamp: Date.now(),
90 },
91 ],
92 });
93
94 const synthesizedText = typeof synthesis.content === "string"
95 ? synthesis.content
96 : synthesis.content
97 .filter((c: any) => c.type === "text")
98 .map((c: any) => c.text)
99 .join("\n");
100
101 if (!synthesizedText.trim()) {
102 return {
103 content: [
104 {
105 type: "text" as const,
106 text: `Research completed for "${topic}", but synthesis produced no output. Raw search results:\n\n${searchText}`,
107 },
108 ],
109 details: {
110 topic,
111 searchResults: searchResult.details?.resultCount || 0,
112 synthesisError: "empty_synthesis",
113 },
114 };
115 }
116
117 // Return synthesized research
118 return {
119 content: [
120 {
121 type: "text" as const,
122 text: synthesizedText.trim(),
123 },
124 ],
125 details: {
126 topic,
127 searchResults: searchResult.details?.resultCount || 0,
128 synthesized: true,
129 },
130 };
131 } catch (error) {
132 console.error("Research error:", error);
133
134 return {
135 content: [
136 {
137 type: "text" as const,
138 text: `Error conducting research on "${topic}": ${error instanceof Error ? error.message : "Unknown error"}`,
139 },
140 ],
141 details: {
142 topic,
143 error: error instanceof Error ? error.message : "unknown_error",
144 },
145 };
146 }
147 },
148};