Skip to content

Commit

Permalink
Add template source adapters for JSON and Markdown files
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
Brian Joseph Petro committed Jan 8, 2025
1 parent 98b7821 commit 4f4b093
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 75 deletions.
57 changes: 57 additions & 0 deletions smart-templates-2/adapters/_adapter.js
Original file line number Diff line number Diff line change
@@ -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<string>} 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<void>} 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<any>} 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,
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,32 @@ export class FileJsonTemplateSourceAdapter extends TemplateSourceAdapter {

/**
* @async
* @returns {Promise<Object>} 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<Object>} 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<void>} Promise that resolves when write is complete
* @description Stringifies and writes JSON data to the template file
* @returns {Promise<void>}
*/
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
};
50 changes: 50 additions & 0 deletions smart-templates-2/adapters/markdown.js
Original file line number Diff line number Diff line change
@@ -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<Object>} 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
};
32 changes: 0 additions & 32 deletions smart-templates-2/adapters/source/_adapter.js

This file was deleted.

124 changes: 124 additions & 0 deletions smart-templates-2/adapters/tool_json.js
Original file line number Diff line number Diff line change
@@ -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<Object>} 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<string>} 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
};
41 changes: 6 additions & 35 deletions smart-templates-2/templates/lookup.json
Original file line number Diff line number Diff line change
@@ -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."
}
}
10 changes: 10 additions & 0 deletions smart-templates-2/templates/lookup.md
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit 4f4b093

Please sign in to comment.