feat: solution for '8. Самописный поисковый агент на основе deep agents from scratch'
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
const { OpenAI } = require('langchain/llms/openai');
|
||||
const { Tool } = require('langchain/tools');
|
||||
const { PromptTemplate } = require('langchain/prompts');
|
||||
const { ConversationBufferMemory } = require('langchain/memory');
|
||||
|
||||
/**
|
||||
* Planner class that uses an LLM to generate a plan of tool calls.
|
||||
*/
|
||||
class Planner {
|
||||
/**
|
||||
* @param {OpenAI} llm - The language model to use for planning.
|
||||
* @param {Tool[]} tools - Available tools for the agent.
|
||||
*/
|
||||
constructor(llm, tools) {
|
||||
this.llm = llm;
|
||||
this.tools = tools;
|
||||
|
||||
// Prompt template for the planner.
|
||||
this.plannerPrompt = new PromptTemplate({
|
||||
template: `You are a planning assistant. Given the user query: "{input}", produce a JSON array of steps. Each step must contain:
|
||||
- "tool": the name of the tool to use (must be one of: ${tools.map(t => t.name).join(', ')}),
|
||||
- "input": the input string for that tool.
|
||||
|
||||
The JSON array should be the only output. Example:
|
||||
[
|
||||
{"tool":"Echo","input":"Hello"}
|
||||
]`,
|
||||
inputVariables: ['input'],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a plan for the given input.
|
||||
* @param {string} input - The user query.
|
||||
* @returns {Promise<Array<{tool: string, input: string}>>} The plan steps.
|
||||
*/
|
||||
async plan(input) {
|
||||
const prompt = this.plannerPrompt.format({ input });
|
||||
const raw = await this.llm.invoke(prompt);
|
||||
let plan;
|
||||
try {
|
||||
plan = JSON.parse(raw);
|
||||
if (!Array.isArray(plan)) throw new Error('Plan is not an array');
|
||||
} catch (e) {
|
||||
throw new Error(`Planner failed to parse JSON: ${e.message}. Raw output: ${raw}`);
|
||||
}
|
||||
// Validate tool names
|
||||
for (const step of plan) {
|
||||
if (!this.tools.find(t => t.name === step.tool)) {
|
||||
throw new Error(`Planner suggested unknown tool "${step.tool}"`);
|
||||
}
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executor class that runs the planned tool calls sequentially.
|
||||
*/
|
||||
class Executor {
|
||||
/**
|
||||
* @param {Tool[]} tools - Available tools for the agent.
|
||||
*/
|
||||
constructor(tools) {
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the plan and returns the final output.
|
||||
* @param {Array<{tool: string, input: string}>} plan - The plan steps.
|
||||
* @returns {Promise<string>} The final output after executing all steps.
|
||||
*/
|
||||
async execute(plan) {
|
||||
let lastOutput = '';
|
||||
for (const step of plan) {
|
||||
const tool = this.tools.find(t => t.name === step.tool);
|
||||
if (!tool) {
|
||||
throw new Error(`Executor cannot find tool "${step.tool}"`);
|
||||
}
|
||||
const output = await tool.func(step.input);
|
||||
lastOutput = output;
|
||||
}
|
||||
return lastOutput;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DeepAgent implements the Deep Agents from Scratch template.
|
||||
*/
|
||||
class DeepAgent {
|
||||
/**
|
||||
* @param {Object} options Configuration options.
|
||||
* @param {string} [options.modelName='gpt-3.5-turbo'] The LLM model to use.
|
||||
* @param {number} [options.temperature=0.7] Temperature for LLM sampling.
|
||||
* @param {number} [options.maxTokens=512] Maximum tokens for LLM output.
|
||||
* @param {string} [options.apiKey] OpenAI API key. If not provided, will use process.env.OPENAI_API_KEY.
|
||||
*/
|
||||
constructor({
|
||||
modelName = 'gpt-3.5-turbo',
|
||||
temperature = 0.7,
|
||||
maxTokens = 512,
|
||||
apiKey,
|
||||
} = {}) {
|
||||
this.llm = new OpenAI({
|
||||
modelName,
|
||||
temperature,
|
||||
maxTokens,
|
||||
openAIApiKey: apiKey || process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
// Define a simple Echo tool that returns the input back.
|
||||
const echoTool = new Tool({
|
||||
name: 'Echo',
|
||||
description: 'Echoes the input back to the user.',
|
||||
func: async (input) => input,
|
||||
});
|
||||
|
||||
// Memory component required by the Deep Agents from Scratch template.
|
||||
this.memory = new ConversationBufferMemory({
|
||||
memoryKey: 'chat_history',
|
||||
inputKey: 'input',
|
||||
outputKey: 'output',
|
||||
});
|
||||
|
||||
this.tools = [echoTool];
|
||||
|
||||
// Instantiate Planner and Executor.
|
||||
this.planner = new Planner(this.llm, this.tools);
|
||||
this.executor = new Executor(this.tools);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the agent on a given question.
|
||||
* @param {string} question The question to answer.
|
||||
* @returns {Promise<string>} The agent's answer.
|
||||
*/
|
||||
async run(question) {
|
||||
try {
|
||||
// Store the question in memory.
|
||||
await this.memory.saveContext({ input: question }, { output: '' });
|
||||
|
||||
// Generate a plan.
|
||||
const plan = await this.planner.plan(question);
|
||||
|
||||
// Execute the plan.
|
||||
const result = await this.executor.execute(plan);
|
||||
|
||||
// Save the result in memory.
|
||||
await this.memory.saveContext({ input: question }, { output: result });
|
||||
|
||||
return result.trim();
|
||||
} catch (err) {
|
||||
console.error('DeepAgent encountered an error:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { DeepAgent };
|
||||
@@ -0,0 +1,18 @@
|
||||
require('dotenv').config();
|
||||
const { DeepAgent } = require('./deepAgent');
|
||||
|
||||
(async () => {
|
||||
const agent = new DeepAgent({
|
||||
modelName: process.env.OPENAI_MODEL || 'gpt-3.5-turbo',
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
const query = process.argv[2] || 'What is the capital of France?';
|
||||
console.log(`Query: ${query}`);
|
||||
try {
|
||||
const answer = await agent.run(query);
|
||||
console.log(`Answer: ${answer}`);
|
||||
} catch (err) {
|
||||
console.error('Error running DeepAgent:', err);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user