A PURE, lightweight Reason-Action-Observation (ReAct) agentic AI core in JavaScript.
This library abstracts the complex "Agentic Loop" (Thought -> Action -> Observation) into a simple, framework-agnostic class. It is decoupled from any specific UI or LLM provider, allowing you to easily embed an autonomous AI agent into your Express backend, Telegram/Discord bots, or CLI applications.
Agentic AI differs from standard ChatGPT. While standard AI simply does "Ask -> Answer", Agentic AI features a Metacognitive Thinking Loop.
- User Asks: "What is the weather in Jakarta?"
- Thought (AI thinks): "I don't have real-time data, I should use the weather tool."
- Action (AI Acts): Executes
get-weather(Jakarta). - Observation (System Observes): The Node.js tool returns:
Hot 32 degrees. - Thought (AI thinks again): "Oh, now I have the info, I just need to answer the user."
- Answer (Complete): "The weather is hot, 32 degrees."
Install via npm:
npm install react-agent-jsUsing react-agent-js requires exactly three things:
- Tools: JavaScript functions your agent can execute.
- LLM Provider: A function that connects the agent to a real AI (like Groq, OpenAI, or Gemini) and returns the response.
- The Agent: The
ReActAgentclass that glues everything together.
Here is a complete, working example:
Tools are simply an object mapping tool names to async functions. The agent will read the keys (tool names) and decide when to use them.
import { ReActAgent, createTool } from 'react-agent-js';
// 1. Define the tools your agent can use
const myTools = [
createTool(
"get-weather",
"Get the current weather for a specific location. Query should be the location name (e.g., 'Tokyo').",
async (location) => {
// In a real app, you would fetch from an API like OpenWeather
if (location.toLowerCase().includes('tokyo')) {
return "The weather in Tokyo is snowy (2°C).";
}
return `Weather data for ${location} is unavailable.`;
}
),
createTool(
"calculate-math",
"Calculate a math equation. Query should be the mathematical equation.",
async (equation) => {
// Agent can do math!
return `Result: ${eval(equation)}`;
}
)
];The agent doesn't care which AI model you use. You just need to provide an async function that takes an array of messages and returns a JSON string from your chosen AI.
Note: You must instruct your API to return JSON. In this example, we use the OpenAI-compatible endpoint.
// 2. Create the LLM Provider (Example using Groq / OpenAI)
async function myLlmProvider(messages) {
const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY_HERE',
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'llama3-70b-8192', // Or gpt-4o, etc.
messages: messages,
// CRITICAL: Ensure the LLM responds strictly in JSON!
response_format: { type: "json_object" }
})
});
const data = await response.json();
return data.choices[0].message.content; // Return the raw JSON string
}Initialize the agent with your provider, tools, and a base system prompt. Then, call agent.run().
async function main() {
const systemPrompt = "You are a helpful, autonomous AI assistant. Be concise.";
// Initialize the Agent
// You can optionally pass an array of previous messages as the 4th argument to resume a chat.
// const previousChat = [{ role: "user", content: "Hi" }, { role: "assistant", content: "Hello!" }];
const agent = new ReActAgent(myLlmProvider, myTools, systemPrompt);
const userInput = "What is the weather in Tokyo? Also, what is 15 * 4?";
console.log(`User: ${userInput}\n`);
// Run the loop!
// You can optionally pass a callback to track its thought process in real-time.
const finalResponse = await agent.run(userInput, (stepData) => {
if (stepData.status === 'thinking') {
console.log(`[Step ${stepData.step}] 🤔 Thinking...`);
} else if (stepData.status === 'executing_tool') {
console.log(`🛠️ Action: Using tool '${stepData.tool}' with query '${stepData.query}'`);
} else if (stepData.status === 'observation') {
console.log(`👀 Observation: ${stepData.result}`);
}
});
console.log(`\n🤖 Final Answer: ${finalResponse}`);
// Retrieve history to save to your database:
// const historyToSave = agent.getHistory();
}
main();When you run the code above, the agent will loop dynamically:
User: What is the weather in Tokyo? Also, what is 15 * 4?
[Step 1] 🤔 Thinking...
🛠️ Action: Using tool 'get-weather' with query 'Tokyo'
👀 Observation: The weather in Tokyo is snowy (2°C).
[Step 2] 🤔 Thinking...
🛠️ Action: Using tool 'calculate-math' with query '15 * 4'
👀 Observation: Result: 60
[Step 3] 🤔 Thinking...
🤖 Final Answer: The weather in Tokyo is currently snowy at 2°C, and the result of 15 * 4 is 60!
When you call agent.run(userInput, onStep), you can optionally provide an onStep callback to listen to the agent's internal lifecycle in real-time. This is extremely useful for building rich UIs (like showing "AI is thinking..." or rendering a dropdown of executed tools).
The callback receives a stepData object. Depending on its status, different properties will be available:
status |
Available Properties | Description |
|---|---|---|
thinking |
step (number) |
Emitted at the very beginning of a new loop iteration. Useful for showing a loading spinner. |
decision |
decision (object) |
Emitted after the LLM successfully outputs its JSON. Contains the raw { thought, action, answer } object. |
intermediate_answer |
answer (string) |
Emitted when the LLM decides to show an intermediate message to the user while simultaneously running a tool (e.g., "Please wait, I'm checking the database..."). |
executing_tool |
tool (string), query (string) |
Emitted right before the agent executes a local tool function. |
observation |
result (string) |
Emitted after the tool finishes running (returns the successful output or an error message). This result is fed back to the LLM. |
done |
finalAnswer (string) |
Emitted when the agent concludes its task and exits the loop. |
Example Usage:
agent.run("Fix my bug", (stepData) => {
switch (stepData.status) {
case 'intermediate_answer':
console.log("AI says:", stepData.answer);
break;
case 'executing_tool':
console.log(`Running ${stepData.tool}(${stepData.query})`);
break;
// ...handle other states
}
});For this library to work, your chosen LLM must be capable of generating consistent JSON. When the ReActAgent calls your llmProvider, it injects a strict JSON schema into the system prompt. If the LLM fails to return JSON, the agent will catch the error and automatically ask the LLM to fix its formatting in the next loop iteration.