modular-agent-examples 0.0.1
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/chunking-demo.ts +339 -0
- package/cleanup-duplicates.ts +142 -0
- package/data/flower.jpg +0 -0
- package/generative.ts +128 -0
- package/graph/context-example.ts +209 -0
- package/graph/data-pipeline/agents.ts +60 -0
- package/graph/data-pipeline/fetchers.ts +166 -0
- package/graph/data-pipeline/index.ts +282 -0
- package/graph/index.ts +154 -0
- package/graph/map-example.ts +227 -0
- package/graph/metrics-example.ts +238 -0
- package/graph/parallel-example.ts +167 -0
- package/graph/pipeline-example.ts +225 -0
- package/graph/planning-example.ts +406 -0
- package/graph/router-example.ts +226 -0
- package/graph/sequential-example.ts +141 -0
- package/graph/voting-example.ts +159 -0
- package/graph-rag/docker-compose.yaml +14 -0
- package/graph-rag/index.js +99 -0
- package/graph-rag/init-db.sh +7 -0
- package/graph-rag/package.json +15 -0
- package/history-compression-example.ts +163 -0
- package/history-persistence.ts +347 -0
- package/index.ts +175 -0
- package/ingestion-pipeline.ts +353 -0
- package/mcp-airbnb-example.ts +69 -0
- package/mcp-http-example.ts +70 -0
- package/mcp-stdio-example.ts +63 -0
- package/multimodal.ts +144 -0
- package/ollama.ts +148 -0
- package/openai-compatible.ts +141 -0
- package/opensearch-vector-store.ts +342 -0
- package/package.json +24 -0
- package/pubmed.ts +289 -0
- package/reasoning-with-sub-agent.ts +311 -0
- package/synchronous/index.ts +48 -0
- package/tsconfig.json +8 -0
- package/vector-store-filtering.ts +303 -0
- package/vector-store.ts +210 -0
- package/vectorstore/index.ts +0 -0
- package/vectorstore/store/dbService.ts +80 -0
- package/voyage-embeddings.ts +99 -0
- package/weather-with-sub-agent.ts +276 -0
- package/weather.ts +389 -0
package/pubmed.ts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
|
|
3
|
+
import { OpenAiAgent } from "../lib/agents/openai/OpenAiAgent";
|
|
4
|
+
import { Tool } from "../lib/tools/Tool";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import { BaseAgent } from "../lib/agents/BaseAgent";
|
|
7
|
+
import { History } from "../lib/history/History";
|
|
8
|
+
|
|
9
|
+
const rl = createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// PubMed Search Tool
|
|
15
|
+
const pubmedSearchTool = new Tool({
|
|
16
|
+
name: "pubmedSearch",
|
|
17
|
+
description: `This tool searches PubMed for medical research articles based on search terms and returns metadata for relevant papers.
|
|
18
|
+
It accepts search terms and additional parameters, and returns citation data for matching articles.`,
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
query: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description:
|
|
25
|
+
"Search terms to use for finding medical literature. For best results, use specific medical terminology.",
|
|
26
|
+
},
|
|
27
|
+
maxResults: {
|
|
28
|
+
type: "number",
|
|
29
|
+
description:
|
|
30
|
+
"Maximum number of results to return (default: 10, max: 20).",
|
|
31
|
+
},
|
|
32
|
+
sortBy: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description:
|
|
35
|
+
"How to sort results: 'relevance', 'date' (newest first), or 'cited' (most cited). Default is 'relevance'.",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
required: ["query", "maxResults", "sortBy"],
|
|
39
|
+
},
|
|
40
|
+
execute: async (input): Promise<any> => {
|
|
41
|
+
try {
|
|
42
|
+
// Set defaults and enforce limits
|
|
43
|
+
const maxResults = Math.min(input.maxResults || 10, 20);
|
|
44
|
+
const sortBy = input.sortBy || "relevance";
|
|
45
|
+
|
|
46
|
+
// Use PubMed's E-utilities API
|
|
47
|
+
// First, search for IDs matching the query
|
|
48
|
+
const searchUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&retmode=json&term=${encodeURIComponent(
|
|
49
|
+
input.query
|
|
50
|
+
)}&retmax=${maxResults}&sort=${
|
|
51
|
+
sortBy === "date"
|
|
52
|
+
? "pub+date"
|
|
53
|
+
: sortBy === "cited"
|
|
54
|
+
? "most+cited"
|
|
55
|
+
: "relevance"
|
|
56
|
+
}`;
|
|
57
|
+
// console.log(searchUrl);
|
|
58
|
+
const searchResponse = await fetch(searchUrl);
|
|
59
|
+
const searchData = await searchResponse.json();
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
!searchData.esearchresult ||
|
|
63
|
+
!searchData.esearchresult.idlist ||
|
|
64
|
+
searchData.esearchresult.idlist.length === 0
|
|
65
|
+
) {
|
|
66
|
+
return {
|
|
67
|
+
count: 0,
|
|
68
|
+
message: "No results found for your query.",
|
|
69
|
+
articles: [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ids = searchData.esearchresult.idlist;
|
|
74
|
+
|
|
75
|
+
// Then, fetch summary data for these IDs
|
|
76
|
+
const summaryUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&id=${ids.join(
|
|
77
|
+
","
|
|
78
|
+
)}`;
|
|
79
|
+
const summaryResponse = await fetch(summaryUrl);
|
|
80
|
+
const summaryData = await summaryResponse.json();
|
|
81
|
+
|
|
82
|
+
// Process and format results
|
|
83
|
+
const articles = ids
|
|
84
|
+
.map((id: string) => {
|
|
85
|
+
const article = summaryData.result[id];
|
|
86
|
+
if (!article) return null;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
pmid: id,
|
|
90
|
+
title: article.title,
|
|
91
|
+
authors: article.authors
|
|
92
|
+
? article.authors.map((a: any) => `${a.name}`)
|
|
93
|
+
: ["No authors listed"],
|
|
94
|
+
journal:
|
|
95
|
+
article.fulljournalname || article.source || "Unknown journal",
|
|
96
|
+
publicationDate: article.pubdate || "Unknown date",
|
|
97
|
+
// abstract: article.abstract || "No abstract available",
|
|
98
|
+
doi: article.elocationid
|
|
99
|
+
? article.elocationid.replace("doi: ", "")
|
|
100
|
+
: null,
|
|
101
|
+
url: `https://pubmed.ncbi.nlm.nih.gov/${id}/`,
|
|
102
|
+
};
|
|
103
|
+
})
|
|
104
|
+
.filter((a: any) => a !== null);
|
|
105
|
+
// console.log({
|
|
106
|
+
// count: articles.length,
|
|
107
|
+
// query: input.query,
|
|
108
|
+
// articles,
|
|
109
|
+
// });
|
|
110
|
+
return {
|
|
111
|
+
count: articles.length,
|
|
112
|
+
query: input.query,
|
|
113
|
+
articles,
|
|
114
|
+
};
|
|
115
|
+
} catch (error: any) {
|
|
116
|
+
console.error("Error searching PubMed:", error);
|
|
117
|
+
return {
|
|
118
|
+
error: "Failed to search PubMed: " + error.message,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// PubMed Abstract Fetcher Tool
|
|
125
|
+
const pubmedAbstractTool = new Tool({
|
|
126
|
+
name: "pubmedGetAbstract",
|
|
127
|
+
description: `This tool fetches the full abstract for a specific PubMed article by its ID (PMID).`,
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
pmid: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description:
|
|
134
|
+
"PubMed ID (PMID) of the article to retrieve the abstract for.",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
required: ["pmid"],
|
|
138
|
+
},
|
|
139
|
+
execute: async (input): Promise<any> => {
|
|
140
|
+
try {
|
|
141
|
+
const url = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=${input.pmid}&retmode=json&rettype=abstract`;
|
|
142
|
+
const response = await fetch(url);
|
|
143
|
+
|
|
144
|
+
// PubMed efetch returns XML by default, so we'll manually extract the abstract
|
|
145
|
+
const text = await response.text();
|
|
146
|
+
// console.log({
|
|
147
|
+
// pmid: input.pmid,
|
|
148
|
+
// abstract: text,
|
|
149
|
+
// fullText: false, // PubMed doesn't provide full text, only abstracts
|
|
150
|
+
// url: `https://pubmed.ncbi.nlm.nih.gov/${input.pmid}/`,
|
|
151
|
+
// });
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
pmid: input.pmid,
|
|
155
|
+
abstract: text,
|
|
156
|
+
fullText: false, // PubMed doesn't provide full text, only abstracts
|
|
157
|
+
url: `https://pubmed.ncbi.nlm.nih.gov/${input.pmid}/`,
|
|
158
|
+
};
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
return {
|
|
161
|
+
error: "Failed to retrieve abstract: " + error.message,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const history = new History();
|
|
168
|
+
|
|
169
|
+
const medicalResearchAgent = new ClaudeAgent(
|
|
170
|
+
{
|
|
171
|
+
id: "medical-research",
|
|
172
|
+
name: "Medical Research Assistant",
|
|
173
|
+
description: `You are a medical research expert specializing in coming up with novel and in-depth knowledge based on literature research and extensive experience.
|
|
174
|
+
|
|
175
|
+
You have access to PubMed, a database of medical research papers. Your goal is to help users:
|
|
176
|
+
- Find relevant medical research for their questions
|
|
177
|
+
- Summarize findings from medical literature
|
|
178
|
+
- Explain medical concepts from research in clear, understandable terms
|
|
179
|
+
- Identify patterns and consensus across multiple papers
|
|
180
|
+
- Come up with novel strategies and thinking
|
|
181
|
+
- Try to dive deep in the subject and cast a wide net
|
|
182
|
+
- ALWAYS list sources
|
|
183
|
+
|
|
184
|
+
When responding:
|
|
185
|
+
1. Use the pubmedSearch tool to find relevant papers before providing any medical information
|
|
186
|
+
2. ALWAYS cite your sources with PMID numbers and links
|
|
187
|
+
3. Be clear about the strength of evidence (e.g., systematic review vs. single study)
|
|
188
|
+
4. Highlight limitations of studies when appropriate
|
|
189
|
+
|
|
190
|
+
You can use the pubmedGetAbstract tool to read full abstracts for the most relevant papers.`,
|
|
191
|
+
tools: [pubmedSearchTool, pubmedAbstractTool],
|
|
192
|
+
apiKey: process.env.ANTHROPIC_API_KEY as string,
|
|
193
|
+
// Using Opus for better medical knowledge and reasoning
|
|
194
|
+
model: "claude-3-5-haiku-latest",
|
|
195
|
+
maxTokens: 8000,
|
|
196
|
+
temperature: 0.4,
|
|
197
|
+
},
|
|
198
|
+
history
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Alternative using OpenAI
|
|
202
|
+
const openAiMedicalAgent = new OpenAiAgent(
|
|
203
|
+
{
|
|
204
|
+
id: "openai-medical-research",
|
|
205
|
+
name: "Medical Research Assistant",
|
|
206
|
+
description: `You are a medical research expert specializing in coming up with novel and in-depth knowledge based on literature research and extensive experience.
|
|
207
|
+
|
|
208
|
+
You have access to PubMed, a database of medical research papers. Your goal is to help users:
|
|
209
|
+
- Find relevant medical research for their questions
|
|
210
|
+
- Summarize findings from medical literature
|
|
211
|
+
- Explain medical concepts from research in clear, understandable terms
|
|
212
|
+
- Identify patterns and consensus across multiple papers
|
|
213
|
+
- Come up with novel strategies and thinking
|
|
214
|
+
- Try to dive deep in the subject and cast a wide net
|
|
215
|
+
|
|
216
|
+
When responding:
|
|
217
|
+
1. Use the pubmedSearch tool to find relevant papers before providing any medical information
|
|
218
|
+
2. ALWAYS cite your sources with PMID numbers and links
|
|
219
|
+
3. Be clear about the strength of evidence (e.g., systematic review vs. single study)
|
|
220
|
+
4. Highlight limitations of studies when appropriate
|
|
221
|
+
|
|
222
|
+
You can use the pubmedGetAbstract tool to read full abstracts for the most relevant papers.`,
|
|
223
|
+
tools: [pubmedSearchTool, pubmedAbstractTool],
|
|
224
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
225
|
+
model: "gpt-4o",
|
|
226
|
+
},
|
|
227
|
+
history
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
async function runMedicalResearchExample() {
|
|
231
|
+
try {
|
|
232
|
+
let running = true;
|
|
233
|
+
let agent: BaseAgent = medicalResearchAgent; // Default to Claude
|
|
234
|
+
// pubmedSearchTool.on(ToolEvent.EXECUTE, console.log);
|
|
235
|
+
// pubmedSearchTool.on(ToolEvent.RESULT, console.log);
|
|
236
|
+
// pubmedAbstractTool.on(ToolEvent.EXECUTE, console.log);
|
|
237
|
+
// Ask which model to use
|
|
238
|
+
const modelChoice = await rl.question(
|
|
239
|
+
"Which model would you like to use? [1] Claude (default) or [2] OpenAI: "
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (modelChoice === "2") {
|
|
243
|
+
agent = openAiMedicalAgent;
|
|
244
|
+
console.log("Using OpenAI model");
|
|
245
|
+
} else {
|
|
246
|
+
console.log("Using Claude model");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log("\n----- Medical Research Assistant -----");
|
|
250
|
+
console.log(
|
|
251
|
+
"Ask medical research questions and the assistant will search PubMed for you."
|
|
252
|
+
);
|
|
253
|
+
console.log("Type 'exit' to quit.");
|
|
254
|
+
console.log("---------------------------------------\n");
|
|
255
|
+
|
|
256
|
+
while (running) {
|
|
257
|
+
const question = await rl.question(
|
|
258
|
+
"\nWhat medical topic would you like to research? \n"
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (question.toLowerCase() === "exit") {
|
|
262
|
+
running = false;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log("\nSearching medical literature...");
|
|
267
|
+
const result = await agent.execute(question);
|
|
268
|
+
|
|
269
|
+
console.log("\n" + result);
|
|
270
|
+
|
|
271
|
+
// Ask if the user wants to continue
|
|
272
|
+
const continueResponse = await rl.question(
|
|
273
|
+
"\nWould you like to ask another question? (y/n): "
|
|
274
|
+
);
|
|
275
|
+
if (continueResponse.toLowerCase() !== "y") {
|
|
276
|
+
running = false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
rl.close();
|
|
281
|
+
process.exit(0);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
console.error("Error:", error);
|
|
284
|
+
rl.close();
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
runMedicalResearchExample();
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reasoning with Sub-Agent Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates using an agent as a reasoning assistant for another agent.
|
|
5
|
+
* A focused "reasoner" agent breaks down complex questions and analyzes them step-by-step,
|
|
6
|
+
* while a main agent coordinates and provides the final synthesized answer.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* Main Agent (coordinator, no specialized tools)
|
|
10
|
+
* └── Reasoner Agent (specialized in analytical thinking and problem decomposition)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import "dotenv/config";
|
|
14
|
+
import { createInterface } from "node:readline/promises";
|
|
15
|
+
import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
|
|
16
|
+
import { OpenAiAgent } from "../lib/agents/openai/OpenAiAgent";
|
|
17
|
+
import { MistralAgent } from "../lib/agents/mistral/MistralAgent";
|
|
18
|
+
import { BaseAgent } from "../lib/agents/BaseAgent";
|
|
19
|
+
import { Tool } from "../lib/tools/Tool";
|
|
20
|
+
import { AgentEvent } from "../lib/agents/AgentEvent";
|
|
21
|
+
|
|
22
|
+
const rl = createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Reasoning Tools (used by the reasoner sub-agent)
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
const decomposeQuestionTool = new Tool({
|
|
32
|
+
name: "decompose_question",
|
|
33
|
+
description: `Break down a complex question into smaller, manageable sub-questions.
|
|
34
|
+
Returns a list of sub-questions that need to be answered to fully address the main question.`,
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
question: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "The complex question to decompose",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ["question"],
|
|
44
|
+
},
|
|
45
|
+
execute: async (input: { question: string }): Promise<unknown> => {
|
|
46
|
+
// This is a simulated tool - in a real system, this might use LLM or rules
|
|
47
|
+
return {
|
|
48
|
+
originalQuestion: input.question,
|
|
49
|
+
subQuestions: [
|
|
50
|
+
`What are the key concepts involved in: "${input.question}"?`,
|
|
51
|
+
`What assumptions are being made in: "${input.question}"?`,
|
|
52
|
+
`What are the possible approaches to answer: "${input.question}"?`,
|
|
53
|
+
],
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const analyzeAssumptionsTool = new Tool({
|
|
60
|
+
name: "analyze_assumptions",
|
|
61
|
+
description: `Identify and analyze the assumptions underlying a question or statement.
|
|
62
|
+
Returns a list of implicit and explicit assumptions that should be considered.`,
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
statement: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "The statement or question to analyze for assumptions",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ["statement"],
|
|
72
|
+
},
|
|
73
|
+
execute: async (input: { statement: string }): Promise<unknown> => {
|
|
74
|
+
return {
|
|
75
|
+
analyzedStatement: input.statement,
|
|
76
|
+
assumptions: {
|
|
77
|
+
implicit: [
|
|
78
|
+
"The question has a definitive answer",
|
|
79
|
+
"Current information is complete and accurate",
|
|
80
|
+
],
|
|
81
|
+
explicit: ["The context provided is sufficient"],
|
|
82
|
+
needsValidation: ["Check if all terms are clearly defined"],
|
|
83
|
+
},
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const evaluateLogicTool = new Tool({
|
|
90
|
+
name: "evaluate_logic",
|
|
91
|
+
description: `Evaluate the logical structure of an argument or reasoning chain.
|
|
92
|
+
Returns an analysis of logical validity, potential fallacies, and strength of reasoning.`,
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
argument: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "The argument or reasoning to evaluate",
|
|
99
|
+
},
|
|
100
|
+
context: {
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "Additional context for the argument (optional)",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
required: ["argument"],
|
|
106
|
+
},
|
|
107
|
+
execute: async (input: {
|
|
108
|
+
argument: string;
|
|
109
|
+
context?: string;
|
|
110
|
+
}): Promise<unknown> => {
|
|
111
|
+
return {
|
|
112
|
+
argument: input.argument,
|
|
113
|
+
context: input.context || "No additional context provided",
|
|
114
|
+
logicalStructure: {
|
|
115
|
+
isValid: true,
|
|
116
|
+
strength: "moderate",
|
|
117
|
+
potentialFallacies: [],
|
|
118
|
+
recommendations: [
|
|
119
|
+
"Consider alternative perspectives",
|
|
120
|
+
"Verify factual claims",
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// Sub-Agent Creation (the analytical reasoner)
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
132
|
+
const REASONER_DESCRIPTION = `You are an analytical reasoning specialist. Your job is to:
|
|
133
|
+
1. Break down complex questions into manageable parts
|
|
134
|
+
2. Identify assumptions and logical structures
|
|
135
|
+
3. Evaluate different perspectives and approaches
|
|
136
|
+
4. Provide structured analytical insights
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
Be thorough but concise, you only have 2000 tokens. Focus on clarity and logical rigor in a few lines.`;
|
|
140
|
+
|
|
141
|
+
function createReasonerAgent(
|
|
142
|
+
provider: "claude" | "openai" | "mistral"
|
|
143
|
+
): BaseAgent {
|
|
144
|
+
const config = {
|
|
145
|
+
id: "reasoner",
|
|
146
|
+
name: "Analytical Reasoner",
|
|
147
|
+
description: REASONER_DESCRIPTION,
|
|
148
|
+
tools: [],
|
|
149
|
+
maxTokens: 2048,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (provider === "openai") {
|
|
153
|
+
return new OpenAiAgent({
|
|
154
|
+
...config,
|
|
155
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
156
|
+
model: "gpt-4o-mini", // Fast and cost-effective, no reasoning overhead
|
|
157
|
+
// Note: For reasoning models (gpt-5-nano, o1, etc.), either:
|
|
158
|
+
// - Set disableReasoning: true to disable extended thinking
|
|
159
|
+
// - Or increase maxTokens (e.g., 8192) to accommodate reasoning tokens
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (provider === "mistral") {
|
|
164
|
+
return new MistralAgent({
|
|
165
|
+
...config,
|
|
166
|
+
apiKey: process.env.MISTRAL_API_KEY as string,
|
|
167
|
+
model: "mistral-small-latest", // Efficient model for structured reasoning
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return new ClaudeAgent({
|
|
172
|
+
...config,
|
|
173
|
+
apiKey: process.env.ANTHROPIC_API_KEY as string,
|
|
174
|
+
model: "claude-haiku-4-5", // Fast and efficient for analytical tasks
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// =============================================================================
|
|
179
|
+
// Main Agent Creation (the coordinator)
|
|
180
|
+
// =============================================================================
|
|
181
|
+
|
|
182
|
+
const MAIN_AGENT_DESCRIPTION = `You are a thoughtful assistant that helps users reason through complex questions.
|
|
183
|
+
You have access to an analytical reasoner who can break down questions, analyze assumptions, and evaluate logic.
|
|
184
|
+
|
|
185
|
+
When the user asks a complex question:
|
|
186
|
+
1. Use the reasoner to analyze the question thoroughly
|
|
187
|
+
2. Synthesize the analysis into a clear, helpful response
|
|
188
|
+
3. Provide insights and perspectives that address the core question
|
|
189
|
+
4. Acknowledge uncertainty where appropriate
|
|
190
|
+
|
|
191
|
+
Be clear, thoughtful, and educational. Help the user understand not just the answer, but the reasoning process itself.`;
|
|
192
|
+
|
|
193
|
+
function createMainAgent(
|
|
194
|
+
provider: "claude" | "openai" | "mistral",
|
|
195
|
+
reasonerTool: Tool<string>
|
|
196
|
+
): BaseAgent {
|
|
197
|
+
const config = {
|
|
198
|
+
id: "main",
|
|
199
|
+
name: "Reasoning Assistant",
|
|
200
|
+
description: MAIN_AGENT_DESCRIPTION,
|
|
201
|
+
tools: [reasonerTool],
|
|
202
|
+
maxTokens: 3072,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
if (provider === "openai") {
|
|
206
|
+
return new OpenAiAgent({
|
|
207
|
+
...config,
|
|
208
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
209
|
+
model: "gpt-4o-mini", // More capable model for synthesis and coordination
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (provider === "mistral") {
|
|
214
|
+
return new MistralAgent({
|
|
215
|
+
...config,
|
|
216
|
+
apiKey: process.env.MISTRAL_API_KEY as string,
|
|
217
|
+
model: "mistral-large-latest",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return new ClaudeAgent({
|
|
222
|
+
...config,
|
|
223
|
+
apiKey: process.env.ANTHROPIC_API_KEY as string,
|
|
224
|
+
model: "claude-haiku-4-5",
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// =============================================================================
|
|
229
|
+
// Main
|
|
230
|
+
// =============================================================================
|
|
231
|
+
|
|
232
|
+
async function main() {
|
|
233
|
+
console.log("=== Reasoning Assistant with Sub-Agent ===\n");
|
|
234
|
+
console.log(
|
|
235
|
+
"This example uses a specialized 'reasoner' agent to analyze questions,"
|
|
236
|
+
);
|
|
237
|
+
console.log(
|
|
238
|
+
"while a coordinator agent synthesizes the analysis into helpful responses.\n"
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Choose provider
|
|
242
|
+
const providerChoice = await rl.question(
|
|
243
|
+
"Which provider? [1] Claude (default), [2] OpenAI, or [3] Mistral: "
|
|
244
|
+
);
|
|
245
|
+
let provider: "claude" | "openai" | "mistral" = "claude";
|
|
246
|
+
if (providerChoice === "2") {
|
|
247
|
+
provider = "openai";
|
|
248
|
+
} else if (providerChoice === "3") {
|
|
249
|
+
provider = "mistral";
|
|
250
|
+
}
|
|
251
|
+
console.log(`Using ${provider}\n`);
|
|
252
|
+
|
|
253
|
+
// Create the reasoner sub-agent
|
|
254
|
+
const reasonerAgent = createReasonerAgent(provider);
|
|
255
|
+
console.log(
|
|
256
|
+
"Created reasoner sub-agent (specialized in analytical thinking)"
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Wrap the reasoner agent as a tool
|
|
260
|
+
const reasonerTool = Tool.fromAgent(
|
|
261
|
+
reasonerAgent,
|
|
262
|
+
`Use this analytical reasoner to break down complex questions, identify assumptions, and evaluate logic.
|
|
263
|
+
Provide the question or problem you want analyzed, and it will return structured analytical insights.`
|
|
264
|
+
);
|
|
265
|
+
console.log("Wrapped reasoner as a tool for the main agent");
|
|
266
|
+
|
|
267
|
+
// Create the main agent with the reasoner tool
|
|
268
|
+
const mainAgent = createMainAgent(provider, reasonerTool);
|
|
269
|
+
console.log("Created main agent (coordinator for synthesis)\n");
|
|
270
|
+
|
|
271
|
+
// Interactive loop for questions
|
|
272
|
+
console.log(
|
|
273
|
+
"Ask me any complex question and I'll reason through it with you."
|
|
274
|
+
);
|
|
275
|
+
console.log("Type 'quit' or 'exit' to end the conversation.\n");
|
|
276
|
+
|
|
277
|
+
while (true) {
|
|
278
|
+
const question = await rl.question("> ");
|
|
279
|
+
|
|
280
|
+
if (
|
|
281
|
+
question.toLowerCase() === "quit" ||
|
|
282
|
+
question.toLowerCase() === "exit"
|
|
283
|
+
) {
|
|
284
|
+
console.log("\nGoodbye!");
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!question.trim()) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log("\n--- Reasoning Process ---");
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
reasonerAgent.on(AgentEvent.AFTER_EXECUTE, console.log);
|
|
296
|
+
reasonerAgent.on(AgentEvent.BEFORE_EXECUTE, console.log);
|
|
297
|
+
const result = await mainAgent.execute(question);
|
|
298
|
+
|
|
299
|
+
console.log("\n--- Response ---");
|
|
300
|
+
console.log(result);
|
|
301
|
+
console.log("\n");
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error("Error:", error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
rl.close();
|
|
308
|
+
process.exit(0);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { OpenAiAgent } from "../../lib/agents/openai/OpenAiAgent";
|
|
3
|
+
import { AgentGraph } from "../../lib/graph/AgentGraph";
|
|
4
|
+
import { AgentEvent } from "../../lib/agents/AgentEvent";
|
|
5
|
+
export const agent1 = new OpenAiAgent({
|
|
6
|
+
id: "medical-research",
|
|
7
|
+
name: "Deep thinker agent",
|
|
8
|
+
description: `You write code for a problem that the user has`,
|
|
9
|
+
tools: [],
|
|
10
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
11
|
+
temperature: 1,
|
|
12
|
+
model: "gpt-4o-mini",
|
|
13
|
+
maxTokens: 8000,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const agent2 = new OpenAiAgent({
|
|
17
|
+
id: "medical-research",
|
|
18
|
+
name: "Deep feedback agent",
|
|
19
|
+
description: `You review code and write tests. Return both the code and the tests.`,
|
|
20
|
+
tools: [],
|
|
21
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
22
|
+
model: "gpt-4o-mini",
|
|
23
|
+
temperature: 0,
|
|
24
|
+
maxTokens: 8000,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const graph = AgentGraph.synchronous(agent1, agent2);
|
|
28
|
+
|
|
29
|
+
agent1.on(AgentEvent.AFTER_EXECUTE, (event: AgentEvent) => {
|
|
30
|
+
console.log(JSON.stringify(event, undefined, " "));
|
|
31
|
+
});
|
|
32
|
+
agent2.on(AgentEvent.AFTER_EXECUTE, (event: AgentEvent) => {
|
|
33
|
+
console.log(JSON.stringify(event, null, " "));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function run() {
|
|
37
|
+
graph
|
|
38
|
+
.execute(
|
|
39
|
+
`Write a TS class that supports Agents working together in a graph.
|
|
40
|
+
An initial execute command will add to the input of the first agent the choice of connected agents.
|
|
41
|
+
interface Agent{
|
|
42
|
+
execute(input:string):Promise<string>
|
|
43
|
+
}
|
|
44
|
+
`
|
|
45
|
+
)
|
|
46
|
+
.then(console.log);
|
|
47
|
+
}
|
|
48
|
+
run();
|