@peebles-group/agentlib-js 1.0.3 → 1.0.5
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 +35 -3
- package/index.js +2 -1
- package/package.json +4 -4
- package/src/Agent.js +102 -38
- package/src/config.js +2 -1
- package/src/llmService.js +9 -14
- package/src/prompt-loader/loadStrategies.js +63 -0
- package/src/prompt-loader/parseStrategies.js +79 -0
- package/src/prompt-loader/promptLoader.js +188 -0
- package/src/prompt-loader/tests/test-prompts.db +0 -0
- package/src/prompt-loader/tests/test-prompts.json +9 -0
- package/src/prompt-loader/tests/test-prompts.md +12 -0
- package/src/prompt-loader/tests/test-prompts.txt +12 -0
- package/src/prompt-loader/tests/test-prompts.yaml +16 -0
- package/src/prompt-loader/tests/test-script.js +151 -0
- package/src/prompts/agentPrompts.yml +3 -0
- package/src/providers/openai.js +1 -1
- package/src/providers/registry.js +18 -5
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ A lightweight Node.js library for building AI agents with LLM providers and MCP
|
|
|
8
8
|
npm install @peebles-group/agentlib-js
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Testing
|
|
12
|
+
|
|
13
|
+
Run `npm test` to run the test script under `tests/test.js`.
|
|
14
|
+
|
|
11
15
|
## Quick Start
|
|
12
16
|
|
|
13
17
|
1. **Set up API keys**
|
|
@@ -40,11 +44,11 @@ import { Agent, LLMService } from '@peebles-group/agentlib-js';
|
|
|
40
44
|
import dotenv from 'dotenv';
|
|
41
45
|
dotenv.config();
|
|
42
46
|
|
|
43
|
-
// Initialize LLM service
|
|
47
|
+
// Initialize LLM service
|
|
44
48
|
const llm = new LLMService('openai', process.env.OPENAI_API_KEY);
|
|
45
49
|
|
|
46
50
|
// Simple agent
|
|
47
|
-
const agent = new Agent(
|
|
51
|
+
const agent = new Agent(llm, {
|
|
48
52
|
model: 'gpt-4o-mini'
|
|
49
53
|
});
|
|
50
54
|
agent.addInput({ role: 'user', content: 'Hello!' });
|
|
@@ -52,7 +56,7 @@ const response = await agent.run();
|
|
|
52
56
|
console.log(response.output_text);
|
|
53
57
|
|
|
54
58
|
// Agent with MCP servers (auto-installs packages)
|
|
55
|
-
const mcpAgent = new Agent(
|
|
59
|
+
const mcpAgent = new Agent(llm, {
|
|
56
60
|
model: 'gpt-4o-mini',
|
|
57
61
|
enableMCP: true
|
|
58
62
|
});
|
|
@@ -64,6 +68,34 @@ await mcpAgent.addMCPServer('browser', {
|
|
|
64
68
|
});
|
|
65
69
|
```
|
|
66
70
|
|
|
71
|
+
## Prompt Management
|
|
72
|
+
|
|
73
|
+
Manage prompts efficiently using the `PromptLoader`. Support for yml/db/md/json/txt files.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import { PromptLoader } from '@peebles-group/agentlib-js';
|
|
77
|
+
|
|
78
|
+
// Load prompts from a file
|
|
79
|
+
const loader = await PromptLoader.create('./prompts.yml');
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
prompts.yml
|
|
83
|
+
|
|
84
|
+
system_instruction: |
|
|
85
|
+
Write an essay on {{topic}}.
|
|
86
|
+
Make sure to make it {{depth}}.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
// Get and format a prompt
|
|
90
|
+
const prompt = loader.getPrompt('system_instruction').format({
|
|
91
|
+
topic: 'AI Agents',
|
|
92
|
+
depth: 'detailed'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
agent.addInput({ role: 'user', content: prompt });
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
|
|
67
99
|
## Structured Outputs
|
|
68
100
|
|
|
69
101
|
AgentLib supports type-safe structured outputs using Zod schemas for reliable JSON responses.
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peebles-group/agentlib-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A minimal JavaScript library implementing concurrent async agents for illustrating multi-agent systems and other agentic design patterns including recursive ones purely through function calling loops.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "node index.js",
|
|
9
|
-
"test": "
|
|
9
|
+
"test": "cd tests && node test.js"
|
|
10
10
|
},
|
|
11
11
|
"author": "Peebles Group",
|
|
12
12
|
"license": "MIT",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"openai": "^6.0.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
+
"@peebles-group/agentlib-js": "^1.0.5",
|
|
19
20
|
"dotenv": "^16.6.1",
|
|
20
21
|
"js-yaml": "^4.1.0",
|
|
21
22
|
"mongodb": "^6.20.0",
|
|
22
23
|
"openai": "^5.16.0",
|
|
23
|
-
"agentlib-js": "^1.0.0",
|
|
24
24
|
"playwright": "^1.55.0",
|
|
25
25
|
"prompt-sync": "^4.2.0",
|
|
26
26
|
"sqlite": "^5.1.1",
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
"README.md",
|
|
34
34
|
"LICENSE"
|
|
35
35
|
]
|
|
36
|
-
}
|
|
36
|
+
}
|
package/src/Agent.js
CHANGED
|
@@ -1,44 +1,93 @@
|
|
|
1
|
-
import { LLMService } from "./llmService.js";
|
|
2
1
|
import { defaultModel } from "./config.js";
|
|
3
2
|
import { MCPManager } from "./mcp/MCPManager.js";
|
|
3
|
+
import { PromptLoader } from "./prompt-loader/promptLoader.js";
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import path, { dirname } from 'path';
|
|
4
6
|
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
const promptLoader = await PromptLoader.create(path.join(__dirname, 'prompts', 'agentPrompts.yml'));
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents an LLM-based agent capable of tool calling.
|
|
14
|
+
*/
|
|
5
15
|
export class Agent {
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} llmService - The LLM service used for communication with LLM client.
|
|
18
|
+
* @param {object} [options] - Configuration options for the agent.
|
|
19
|
+
* @param {string} [options.model=defaultModel] - The model identifier to use.
|
|
20
|
+
* @param {Array<object>} [options.tools=[]] - Array of native tools available to the agent.
|
|
21
|
+
* @param {zod object|null} [options.inputSchema=null] - Zod schema for validating input messages.
|
|
22
|
+
* @param {zod object|null} [options.outputSchema=null] - Zod schema for expected final output format.
|
|
23
|
+
* @param {boolean} [options.enableMCP=false] - Whether to enable MCP (Model Context Protocol) usage.
|
|
24
|
+
* @param {boolean} [options.redundantToolInfo=true] - Whether to include tool descriptions in the system prompt.
|
|
25
|
+
* @param {object} [options...] - Additional options passed to the LLM service.
|
|
26
|
+
*/
|
|
27
|
+
constructor(llmService, { model = defaultModel, tools = [], inputSchema = null, outputSchema = null, enableMCP = false, redundantToolInfo = true, ...options } = {}) {
|
|
28
|
+
this.llmService = llmService;
|
|
8
29
|
this.model = model;
|
|
9
30
|
this.nativeTools = tools;
|
|
10
31
|
this.inputSchema = inputSchema;
|
|
11
32
|
this.outputSchema = outputSchema;
|
|
12
33
|
this.mcpManager = enableMCP ? new MCPManager() : null;
|
|
13
|
-
this.
|
|
34
|
+
this.redundantToolInfo = redundantToolInfo;
|
|
35
|
+
this.additionalOptions = options;
|
|
36
|
+
this.input = [];
|
|
37
|
+
|
|
38
|
+
if (this.redundantToolInfo) {
|
|
39
|
+
this.updateSystemPrompt();
|
|
40
|
+
}
|
|
14
41
|
}
|
|
15
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Adds a new MCP server for remote tool access.
|
|
45
|
+
* @param {string} serverName - A unique name for the server.
|
|
46
|
+
* @param {object} config - Configuration details for the MCP server connection.
|
|
47
|
+
* @returns {Promise<object>} The result of adding the server.
|
|
48
|
+
* @throws {Error} If MCP is not enabled.
|
|
49
|
+
*/
|
|
16
50
|
async addMCPServer(serverName, config) {
|
|
17
51
|
if (!this.mcpManager) {
|
|
18
52
|
throw new Error("MCP is not enabled for this agent");
|
|
19
|
-
}
|
|
53
|
+
}
|
|
20
54
|
|
|
21
55
|
const result = await this.mcpManager.addServer(serverName, config);
|
|
22
|
-
this.
|
|
56
|
+
if (this.redundantToolInfo) {
|
|
57
|
+
this.updateSystemPrompt();
|
|
58
|
+
}
|
|
23
59
|
return result;
|
|
24
60
|
}
|
|
25
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Removes an existing MCP server.
|
|
64
|
+
* @param {string} serverName - The unique name of the server to remove.
|
|
65
|
+
* @returns {Promise<boolean>} True if the server was successfully removed, false otherwise.
|
|
66
|
+
*/
|
|
26
67
|
async removeMCPServer(serverName) {
|
|
27
68
|
if (!this.mcpManager) return false;
|
|
28
69
|
|
|
29
70
|
const result = await this.mcpManager.removeServer(serverName);
|
|
30
|
-
if (result
|
|
71
|
+
if (result && this.redundantToolInfo) {
|
|
72
|
+
this.updateSystemPrompt();
|
|
73
|
+
}
|
|
31
74
|
return result;
|
|
32
75
|
}
|
|
33
76
|
|
|
34
77
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
78
|
+
* Adds a native tool to the agent's array of tools.
|
|
79
|
+
* @param {object} tool - The tool object.
|
|
80
|
+
* @param {string} tool.name - The unique name of the tool.
|
|
81
|
+
* @param {function} tool.func - The function to execute when the tool is called.
|
|
82
|
+
* @param {string} [tool.description=''] - A description for the LLM on when to use the tool.
|
|
83
|
+
* @returns {object} The added tool object.
|
|
84
|
+
* @throws {Error} If the tool is invalid or a name collision occurs.
|
|
37
85
|
*/
|
|
38
86
|
addTool(tool) {
|
|
39
87
|
if (!tool || typeof tool !== 'object') {
|
|
40
|
-
throw new Error(
|
|
88
|
+
throw new Error('Invalid tool: expected an object');
|
|
41
89
|
}
|
|
90
|
+
|
|
42
91
|
const { name, func } = tool;
|
|
43
92
|
if (typeof name !== 'string' || name.trim() === '') {
|
|
44
93
|
throw new Error("Invalid tool: missing valid 'name' (string)");
|
|
@@ -47,7 +96,6 @@ export class Agent {
|
|
|
47
96
|
throw new Error("Invalid tool: missing 'func' (function)");
|
|
48
97
|
}
|
|
49
98
|
|
|
50
|
-
// Prevent name collisions across native and MCP tools
|
|
51
99
|
const nameExistsInNative = this.nativeTools.some(t => t && t.name === name);
|
|
52
100
|
const nameExistsInMCP = this.mcpManager ? this.mcpManager.getAllTools().some(t => t && t.name === name) : false;
|
|
53
101
|
if (nameExistsInNative || nameExistsInMCP) {
|
|
@@ -59,30 +107,45 @@ export class Agent {
|
|
|
59
107
|
}
|
|
60
108
|
|
|
61
109
|
this.nativeTools.push(tool);
|
|
62
|
-
this.
|
|
110
|
+
if (this.redundantToolInfo) {
|
|
111
|
+
this.updateSystemPrompt();
|
|
112
|
+
}
|
|
63
113
|
return tool;
|
|
64
114
|
}
|
|
65
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Retrieves all available tools, including native and MCP tools.
|
|
118
|
+
* @returns {Array<object>} An array of all tools.
|
|
119
|
+
*/
|
|
66
120
|
getAllTools() {
|
|
67
121
|
const mcpTools = this.mcpManager ? this.mcpManager.getAllTools() : [];
|
|
68
122
|
return [...this.nativeTools, ...mcpTools];
|
|
69
123
|
}
|
|
70
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Gets status information about the MCP manager.
|
|
127
|
+
* @returns {object} Information about the MCP manager, or { enabled: false } if disabled.
|
|
128
|
+
*/
|
|
71
129
|
getMCPInfo() {
|
|
72
130
|
return this.mcpManager ? this.mcpManager.getServerInfo() : { enabled: false };
|
|
73
131
|
}
|
|
74
132
|
|
|
75
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Updates the system prompt with descriptions of all currently available tools.
|
|
135
|
+
*/
|
|
76
136
|
updateSystemPrompt() {
|
|
77
137
|
const allTools = this.getAllTools();
|
|
138
|
+
const toolDescriptions = allTools.map(tool => `${tool.name}: ${tool.description}`).join('; ');
|
|
78
139
|
this.input = [{
|
|
79
140
|
role: 'system',
|
|
80
|
-
content: '
|
|
81
|
-
allTools.map(tool => `${tool.name}: ${tool.description}`).join('; ') +
|
|
82
|
-
'. Use these tools to answer the user\'s questions.'
|
|
141
|
+
content: promptLoader.getPrompt('systemPrompt').format({ toolDescriptions })
|
|
83
142
|
}];
|
|
84
143
|
}
|
|
85
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Adds user instruction or assistant response to the current conversation history.
|
|
147
|
+
* @param {object} input - The message object to add.
|
|
148
|
+
*/
|
|
86
149
|
addInput(input) {
|
|
87
150
|
if (this.inputSchema) {
|
|
88
151
|
this.inputSchema.parse(input);
|
|
@@ -91,74 +154,75 @@ export class Agent {
|
|
|
91
154
|
}
|
|
92
155
|
|
|
93
156
|
/**
|
|
94
|
-
*
|
|
157
|
+
* Runs the agent for a single conversational turn, including tool use if necessary.
|
|
158
|
+
* This method handles the multi-step reasoning: LLM -> Tool Execution -> LLM Final Response.
|
|
159
|
+
* @returns {Promise<object>} The final response object from the LLM, including execution details.
|
|
95
160
|
*/
|
|
96
161
|
async run() {
|
|
97
162
|
const allTools = this.getAllTools();
|
|
163
|
+
const executed = []
|
|
98
164
|
|
|
99
|
-
// Step 1: send input to model
|
|
100
165
|
let response = await this.llmService.chat(this.input, {
|
|
101
166
|
model: this.model,
|
|
102
167
|
outputSchema: this.outputSchema,
|
|
103
168
|
tools: allTools,
|
|
169
|
+
...this.additionalOptions
|
|
104
170
|
});
|
|
105
171
|
|
|
106
172
|
const { output, rawResponse } = response;
|
|
107
173
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
174
|
+
rawResponse.output.forEach(item => {
|
|
175
|
+
if (item.type === "function_call") {
|
|
176
|
+
const { parsed_arguments, ...rest } = item;
|
|
177
|
+
const cleanedItem = { ...rest, arguments: JSON.stringify(item.arguments) };
|
|
178
|
+
this.addInput(cleanedItem);
|
|
179
|
+
} else {
|
|
180
|
+
this.addInput(item);
|
|
114
181
|
}
|
|
115
|
-
return item;
|
|
116
182
|
});
|
|
117
|
-
|
|
118
|
-
this.input = this.input.concat(cleanedOutput);
|
|
119
183
|
|
|
120
|
-
// Step 3: collect all function calls
|
|
121
184
|
const functionCalls = rawResponse.output.filter(item => item.type === "function_call");
|
|
122
185
|
|
|
123
186
|
if (functionCalls.length > 0) {
|
|
124
187
|
for (const call of functionCalls) {
|
|
125
188
|
let args;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
console.error("Failed to parse function call arguments:", call.arguments);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
189
|
+
args = JSON.parse(call.arguments);
|
|
190
|
+
call.arguments = args
|
|
191
|
+
executed.push(call)
|
|
132
192
|
|
|
133
193
|
const tool = allTools.find(t => t.name === call.name);
|
|
134
194
|
if (!tool || !tool.func) {
|
|
135
195
|
throw new Error(`Tool ${call.name} not found or missing implementation.`);
|
|
136
196
|
}
|
|
137
197
|
|
|
138
|
-
// Step 4: execute the function
|
|
139
198
|
const result = await tool.func(args);
|
|
140
199
|
|
|
141
|
-
// Step 5: append function call output to input
|
|
142
200
|
this.input.push({
|
|
143
201
|
type: "function_call_output",
|
|
144
202
|
call_id: call.call_id,
|
|
145
203
|
output: JSON.stringify(result),
|
|
146
204
|
});
|
|
147
205
|
}
|
|
148
|
-
|
|
206
|
+
|
|
149
207
|
// Step 6: send updated input back to model for final response
|
|
150
208
|
response = await this.llmService.chat(this.input, {
|
|
151
209
|
tools: allTools,
|
|
152
210
|
model: this.model,
|
|
153
211
|
outputSchema: this.outputSchema,
|
|
212
|
+
...this.additionalOptions
|
|
154
213
|
});
|
|
155
214
|
}
|
|
215
|
+
response.executed = executed;
|
|
156
216
|
return response;
|
|
157
217
|
}
|
|
158
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Performs cleanup operations, primarily closing MCP server connections.
|
|
221
|
+
* @returns {Promise<void>}
|
|
222
|
+
*/
|
|
159
223
|
async cleanup() {
|
|
160
224
|
if (this.mcpManager) {
|
|
161
225
|
await this.mcpManager.cleanup();
|
|
162
226
|
}
|
|
163
227
|
}
|
|
164
|
-
}
|
|
228
|
+
}
|
package/src/config.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const defaultProvider = 'openai';
|
|
2
|
+
export const defaultModel = 'gpt-5';
|
package/src/llmService.js
CHANGED
|
@@ -1,30 +1,25 @@
|
|
|
1
|
-
import { validateProviderName } from './providers/registry.js';
|
|
1
|
+
import { getAllowedProviders, validateProviderName } from './providers/registry.js';
|
|
2
2
|
|
|
3
3
|
export class LLMService {
|
|
4
4
|
constructor(provider, apiKey) {
|
|
5
5
|
this.provider = validateProviderName(provider);
|
|
6
|
+
this.providerNamespace = getAllowedProviders()[this.provider]?.namespace;
|
|
6
7
|
this.apiKey = apiKey;
|
|
7
|
-
this.client =
|
|
8
|
+
this.client = this._getProviderClient();
|
|
8
9
|
|
|
9
10
|
if (!apiKey) {
|
|
10
11
|
throw new Error(`API key is required for provider: ${provider}`);
|
|
11
12
|
}
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.client = provider.createClient(this.apiKey);
|
|
19
|
-
}
|
|
20
|
-
return this.client;
|
|
15
|
+
// Instead of using a dynamic import here, we use the imported registry namespace
|
|
16
|
+
_getProviderClient() {
|
|
17
|
+
// Returns the client instance for the specified provider
|
|
18
|
+
return this.providerNamespace.createClient(this.apiKey);
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
async chat(input, {inputSchema = null, outputSchema = null, ...options} = {}) {
|
|
24
|
-
|
|
25
|
-
const provider = await import(`./providers/${this.provider}.js`);
|
|
26
|
-
|
|
27
|
-
return provider.chat(client, input, {
|
|
21
|
+
async chat(input, {inputSchema = null, outputSchema = null, ...options} = {}) {
|
|
22
|
+
return this.providerNamespace.chat(this.client, input, {
|
|
28
23
|
inputSchema,
|
|
29
24
|
outputSchema,
|
|
30
25
|
...options
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import sqlite3 from 'sqlite3';
|
|
5
|
+
|
|
6
|
+
export const loadStrategies = {
|
|
7
|
+
/**
|
|
8
|
+
* Loads and parses a file from the local file system.
|
|
9
|
+
* @param {string} path - The file path.
|
|
10
|
+
* @returns {Promise<string>} The file contents.
|
|
11
|
+
*/
|
|
12
|
+
file: async (path) => {
|
|
13
|
+
try {
|
|
14
|
+
const fileContents = await fs.readFile(path, 'utf-8');
|
|
15
|
+
return fileContents;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error(`File loading error: ${error.message}`);
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Loads and parses data from a URL.
|
|
23
|
+
* @param {string} path - The URL.
|
|
24
|
+
* @param {string} parserName - The key of the parser (e.g., 'json', 'yaml').
|
|
25
|
+
* @returns {Promise<string>} The URL contents.
|
|
26
|
+
*/
|
|
27
|
+
url: (path) => {
|
|
28
|
+
const protocol = path.startsWith('https') ? https : http;
|
|
29
|
+
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
protocol.get(path, (response) => {
|
|
32
|
+
let data = '';
|
|
33
|
+
response.on('data', (chunk) => data += chunk);
|
|
34
|
+
response.on('end', () => {
|
|
35
|
+
try {
|
|
36
|
+
// Use the specified parser
|
|
37
|
+
resolve(data);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
reject(error); // Pass up parsing error
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}).on('error', (error) => {
|
|
43
|
+
reject(new Error(`URL loading error: ${error.message}`));
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Loads prompt data from an SQLite database.
|
|
50
|
+
* @param {string} path - The path to the SQLite DB file.
|
|
51
|
+
* @returns {Promise<object>} The prompt data object in key-value pairs of signature: { prompt: string, output: string }.
|
|
52
|
+
*/
|
|
53
|
+
sqlite: (path) => {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const db = new sqlite3.Database(path, sqlite3.OPEN_READONLY, (err) => {
|
|
56
|
+
if (err) {
|
|
57
|
+
return reject(new Error(`SQLite connection error: ${err.message}`));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
resolve(db);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import yaml from 'js-yaml';
|
|
2
|
+
|
|
3
|
+
export const parseStrategies = {
|
|
4
|
+
/**
|
|
5
|
+
* Parses a YAML string.
|
|
6
|
+
* @param {string} data - The raw YAML string.
|
|
7
|
+
* @returns {object} The parsed JavaScript object.
|
|
8
|
+
*/
|
|
9
|
+
yaml: (data) => {
|
|
10
|
+
try {
|
|
11
|
+
return yaml.load(data);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new Error(`YAML parsing error: ${error.message}`);
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parses a JSON string.
|
|
19
|
+
* @param {string} data - The raw JSON string.
|
|
20
|
+
* @returns {object} The parsed JavaScript object.
|
|
21
|
+
*/
|
|
22
|
+
json: (data) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(data);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw new Error(`JSON parsing error: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parses a sqlite database.
|
|
32
|
+
* @param {object} db - The sqlite database.
|
|
33
|
+
* @returns {object} The parsed sqlite database in key-value pairs of signature: { prompt: string, output: string }.
|
|
34
|
+
*/
|
|
35
|
+
sqlite: (db) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const query = 'SELECT signature, prompt, output FROM prompt_store';
|
|
38
|
+
db.all(query, [], (err, rows) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
return reject(new Error(`SQLite query error: ${err.message}`));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = {};
|
|
44
|
+
rows.forEach(row => {
|
|
45
|
+
data[row.signature] = { prompt: row.prompt, output: row.output };
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
db.close((err) => {
|
|
49
|
+
if (err) {
|
|
50
|
+
console.error(`SQLite close error: ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
resolve(data);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parses a custom text format.
|
|
60
|
+
* Sections are delimited by a delimiter at the start of a line (default is '#').
|
|
61
|
+
* The first line of a section is the key.
|
|
62
|
+
* @param {string} data - The raw text data.
|
|
63
|
+
* @param {string} delimiter - The delimiter at the start of a line (default is '#').
|
|
64
|
+
* @returns {object} A dictionary of sections.
|
|
65
|
+
*/
|
|
66
|
+
customText: (data, delimiter = '#') => {
|
|
67
|
+
// Use RegExp to split only on delimiters at the beginning of a line
|
|
68
|
+
const sections = data.split(new RegExp(`^${delimiter}`, 'm'));
|
|
69
|
+
const sectionDict = sections.reduce((acc, section) => {
|
|
70
|
+
const lines = section.trim().split('\n');
|
|
71
|
+
const key = lines.shift();
|
|
72
|
+
if (key) {
|
|
73
|
+
acc[key.trim()] = lines.join('\n');
|
|
74
|
+
}
|
|
75
|
+
return acc;
|
|
76
|
+
}, {});
|
|
77
|
+
return sectionDict;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { loadStrategies } from './loadStrategies.js';
|
|
2
|
+
import { parseStrategies } from './parseStrategies.js';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
class Prompt {
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} templateString The raw prompt string.
|
|
8
|
+
* @param {string} startDel The start delimiter, e.g., '{{'
|
|
9
|
+
* @param {string} endDel The end delimiter, e.g., '}}'
|
|
10
|
+
*/
|
|
11
|
+
constructor(templateString, startDel = '{{', endDel = '}}') {
|
|
12
|
+
this.template = templateString;
|
|
13
|
+
this.delimiterStart = startDel;
|
|
14
|
+
this.delimiterEnd = endDel;
|
|
15
|
+
|
|
16
|
+
// Escape special chars so user delimiters like '?' don't break regex logic.
|
|
17
|
+
const escapedStart = this.delimiterStart.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
18
|
+
const escapedEnd = this.delimiterEnd.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
19
|
+
|
|
20
|
+
// Matches start delimiter, captures variable name (alphanumeric + dots), matches end.
|
|
21
|
+
this.varRegex = new RegExp(`${escapedStart}\\s*([a-zA-Z0-9_.]+)\\s*${escapedEnd}`, 'g');
|
|
22
|
+
|
|
23
|
+
this.variables = this._discoverVariables();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Discovers all unique variables in the template.
|
|
28
|
+
* @returns {Set<string>} A set of variable names.
|
|
29
|
+
*/
|
|
30
|
+
_discoverVariables() {
|
|
31
|
+
const vars = new Set();
|
|
32
|
+
let match;
|
|
33
|
+
this.varRegex.lastIndex = 0;
|
|
34
|
+
while ((match = this.varRegex.exec(this.template)) !== null) {
|
|
35
|
+
vars.add(match[1].trim());
|
|
36
|
+
}
|
|
37
|
+
this.varRegex.lastIndex = 0;
|
|
38
|
+
return vars;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Formats the prompt template with the given variables.
|
|
43
|
+
* @param {object} variables - An object where keys are variable names.
|
|
44
|
+
* @returns {string} The formatted prompt string.
|
|
45
|
+
*/
|
|
46
|
+
format(variables = {}) {
|
|
47
|
+
return this.template.replace(this.varRegex, (match, varPath) => {
|
|
48
|
+
const cleanPath = varPath.trim();
|
|
49
|
+
const value = variables[cleanPath];
|
|
50
|
+
|
|
51
|
+
// If value is undefined, leave the variable intact
|
|
52
|
+
return value !== undefined ? String(value) : match;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @returns {string[]} An array of variable names found in the prompt.
|
|
58
|
+
*/
|
|
59
|
+
getVars() {
|
|
60
|
+
return Array.from(this.variables);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// --- Main Prompt Loader Class ---
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Handles loading prompts from various sources and provides
|
|
69
|
+
* access to formatted Prompt objects.
|
|
70
|
+
* * Use the static `PromptLoader.create()` method to instantiate.
|
|
71
|
+
*/
|
|
72
|
+
class PromptLoader {
|
|
73
|
+
/**
|
|
74
|
+
* Private constructor. Use `PromptLoader.create()` to instantiate.
|
|
75
|
+
* @param {object} promptData - The raw, parsed object (e.g., { "greeting": "..." })
|
|
76
|
+
* @param {object} [options] - Options passed from create, including delimiter settings.
|
|
77
|
+
*/
|
|
78
|
+
constructor(promptData, options = {}) {
|
|
79
|
+
this.prompts = new Map();
|
|
80
|
+
|
|
81
|
+
const startDel = options.delimiterStart || '{{';
|
|
82
|
+
const endDel = options.delimiterEnd || '}}';
|
|
83
|
+
|
|
84
|
+
for (const [key, value] of Object.entries(promptData)) {
|
|
85
|
+
let promptString;
|
|
86
|
+
|
|
87
|
+
// Handle different data structures
|
|
88
|
+
if (typeof value === 'string') {
|
|
89
|
+
// E.g., { "greeting": "Hello {{name}}" }
|
|
90
|
+
promptString = value;
|
|
91
|
+
} else if (value && typeof value.prompt === 'string') {
|
|
92
|
+
// E.g., { "greeting": { "prompt": "Hello {{name}}", "output": "..." } }
|
|
93
|
+
promptString = value.prompt;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (promptString) {
|
|
97
|
+
// Pass custom delimiters to the Prompt constructor
|
|
98
|
+
this.prompts.set(key, new Prompt(promptString, startDel, endDel));
|
|
99
|
+
} else {
|
|
100
|
+
console.warn(`No valid prompt string found for key: ${key}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static _determineLoader(resourcePathOrUrl, parserName) {
|
|
106
|
+
// SQLite requires a specific loader (db connection), regardless of path
|
|
107
|
+
if (parserName === 'sqlite') return 'sqlite';
|
|
108
|
+
|
|
109
|
+
// Otherwise, check protocol
|
|
110
|
+
const isUrl = resourcePathOrUrl.startsWith('http://') || resourcePathOrUrl.startsWith('https://');
|
|
111
|
+
return isUrl ? 'url' : 'file';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Determines the parser name based on the resource path.
|
|
116
|
+
* @param {string} resourcePath - The path or URL of the resource.
|
|
117
|
+
* @returns {string} The name of the parser strategy ('yaml', 'json', 'sqlite', or 'customText').
|
|
118
|
+
*/
|
|
119
|
+
static _determineParser(resourcePath) {
|
|
120
|
+
const cleanPath = resourcePath.split('?')[0];
|
|
121
|
+
const extension = path.extname(cleanPath).toLowerCase();
|
|
122
|
+
|
|
123
|
+
const map = {
|
|
124
|
+
'.yaml': 'yaml',
|
|
125
|
+
'.yml': 'yaml',
|
|
126
|
+
'.json': 'json',
|
|
127
|
+
'.db': 'sqlite',
|
|
128
|
+
'.sqlite': 'sqlite',
|
|
129
|
+
'.txt': 'customText',
|
|
130
|
+
'.md': 'customText'
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
return map[extension] || 'customText';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Asynchronously creates and initializes a PromptLoader.
|
|
138
|
+
* This is the main entry point for the class.
|
|
139
|
+
* @param {string} resourcePathOrUrl - The file path or URL to the prompt resource.
|
|
140
|
+
* @param {object} [options] - Optional settings.
|
|
141
|
+
* @param {string} [options.parser] - Explicitly set the parser (overrides extension analysis).
|
|
142
|
+
* @returns {Promise<PromptLoader>} A new, initialized PromptLoader instance.
|
|
143
|
+
*/
|
|
144
|
+
static async create(resourcePathOrUrl, options = {}) {
|
|
145
|
+
// 1. Determine Strategies
|
|
146
|
+
const parserName = options.parser || PromptLoader._determineParser(resourcePathOrUrl);
|
|
147
|
+
const loaderName = PromptLoader._determineLoader(resourcePathOrUrl, parserName);
|
|
148
|
+
|
|
149
|
+
// 2. Validate
|
|
150
|
+
if (!loadStrategies[loaderName]) throw new Error(`Unknown loader: ${loaderName}`);
|
|
151
|
+
if (!parseStrategies[parserName]) throw new Error(`Unknown parser: ${parserName}`);
|
|
152
|
+
|
|
153
|
+
// 3. Universal Execution Flow
|
|
154
|
+
// 'rawResource' adapts: it's a String for files, or a DbConnection for sqlite
|
|
155
|
+
const rawResource = await loadStrategies[loaderName](resourcePathOrUrl);
|
|
156
|
+
|
|
157
|
+
// The parser strategy handles its specific input type (String or DbConnection)
|
|
158
|
+
const promptData = await parseStrategies[parserName](rawResource);
|
|
159
|
+
|
|
160
|
+
return new PromptLoader(promptData, options);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Retrieves an initialized Prompt object by its ID.
|
|
165
|
+
* @param {string} id - The key of the prompt.
|
|
166
|
+
* @returns {Prompt | undefined} The Prompt object, or undefined if not found.
|
|
167
|
+
*/
|
|
168
|
+
getPrompt(id) {
|
|
169
|
+
const prompt = this.prompts.get(id);
|
|
170
|
+
if (!prompt) {
|
|
171
|
+
console.error(`Prompt ID "${id}" does not exist.`);
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
return prompt;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @returns {Map<string, Prompt>} A map of all loaded prompt objects.
|
|
179
|
+
*/
|
|
180
|
+
getAllPrompts() {
|
|
181
|
+
return this.prompts;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export {
|
|
186
|
+
PromptLoader,
|
|
187
|
+
Prompt,
|
|
188
|
+
};
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"greeting_simple": "Hello, {{user_name}}! Welcome to the system.",
|
|
3
|
+
"analyzer_complex": "Review the following text and perform a {{task_type}} analysis.\nFocus on {{metric_1}} and {{metric_2}}.\n\n***TEXT TO ANALYZE***\n{{raw_text_input}}\n***END OF TEXT***\n\nProvide your output as a JSON object.",
|
|
4
|
+
"summarizer_with_metadata": {
|
|
5
|
+
"prompt": "Summarize the document titled '{{document_title}}' into a maximum of {{word_count}} words.",
|
|
6
|
+
"output": "plain_text",
|
|
7
|
+
"version": 2.1
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# greeting_simple
|
|
2
|
+
Hello, {{user_name}}! Welcome to the system.
|
|
3
|
+
|
|
4
|
+
# analyzer_complex
|
|
5
|
+
Review the following text and perform a {{task_type}} analysis.
|
|
6
|
+
Focus on {{metric_1}} and {{metric_2}}.
|
|
7
|
+
|
|
8
|
+
***TEXT TO ANALYZE***
|
|
9
|
+
{{raw_text_input}}
|
|
10
|
+
***END OF TEXT***
|
|
11
|
+
|
|
12
|
+
Provide your output as a JSON object.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# greeting_simple
|
|
2
|
+
Hello, {{user_name}}! Welcome to the system.
|
|
3
|
+
|
|
4
|
+
# analyzer_complex
|
|
5
|
+
Review the following text and perform a {{task_type}} analysis.
|
|
6
|
+
Focus on {{metric_1}} and {{metric_2}}.
|
|
7
|
+
|
|
8
|
+
***TEXT TO ANALYZE***
|
|
9
|
+
{{raw_text_input}}
|
|
10
|
+
***END OF TEXT***
|
|
11
|
+
|
|
12
|
+
Provide your output as a JSON object.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
greeting_simple: "Hello, {{user_name}}! Welcome to the system."
|
|
2
|
+
|
|
3
|
+
analyzer_complex: |
|
|
4
|
+
Review the following text and perform a {{task_type}} analysis.
|
|
5
|
+
Focus on {{metric_1}} and {{metric_2}}.
|
|
6
|
+
|
|
7
|
+
***TEXT TO ANALYZE***
|
|
8
|
+
{{raw_text_input}}
|
|
9
|
+
***END OF TEXT***
|
|
10
|
+
|
|
11
|
+
Provide your output as a JSON object.
|
|
12
|
+
|
|
13
|
+
summarizer_with_metadata:
|
|
14
|
+
prompt: "Summarize the document titled '{{document_title}}' into a maximum of {{word_count}} words."
|
|
15
|
+
output: "plain_text"
|
|
16
|
+
version: 2.1
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PromptLoader
|
|
3
|
+
} from '../promptLoader.js';
|
|
4
|
+
import { LLMService } from '@peebles-group/agentlib-js';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
dotenv.config({ path: '../../../.env' });
|
|
7
|
+
|
|
8
|
+
const llm = new LLMService('openai', process.env.OPENAI_API_KEY);
|
|
9
|
+
|
|
10
|
+
const DB_FILE = './test-prompts.db';
|
|
11
|
+
const YAML_FILE = './test-prompts.yaml';
|
|
12
|
+
const JSON_FILE = './test-prompts.json';
|
|
13
|
+
const TXT_FILE = './test-prompts.txt';
|
|
14
|
+
const MD_FILE = './test-prompts.md';
|
|
15
|
+
|
|
16
|
+
const COMMON_VARIABLES = {
|
|
17
|
+
user_name: 'Arsen',
|
|
18
|
+
current_date: 'Monday'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const FILE_EXPECTED = "Hello, Arsen! Welcome to the system.";
|
|
22
|
+
|
|
23
|
+
const DB_EXPECTED = "Hello, Arsen! Welcome to the system. Today is Monday.";
|
|
24
|
+
|
|
25
|
+
const TEST_MAP = [
|
|
26
|
+
{
|
|
27
|
+
name: "YAML Test (.yaml)",
|
|
28
|
+
path: YAML_FILE,
|
|
29
|
+
signature: "greeting_simple",
|
|
30
|
+
variables: COMMON_VARIABLES,
|
|
31
|
+
expected: FILE_EXPECTED
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "JSON Test (.json)",
|
|
35
|
+
path: JSON_FILE,
|
|
36
|
+
signature: "greeting_simple",
|
|
37
|
+
variables: COMMON_VARIABLES,
|
|
38
|
+
expected: FILE_EXPECTED
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "Text Test (.txt)",
|
|
42
|
+
path: TXT_FILE,
|
|
43
|
+
signature: "greeting_simple",
|
|
44
|
+
variables: COMMON_VARIABLES,
|
|
45
|
+
expected: FILE_EXPECTED
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "Markdown Test (.md)",
|
|
49
|
+
path: MD_FILE,
|
|
50
|
+
signature: "greeting_simple",
|
|
51
|
+
variables: COMMON_VARIABLES,
|
|
52
|
+
expected: FILE_EXPECTED
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "SQLite Test (.db)",
|
|
56
|
+
path: DB_FILE,
|
|
57
|
+
signature: "greeting_simple",
|
|
58
|
+
variables: COMMON_VARIABLES,
|
|
59
|
+
expected: DB_EXPECTED
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
async function runTest(testName, resourcePath, promptSignature, variables, expectedOutput) {
|
|
64
|
+
let loader;
|
|
65
|
+
console.log(`\n--- Running Test: ${testName} ---`);
|
|
66
|
+
console.log(` Resource Path: ${resourcePath}`);
|
|
67
|
+
try {
|
|
68
|
+
loader = await PromptLoader.create(resourcePath);
|
|
69
|
+
const prompt = loader.getPrompt(promptSignature);
|
|
70
|
+
|
|
71
|
+
if (!prompt) {
|
|
72
|
+
console.error(`[FAIL]: Prompt signature "${promptSignature}" not found.`);
|
|
73
|
+
const availableKeys = Array.from(loader.getAllPrompts().keys());
|
|
74
|
+
console.log(` [WARNING]: Available keys found in file: [${availableKeys.join(', ')}]`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const formatted = prompt.format(variables)
|
|
79
|
+
.replace(/\s+/g, ' ')
|
|
80
|
+
.trim();
|
|
81
|
+
|
|
82
|
+
const cleanExpected = expectedOutput.replace(/\s+/g, ' ').trim();
|
|
83
|
+
|
|
84
|
+
if (formatted === cleanExpected) {
|
|
85
|
+
console.log(`[PASS]: Prompt loaded and formatted correctly.`);
|
|
86
|
+
return true;
|
|
87
|
+
} else {
|
|
88
|
+
console.error(`[FAIL]: Output Mismatch.`);
|
|
89
|
+
console.error(` Expected: "${cleanExpected}"`);
|
|
90
|
+
console.error(` Received: "${formatted}"`);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(`[FATAL FAIL]: Error during ${testName}: ${error.message}`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
console.log("Starting PromptLoader Versatility Test (Custom Delimiter mode)");
|
|
102
|
+
|
|
103
|
+
let allPassed = true;
|
|
104
|
+
|
|
105
|
+
for (const testCase of TEST_MAP) {
|
|
106
|
+
const passed = await runTest(
|
|
107
|
+
testCase.name,
|
|
108
|
+
testCase.path,
|
|
109
|
+
testCase.signature,
|
|
110
|
+
testCase.variables,
|
|
111
|
+
testCase.expected
|
|
112
|
+
);
|
|
113
|
+
allPassed = allPassed && passed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log("\n----------------------------------------------------");
|
|
117
|
+
console.log(allPassed ? "[SUCCESS]: ALL TESTS PASSED SUCCESSFULLY!" : "[FAILURE]: ONE OR MORE TESTS FAILED. CHECK LOGS ABOVE.");
|
|
118
|
+
console.log("----------------------------------------------------");
|
|
119
|
+
|
|
120
|
+
// --- LLM Service Test ---
|
|
121
|
+
console.log("\n--- Starting LLM Service Test ---");
|
|
122
|
+
|
|
123
|
+
const LLM_TEST_SIGNATURE = 'analyzer_complex';
|
|
124
|
+
const LLM_TEST_PATH = YAML_FILE;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const loader = await PromptLoader.create(LLM_TEST_PATH);
|
|
128
|
+
const prompt = loader.getPrompt(LLM_TEST_SIGNATURE);
|
|
129
|
+
|
|
130
|
+
if (!prompt) {
|
|
131
|
+
console.error(`[LLM TEST FAIL]: Prompt signature '${LLM_TEST_SIGNATURE}' not found in ${LLM_TEST_PATH}. Skipping LLM call.`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const formattedPrompt = prompt.format(COMMON_VARIABLES);
|
|
136
|
+
|
|
137
|
+
const input = [{ role: 'user', content: formattedPrompt }];
|
|
138
|
+
|
|
139
|
+
console.log(`[INFO]: Sending prompt to LLM: "${formattedPrompt}"`);
|
|
140
|
+
|
|
141
|
+
const response = await llm.chat(input, { model: 'gpt-4o-mini' });
|
|
142
|
+
|
|
143
|
+
console.log(`\n[LLM RESPONSE]:`);
|
|
144
|
+
console.log(response);
|
|
145
|
+
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(`[LLM TEST FAIL]: Failed to execute LLM call.`, error.message);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main();
|
package/src/providers/openai.js
CHANGED
|
@@ -36,7 +36,7 @@ export async function chat(client, input, { inputSchema, outputSchema, ...option
|
|
|
36
36
|
}
|
|
37
37
|
return {output: output, rawResponse: response};
|
|
38
38
|
} catch (error) {
|
|
39
|
-
console.error(`Error during OpenAI chat
|
|
39
|
+
console.error(`Error during OpenAI chat response creation:`, error);
|
|
40
40
|
throw error;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -1,18 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import * as OpenAIProvider from './openai.js';
|
|
2
|
+
import * as GeminiProvider from './gemini.js';
|
|
3
|
+
// Need to import namespaces when adding new providers here
|
|
4
|
+
|
|
5
|
+
const ALLOWED_PROVIDERS = {
|
|
6
|
+
openai: {name: 'OpenAI', namespace: OpenAIProvider},
|
|
7
|
+
gemini: {name: 'Gemini', namespace: GeminiProvider},
|
|
8
|
+
};
|
|
9
|
+
// Need to add new providers to this object in this format
|
|
2
10
|
|
|
3
11
|
export function getAllowedProviders() {
|
|
4
|
-
|
|
12
|
+
// Procedure that returns the object
|
|
13
|
+
return ALLOWED_PROVIDERS;
|
|
5
14
|
}
|
|
6
15
|
|
|
7
16
|
export function validateProviderName(providerName) {
|
|
17
|
+
// Checks if a valid provider name has been passed and returns normalized name
|
|
8
18
|
if (typeof providerName !== 'string') {
|
|
9
19
|
throw new TypeError('Provider name must be a string.');
|
|
10
20
|
}
|
|
11
21
|
|
|
12
|
-
const
|
|
22
|
+
const normalize = text => text.trim().toLowerCase();
|
|
23
|
+
|
|
24
|
+
const allowedProviders = Object.values(getAllowedProviders()).map(provider => normalize(provider.name));
|
|
25
|
+
const normalizedName = normalize(providerName); // this part is ok
|
|
13
26
|
|
|
14
|
-
if (!
|
|
15
|
-
throw new Error(`Unsupported provider. Allowed providers: ${
|
|
27
|
+
if (!allowedProviders.includes(normalizedName)) {
|
|
28
|
+
throw new Error(`Unsupported provider. Allowed providers: ${allowedProviders.join(', ')}`);
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
return normalizedName;
|