reActLoop
Problem
A single LLM call is not enough for tasks that require multiple steps — like looking up data, using a tool result, then forming a final answer. Wiring that loop yourself means managing conversation history, step counters, tool routing, and exit conditions in every project.
Solution
reActLoop runs a full Reason → Act → Observe cycle for you. It calls your provider, checks if tools are needed, executes them, feeds results back into the conversation, and repeats — until the agent produces a final text answer or hits maxSteps.
Feature & Use-Case
Use reActLoop when:
- Your agent needs to call tools to answer a question
- The task requires multiple LLM steps (plan → act → respond)
- You want automatic conversation history management across steps
- You are building LangChain-powered agents with dynamic tool usage
Import
import { reActLoop } from "llm-layer-engine";Function Signature
function reActLoop(options: ReActOptions): {
run(): Promise<{
result: string;
stats: {
steps: number;
toolCalls: number;
errors: number;
executionTime: {
startTime: Date;
endTime: Date;
};
};
}>;
}Parameters (ReActOptions)
| Parameter | Type | Required | Description |
|---|---|---|---|
config | AgentConfig | ✅ | Provider + model + temperature config |
provider | LLMProvider | ✅ | Your LangChain-compatible LLM provider |
messages | Message[] | ✅ | Initial conversation to start the loop |
tools | Tool[] | ❌ | Array of tools the agent can call |
maxSteps | number | ❌ | Max iterations before stopping (default: 5) |
Example — Agent With a Tool
import { reActLoop } from "llm-layer-engine";
import type { LLMProvider, Tool } from "llm-layer-engine";
import { ChatAnthropic } from "@langchain/anthropic";
// Provider setup
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-20241022" });
const provider: LLMProvider = {
name: "anthropic",
async run({ messages, config }) {
const res = await model.invoke(
messages.map((m) => ({ role: m.role, content: m.content }))
);
return { content: res.content as string };
},
};
// Define a tool
const weatherTool: Tool = {
name: "get_weather",
execute: async (input) => {
const city = input.city as string;
return { city, weather: "Clear skies, 28°C" };
},
};
// Run the loop
const loop = reActLoop({
config: {
provider,
model: "claude-3-5-sonnet-20241022",
temperature: 0.5,
},
provider,
messages: [
{ role: "system", content: "You are a weather assistant. Use tools when needed." },
{ role: "user", content: "What is the weather in Karachi?" },
],
tools: [weatherTool],
maxSteps: 5,
});
const { result, stats } = await loop.run();
console.log(result);
// → "The weather in Karachi is clear skies at 28°C."
console.log(stats);
// → { steps: 2, toolCalls: 1, errors: 0, executionTime: { ... } }How Steps Work
| Step | What Happens |
|---|---|
| 1 | Provider receives initial messages |
| 2 | If provider returns tool calls → tools execute, results added to conversation |
| 3 | Provider runs again with updated conversation |
| 4 | If provider returns plain text → loop ends, result returned |
| 5+ | Repeats until isFinished or maxSteps reached |
Stats Reference
| Field | Type | Description |
|---|---|---|
steps | number | Total steps executed |
toolCalls | number | Total tool calls made across all steps |
errors | number | Errors caught during execution |
executionTime.startTime | Date | When the loop started |
executionTime.endTime | Date | When the loop ended |
Conclusion
reActLoop is the core of any tool-using agent. Pass in your provider, your tools, and your messages — the loop handles the rest. Keep maxSteps reasonable (3–7) to avoid runaway loops and unnecessary token usage.
Last updated on