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
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weather with Sub-Agent Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates using an agent as a tool for another agent.
|
|
5
|
+
* A cheap "weatherman" agent handles the tool calls (geocoding + weather API),
|
|
6
|
+
* while a main agent coordinates and provides the final response.
|
|
7
|
+
*
|
|
8
|
+
* Architecture:
|
|
9
|
+
* Main Agent (expensive model, no tools)
|
|
10
|
+
* └── Weatherman Agent (cheap model, has geocoding + weather tools)
|
|
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 { OllamaAgent } from "../lib/agents/ollama/OllamaAgent";
|
|
19
|
+
import { BaseAgent } from "../lib/agents/BaseAgent";
|
|
20
|
+
import { Tool } from "../lib/tools/Tool";
|
|
21
|
+
|
|
22
|
+
const rl = createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Weather Tools (used by the weatherman sub-agent)
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
const geoCodingTool = new Tool({
|
|
32
|
+
name: "geocoding",
|
|
33
|
+
description: `Look up coordinates for a location. Returns city name, latitude, longitude, country, and timezone.`,
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
term: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Location name to search for (city, town, etc.)",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ["term"],
|
|
43
|
+
},
|
|
44
|
+
execute: async (input: { term: string }): Promise<unknown> => {
|
|
45
|
+
const res = await fetch(
|
|
46
|
+
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(
|
|
47
|
+
input.term
|
|
48
|
+
)}&count=3&language=en&format=json`
|
|
49
|
+
).then((res) => res.json());
|
|
50
|
+
|
|
51
|
+
if (!res.results || res.results.length === 0) {
|
|
52
|
+
return { error: `No location found for "${input.term}"` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
locations: res.results.map((r: Record<string, unknown>) => ({
|
|
57
|
+
name: r.name,
|
|
58
|
+
country: r.country,
|
|
59
|
+
latitude: r.latitude,
|
|
60
|
+
longitude: r.longitude,
|
|
61
|
+
timezone: r.timezone,
|
|
62
|
+
})),
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const weatherTool = new Tool({
|
|
68
|
+
name: "weather",
|
|
69
|
+
description: `Get current weather for a location using latitude and longitude coordinates.`,
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
lat: {
|
|
74
|
+
type: "number",
|
|
75
|
+
description: "Latitude",
|
|
76
|
+
},
|
|
77
|
+
long: {
|
|
78
|
+
type: "number",
|
|
79
|
+
description: "Longitude",
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ["lat", "long"],
|
|
83
|
+
},
|
|
84
|
+
execute: async (input: { lat: number; long: number }): Promise<unknown> => {
|
|
85
|
+
const res = await fetch(
|
|
86
|
+
`https://api.open-meteo.com/v1/forecast?latitude=${input.lat}&longitude=${input.long}¤t=temperature_2m,wind_speed_10m,weather_code&daily=temperature_2m_max,temperature_2m_min&timezone=auto`
|
|
87
|
+
).then((res) => res.json());
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
current: {
|
|
91
|
+
temperature: res.current?.temperature_2m,
|
|
92
|
+
unit: res.current_units?.temperature_2m,
|
|
93
|
+
windSpeed: res.current?.wind_speed_10m,
|
|
94
|
+
windUnit: res.current_units?.wind_speed_10m,
|
|
95
|
+
},
|
|
96
|
+
daily: res.daily
|
|
97
|
+
? {
|
|
98
|
+
dates: res.daily.time?.slice(0, 3),
|
|
99
|
+
maxTemps: res.daily.temperature_2m_max?.slice(0, 3),
|
|
100
|
+
minTemps: res.daily.temperature_2m_min?.slice(0, 3),
|
|
101
|
+
}
|
|
102
|
+
: null,
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// =============================================================================
|
|
108
|
+
// Sub-Agent Creation (the cheap weatherman)
|
|
109
|
+
// =============================================================================
|
|
110
|
+
|
|
111
|
+
const WEATHERMAN_DESCRIPTION = `You are a weather data retrieval specialist. Your job is to:
|
|
112
|
+
1. Use the geocoding tool to find coordinates for the requested location
|
|
113
|
+
2. Use the weather tool to get the current weather data
|
|
114
|
+
3. Return the raw weather data in a structured format
|
|
115
|
+
|
|
116
|
+
Be efficient and only make the necessary tool calls. Return the data concisely.`;
|
|
117
|
+
|
|
118
|
+
function createWeathermanAgent(
|
|
119
|
+
provider: "claude" | "openai" | "mistral" | "ollama"
|
|
120
|
+
): BaseAgent {
|
|
121
|
+
const config = {
|
|
122
|
+
id: "weatherman",
|
|
123
|
+
name: "Weatherman",
|
|
124
|
+
description: WEATHERMAN_DESCRIPTION,
|
|
125
|
+
tools: [geoCodingTool, weatherTool],
|
|
126
|
+
maxTokens: 1024,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (provider === "openai") {
|
|
130
|
+
return new OpenAiAgent({
|
|
131
|
+
...config,
|
|
132
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
133
|
+
model: "gpt-4o-mini", // Cheap model for tool calls
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (provider === "mistral") {
|
|
138
|
+
return new MistralAgent({
|
|
139
|
+
...config,
|
|
140
|
+
apiKey: process.env.MISTRAL_API_KEY as string,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (provider === "ollama") {
|
|
144
|
+
return new OllamaAgent({
|
|
145
|
+
...config,
|
|
146
|
+
apiKey: "",
|
|
147
|
+
model: "gemma4:e4b",
|
|
148
|
+
think: false,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return new ClaudeAgent({
|
|
152
|
+
...config,
|
|
153
|
+
apiKey: process.env.ANTHROPIC_API_KEY as string,
|
|
154
|
+
model: "claude-haiku-4-5", // Cheap model for tool calls
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// =============================================================================
|
|
159
|
+
// Main Agent Creation (the coordinator)
|
|
160
|
+
// =============================================================================
|
|
161
|
+
|
|
162
|
+
const MAIN_AGENT_DESCRIPTION = `You are a helpful weather assistant. You have access to a weatherman assistant who can fetch weather data for any location.
|
|
163
|
+
|
|
164
|
+
When the user asks about weather:
|
|
165
|
+
1. Ask the weatherman to get the weather data
|
|
166
|
+
2. Interpret the data and provide a helpful, conversational response
|
|
167
|
+
3. Include practical advice based on the weather conditions
|
|
168
|
+
|
|
169
|
+
Be friendly and helpful. Add interesting observations about the weather when appropriate.`;
|
|
170
|
+
|
|
171
|
+
function createMainAgent(
|
|
172
|
+
provider: "claude" | "openai" | "mistral" | "ollama",
|
|
173
|
+
weathermanTool: Tool<string>
|
|
174
|
+
): BaseAgent {
|
|
175
|
+
const config = {
|
|
176
|
+
id: "main",
|
|
177
|
+
name: "Weather Assistant",
|
|
178
|
+
description: MAIN_AGENT_DESCRIPTION,
|
|
179
|
+
tools: [weathermanTool],
|
|
180
|
+
maxTokens: 2048,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (provider === "openai") {
|
|
184
|
+
return new OpenAiAgent({
|
|
185
|
+
...config,
|
|
186
|
+
apiKey: process.env.OPENAI_API_KEY as string,
|
|
187
|
+
model: "gpt-4o", // More capable model for the main agent
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (provider === "mistral") {
|
|
192
|
+
return new MistralAgent({
|
|
193
|
+
...config,
|
|
194
|
+
apiKey: process.env.MISTRAL_API_KEY as string,
|
|
195
|
+
model: "mistral-large-latest",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (provider === "ollama") {
|
|
199
|
+
return new OllamaAgent({
|
|
200
|
+
...config,
|
|
201
|
+
apiKey: "",
|
|
202
|
+
model: "gemma4:e4b",
|
|
203
|
+
think: false,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return new ClaudeAgent({
|
|
207
|
+
...config,
|
|
208
|
+
apiKey: process.env.ANTHROPIC_API_KEY as string,
|
|
209
|
+
model: "claude-sonnet-4-5", // More capable model for the main agent
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// =============================================================================
|
|
214
|
+
// Main
|
|
215
|
+
// =============================================================================
|
|
216
|
+
|
|
217
|
+
async function main() {
|
|
218
|
+
console.log("=== Weather Assistant with Sub-Agent ===\n");
|
|
219
|
+
console.log(
|
|
220
|
+
"This example uses a cheap 'weatherman' agent to handle API calls,"
|
|
221
|
+
);
|
|
222
|
+
console.log("while a smarter main agent provides the final response.\n");
|
|
223
|
+
|
|
224
|
+
// Choose provider
|
|
225
|
+
const providerChoice = await rl.question(
|
|
226
|
+
"Which provider? [1] Claude (default), [2] OpenAI, or [3] Mistral or [4] Ollama: "
|
|
227
|
+
);
|
|
228
|
+
let provider: "claude" | "openai" | "mistral" | "ollama" = "claude";
|
|
229
|
+
if (providerChoice === "2") {
|
|
230
|
+
provider = "openai";
|
|
231
|
+
} else if (providerChoice === "3") {
|
|
232
|
+
provider = "mistral";
|
|
233
|
+
} else if (providerChoice === "4") {
|
|
234
|
+
provider = "ollama";
|
|
235
|
+
}
|
|
236
|
+
console.log(`Using ${provider}\n`);
|
|
237
|
+
|
|
238
|
+
// Create the weatherman sub-agent
|
|
239
|
+
const weathermanAgent = createWeathermanAgent(provider);
|
|
240
|
+
console.log("Created weatherman sub-agent (cheap model for tool calls)");
|
|
241
|
+
|
|
242
|
+
// Wrap the weatherman agent as a tool
|
|
243
|
+
const weathermanTool = Tool.fromAgent(
|
|
244
|
+
weathermanAgent,
|
|
245
|
+
"Use this assistant to get weather data for any location. Provide the location name and it will return current weather conditions and a 3-day forecast."
|
|
246
|
+
);
|
|
247
|
+
console.log("Wrapped weatherman as a tool for the main agent");
|
|
248
|
+
|
|
249
|
+
// Create the main agent with the weatherman tool
|
|
250
|
+
const mainAgent = createMainAgent(provider, weathermanTool);
|
|
251
|
+
console.log("Created main agent (smarter model for responses)\n");
|
|
252
|
+
|
|
253
|
+
// Get weather query from user
|
|
254
|
+
const location = await rl.question(
|
|
255
|
+
"For what location do you want to know the weather?\n> "
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
console.log("\nProcessing your request...\n");
|
|
259
|
+
console.log("--- Agent Activity ---");
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const result = await mainAgent.execute(
|
|
263
|
+
`What's the weather like in ${location}? Please provide current conditions and any advice for the day.`
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
console.log("\n--- Response ---");
|
|
267
|
+
console.log(result);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error("Error:", error);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
rl.close();
|
|
273
|
+
process.exit(0);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
main().catch(console.error);
|
package/weather.ts
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weather Agent Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates a multi-provider weather agent that:
|
|
5
|
+
* - Uses geocoding to find locations
|
|
6
|
+
* - Fetches weather data from Open-Meteo API
|
|
7
|
+
* - Supports Claude, OpenAI, Mistral, and Gemini
|
|
8
|
+
* - Includes proper error handling, type safety, and validation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import "dotenv/config";
|
|
12
|
+
import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
|
|
13
|
+
import { MistralAgent } from "../lib/agents/mistral/MistralAgent";
|
|
14
|
+
import { OpenAiAgent } from "../lib/agents/openai/OpenAiAgent";
|
|
15
|
+
import { GeminiAgent } from "../lib";
|
|
16
|
+
import { Tool } from "../lib/tools/Tool";
|
|
17
|
+
import { createInterface } from "node:readline/promises";
|
|
18
|
+
import { BaseAgent } from "../lib/agents/BaseAgent";
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Type Definitions
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
interface GeocodingInput {
|
|
25
|
+
term: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Location {
|
|
29
|
+
name: string;
|
|
30
|
+
latitude: number;
|
|
31
|
+
longitude: number;
|
|
32
|
+
country: string;
|
|
33
|
+
country_code: string;
|
|
34
|
+
timezone: string;
|
|
35
|
+
elevation?: number;
|
|
36
|
+
feature_code?: string;
|
|
37
|
+
population?: number;
|
|
38
|
+
postcodes?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface GeocodingResult {
|
|
42
|
+
results?: Location[];
|
|
43
|
+
generationtime_ms?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface WeatherInput {
|
|
47
|
+
lat: number;
|
|
48
|
+
long: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface CurrentWeather {
|
|
52
|
+
temperature_2m: number;
|
|
53
|
+
wind_speed_10m: number;
|
|
54
|
+
time: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface HourlyWeather {
|
|
58
|
+
temperature_2m: number[];
|
|
59
|
+
relative_humidity_2m: number[];
|
|
60
|
+
time: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface WeatherResult {
|
|
64
|
+
current: CurrentWeather;
|
|
65
|
+
current_units: {
|
|
66
|
+
temperature_2m: string;
|
|
67
|
+
wind_speed_10m: string;
|
|
68
|
+
};
|
|
69
|
+
hourly: HourlyWeather;
|
|
70
|
+
hourly_units: {
|
|
71
|
+
temperature_2m: string;
|
|
72
|
+
relative_humidity_2m: string;
|
|
73
|
+
};
|
|
74
|
+
latitude: number;
|
|
75
|
+
longitude: number;
|
|
76
|
+
timezone: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
type Provider = "claude" | "openai" | "mistral" | "gemini";
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Tools with Proper Type Safety and Error Handling
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
const geoCodingTool = new Tool({
|
|
86
|
+
name: "geocodingTool",
|
|
87
|
+
description: `This tool accepts a search term and returns a list of matching locations.
|
|
88
|
+
Returns an array of results including city name, latitude, longitude, elevation, feature_code, country_code, timezone, population, and postcodes.`,
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
term: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description:
|
|
95
|
+
"String to search for. An empty string or only 1 character will return an empty result. 2 characters will only match exact matching locations. 3 and more characters will perform fuzzy matching. The search string can be a location name or a postal code, should NOT contain a country or state code.",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
required: ["term"],
|
|
99
|
+
},
|
|
100
|
+
execute: async (input: GeocodingInput): Promise<GeocodingResult> => {
|
|
101
|
+
try {
|
|
102
|
+
if (!input.term || input.term.trim().length === 0) {
|
|
103
|
+
throw new Error("Search term cannot be empty");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Encode URL parameter to prevent injection
|
|
107
|
+
const encodedTerm = encodeURIComponent(input.term.trim());
|
|
108
|
+
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodedTerm}&count=10&language=en&format=json`;
|
|
109
|
+
|
|
110
|
+
const response = await fetch(url);
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Geocoding API error: ${response.status} ${response.statusText}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const data = (await response.json()) as GeocodingResult;
|
|
119
|
+
|
|
120
|
+
// Return empty results if no locations found
|
|
121
|
+
if (!data.results || data.results.length === 0) {
|
|
122
|
+
return { results: [] };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return data;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const errorMessage =
|
|
128
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
129
|
+
throw new Error(`Failed to geocode location: ${errorMessage}`);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const weatherTool = new Tool({
|
|
135
|
+
name: "weatherTool",
|
|
136
|
+
description: `This tool accepts latitude and longitude coordinates and returns the current weather forecast for the location.`,
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: {
|
|
140
|
+
lat: {
|
|
141
|
+
type: "number",
|
|
142
|
+
description: "Latitude for the location.",
|
|
143
|
+
},
|
|
144
|
+
long: {
|
|
145
|
+
type: "number",
|
|
146
|
+
description: "Longitude for the location.",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
required: ["lat", "long"],
|
|
150
|
+
},
|
|
151
|
+
execute: async (input: WeatherInput): Promise<WeatherResult> => {
|
|
152
|
+
try {
|
|
153
|
+
// Validate coordinates
|
|
154
|
+
if (input.lat < -90 || input.lat > 90) {
|
|
155
|
+
throw new Error("Latitude must be between -90 and 90");
|
|
156
|
+
}
|
|
157
|
+
if (input.long < -180 || input.long > 180) {
|
|
158
|
+
throw new Error("Longitude must be between -180 and 180");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const url = `https://api.open-meteo.com/v1/forecast?latitude=${input.lat}&longitude=${input.long}¤t=temperature_2m,wind_speed_10m&hourly=temperature_2m,relative_humidity_2m`;
|
|
162
|
+
|
|
163
|
+
const response = await fetch(url);
|
|
164
|
+
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Weather API error: ${response.status} ${response.statusText}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const data = (await response.json()) as WeatherResult;
|
|
172
|
+
return data;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const errorMessage =
|
|
175
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
176
|
+
throw new Error(`Failed to fetch weather data: ${errorMessage}`);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const tools = [geoCodingTool, weatherTool];
|
|
182
|
+
|
|
183
|
+
// =============================================================================
|
|
184
|
+
// Agent Configuration
|
|
185
|
+
// =============================================================================
|
|
186
|
+
|
|
187
|
+
const AGENT_DESCRIPTION = `You are an agent that gets the weather for a specific location. You are concise and to the point.
|
|
188
|
+
Do NOT ask follow up questions, but end the conversation. Format the output as JSON in the following format:
|
|
189
|
+
<tooluse>List the tools used</tooluse>
|
|
190
|
+
<result>{
|
|
191
|
+
textContent: textual description of the weather,
|
|
192
|
+
currentTempinC: Number,
|
|
193
|
+
currentWind: Number,
|
|
194
|
+
currentPrecip: 'None' | 'light' | 'heavy'
|
|
195
|
+
}</result>`;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Create the agent based on provider choice
|
|
199
|
+
*/
|
|
200
|
+
function createAgent(provider: Provider): BaseAgent {
|
|
201
|
+
const commonConfig = {
|
|
202
|
+
id: "weather-agent",
|
|
203
|
+
name: "Weather Agent",
|
|
204
|
+
description: AGENT_DESCRIPTION,
|
|
205
|
+
tools,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
switch (provider) {
|
|
209
|
+
case "openai": {
|
|
210
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
211
|
+
if (!apiKey) {
|
|
212
|
+
throw new Error("OPENAI_API_KEY environment variable is not set");
|
|
213
|
+
}
|
|
214
|
+
return new OpenAiAgent({
|
|
215
|
+
...commonConfig,
|
|
216
|
+
model: "gpt-4o-mini",
|
|
217
|
+
apiKey,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case "gemini": {
|
|
222
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
223
|
+
if (!apiKey) {
|
|
224
|
+
throw new Error("GEMINI_API_KEY environment variable is not set");
|
|
225
|
+
}
|
|
226
|
+
return new GeminiAgent({
|
|
227
|
+
...commonConfig,
|
|
228
|
+
model: "gemini-flash-lite-latest",
|
|
229
|
+
apiKey,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case "mistral": {
|
|
234
|
+
const apiKey = process.env.MISTRAL_API_KEY;
|
|
235
|
+
if (!apiKey) {
|
|
236
|
+
throw new Error("MISTRAL_API_KEY environment variable is not set");
|
|
237
|
+
}
|
|
238
|
+
return new MistralAgent({
|
|
239
|
+
...commonConfig,
|
|
240
|
+
model: "ministral-3b-latest",
|
|
241
|
+
apiKey,
|
|
242
|
+
disableParallelToolUse: false,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case "claude":
|
|
247
|
+
default: {
|
|
248
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
249
|
+
if (!apiKey) {
|
|
250
|
+
throw new Error("ANTHROPIC_API_KEY environment variable is not set");
|
|
251
|
+
}
|
|
252
|
+
return new ClaudeAgent({
|
|
253
|
+
...commonConfig,
|
|
254
|
+
model: "claude-haiku-4-5",
|
|
255
|
+
apiKey,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Validate user input for location
|
|
263
|
+
*/
|
|
264
|
+
function validateLocationInput(input: string): string {
|
|
265
|
+
const trimmed = input.trim();
|
|
266
|
+
|
|
267
|
+
if (!trimmed) {
|
|
268
|
+
throw new Error("Location cannot be empty");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (trimmed.length < 2) {
|
|
272
|
+
throw new Error("Location must be at least 2 characters");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Basic sanitization - remove potentially dangerous characters
|
|
276
|
+
const sanitized = trimmed.replace(/[<>]/g, "");
|
|
277
|
+
|
|
278
|
+
return sanitized;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Parse provider choice from user input
|
|
283
|
+
*/
|
|
284
|
+
function parseProviderChoice(choice: string): Provider {
|
|
285
|
+
const trimmed = choice.trim();
|
|
286
|
+
|
|
287
|
+
switch (trimmed) {
|
|
288
|
+
case "2":
|
|
289
|
+
return "openai";
|
|
290
|
+
case "3":
|
|
291
|
+
return "mistral";
|
|
292
|
+
case "4":
|
|
293
|
+
return "gemini";
|
|
294
|
+
case "1":
|
|
295
|
+
case "":
|
|
296
|
+
default:
|
|
297
|
+
return "claude";
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// =============================================================================
|
|
302
|
+
// Main Function
|
|
303
|
+
// =============================================================================
|
|
304
|
+
|
|
305
|
+
async function getWeatherExample(): Promise<void> {
|
|
306
|
+
const rl = createInterface({
|
|
307
|
+
input: process.stdin,
|
|
308
|
+
output: process.stdout,
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
console.log("=== Weather Agent Example ===\n");
|
|
313
|
+
|
|
314
|
+
// Choose provider
|
|
315
|
+
const providerChoice = await rl.question(
|
|
316
|
+
"Which provider? [1] Claude (default), [2] OpenAI, [3] Mistral, [4] Gemini: "
|
|
317
|
+
);
|
|
318
|
+
const provider = parseProviderChoice(providerChoice);
|
|
319
|
+
|
|
320
|
+
console.log(`Using ${provider}\n`);
|
|
321
|
+
|
|
322
|
+
// Create agent
|
|
323
|
+
let agent: BaseAgent;
|
|
324
|
+
try {
|
|
325
|
+
agent = createAgent(provider);
|
|
326
|
+
} catch (error) {
|
|
327
|
+
const errorMessage =
|
|
328
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
329
|
+
console.error(`Failed to create agent: ${errorMessage}`);
|
|
330
|
+
console.error(
|
|
331
|
+
"Please ensure the appropriate API key is set in your .env file"
|
|
332
|
+
);
|
|
333
|
+
rl.close();
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Get location from user
|
|
338
|
+
const locationInput = await rl.question(
|
|
339
|
+
"For what location do you want to know the weather?\n> "
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
let location: string;
|
|
343
|
+
try {
|
|
344
|
+
location = validateLocationInput(locationInput);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const errorMessage =
|
|
347
|
+
error instanceof Error ? error.message : "Invalid input";
|
|
348
|
+
console.error(`Input validation error: ${errorMessage}`);
|
|
349
|
+
rl.close();
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Execute agent
|
|
354
|
+
console.log("\nFetching weather data...\n");
|
|
355
|
+
|
|
356
|
+
const result = await agent.execute(
|
|
357
|
+
`Find the weather forecast for ${location}. Format the answer in the correct way. Give some advice about the weather as well. If the location isn't obvious, make an informed guess.`
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
console.log("\n=== Weather Forecast ===\n");
|
|
361
|
+
console.log(result);
|
|
362
|
+
|
|
363
|
+
// Display token usage if available (without exposing sensitive data)
|
|
364
|
+
const tokenUsage = (agent as { lastTokenUsage?: unknown }).lastTokenUsage;
|
|
365
|
+
if (tokenUsage) {
|
|
366
|
+
console.log(`\n--- Model: ${agent.getModel()} ---`);
|
|
367
|
+
console.log("Token usage:", JSON.stringify(tokenUsage, null, 2));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
rl.close();
|
|
371
|
+
process.exit(0);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
const errorMessage =
|
|
374
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
375
|
+
console.error("\n=== Error ===");
|
|
376
|
+
console.error(errorMessage);
|
|
377
|
+
|
|
378
|
+
if (error instanceof Error && error.stack) {
|
|
379
|
+
console.error("\nStack trace:");
|
|
380
|
+
console.error(error.stack);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
rl.close();
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Run the example
|
|
389
|
+
getWeatherExample();
|