From 4f4b0932b6525c40b346c8d3c471d5dbd35dba06 Mon Sep 17 00:00:00 2001 From: Brian Joseph Petro Date: Tue, 7 Jan 2025 21:52:51 -0500 Subject: [PATCH] Add template source adapters for JSON and Markdown files - Introduced a base `TemplateSourceAdapter` class for handling template file operations, including methods for reading and writing files. - Implemented `FileJsonTemplateSourceAdapter` for JSON files, providing functionality to import and write JSON data. - Created `MarkdownTemplateAdapter` to read and parse Markdown files, extracting headings and content into a structured format. - Added a specialized `ToolJsonTemplateAdapter` for handling `.tool.json` files, including methods for importing and exporting tool definitions. - Removed the old `_adapter.js` file to streamline the adapter structure. - Updated the `lookup` template to reflect new data structures and added corresponding Markdown and JSON representations. --- smart-templates-2/adapters/_adapter.js | 57 ++++++++ .../adapters/{source => }/json.js | 27 ++-- smart-templates-2/adapters/markdown.js | 50 +++++++ smart-templates-2/adapters/source/_adapter.js | 32 ----- smart-templates-2/adapters/tool_json.js | 124 ++++++++++++++++++ smart-templates-2/templates/lookup.json | 41 +----- smart-templates-2/templates/lookup.md | 10 ++ smart-templates-2/templates/lookup.tool.json | 41 ++++++ 8 files changed, 307 insertions(+), 75 deletions(-) create mode 100644 smart-templates-2/adapters/_adapter.js rename smart-templates-2/adapters/{source => }/json.js (50%) create mode 100644 smart-templates-2/adapters/markdown.js delete mode 100644 smart-templates-2/adapters/source/_adapter.js create mode 100644 smart-templates-2/adapters/tool_json.js create mode 100644 smart-templates-2/templates/lookup.md create mode 100644 smart-templates-2/templates/lookup.tool.json diff --git a/smart-templates-2/adapters/_adapter.js b/smart-templates-2/adapters/_adapter.js new file mode 100644 index 00000000..47fcbba9 --- /dev/null +++ b/smart-templates-2/adapters/_adapter.js @@ -0,0 +1,57 @@ +/** + * @class TemplateSourceAdapter + * @description Base adapter class for handling template source file operations. + * Each subclass should override `import()` to parse or transform the file into `this.item.data`. + */ +export class TemplateSourceAdapter { + /** + * @constructor + * @param {Object} item - The template item this adapter is associated with. + */ + constructor(item) { + this.item = item; + } + + /** + * Helper to read the raw file content. Usually used internally by `import()`. + * @async + * @returns {Promise} The file contents. + */ + async read() { + return await this.fs.read(this.file_path); + } + + /** + * Helper to write raw data back to the file system. + * @async + * @param {string|Object} data - The data to write to the file + * @returns {Promise} Resolves when write completes + */ + async write(data) { + return await this.fs.write(this.file_path, data); + } + + /** + * The main method each adapter should override to parse or transform the file + * and store the result in `this.item.data`. + * @async + * @returns {Promise} The parsed or processed result + */ + async import() { + throw new Error('import() not implemented by subclass'); + } + + /** + * For convenience, references to the file path and FS from the item. + */ + get fs() { + return this.item.collection.fs; + } + get file_path() { + return this.item.path; // or logic to unify extension if needed + } +} + +export default { + item: TemplateSourceAdapter, +} \ No newline at end of file diff --git a/smart-templates-2/adapters/source/json.js b/smart-templates-2/adapters/json.js similarity index 50% rename from smart-templates-2/adapters/source/json.js rename to smart-templates-2/adapters/json.js index 467e88fd..b67e7927 100644 --- a/smart-templates-2/adapters/source/json.js +++ b/smart-templates-2/adapters/json.js @@ -13,21 +13,32 @@ export class FileJsonTemplateSourceAdapter extends TemplateSourceAdapter { /** * @async - * @returns {Promise} Parsed JSON data from the file - * @description Reads and parses JSON data from the template file + * @method import + * @description Reads and parses JSON data, merges result into `this.item.data`. + * @returns {Promise} The parsed JSON object. */ - async read() { - const data = await super.read(); - return JSON.parse(data); + async import() { + const dataRaw = await this.read(); + const parsed = JSON.parse(dataRaw); + + // Merge into item.data (up to you how you store it) + Object.assign(this.item.data, parsed); + + return parsed; } /** + * Write data object as JSON to the file * @async * @param {Object} data - The data to stringify and write - * @returns {Promise} Promise that resolves when write is complete - * @description Stringifies and writes JSON data to the template file + * @returns {Promise} */ async write(data) { - return await super.write(JSON.stringify(data, null, 2)); + const jsonStr = JSON.stringify(data, null, 2); + return super.write(jsonStr); } } + +export default { + item: FileJsonTemplateSourceAdapter +}; diff --git a/smart-templates-2/adapters/markdown.js b/smart-templates-2/adapters/markdown.js new file mode 100644 index 00000000..1c3e2616 --- /dev/null +++ b/smart-templates-2/adapters/markdown.js @@ -0,0 +1,50 @@ +import { TemplateSourceAdapter } from './_adapter.js'; +import { parse_blocks } from 'smart-blocks/parsers/markdown.js'; + +/** + * @class MarkdownTemplateAdapter + * @extends TemplateSourceAdapter + * @description + * Adapter that reads a Markdown file, uses `parse_blocks` to find headings and content, + * and populates `this.item.data` accordingly. + */ +export class MarkdownTemplateAdapter extends TemplateSourceAdapter { + /** @type {string} The file extension this adapter targets. */ + static extension = 'md'; + extension = 'md'; + + /** + * @async + * @method import + * @description + * 1) Read the markdown content + * 2) Parse via `parse_blocks` + * 3) Store heading => content mapping in `this.item.data.headings` + * @returns {Promise} The headings data object + */ + async import() { + const markdownContent = await this.read(); + const blocksMap = parse_blocks(markdownContent); + + const headingsData = {}; + for (const [blockKey, [start, end]] of Object.entries(blocksMap)) { + const lines = markdownContent.split('\n').slice(start - 1, end); + const blockText = lines.join('\n').trim(); + if (!blockText) continue; + + // Clean the heading name from blockKey + let headingName = blockKey.replace(/^#+/, '').trim() || '(root)'; + headingsData[headingName] = blockText; + } + + // Store in item.data or merge if needed + if (!this.item.data.headings) this.item.data.headings = {}; + Object.assign(this.item.data.headings, headingsData); + + return headingsData; + } +} + +export default { + item: MarkdownTemplateAdapter +}; diff --git a/smart-templates-2/adapters/source/_adapter.js b/smart-templates-2/adapters/source/_adapter.js deleted file mode 100644 index 830466f4..00000000 --- a/smart-templates-2/adapters/source/_adapter.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @class TemplateSourceAdapter - * @description Base adapter class for handling template source file operations - */ -export class TemplateSourceAdapter { - /** - * @constructor - * @param {Object} item - The template item to be adapted - */ - constructor(item) { - this.item = item; - } - - /** - * @async - * @returns {Promise} The contents of the file - * @description Reads the contents of the template file - */ - async read() { - return await this.fs.read(this.file_path); - } - - /** - * @async - * @param {string|Object} data - The data to write to the file - * @returns {Promise} Promise that resolves when write is complete - * @description Writes data to the template file - */ - async write(data) { - return await this.fs.write(this.file_path, data); - } -} \ No newline at end of file diff --git a/smart-templates-2/adapters/tool_json.js b/smart-templates-2/adapters/tool_json.js new file mode 100644 index 00000000..ec9070ec --- /dev/null +++ b/smart-templates-2/adapters/tool_json.js @@ -0,0 +1,124 @@ +// File: smart-templates-2/adapters/tool_json.js + +import { TemplateSourceAdapter } from "./_adapter.js"; + +/** + * @class ToolJsonTemplateAdapter + * @extends TemplateSourceAdapter + * @description Adapter for specialized `.tool.json` files containing tool definitions + * (OpenAI function format) plus optional fields like var_prompts, prompt, opts, output, etc. + */ +export class ToolJsonTemplateAdapter extends TemplateSourceAdapter { + static extension = 'tool.json'; + extension = 'tool.json'; + + /** + * @async + * @method import + * @description Reads the `.tool.json` file and converts it to the standard template format + * @returns {Promise} The parsed and converted template data + */ + async import() { + const raw = await this.read(); + const parsed = JSON.parse(raw); + + // Initialize data structure if needed + if (!this.item.data.var_prompts) this.item.data.var_prompts = {}; + if (!this.item.data.opts) this.item.data.opts = {}; + + // Handle tool function parameters conversion + if (parsed.tool?.function?.parameters?.properties) { + const props = parsed.tool.function.parameters.properties; + + // Convert nested object parameters to multiple_output format + for (const [key, value] of Object.entries(props)) { + if (value.type === 'object' && value.properties) { + // Store the original description in var_prompts + if (value.description) { + this.item.data.var_prompts[key] = value.description; + } + + // Set up multiple_output options + if (!this.item.data.opts.multiple_output) { + this.item.data.opts.multiple_output = []; + } + if (!this.item.data.opts.multiple_output.includes(key)) { + this.item.data.opts.multiple_output.push(key); + } + + // Count the number of required outputs + const numOutputs = Object.keys(value.properties).length; + this.item.data.opts.multiple_output_count = numOutputs; + + } + } + } + + // Copy over the function description as the prompt + if (parsed.tool?.function?.description) { + this.item.data.prompt = parsed.tool.function.description; + } + + // Copy the name + if (parsed.name) { + this.item.data.name = parsed.name; + } + + return this.item.data; + } + + /** + * @async + * @method export + * @description Converts the standard template format back to tool.json format + * @returns {Promise} The stringified tool.json content + */ + async export() { + const data = this.item.data; + const toolJson = { + name: data.name, + tool: { + type: "function", + function: { + name: data.name, + description: data.prompt, + parameters: { + type: "object", + properties: {}, + required: [] + } + } + }, + output: data.output || {}, + opts: { ...data.opts } + }; + + // Convert multiple_output format back to nested object parameters + if (data.opts?.multiple_output) { + for (const key of data.opts.multiple_output) { + const numOutputs = data.opts.multiple_output_count || 3; + const properties = {}; + + // Create numbered properties + for (let i = 1; i <= numOutputs; i++) { + properties[i] = { type: "string" }; + } + + toolJson.tool.function.parameters.properties[key] = { + type: "object", + description: data.var_prompts[key], + properties: properties, + required: Object.keys(properties) + }; + + toolJson.tool.function.parameters.required.push(key); + } + } + + return JSON.stringify(toolJson, null, 2); + } +} + +export default { + item: ToolJsonTemplateAdapter +}; diff --git a/smart-templates-2/templates/lookup.json b/smart-templates-2/templates/lookup.json index c8a7b177..9cf2bc37 100644 --- a/smart-templates-2/templates/lookup.json +++ b/smart-templates-2/templates/lookup.json @@ -1,40 +1,11 @@ { "opts": { - "lookup_hypotheticals_map": { - "1": "hypotheticals.1", - "2": "hypotheticals.2", - "3": "hypotheticals.3" - } + "multiple_output": ["hypothetical"], + "multiple_output_count": 3 }, "name": "lookup", - "tool": { - "type": "function", - "function": { - "name": "lookup", - "description": "Performs a semantic search of the user's data. Use this function to respond to queries like 'Based on my notes...' or any other request that requires surfacing relevant content.", - "parameters": { - "type": "object", - "properties": { - "hypotheticals": { - "type": "object", - "description": "Short hypothetical notes predicted to be semantically similar to the notes necessary to fulfill the user's request. Provide at least three hypotheticals per request. The hypothetical notes may contain paragraphs, lists, or checklists in markdown format. Each hypothetical note should begin with breadcrumbs indicating the anticipated folder(s), file name, and relevant headings separated by ' > ' (no slashes). Example: PARENT FOLDER NAME > CHILD FOLDER NAME > FILE NAME > HEADING 1 > HEADING 2 > HEADING 3: HYPOTHETICAL NOTE CONTENTS.", - "properties": { - "1": { - "type": "string" - }, - "2": { - "type": "string" - }, - "3": { - "type": "string" - } - }, - "required": ["1", "2", "3"] - } - }, - "required": ["hypotheticals"] - } - } - }, - "output": {} + "prompt": "Performs a semantic search of the user's data. Use this function to respond to queries like 'Based on my notes...' or any other request that requires surfacing relevant content.", + "var_prompts": { + "hypothetical": "Short hypothetical notes predicted to be semantically similar to the notes necessary to fulfill the user's request. Provide at least three hypotheticals per request. The hypothetical notes may contain paragraphs, lists, or checklists in markdown format. Each hypothetical note should begin with breadcrumbs indicating the anticipated folder(s), file name, and relevant headings separated by ' > ' (no slashes). Example: PARENT FOLDER NAME > CHILD FOLDER NAME > FILE NAME > HEADING 1 > HEADING 2 > HEADING 3: HYPOTHETICAL NOTE CONTENTS." + } } \ No newline at end of file diff --git a/smart-templates-2/templates/lookup.md b/smart-templates-2/templates/lookup.md new file mode 100644 index 00000000..8a491bca --- /dev/null +++ b/smart-templates-2/templates/lookup.md @@ -0,0 +1,10 @@ +--- +name: lookup +multiple_output: + - hypothetical +multiple_output_count: 3 +--- +Performs a semantic search of the user's data. Use this function to respond to queries like 'Based on my notes...' or any other request that requires surfacing relevant content. + +# hypothetical +Short hypothetical notes predicted to be semantically similar to the notes necessary to fulfill the user's request. Provide at least three hypotheticals per request. The hypothetical notes may contain paragraphs, lists, or checklists in markdown format. Each hypothetical note should begin with breadcrumbs indicating the anticipated folder(s), file name, and relevant headings separated by ' > ' (no slashes). Example: PARENT FOLDER NAME > CHILD FOLDER NAME > FILE NAME > HEADING 1 > HEADING 2 > HEADING 3: HYPOTHETICAL NOTE CONTENTS. \ No newline at end of file diff --git a/smart-templates-2/templates/lookup.tool.json b/smart-templates-2/templates/lookup.tool.json new file mode 100644 index 00000000..22a59b40 --- /dev/null +++ b/smart-templates-2/templates/lookup.tool.json @@ -0,0 +1,41 @@ +{ + "opts": { + "multiple_output": { + "hypotheticals": [ + "1", + "2", + "3" + ] + } + }, + "name": "lookup", + "tool": { + "type": "function", + "function": { + "name": "lookup", + "description": "Performs a semantic search of the user's data. Use this function to respond to queries like 'Based on my notes...' or any other request that requires surfacing relevant content.", + "parameters": { + "type": "object", + "properties": { + "hypotheticals": { + "type": "object", + "description": "Short hypothetical notes predicted to be semantically similar to the notes necessary to fulfill the user's request. Provide at least three hypotheticals per request. The hypothetical notes may contain paragraphs, lists, or checklists in markdown format. Each hypothetical note should begin with breadcrumbs indicating the anticipated folder(s), file name, and relevant headings separated by ' > ' (no slashes). Example: PARENT FOLDER NAME > CHILD FOLDER NAME > FILE NAME > HEADING 1 > HEADING 2 > HEADING 3: HYPOTHETICAL NOTE CONTENTS.", + "properties": { + "1": { + "type": "string" + }, + "2": { + "type": "string" + }, + "3": { + "type": "string" + } + }, + "required": ["1", "2", "3"] + } + }, + "required": ["hypotheticals"] + } + } + } +} \ No newline at end of file