tiny-ai-api
Version:
A customizable and extensible client api for managing conversations and AI interactions, currently supporting the **Google Gemini** API — with flexibility to support any similar AI APIs.
252 lines (249 loc) • 8.68 kB
JavaScript
'use strict';
/**
* Internal helper to validate an array of FuzzySets.
* @param {FuzzySet[]} sets
*/
const validateFuzzySets = sets => {
if (!Array.isArray(sets)) {
throw new TypeError("Parameter 'sets' must be an array.");
}
if (!sets.every(set => set instanceof FuzzySet)) {
throw new TypeError('All elements in the array must be instances of FuzzySet.');
}
};
/**
* Performs defuzzification using the Centroid (Center of Gravity) method.
* @param {Object.<string, number>} fuzzyOutput - Results from rule evaluation.
* @param {FuzzySet[]} outputSets - The sets defining the output range.
* @param {number} [step=0.5] - Resolution of the integral approximation.
* @returns {number} The crisp output value.
*/
const defuzzifyCentroid = (fuzzyOutput, outputSets, step = 0.5) => {
validateType(fuzzyOutput, 'object', 'defuzzifyCentroid.fuzzyOutput');
for (const outputName in fuzzyOutput) {
validateType(fuzzyOutput[outputName], 'number', `fuzzyOutput['${outputName}']`);
}
validateFuzzySets(outputSets);
validateType(step, 'number', 'defuzzifyCentroid.step');
/** @type {number} - Accumulated weighted area for centroid */
let totalAreaWeighted = 0;
/** @type {number} - Accumulated total area */
let totalArea = 0;
// Numerical integration (Centroid approximation)
for (let i = 0; i <= 100; i += step) {
/** @type {number} - Maximum membership found at point x(i) */
let maxMembershipAtX = 0;
outputSets.forEach(set => {
/** @type {number} - Rule strength applied to the output set */
const strength = fuzzyOutput[set.name] || 0;
/** @type {number} - Cut or scale the output set membership */
const membership = Math.min(strength, set.calculate(i));
maxMembershipAtX = Math.max(maxMembershipAtX, membership);
});
totalAreaWeighted += i * maxMembershipAtX;
totalArea += maxMembershipAtX;
}
return totalArea === 0 ? 0 : totalAreaWeighted / totalArea;
};
/**
* Utility to calculate fuzzy membership using a trapezoidal shape safely.
* @param {number} value - The input value to check.
* @param {number} a - Start of the rise.
* @param {number} b - End of the rise (start of plateau).
* @param {number} c - Start of the fall (end of plateau).
* @param {number} d - End of the fall.
* @param {boolean} [optimize=false] - Optimizes performance by skipping math for absolute bounds.
* @returns {number} Degree of membership [0, 1].
*/
const trapezoid = (value, a, b, c, d, optimize = false) => {
if (optimize) {
// If the value is completely outside the outer bounds, return 0 immediately
if (value <= a || value >= d) return 0;
// If the value is entirely within the plateau, return 1 immediately
if (value >= b && value <= c) return 1;
}
/** @type {number} - Safely calculate rising slope */
const rise = a === b ? 1 : (value - a) / (b - a);
/** @type {number} - Safely calculate falling slope */
const fall = c === d ? 1 : (d - value) / (d - c);
/** @type {number} - Internal value clamping between 0 and 1 */
const membership = Math.max(0, Math.min(rise, 1, fall));
return isNaN(membership) ? 0 : membership;
};
/**
* Utility to validate types and throw formatted errors, preventing code repetition.
* @param {any} value - The value to evaluate.
* @param {string} expectedType - The expected data type.
* @param {string} paramName - The name of the parameter for the error message.
*/
const validateType = (value, expectedType, paramName) => {
if (typeof value !== expectedType) {
throw new TypeError(`Parameter '${paramName}' must be a ${expectedType}.`);
}
};
/**
* Represents a single Membership Function (Trapezoidal).
*/
class FuzzySet {
/**
* Utility to calculate fuzzy membership using a trapezoidal shape.
* @param {number} value - The input value to check.
* @param {number} a - Start of the rise.
* @param {number} b - End of the rise (start of plateau).
* @param {number} c - Start of the fall (end of plateau).
* @param {number} d - End of the fall.
* @param {boolean} [optimize=false] - Performance optimization flag.
* @returns {number} Degree of membership [0, 1].
*/
static trapezoid(value, a, b, c, d, optimize = false) {
return trapezoid(value, a, b, c, d, optimize);
}
/** @type {string} - Internal name of the fuzzy set */
#name = '';
/** @type {number} - Internal left foot coordinate */
#a = 0;
/** @type {number} - Internal left shoulder coordinate */
#b = 0;
/** @type {number} - Internal right shoulder coordinate */
#c = 0;
/** @type {number} - Internal right foot coordinate */
#d = 0;
/** @type {boolean} - Internal flag to enable calculation optimization */
#optimize = false;
get name() {
return this.#name;
}
set name(value) {
validateType(value, 'string', 'FuzzySet.name');
this.#name = value;
}
get a() {
return this.#a;
}
set a(value) {
validateType(value, 'number', 'FuzzySet.a');
this.#a = value;
}
get b() {
return this.#b;
}
set b(value) {
validateType(value, 'number', 'FuzzySet.b');
this.#b = value;
}
get c() {
return this.#c;
}
set c(value) {
validateType(value, 'number', 'FuzzySet.c');
this.#c = value;
}
get d() {
return this.#d;
}
set d(value) {
validateType(value, 'number', 'FuzzySet.d');
this.#d = value;
}
get optimize() {
return this.#optimize;
}
set optimize(value) {
validateType(value, 'boolean', 'FuzzySet.optimize');
this.#optimize = value;
}
/**
* @param {string} name - Name of the set (e.g., "Hot").
* @param {number} a - Left foot.
* @param {number} b - Left shoulder.
* @param {number} c - Right shoulder.
* @param {number} d - Right feet.
* @param {boolean} [optimize=false] - Enables performance optimization.
*/
constructor(name, a, b, c, d, optimize = false) {
this.name = name;
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.optimize = optimize;
}
/**
* Calculates the membership degree.
* @param {number} x - Crisp input.
* @returns {number}
*/
calculate(x) {
validateType(x, 'number', 'calculate.x');
return FuzzySet.trapezoid(x, this.#a, this.#b, this.#c, this.#d, this.#optimize);
}
}
/**
* The Inference Engine handles linguistic variables and defuzzification.
*/
class MamdaniInferenceSystem {
/** @type {Map<string, FuzzySet[]>} - Storage for linguistic variables */
#variables = new Map();
/**
* Registers a linguistic variable and its sets.
* @param {string} name - Variable name (e.g., "temperature").
* @param {FuzzySet[]} sets - Array of fuzzy sets.
*/
addVariable(name, sets) {
validateType(name, 'string', 'addVariable.name');
validateFuzzySets(sets);
this.#variables.set(name, sets);
}
/**
* Removes a linguistic variable.
* @param {string} name - Variable name (e.g., "temperature").
* @returns {boolean} True if the element was successfully removed.
*/
removeVariable(name) {
validateType(name, 'string', 'removeVariable.name');
return this.#variables.delete(name);
}
/**
* Gets a linguistic variable and its sets.
* @param {string} name - Variable name (e.g., "temperature").
* @returns {FuzzySet[]}
*/
getVariable(name) {
validateType(name, 'string', 'getVariable.name');
/** @type {FuzzySet[] | undefined} - Attempted fetch from map */
const result = this.#variables.get(name);
if (!result) throw new Error(`Linguistic variable '${name}' not found in the inference system.`);
return [...result];
}
/**
* Checks if a linguistic variable exists.
* @param {string} name - Variable name (e.g., "temperature").
* @returns {boolean}
*/
hasVariable(name) {
validateType(name, 'string', 'hasVariable.name');
return this.#variables.has(name);
}
/**
* Fuzzifies a crisp input into a map of memberships.
* @param {string} varName
* @param {number} value
* @returns {Object.<string, number>}
*/
fuzzify(varName, value) {
validateType(varName, 'string', 'fuzzify.varName');
validateType(value, 'number', 'fuzzify.value');
/** @type {FuzzySet[]} - The sets associated with the variable */
const sets = this.#variables.get(varName) || [];
/** @type {Object.<string, number>} - The fuzzified results dictionary */
const results = {};
sets.forEach(set => {
results[set.name] = set.calculate(value);
});
return results;
}
}
exports.FuzzySet = FuzzySet;
exports.MamdaniInferenceSystem = MamdaniInferenceSystem;
exports.defuzzifyCentroid = defuzzifyCentroid;
exports.trapezoid = trapezoid;