|
|
import {defineChatSessionFunction, getLlama, LlamaChatSession} from "node-llama-cpp";
|
|
|
import {fileURLToPath} from "url";
|
|
|
import path from "path";
|
|
|
import {PromptDebugger} from "../../helper/prompt-debugger.js";
|
|
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
const debug = false;
|
|
|
|
|
|
const llama = await getLlama({debug});
|
|
|
const model = await llama.loadModel({
|
|
|
modelPath: path.join(
|
|
|
__dirname,
|
|
|
'..',
|
|
|
'..',
|
|
|
'models',
|
|
|
'hf_giladgd_gpt-oss-20b.MXFP4.gguf'
|
|
|
)
|
|
|
});
|
|
|
const context = await model.createContext({contextSize: 2000});
|
|
|
|
|
|
|
|
|
const systemPrompt = `You are a mathematical assistant that uses the ReAct (Reasoning + Acting) approach.
|
|
|
|
|
|
CRITICAL: You must follow this EXACT pattern for every problem:
|
|
|
|
|
|
Thought: [Explain what calculation you need to do next and why]
|
|
|
Action: [Call ONE tool with specific numbers]
|
|
|
Observation: [Wait for the tool result]
|
|
|
Thought: [Analyze the result and decide next step]
|
|
|
Action: [Call another tool if needed]
|
|
|
Observation: [Wait for the tool result]
|
|
|
... (repeat as many times as needed)
|
|
|
Thought: [Once you have ALL the information needed to answer the question]
|
|
|
Answer: [Give the final answer and STOP]
|
|
|
|
|
|
RULES:
|
|
|
1. Only write "Answer:" when you have the complete final answer to the user's question
|
|
|
2. After writing "Answer:", DO NOT continue calculating or thinking
|
|
|
3. Break complex problems into the smallest possible steps
|
|
|
4. Use tools for ALL calculations - never calculate in your head
|
|
|
5. Each Action should call exactly ONE tool
|
|
|
|
|
|
EXAMPLE:
|
|
|
User: "What is 5 + 3, then multiply that by 2?"
|
|
|
|
|
|
Thought: First I need to add 5 and 3
|
|
|
Action: add(5, 3)
|
|
|
Observation: 8
|
|
|
Thought: Now I need to multiply that result by 2
|
|
|
Action: multiply(8, 2)
|
|
|
Observation: 16
|
|
|
Thought: I now have the final result
|
|
|
Answer: 16`;
|
|
|
|
|
|
const session = new LlamaChatSession({
|
|
|
contextSequence: context.getSequence(),
|
|
|
systemPrompt,
|
|
|
});
|
|
|
|
|
|
|
|
|
const add = defineChatSessionFunction({
|
|
|
description: "Add two numbers together",
|
|
|
params: {
|
|
|
type: "object",
|
|
|
properties: {
|
|
|
a: {
|
|
|
type: "number",
|
|
|
description: "First number"
|
|
|
},
|
|
|
b: {
|
|
|
type: "number",
|
|
|
description: "Second number"
|
|
|
}
|
|
|
},
|
|
|
required: ["a", "b"]
|
|
|
},
|
|
|
async handler(params) {
|
|
|
const result = params.a + params.b;
|
|
|
console.log(`\n π§ TOOL CALLED: add(${params.a}, ${params.b})`);
|
|
|
console.log(` π RESULT: ${result}\n`);
|
|
|
return result.toString();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const multiply = defineChatSessionFunction({
|
|
|
description: "Multiply two numbers together",
|
|
|
params: {
|
|
|
type: "object",
|
|
|
properties: {
|
|
|
a: {
|
|
|
type: "number",
|
|
|
description: "First number"
|
|
|
},
|
|
|
b: {
|
|
|
type: "number",
|
|
|
description: "Second number"
|
|
|
}
|
|
|
},
|
|
|
required: ["a", "b"]
|
|
|
},
|
|
|
async handler(params) {
|
|
|
const result = params.a * params.b;
|
|
|
console.log(`\n π§ TOOL CALLED: multiply(${params.a}, ${params.b})`);
|
|
|
console.log(` π RESULT: ${result}\n`);
|
|
|
return result.toString();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const subtract = defineChatSessionFunction({
|
|
|
description: "Subtract second number from first number",
|
|
|
params: {
|
|
|
type: "object",
|
|
|
properties: {
|
|
|
a: {
|
|
|
type: "number",
|
|
|
description: "Number to subtract from"
|
|
|
},
|
|
|
b: {
|
|
|
type: "number",
|
|
|
description: "Number to subtract"
|
|
|
}
|
|
|
},
|
|
|
required: ["a", "b"]
|
|
|
},
|
|
|
async handler(params) {
|
|
|
const result = params.a - params.b;
|
|
|
console.log(`\n π§ TOOL CALLED: subtract(${params.a}, ${params.b})`);
|
|
|
console.log(` π RESULT: ${result}\n`);
|
|
|
return result.toString();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const divide = defineChatSessionFunction({
|
|
|
description: "Divide first number by second number",
|
|
|
params: {
|
|
|
type: "object",
|
|
|
properties: {
|
|
|
a: {
|
|
|
type: "number",
|
|
|
description: "Dividend (number to be divided)"
|
|
|
},
|
|
|
b: {
|
|
|
type: "number",
|
|
|
description: "Divisor (number to divide by)"
|
|
|
}
|
|
|
},
|
|
|
required: ["a", "b"]
|
|
|
},
|
|
|
async handler(params) {
|
|
|
if (params.b === 0) {
|
|
|
console.log(`\n π§ TOOL CALLED: divide(${params.a}, ${params.b})`);
|
|
|
console.log(` β ERROR: Division by zero\n`);
|
|
|
return "Error: Cannot divide by zero";
|
|
|
}
|
|
|
const result = params.a / params.b;
|
|
|
console.log(`\n π§ TOOL CALLED: divide(${params.a}, ${params.b})`);
|
|
|
console.log(` π RESULT: ${result}\n`);
|
|
|
return result.toString();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const functions = {add, multiply, subtract, divide};
|
|
|
|
|
|
|
|
|
async function reactAgent(userPrompt, maxIterations = 10) {
|
|
|
console.log("\n" + "=".repeat(70));
|
|
|
console.log("USER QUESTION:", userPrompt);
|
|
|
console.log("=".repeat(70) + "\n");
|
|
|
|
|
|
let iteration = 0;
|
|
|
let fullResponse = "";
|
|
|
|
|
|
while (iteration < maxIterations) {
|
|
|
iteration++;
|
|
|
console.log(`--- Iteration ${iteration} ---`);
|
|
|
|
|
|
|
|
|
let currentChunk = "";
|
|
|
const response = await session.prompt(
|
|
|
iteration === 1 ? userPrompt : "Continue your reasoning. What's the next step?",
|
|
|
{
|
|
|
functions,
|
|
|
maxTokens: 300,
|
|
|
onTextChunk: (chunk) => {
|
|
|
|
|
|
process.stdout.write(chunk);
|
|
|
currentChunk += chunk;
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
|
|
|
console.log();
|
|
|
|
|
|
fullResponse += currentChunk;
|
|
|
|
|
|
|
|
|
if (!currentChunk.trim() && !response.trim()) {
|
|
|
console.log(" (No output generated this iteration)\n");
|
|
|
}
|
|
|
|
|
|
|
|
|
if (response.toLowerCase().includes("answer:") ||
|
|
|
fullResponse.toLowerCase().includes("answer:")) {
|
|
|
console.log("\n" + "=".repeat(70));
|
|
|
console.log("FINAL ANSWER REACHED");
|
|
|
console.log("=".repeat(70));
|
|
|
return fullResponse;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
console.log("\nβ οΈ Max iterations reached without final answer");
|
|
|
return fullResponse || "Could not complete reasoning within iteration limit.";
|
|
|
}
|
|
|
|
|
|
|
|
|
const queries = [
|
|
|
|
|
|
|
|
|
|
|
|
"A store sells 15 items on Monday at $8 each, 20 items on Tuesday at $8 each, and 10 items on Wednesday at $8 each. What's the average number of items sold per day, and what's the total revenue?",
|
|
|
];
|
|
|
|
|
|
for (const query of queries) {
|
|
|
await reactAgent(query, 3);
|
|
|
console.log("\n");
|
|
|
}
|
|
|
|
|
|
|
|
|
const promptDebugger = new PromptDebugger({
|
|
|
outputDir: './logs',
|
|
|
filename: 'react_calculator.txt',
|
|
|
includeTimestamp: true,
|
|
|
appendMode: false
|
|
|
});
|
|
|
await promptDebugger.debugContextState({session, model});
|
|
|
|
|
|
|
|
|
session.dispose()
|
|
|
context.dispose()
|
|
|
model.dispose()
|
|
|
llama.dispose() |