feat: solution for 'Повторный экзамен: Граф с рефлексией и доработкой'
This commit is contained in:
+27
-125
@@ -1,144 +1,46 @@
|
||||
/**
|
||||
* Graph implementation supporting:
|
||||
* - Self-referential edges (edges from a node to itself)
|
||||
* - Reflection (creating a reverse edge)
|
||||
* - Refinement (cloning nodes or edges with updated properties)
|
||||
*
|
||||
* All code is written manually without external IDE tools.
|
||||
* Simple graph implementation that executes nodes in a defined sequence.
|
||||
*/
|
||||
|
||||
class Graph {
|
||||
constructor() {
|
||||
/** @type {Map<string, any>} */
|
||||
this.nodes = new Map(); // nodeId -> nodeData
|
||||
/** @type {Map<string, {from: string, to: string, data: any}>} */
|
||||
this.edges = new Map(); // edgeId -> edgeObject
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
this.adj = new Map(); // fromNodeId -> Set of edgeIds
|
||||
this._edgeCounter = 0;
|
||||
this.nodes = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node to the graph.
|
||||
* @param {string} id - Unique identifier for the node.
|
||||
* @param {any} data - Arbitrary data associated with the node.
|
||||
* @throws {Error} If a node with the same id already exists.
|
||||
* @param {string} name - Unique name of the node.
|
||||
* @param {function} fn - Function that processes input and returns output.
|
||||
*/
|
||||
addNode(id, data = null) {
|
||||
if (this.nodes.has(id)) {
|
||||
throw new Error(`Node with id "${id}" already exists.`);
|
||||
addNode(name, fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error('Node must be a function.');
|
||||
}
|
||||
this.nodes.set(id, data);
|
||||
this.adj.set(id, new Set());
|
||||
return id;
|
||||
this.nodes[name] = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an edge between two nodes.
|
||||
* Self-referential edges are allowed.
|
||||
* @param {string} from - Source node id.
|
||||
* @param {string} to - Target node id.
|
||||
* @param {any} data - Arbitrary data associated with the edge.
|
||||
* @returns {string} The unique id of the created edge.
|
||||
* @throws {Error} If either node does not exist.
|
||||
* Executes a sequence of nodes with the given input.
|
||||
* @param {Array<string>} nodeSequence - Ordered list of node names to execute.
|
||||
* @param {any} input - Initial input for the first node.
|
||||
* @returns {Promise<any>} - Final output after all nodes have processed the data.
|
||||
*/
|
||||
addEdge(from, to, data = null) {
|
||||
if (!this.nodes.has(from)) {
|
||||
throw new Error(`Source node "${from}" does not exist.`);
|
||||
async run(nodeSequence, input) {
|
||||
if (!Array.isArray(nodeSequence)) {
|
||||
throw new Error('nodeSequence must be an array of node names.');
|
||||
}
|
||||
if (!this.nodes.has(to)) {
|
||||
throw new Error(`Target node "${to}" does not exist.`);
|
||||
let data = input;
|
||||
for (const name of nodeSequence) {
|
||||
const fn = this.nodes[name];
|
||||
if (!fn) {
|
||||
throw new Error(`Node "${name}" not found in the graph.`);
|
||||
}
|
||||
try {
|
||||
data = await fn(data);
|
||||
} catch (err) {
|
||||
throw new Error(`Error in node "${name}": ${err.message}`);
|
||||
}
|
||||
}
|
||||
const edgeId = `e${++this._edgeCounter}`;
|
||||
const edge = { from, to, data };
|
||||
this.edges.set(edgeId, edge);
|
||||
this.adj.get(from).add(edgeId);
|
||||
return edgeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reverse edge for the specified edge.
|
||||
* @param {string} edgeId - The id of the edge to reflect.
|
||||
* @returns {string} The id of the newly created reverse edge.
|
||||
* @throws {Error} If the edge does not exist.
|
||||
*/
|
||||
reflect(edgeId) {
|
||||
const original = this.edges.get(edgeId);
|
||||
if (!original) {
|
||||
throw new Error(`Edge "${edgeId}" does not exist.`);
|
||||
}
|
||||
return this.addEdge(original.to, original.from, original.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refines a node by cloning it with updated data.
|
||||
* All outgoing edges are also cloned to the new node.
|
||||
* @param {string} nodeId - The id of the node to refine.
|
||||
* @param {any} newData - New data to merge with the original node data.
|
||||
* @returns {string} The id of the newly created refined node.
|
||||
* @throws {Error} If the node does not exist.
|
||||
*/
|
||||
refineNode(nodeId, newData = null) {
|
||||
if (!this.nodes.has(nodeId)) {
|
||||
throw new Error(`Node "${nodeId}" does not exist.`);
|
||||
}
|
||||
const refinedId = `${nodeId}_refined`;
|
||||
const originalData = this.nodes.get(nodeId);
|
||||
const mergedData = newData !== null ? { ...originalData, ...newData } : originalData;
|
||||
this.addNode(refinedId, mergedData);
|
||||
|
||||
// Clone outgoing edges
|
||||
const outgoing = this.adj.get(nodeId);
|
||||
for (const edgeId of outgoing) {
|
||||
const edge = this.edges.get(edgeId);
|
||||
this.addEdge(refinedId, edge.to, edge.data);
|
||||
}
|
||||
return refinedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refines an edge by cloning it with updated data.
|
||||
* @param {string} edgeId - The id of the edge to refine.
|
||||
* @param {any} newData - New data to merge with the original edge data.
|
||||
* @returns {string} The id of the newly created refined edge.
|
||||
* @throws {Error} If the edge does not exist.
|
||||
*/
|
||||
refineEdge(edgeId, newData = null) {
|
||||
const original = this.edges.get(edgeId);
|
||||
if (!original) {
|
||||
throw new Error(`Edge "${edgeId}" does not exist.`);
|
||||
}
|
||||
const refinedId = `${edgeId}_refined`;
|
||||
const mergedData = newData !== null ? { ...original.data, ...newData } : original.data;
|
||||
this.addEdge(original.from, original.to, mergedData);
|
||||
return refinedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves node data.
|
||||
* @param {string} id
|
||||
* @returns {any}
|
||||
*/
|
||||
getNode(id) {
|
||||
return this.nodes.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves edge data.
|
||||
* @param {string} id
|
||||
* @returns {{from: string, to: string, data: any}}
|
||||
*/
|
||||
getEdge(id) {
|
||||
return this.edges.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns adjacency list for a node.
|
||||
* @param {string} id
|
||||
* @returns {Set<string>}
|
||||
*/
|
||||
getAdjacency(id) {
|
||||
return this.adj.get(id);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-5
@@ -1,6 +1,41 @@
|
||||
/**
|
||||
* Export the Graph class for external use.
|
||||
* This file contains no IDE-generated boilerplate.
|
||||
*/
|
||||
const Graph = require('./graph');
|
||||
module.exports = { Graph };
|
||||
const { reflect } = require('./nodes/reflect');
|
||||
const { rewrite } = require('./nodes/rewrite');
|
||||
|
||||
/**
|
||||
* Entry point of the application.
|
||||
* Builds a simple graph with reflect and rewrite nodes and runs it on sample input.
|
||||
*/
|
||||
async function main() {
|
||||
// Ensure the OpenAI API key is set
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
console.error('Error: OPENAI_API_KEY environment variable is not set.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create graph and add nodes
|
||||
const graph = new Graph();
|
||||
graph.addNode('reflect', reflect);
|
||||
graph.addNode('rewrite', rewrite);
|
||||
|
||||
// Sample input message
|
||||
const inputMessage = 'I am feeling overwhelmed with my workload and unsure how to prioritize tasks.';
|
||||
|
||||
console.log('--- Input Message ---');
|
||||
console.log(inputMessage);
|
||||
console.log('---------------------\n');
|
||||
|
||||
try {
|
||||
// Execute the graph: first reflect, then rewrite
|
||||
const finalOutput = await graph.run(['reflect', 'rewrite'], inputMessage);
|
||||
|
||||
console.log('--- Final Output ---');
|
||||
console.log(finalOutput);
|
||||
console.log('---------------------');
|
||||
} catch (err) {
|
||||
console.error('An error occurred during graph execution:');
|
||||
console.error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,35 @@
|
||||
const { OpenAI } = require('langchain-openai');
|
||||
const { ChatPromptTemplate, HumanMessagePromptTemplate } = require('langchain-core/prompts');
|
||||
const { LLMChain } = require('langchain-core/chains');
|
||||
|
||||
// Initialize the LLM (OpenAI) with a moderate temperature for reflective responses
|
||||
const llm = new OpenAI({ temperature: 0.7 });
|
||||
|
||||
// Prompt template for reflection
|
||||
const prompt = ChatPromptTemplate.fromPromptMessages([
|
||||
HumanMessagePromptTemplate.fromTemplate(
|
||||
"Please reflect on the following message:\n\n{input}"
|
||||
),
|
||||
]);
|
||||
|
||||
// Chain that combines the prompt and the LLM
|
||||
const chain = new LLMChain({ llm, prompt });
|
||||
|
||||
/**
|
||||
* Reflects on the provided input using an LLM.
|
||||
* @param {string} input - The message to reflect upon.
|
||||
* @returns {Promise<string>} - The reflective output from the LLM.
|
||||
*/
|
||||
async function reflect(input) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error('Reflect node expects a string input.');
|
||||
}
|
||||
try {
|
||||
const result = await chain.invoke({ input });
|
||||
return result.output;
|
||||
} catch (err) {
|
||||
throw new Error(`Reflect node error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { reflect };
|
||||
@@ -0,0 +1,35 @@
|
||||
const { OpenAI } = require('langchain-openai');
|
||||
const { ChatPromptTemplate, HumanMessagePromptTemplate } = require('langchain-core/prompts');
|
||||
const { LLMChain } = require('langchain-core/chains');
|
||||
|
||||
// Initialize the LLM (OpenAI) with a moderate temperature for rewriting
|
||||
const llm = new OpenAI({ temperature: 0.7 });
|
||||
|
||||
// Prompt template for rewriting
|
||||
const prompt = ChatPromptTemplate.fromPromptMessages([
|
||||
HumanMessagePromptTemplate.fromTemplate(
|
||||
"Rewrite the following message in a more concise and formal style:\n\n{input}"
|
||||
),
|
||||
]);
|
||||
|
||||
// Chain that combines the prompt and the LLM
|
||||
const chain = new LLMChain({ llm, prompt });
|
||||
|
||||
/**
|
||||
* Rewrites the provided input using an LLM.
|
||||
* @param {string} input - The message to rewrite.
|
||||
* @returns {Promise<string>} - The rewritten output from the LLM.
|
||||
*/
|
||||
async function rewrite(input) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error('Rewrite node expects a string input.');
|
||||
}
|
||||
try {
|
||||
const result = await chain.invoke({ input });
|
||||
return result.output;
|
||||
} catch (err) {
|
||||
throw new Error(`Rewrite node error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { rewrite };
|
||||
Reference in New Issue
Block a user