llm-advanced-tools 0.1.1 → 0.1.3
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/README.md +83 -70
- package/dist/adapters/index.d.ts +1 -2
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +1 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/vercel-ai.d.ts +15 -2
- package/dist/adapters/vercel-ai.d.ts.map +1 -1
- package/dist/adapters/vercel-ai.js +97 -54
- package/dist/adapters/vercel-ai.js.map +1 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +5 -2
- package/dist/core/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/package.json +28 -11
- package/dist/adapters/openai.d.ts +0 -38
- package/dist/adapters/openai.d.ts.map +0 -1
- package/dist/adapters/openai.js +0 -170
- package/dist/adapters/openai.js.map +0 -1
- package/plan.md +0 -576
- package/src/adapters/index.ts +0 -2
- package/src/adapters/openai.ts +0 -195
- package/src/adapters/vercel-ai.ts +0 -270
- package/src/core/client.ts +0 -232
- package/src/core/index.ts +0 -2
- package/src/core/registry.ts +0 -198
- package/src/executor/base.ts +0 -122
- package/src/executor/index.ts +0 -2
- package/src/executor/vm.ts +0 -87
- package/src/index.ts +0 -26
- package/src/search/base.ts +0 -63
- package/src/search/bm25.ts +0 -64
- package/src/search/index.ts +0 -3
- package/src/search/regex.ts +0 -66
- package/src/types/index.ts +0 -221
- package/test-advanced.ts +0 -212
- package/test-simple.ts +0 -91
package/src/core/registry.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { ToolDefinition, SearchConfig } from '../types';
|
|
2
|
-
import { SearchStrategy, RegexSearchStrategy, BM25SearchStrategy } from '../search';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Registry for managing tool definitions and discovery
|
|
6
|
-
*/
|
|
7
|
-
export class ToolRegistry {
|
|
8
|
-
private tools: Map<string, ToolDefinition>;
|
|
9
|
-
private deferredTools: Map<string, ToolDefinition>;
|
|
10
|
-
private loadedTools: Set<string>;
|
|
11
|
-
private searchStrategy: SearchStrategy;
|
|
12
|
-
|
|
13
|
-
constructor(searchConfig?: SearchConfig) {
|
|
14
|
-
this.tools = new Map();
|
|
15
|
-
this.deferredTools = new Map();
|
|
16
|
-
this.loadedTools = new Set();
|
|
17
|
-
|
|
18
|
-
// Initialize search strategy
|
|
19
|
-
if (searchConfig?.customSearchFn) {
|
|
20
|
-
this.searchStrategy = {
|
|
21
|
-
search: async (query, tools) =>
|
|
22
|
-
searchConfig.customSearchFn!(query, tools)
|
|
23
|
-
};
|
|
24
|
-
} else {
|
|
25
|
-
const strategy = searchConfig?.strategy ?? 'smart';
|
|
26
|
-
const maxResults = searchConfig?.maxResults ?? 10;
|
|
27
|
-
const threshold = searchConfig?.threshold ?? 0.0;
|
|
28
|
-
|
|
29
|
-
// Map user-friendly names (and legacy names) to implementations
|
|
30
|
-
// 'smart' or 'bm25' → BM25 (best relevance ranking)
|
|
31
|
-
// 'keyword' or 'regex' → Regex (fast keyword matching)
|
|
32
|
-
const useSmartSearch =
|
|
33
|
-
strategy === 'smart' ||
|
|
34
|
-
strategy === 'bm25';
|
|
35
|
-
|
|
36
|
-
this.searchStrategy = useSmartSearch
|
|
37
|
-
? new BM25SearchStrategy(maxResults, threshold)
|
|
38
|
-
: new RegexSearchStrategy(maxResults, threshold);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Register a tool
|
|
44
|
-
*/
|
|
45
|
-
register(tool: ToolDefinition): void {
|
|
46
|
-
if (this.tools.has(tool.name)) {
|
|
47
|
-
throw new Error(`Tool "${tool.name}" is already registered`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
this.tools.set(tool.name, tool);
|
|
51
|
-
|
|
52
|
-
if (tool.deferLoading) {
|
|
53
|
-
this.deferredTools.set(tool.name, tool);
|
|
54
|
-
} else {
|
|
55
|
-
this.loadedTools.add(tool.name);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Register multiple tools at once
|
|
61
|
-
*/
|
|
62
|
-
registerMany(tools: ToolDefinition[]): void {
|
|
63
|
-
tools.forEach(tool => this.register(tool));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get a specific tool by name
|
|
68
|
-
*/
|
|
69
|
-
get(name: string): ToolDefinition | undefined {
|
|
70
|
-
return this.tools.get(name);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Load a deferred tool into active context
|
|
75
|
-
*/
|
|
76
|
-
load(name: string): ToolDefinition | undefined {
|
|
77
|
-
const tool = this.deferredTools.get(name);
|
|
78
|
-
if (tool) {
|
|
79
|
-
this.loadedTools.add(name);
|
|
80
|
-
return tool;
|
|
81
|
-
}
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get all currently loaded tools
|
|
87
|
-
*/
|
|
88
|
-
getLoadedTools(): ToolDefinition[] {
|
|
89
|
-
return Array.from(this.loadedTools)
|
|
90
|
-
.map(name => this.tools.get(name))
|
|
91
|
-
.filter((tool): tool is ToolDefinition => tool !== undefined);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get all tools (including deferred)
|
|
96
|
-
*/
|
|
97
|
-
getAllTools(): ToolDefinition[] {
|
|
98
|
-
return Array.from(this.tools.values());
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Search for tools matching a query
|
|
103
|
-
* @param query Search query
|
|
104
|
-
* @param maxResults Maximum results to return
|
|
105
|
-
* @param loadResults Whether to automatically load found tools
|
|
106
|
-
*/
|
|
107
|
-
async search(
|
|
108
|
-
query: string,
|
|
109
|
-
maxResults?: number,
|
|
110
|
-
loadResults = true
|
|
111
|
-
): Promise<ToolDefinition[]> {
|
|
112
|
-
// Search within deferred tools
|
|
113
|
-
const deferredToolsList = Array.from(this.deferredTools.values());
|
|
114
|
-
const results = await this.searchStrategy.search(query, deferredToolsList, maxResults);
|
|
115
|
-
|
|
116
|
-
// Optionally load the found tools
|
|
117
|
-
if (loadResults) {
|
|
118
|
-
results.forEach(tool => this.loadedTools.add(tool.name));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return results;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Clear all loaded tools (keeps registrations)
|
|
126
|
-
*/
|
|
127
|
-
clearLoaded(): void {
|
|
128
|
-
this.loadedTools.clear();
|
|
129
|
-
// Re-add non-deferred tools
|
|
130
|
-
this.tools.forEach((tool, name) => {
|
|
131
|
-
if (!tool.deferLoading) {
|
|
132
|
-
this.loadedTools.add(name);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Unregister a tool
|
|
139
|
-
*/
|
|
140
|
-
unregister(name: string): boolean {
|
|
141
|
-
const deleted = this.tools.delete(name);
|
|
142
|
-
this.deferredTools.delete(name);
|
|
143
|
-
this.loadedTools.delete(name);
|
|
144
|
-
return deleted;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Get statistics about the registry
|
|
149
|
-
*/
|
|
150
|
-
getStats(): {
|
|
151
|
-
total: number;
|
|
152
|
-
loaded: number;
|
|
153
|
-
deferred: number;
|
|
154
|
-
} {
|
|
155
|
-
return {
|
|
156
|
-
total: this.tools.size,
|
|
157
|
-
loaded: this.loadedTools.size,
|
|
158
|
-
deferred: this.deferredTools.size
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Create a tool search tool definition
|
|
164
|
-
* This is a meta-tool that allows LLMs to search for other tools
|
|
165
|
-
*/
|
|
166
|
-
createToolSearchTool(): ToolDefinition {
|
|
167
|
-
return {
|
|
168
|
-
name: 'tool_search',
|
|
169
|
-
description:
|
|
170
|
-
'Search for available tools by name or description. Use this when you need to find tools for a specific task.',
|
|
171
|
-
inputSchema: {
|
|
172
|
-
type: 'object',
|
|
173
|
-
properties: {
|
|
174
|
-
query: {
|
|
175
|
-
type: 'string',
|
|
176
|
-
description: 'Search query describing the capability you need'
|
|
177
|
-
},
|
|
178
|
-
maxResults: {
|
|
179
|
-
type: 'number',
|
|
180
|
-
description: 'Maximum number of tools to return',
|
|
181
|
-
default: 5
|
|
182
|
-
}
|
|
183
|
-
},
|
|
184
|
-
required: ['query']
|
|
185
|
-
},
|
|
186
|
-
handler: async (input: { query: string; maxResults?: number }) => {
|
|
187
|
-
const results = await this.search(input.query, input.maxResults ?? 5, true);
|
|
188
|
-
return {
|
|
189
|
-
tools: results.map(tool => ({
|
|
190
|
-
name: tool.name,
|
|
191
|
-
description: tool.description
|
|
192
|
-
})),
|
|
193
|
-
count: results.length
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
package/src/executor/base.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { ExecutionResult, ExecutorConfig, ToolDefinition, ToolCall } from '../types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base interface for code executors
|
|
5
|
-
*/
|
|
6
|
-
export interface CodeExecutor {
|
|
7
|
-
/**
|
|
8
|
-
* Execute code with access to tools
|
|
9
|
-
* @param code Code to execute
|
|
10
|
-
* @param tools Available tools that can be called from code
|
|
11
|
-
* @param context Additional context/variables
|
|
12
|
-
*/
|
|
13
|
-
execute(
|
|
14
|
-
code: string,
|
|
15
|
-
tools: ToolDefinition[],
|
|
16
|
-
context?: Record<string, any>
|
|
17
|
-
): Promise<ExecutionResult>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Abstract base class for code executors
|
|
22
|
-
*/
|
|
23
|
-
export abstract class BaseCodeExecutor implements CodeExecutor {
|
|
24
|
-
protected config: ExecutorConfig;
|
|
25
|
-
protected pendingToolCalls: ToolCall[] = [];
|
|
26
|
-
|
|
27
|
-
constructor(config: ExecutorConfig = {}) {
|
|
28
|
-
this.config = {
|
|
29
|
-
timeout: 30000, // 30 seconds default
|
|
30
|
-
memoryLimit: '256mb',
|
|
31
|
-
...config
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
abstract execute(
|
|
36
|
-
code: string,
|
|
37
|
-
tools: ToolDefinition[],
|
|
38
|
-
context?: Record<string, any>
|
|
39
|
-
): Promise<ExecutionResult>;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Create a tool wrapper function that the executed code can call
|
|
43
|
-
*/
|
|
44
|
-
protected createToolWrapper(tool: ToolDefinition): (...args: any[]) => Promise<any> {
|
|
45
|
-
return async (...args: any[]) => {
|
|
46
|
-
// Determine input based on schema
|
|
47
|
-
let input: any;
|
|
48
|
-
|
|
49
|
-
if (args.length === 1 && typeof args[0] === 'object') {
|
|
50
|
-
input = args[0];
|
|
51
|
-
} else {
|
|
52
|
-
// If multiple args, create object from schema
|
|
53
|
-
// This is simplified - in reality would need to parse schema
|
|
54
|
-
input = args[0];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Generate unique ID for this call
|
|
58
|
-
const callId = `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
59
|
-
|
|
60
|
-
// Record the tool call
|
|
61
|
-
this.pendingToolCalls.push({
|
|
62
|
-
id: callId,
|
|
63
|
-
name: tool.name,
|
|
64
|
-
input,
|
|
65
|
-
caller: {
|
|
66
|
-
type: 'code_execution',
|
|
67
|
-
toolId: 'code_exec'
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Execute the tool
|
|
72
|
-
try {
|
|
73
|
-
const result = await tool.handler(input);
|
|
74
|
-
return result;
|
|
75
|
-
} catch (error) {
|
|
76
|
-
throw new Error(`Tool ${tool.name} failed: ${error}`);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create the context object with tool functions
|
|
83
|
-
*/
|
|
84
|
-
protected createExecutionContext(
|
|
85
|
-
tools: ToolDefinition[],
|
|
86
|
-
additionalContext?: Record<string, any>
|
|
87
|
-
): Record<string, any> {
|
|
88
|
-
const context: Record<string, any> = {
|
|
89
|
-
console: {
|
|
90
|
-
log: (...args: any[]) => {
|
|
91
|
-
// Capture console output
|
|
92
|
-
return args.join(' ');
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
...additionalContext
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Add each tool as a callable function
|
|
99
|
-
tools.forEach(tool => {
|
|
100
|
-
if (tool.allowedCallers?.includes('code_execution')) {
|
|
101
|
-
context[tool.name] = this.createToolWrapper(tool);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return context;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Wrap code with timeout
|
|
110
|
-
*/
|
|
111
|
-
protected async withTimeout<T>(
|
|
112
|
-
promise: Promise<T>,
|
|
113
|
-
timeoutMs: number
|
|
114
|
-
): Promise<T> {
|
|
115
|
-
return Promise.race([
|
|
116
|
-
promise,
|
|
117
|
-
new Promise<T>((_, reject) =>
|
|
118
|
-
setTimeout(() => reject(new Error('Execution timeout')), timeoutMs)
|
|
119
|
-
)
|
|
120
|
-
]);
|
|
121
|
-
}
|
|
122
|
-
}
|
package/src/executor/index.ts
DELETED
package/src/executor/vm.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { ExecutionResult, ExecutorConfig, ToolDefinition } from '../types';
|
|
2
|
-
import { BaseCodeExecutor } from './base';
|
|
3
|
-
import * as vm from 'vm';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* VM-based code executor
|
|
7
|
-
*
|
|
8
|
-
* WARNING: This provides basic isolation but is NOT secure for untrusted code.
|
|
9
|
-
* For production use with untrusted code, use DockerExecutor or a cloud
|
|
10
|
-
* sandbox service like E2B.
|
|
11
|
-
*/
|
|
12
|
-
export class VMExecutor extends BaseCodeExecutor {
|
|
13
|
-
constructor(config: ExecutorConfig = {}) {
|
|
14
|
-
super(config);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async execute(
|
|
18
|
-
code: string,
|
|
19
|
-
tools: ToolDefinition[],
|
|
20
|
-
context?: Record<string, any>
|
|
21
|
-
): Promise<ExecutionResult> {
|
|
22
|
-
this.pendingToolCalls = [];
|
|
23
|
-
|
|
24
|
-
const capturedLogs: string[] = [];
|
|
25
|
-
let returnValue: any;
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
// Create execution context with tools
|
|
29
|
-
const execContext = this.createExecutionContext(tools, context);
|
|
30
|
-
|
|
31
|
-
// Override console.log to capture output
|
|
32
|
-
execContext.console = {
|
|
33
|
-
log: (...args: any[]) => {
|
|
34
|
-
const message = args.map(arg =>
|
|
35
|
-
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
|
|
36
|
-
).join(' ');
|
|
37
|
-
capturedLogs.push(message);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Wrap code in async function
|
|
42
|
-
const wrappedCode = `
|
|
43
|
-
(async () => {
|
|
44
|
-
${code}
|
|
45
|
-
})()
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
// Create VM context
|
|
49
|
-
const vmContext = vm.createContext(execContext);
|
|
50
|
-
|
|
51
|
-
// Execute with timeout
|
|
52
|
-
const script = new vm.Script(wrappedCode);
|
|
53
|
-
const result = await this.withTimeout(
|
|
54
|
-
script.runInContext(vmContext, {
|
|
55
|
-
timeout: this.config.timeout
|
|
56
|
-
}),
|
|
57
|
-
this.config.timeout!
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
returnValue = result;
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
success: true,
|
|
64
|
-
stdout: capturedLogs.join('\n'),
|
|
65
|
-
returnValue,
|
|
66
|
-
toolCalls: this.pendingToolCalls
|
|
67
|
-
};
|
|
68
|
-
} catch (error: any) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
stderr: error.message,
|
|
72
|
-
error: {
|
|
73
|
-
message: error.message,
|
|
74
|
-
stack: error.stack
|
|
75
|
-
},
|
|
76
|
-
toolCalls: this.pendingToolCalls
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Create a default executor instance
|
|
84
|
-
*/
|
|
85
|
-
export function createDefaultExecutor(config?: ExecutorConfig): VMExecutor {
|
|
86
|
-
return new VMExecutor(config);
|
|
87
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Core exports
|
|
2
|
-
export { Client, ToolRegistry } from './core';
|
|
3
|
-
|
|
4
|
-
// Type exports
|
|
5
|
-
export type {
|
|
6
|
-
ToolDefinition,
|
|
7
|
-
ToolCall,
|
|
8
|
-
ToolResult,
|
|
9
|
-
Message,
|
|
10
|
-
ChatRequest,
|
|
11
|
-
ChatResponse,
|
|
12
|
-
SearchConfig,
|
|
13
|
-
ExecutorConfig,
|
|
14
|
-
ProviderAdapter,
|
|
15
|
-
ProviderCapabilities,
|
|
16
|
-
ClientConfig
|
|
17
|
-
} from './types';
|
|
18
|
-
|
|
19
|
-
// Adapter exports
|
|
20
|
-
export { OpenAIAdapter, VercelAIAdapter } from './adapters';
|
|
21
|
-
|
|
22
|
-
// Search exports
|
|
23
|
-
export { SearchStrategy, RegexSearchStrategy, BM25SearchStrategy } from './search';
|
|
24
|
-
|
|
25
|
-
// Executor exports
|
|
26
|
-
export { CodeExecutor, VMExecutor, createDefaultExecutor } from './executor';
|
package/src/search/base.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { ToolDefinition } from '../types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Base interface for tool search strategies
|
|
5
|
-
*/
|
|
6
|
-
export interface SearchStrategy {
|
|
7
|
-
/**
|
|
8
|
-
* Search for tools matching the query
|
|
9
|
-
* @param query Search query
|
|
10
|
-
* @param tools Available tools to search
|
|
11
|
-
* @param maxResults Maximum number of results to return
|
|
12
|
-
* @returns Matching tools, sorted by relevance
|
|
13
|
-
*/
|
|
14
|
-
search(
|
|
15
|
-
query: string,
|
|
16
|
-
tools: ToolDefinition[],
|
|
17
|
-
maxResults?: number
|
|
18
|
-
): Promise<ToolDefinition[]>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Abstract base class for search strategies
|
|
23
|
-
*/
|
|
24
|
-
export abstract class BaseSearchStrategy implements SearchStrategy {
|
|
25
|
-
protected maxResults: number;
|
|
26
|
-
protected threshold: number;
|
|
27
|
-
|
|
28
|
-
constructor(maxResults = 10, threshold = 0.0) {
|
|
29
|
-
this.maxResults = maxResults;
|
|
30
|
-
this.threshold = threshold;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
abstract search(
|
|
34
|
-
query: string,
|
|
35
|
-
tools: ToolDefinition[],
|
|
36
|
-
maxResults?: number
|
|
37
|
-
): Promise<ToolDefinition[]>;
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Calculate relevance score for a tool
|
|
41
|
-
*/
|
|
42
|
-
protected abstract scoreRelevance(query: string, tool: ToolDefinition): number;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Rank tools by relevance
|
|
46
|
-
*/
|
|
47
|
-
protected rankTools(
|
|
48
|
-
query: string,
|
|
49
|
-
tools: ToolDefinition[],
|
|
50
|
-
maxResults?: number
|
|
51
|
-
): ToolDefinition[] {
|
|
52
|
-
const scored = tools
|
|
53
|
-
.map(tool => ({
|
|
54
|
-
tool,
|
|
55
|
-
score: this.scoreRelevance(query, tool)
|
|
56
|
-
}))
|
|
57
|
-
.filter(({ score }) => score >= this.threshold)
|
|
58
|
-
.sort((a, b) => b.score - a.score);
|
|
59
|
-
|
|
60
|
-
const limit = maxResults ?? this.maxResults;
|
|
61
|
-
return scored.slice(0, limit).map(({ tool }) => tool);
|
|
62
|
-
}
|
|
63
|
-
}
|
package/src/search/bm25.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { ToolDefinition } from '../types';
|
|
2
|
-
import { BaseSearchStrategy } from './base';
|
|
3
|
-
import * as natural from 'natural';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* BM25-based search strategy
|
|
7
|
-
* Better relevance ranking, handles synonyms and term frequency
|
|
8
|
-
*/
|
|
9
|
-
export class BM25SearchStrategy extends BaseSearchStrategy {
|
|
10
|
-
private tokenizer: natural.WordTokenizer;
|
|
11
|
-
private tfidf: natural.TfIdf;
|
|
12
|
-
private toolsIndex: Map<number, ToolDefinition>;
|
|
13
|
-
|
|
14
|
-
constructor(maxResults = 10, threshold = 0.0) {
|
|
15
|
-
super(maxResults, threshold);
|
|
16
|
-
this.tokenizer = new natural.WordTokenizer();
|
|
17
|
-
this.tfidf = new natural.TfIdf();
|
|
18
|
-
this.toolsIndex = new Map();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async search(
|
|
22
|
-
query: string,
|
|
23
|
-
tools: ToolDefinition[],
|
|
24
|
-
maxResults?: number
|
|
25
|
-
): Promise<ToolDefinition[]> {
|
|
26
|
-
// Rebuild index for current tool set
|
|
27
|
-
this.buildIndex(tools);
|
|
28
|
-
|
|
29
|
-
// Search using TF-IDF
|
|
30
|
-
const results: Array<{ tool: ToolDefinition; score: number }> = [];
|
|
31
|
-
|
|
32
|
-
this.tfidf.tfidfs(query, (i, measure) => {
|
|
33
|
-
const tool = this.toolsIndex.get(i);
|
|
34
|
-
if (tool && measure >= this.threshold) {
|
|
35
|
-
results.push({ tool, score: measure });
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Sort by score (descending)
|
|
40
|
-
results.sort((a, b) => b.score - a.score);
|
|
41
|
-
|
|
42
|
-
// Return top results
|
|
43
|
-
const limit = maxResults ?? this.maxResults;
|
|
44
|
-
return results.slice(0, limit).map(({ tool }) => tool);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
protected scoreRelevance(query: string, tool: ToolDefinition): number {
|
|
48
|
-
// This is handled by TF-IDF in the search method
|
|
49
|
-
// This method is not used for BM25 but required by base class
|
|
50
|
-
return 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private buildIndex(tools: ToolDefinition[]): void {
|
|
54
|
-
this.tfidf = new natural.TfIdf();
|
|
55
|
-
this.toolsIndex.clear();
|
|
56
|
-
|
|
57
|
-
tools.forEach((tool, index) => {
|
|
58
|
-
// Combine name (with higher weight) and description for indexing
|
|
59
|
-
const document = `${tool.name} ${tool.name} ${tool.name} ${tool.description}`;
|
|
60
|
-
this.tfidf.addDocument(document);
|
|
61
|
-
this.toolsIndex.set(index, tool);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
package/src/search/index.ts
DELETED
package/src/search/regex.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { ToolDefinition } from '../types';
|
|
2
|
-
import { BaseSearchStrategy } from './base';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Regex-based search strategy
|
|
6
|
-
* Fast and simple, good for exact matches
|
|
7
|
-
*/
|
|
8
|
-
export class RegexSearchStrategy extends BaseSearchStrategy {
|
|
9
|
-
async search(
|
|
10
|
-
query: string,
|
|
11
|
-
tools: ToolDefinition[],
|
|
12
|
-
maxResults?: number
|
|
13
|
-
): Promise<ToolDefinition[]> {
|
|
14
|
-
return this.rankTools(query, tools, maxResults);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
protected scoreRelevance(query: string, tool: ToolDefinition): number {
|
|
18
|
-
const normalizedQuery = query.toLowerCase();
|
|
19
|
-
const toolName = tool.name.toLowerCase();
|
|
20
|
-
const toolDesc = tool.description.toLowerCase();
|
|
21
|
-
|
|
22
|
-
let score = 0;
|
|
23
|
-
|
|
24
|
-
// Exact name match gets highest score
|
|
25
|
-
if (toolName === normalizedQuery) {
|
|
26
|
-
score += 100;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Name contains query
|
|
30
|
-
if (toolName.includes(normalizedQuery)) {
|
|
31
|
-
score += 50;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Query matches word boundary in name
|
|
35
|
-
const nameWordBoundary = new RegExp(`\\b${this.escapeRegex(normalizedQuery)}`, 'i');
|
|
36
|
-
if (nameWordBoundary.test(toolName)) {
|
|
37
|
-
score += 30;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Description contains query
|
|
41
|
-
if (toolDesc.includes(normalizedQuery)) {
|
|
42
|
-
score += 20;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Query matches word boundary in description
|
|
46
|
-
const descWordBoundary = new RegExp(`\\b${this.escapeRegex(normalizedQuery)}`, 'i');
|
|
47
|
-
if (descWordBoundary.test(toolDesc)) {
|
|
48
|
-
score += 10;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Check for individual query words
|
|
52
|
-
const queryWords = normalizedQuery.split(/\s+/);
|
|
53
|
-
queryWords.forEach(word => {
|
|
54
|
-
if (word.length > 2) {
|
|
55
|
-
if (toolName.includes(word)) score += 5;
|
|
56
|
-
if (toolDesc.includes(word)) score += 2;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
return score;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private escapeRegex(str: string): string {
|
|
64
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
65
|
-
}
|
|
66
|
-
}
|