Add Pi Research Agent package
Autonomous research agent with web search, source analysis, and report synthesis. Built on Pi agent core. Available as CLI, programmatic API, and MCP server. Features: - 4 built-in tools: web_search, read_url, save_note, synthesize_report - CLI with TUI and print modes - Programmatic API via ResearchAgent class - MCP server with 3 tools: research, quick_search, read_url - Skills system for custom research behavior - Automatic .env file loading for API keys - Configurable max turns, output formats (markdown/JSON/bullet) - Automatic report saving to research-output/
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Examples
|
||||
|
||||
This directory contains working examples of how to use Pi Research Agent.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before running any example, make sure you have:
|
||||
|
||||
1. Built the project: `npm run build` (from the package root)
|
||||
2. Set up environment variables in a `.env` file or exported in your shell:
|
||||
```bash
|
||||
TAVILY_API_KEY=your-tavily-key
|
||||
OPENROUTER_API_KEY=your-openrouter-key
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### [basic.ts](./basic.ts)
|
||||
|
||||
The simplest example — basic programmatic research.
|
||||
|
||||
```bash
|
||||
npx tsx examples/basic.ts
|
||||
```
|
||||
|
||||
### [custom-model.ts](./custom-model.ts)
|
||||
|
||||
Using a custom LLM model (GPT-4o instead of the default).
|
||||
|
||||
```bash
|
||||
npx tsx examples/custom-model.ts
|
||||
```
|
||||
|
||||
### [custom-skills.ts](./custom-skills.ts)
|
||||
|
||||
Adding custom research skills to specialize the agent's behavior.
|
||||
|
||||
```bash
|
||||
npx tsx examples/custom-skills.ts
|
||||
```
|
||||
|
||||
### [with-env.ts](./with-env.ts)
|
||||
|
||||
Loading API keys from a `.env` file automatically.
|
||||
|
||||
```bash
|
||||
npx tsx examples/with-env.ts
|
||||
```
|
||||
|
||||
### [mcp-client.ts](./mcp-client.ts)
|
||||
|
||||
Connecting to the MCP server programmatically from another agent.
|
||||
|
||||
```bash
|
||||
npx tsx examples/mcp-client.ts
|
||||
```
|
||||
|
||||
## MCP Configuration Files
|
||||
|
||||
### [mcp-config-claude.json](./mcp-config-claude.json)
|
||||
|
||||
Configuration for Claude Desktop. Copy to:
|
||||
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
||||
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
### [mcp-config-cursor.json](./mcp-config-cursor.json)
|
||||
|
||||
Configuration for Cursor. Copy to `.cursor/mcp.json` in your project root.
|
||||
|
||||
## Running Examples
|
||||
|
||||
All examples use `tsx` to run TypeScript files directly. You can run them with:
|
||||
|
||||
```bash
|
||||
# From the package root
|
||||
npx tsx examples/<example-name>.ts
|
||||
|
||||
# Or with a specific environment variable
|
||||
TAVILY_API_KEY=*** OPENROUTER_API_KEY=*** npx tsx examples/basic.ts
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Examples will:
|
||||
1. Connect to Tavily and OpenRouter APIs
|
||||
2. Run the research workflow
|
||||
3. Print progress to the console
|
||||
4. Save results to `./research-output/`
|
||||
5. Display the final report
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you get an error about missing API keys:
|
||||
- Make sure your `.env` file exists in the package root
|
||||
- Or export the variables in your shell before running
|
||||
|
||||
If you get a build error:
|
||||
- Run `npm run build` first
|
||||
- Make sure dependencies are installed: `npm install`
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Basic Research Agent Example
|
||||
*
|
||||
* Demonstrates how to use the ResearchAgent programmatically.
|
||||
*
|
||||
* Run with:
|
||||
* TAVILY_API_KEY=*** \
|
||||
* OPENROUTER_API_KEY=*** \
|
||||
* npx tsx examples/basic.ts
|
||||
*/
|
||||
|
||||
import { ResearchAgent } from "../src/index.ts";
|
||||
|
||||
const agent = new ResearchAgent({
|
||||
maxTurns: 10,
|
||||
});
|
||||
|
||||
agent.subscribe((event) => {
|
||||
switch (event.type) {
|
||||
case "tool_execution_start": {
|
||||
const toolName = event.toolName;
|
||||
const args = event.args as Record<string, unknown>;
|
||||
if (toolName === "web_search") {
|
||||
console.error(`[Searching: ${args.query}]`);
|
||||
} else if (toolName === "read_url") {
|
||||
console.error(`[Reading: ${args.url}]`);
|
||||
} else if (toolName === "save_note") {
|
||||
console.error(`[Saving note: ${args.topic}]`);
|
||||
} else if (toolName === "synthesize_report") {
|
||||
console.error("[Synthesizing report...]");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "message_update": {
|
||||
if (event.assistantMessageEvent.type === "text_delta") {
|
||||
process.stdout.write(event.assistantMessageEvent.delta);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const topic = "Latest developments in quantum error correction 2026";
|
||||
console.log(`Researching: ${topic}\n`);
|
||||
|
||||
const result = await agent.research(topic);
|
||||
|
||||
console.log("\n\n" + "=".repeat(60));
|
||||
console.log(`Research complete!`);
|
||||
console.log(`Turns used: ${result.turnsUsed}`);
|
||||
console.log(`Notes collected: ${result.notes.length}`);
|
||||
console.log("=".repeat(60) + "\n");
|
||||
|
||||
console.log(result.report);
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Custom Model Example
|
||||
*
|
||||
* Demonstrates how to use a custom model with the ResearchAgent.
|
||||
*
|
||||
* Run with:
|
||||
* TAVILY_API_KEY=*** \
|
||||
* OPENROUTER_API_KEY=*** \
|
||||
* npx tsx examples/custom-model.ts
|
||||
*/
|
||||
|
||||
import { getModels } from "@earendil-works/pi-ai";
|
||||
import { ResearchAgent } from "../src/index.ts";
|
||||
|
||||
const models = getModels("openrouter");
|
||||
const gpt4o = models.find((m) => m.id === "openai/gpt-4o");
|
||||
|
||||
if (!gpt4o) {
|
||||
console.error("GPT-4o model not found");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const agent = new ResearchAgent({
|
||||
model: gpt4o,
|
||||
thinkingLevel: "high",
|
||||
maxTurns: 15,
|
||||
});
|
||||
|
||||
agent.subscribe((event) => {
|
||||
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
||||
process.stdout.write(event.assistantMessageEvent.delta);
|
||||
}
|
||||
});
|
||||
|
||||
const topic = "Comparison of renewable energy storage solutions";
|
||||
console.log(`Researching: ${topic}\n`);
|
||||
|
||||
const result = await agent.research(topic);
|
||||
|
||||
console.log("\n\n" + result.report);
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Custom Skills Example
|
||||
*
|
||||
* Demonstrates how to use custom research skills with the ResearchAgent.
|
||||
*
|
||||
* Run with:
|
||||
* TAVILY_API_KEY=*** \
|
||||
* OPENROUTER_API_KEY=*** \
|
||||
* npx tsx examples/custom-skills.ts
|
||||
*/
|
||||
|
||||
import type { ResearchSkill } from "../src/index.ts";
|
||||
import { ResearchAgent } from "../src/index.ts";
|
||||
|
||||
const academicResearchSkill: ResearchSkill = {
|
||||
name: "academic-research",
|
||||
description: "Focus on academic sources and peer-reviewed papers",
|
||||
content: `When conducting research:
|
||||
1. Prioritize academic sources (Google Scholar, arXiv, PubMed)
|
||||
2. Look for peer-reviewed papers and citations
|
||||
3. Check publication dates and author credentials
|
||||
4. Note methodology and sample sizes
|
||||
5. Distinguish between correlation and causation`,
|
||||
maxTurns: 15,
|
||||
};
|
||||
|
||||
const factCheckingSkill: ResearchSkill = {
|
||||
name: "fact-checking",
|
||||
description: "Verify claims with multiple sources",
|
||||
content: `For each major claim:
|
||||
1. Find at least 3 independent sources
|
||||
2. Check for conflicting information
|
||||
3. Note the confidence level appropriately
|
||||
4. Flag any claims that cannot be verified`,
|
||||
};
|
||||
|
||||
const agent = new ResearchAgent({
|
||||
maxTurns: 12,
|
||||
skills: [academicResearchSkill, factCheckingSkill],
|
||||
});
|
||||
|
||||
agent.subscribe((event) => {
|
||||
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
||||
process.stdout.write(event.assistantMessageEvent.delta);
|
||||
}
|
||||
});
|
||||
|
||||
const topic = "Effectiveness of cognitive behavioral therapy for anxiety";
|
||||
console.log(`Researching: ${topic}\n`);
|
||||
|
||||
const result = await agent.research(topic);
|
||||
|
||||
console.log("\n\n" + result.report);
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* MCP Client Example
|
||||
*
|
||||
* Demonstrates how to call the research agent MCP server from another agent.
|
||||
*
|
||||
* Run with:
|
||||
* TAVILY_API_KEY=*** \
|
||||
* OPENROUTER_API_KEY=*** \
|
||||
* npx tsx examples/mcp-client.ts
|
||||
*/
|
||||
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { join } from "node:path";
|
||||
|
||||
const mcpServerPath = join(import.meta.dirname, "../dist/mcp-cli.js");
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command: "node",
|
||||
args: [mcpServerPath],
|
||||
env: {
|
||||
...process.env,
|
||||
},
|
||||
});
|
||||
|
||||
const client = new Client({
|
||||
name: "example-client",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
await client.connect(transport);
|
||||
|
||||
console.log("Connected to research agent MCP server\n");
|
||||
|
||||
const toolsResult = await client.listTools();
|
||||
console.log("Available tools:");
|
||||
for (const tool of toolsResult.tools) {
|
||||
console.log(` - ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
console.log();
|
||||
|
||||
console.log("Starting research on 'Rust async runtime'...\n");
|
||||
|
||||
const result = await client.callTool({
|
||||
name: "quick_search",
|
||||
arguments: {
|
||||
query: "Rust async runtime tokio vs async-std 2026",
|
||||
maxResults: 5,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Search results:");
|
||||
for (const content of result.content) {
|
||||
if (content.type === "text") {
|
||||
console.log(content.text);
|
||||
}
|
||||
}
|
||||
|
||||
await client.close();
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"research-agent": {
|
||||
"command": "node",
|
||||
"args": ["./packages/research-agent/dist/mcp-cli.js"],
|
||||
"env": {
|
||||
"TAVILY_API_KEY": "***",
|
||||
"OPENROUTER_API_KEY": "***",
|
||||
"LLM_MODEL": "minimax/minimax-m3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"research-agent": {
|
||||
"command": "node",
|
||||
"args": ["./node_modules/@earendil-works/pi-research-agent/dist/mcp-cli.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Environment Variables Example
|
||||
*
|
||||
* This example demonstrates how to use .env files with the research agent.
|
||||
*
|
||||
* Setup:
|
||||
* 1. Copy .env.example to .env:
|
||||
* cp packages/research-agent/.env.example packages/research-agent/.env
|
||||
*
|
||||
* 2. Edit .env with your API keys:
|
||||
* TAVILY_API_KEY=***
|
||||
* OPENROUTER_API_KEY=***
|
||||
*
|
||||
* 3. Run the example:
|
||||
* npx tsx examples/with-env.ts
|
||||
*
|
||||
* The agent will automatically load variables from .env file.
|
||||
* Environment variables take precedence over .env file values.
|
||||
*/
|
||||
|
||||
import { ResearchAgent } from "../src/index.ts";
|
||||
|
||||
// No need to manually read process.env - the CLI does this automatically
|
||||
// But when using the programmatic API, you can still pass keys explicitly
|
||||
const agent = new ResearchAgent({
|
||||
maxTurns: 5,
|
||||
// Keys will be read from process.env (loaded from .env by CLI)
|
||||
// Or you can pass them explicitly:
|
||||
// tavilyApiKey: process.env.TAVILY_API_KEY,
|
||||
// llmApiKey: process.env.OPENROUTER_API_KEY,
|
||||
});
|
||||
|
||||
agent.subscribe((event) => {
|
||||
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
||||
process.stdout.write(event.assistantMessageEvent.delta);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Starting research with environment variables from .env file...\n");
|
||||
|
||||
const result = await agent.research("Benefits of morning exercise");
|
||||
|
||||
console.log("\n\n" + "=".repeat(60));
|
||||
console.log(`Research complete!`);
|
||||
console.log(`Turns used: ${result.turnsUsed}`);
|
||||
console.log(`Notes collected: ${result.notes.length}`);
|
||||
console.log("=".repeat(60));
|
||||
Reference in New Issue
Block a user