Skip to content

Commit

Permalink
- Added optional chaining (?.) in smart-chat-model, smart-embed-mod…
Browse files Browse the repository at this point in the history
…el, and smart-entities

- Updated Azure adapter API version to latest preview
- Improved error handling in `nearest` method of SmartEntities
- Added concurrency prevention in `process_embed_queue` method
- Fixed potential division by zero in token calculation
  • Loading branch information
Brian Joseph Petro committed Feb 4, 2025
1 parent b052047 commit 8219c54
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 77 deletions.
2 changes: 1 addition & 1 deletion smart-chat-model/adapters/_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ export class SmartChatModelRequestAdapter {
body.tool_choice = this.tool_choice;
}
// special handling for o1 models
if(this.model.startsWith('o1-')){
if(this.model?.startsWith('o1-')){
body.messages = body.messages.filter(m => m.role !== 'system'); // remove system messages (not supported by o1 models)
delete body.temperature; // not supported by o1 models
}
Expand Down
7 changes: 4 additions & 3 deletions smart-chat-model/adapters/azure.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class SmartChatModelAzureAdapter extends SmartChatModelOpenaiAdapter {
api_key_header: "api-key",
azure_resource_name: "",
azure_deployment_name: "",
azure_api_version: "2023-05-15",
azure_api_version: "2024-10-01-preview",
default_model: "gpt-35-turbo",
signup_url: "https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?tabs=command-line",
models_endpoint: "https://{azure_resource_name}.openai.azure.com/openai/deployments?api-version={azure_api_version}",
Expand Down Expand Up @@ -49,12 +49,13 @@ export class SmartChatModelAzureAdapter extends SmartChatModelOpenaiAdapter {
"[CHAT_ADAPTER].azure_api_version": {
name: 'Azure API Version',
type: "text",
description: "The API version for Azure OpenAI (e.g. '2023-05-15').",
default: "2023-05-15",
description: "The API version for Azure OpenAI (e.g. '2024-10-01-preview').",
default: "2024-10-01-preview",
},
};
}


/**
* Build the endpoint dynamically based on Azure settings.
* Example:
Expand Down
2 changes: 1 addition & 1 deletion smart-embed-model/adapters/transformers.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class SmartEmbedTransformersAdapter extends SmartEmbedAdapter {
});
} catch (err) {
console.error("error_processing_batch", err);
this.pipeline.dispose();
this.pipeline?.dispose();
this.pipeline = null;
await this.load();
return Promise.all(batch_inputs.map(async (item) => {
Expand Down
2 changes: 1 addition & 1 deletion smart-embed-model/connectors/transformers_iframe.js

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions smart-embed-model/connectors/transformers_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,6 @@ var SmartModel = class {
*/
process_settings_config(_settings_config, prefix = null) {
return Object.entries(_settings_config).reduce((acc, [key, val]) => {
if (val.conditional) {
if (!val.conditional(this)) return acc;
delete val.conditional;
}
const new_key = (prefix ? prefix + "." : "") + this.process_setting_key(key);
acc[new_key] = val;
return acc;
Expand Down Expand Up @@ -816,7 +812,7 @@ var SmartEmbedTransformersAdapter = class extends SmartEmbedAdapter {
});
} catch (err) {
console.error("error_processing_batch", err);
this.pipeline.dispose();
this.pipeline?.dispose();
this.pipeline = null;
await this.load();
return Promise.all(batch_inputs.map(async (item) => {
Expand Down
151 changes: 86 additions & 65 deletions smart-entities/adapters/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EntitiesVectorAdapter, EntityVectorAdapter } from "./_adapter.js";
import { cos_sim } from "../utils/cos_sim.js";
import { results_acc, furthest_acc } from "../utils/results_acc.js";
import { sort_by_score_ascending, sort_by_score_descending } from "../utils/sort_by_score.js";

/**
* @class DefaultEntitiesVectorAdapter
* @extends EntitiesVectorAdapter
Expand All @@ -20,8 +21,15 @@ import { sort_by_score_ascending, sort_by_score_descending } from "../utils/sort
export class DefaultEntitiesVectorAdapter extends EntitiesVectorAdapter {
constructor(collection) {
super(collection);
/**
* Prevents concurrency in process_embed_queue().
* @type {boolean}
* @private
*/
this._is_processing_embed_queue = false;
this._reset_embed_queue_stats();
}

/**
* Find the nearest entities to the given vector.
* @async
Expand Down Expand Up @@ -94,84 +102,96 @@ export class DefaultEntitiesVectorAdapter extends EntitiesVectorAdapter {

/**
* Process a queue of entities waiting to be embedded.
* Typically, this will call embed_batch in batches and update entities.
* Prevents multiple concurrent runs by using `_is_processing_embed_queue`.
* @async
* @returns {Promise<void>}
*/
async process_embed_queue() {
const embed_queue = this.collection.embed_queue;
// Reset stats as in SmartEntities
this._reset_embed_queue_stats();

if (this.collection.embed_model_key === "None") {
console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`);
return;
}

if (!this.collection.embed_model) {
console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`);
if (this._is_processing_embed_queue) {
console.log("process_embed_queue is already running, skipping concurrent call.");
return;
}
this._is_processing_embed_queue = true;

const datetime_start = new Date();
if (!embed_queue.length) {
return console.log(`Smart Connections: No items in ${this.collection.collection_key} embed queue`);
}
try {
const embed_queue = this.collection.embed_queue;
// Reset stats as in SmartEntities
this._reset_embed_queue_stats();

if (this.collection.embed_model_key === "None") {
console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`);
return;
}

console.log(`Time spent getting embed queue: ${(new Date()).getTime() - datetime_start.getTime()}ms`);
console.log(`Processing ${this.collection.collection_key} embed queue: ${embed_queue.length} items`);
if (!this.collection.embed_model) {
console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`);
return;
}

// Process in batches according to embed_model.batch_size
for (let i = 0; i < embed_queue.length; i += this.collection.embed_model.batch_size) {
if (this.is_queue_halted) {
this.is_queue_halted = false; // reset halt after break
break;
const datetime_start = new Date();
if (!embed_queue.length) {
console.log(`Smart Connections: No items in ${this.collection.collection_key} embed queue`);
return;
}
const batch = embed_queue.slice(i, i + this.collection.embed_model.batch_size);

// Prepare input
await Promise.all(batch.map(item => item.get_embed_input()));
console.log(`Time spent getting embed queue: ${(new Date()).getTime() - datetime_start.getTime()}ms`);
console.log(`Processing ${this.collection.collection_key} embed queue: ${embed_queue.length} items`);

// Embed batch
try {
const start_time = Date.now();
await this.embed_batch(batch);
this.total_time += Date.now() - start_time;
} catch (e) {
if (e && e.message && e.message.includes("API key not set")) {
this.halt_embed_queue_processing(`API key not set for ${this.collection.embed_model_key}\nPlease set the API key in the settings.`);
// Process in batches according to embed_model.batch_size
for (let i = 0; i < embed_queue.length; i += this.collection.embed_model.batch_size) {
if (this.is_queue_halted) {
this.is_queue_halted = false; // reset halt after break
break;
}
console.error(e);
console.error(`Error processing ${this.collection.collection_key} embed queue: ` + JSON.stringify((e || {}), null, 2));
}
const batch = embed_queue.slice(i, i + this.collection.embed_model.batch_size);

// Prepare input
await Promise.all(batch.map(item => item.get_embed_input()));

// Update hash and stats
batch.forEach(item => {
item.embed_hash = item.read_hash;
item._queue_save = true;
});
this.embedded_total += batch.length;
this.total_tokens += batch.reduce((acc, item) => acc + (item.tokens || 0), 0);
// Embed batch
try {
const start_time = Date.now();
await this.embed_batch(batch);
this.total_time += Date.now() - start_time;
} catch (e) {
if (e && e.message && e.message.includes("API key not set")) {
this.halt_embed_queue_processing(`API key not set for ${this.collection.embed_model_key}\nPlease set the API key in the settings.`);
}
console.error(e);
console.error(`Error processing ${this.collection.collection_key} embed queue: ` + JSON.stringify((e || {}), null, 2));
}

// Show progress notice every ~100 items
this._show_embed_progress_notice(embed_queue.length);
// Update hash and stats
batch.forEach(item => {
item.embed_hash = item.read_hash;
item._queue_save = true;
});
this.embedded_total += batch.length;
this.total_tokens += batch.reduce((acc, item) => acc + (item.tokens || 0), 0);

// Process save queue every 1000 items
if (this.embedded_total - this.last_save_total > 1000) {
this.last_save_total = this.embedded_total;
await this.collection.process_save_queue();
if(this.collection.block_collection) {
console.log(`Saving ${this.collection.block_collection.collection_key} block collection`);
await this.collection.block_collection.process_save_queue();
// Show progress notice every ~100 items
this._show_embed_progress_notice(embed_queue.length);

// Process save queue every 1000 items
if (this.embedded_total - this.last_save_total > 1000) {
this.last_save_total = this.embedded_total;
await this.collection.process_save_queue();
if(this.collection.block_collection) {
console.log(`Saving ${this.collection.block_collection.collection_key} block collection`);
await this.collection.block_collection.process_save_queue();
}
}
}
}

// Show completion notice
this._show_embed_completion_notice(embed_queue.length);
await this.collection.process_save_queue();
if(this.collection.block_collection) {
await this.collection.block_collection.process_save_queue();
// Show completion notice
this._show_embed_completion_notice(embed_queue.length);
await this.collection.process_save_queue();
if(this.collection.block_collection) {
await this.collection.block_collection.process_save_queue();
}
} finally {
// Always clear the concurrency flag, even on errors or halts
this._is_processing_embed_queue = false;
}
}

Expand All @@ -190,9 +210,9 @@ export class DefaultEntitiesVectorAdapter extends EntitiesVectorAdapter {
model_name: this.collection.embed_model_key
});
}

/**
* Displays the embedding completion notice.
* @private
* @returns {void}
*/
Expand All @@ -204,9 +224,9 @@ export class DefaultEntitiesVectorAdapter extends EntitiesVectorAdapter {
model_name: this.collection.embed_model_key
});
}

/**
* Halts the embed queue processing.
* @param {string|null} msg - Optional message.
*/
halt_embed_queue_processing(msg=null) {
Expand All @@ -220,9 +240,9 @@ export class DefaultEntitiesVectorAdapter extends EntitiesVectorAdapter {
model_name: this.collection.embed_model_key
});
}

/**
* Resumes the embed queue processing after a delay.
* @param {number} [delay=0] - The delay in milliseconds before resuming.
* @returns {void}
*/
Expand All @@ -234,15 +254,17 @@ export class DefaultEntitiesVectorAdapter extends EntitiesVectorAdapter {
this.process_embed_queue();
}, delay);
}

/**
* Calculates the number of tokens processed per second.
* @private
* @returns {number} Tokens per second.
*/
_calculate_embed_tokens_per_second() {
const elapsed_time = this.total_time / 1000;
return Math.round(this.total_tokens / elapsed_time);
return Math.round(this.total_tokens / (elapsed_time || 1));
}

/**
* Resets the statistics related to embed queue processing.
* @private
Expand Down Expand Up @@ -317,10 +339,9 @@ export class DefaultEntityVectorAdapter extends EntityVectorAdapter {
}
this.item.data.embeddings[this.item.embed_model_key].vec = vec;
}

}

export default {
collection: DefaultEntitiesVectorAdapter,
item: DefaultEntityVectorAdapter
};
};
6 changes: 5 additions & 1 deletion smart-entities/smart_entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,14 @@ export class SmartEntities extends Collection {
* @returns {Promise<Array<{item:Object, score:number}>>} An array of result objects with score and item.
*/
async nearest(vec, filter = {}) {
if (!vec) return console.warn("nearest: no vec");
if (!vec) {
console.warn("nearest: no vec");
return [];
}
return await this.entities_vector_adapter.nearest(vec, filter);
}


/**
* Finds the furthest entities from a vector using the default adapter.
* @async
Expand Down

0 comments on commit 8219c54

Please sign in to comment.