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.
Files changed (44) hide show
  1. package/chunking-demo.ts +339 -0
  2. package/cleanup-duplicates.ts +142 -0
  3. package/data/flower.jpg +0 -0
  4. package/generative.ts +128 -0
  5. package/graph/context-example.ts +209 -0
  6. package/graph/data-pipeline/agents.ts +60 -0
  7. package/graph/data-pipeline/fetchers.ts +166 -0
  8. package/graph/data-pipeline/index.ts +282 -0
  9. package/graph/index.ts +154 -0
  10. package/graph/map-example.ts +227 -0
  11. package/graph/metrics-example.ts +238 -0
  12. package/graph/parallel-example.ts +167 -0
  13. package/graph/pipeline-example.ts +225 -0
  14. package/graph/planning-example.ts +406 -0
  15. package/graph/router-example.ts +226 -0
  16. package/graph/sequential-example.ts +141 -0
  17. package/graph/voting-example.ts +159 -0
  18. package/graph-rag/docker-compose.yaml +14 -0
  19. package/graph-rag/index.js +99 -0
  20. package/graph-rag/init-db.sh +7 -0
  21. package/graph-rag/package.json +15 -0
  22. package/history-compression-example.ts +163 -0
  23. package/history-persistence.ts +347 -0
  24. package/index.ts +175 -0
  25. package/ingestion-pipeline.ts +353 -0
  26. package/mcp-airbnb-example.ts +69 -0
  27. package/mcp-http-example.ts +70 -0
  28. package/mcp-stdio-example.ts +63 -0
  29. package/multimodal.ts +144 -0
  30. package/ollama.ts +148 -0
  31. package/openai-compatible.ts +141 -0
  32. package/opensearch-vector-store.ts +342 -0
  33. package/package.json +24 -0
  34. package/pubmed.ts +289 -0
  35. package/reasoning-with-sub-agent.ts +311 -0
  36. package/synchronous/index.ts +48 -0
  37. package/tsconfig.json +8 -0
  38. package/vector-store-filtering.ts +303 -0
  39. package/vector-store.ts +210 -0
  40. package/vectorstore/index.ts +0 -0
  41. package/vectorstore/store/dbService.ts +80 -0
  42. package/voyage-embeddings.ts +99 -0
  43. package/weather-with-sub-agent.ts +276 -0
  44. package/weather.ts +389 -0
package/ollama.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Ollama Agent Example
3
+ *
4
+ * Demonstrates using OllamaAgent with locally-hosted Ollama models.
5
+ * Requires: ollama running on localhost:11434 and the `ollama` npm package.
6
+ *
7
+ * Install ollama npm package:
8
+ * npm install ollama
9
+ *
10
+ * Pull a model with tool support (in terminal):
11
+ * ollama pull llama3.2
12
+ * ollama pull qwen2.5 # better tool-use support
13
+ *
14
+ * Run this example:
15
+ * npx tsx examples/ollama.ts
16
+ */
17
+
18
+ import { OllamaAgent } from "../lib/agents/ollama/OllamaAgent";
19
+ import { Tool } from "../lib/tools/Tool";
20
+
21
+ // =============================================================================
22
+ // Tools
23
+ // =============================================================================
24
+
25
+ interface WeatherInput {
26
+ location: string;
27
+ }
28
+
29
+ const getWeatherTool = new Tool({
30
+ name: "get_weather",
31
+ description: "Get the current weather for a location",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: {
35
+ location: {
36
+ type: "string",
37
+ description: "City name or location (e.g. 'Amsterdam', 'New York')",
38
+ },
39
+ },
40
+ required: ["location"],
41
+ },
42
+ execute: async (input: WeatherInput) => {
43
+ // Simulated weather data — replace with a real API call
44
+ const conditions = ["sunny", "cloudy", "rainy", "windy", "snowy"];
45
+ const condition = conditions[Math.floor(Math.random() * conditions.length)];
46
+ const temp = Math.round(5 + Math.random() * 25);
47
+ return {
48
+ location: input.location,
49
+ temperature: temp,
50
+ unit: "celsius",
51
+ condition,
52
+ humidity: Math.round(40 + Math.random() * 40),
53
+ };
54
+ },
55
+ });
56
+
57
+ interface CalculatorInput {
58
+ expression: string;
59
+ }
60
+
61
+ const calculatorTool = new Tool({
62
+ name: "calculate",
63
+ description: "Evaluate a mathematical expression",
64
+ inputSchema: {
65
+ type: "object",
66
+ properties: {
67
+ expression: {
68
+ type: "string",
69
+ description:
70
+ "Math expression to evaluate, e.g. '2 + 2' or '10 * 5 / 2'",
71
+ },
72
+ },
73
+ required: ["expression"],
74
+ },
75
+ execute: async (input: CalculatorInput) => {
76
+ // Safe evaluation of simple math expressions
77
+ const sanitized = input.expression.replace(/[^0-9+\-*/().\s]/g, "");
78
+ try {
79
+ const result = Function(`"use strict"; return (${sanitized})`)();
80
+ return { expression: input.expression, result };
81
+ } catch {
82
+ return { expression: input.expression, error: "Invalid expression" };
83
+ }
84
+ },
85
+ });
86
+
87
+ // =============================================================================
88
+ // Main
89
+ // =============================================================================
90
+
91
+ async function main() {
92
+ console.log("=== Ollama Agent Example ===\n");
93
+
94
+ // Basic chat (no tools)
95
+ console.log("--- Basic Chat ---");
96
+ const chatAgent = new OllamaAgent({
97
+ id: "ollama-chat",
98
+ name: "Ollama Chat",
99
+ description: "A helpful assistant",
100
+ model: "gemma4:e4b",
101
+ think: false,
102
+ apiKey: "", // Ollama doesn't require an API key
103
+ });
104
+
105
+ const chatResponse = await chatAgent.execute(
106
+ "What is the capital of France? Answer in one sentence."
107
+ );
108
+ console.log("Response:", chatResponse);
109
+ console.log("Tokens:", chatAgent.lastTokenUsage);
110
+
111
+ console.log("\n--- Tool-Use Agent ---");
112
+
113
+ // Agent with tools — use a model with strong tool support
114
+ const toolAgent = new OllamaAgent({
115
+ id: "ollama-tool-agent",
116
+ name: "Ollama Tool Agent",
117
+ think: false,
118
+
119
+ description: "An agent that can check weather and do math",
120
+ model: "gemma4:e4b", // qwen2.5 has good tool-call support
121
+ tools: [getWeatherTool, calculatorTool],
122
+ apiKey: "",
123
+ });
124
+
125
+ const toolResponse = await toolAgent.execute(
126
+ "What's the weather in Amsterdam? Also, what is 42 * 7?"
127
+ );
128
+ console.log("Response:", toolResponse);
129
+ console.log("Tokens:", toolAgent.lastTokenUsage);
130
+
131
+ // Custom host example
132
+ console.log("\n--- Custom Host ---");
133
+ const remoteAgent = new OllamaAgent({
134
+ id: "ollama-remote",
135
+ name: "Remote Ollama",
136
+ thinking: false,
137
+
138
+ description: "Agent connecting to a non-default Ollama server",
139
+ model: "gemma4:e4b",
140
+ host: "http://localhost:11434", // default, but can point to any Ollama instance
141
+ apiKey: "",
142
+ });
143
+
144
+ const remoteResponse = await remoteAgent.execute("Say hello in Dutch.");
145
+ console.log("Response:", remoteResponse);
146
+ }
147
+
148
+ main().catch(console.error);
@@ -0,0 +1,141 @@
1
+ /**
2
+ * OpenAI-Compatible Agent Example
3
+ *
4
+ * Demonstrates how to build a custom agent for any server that exposes an
5
+ * OpenAI-compatible `/v1/chat/completions` API — vLLM, LM Studio, Together AI,
6
+ * Groq, or your own fine-tuned model server.
7
+ *
8
+ * This example shows:
9
+ * 1. Using LlamaCppAgent (the built-in OpenAI-compatible agent for llama.cpp)
10
+ * 2. Extending OpenAICompatibleAgent to create your own custom agent
11
+ *
12
+ * Prerequisites for the llama.cpp demo:
13
+ * npm install openai
14
+ * llama-server -m ./models/your-model.gguf # defaults to http://localhost:8080
15
+ *
16
+ * Run this example:
17
+ * npx tsx examples/openai-compatible.ts
18
+ */
19
+
20
+ import { LlamaCppAgent } from "../lib/agents/llamacpp/LlamaCppAgent";
21
+ import {
22
+ OpenAICompatibleAgent,
23
+ OpenAICompatibleConfig,
24
+ } from "../lib/agents/openai-compatible/OpenAICompatibleAgent";
25
+ import { Tool } from "../lib/tools/Tool";
26
+ import { History } from "../lib/history/History";
27
+
28
+ // =============================================================================
29
+ // Part 1: LlamaCppAgent — built-in, zero boilerplate
30
+ // =============================================================================
31
+
32
+ async function llamaCppDemo() {
33
+ console.log("=== LlamaCppAgent (built-in) ===\n");
34
+
35
+ const agent = new LlamaCppAgent({
36
+ id: "llama-1",
37
+ name: "Local Assistant",
38
+ description: "You are a helpful assistant running locally via llama.cpp.",
39
+ baseURL: "http://localhost:8080/v1", // default — can omit
40
+ model: "default",
41
+ });
42
+
43
+ const response = await agent.execute(
44
+ "What is the capital of France? Answer in one sentence."
45
+ );
46
+ console.log("Response:", response);
47
+ console.log("Tokens:", agent.lastTokenUsage);
48
+
49
+ // Discover which models the server has loaded
50
+ const models = await agent.listModels();
51
+ console.log(
52
+ "Available models:",
53
+ models.map((m) => m.id)
54
+ );
55
+ }
56
+
57
+ // =============================================================================
58
+ // Part 2: Custom OpenAICompatibleAgent subclass
59
+ //
60
+ // Use this pattern to add your own config, vendor-specific request params, or
61
+ // a display name for a server that isn't llama.cpp (e.g. vLLM, LM Studio).
62
+ // =============================================================================
63
+
64
+ type VLLMConfig = Omit<OpenAICompatibleConfig, "baseURL" | "vendor"> & {
65
+ /** URL of the vLLM server (default: http://localhost:8000/v1) */
66
+ baseURL?: string;
67
+ };
68
+
69
+ class VLLMAgent extends OpenAICompatibleAgent {
70
+ constructor(config: VLLMConfig, history?: History) {
71
+ super(
72
+ {
73
+ ...config,
74
+ vendor: "llamacpp", // reuse the "llamacpp" vendor slot for OpenAI-compatible servers
75
+ baseURL: config.baseURL ?? "http://localhost:8000/v1",
76
+ model: config.model ?? "default",
77
+ },
78
+ history
79
+ );
80
+ }
81
+
82
+ protected getVendorName(): string {
83
+ return "vLLM";
84
+ }
85
+ }
86
+
87
+ async function vllmDemo() {
88
+ console.log("\n=== VLLMAgent (custom subclass) ===\n");
89
+
90
+ const weatherTool = new Tool({
91
+ name: "get_weather",
92
+ description: "Get the current weather for a location",
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ location: {
97
+ type: "string",
98
+ description: "City name (e.g. 'Amsterdam')",
99
+ },
100
+ },
101
+ required: ["location"],
102
+ },
103
+ execute: async (input: { location: string }) => ({
104
+ location: input.location,
105
+ temperature: Math.round(10 + Math.random() * 20),
106
+ unit: "celsius",
107
+ condition: ["sunny", "cloudy", "rainy"][Math.floor(Math.random() * 3)],
108
+ }),
109
+ });
110
+
111
+ const agent = new VLLMAgent({
112
+ id: "vllm-1",
113
+ name: "vLLM Assistant",
114
+ description: "You are a helpful assistant. Use tools when helpful.",
115
+ baseURL: "http://localhost:8000/v1",
116
+ tools: [weatherTool],
117
+ });
118
+
119
+ const response = await agent.execute(
120
+ "What's the weather like in Amsterdam today?"
121
+ );
122
+ console.log("Response:", response);
123
+ console.log("Tokens:", agent.lastTokenUsage);
124
+ }
125
+
126
+ // =============================================================================
127
+ // Main
128
+ // =============================================================================
129
+
130
+ async function main() {
131
+ // Run whichever demo matches your running server.
132
+ // Comment out the one you don't need.
133
+ await llamaCppDemo().catch((err) =>
134
+ console.warn("llama.cpp demo skipped:", err.message)
135
+ );
136
+ await vllmDemo().catch((err) =>
137
+ console.warn("vLLM demo skipped:", err.message)
138
+ );
139
+ }
140
+
141
+ main().catch(console.error);
@@ -0,0 +1,342 @@
1
+ /**
2
+ * OpenSearch Vector Store example
3
+ *
4
+ * Demonstrates how to use OpenSearchVectorStore for RAG (Retrieval-Augmented Generation)
5
+ * with a Claude agent.
6
+ *
7
+ * Prerequisites:
8
+ * - A running OpenSearch instance (e.g. via Docker):
9
+ *
10
+ * docker run -p 9200:9200 -p 9600:9600 \
11
+ * -e "discovery.type=single-node" \
12
+ * -e 'OPENSEARCH_INITIAL_ADMIN_PASSWORD=MySearch@7742' \
13
+ * opensearchproject/opensearch:latest
14
+ *
15
+ *
16
+ * - Required env vars:
17
+ * OPENAI_API_KEY — for OpenAI embeddings
18
+ * ANTHROPIC_API_KEY — for the Claude agent
19
+ * OPENSEARCH_NODE — OpenSearch endpoint (default: https://localhost:9200)
20
+ * OPENSEARCH_USERNAME — username (default: admin)
21
+ * OPENSEARCH_PASSWORD — password (default: admin)
22
+ *
23
+ * Run with:
24
+ * npm run example examples/opensearch-vector-store.ts
25
+ */
26
+
27
+ import "dotenv/config";
28
+ import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
29
+ import { OpenSearchVectorStore } from "../lib/vectorstore/OpenSearchVectorStore";
30
+ import { OpenAIEmbeddings } from "../lib/embeddings/OpenAIEmbeddings";
31
+
32
+ const OPENSEARCH_NODE = process.env.OPENSEARCH_NODE ?? "https://localhost:9200";
33
+ const OPENSEARCH_USERNAME = process.env.OPENSEARCH_USERNAME ?? "admin";
34
+ const OPENSEARCH_PASSWORD = process.env.OPENSEARCH_PASSWORD ?? "admin";
35
+ const INDEX_NAME = "agention_example";
36
+
37
+ async function main() {
38
+ console.log("OpenSearch Vector Store Example\n");
39
+ console.log("================================\n");
40
+
41
+ if (!process.env.OPENAI_API_KEY) {
42
+ console.error("Error: OPENAI_API_KEY is required for embeddings");
43
+ process.exit(1);
44
+ }
45
+ if (!process.env.ANTHROPIC_API_KEY) {
46
+ console.error("Error: ANTHROPIC_API_KEY is required for the agent");
47
+ process.exit(1);
48
+ }
49
+
50
+ // 1. Embeddings
51
+ console.log("1. Creating OpenAI embeddings provider...");
52
+ const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
53
+ console.log(
54
+ ` Model: ${embeddings.model}, Dimensions: ${embeddings.dimensions}\n`
55
+ );
56
+
57
+ // 2. Vector store
58
+ console.log(`2. Connecting to OpenSearch at ${OPENSEARCH_NODE}...`);
59
+ const store = await OpenSearchVectorStore.create({
60
+ name: "knowledge_base",
61
+ node: OPENSEARCH_NODE,
62
+ auth: { username: OPENSEARCH_USERNAME, password: OPENSEARCH_PASSWORD },
63
+ ssl: { rejectUnauthorized: false }, // allow self-signed certs in dev
64
+ indexName: INDEX_NAME,
65
+ embeddings,
66
+ spaceType: "cosinesimil",
67
+ metadataFields: [
68
+ { name: "category", type: "string" },
69
+ { name: "source", type: "string" },
70
+ ],
71
+ });
72
+ console.log(
73
+ ` Index: "${store.getIndexName()}", Dimensions: ${store.getDimensions()}\n`
74
+ );
75
+
76
+ // 3. Ingest some documents
77
+ console.log("3. Ingesting sample documents...");
78
+ const documents = [
79
+ {
80
+ id: "doc-1",
81
+ content:
82
+ "OpenSearch is a distributed, open-source search and analytics engine. " +
83
+ "It is a community-driven fork of Elasticsearch maintained by Amazon.",
84
+ metadata: { source: "docs", category: "overview" },
85
+ },
86
+ {
87
+ id: "doc-2",
88
+ content:
89
+ "The OpenSearch k-NN plugin enables approximate k-nearest-neighbour (ANN) search " +
90
+ "using the HNSW algorithm. It supports cosine similarity, L2, and inner product space types.",
91
+ metadata: { source: "docs", category: "knn" },
92
+ },
93
+ {
94
+ id: "doc-3",
95
+ content:
96
+ "Agention-lib provides an OpenSearchVectorStore that wraps the k-NN plugin " +
97
+ "and exposes a unified VectorStore interface for use in RAG pipelines.",
98
+ metadata: { source: "agention", category: "vectorstore" },
99
+ },
100
+ {
101
+ id: "doc-4",
102
+ content:
103
+ "Retrieval-Augmented Generation (RAG) is a pattern where an LLM is given relevant " +
104
+ "context retrieved from a vector store before generating a response.",
105
+ metadata: { source: "docs", category: "rag" },
106
+ },
107
+ {
108
+ id: "doc-5",
109
+ content:
110
+ "HNSW (Hierarchical Navigable Small World) is a graph-based ANN algorithm. " +
111
+ "The M parameter controls the number of bidirectional links per node, " +
112
+ "and ef_construction controls graph quality at index build time.",
113
+ metadata: { source: "docs", category: "knn" },
114
+ },
115
+ ];
116
+
117
+ const ids = await store.addDocuments(documents);
118
+ console.log(` Indexed ${ids.length} documents\n`);
119
+
120
+ // 4. Index overview & data snapshot
121
+ console.log("4. Index overview & data snapshot");
122
+
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ const rawClient = store.getClient() as any;
125
+
126
+ // Fetch the full index definition (settings + mappings)
127
+ const indexInfo = await rawClient.indices.get({ index: store.getIndexName() });
128
+ const indexDef = indexInfo.body[store.getIndexName()];
129
+ const knnSettings = indexDef?.settings?.index?.knn;
130
+ const embeddingMapping = indexDef?.mappings?.properties?.embedding;
131
+ const method = embeddingMapping?.method ?? {};
132
+
133
+ console.log(` Engine: ${method.engine ?? "—"}`);
134
+ console.log(` Space type: ${method.space_type ?? "—"}`);
135
+ console.log(` Dimensions: ${embeddingMapping?.dimension ?? "—"}`);
136
+ console.log(` k-NN index: ${knnSettings ?? "—"}`);
137
+
138
+ // List mapped metadata fields and their distinct values via terms aggs
139
+ const metaProps = indexDef?.mappings?.properties?.metadata?.properties ?? {};
140
+ const metaFieldNames = Object.keys(metaProps);
141
+
142
+ if (metaFieldNames.length === 0) {
143
+ console.log(" Metadata fields: (none)");
144
+ } else {
145
+ const aggs: Record<string, unknown> = {};
146
+ for (const field of metaFieldNames) {
147
+ const fieldType = (metaProps[field] as { type?: string }).type;
148
+ // text fields from dynamic mapping need the .keyword sub-field for aggregations
149
+ const aggField = fieldType === "keyword" ? `metadata.${field}` : `metadata.${field}.keyword`;
150
+ aggs[field] = { terms: { field: aggField, size: 20 } };
151
+ }
152
+ const aggsResult = await rawClient.search({
153
+ index: store.getIndexName(),
154
+ body: { size: 0, aggs },
155
+ });
156
+ const aggsBody = aggsResult.body.aggregations ?? {};
157
+
158
+ console.log(" Metadata fields:");
159
+ for (const [field, v] of Object.entries(metaProps)) {
160
+ const fieldType = (v as { type?: string }).type ?? "?";
161
+ const buckets: Array<{ key: string; doc_count: number }> =
162
+ aggsBody[field]?.buckets ?? [];
163
+ const values = buckets.length > 0
164
+ ? buckets.map((b) => `${b.key} (${b.doc_count})`).join(", ")
165
+ : "(no values)";
166
+ console.log(` ${field} [${fieldType}]: ${values}`);
167
+ }
168
+ }
169
+
170
+ // Data snapshot via match_all (no embedding needed)
171
+ const snapshot = await rawClient.search({
172
+ index: store.getIndexName(),
173
+ body: { size: 10, query: { match_all: {} }, _source: ["id", "content", "namespace", "metadata"] },
174
+ });
175
+ const hits = snapshot.body.hits?.hits ?? [];
176
+ console.log(`\n Documents in index (${hits.length}):`);
177
+ for (const hit of hits) {
178
+ const src = hit._source;
179
+ const ns = src.namespace ? ` [ns:${src.namespace}]` : "";
180
+ const metaPairs = Object.entries(src.metadata ?? {})
181
+ .map(([k, val]) => `${k}=${val}`)
182
+ .join(" ");
183
+ console.log(` ${src.id}${ns} ${metaPairs}`);
184
+ console.log(` "${src.content.substring(0, 80)}..."`);
185
+ }
186
+ console.log();
187
+
188
+ // 5. Direct search
189
+ console.log('5. Direct search: "how does vector search work in OpenSearch?"');
190
+ const searchResults = await store.search(
191
+ "how does vector search work in OpenSearch?",
192
+ { limit: 3 }
193
+ );
194
+ console.log(" Top results:");
195
+ for (const result of searchResults) {
196
+ console.log(
197
+ ` [${result.score.toFixed(3)}] (${
198
+ result.document.metadata?.category
199
+ }) ` +
200
+ result.document.content.substring(0, 80) +
201
+ "..."
202
+ );
203
+ }
204
+ console.log();
205
+
206
+ // 6. Metadata filter tests
207
+ console.log("6. Metadata filter tests");
208
+ console.log(" (All results show [score] (category|source) snippet)\n");
209
+
210
+ function printResults(results: Awaited<ReturnType<typeof store.search>>) {
211
+ if (results.length === 0) {
212
+ console.log(" (no results)");
213
+ return;
214
+ }
215
+ for (const r of results) {
216
+ const m = r.document.metadata ?? {};
217
+ console.log(
218
+ ` [${r.score.toFixed(3)}] (${m.category}|${m.source}) ${r.document.content.substring(0, 70)}...`
219
+ );
220
+ }
221
+ }
222
+
223
+ function assertCategories(
224
+ results: Awaited<ReturnType<typeof store.search>>,
225
+ expected: string,
226
+ label: string
227
+ ) {
228
+ const wrong = results.filter((r) => r.document.metadata?.category !== expected);
229
+ const status = wrong.length === 0 ? "PASS" : `FAIL (${wrong.length} wrong)`;
230
+ console.log(` ${status}: ${label}`);
231
+ }
232
+
233
+ // 6a. Filter by category = "knn"
234
+ console.log(' 6a. category = "knn" (expect only knn docs)');
235
+ const knnResults = await store.search("nearest neighbour algorithm", {
236
+ limit: 5,
237
+ filter: { category: "knn" },
238
+ });
239
+ printResults(knnResults);
240
+ assertCategories(knnResults, "knn", "all results have category=knn");
241
+ console.log();
242
+
243
+ // 6b. Filter by source = "agention"
244
+ console.log(' 6b. source = "agention" (expect only agention docs)');
245
+ const agentionResults = await store.search("vector store RAG", {
246
+ limit: 5,
247
+ filter: { source: "agention" },
248
+ });
249
+ printResults(agentionResults);
250
+ const wrongSource = agentionResults.filter(
251
+ (r) => r.document.metadata?.source !== "agention"
252
+ );
253
+ console.log(
254
+ ` ${wrongSource.length === 0 ? "PASS" : "FAIL"}: all results have source=agention`
255
+ );
256
+ console.log();
257
+
258
+ // 6c. Filter by category = "overview" (only 1 doc exists)
259
+ console.log(' 6c. category = "overview" (expect exactly 1 doc)');
260
+ const overviewResults = await store.search("OpenSearch", {
261
+ limit: 5,
262
+ filter: { category: "overview" },
263
+ });
264
+ printResults(overviewResults);
265
+ console.log(
266
+ ` ${overviewResults.length === 1 ? "PASS" : "FAIL"}: got ${overviewResults.length} result(s), expected 1`
267
+ );
268
+ console.log();
269
+
270
+ // 6d. Filter by non-existent category (expect 0 results)
271
+ console.log(' 6d. category = "nonexistent" (expect 0 results)');
272
+ const emptyResults = await store.search("anything", {
273
+ limit: 5,
274
+ filter: { category: "nonexistent" },
275
+ });
276
+ printResults(emptyResults);
277
+ console.log(
278
+ ` ${emptyResults.length === 0 ? "PASS" : "FAIL"}: got ${emptyResults.length} result(s), expected 0`
279
+ );
280
+ console.log();
281
+
282
+ // 7. Namespace demo
283
+ console.log(
284
+ "7. Namespace demo — adding a document to namespace 'internal'..."
285
+ );
286
+ await store.addDocuments(
287
+ [
288
+ {
289
+ id: "internal-1",
290
+ content: "This is an internal document not shown in public searches.",
291
+ metadata: { source: "internal" },
292
+ },
293
+ ],
294
+ { namespace: "internal" }
295
+ );
296
+
297
+ const publicResults = await store.search("internal document", {
298
+ limit: 3,
299
+ namespace: "public", // won't match the 'internal' doc
300
+ });
301
+ console.log(
302
+ ` Search in namespace 'public' returned ${publicResults.length} results (expected 0 for the internal doc)\n`
303
+ );
304
+
305
+ // 8. Agent with retrieval tool
306
+ console.log("8. Creating Claude agent with OpenSearch retrieval tool...");
307
+ const searchTool = store.toRetrievalTool(
308
+ "Search the knowledge base for information about OpenSearch, vector search, RAG, and Agention.",
309
+ { defaultLimit: 3 }
310
+ );
311
+
312
+ const agent = new ClaudeAgent({
313
+ name: "RAG Assistant",
314
+ id: "rag_assistant",
315
+ model: "claude-haiku-4-5",
316
+ description:
317
+ "You are a helpful assistant. Always use the search tool to retrieve relevant context " +
318
+ "before answering questions. Base your answers on the search results.",
319
+ apiKey: process.env.ANTHROPIC_API_KEY as string,
320
+ tools: [searchTool],
321
+ });
322
+ console.log(" Agent created\n");
323
+
324
+ // 9. Ask the agent a question
325
+ console.log(
326
+ '9. Asking the agent: "What is the HNSW algorithm and how is it configured?"\n'
327
+ );
328
+ const answer = await agent.execute(
329
+ "What is the HNSW algorithm and how is it configured?"
330
+ );
331
+ console.log(`Answer:\n${answer}\n`);
332
+
333
+ // 10. Clean up (delete all docs from the index)
334
+ console.log("10. Clearing the index...");
335
+ await store.clear();
336
+ console.log(" Done.\n");
337
+ }
338
+
339
+ main().catch((err) => {
340
+ console.error("Error:", err);
341
+ process.exit(1);
342
+ });
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "modular-agent-examples",
3
+ "author": "Laurent Zuijdwijk",
4
+ "license": "ISC",
5
+ "version": "0.0.1",
6
+ "dependencies": {
7
+ "@lancedb/lancedb": "^0.18.1",
8
+ "@opensearch-project/opensearch": "^3.5.1",
9
+ "@types/ws": "^8.18.0",
10
+ "colors": "^1.4.0",
11
+ "dotenv": "^16.6.1",
12
+ "install": "^0.13.0",
13
+ "npm": "^11.2.0",
14
+ "ora": "^8.2.0",
15
+ "ora-classic": "^5.4.2",
16
+ "ws": "^8.18.1"
17
+ },
18
+ "overrides": {
19
+ "ajv": "^8.17.1"
20
+ },
21
+ "devDependencies": {
22
+ "tokenx": "^1.3.0"
23
+ }
24
+ }