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/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
|
+
}
|