From fabf930527b802235afb43cd005d31213efbcde9 Mon Sep 17 00:00:00 2001 From: Maher Khalil Date: Mon, 17 Aug 2026 12:29:12 -0400 Subject: [PATCH] feat: adding in javascript and updated overview tab --- .../reactor/utils/GetEngineUsageReactor.java | 717 ++++++++++++++++-- 1 file changed, 662 insertions(+), 55 deletions(-) diff --git a/src/prerna/reactor/utils/GetEngineUsageReactor.java b/src/prerna/reactor/utils/GetEngineUsageReactor.java index abaa0c9fca5..d2f0f735688 100644 --- a/src/prerna/reactor/utils/GetEngineUsageReactor.java +++ b/src/prerna/reactor/utils/GetEngineUsageReactor.java @@ -52,11 +52,13 @@ public class GetEngineUsageReactor extends AbstractReactor { private static final String INTRODUCTION = "introduction"; private static final String PYTHON = "python"; private static final String JAVA = "java"; + private static final String JAVASCRIPT = "javascript"; private static final String PIXEL = "pixel"; private static final String LANGCHAIN = "LANGCHAIN"; private static final String OPENAI = "OPENAI"; private static final String ANTHROPIC = "ANTHROPIC"; private static final String OLLAMA = "OLLAMA"; + private static final String ENGINE_ID_PLACEHOLDER = ""; private static final String API_ENDPOINT_PLACEHOLDER = ""; private static final String OPENAI_ENDPOINT_PLACEHOLDER = ""; @@ -67,6 +69,7 @@ public class GetEngineUsageReactor extends AbstractReactor { private static final String PIXEL_LABEL = "How to use in REST via Pixel"; private static final String PYTHON_LABEL = "How to use in Python"; private static final String JAVA_LABEL = "How to use in Java"; + private static final String JAVASCRIPT_LABEL = "How to use in JavaScript/TypeScript with the @semoss/sdk"; private static final String LANGCHAIN_LABEL = "How to use with LangChain API"; private static final String OPENAI_LABEL = "How to use externally with OpenAI API"; private static final String ANTHROPIC_LABEL = "How to use externally with Anthropic API"; @@ -77,21 +80,39 @@ public class GetEngineUsageReactor extends AbstractReactor { // Shared platform primer appended to every engine's Introduction section so the // orientation text lives in one place instead of being repeated per channel. private static final String PLATFORM_INTRODUCTION = """ - **How to reach this engine** -
-
+ ## How to reach this engine + + Every engine is addressed by its `engineId` and does the same work no matter which channel calls it. Pick the tab that matches where your code runs: + + - **Pixel** - the platform's server-side scripting language, submitted to the `runPixel` REST endpoint. Every other channel is a wrapper around it, so this tab is the reference for what each operation actually does. + - **JavaScript/TypeScript** - `runPixel` from the `@semoss/sdk` package, for app front ends. + - **Python** - engine classes from the `ai_server` package, which build the Pixel and hand back a plain dict. + - **Java** - `Utility` helpers, for reactors and other server-side code. + + Some engine types add further tabs, for example LangChain adapters or an OpenAI-compatible endpoint for a Model engine. + + ## Reading a response + + `runPixel` returns a JSON envelope rather than a bare result: + + - `pixelReturn[i].output` holds the result of the i-th expression you submitted. This is the payload each tab documents. + - `pixelReturn[i].operationType` of `["ERROR"]` means that expression failed and its `output` is the error message. + - Entries with `isMeta` set to `true` are bookkeeping; skip them. + - `insightID` identifies the session the Pixel ran under. Reuse it to keep server-side state across calls. + + The SDKs unwrap most of that for you. The Python engine classes return `pixelReturn[0].output` directly, and `runPixel` from `@semoss/sdk` returns the parsed envelope alongside an `errors` array it pre-collects from every `ERROR` entry, so one check covers both transport and application failures. + + ## Escaping text inside Pixel + + Wrapping a string argument in `...` URL-encodes that text before it is parsed, so inner quotes and special characters do not need escaping: - **REST via Pixel** - submit Pixel (the platform's server-side scripting language) to the `runPixel` REST endpoint. It returns a JSON envelope where each executed expression's result is at `pixelReturn[i].output`. -
- **Python** - classes that generate the Pixel and unwraps the JSON response into a plain Python dict. -
-
+ ``` + command = "She said "hi" to O'Brien" + ``` - A note on `` in Pixel: wrapping a string argument in `...` (for example `command = "She said "hi" to O'Brien"`) URL-encodes that text before parsing, so inner quotes and special characters do not need escaping. It is entirely optional. If you are programmatically generating Pixel, it is usually simpler and less error-prone to skip `` and instead escape inner double quotes with `\\"` and reserve `` for cases where escaping by hand would be tedious. -
-
+ It is entirely optional. When you generate Pixel programmatically it is usually simpler to escape inner double quotes with `\\"` and save `` for text that would be tedious to escape by hand. - Each section lists the common operations and shows the return structure. + The remaining tabs list the common operations for this engine, each with an example call and the shape it returns. """; private static class EngineSelection { @@ -163,17 +184,21 @@ private List> getModelUsage(String engineId) { List> usage = new ArrayList<>(); addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, """ - A **Model** engine wraps one specific LLM (chat + embeddings) behind a single, consistent interface, so provider differences (OpenAI, Anthropic, Ollama, and others) are abstracted away and you interact with all of them the same way. -
-
+ A **Model** engine wraps one specific LLM behind a single, consistent interface, so provider differences (OpenAI, Anthropic, Ollama, and others) are abstracted away and every model is called the same way. - Model/chat calls are *stateful* by default: a **room** object is created with each call which holds the conversation history, so follow-up calls can build on earlier turns. Pass a `roomId` (Pixel) / `room_id` (Python) to keep a thread going, or omit it and the current insight id will be used as the room identifier. -
-
+ ## What you can do - Responses use schemaVersion 2: `response` is the convenience concatenated text, while `parts` is the full ordered content (text, tool_call, media, ...) that can mix modalities in a single turn. `messageType` (`CHAT`, `TOOL`, `IMAGE`) summarizes the turn. -
-
+ - **Generate text** - send a prompt and get the model's answer back. + - **Hold a conversation** - chat calls are stateful by default; a *room* holds the history so follow-up turns build on earlier ones. + - **Send images** - pass a public URL or an uploaded file to a vision-capable model. + - **Constrain the output** - pass a JSON schema and get schema-valid JSON back. + - **Create embeddings** - turn text into vectors, usually to store in a Vector engine. + + ## Key concepts + + - **Rooms.** Pass a `roomId` (Pixel, JavaScript) or `room_id` (Python) to keep a thread going. Omit it and the current insight id is used as the room identifier. + - **schemaVersion 2 responses.** `response` is the concatenated text kept for convenience; `parts` is the full ordered content and can mix modalities in one turn (text, thinking, tool_call, tool_result, media). `messageType` (`CHAT`, `TOOL`, `IMAGE`) summarizes the turn. + - **Token accounting.** Every response carries `numberOfTokensInPrompt` and `numberOfTokensInResponse`, plus cache and thinking counts when the provider reports them. """ + PLATFORM_INTRODUCTION, @@ -319,6 +344,198 @@ Setup Room ID (Optional - this will default to the current insight id if not pro """, engineId); + addUsage(usage, JAVASCRIPT, JAVASCRIPT_LABEL, + """ + Getting Started + + `runPixel` from `@semoss/sdk` submits Pixel from an app front end and returns the parsed envelope, so the model answer is at `pixelReturn[0].output`. + + ```typescript + import { runPixel } from "@semoss/sdk"; + + const MODEL_ID = ""; + const prompt = "Sample Question"; + + const { errors, pixelReturn } = await runPixel( + `LLM(engine="${MODEL_ID}", command=["${prompt}"], paramValues=[{"temperature":0.3, "max_completion_tokens":2000}]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const response = pixelReturn[0].output.response; + ``` + + `errors` already contains any expression the server flagged with `operationType` `["ERROR"]`, so this one check is enough. `output` is the same schemaVersion 2 map documented in the Pixel section (`response`, `parts`, `messageType`, `messageId`, `roomId`, token counts). + + The variations below show only the Pixel string, the one that goes inside the `runPixel` template literal. The surrounding `runPixel(...)` call, the `errors` check, and the response parsing are the same as above. + + Insight Sessions + + `runPixel(pixel, insightId)` takes an optional second argument. Pass `"new"` to start a fresh insight and reuse the returned `insightId` on every later call in that session; omit it to run on the app's current insight. + + ```typescript + const { insightId, pixelReturn } = await runPixel( + `LLM(engine="${MODEL_ID}", command=["Sample Question"]);`, + "new", + ); + ``` + + Generation with ChatML + + Pass a `full_prompt` array inside `paramValues` to control the conversation history for that call. `command` is ignored when `full_prompt` is present, so pass `"ignore"` as a placeholder. + + ``` + LLM(engine="${MODEL_ID}", command=["ignore"], paramValues=[{ + "full_prompt": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Who won the world series in 2020?"}, + {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}, + {"role": "user", "content": "Where was it played?"} + ], + "max_completion_tokens": 2000, + "temperature": 0.3 + }]); + ``` + + Structured Outputs + + Pass `schema` in `paramValues` for models that support schema-constrained generation. `response` comes back as a JSON string, so parse it with `JSON.parse`. + + ``` + LLM(engine="${MODEL_ID}", command=["Sample Question"], paramValues=[{"schema": {"type":"object","properties":{"sample_property":{"type":"string"}},"required":["sample_property"]}}]); + ``` + + Generation with Image + + Pass either a public `url` or a server-accessible `image` filename as a top-level argument. Use `roomId` to thread several image turns into one conversation. + + ``` + LLM(engine="${MODEL_ID}", roomId="my_room_id", command=["What is in this image?"], url="https://your_image_url.com"); + ``` + + Attaching Uploaded Files + + Sending a file with a prompt is always two calls, in order: upload the bytes, then reference the returned `fileLocation`. The bytes are never re-sent. + + ```typescript + import { uploadInsight } from "@semoss/sdk"; + + // data is one { fileName, fileLocation } per input file, in input order + const { data: uploaded } = await uploadInsight(insightId, "", files); + ``` + + The helper posts to `{MODULE}/api/uploadFile/baseUpload?insightId=...&path=...&userSpace=false` with every file under the same form field name, `file`. If you call that endpoint directly, do not set `Content-Type` by hand - the browser has to set it so the multipart boundary is added - and send the request with the session cookie. + + Each uploaded file then travels as a `MEDIA` part alongside the user's `TEXT` part: + + ```typescript + const parts = [{ type: "TEXT", text: prompt, uiText: prompt }]; + for (const f of uploaded) { + parts.push({ + type: "MEDIA", + mediaInfo: { + fileName: f.fileName, + fileLocation: f.fileLocation, + mediaInputType: "FILE", + base64Data: "", + fileFormat: "", + mimeType: "", + }, + }); + } + ``` + + Threaded Chat with a Room + + A room is a durable conversation that owns the history `roomId` threads into. Create it on a new insight, bind the insight to it once, then pass `roomId` on every turn. + + ```typescript + import { runPixel } from "@semoss/sdk"; + + const { errors, pixelReturn, insightId } = await runPixel<[{ roomId: string }]>( + `CreatePlaygroundRoom();`, + "new", + ); + if (errors.length) throw new Error(errors[0]); + + const roomId = pixelReturn[0].output.roomId; + + // bind this insight to the room so later turns are recorded against it + await runPixel(`SetRoomForInsight(roomId="${roomId}");`, insightId); + + await runPixel( + `LLM(engine="${MODEL_ID}", roomId="${roomId}", command=["Hello"]);`, + insightId, + ); + ``` + + Read the history back with `GetPlaygroundMessages(roomId=["${roomId}"]);`. It returns one object per turn carrying `io` (`INPUT` or `OUTPUT`), `messageId`, `parentMessageId`, and the same `parts` array documented in the Pixel section. + + Streaming a Response + + `runPixel` buffers the whole answer and returns only the final value. To render tokens as they arrive, submit the job with `runPixelAsync`, poll `getPixelJobStreaming` with the returned `jobId` until a chunk carries `finish_reason`, then collect the final envelope with `getPixelAsyncResult`. + + ```typescript + import { + runPixelAsync, + getPixelJobStreaming, + getPixelAsyncResult, + } from "@semoss/sdk"; + + const { jobId } = await runPixelAsync( + `LLM(engine="${MODEL_ID}", command=["Sample Question"]);`, + ); + + let done = false; + while (!done) { + const { message, status } = await getPixelJobStreaming(jobId); + for (const chunk of message) { + if (chunk.stream_type === "content" && chunk.data.content) { + appendToUi(chunk.data.content); + } + if (chunk.data.finish_reason) done = true; + } + if (status === "Complete" || status === "Error") done = true; + if (!done) await new Promise((resolve) => setTimeout(resolve, 500)); + } + + const { errors, results } = await getPixelAsyncResult(jobId); + ``` + + Chunks arrive with a `stream_type` of `content`, `thinking`, or `tool`, matching the `TEXT`, `THINKING`, and `TOOL_CALL` parts of the final message. + + Embeddings + + ``` + Embeddings(engine="${MODEL_ID}", values=["Sample String 1", "Sample String 2"], paramValues=[{}]); + ``` + + `pixelReturn[0].output.response` holds one vector per input string. + + Listing Available Models + + Use `MyEngines` with `engineTypes=["MODEL"]` to show only the models the current user can reach, then feed the chosen `engine_id` into the calls above. + + ```typescript + const { errors, pixelReturn } = await runPixel( + `MyEngines(engineTypes=["MODEL"], limit=[50], offset=[0]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const models = pixelReturn[0].output as Array<{ + engine_id: string; + engine_name: string; + engine_display_name: string; + engine_subtype: string; // for example "CLAUDE", "OPEN_AI", "VERTEX" + engine_favorite: 0 | 1; + }>; + ``` + + `MyEngines` also accepts `filterWord=["claude"]` (substring match on the name), `onlyFavorites=[true]`, and `sort={"ENGINENAME": "ASC"}` (or `DATECREATED`, with `ASC`/`DESC`). Omit `limit` and `offset` to return everything. Read the `engine_*` fields; the `app_*` and `database_*` fields carry the same values but are legacy aliases. + """, + engineId); + addUsage(usage, PYTHON, PYTHON_LABEL, """ Method Parameters
@@ -719,13 +936,20 @@ private List> getStorageUsage(String engineId) { List> usage = new ArrayList<>(); addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, """ - A **Storage** engine is a file/object store (for example a cloud bucket or a mounted filesystem) exposed behind one consistent interface. You reference files by a `storagePath` inside the engine and move them to and from a local `filePath`. -
-
+ A **Storage** engine is a file or object store, for example a cloud bucket or a mounted filesystem, exposed behind one consistent interface. You address content by a `storagePath` inside the engine and move it to and from a local `filePath`. - Common operations are: list paths, list path details (name, size, mime type, modified time, metadata), upload/download single files, sync a folder in either direction, and delete. Uploads can attach arbitrary key-value `metadata`. -
-
+ ## What you can do + + - **List** - the names under a path, or full details for each entry (name, size, mime type, modified time, metadata). + - **Download** - pull a single file, or sync a whole folder down to local. + - **Upload** - push a single file, or sync a local folder up. Uploads can attach arbitrary key-value `metadata`. + - **Delete** - remove a path, optionally leaving the folder structure in place. + + ## Key concepts + + - **Paths, not handles.** Every operation takes a `storagePath` relative to the engine root. There is nothing to open or close. + - **Metadata travels with the file.** Key-value pairs attached at upload come back on the detail listing. + - **Transfers report a boolean.** Upload, download, sync, and delete return `true` on success and surface failures as an error rather than a partial result. """ + PLATFORM_INTRODUCTION, @@ -855,6 +1079,89 @@ Example Output (transfer + delete operations) """, engineId); + addUsage(usage, JAVASCRIPT, JAVASCRIPT_LABEL, + """ + Getting Started + + `runPixel` from `@semoss/sdk` submits Pixel from an app front end and returns the parsed envelope. Storage pixels put their payload directly at `pixelReturn[0].output` rather than wrapping it in a tabular `data` object. + + ```typescript + import { runPixel } from "@semoss/sdk"; + + const STORAGE_ID = ""; + + const { errors, pixelReturn } = await runPixel( + `Storage(storage="${STORAGE_ID}") | ListStoragePath(storagePath="/your/storage/path");`, + ); + + if (errors.length) throw new Error(errors[0]); + + const paths = pixelReturn[0].output as string[]; + ``` + + `errors` already contains any expression the server flagged with `operationType` `["ERROR"]`, so this one check is enough. `runPixel(pixel, insightId)` takes an optional second argument: pass `"new"` to start a fresh insight and reuse the returned `insightId` for the rest of the session, or omit it to run on the app's current insight. + + The variations below show only the Pixel string, the one that goes inside the `runPixel` template literal. The surrounding `runPixel(...)` call and the `errors` check are the same as above. + + List Path Details + + Returns one object per file/folder with `Path`, `Name`, `Size` (bytes), `MimeType`, `ModTime` (null for folders), `IsDir`, and `Metadata` (an empty map when none exists). + + ``` + Storage(storage="${STORAGE_ID}") | ListStoragePathDetails(storagePath="/your/storage/path"); + ``` + + Download from Storage + + ``` + Storage(storage="${STORAGE_ID}") | PullFromStorage(storagePath="/your/storage/path", filePath="/your/local/path"); + ``` + + Upload to Storage + + ``` + Storage(storage="${STORAGE_ID}") | PushToStorage(storagePath="/your/storage/path", filePath="/your/local/path", metadata=[{"metaKey":"metaValue"}]); + ``` + + Sync a Folder + + ``` + Storage(storage="${STORAGE_ID}") | SyncStorageToLocal(storagePath="/your/storage/path", filePath="/your/local/path"); + Storage(storage="${STORAGE_ID}") | SyncLocalToStorage(storagePath="/your/storage/path", filePath="/your/local/path", metadata=[{"metaKey":"metaValue"}]); + ``` + + Delete from Storage + + ``` + Storage(storage="${STORAGE_ID}") | DeleteFromStorage(storagePath="/your/storage/path", leaveFolderStructure=false); + ``` + + The transfer and delete operations return a boolean `true` in `output` on success and surface failures through `errors`. + + Listing Available Storage Engines + + Use `MyEngines` with `engineTypes=["STORAGE"]` to show only the storage engines the current user can reach, then feed the chosen `engine_id` into the calls above. + + ```typescript + const { errors, pixelReturn } = await runPixel( + `MyEngines(engineTypes=["STORAGE"], limit=[50], offset=[0]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const storageEngines = pixelReturn[0].output as Array<{ + engine_id: string; + engine_name: string; + engine_display_name: string; + engine_subtype: string; + engine_favorite: 0 | 1; + }>; + ``` + + `MyEngines` also accepts `filterWord=["reports"]` (substring match on the name), `onlyFavorites=[true]`, and `sort={"ENGINENAME": "ASC"}` (or `DATECREATED`, with `ASC`/`DESC`). Omit `limit` and `offset` to return everything. Read the `engine_*` fields; the `app_*` and `database_*` fields carry the same values but are legacy aliases. + """, + engineId); + addUsage(usage, PYTHON, PYTHON_LABEL, """ Getting Started @@ -994,17 +1301,19 @@ private List> getDatabaseUsage(String engineId) { List> usage = new ArrayList<>(); addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, """ - A **Database** engine is a connected data source (RDBMS, RDF, Graph, NoSQL, etc.) you query behind one consistent interface. For ease of documentation, we will assume a SQL database, but the query can be replaced with SPARQL, Gremlin, etc. query based on the database type. -
-
+ A **Database** engine is a connected data source (RDBMS, RDF, graph, NoSQL, and others) queried behind one consistent interface. The examples below assume SQL; substitute SPARQL, Gremlin, or whichever dialect the underlying database speaks. - Run `SELECT` queries to read rows and `INSERT`/`UPDATE`/`DELETE` to modify data. -
-
+ ## What you can do - You can also fetch the database structure (logical and physical table/column or vertex/property names plus data types), which is useful for building queries or grounding an LLM. Select-style calls take a row `limit`; modification calls take a `commit` flag. -
-
+ - **Read** - run a `SELECT` and get back rows plus per-column metadata. Select-style calls take a row `limit`. + - **Write** - run `INSERT`, `UPDATE`, or `DELETE`. Modification calls take a `commit` flag instead. + - **Inspect the schema** - fetch logical and physical table/column (or vertex/property) names with their data types. Useful both for building queries and for grounding an LLM. + + ## Key concepts + + - **Results are tabular and positional.** `data.values` is an array of row arrays aligned to `data.headers`, and `headerInfo` describes each column's type. + - **Logical and physical names can differ.** The catalog can alias a table or column, so the structure call returns both and lets you map between what users see and what the database stores. + - **The statement type picks the path.** `SqlQuery` detects whether it was handed a read or a write and routes it accordingly, so the same command covers both. """ + PLATFORM_INTRODUCTION, @@ -1093,6 +1402,113 @@ Get Database Structure (logical + physical metadata)
""", engineId); + addUsage(usage, JAVASCRIPT, JAVASCRIPT_LABEL, + """ + Getting Started + + `runPixel` from `@semoss/sdk` submits Pixel from an app front end and returns the parsed envelope, so the tabular result is at `pixelReturn[0].output.data`. + + ```typescript + import { runPixel } from "@semoss/sdk"; + + const DATABASE_ID = ""; + const sql = "SELECT ID, AGE, GENDER FROM DIABETES"; + + const { errors, pixelReturn } = await runPixel( + `SqlQuery(database="${DATABASE_ID}", query="${sql}", limit=500);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const { headers, values } = pixelReturn[0].output.data; + // headers: ["ID", "AGE", "GENDER"] + // values: [["1000", 59, "female"], ["1001", 68, "female"]] + ``` + + `errors` already contains any expression the server flagged with `operationType` `["ERROR"]`, so this one check is enough. `output` is the same map documented in the Pixel section (`data.values`, `data.headers`, `data.rawHeaders`, `headerInfo`, `sources`, `numCollected`). + + `runPixel(pixel, insightId)` takes an optional second argument: pass `"new"` to start a fresh insight and reuse the returned `insightId` for the rest of the session, or omit it to run on the app's current insight. + + The variations below show only the Pixel string, the one that goes inside the `runPixel` template literal. The surrounding `runPixel(...)` call and the `errors` check are the same as above. + + Insert/Update/Delete Queries + + Pass `commit=true` instead of `limit`. `SqlQuery` auto-detects the statement type, so the same pixel handles any modification. + + ``` + SqlQuery(database="${DATABASE_ID}", query="UPDATE table_name SET column1 = value1 WHERE condition", commit=true); + ``` + + Dynamic SQL + + `SqlQueryBase64` takes the same arguments as `SqlQuery`; only the query format changes to a base64-encoded UTF-8 SQL string. Use it when the SQL carries quotes, newlines, or non-ASCII characters that are awkward to embed in a Pixel literal. Escape SQL string literals yourself - base64 protects the Pixel transport, not against SQL injection. + + ```typescript + function encodeUtf8Base64(value: string): string { + const bytes = new TextEncoder().encode(value); + let binary = ""; + for (const byte of bytes) binary += String.fromCharCode(byte); + return btoa(binary); + } + + function sqlString(value: string): string { + return `'${value.replace(/'/g, "''")}'`; + } + + const mutationSql = `INSERT INTO CUSTOMER_NOTES (CUSTOMER_NAME, NOTES) VALUES (${sqlString(customerName)}, ${sqlString(notes)});`; + + const { errors } = await runPixel( + `SqlQueryBase64(database="${DATABASE_ID}", query="${encodeUtf8Base64(mutationSql)}", commit=true);`, + ); + + if (errors.length) throw new Error(errors[0]); + ``` + + When a write fails after it may have started, read the database state back before retrying - a blind retry can duplicate a partially completed mutation. + + Get Database Structure + + ``` + GetDatabaseTableStructure(database="${DATABASE_ID}"); + ``` + + Each row in `output.data.values` is a 6-tuple: logical table (RDBMS) or vertex (graph) name, logical column or property name, data type, whether the row is a graph vertex itself rather than a property on it, physical column/property name, and physical table/vertex name. + + Mapping Rows to Objects + + `data.values` is positional, so zip it against `data.headers` when you want records. + + ```typescript + const { headers, values } = pixelReturn[0].output.data; + const records = values.map((row) => + Object.fromEntries(headers.map((header, i) => [header, row[i]])), + ); + ``` + + Listing Available Databases + + Use `MyEngines` with `engineTypes=["DATABASE"]` to show only the databases the current user can reach, then feed the chosen `engine_id` into the calls above. + + ```typescript + const { errors, pixelReturn } = await runPixel( + `MyEngines(engineTypes=["DATABASE"], limit=[50], offset=[0]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const databases = pixelReturn[0].output as Array<{ + engine_id: string; + engine_name: string; + engine_display_name: string; + engine_subtype: string; // for example "H2_DB", "POSTGRES", "MYSQL", "TINKER" + engine_favorite: 0 | 1; + }>; + ``` + + `MyEngines` also accepts `filterWord=["sales"]` (substring match on the name), `onlyFavorites=[true]`, and `sort={"ENGINENAME": "ASC"}` (or `DATECREATED`, with `ASC`/`DESC`). Omit `limit` and `offset` to return everything. Read the `engine_*` fields; the `app_*` and `database_*` fields carry the same values but are legacy aliases. + """, + engineId); + addUsage(usage, PYTHON, PYTHON_LABEL, """ Getting Started @@ -1195,13 +1611,19 @@ private List> getVectorUsage(String engineId) { List> usage = new ArrayList<>(); addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, """ - A **Vector** engine indexes documents as embeddings so you can run semantic (nearest-neighbor) search - the backbone of retrieval-augmented generation (RAG). It handles chunking and embedding for you; you just add documents and query in natural language. -
-
+ A **Vector** engine indexes documents as embeddings so you can search them by meaning rather than by keyword. It is the retrieval half of retrieval-augmented generation (RAG), and it handles the chunking and embedding for you. - Common operations are: list indexed documents (unique `Source` values), add documents (files or pre-chunked VectorCSV files, from the insight/room space or an app/user space), run a nearest-neighbor search with optional `filters`/`metaFilters`, and remove documents. Pair a Vector engine with a Model engine to answer questions grounded in your own content. -
-
+ ## What you can do + + - **Add documents** - point at uploaded files and the engine chunks, embeds, and indexes them. Pre-chunked VectorCSV files skip the default splitter. + - **Search** - run a nearest-neighbor query in natural language, optionally scoped with `filters` (by source document) or `metaFilters` (by metadata attached at embed time). + - **List and remove** - see which documents are currently indexed, and drop the ones you no longer want. + + ## Key concepts + + - **You get chunks, not documents.** A search returns the closest chunks, each carrying its `Content` plus provenance (`Source`, `Divider`, `Part`) so an answer can cite where it came from. + - **Files can live in several places.** Documents in the current insight or room are addressed by name; pass `space` to reach a project/app folder or the user space instead. + - **Pair it with a Model engine.** The usual flow is retrieve, then hand the chunk text to an LLM as grounding context. The Pixel and JavaScript tabs show the full chain. """ + PLATFORM_INTRODUCTION, @@ -1352,6 +1774,135 @@ List current vector documents (unique `Source` values) """, engineId); + addUsage(usage, JAVASCRIPT, JAVASCRIPT_LABEL, + """ + Getting Started + + `runPixel` from `@semoss/sdk` submits Pixel from an app front end and returns the parsed envelope. Vector pixels put their payload directly at `pixelReturn[0].output` rather than wrapping it in a tabular `data` object the way `SqlQuery` does. + + ```typescript + import { runPixel } from "@semoss/sdk"; + + const VECTOR_ID = ""; + const query = "Sample Search Statement"; + + const { errors, pixelReturn } = await runPixel( + `VectorDatabaseQuery(engine="${VECTOR_ID}", command="${query}", limit=5, filters=[], metaFilters=[]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const hits = pixelReturn[0].output as Array<{ + Score: number; + Content: string; + Source: string; + Modality: string; + Divider: string; + Part: string; + Tokens: number; + }>; + ``` + + `errors` already contains any expression the server flagged with `operationType` `["ERROR"]`, so this one check is enough. `runPixel(pixel, insightId)` takes an optional second argument: pass `"new"` to start a fresh insight and reuse the returned `insightId` for the rest of the session, or omit it to run on the app's current insight. + + The variations below show only the Pixel string, the one that goes inside the `runPixel` template literal. The surrounding `runPixel(...)` call and the `errors` check are the same as above. + + Filtering + + `filters` matches the chunk's `Source`; `metaFilters` matches metadata keys attached at embed time. Both can be combined in the same call. + + ``` + VectorDatabaseQuery(engine="${VECTOR_ID}", command="${query}", limit=5, filters=[Filter(Source == ["doc1.pdf", "doc2.pdf"])], metaFilters=[Filter(department == "finance")]); + ``` + + List Indexed Documents + + Returns one entry per unique `Source` currently indexed, each with `fileName`, `fileSize`, and `lastModified`. + + ``` + ListDocumentsInVectorDatabase(engine="${VECTOR_ID}"); + ``` + + Add Documents + + `space` is only needed when the files live outside the current insight: pass the project/app UUID, or `"user"` for the user space. `CreateEmbeddingsFromVectorCSVFile` takes csv (or zip-of-csv) files already chunked with the headers `Source`, `Modality`, `Divider`, `Part`, `Tokens`, `Content`. + + ``` + CreateEmbeddingsFromDocuments(engine="${VECTOR_ID}", filePaths=["fileName1.pdf", "fileName2.pdf"]); + CreateEmbeddingsFromDocuments(engine="${VECTOR_ID}", filePaths=["docs/file1.pdf"], space="app_id"); + CreateEmbeddingsFromVectorCSVFile(engine="${VECTOR_ID}", filePaths=["chunks.csv"]); + ``` + + Embedding calls return a status string at `output` and a per-file breakdown at `pixelReturn[0].additionalOutput[0].output`, each entry carrying `fileName`, `status`, `insertedRecords`, `failedRecords`, and `totalRecords`. + + ```typescript + const perFile = pixelReturn[0].additionalOutput?.[0]?.output ?? []; + const failed = perFile.filter((f) => f.status !== "SUCCESS"); + ``` + + Remove Indexed Documents + + Pass `fileNames`, the source identifiers listed by `ListDocumentsInVectorDatabase`, not file paths. + + ``` + RemoveDocumentFromVectorDatabase(engine="${VECTOR_ID}", fileNames=["fileName1.pdf", "fileName2.pdf"]); + ``` + + Retrieval-augmented Generation + + The usual flow is two calls: retrieve the closest chunks, then hand them to a Model engine as grounding context. + + ```typescript + const MODEL_ID = "your_model_engine_id"; + + const { pixelReturn: retrieval } = await runPixel( + `VectorDatabaseQuery(engine="${VECTOR_ID}", command="${question}", limit=3);`, + ); + + const context = retrieval[0].output + .map((hit) => `* Document Name: ${hit.Source}, ${hit.Content}`) + .join("\\n"); + + const prompt = `Answer the question using only the context below. + + Question: ${question} + + Context: + ${context}`; + + const { pixelReturn: answer } = await runPixel( + `LLM(engine="${MODEL_ID}", command=["${prompt}"], paramValues=[{"temperature":0.1}]);`, + ); + + const text = answer[0].output.response; + ``` + + Ask the model to cite `Source` and `Part`/`Divider` so the answer points back at the retrieved chunks. + + Listing Available Vector Engines + + Use `MyEngines` with `engineTypes=["VECTOR"]` to show only the vector engines the current user can reach, then feed the chosen `engine_id` into the calls above. + + ```typescript + const { errors, pixelReturn } = await runPixel( + `MyEngines(engineTypes=["VECTOR"], limit=[50], offset=[0]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const vectorEngines = pixelReturn[0].output as Array<{ + engine_id: string; + engine_name: string; + engine_display_name: string; + engine_subtype: string; // for example "FAISS", "CHROMA", "WEAVIATE" + engine_favorite: 0 | 1; + }>; + ``` + + `MyEngines` also accepts `filterWord=["policy"]` (substring match on the name), `onlyFavorites=[true]`, and `sort={"ENGINENAME": "ASC"}` (or `DATECREATED`, with `ASC`/`DESC`). Omit `limit` and `offset` to return everything. Read the `engine_*` fields; the `app_*` and `database_*` fields carry the same values but are legacy aliases. + """, + engineId); + addUsage(usage, PYTHON, PYTHON_LABEL, """ Getting Started @@ -1544,12 +2095,16 @@ private List> getFunctionUsage(String engineId) { addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, """ A **Function** engine exposes a callable tool or API behind one consistent interface. You execute it with a map of named parameter values and it returns the tool's result. -
-
- Its parameter contract (each parameter's `name`, `type`, `description`, and whether it is `required`) is published alongside these snippets in the `parameters` field, and the example call below is pre-filled with this engine's actual parameters. Function engines are also what a Model engine calls when doing tool use. -
-
+ ## What you can do + + - **Execute the tool** with a parameter map. The example calls in the other tabs come pre-filled with this engine's actual parameters. + - **Read the contract** - this engine's parameters are published alongside these snippets in the `parameters` field, each with its `name`, `type`, `description`, and whether it is `required`. + + ## Key concepts + + - **This is what tool use calls.** When a Model engine performs tool use, the tools it invokes are Function engines. Calling one directly runs exactly what the model would have run, which makes it the fastest way to test a tool in isolation. + - **Parameters are named, not positional.** Pass a map keyed by parameter name; omit the optional ones. """ + PLATFORM_INTRODUCTION, @@ -1562,6 +2117,55 @@ Its parameter contract (each parameter's `name`, `type`, `description`, and whet ``` """.replace("", pixelMapArg), engineId, paramInfo); + addUsage(usage, JAVASCRIPT, JAVASCRIPT_LABEL, + """ + Getting Started + + `runPixel` from `@semoss/sdk` submits Pixel from an app front end and returns the parsed envelope, so the function result is at `pixelReturn[0].output`. The example call below is pre-filled with this engine's actual parameters. + + ```typescript + import { runPixel } from "@semoss/sdk"; + + const FUNCTION_ID = ""; + + const { errors, pixelReturn } = await runPixel( + `ExecuteFunctionEngine(engine="${FUNCTION_ID}");`, + ); + + if (errors.length) throw new Error(errors[0]); + + const output = pixelReturn[0].output; + ``` + + `errors` already contains any expression the server flagged with `operationType` `["ERROR"]`, so this one check is enough. `runPixel(pixel, insightId)` takes an optional second argument: pass `"new"` to start a fresh insight and reuse the returned `insightId` for the rest of the session, or omit it to run on the app's current insight. + + The engine's parameter contract is published alongside these snippets in the `parameters` field, where each entry carries `name`, `type`, `description`, and `required`. + + Listing Available Function Engines + + Use `MyEngines` with `engineTypes=["FUNCTION"]` to show only the function engines the current user can reach, then feed the chosen `engine_id` into the call above. + + ```typescript + const { errors, pixelReturn } = await runPixel( + `MyEngines(engineTypes=["FUNCTION"], limit=[50], offset=[0]);`, + ); + + if (errors.length) throw new Error(errors[0]); + + const functionEngines = pixelReturn[0].output as Array<{ + engine_id: string; + engine_name: string; + engine_display_name: string; + engine_subtype: string; + engine_favorite: 0 | 1; + }>; + ``` + + `MyEngines` also accepts `filterWord=["weather"]` (substring match on the name), `onlyFavorites=[true]`, and `sort={"ENGINENAME": "ASC"}` (or `DATECREATED`, with `ASC`/`DESC`). Omit `limit` and `offset` to return everything. Read the `engine_*` fields; the `app_*` and `database_*` fields carry the same values but are legacy aliases. + """ + .replace("", pixelMapArg), + engineId, paramInfo); + addUsage(usage, PYTHON, PYTHON_LABEL, """ Getting Started ```python @@ -1589,13 +2193,15 @@ Its parameter contract (each parameter's `name`, `type`, `description`, and whet private List> getPendingUsage() { List> usage = new ArrayList<>(); - addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, """ - Detailed usage examples for this engine type are not available yet. -
-
+ addUsage(usage, INTRODUCTION, INTRODUCTION_LABEL, + """ + Detailed usage examples for this engine type have not been written yet. The platform notes below still apply - every engine is reachable the same way, whichever type it is. - """ + PLATFORM_INTRODUCTION, null); + """ + + PLATFORM_INTRODUCTION, + null); addUsage(usage, PIXEL, PIXEL_LABEL, "Documentation pending", null); + addUsage(usage, JAVASCRIPT, JAVASCRIPT_LABEL, "Documentation pending", null); addUsage(usage, PYTHON, PYTHON_LABEL, "Documentation pending", null); addUsage(usage, JAVA, JAVA_LABEL, "Documentation pending", null); return usage; @@ -1707,15 +2313,16 @@ private Map fillMap(String type, String label, String code, List @Override public String getReactorDescription() { return """ - Builds tutorial-style usage snippets for a selected engine across Pixel, Python, Java, and optional integrations (for example LangChain or OpenAI-compatible usage when supported). + Builds tutorial-style usage snippets for a selected engine across Pixel, JavaScript/TypeScript, Python, Java, and optional integrations (for example LangChain or OpenAI-compatible usage when supported). - Platform context (useful for both human readers and machine consumers): on the Semoss AI Server platform every capability is an *engine* registered in a shared catalog - Model (LLMs), Vector (semantic search), Database (SQL/graph), Storage (files), and Function (callable tools) - addressed by a stable `engineId`. Pixel is the server-side scripting language executed through the `runPixel` REST endpoint; the `ai_server` Python SDK and the Java `Utility` helpers call the same engines. Model/chat calls are stateful via a *room* (insight) that holds conversation history. + Platform context (useful for both human readers and machine consumers): on the Semoss AI Server platform every capability is an *engine* registered in a shared catalog - Model (LLMs), Vector (semantic search), Database (SQL/graph), Storage (files), and Function (callable tools) - addressed by a stable `engineId`. Pixel is the server-side scripting language executed through the `runPixel` REST endpoint; the `@semoss/sdk` JavaScript package, the `ai_server` Python SDK, and the Java `Utility` helpers call the same engines. Model/chat calls are stateful via a *room* (insight) that holds conversation history. - Input resolution order is: `engine` first, then `type` if `engine` is not provided. - When only `type` is supplied, snippets are generated with `SAMPLE_ENGINE_ID` as the placeholder engine identifier. - The returned vector contains one object per usage channel with `type`, `label`, and `code`. - - The first channel is always `type = "introduction"`: a markdown primer explaining what this engine type does plus a shared "how to reach this engine" platform section. The remaining channels (`pixel`, `python`, `java`, ...) are per-integration. + - The first channel is always `type = "introduction"`: a markdown primer explaining what this engine type does plus a shared "how to reach this engine" platform section. The remaining channels (`pixel`, `javascript`, `python`, `java`, ...) are per-integration. - Each integration channel's `code` is markdown with per-operation example calls, each followed by an "Example Output" block showing the JSON/dict payload it returns. Pixel outputs show the full `runPixel` envelope (`pixelReturn[i].output`); Python SDK outputs show the unwrapped payload. + - The `javascript` channel covers the `@semoss/sdk` front-end package: `runPixel` (which returns the parsed envelope plus a pre-collected `errors` array), insight sessions, engine discovery via `MyEngines`, and, for Model engines, room-threaded chat, file uploads, and streaming through `runPixelAsync`/`getPixelJobStreaming`. - Model responses are schemaVersion 2: `response` is the convenience concatenated text while `parts` is the full ordered content (text, tool_call, media, etc.) that can mix modalities in one turn. - Function-engine responses also include `parameters`, where each item contains `name`, `type`, `description`, and `required`. """;