matimo-examples 0.1.0-alpha.7
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/.env.example +36 -0
- package/LICENSE +21 -0
- package/README.md +525 -0
- package/agents/decorator-pattern-agent.ts +368 -0
- package/agents/factory-pattern-agent.ts +253 -0
- package/agents/langchain-agent.ts +146 -0
- package/edit/edit-decorator.ts +128 -0
- package/edit/edit-factory.ts +120 -0
- package/edit/edit-langchain.ts +272 -0
- package/execute/execute-decorator.ts +49 -0
- package/execute/execute-factory.ts +46 -0
- package/execute/execute-langchain.ts +163 -0
- package/gmail/README.md +345 -0
- package/gmail/gmail-decorator.ts +216 -0
- package/gmail/gmail-factory.ts +231 -0
- package/gmail/gmail-langchain.ts +201 -0
- package/package.json +58 -0
- package/postgres/README.md +188 -0
- package/postgres/postgres-decorator.ts +198 -0
- package/postgres/postgres-factory.ts +180 -0
- package/postgres/postgres-langchain.ts +213 -0
- package/postgres/postgres-with-approval.ts +250 -0
- package/read/read-decorator.ts +107 -0
- package/read/read-factory.ts +104 -0
- package/read/read-langchain.ts +253 -0
- package/search/search-decorator.ts +154 -0
- package/search/search-factory.ts +129 -0
- package/search/search-langchain.ts +215 -0
- package/slack/README.md +339 -0
- package/slack/slack-decorator.ts +245 -0
- package/slack/slack-factory.ts +226 -0
- package/slack/slack-langchain.ts +242 -0
- package/tsconfig.json +20 -0
- package/web/web-decorator.ts +52 -0
- package/web/web-factory.ts +70 -0
- package/web/web-langchain.ts +163 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Matimo Decorator Pattern - True AI Agent with TypeScript @tool Decorators
|
|
4
|
+
*
|
|
5
|
+
* The agent receives a prompt, uses OpenAI to decide which tool to use,
|
|
6
|
+
* then executes that tool via Matimo using real TypeScript @tool decorators.
|
|
7
|
+
*
|
|
8
|
+
* Run: npm run agent:decorator
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import 'dotenv/config';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
15
|
+
import { MatimoInstance, tool, setGlobalMatimoInstance } from 'matimo';
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Decorator Pattern Agent - Uses @tool decorators for automatic execution
|
|
21
|
+
*/
|
|
22
|
+
class DecoratorPatternAgent {
|
|
23
|
+
private llm: ChatOpenAI;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private matimo: MatimoInstance,
|
|
27
|
+
llm: ChatOpenAI
|
|
28
|
+
) {
|
|
29
|
+
this.llm = llm;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Calculator tool - automatically executes via @tool decorator
|
|
34
|
+
* Parameters map to tool parameters in order: operation, a, b
|
|
35
|
+
*/
|
|
36
|
+
@tool('calculator')
|
|
37
|
+
async calculate(operation: string, a: number, b: number): Promise<unknown> {
|
|
38
|
+
// Decorator automatically calls: matimo.execute('calculator', { operation, a, b })
|
|
39
|
+
// Method body is not executed - decorator intercepts the call
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Echo tool - automatically executes via @tool decorator
|
|
45
|
+
* Parameters map to tool parameter: message
|
|
46
|
+
*/
|
|
47
|
+
@tool('echo-tool')
|
|
48
|
+
async echo(message: string): Promise<unknown> {
|
|
49
|
+
// Decorator automatically calls: matimo.execute('echo-tool', { message })
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* HTTP client tool - automatically executes via @tool decorator
|
|
55
|
+
* Parameters map to tool parameters: method, url
|
|
56
|
+
*/
|
|
57
|
+
@tool('http-client')
|
|
58
|
+
async fetch(method: string, url: string): Promise<unknown> {
|
|
59
|
+
// Decorator automatically calls: matimo.execute('http-client', { method, url })
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate OpenAI function schemas from Matimo tool definitions
|
|
65
|
+
* Single source of truth - schemas come from tool YAML, not duplicated here
|
|
66
|
+
*/
|
|
67
|
+
private getToolSchemas() {
|
|
68
|
+
return this.matimo.listTools().map((tool) => ({
|
|
69
|
+
type: 'function',
|
|
70
|
+
function: {
|
|
71
|
+
name: tool.name,
|
|
72
|
+
description: tool.description,
|
|
73
|
+
parameters: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: Object.entries(tool.parameters || {}).reduce(
|
|
76
|
+
(acc, [paramName, param]) => ({
|
|
77
|
+
...acc,
|
|
78
|
+
[paramName]: {
|
|
79
|
+
type: param.type,
|
|
80
|
+
enum: param.enum,
|
|
81
|
+
description: param.description,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
{}
|
|
85
|
+
),
|
|
86
|
+
required: Object.entries(tool.parameters || {})
|
|
87
|
+
.filter(([_, param]) => param.required)
|
|
88
|
+
.map(([paramName]) => paramName),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Process a prompt - AI decides which tool to call
|
|
96
|
+
*/
|
|
97
|
+
async process(prompt: string): Promise<void> {
|
|
98
|
+
console.info(`\nโ Prompt: "${prompt}"`);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Prepare system message for tool calling
|
|
102
|
+
const systemMessage = `You are an AI assistant with access to tools.
|
|
103
|
+
Based on the user's request, decide which tool to use and extract the EXACT required parameters.
|
|
104
|
+
IMPORTANT: Use exact parameter names and enum values as specified.
|
|
105
|
+
Respond ONLY with valid JSON in this format: {"tool": "<tool_name>", "parameters": {<exact_params>}}`;
|
|
106
|
+
|
|
107
|
+
// Get tool schemas dynamically from Matimo
|
|
108
|
+
const toolSchemas = this.getToolSchemas();
|
|
109
|
+
|
|
110
|
+
// Build detailed tool specifications for the prompt
|
|
111
|
+
const toolSpecifications = toolSchemas
|
|
112
|
+
.map((t) => {
|
|
113
|
+
const paramSpecs = Object.entries(t.function.parameters.properties)
|
|
114
|
+
.map(([paramName, prop]: [string, any]) => {
|
|
115
|
+
let spec = `${paramName} (${prop.type})`;
|
|
116
|
+
if (prop.enum) {
|
|
117
|
+
spec += ` - valid values: [${prop.enum.join(', ')}]`;
|
|
118
|
+
}
|
|
119
|
+
spec += ` - ${prop.description}`;
|
|
120
|
+
return spec;
|
|
121
|
+
})
|
|
122
|
+
.join('; ');
|
|
123
|
+
return `${t.function.name}: ${t.function.description}\n Parameters: ${paramSpecs}`;
|
|
124
|
+
})
|
|
125
|
+
.join('\n\n');
|
|
126
|
+
|
|
127
|
+
// Create a message with proper formatting for function calling
|
|
128
|
+
const messages = [
|
|
129
|
+
{
|
|
130
|
+
type: 'system',
|
|
131
|
+
content: systemMessage,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
type: 'human',
|
|
135
|
+
content: `User request: "${prompt}"\n\nAvailable tools:\n${toolSpecifications}`,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
// Call LLM with tool definitions
|
|
140
|
+
const response = await this.llm.invoke(messages as any);
|
|
141
|
+
|
|
142
|
+
// Parse the LLM response
|
|
143
|
+
let toolName: string | null = null;
|
|
144
|
+
let toolParams: Record<string, unknown> | null = null;
|
|
145
|
+
|
|
146
|
+
// Try to extract tool call from response
|
|
147
|
+
const content = response.content;
|
|
148
|
+
|
|
149
|
+
if (typeof content === 'string') {
|
|
150
|
+
// Try to parse as JSON
|
|
151
|
+
try {
|
|
152
|
+
const parsed = JSON.parse(content);
|
|
153
|
+
toolName = parsed.tool || parsed.function?.name;
|
|
154
|
+
toolParams = parsed.parameters || parsed.function?.parameters || parsed;
|
|
155
|
+
} catch {
|
|
156
|
+
// If not JSON, try to extract from text
|
|
157
|
+
const jsonMatch = content.match(/\{[^{}]*"tool"[^{}]*\}/);
|
|
158
|
+
if (jsonMatch) {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
161
|
+
toolName = parsed.tool;
|
|
162
|
+
toolParams = parsed.parameters;
|
|
163
|
+
} catch {
|
|
164
|
+
// Continue without params
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If we found a tool, execute it via decorated method
|
|
171
|
+
if (toolName && toolParams) {
|
|
172
|
+
await this.executeTool(toolName, toolParams);
|
|
173
|
+
} else {
|
|
174
|
+
console.info(`\nโ ๏ธ No tool call detected in response`);
|
|
175
|
+
console.info(
|
|
176
|
+
`Response: ${typeof content === 'string' ? content.substring(0, 200) : content}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`\nโ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Map of tool names to their decorated methods
|
|
186
|
+
* Built at runtime to discover which tools this agent supports
|
|
187
|
+
*/
|
|
188
|
+
private getToolMethodMap(): Map<string, string> {
|
|
189
|
+
const toolMap = new Map<string, string>();
|
|
190
|
+
|
|
191
|
+
// Get all methods decorated with @tool
|
|
192
|
+
// The decorator stores tool name in a metadata property
|
|
193
|
+
for (const [methodName, descriptor] of Object.entries(
|
|
194
|
+
Object.getOwnPropertyDescriptors(Object.getPrototypeOf(this))
|
|
195
|
+
)) {
|
|
196
|
+
if (typeof descriptor.value === 'function') {
|
|
197
|
+
// Check if method has tool metadata (added by decorator)
|
|
198
|
+
const method = descriptor.value as any;
|
|
199
|
+
if (method.__toolName) {
|
|
200
|
+
toolMap.set(method.__toolName, methodName);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Manual mapping as fallback (decorators should set __toolName)
|
|
206
|
+
// This ensures we catch all @tool decorated methods
|
|
207
|
+
toolMap.set('calculator', 'calculate');
|
|
208
|
+
toolMap.set('echo-tool', 'echo');
|
|
209
|
+
toolMap.set('http-client', 'fetch');
|
|
210
|
+
|
|
211
|
+
return toolMap;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Execute a tool via decorated method
|
|
216
|
+
* Uses reflection to dynamically call the appropriate decorated method
|
|
217
|
+
* This keeps decorators as the source of truth for agent API
|
|
218
|
+
*/
|
|
219
|
+
private async executeTool(toolName: string, params: Record<string, unknown>): Promise<void> {
|
|
220
|
+
try {
|
|
221
|
+
// Normalize parameters based on tool
|
|
222
|
+
let normalizedParams = params;
|
|
223
|
+
|
|
224
|
+
// Calculator: Handle "operands" array format by converting to a, b
|
|
225
|
+
if (toolName === 'calculator' && params.operands && Array.isArray(params.operands)) {
|
|
226
|
+
const [a, b] = params.operands as number[];
|
|
227
|
+
normalizedParams = {
|
|
228
|
+
operation: params.operation,
|
|
229
|
+
a,
|
|
230
|
+
b,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.info(`\n๐ง Using tool: ${toolName}`);
|
|
235
|
+
console.info(` Parameters: ${JSON.stringify(normalizedParams)}`);
|
|
236
|
+
|
|
237
|
+
// Find the decorated method for this tool
|
|
238
|
+
const toolMethodMap = this.getToolMethodMap();
|
|
239
|
+
const methodName = toolMethodMap.get(toolName);
|
|
240
|
+
|
|
241
|
+
if (!methodName) {
|
|
242
|
+
console.info(`\nโ Tool '${toolName}' not in agent's API`);
|
|
243
|
+
console.info(`Available tools: ${Array.from(toolMethodMap.keys()).join(', ')}`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Dynamically call the decorated method
|
|
248
|
+
// The decorator intercepts the call and executes the tool
|
|
249
|
+
const method = (this as any)[methodName];
|
|
250
|
+
if (typeof method !== 'function') {
|
|
251
|
+
console.info(`\nโ Method '${methodName}' for tool '${toolName}' not found`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Convert params object to positional args matching method signature
|
|
256
|
+
const args = this.getMethodArgsFromParams(toolName, normalizedParams);
|
|
257
|
+
const result = await method.apply(this, args);
|
|
258
|
+
|
|
259
|
+
// Format result
|
|
260
|
+
if (typeof result === 'object' && result !== null) {
|
|
261
|
+
const resultData = result as any;
|
|
262
|
+
if (resultData.stdout) {
|
|
263
|
+
try {
|
|
264
|
+
const parsed = JSON.parse(resultData.stdout);
|
|
265
|
+
console.info(`\nโ
Result:`, parsed);
|
|
266
|
+
} catch {
|
|
267
|
+
console.info(`\nโ
Result:`, resultData.stdout);
|
|
268
|
+
}
|
|
269
|
+
} else if (resultData.data) {
|
|
270
|
+
// HTTP response
|
|
271
|
+
console.info(
|
|
272
|
+
`\nโ
Result (HTTP ${resultData.statusCode}):`,
|
|
273
|
+
typeof resultData.data === 'string'
|
|
274
|
+
? resultData.data.substring(0, 200)
|
|
275
|
+
: JSON.stringify(resultData.data).substring(0, 200)
|
|
276
|
+
);
|
|
277
|
+
} else {
|
|
278
|
+
console.info(`\nโ
Result:`, result);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error(`\nโ Tool Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Convert parameter object to positional arguments for method call
|
|
288
|
+
* Maps normalized params to the decorated method's parameter order
|
|
289
|
+
*/
|
|
290
|
+
private getMethodArgsFromParams(toolName: string, params: Record<string, unknown>): unknown[] {
|
|
291
|
+
switch (toolName) {
|
|
292
|
+
case 'calculator':
|
|
293
|
+
return [params.operation, params.a, params.b];
|
|
294
|
+
case 'echo-tool':
|
|
295
|
+
return [params.message];
|
|
296
|
+
case 'http-client':
|
|
297
|
+
return [params.method, params.url];
|
|
298
|
+
default:
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Run decorator pattern agent with prompts
|
|
306
|
+
*/
|
|
307
|
+
async function runDecoratorPatternAgent() {
|
|
308
|
+
console.info('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
309
|
+
console.info('โ Matimo Decorator Pattern - True AI Agent โ');
|
|
310
|
+
console.info('โ (AI decides which tool to use based on prompt) โ');
|
|
311
|
+
console.info('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n');
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
315
|
+
if (!apiKey) {
|
|
316
|
+
console.error('โ Error: OPENAI_API_KEY not set in .env');
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Initialize Matimo with auto-discovery
|
|
321
|
+
console.info('๐ Initializing Matimo...');
|
|
322
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
323
|
+
|
|
324
|
+
// Set global Matimo instance for @tool decorators
|
|
325
|
+
setGlobalMatimoInstance(matimo);
|
|
326
|
+
|
|
327
|
+
const matimoTools = matimo.listTools();
|
|
328
|
+
console.info(`๐ฆ Loaded ${matimoTools.length} tools:\n`);
|
|
329
|
+
matimoTools.forEach((t) => {
|
|
330
|
+
console.info(` โข ${t.name}`);
|
|
331
|
+
console.info(` ${t.description}\n`);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Initialize OpenAI LLM
|
|
335
|
+
console.info('๐ค Initializing OpenAI LLM (gpt-3.5-turbo)...\n');
|
|
336
|
+
const llm = new ChatOpenAI({
|
|
337
|
+
modelName: 'gpt-4o-mini',
|
|
338
|
+
temperature: 0,
|
|
339
|
+
openAIApiKey: apiKey,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Create agent
|
|
343
|
+
const agent = new DecoratorPatternAgent(matimo, llm);
|
|
344
|
+
|
|
345
|
+
// Test prompts - each should trigger a different tool
|
|
346
|
+
const prompts = [
|
|
347
|
+
'๐งฎ What is 42 plus 8?',
|
|
348
|
+
'๐ Echo the message: "Decorator pattern is elegant and powerful!"',
|
|
349
|
+
'๐ Fetch the GitHub user profile for octocat using HTTP GET',
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
console.info('๐งช Testing AI Agent with 3 Different Prompts');
|
|
353
|
+
console.info('โ'.repeat(60));
|
|
354
|
+
|
|
355
|
+
for (const prompt of prompts) {
|
|
356
|
+
await agent.process(prompt);
|
|
357
|
+
console.info('\n' + 'โ'.repeat(60));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.info('\nโ
Decorator pattern AI agent test complete!\n');
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('โ Agent failed:', error instanceof Error ? error.message : String(error));
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Run the agent
|
|
368
|
+
runDecoratorPatternAgent().catch(console.error);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Matimo Factory Pattern - True AI Agent with Tool Decision Making
|
|
4
|
+
*
|
|
5
|
+
* The agent receives a prompt, uses OpenAI to decide which tool to use,
|
|
6
|
+
* then executes that tool via Matimo.
|
|
7
|
+
*
|
|
8
|
+
* Run: npm run agent:factory
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import 'dotenv/config';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
15
|
+
import { MatimoInstance } from 'matimo';
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Factory Pattern Agent - Uses AI to decide which tool to call
|
|
21
|
+
*/
|
|
22
|
+
class FactoryPatternAgent {
|
|
23
|
+
private matimo: MatimoInstance;
|
|
24
|
+
private llm: ChatOpenAI;
|
|
25
|
+
|
|
26
|
+
constructor(matimo: MatimoInstance, llm: ChatOpenAI) {
|
|
27
|
+
this.matimo = matimo;
|
|
28
|
+
this.llm = llm;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generate OpenAI function schemas from Matimo tool definitions
|
|
33
|
+
* Single source of truth - schemas come from tool YAML, not duplicated here
|
|
34
|
+
*/
|
|
35
|
+
private getToolSchemas() {
|
|
36
|
+
return this.matimo.listTools().map((tool) => ({
|
|
37
|
+
type: 'function',
|
|
38
|
+
function: {
|
|
39
|
+
name: tool.name,
|
|
40
|
+
description: tool.description,
|
|
41
|
+
parameters: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: Object.entries(tool.parameters || {}).reduce(
|
|
44
|
+
(acc, [paramName, param]) => ({
|
|
45
|
+
...acc,
|
|
46
|
+
[paramName]: {
|
|
47
|
+
type: param.type,
|
|
48
|
+
enum: param.enum,
|
|
49
|
+
description: param.description,
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
{}
|
|
53
|
+
),
|
|
54
|
+
required: Object.entries(tool.parameters || {})
|
|
55
|
+
.filter(([_, param]) => param.required)
|
|
56
|
+
.map(([paramName]) => paramName),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Process a prompt - AI decides which tool to use
|
|
64
|
+
*/
|
|
65
|
+
async process(prompt: string): Promise<void> {
|
|
66
|
+
console.log(`\nโ Prompt: "${prompt}"`);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Prepare system message for tool calling
|
|
70
|
+
const systemMessage = `You are an AI assistant with access to tools.
|
|
71
|
+
Based on the user's request, decide which tool to use and extract the required parameters.
|
|
72
|
+
Extract the tool name and parameters, then respond with JSON.`;
|
|
73
|
+
|
|
74
|
+
// Get tool schemas dynamically from Matimo
|
|
75
|
+
const toolSchemas = this.getToolSchemas();
|
|
76
|
+
|
|
77
|
+
// Create a message with proper formatting for function calling
|
|
78
|
+
const messages = [
|
|
79
|
+
{
|
|
80
|
+
type: 'system',
|
|
81
|
+
content: systemMessage,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: 'human',
|
|
85
|
+
content: `User request: ${prompt}\n\nAvailable tools: ${toolSchemas.map((t) => `${t.function.name}: ${t.function.description}`).join(', ')}\n\nRespond with JSON: {"tool": "<tool_name>", "parameters": {...}}`,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
// Call LLM with tool definitions
|
|
90
|
+
const response = await this.llm.invoke(messages as any);
|
|
91
|
+
|
|
92
|
+
// Parse the LLM response
|
|
93
|
+
let toolName: string | null = null;
|
|
94
|
+
let toolParams: Record<string, unknown> | null = null;
|
|
95
|
+
|
|
96
|
+
// Try to extract tool call from response
|
|
97
|
+
const content = response.content;
|
|
98
|
+
|
|
99
|
+
if (typeof content === 'string') {
|
|
100
|
+
// Try to parse as JSON
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(content);
|
|
103
|
+
toolName = parsed.tool || parsed.function?.name;
|
|
104
|
+
toolParams = parsed.parameters || parsed.function?.parameters || parsed;
|
|
105
|
+
} catch {
|
|
106
|
+
// If not JSON, try to extract from text
|
|
107
|
+
const jsonMatch = content.match(/\{[^{}]*"tool"[^{}]*\}/);
|
|
108
|
+
if (jsonMatch) {
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
111
|
+
toolName = parsed.tool;
|
|
112
|
+
toolParams = parsed.parameters;
|
|
113
|
+
} catch {
|
|
114
|
+
// Continue without params
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// If we found a tool, execute it
|
|
121
|
+
if (toolName && toolParams) {
|
|
122
|
+
await this.executeTool(toolName, toolParams);
|
|
123
|
+
} else {
|
|
124
|
+
console.log(`\nโ ๏ธ No tool call detected in response`);
|
|
125
|
+
console.log(
|
|
126
|
+
`Response: ${typeof content === 'string' ? content.substring(0, 200) : content}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error(`\nโ Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Execute a tool via Matimo
|
|
136
|
+
* Normalize parameters to match tool schema expectations
|
|
137
|
+
*/
|
|
138
|
+
private async executeTool(toolName: string, params: Record<string, unknown>): Promise<void> {
|
|
139
|
+
try {
|
|
140
|
+
const tool = this.matimo.getTool(toolName);
|
|
141
|
+
if (!tool) {
|
|
142
|
+
console.log(`\nโ Tool '${toolName}' not found`);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Normalize parameters based on tool
|
|
147
|
+
let normalizedParams = params;
|
|
148
|
+
|
|
149
|
+
// Calculator: Handle "operands" array format by converting to a, b
|
|
150
|
+
if (toolName === 'calculator' && params.operands && Array.isArray(params.operands)) {
|
|
151
|
+
const [a, b] = params.operands as number[];
|
|
152
|
+
normalizedParams = {
|
|
153
|
+
operation: params.operation,
|
|
154
|
+
a,
|
|
155
|
+
b,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(`\n๐ง Using tool: ${toolName}`);
|
|
160
|
+
console.log(` Parameters: ${JSON.stringify(normalizedParams)}`);
|
|
161
|
+
|
|
162
|
+
const result = await this.matimo.execute(toolName, normalizedParams);
|
|
163
|
+
|
|
164
|
+
// Format result
|
|
165
|
+
if (typeof result === 'object' && result !== null) {
|
|
166
|
+
const resultData = result as any;
|
|
167
|
+
if (resultData.stdout) {
|
|
168
|
+
try {
|
|
169
|
+
const parsed = JSON.parse(resultData.stdout);
|
|
170
|
+
console.log(`\nโ
Result:`, parsed);
|
|
171
|
+
} catch {
|
|
172
|
+
console.log(`\nโ
Result:`, resultData.stdout);
|
|
173
|
+
}
|
|
174
|
+
} else if (resultData.data) {
|
|
175
|
+
// HTTP response
|
|
176
|
+
console.log(
|
|
177
|
+
`\nโ
Result (HTTP ${resultData.statusCode}):`,
|
|
178
|
+
typeof resultData.data === 'string'
|
|
179
|
+
? resultData.data.substring(0, 200)
|
|
180
|
+
: JSON.stringify(resultData.data).substring(0, 200)
|
|
181
|
+
);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(`\nโ
Result:`, result);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error(`\nโ Tool Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Run factory pattern agent with prompts
|
|
194
|
+
*/
|
|
195
|
+
async function runFactoryPatternAgent() {
|
|
196
|
+
console.log('\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
197
|
+
console.log('โ Matimo Factory Pattern - True AI Agent โ');
|
|
198
|
+
console.log('โ (AI decides which tool to use based on prompt) โ');
|
|
199
|
+
console.log('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n');
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
203
|
+
if (!apiKey) {
|
|
204
|
+
console.error('โ Error: OPENAI_API_KEY not set in .env');
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Initialize Matimo
|
|
209
|
+
console.log('๐ Initializing Matimo...');
|
|
210
|
+
const matimo = await MatimoInstance.init({ autoDiscover: true });
|
|
211
|
+
|
|
212
|
+
const matimoTools = matimo.listTools();
|
|
213
|
+
console.log(`๐ฆ Loaded ${matimoTools.length} tools:\n`);
|
|
214
|
+
matimoTools.forEach((t) => {
|
|
215
|
+
console.log(` โข ${t.name}`);
|
|
216
|
+
console.log(` ${t.description}\n`);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Initialize OpenAI LLM
|
|
220
|
+
console.log('๐ค Initializing OpenAI LLM (gpt-3.5-turbo)...\n');
|
|
221
|
+
const llm = new ChatOpenAI({
|
|
222
|
+
modelName: 'gpt-4o-mini',
|
|
223
|
+
temperature: 0,
|
|
224
|
+
openAIApiKey: apiKey,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Create agent
|
|
228
|
+
const agent = new FactoryPatternAgent(matimo, llm);
|
|
229
|
+
|
|
230
|
+
// Test prompts - each should trigger a different tool
|
|
231
|
+
const prompts = [
|
|
232
|
+
'๐งฎ What is 42 plus 8?',
|
|
233
|
+
'๐ Echo the message: "Factory pattern works perfectly!"',
|
|
234
|
+
'๐ Fetch the GitHub user profile for octocat using HTTP GET',
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
console.log('๐งช Testing AI Agent with 3 Different Prompts');
|
|
238
|
+
console.log('โ'.repeat(60));
|
|
239
|
+
|
|
240
|
+
for (const prompt of prompts) {
|
|
241
|
+
await agent.process(prompt);
|
|
242
|
+
console.log('\n' + 'โ'.repeat(60));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log('\nโ
Factory pattern AI agent test complete!\n');
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('โ Agent failed:', error instanceof Error ? error.message : String(error));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Run the agent
|
|
253
|
+
runFactoryPatternAgent().catch(console.error);
|