@jackhua/mini-langchain
Version:
A lightweight TypeScript implementation of LangChain with cost optimization features
135 lines (130 loc) • 4.54 kB
JavaScript
;
/**
* ReAct (Reasoning + Acting) Agent implementation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReActAgent = void 0;
exports.createReActAgent = createReActAgent;
const base_1 = require("./base");
const prompt_1 = require("../prompts/prompt");
/**
* ReAct agent prompt template
*/
const REACT_PROMPT = `You are an AI assistant that uses tools to help answer questions and solve problems.
You have access to the following tools:
{tools}
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of the available tools
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {input}
{agent_scratchpad}`;
/**
* ReAct Agent - Combines reasoning with acting
*/
class ReActAgent extends base_1.BaseAgent {
constructor(config) {
super(config);
this.promptTemplate = new prompt_1.PromptTemplate({
template: REACT_PROMPT,
inputVariables: ['tools', 'input', 'agent_scratchpad']
});
}
/**
* Parse LLM output to extract action or finish
*/
parseOutput(output) {
// Check for Final Answer
const finalAnswerMatch = output.match(/Final Answer:\s*(.*)/s);
if (finalAnswerMatch) {
return {
output: finalAnswerMatch[1].trim(),
log: output
};
}
// Parse Action and Action Input
const actionMatch = output.match(/Action:\s*(.*)/);
const actionInputMatch = output.match(/Action Input:\s*(.*)/s);
if (actionMatch && actionInputMatch) {
const action = actionMatch[1].trim();
let actionInput = actionInputMatch[1].trim();
// Handle multi-line input by taking everything until the next section
const nextSectionIndex = actionInput.search(/\n(Observation|Thought|Action|Final Answer):/);
if (nextSectionIndex > -1) {
actionInput = actionInput.substring(0, nextSectionIndex).trim();
}
return {
tool: action,
toolInput: actionInput,
log: output
};
}
// If we can't parse, treat as a finish with the output
return {
output: output,
log: output
};
}
/**
* Plan the next action based on current context
*/
async plan(context) {
// Format the agent scratchpad (history)
const agentScratchpad = this.formatHistory(context);
// Add "Thought:" if we have history to continue the chain
const scratchpadWithPrompt = agentScratchpad
? agentScratchpad + '\nThought:'
: 'Thought:';
// Create prompt
const prompt = await this.promptTemplate.format({
tools: this.getToolsDescription(),
input: context.input,
agent_scratchpad: scratchpadWithPrompt
});
// Get LLM response
const response = await this.llm.call(prompt);
if (this.verbose) {
console.log('LLM Response:', response);
}
// Parse and return the next step
return this.parseOutput(response);
}
/**
* Format history specifically for ReAct format
*/
formatHistory(context) {
return context.steps
.map(step => {
const parts = [];
if (step.action) {
// Extract thought from log if available
const thoughtMatch = step.action.log?.match(/Thought:\s*(.*?)(?=\nAction:|$)/s);
if (thoughtMatch) {
parts.push(`Thought: ${thoughtMatch[1].trim()}`);
}
parts.push(`Action: ${step.action.tool}`);
parts.push(`Action Input: ${step.action.toolInput}`);
}
if (step.observation !== undefined) {
parts.push(`Observation: ${step.observation}`);
}
return parts.join('\n');
})
.filter(s => s)
.join('\n');
}
}
exports.ReActAgent = ReActAgent;
/**
* Create a ReAct agent with tools
*/
function createReActAgent(config) {
return new ReActAgent(config);
}
//# sourceMappingURL=react.js.map