Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-agent-js

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.


🧠 Core Concept (The ReAct Loop)

Agentic AI differs from standard ChatGPT. While standard AI simply does "Ask -> Answer", Agentic AI features a Metacognitive Thinking Loop.

  1. User Asks: "What is the weather in Jakarta?"
  2. Thought (AI thinks): "I don't have real-time data, I should use the weather tool."
  3. Action (AI Acts): Executes get-weather(Jakarta).
  4. Observation (System Observes): The Node.js tool returns: Hot 32 degrees.
  5. Thought (AI thinks again): "Oh, now I have the info, I just need to answer the user."
  6. Answer (Complete): "The weather is hot, 32 degrees."

📦 Installation

Install via npm:

npm install react-agent-js

🚀 Full Tutorial: How to Build Your Own Agent

Using react-agent-js requires exactly three things:

  1. Tools: JavaScript functions your agent can execute.
  2. LLM Provider: A function that connects the agent to a real AI (like Groq, OpenAI, or Gemini) and returns the response.
  3. The Agent: The ReActAgent class that glues everything together.

Here is a complete, working example:

Step 1: Define Your Tools

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)}`;
    }
  )
];

Step 2: Create the LLM Provider

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
}

Step 3: Run the Agent

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();

Expected Output

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!

📡 The onStep Callback & StepData

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
  }
});

⚠️ Important Rules for LLM Providers

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.

About

Lightweight, framework-agnostic ReAct (Reason-Action-Observation) agentic AI core for JavaScript. Build autonomous AI agents with tool calling for any LLM provider (OpenAI, Groq, Gemini) — works in Node.js backends, Telegram/Discord bots, or CLI apps. Zero framework lock-in.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages