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,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequential Executor Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates chaining agents in sequence where each agent's output
|
|
5
|
+
* becomes the input for the next agent.
|
|
6
|
+
*
|
|
7
|
+
* Use case: Research pipeline with fact-checking and summarization
|
|
8
|
+
*
|
|
9
|
+
* For data-fetching pipelines with factories, see: ./data-pipeline/
|
|
10
|
+
*/
|
|
11
|
+
import "dotenv/config";
|
|
12
|
+
import { ClaudeAgent } from "../../lib/agents/anthropic/ClaudeAgent";
|
|
13
|
+
import { AgentGraph } from "../../lib/graph/AgentGraph";
|
|
14
|
+
|
|
15
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
16
|
+
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
console.error("Please set ANTHROPIC_API_KEY environment variable");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Agent Definitions
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
const researchAgent = new ClaudeAgent({
|
|
27
|
+
id: "researcher",
|
|
28
|
+
name: "Research Agent",
|
|
29
|
+
description: `You are a research agent. Given a topic, provide detailed factual information.
|
|
30
|
+
Be thorough but concise. Focus on key facts, dates, and verifiable information.
|
|
31
|
+
Output your findings in a structured format.`,
|
|
32
|
+
apiKey,
|
|
33
|
+
maxTokens: 1024,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const factCheckerAgent = new ClaudeAgent({
|
|
37
|
+
id: "fact-checker",
|
|
38
|
+
name: "Fact Checker Agent",
|
|
39
|
+
description: `You are a fact-checking agent. You receive research findings and verify their accuracy.
|
|
40
|
+
Mark any claims that seem questionable or need verification.
|
|
41
|
+
Add confidence levels (High/Medium/Low) to each fact.
|
|
42
|
+
Correct any obvious errors you find.`,
|
|
43
|
+
apiKey,
|
|
44
|
+
maxTokens: 1024,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const summaryAgent = new ClaudeAgent({
|
|
48
|
+
id: "summarizer",
|
|
49
|
+
name: "Summary Agent",
|
|
50
|
+
description: `You are a summarization agent. You receive fact-checked research and create a clear,
|
|
51
|
+
concise summary suitable for a general audience.
|
|
52
|
+
Highlight the most important points.
|
|
53
|
+
Include a brief conclusion with key takeaways.
|
|
54
|
+
Keep the summary under 200 words.`,
|
|
55
|
+
apiKey,
|
|
56
|
+
maxTokens: 512,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Example 1: Basic Sequential Pipeline
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
async function runSequentialExample() {
|
|
64
|
+
console.log("=== Sequential Executor Example ===\n");
|
|
65
|
+
console.log("Pipeline: Research -> Fact-Check -> Summarize\n");
|
|
66
|
+
|
|
67
|
+
const researchPipeline = AgentGraph.sequential(
|
|
68
|
+
researchAgent,
|
|
69
|
+
factCheckerAgent,
|
|
70
|
+
summaryAgent
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const topic = "The discovery of penicillin and its impact on medicine";
|
|
74
|
+
console.log(`Topic: "${topic}"\n`);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = await researchPipeline.execute(topic);
|
|
78
|
+
console.log("\n=== Final Summary ===\n");
|
|
79
|
+
console.log(result);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("Pipeline failed:", error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Example 2: Raw Input Mode (no JSON wrapping)
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
async function runRawSequentialExample() {
|
|
90
|
+
console.log("\n=== Sequential with Raw Input ===\n");
|
|
91
|
+
|
|
92
|
+
const translator = new ClaudeAgent({
|
|
93
|
+
id: "translator",
|
|
94
|
+
name: "Translator",
|
|
95
|
+
description: `Translate the input text to French. Output only the translation, nothing else.`,
|
|
96
|
+
apiKey,
|
|
97
|
+
maxTokens: 256,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const polisher = new ClaudeAgent({
|
|
101
|
+
id: "polisher",
|
|
102
|
+
name: "Polisher",
|
|
103
|
+
description: `You receive French text. Make it more elegant and poetic while keeping the meaning.
|
|
104
|
+
Output only the polished French text.`,
|
|
105
|
+
apiKey,
|
|
106
|
+
maxTokens: 256,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// wrapInput: false passes raw output between agents
|
|
110
|
+
const translationPipeline = AgentGraph.sequential(
|
|
111
|
+
{ wrapInput: false },
|
|
112
|
+
translator,
|
|
113
|
+
polisher
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const englishText =
|
|
117
|
+
"The sun sets slowly over the mountains, painting the sky in shades of orange and purple.";
|
|
118
|
+
console.log(`English: "${englishText}"\n`);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await translationPipeline.execute(englishText);
|
|
122
|
+
console.log(`Polished French: "${result}"`);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error("Translation failed:", error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Main
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
async function main() {
|
|
133
|
+
await runSequentialExample();
|
|
134
|
+
await runRawSequentialExample();
|
|
135
|
+
|
|
136
|
+
console.log("\n---");
|
|
137
|
+
console.log("For data-fetching pipelines with DB/API integration,");
|
|
138
|
+
console.log("see: examples/graph/data-pipeline/");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voting System Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates using a judge agent to select the best answer
|
|
5
|
+
* from multiple expert responses.
|
|
6
|
+
*
|
|
7
|
+
* Use case: Getting diverse opinions and synthesizing the best answer
|
|
8
|
+
*/
|
|
9
|
+
import "dotenv/config";
|
|
10
|
+
import { ClaudeAgent } from "../../lib/agents/anthropic/ClaudeAgent";
|
|
11
|
+
import { AgentGraph, VotingInput } from "../../lib/graph/AgentGraph";
|
|
12
|
+
|
|
13
|
+
const apiKey = process.env.ANTHROPIC_API_KEY as string;
|
|
14
|
+
|
|
15
|
+
if (!apiKey) {
|
|
16
|
+
console.error("Please set ANTHROPIC_API_KEY environment variable");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Three experts with different approaches
|
|
21
|
+
const conservativeExpert = new ClaudeAgent({
|
|
22
|
+
id: "conservative",
|
|
23
|
+
name: "Conservative Expert",
|
|
24
|
+
description: `You are a conservative, risk-averse advisor.
|
|
25
|
+
When answering questions, prioritize safety, proven methods, and caution.
|
|
26
|
+
Recommend established approaches over experimental ones.
|
|
27
|
+
Keep responses under 100 words.`,
|
|
28
|
+
apiKey,
|
|
29
|
+
maxTokens: 256,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const innovativeExpert = new ClaudeAgent({
|
|
33
|
+
id: "innovative",
|
|
34
|
+
name: "Innovative Expert",
|
|
35
|
+
description: `You are an innovative, forward-thinking advisor.
|
|
36
|
+
When answering questions, prioritize new approaches, cutting-edge solutions, and bold ideas.
|
|
37
|
+
Recommend creative and unconventional methods.
|
|
38
|
+
Keep responses under 100 words.`,
|
|
39
|
+
apiKey,
|
|
40
|
+
maxTokens: 256,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const balancedExpert = new ClaudeAgent({
|
|
44
|
+
id: "balanced",
|
|
45
|
+
name: "Balanced Expert",
|
|
46
|
+
description: `You are a balanced, pragmatic advisor.
|
|
47
|
+
When answering questions, weigh both traditional and innovative approaches.
|
|
48
|
+
Recommend solutions that balance risk and reward.
|
|
49
|
+
Keep responses under 100 words.`,
|
|
50
|
+
apiKey,
|
|
51
|
+
maxTokens: 256,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Judge agent to evaluate and select the best answer
|
|
55
|
+
const judgeAgent = new ClaudeAgent({
|
|
56
|
+
id: "judge",
|
|
57
|
+
name: "Judge",
|
|
58
|
+
description: `You are an impartial judge evaluating expert opinions.
|
|
59
|
+
Analyze each expert's response for:
|
|
60
|
+
- Practicality and feasibility
|
|
61
|
+
- Completeness of the answer
|
|
62
|
+
- Quality of reasoning
|
|
63
|
+
|
|
64
|
+
Select the best answer OR synthesize a superior answer combining the best elements.
|
|
65
|
+
Explain your choice briefly, then provide the final recommended answer.`,
|
|
66
|
+
apiKey,
|
|
67
|
+
maxTokens: 512,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
async function runBasicVotingExample() {
|
|
71
|
+
console.log("=== Basic Voting System Example ===\n");
|
|
72
|
+
|
|
73
|
+
const question = "What's the best strategy for a small business to adopt AI?";
|
|
74
|
+
console.log(`Question: "${question}"\n`);
|
|
75
|
+
|
|
76
|
+
// Get opinions from all experts
|
|
77
|
+
console.log("Gathering expert opinions...\n");
|
|
78
|
+
|
|
79
|
+
const [conservative, innovative, balanced] = await Promise.all([
|
|
80
|
+
conservativeExpert.execute(question),
|
|
81
|
+
innovativeExpert.execute(question),
|
|
82
|
+
balancedExpert.execute(question),
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
console.log("--- Conservative Expert ---");
|
|
86
|
+
console.log(conservative);
|
|
87
|
+
console.log("\n--- Innovative Expert ---");
|
|
88
|
+
console.log(innovative);
|
|
89
|
+
console.log("\n--- Balanced Expert ---");
|
|
90
|
+
console.log(balanced);
|
|
91
|
+
|
|
92
|
+
// Create voting system
|
|
93
|
+
const votingSystem = AgentGraph.votingSystem(judgeAgent);
|
|
94
|
+
|
|
95
|
+
// Submit for voting
|
|
96
|
+
const votingInput: VotingInput = {
|
|
97
|
+
originalInput: question,
|
|
98
|
+
solutions: [
|
|
99
|
+
conservative as string,
|
|
100
|
+
innovative as string,
|
|
101
|
+
balanced as string,
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
console.log("\n--- Judge's Verdict ---\n");
|
|
106
|
+
const verdict = await votingSystem.execute(votingInput);
|
|
107
|
+
console.log(verdict);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Custom prompt template example
|
|
111
|
+
async function runCustomTemplateExample() {
|
|
112
|
+
console.log("\n=== Custom Voting Template Example ===\n");
|
|
113
|
+
|
|
114
|
+
const customJudge = new ClaudeAgent({
|
|
115
|
+
id: "custom-judge",
|
|
116
|
+
name: "Custom Judge",
|
|
117
|
+
description: `You evaluate code solutions. Pick the best one based on:
|
|
118
|
+
- Code quality and readability
|
|
119
|
+
- Performance considerations
|
|
120
|
+
- Best practices
|
|
121
|
+
|
|
122
|
+
Output only the winning solution number and a brief explanation.`,
|
|
123
|
+
apiKey,
|
|
124
|
+
maxTokens: 256,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const votingSystem = AgentGraph.votingSystem(customJudge, {
|
|
128
|
+
promptTemplate: `## Code Review Task
|
|
129
|
+
|
|
130
|
+
**Original Request:** {originalQuestion}
|
|
131
|
+
|
|
132
|
+
**Submitted Solutions:**
|
|
133
|
+
{expertAnswers}
|
|
134
|
+
|
|
135
|
+
Evaluate each solution and declare a winner.`,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const solutions = [
|
|
139
|
+
"Solution using a for loop: for(let i=0; i<arr.length; i++) { sum += arr[i]; }",
|
|
140
|
+
"Solution using reduce: arr.reduce((sum, val) => sum + val, 0)",
|
|
141
|
+
"Solution using forEach: let sum=0; arr.forEach(x => sum += x);",
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
const result = await votingSystem.execute({
|
|
145
|
+
originalInput: "Sum all numbers in an array",
|
|
146
|
+
solutions,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log("Judge's decision:");
|
|
150
|
+
console.log(result);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Run examples
|
|
154
|
+
async function main() {
|
|
155
|
+
await runBasicVotingExample();
|
|
156
|
+
await runCustomTemplateExample();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const neo4j = require("neo4j-driver");
|
|
2
|
+
|
|
3
|
+
// Neo4j connection details
|
|
4
|
+
const uri = "bolt://localhost:7687";
|
|
5
|
+
const user = "neo4j";
|
|
6
|
+
const password = "your_password";
|
|
7
|
+
|
|
8
|
+
const driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
|
|
9
|
+
|
|
10
|
+
// Helper function to generate random vectors
|
|
11
|
+
function generateRandomVector(dim) {
|
|
12
|
+
return Array.from({ length: dim }, () => (Math.random() * 2 - 1).toFixed(4));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function runTests() {
|
|
16
|
+
const session = driver.session();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Clear the database
|
|
20
|
+
await session.run("MATCH (n) DETACH DELETE n");
|
|
21
|
+
console.log("Database cleared.");
|
|
22
|
+
|
|
23
|
+
// Insert nodes with vector embeddings
|
|
24
|
+
const items = [
|
|
25
|
+
{ name: "Item1", embedding: generateRandomVector(3) },
|
|
26
|
+
{ name: "Item2", embedding: generateRandomVector(3) },
|
|
27
|
+
{ name: "Item3", embedding: generateRandomVector(3) },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
await session.run(
|
|
32
|
+
"CREATE (i:Item {name: $name, embedding: $embedding})",
|
|
33
|
+
{ name: item.name, embedding: item.embedding.map(Number) }
|
|
34
|
+
);
|
|
35
|
+
console.log(
|
|
36
|
+
`Inserted node: ${item.name} with embedding ${item.embedding}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Perform vector similarity search (cosine similarity)
|
|
41
|
+
const queryVector = generateRandomVector(3);
|
|
42
|
+
console.log(`Querying with vector: ${queryVector}`);
|
|
43
|
+
|
|
44
|
+
const result = await session.run(
|
|
45
|
+
`
|
|
46
|
+
MATCH (i:Item)
|
|
47
|
+
WITH i, $queryVector AS query
|
|
48
|
+
RETURN i.name, i.embedding,
|
|
49
|
+
gds.similarity.cosine(i.embedding, query) AS similarity
|
|
50
|
+
ORDER BY similarity DESC
|
|
51
|
+
LIMIT 2
|
|
52
|
+
`,
|
|
53
|
+
{ queryVector: queryVector.map(Number) }
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
console.log("Vector similarity search results:");
|
|
57
|
+
result.records.forEach((record) => {
|
|
58
|
+
console.log(
|
|
59
|
+
`Item: ${record.get("i.name")}, Embedding: ${record.get(
|
|
60
|
+
"i.embedding"
|
|
61
|
+
)}, Similarity: ${record.get("similarity")}`
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Create a simple graph with relationships
|
|
66
|
+
await session.run(
|
|
67
|
+
`
|
|
68
|
+
MATCH (i1:Item {name: 'Item1'}), (i2:Item {name: 'Item2'})
|
|
69
|
+
CREATE (i1)-[:RELATED_TO {weight: 0.8}]->(i2)
|
|
70
|
+
`
|
|
71
|
+
);
|
|
72
|
+
console.log("Created relationship between Item1 and Item2.");
|
|
73
|
+
|
|
74
|
+
// Query the graph
|
|
75
|
+
const graphResult = await session.run(
|
|
76
|
+
`
|
|
77
|
+
MATCH (i:Item)-[r:RELATED_TO]->(other)
|
|
78
|
+
RETURN i.name, type(r), other.name, r.weight
|
|
79
|
+
`
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
console.log("Graph query results:");
|
|
83
|
+
graphResult.records.forEach((record) => {
|
|
84
|
+
console.log(
|
|
85
|
+
`From: ${record.get("i.name")} -> ${record.get(
|
|
86
|
+
"other.name"
|
|
87
|
+
)} via ${record.get("type(r)")}, Weight: ${record.get("r.weight")}`
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("Error:", error);
|
|
92
|
+
} finally {
|
|
93
|
+
await session.close();
|
|
94
|
+
await driver.close();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Run the tests
|
|
99
|
+
runTests();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "graph-rag",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"author": "",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"description": "",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"neo4j-driver": "^5.28.1"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History Compression Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the history plugin system with two complementary strategies:
|
|
5
|
+
*
|
|
6
|
+
* 1. toolResultMaskingPlugin — Read-time masking of old tool results.
|
|
7
|
+
* Full content is always stored; only the view seen by the LLM is masked.
|
|
8
|
+
* The agent can fetch any masked result on demand via retrieve_tool_result.
|
|
9
|
+
*
|
|
10
|
+
* 2. compressionPlugin — Rolling LLM summarization of old conversation turns.
|
|
11
|
+
* Older turns are replaced by a compact summary entry, reducing token usage
|
|
12
|
+
* while preserving the gist of what happened.
|
|
13
|
+
*
|
|
14
|
+
* Both plugins compose on the same history instance. Tool result masking is
|
|
15
|
+
* free (sync, no LLM calls). Rolling summarization runs automatically via
|
|
16
|
+
* autoReduceWhen when the token budget is exceeded — no manual history.reduce()
|
|
17
|
+
* call required.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import "dotenv/config";
|
|
21
|
+
import { ClaudeAgent } from "../lib/agents/anthropic/ClaudeAgent";
|
|
22
|
+
import { History } from "../lib/history/History";
|
|
23
|
+
import { Tool } from "../lib/tools/Tool";
|
|
24
|
+
import { compressionPlugin } from "../lib/history/plugins/compressionPlugin";
|
|
25
|
+
import { toolResultMaskingPlugin } from "../lib/history/plugins/toolResultMaskingPlugin";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Simulated tools
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
const searchTool = new Tool<string>({
|
|
32
|
+
name: "web_search",
|
|
33
|
+
description: "Search the web for information",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
query: { type: "string", description: "Search query" },
|
|
38
|
+
},
|
|
39
|
+
required: ["query"],
|
|
40
|
+
},
|
|
41
|
+
execute: async ({ query }: { query: string }): Promise<string> => {
|
|
42
|
+
// Simulate a large search result
|
|
43
|
+
return (
|
|
44
|
+
`Search results for "${query}":\n` +
|
|
45
|
+
"1. Result one with detailed information about the topic...\n".repeat(
|
|
46
|
+
20
|
|
47
|
+
) +
|
|
48
|
+
"2. Result two with more details...\n".repeat(20)
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const calculatorTool = new Tool<number>({
|
|
54
|
+
name: "calculator",
|
|
55
|
+
description: "Perform arithmetic calculations",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
expression: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Math expression to evaluate",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
required: ["expression"],
|
|
65
|
+
},
|
|
66
|
+
execute: async ({ expression }: { expression: string }): Promise<number> => {
|
|
67
|
+
// Safe eval for demo purposes only
|
|
68
|
+
return Function(`"use strict"; return (${expression})`)() as number;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Setup
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
77
|
+
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");
|
|
78
|
+
|
|
79
|
+
// Dedicated summarization agent — cheap, fast model, no tools
|
|
80
|
+
const summaryAgent = new ClaudeAgent({
|
|
81
|
+
id: "summarizer",
|
|
82
|
+
name: "Summarizer",
|
|
83
|
+
description: "Summarizes conversation history concisely",
|
|
84
|
+
apiKey,
|
|
85
|
+
model: "claude-haiku-4-5-20251001",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Masking plugin: keep 1 recent web_search result verbatim; mask older ones.
|
|
89
|
+
// Calculator results are excluded — they're tiny and always useful verbatim.
|
|
90
|
+
const maskingPlugin = toolResultMaskingPlugin({
|
|
91
|
+
keepRecentResults: 1,
|
|
92
|
+
exclude: ["calculator"],
|
|
93
|
+
minTokensToMask: 50,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Shared history with both plugins registered.
|
|
97
|
+
// compressionPlugin auto-triggers when the history exceeds 2 000 tokens.
|
|
98
|
+
const history = new History([], { maxTokens: 2000 })
|
|
99
|
+
.use(maskingPlugin)
|
|
100
|
+
.use(compressionPlugin(summaryAgent, { autoReduceWhen: { maxTokens: 2000 } }));
|
|
101
|
+
|
|
102
|
+
// Surface async plugin errors
|
|
103
|
+
history.on("pluginError", (error: Error, _plugin: unknown, hook: string) => {
|
|
104
|
+
console.error(`[pluginError] ${hook}: ${error.message}`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Main agent with the retrieve tool wired in
|
|
108
|
+
const agent = new ClaudeAgent(
|
|
109
|
+
{
|
|
110
|
+
id: "assistant",
|
|
111
|
+
name: "Research Assistant",
|
|
112
|
+
description: "A research assistant with web search and calculation tools",
|
|
113
|
+
apiKey,
|
|
114
|
+
model: "claude-sonnet-4-6",
|
|
115
|
+
tools: [
|
|
116
|
+
searchTool,
|
|
117
|
+
calculatorTool,
|
|
118
|
+
maskingPlugin.retrieveTool, // allows retrieval of masked results
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
history
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Simulated conversation
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
async function main() {
|
|
129
|
+
console.log("=== History Compression Example ===\n");
|
|
130
|
+
|
|
131
|
+
const turns = [
|
|
132
|
+
"Search for information about the history of the internet.",
|
|
133
|
+
"Now search for information about artificial intelligence milestones.",
|
|
134
|
+
"What is 1024 * 768?",
|
|
135
|
+
"Search for quantum computing breakthroughs in 2024.",
|
|
136
|
+
"Summarise everything we've discussed so far.",
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
for (const turn of turns) {
|
|
140
|
+
console.log(`\n[User] ${turn}`);
|
|
141
|
+
console.log(
|
|
142
|
+
` History before: ${history.length} entries, ~${history.totalEstimatedTokens} tokens`
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const response = await agent.execute(turn);
|
|
146
|
+
console.log(`[Assistant] ${response.slice(0, 200)}...`);
|
|
147
|
+
console.log(
|
|
148
|
+
` History after: ${history.length} entries, ~${history.totalEstimatedTokens} tokens`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(
|
|
153
|
+
"\nNote: compressionPlugin auto-triggered whenever history exceeded 2 000 tokens."
|
|
154
|
+
);
|
|
155
|
+
console.log(
|
|
156
|
+
"Tool results are masked in the agent view but stored in full."
|
|
157
|
+
);
|
|
158
|
+
console.log(
|
|
159
|
+
"The agent can call retrieve_tool_result(tool_call_id) to access any masked result."
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
main().catch(console.error);
|