From e3f6f74b1bff75e73b5cf5e184c87139d9b08ea2 Mon Sep 17 00:00:00 2001 From: babayaga Date: Mon, 7 Apr 2025 17:41:05 +0200 Subject: [PATCH] kbot iterator example: md-ast transformer --- .../core/iterator-markdown-example.d.ts | 2 +- .../core/iterator-markdown-example.js | 298 ++++++++------- packages/kbot/logs/params.json | 2 +- .../core/iterator-markdown-example.ts | 340 ++++++++++-------- 4 files changed, 361 insertions(+), 281 deletions(-) diff --git a/packages/kbot/dist-in/examples/core/iterator-markdown-example.d.ts b/packages/kbot/dist-in/examples/core/iterator-markdown-example.d.ts index cb228352..b1805c1e 100644 --- a/packages/kbot/dist-in/examples/core/iterator-markdown-example.d.ts +++ b/packages/kbot/dist-in/examples/core/iterator-markdown-example.d.ts @@ -1 +1 @@ -export declare function markdownTransformExample(useCache?: boolean): Promise; +export declare function markdownTransformExample(useCache?: boolean): Promise; diff --git a/packages/kbot/dist-in/examples/core/iterator-markdown-example.js b/packages/kbot/dist-in/examples/core/iterator-markdown-example.js index bb6f3461..8ed761e1 100644 --- a/packages/kbot/dist-in/examples/core/iterator-markdown-example.js +++ b/packages/kbot/dist-in/examples/core/iterator-markdown-example.js @@ -8,6 +8,8 @@ import { sync as write } from "@polymech/fs/write"; import { E_OPENROUTER_MODEL } from '../../models/cache/openrouter-models.js'; import { E_Mode } from '../../zod_schema.js'; import { transform, createLLMTransformer } from '../../iterator.js'; +import { deepClone } from '@polymech/core/objects'; +import { run } from '../../commands/run.js'; /** * Notes for LLM modifications * @@ -21,6 +23,7 @@ const MODEL = E_OPENROUTER_MODEL.MODEL_OPENAI_GPT_4O_MINI; // Corrected model na const ROUTER = 'openrouter'; const INPUT_MD_PATH = path.resolve('./tests/test-data/core/md-test.md'); const OUTPUT_MD_PATH = path.resolve('./tests/test-data/core/md-test-out.md'); +const OUTPUT_JSON_PATH = path.resolve('./tests/test-data/core/md-test-out.json'); // Basic logger const logger = { info: (message) => console.log(`INFO: ${message}`), @@ -41,56 +44,22 @@ function getLLMTransformerForMdast(options, baseLogger = logger, cacheConfig) { return llmTransformer; } // Define field mappings for the mdast structure -const fieldMappings = [ +// Mappings for standard transforms (targetPath: null) +const standardMappings = [ { - // Target text value within H1-H5 headings' children jsonPath: '$.children[?(@.type=="heading" && @.depth <= 5)].children[?(@.type=="text")].value', - targetPath: null, // Modify in place (NOTE: This likely won't work due to iterator limitations) - options: { - prompt: 'Rewrite this heading to be more concise and impactful (max 5 words).' - } + targetPath: null, + options: { prompt: 'Rewrite this heading to be more concise and impactful (max 5 words).' } }, { - // Target the specific paragraph NODE in Chapter 4 for structured analysis. - // The transformer will extract text from the node passed to it. - // Using targetPath='analysisResult' stores the LLM output on the node itself. - jsonPath: '$.children[?(@.type=="paragraph" && @.children[0].value.includes("results"))]', - targetPath: 'analysisResult', // Store result in a new property on the paragraph node - options: { - prompt: 'Extract keywords and sentiment from this text.', - format: { - type: "object", - properties: { - sentiment: { - type: "string", - enum: ["positive", "neutral", "negative"], - description: "Overall sentiment" - }, - keywords: { - type: "array", - items: { type: "string" }, - description: "Main keywords (max 5)" - } - }, - required: ["sentiment", "keywords"] - } - } - }, - { - // Target text value within paragraphs' children jsonPath: '$.children[?(@.type=="paragraph")].children[?(@.type=="text")].value', - targetPath: null, // Modify in place (NOTE: This likely won't work due to iterator limitations) - options: { - prompt: 'Summarize this paragraph in one short sentence (max 15 words).' - } + targetPath: null, + options: { prompt: 'Summarize this paragraph in one short sentence (max 15 words).' } }, { - // Target text value within table cells' children jsonPath: '$.children[?(@.type=="table")].children[*].children[*].children[?(@.type=="text")].value', - targetPath: null, // Modify in place (NOTE: This likely won't work due to iterator limitations) - options: { - prompt: 'Rephrase this table cell content slightly.' - } + targetPath: null, + options: { prompt: 'Rephrase this table cell content slightly.' } } ]; // Example onTransform callback for Markdown @@ -113,8 +82,25 @@ export async function markdownTransformExample(useCache = true) { const ast = processor.parse(markdownInput); // Make a deep copy to avoid modifying the original AST if transform fails // Note: Standard deepClone might not work perfectly with complex AST nodes (position, data fields). - // For this example, JSON stringify/parse is a common workaround, but beware of data loss. - let astToTransform = JSON.parse(JSON.stringify(ast)); + // Using deepClone instead of JSON.parse/stringify + let astToTransform = deepClone(ast); + // --- Pre-processing: Add temporary ID to the target node --- + const analysisTargetText = "results"; // Text to identify the node + const tempIdValue = 'analyze-me'; + let identifiedNodeForAnalysis = null; // Store reference to the node + try { + visit(astToTransform, 'paragraph', (node) => { + if (node.children?.[0]?.value?.includes(analysisTargetText)) { + node.tempId = tempIdValue; + identifiedNodeForAnalysis = node; // Store the reference + logger.info(`[Pre-process] Added tempId='${tempIdValue}' and stored reference to node containing '${analysisTargetText}'`); + } + }); + } + catch (e) { + logger.error("[Pre-process] Error adding temporary ID:", e); + identifiedNodeForAnalysis = null; // Ensure we don't proceed if tagging failed + } // 3. Define global options and iterator options const globalOptionsMixin = { model: MODEL, @@ -122,40 +108,23 @@ export async function markdownTransformExample(useCache = true) { mode: E_Mode.COMPLETION, }; const iteratorOptions = { - // We provide our custom transformer factory transformerFactory: (opts) => getLLMTransformerForMdast(opts, logger, { enabled: useCache }), logger: logger, cacheConfig: { enabled: useCache, namespace: 'markdown-transforms' }, - // onTransform receives the value matched by jsonPath (can be string or node). - // It must return the STRING to be transformed by the LLM. onTransform: async (jsonPath, value, kbotOptions) => { let textContent = ''; - // Check if the value is a node object or just a string if (typeof value === 'string') { textContent = value; console.log(` -> onTransform String Value: Path='${jsonPath}', Value='${textContent.substring(0, 50)}...'`); } else if (typeof value === 'object' && value !== null) { - // Attempt to extract text if it's a node (e.g., for the analysisResult mapping) - const node = value; // Basic type assertion - if ((node.type === 'heading' || node.type === 'paragraph' || node.type === 'tableCell') && node.children?.length === 1 && node.children[0].type === 'text') { - textContent = node.children[0].value || ''; - } - else if (node.type === 'text') { - textContent = node.value || ''; - } - else if (Array.isArray(node.children)) { // Handle complex children - textContent = node.children - .filter((child) => child.type === 'text' || child.type === 'inlineCode') - .map((child) => child.value) - .join(''); - } + const node = value; + textContent = node.children?.[0]?.value || node.value || ''; console.log(` -> onTransform AST Node: Path='${jsonPath}', Node Type='${node?.type}', Extracted Text='${textContent.substring(0, 50)}...'`); } else { console.log(` -> onTransform Unexpected Value Type: Path='${jsonPath}', Type='${typeof value}'`); } - // Return the extracted string for the LLM transformer return textContent; }, errorCallback: (path, value, error) => { @@ -167,93 +136,176 @@ export async function markdownTransformExample(useCache = true) { } }, filterCallback: async (value, jsonPath) => { - // Allow transformation if the value is an object (likely an AST node targeted directly) if (typeof value === 'object' && value !== null) { return true; } - // If it's a string, apply the default string filter logic if (typeof value === 'string') { - // Reuse the default isValidString filter logic for strings const allow = value.trim() !== ''; if (!allow) { logger.info(`Filter: Skipping empty string at ${jsonPath}`); } return allow; } - // Skip other types (numbers, booleans, null, undefined) logger.warn(`Filter: Skipping non-string/non-object value type (${typeof value}) at ${jsonPath}`); return false; } }; - // 4. Use the transform function - console.log("Applying transformations to AST..."); - // The 'transform' function iterates based on JSONPath and applies the transformerFactory's result - // It modifies the 'astToTransform' object in place. - await transform(astToTransform, // Pass the AST object - fieldMappings, globalOptionsMixin, iteratorOptions); - // *** Add logging before visit *** - console.log("\n[DEBUG] Inspecting AST before visit call:"); + // 4. Run standard transformations + console.log("Applying standard transformations to AST..."); + await transform(astToTransform, standardMappings, globalOptionsMixin, iteratorOptions); + // 5. Perform Analysis Manually (if node was identified) + console.log("\nPerforming manual analysis..."); + let analysisData = null; // Store successfully parsed result here + let rawAnalysisResult = null; // Store raw LLM result + if (identifiedNodeForAnalysis) { + try { + let nodeText = ''; + // Extract text from the (now potentially modified) node + if (identifiedNodeForAnalysis.children?.[0]?.type === 'text') { + nodeText = identifiedNodeForAnalysis.children[0].value || ''; + } + logger.info(`[Manual Analysis] Found node with tempId. Extracted text: "${nodeText.substring(0, 50)}..."`); + if (nodeText) { + // Define analysis task options + const analysisTaskOptions = { + ...globalOptionsMixin, + // Explicitly request ONLY JSON matching the schema + prompt: `Analyze the following text and extract keywords and sentiment. Respond ONLY with a valid JSON object matching the schema provided in the 'format' field. Do not include any other text, explanations, or markdown formatting.\n\nText to analyze:\n"${nodeText}"`, + format: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "neutral", "negative"] }, + keywords: { type: "array", items: { type: "string" }, maxItems: 5 } + }, + required: ["sentiment", "keywords"] + }, + mode: E_Mode.COMPLETION + }; + // Call the run command + logger.info(`[Manual Analysis] Calling run command with format option...`); + const results = await run(analysisTaskOptions); + // Process the result (run should return the parsed object) + if (results && results.length > 0) { + const rawResult = results[0]; + logger.info(`[Manual Analysis] Raw result from run: ${JSON.stringify(rawResult)}`); + if (typeof rawResult === 'object') { + // If it's already an object, use it directly + analysisData = rawResult; + logger.info(`[Manual Analysis] 'run' returned an object.`); + } + else if (typeof rawResult === 'string') { + // If it's a string, try to parse it as JSON + logger.info(`[Manual Analysis] 'run' returned a string, attempting JSON parse...`); + try { + // Basic cleanup: Remove potential markdown like ```json ... ``` wrapper + const cleanedString = rawResult.replace(/^```json\s*|```$/g, '').trim(); + analysisData = JSON.parse(cleanedString); + logger.info(`[Manual Analysis] Successfully parsed string result.`); + } + catch (parseError) { + // Corrected logger calls + logger.error(`[Manual Analysis] Failed to parse string result from 'run': ${parseError instanceof Error ? parseError.message : parseError}`); + logger.error(`[Manual Analysis] Raw string was: ${rawResult}`); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'parse failed', raw: rawResult }; // Store error/raw + } + } + else { + // Unexpected type + logger.warn(`[Manual Analysis] 'run' returned unexpected type: ${typeof rawResult}`); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'unexpected result type', raw: rawResult }; + } + // If parsing succeeded and we have data, attach it + if (analysisData) { + identifiedNodeForAnalysis.manualAnalysisResult = analysisData; // Attach parsed data + logger.info(`[Manual Analysis] Successfully attached analysis result object.`); + logger.info(`[Manual Analysis] Result Content: ${JSON.stringify(analysisData, null, 2)}`); + } + else { + logger.warn(`[Manual Analysis] 'run' command returned empty or unexpected result: ${JSON.stringify(results)}`); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'empty or unexpected result', raw: results }; + } + } + // Clean up tempId after processing + if (identifiedNodeForAnalysis && identifiedNodeForAnalysis.tempId) { + delete identifiedNodeForAnalysis.tempId; + logger.info(`[Manual Analysis] Removed tempId.`); + } + } + else { + logger.warn("[Manual Analysis] Node text was empty, skipping analysis LLM call."); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'empty source text' }; + } + } + catch (manualAnalysisError) { + logger.error("Error during manual analysis step:", manualAnalysisError); + // Attempt to clean up tempId even if analysis failed + if (identifiedNodeForAnalysis && identifiedNodeForAnalysis.tempId) { + delete identifiedNodeForAnalysis.tempId; + } + } + } + else { + logger.warn("[Manual Analysis] Target node for analysis was not identified in pre-processing. Skipping analysis."); + } + // --- Debug log checks for manualAnalysisResult --- + console.log("\n[DEBUG] Inspecting AST after all transforms, before visit call:"); try { - // Attempt to find the specific paragraph node expected to have analysisResult - // NOTE: This relies on index/structure and might be brittle - const potentialNode = astToTransform.children?.find((node) => node.type === 'paragraph' && - node.children?.[0]?.value?.includes("results")); - if (potentialNode) { - console.log("[DEBUG] Found potential node:", JSON.stringify(potentialNode, null, 2)); - console.log(`[DEBUG] Does potential node have analysisResult? ${potentialNode.hasOwnProperty('analysisResult')}`); + const potentialNode = astToTransform.children?.[16]; + if (potentialNode && potentialNode.type === 'paragraph') { + const hasManualProp = potentialNode.hasOwnProperty('manualAnalysisResult'); + console.log(`[DEBUG] Does potential node have manualAnalysisResult? ${hasManualProp}`); + if (hasManualProp) { + console.log("[DEBUG] manualAnalysisResult Content:", potentialNode.manualAnalysisResult); + } } else { - console.log("[DEBUG] Could not find the target paragraph node directly before visit."); + console.log("[DEBUG] Could not find the target paragraph node at index 16."); } console.log("---------------------------------------"); } catch (e) { console.error("[DEBUG] Error during pre-visit inspection:", e); } - // Retrieve the structured analysis result (if any) - // Note: The 'analysisResult' was added as a new property to the AST node by the mapping. - // We need to find that node again to see the result. This is clumsy. - let analysisData = null; + // Log the manually obtained result (if parsed successfully) + if (analysisData) { + console.log("\nStructured Analysis Result (Manually Obtained):"); + console.log(JSON.stringify(analysisData, null, 2)); + } + else { + console.log("\nStructured Analysis Result was not successfully obtained."); + // Raw result might be in the error object attached to the node now + } + // 6. Save the transformed AST to JSON (will include manualAnalysisResult) + console.log(`\nWriting transformed AST to JSON: ${OUTPUT_JSON_PATH}`); try { - visit(astToTransform, 'paragraph', (node) => { - if (node.analysisResult) { - analysisData = node.analysisResult; - // Optional: Remove the temporary property from the AST before stringifying - // delete node.analysisResult; - } - }); - if (analysisData) { - console.log("\nStructured Analysis Result:"); - // The LLM might return a stringified JSON, try parsing it - try { - const parsedResult = typeof analysisData === 'string' ? JSON.parse(analysisData) : analysisData; - console.log(JSON.stringify(parsedResult, null, 2)); - } - catch (e) { - console.log("Analysis result (raw):", analysisData); - } + const outputJsonDir = path.dirname(OUTPUT_JSON_PATH); + if (!fs.existsSync(outputJsonDir)) { + fs.mkdirSync(outputJsonDir, { recursive: true }); } - else { - console.log("\nStructured Analysis Result not found on AST (check targetPath logic and LLM response)."); + fs.writeFileSync(OUTPUT_JSON_PATH, JSON.stringify(astToTransform, null, 2)); + console.log("AST JSON saved successfully."); + } + catch (error) { + logger.error("Failed to write AST JSON:", error); + } + // 7. Stringify the modified AST back to Markdown + console.log("\nStringifying transformed AST to Markdown..."); + try { + const processorStringify = unified().use(remarkStringify); + const outputMarkdown = processorStringify.stringify(astToTransform); + // 8. Write the Markdown output file + console.log(`Writing output Markdown to: ${OUTPUT_MD_PATH}`); + const outputMdDir = path.dirname(OUTPUT_MD_PATH); + if (!fs.existsSync(outputMdDir)) { + fs.mkdirSync(outputMdDir, { recursive: true }); } + write(OUTPUT_MD_PATH, outputMarkdown); + console.log("Markdown file saved successfully."); } - catch (e) { - logger.error("Error visiting AST for analysis result retrieval:", e); + catch (stringifyError) { + logger.error("Failed to stringify or write Markdown output:", stringifyError); } - // 5. Stringify the modified AST back to Markdown - console.log("\nStringifying transformed AST..."); - const processorStringify = unified().use(remarkStringify); - // Stringify might fail if the AST structure became invalid during transformation - const outputMarkdown = processorStringify.stringify(astToTransform); - // 6. Write the output file - console.log(`Writing output to: ${OUTPUT_MD_PATH}`); - const outputDir = path.dirname(OUTPUT_MD_PATH); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - write(OUTPUT_MD_PATH, outputMarkdown); - console.log("Markdown transformation complete."); - return astToTransform; // Return the transformed AST + return astToTransform; } catch (error) { logger.error("ERROR during Markdown transformation:", error); @@ -272,4 +324,4 @@ if (process.argv[1] && process.argv[1].includes('iterator-markdown-example')) { process.exit(1); // Exit with error code }); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaXRlcmF0b3ItbWFya2Rvd24tZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9leGFtcGxlcy9jb3JlL2l0ZXJhdG9yLW1hcmtkb3duLWV4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNsQyxPQUFPLFdBQVcsTUFBTSxjQUFjLENBQUM7QUFDdkMsT0FBTyxlQUFlLE1BQU0sa0JBQWtCLENBQUM7QUFDL0MsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbkQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDN0UsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQzdDLE9BQU8sRUFBZ0IsU0FBUyxFQUFZLG9CQUFvQixFQUF3QixNQUFNLG1CQUFtQixDQUFDO0FBR2xIOzs7Ozs7OztHQVFHO0FBRUgsTUFBTSxLQUFLLEdBQUcsa0JBQWtCLENBQUMsd0JBQXdCLENBQUMsQ0FBQyx1QkFBdUI7QUFDbEYsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDO0FBQzVCLE1BQU0sYUFBYSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsbUNBQW1DLENBQUMsQ0FBQztBQUN4RSxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLHVDQUF1QyxDQUFDLENBQUM7QUFFN0UsZUFBZTtBQUNmLE1BQU0sTUFBTSxHQUFZO0lBQ3BCLElBQUksRUFBRSxDQUFDLE9BQWUsRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLE9BQU8s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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaXRlcmF0b3ItbWFya2Rvd24tZXhhbXBsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9leGFtcGxlcy9jb3JlL2l0ZXJhdG9yLW1hcmtkb3duLWV4YW1wbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFDN0IsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNsQyxPQUFPLFdBQVcsTUFBTSxjQUFjLENBQUM7QUFDdkMsT0FBTyxlQUFlLE1BQU0sa0JBQWtCLENBQUM7QUFDL0MsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxJQUFJLElBQUksS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbkQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDN0UsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQzdDLE9BQU8sRUFBZ0IsU0FBUyxFQUFZLG9CQUFvQixFQUF3QixNQUFNLG1CQUFtQixDQUFDO0FBRWxILE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNuRCxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFNUM7Ozs7Ozs7O0dBUUc7QUFFSCxNQUFNLEtBQUssR0FBRyxrQkFBa0IsQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDLHVCQUF1QjtBQUNsRixNQUFNLE1BQU0sR0FBRyxZQUFZLENBQUM7QUFDNUIsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxtQ0FBbUMsQ0FBQyxDQUFDO0FBQ3hFLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsdUNBQXVDLENBQUMsQ0FBQztBQUM3RSxNQUFNLGdCQUFn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 \ No newline at end of file diff --git a/packages/kbot/logs/params.json b/packages/kbot/logs/params.json index 041bc522..8a6a3af5 100644 --- a/packages/kbot/logs/params.json +++ b/packages/kbot/logs/params.json @@ -3,7 +3,7 @@ "messages": [ { "role": "user", - "content": "Summarize this paragraph in one short sentence (max 15 words).\n\nText to transform: \"A concluding paragraph summarizing the key findings.\"" + "content": "Analyze the following text and extract keywords and sentiment. Respond ONLY with a valid JSON object matching the schema provided in the 'format' field. Do not include any other text, explanations, or markdown formatting.\n\nText to analyze:\n\"The text mentions sunny weather, making it ideal for a park walk.\"" }, { "role": "user", diff --git a/packages/kbot/src/examples/core/iterator-markdown-example.ts b/packages/kbot/src/examples/core/iterator-markdown-example.ts index a369f4f9..fc80d439 100644 --- a/packages/kbot/src/examples/core/iterator-markdown-example.ts +++ b/packages/kbot/src/examples/core/iterator-markdown-example.ts @@ -10,6 +10,8 @@ import { E_OPENROUTER_MODEL } from '../../models/cache/openrouter-models.js'; import { E_Mode } from '../../zod_schema.js'; import { FieldMapping, transform, IOptions, createLLMTransformer, CacheConfig, ILogger } from '../../iterator.js'; import { OnTransformCallback } from '../../async-iterator.js'; +import { deepClone } from '@polymech/core/objects'; +import { run } from '../../commands/run.js'; /** * Notes for LLM modifications @@ -25,6 +27,7 @@ const MODEL = E_OPENROUTER_MODEL.MODEL_OPENAI_GPT_4O_MINI; // Corrected model na const ROUTER = 'openrouter'; const INPUT_MD_PATH = path.resolve('./tests/test-data/core/md-test.md'); const OUTPUT_MD_PATH = path.resolve('./tests/test-data/core/md-test-out.md'); +const OUTPUT_JSON_PATH = path.resolve('./tests/test-data/core/md-test-out.json'); // Basic logger const logger: ILogger = { @@ -52,56 +55,22 @@ function getLLMTransformerForMdast( } // Define field mappings for the mdast structure -const fieldMappings: FieldMapping[] = [ +// Mappings for standard transforms (targetPath: null) +const standardMappings: FieldMapping[] = [ { - // Target text value within H1-H5 headings' children jsonPath: '$.children[?(@.type=="heading" && @.depth <= 5)].children[?(@.type=="text")].value', - targetPath: null, // Modify in place (NOTE: This likely won't work due to iterator limitations) - options: { - prompt: 'Rewrite this heading to be more concise and impactful (max 5 words).' - } + targetPath: null, + options: { prompt: 'Rewrite this heading to be more concise and impactful (max 5 words).' } }, { - // Target the specific paragraph NODE in Chapter 4 for structured analysis. - // The transformer will extract text from the node passed to it. - // Using targetPath='analysisResult' stores the LLM output on the node itself. - jsonPath: '$.children[?(@.type=="paragraph" && @.children[0].value.includes("results"))]', - targetPath: 'analysisResult', // Store result in a new property on the paragraph node - options: { - prompt: 'Extract keywords and sentiment from this text.', - format: { - type: "object", - properties: { - sentiment: { - type: "string", - enum: ["positive", "neutral", "negative"], - description: "Overall sentiment" - }, - keywords: { - type: "array", - items: { type: "string" }, - description: "Main keywords (max 5)" - } - }, - required: ["sentiment", "keywords"] - } - } - }, - { - // Target text value within paragraphs' children jsonPath: '$.children[?(@.type=="paragraph")].children[?(@.type=="text")].value', - targetPath: null, // Modify in place (NOTE: This likely won't work due to iterator limitations) - options: { - prompt: 'Summarize this paragraph in one short sentence (max 15 words).' - } + targetPath: null, + options: { prompt: 'Summarize this paragraph in one short sentence (max 15 words).' } }, { - // Target text value within table cells' children jsonPath: '$.children[?(@.type=="table")].children[*].children[*].children[?(@.type=="text")].value', - targetPath: null, // Modify in place (NOTE: This likely won't work due to iterator limitations) - options: { - prompt: 'Rephrase this table cell content slightly.' - } + targetPath: null, + options: { prompt: 'Rephrase this table cell content slightly.' } } ]; @@ -129,9 +98,25 @@ export async function markdownTransformExample(useCache = true) { // Make a deep copy to avoid modifying the original AST if transform fails // Note: Standard deepClone might not work perfectly with complex AST nodes (position, data fields). - // For this example, JSON stringify/parse is a common workaround, but beware of data loss. - let astToTransform = JSON.parse(JSON.stringify(ast)); + // Using deepClone instead of JSON.parse/stringify + let astToTransform = deepClone(ast); + // --- Pre-processing: Add temporary ID to the target node --- + const analysisTargetText = "results"; // Text to identify the node + const tempIdValue = 'analyze-me'; + let identifiedNodeForAnalysis: any = null; // Store reference to the node + try { + visit(astToTransform, 'paragraph', (node: any) => { + if (node.children?.[0]?.value?.includes(analysisTargetText)) { + node.tempId = tempIdValue; + identifiedNodeForAnalysis = node; // Store the reference + logger.info(`[Pre-process] Added tempId='${tempIdValue}' and stored reference to node containing '${analysisTargetText}'`); + } + }); + } catch(e) { + logger.error("[Pre-process] Error adding temporary ID:", e); + identifiedNodeForAnalysis = null; // Ensure we don't proceed if tagging failed + } // 3. Define global options and iterator options const globalOptionsMixin: Partial = { @@ -141,142 +126,185 @@ export async function markdownTransformExample(useCache = true) { }; const iteratorOptions: IOptions = { - // We provide our custom transformer factory transformerFactory: (opts) => getLLMTransformerForMdast(opts, logger, { enabled: useCache }), logger: logger, cacheConfig: { enabled: useCache, namespace: 'markdown-transforms' }, - // onTransform receives the value matched by jsonPath (can be string or node). - // It must return the STRING to be transformed by the LLM. onTransform: async (jsonPath, value, kbotOptions) => { let textContent = ''; - // Check if the value is a node object or just a string - if (typeof value === 'string') { - textContent = value; - console.log(` -> onTransform String Value: Path='${jsonPath}', Value='${textContent.substring(0, 50)}...'`); - } else if (typeof value === 'object' && value !== null) { - // Attempt to extract text if it's a node (e.g., for the analysisResult mapping) - const node = value as any; // Basic type assertion - if ((node.type === 'heading' || node.type === 'paragraph' || node.type === 'tableCell') && node.children?.length === 1 && node.children[0].type === 'text') { - textContent = node.children[0].value || ''; - } else if (node.type === 'text') { - textContent = node.value || ''; - } else if (Array.isArray(node.children)) { // Handle complex children - textContent = node.children - .filter((child: any) => child.type === 'text' || child.type === 'inlineCode') - .map((child: any) => child.value) - .join(''); - } - console.log(` -> onTransform AST Node: Path='${jsonPath}', Node Type='${node?.type}', Extracted Text='${textContent.substring(0, 50)}...'`); - } else { - console.log(` -> onTransform Unexpected Value Type: Path='${jsonPath}', Type='${typeof value}'`); - } - - // Return the extracted string for the LLM transformer - return textContent; + if (typeof value === 'string') { textContent = value; console.log(` -> onTransform String Value: Path='${jsonPath}', Value='${textContent.substring(0, 50)}...'`); } + else if (typeof value === 'object' && value !== null) { const node = value as any; textContent = node.children?.[0]?.value || node.value || ''; console.log(` -> onTransform AST Node: Path='${jsonPath}', Node Type='${node?.type}', Extracted Text='${textContent.substring(0, 50)}...'`); } + else { console.log(` -> onTransform Unexpected Value Type: Path='${jsonPath}', Type='${typeof value}'`); } + return textContent; }, - errorCallback: (path, value, error) => { - if (error instanceof Error) { - logger.error(`Error processing path ${path}: ${error.message}`, error); - } else { - logger.error(`Error processing path ${path}: ${error}`, error); - } - }, - filterCallback: async (value, jsonPath) => { - // Allow transformation if the value is an object (likely an AST node targeted directly) - if (typeof value === 'object' && value !== null) { - return true; - } - // If it's a string, apply the default string filter logic - if (typeof value === 'string') { - // Reuse the default isValidString filter logic for strings - const allow = value.trim() !== ''; - if (!allow) { - logger.info(`Filter: Skipping empty string at ${jsonPath}`); - } - return allow; - } - // Skip other types (numbers, booleans, null, undefined) - logger.warn(`Filter: Skipping non-string/non-object value type (${typeof value}) at ${jsonPath}`); - return false; - } + errorCallback: (path, value, error) => { + if (error instanceof Error) { logger.error(`Error processing path ${path}: ${error.message}`, error); } else { logger.error(`Error processing path ${path}: ${error}`, error); } + }, + filterCallback: async (value, jsonPath) => { + if (typeof value === 'object' && value !== null) { return true; } if (typeof value === 'string') { const allow = value.trim() !== ''; if (!allow) { logger.info(`Filter: Skipping empty string at ${jsonPath}`); } return allow; } logger.warn(`Filter: Skipping non-string/non-object value type (${typeof value}) at ${jsonPath}`); return false; + } }; - // 4. Use the transform function - console.log("Applying transformations to AST..."); - // The 'transform' function iterates based on JSONPath and applies the transformerFactory's result - // It modifies the 'astToTransform' object in place. + // 4. Run standard transformations + console.log("Applying standard transformations to AST..."); await transform( - astToTransform, // Pass the AST object - fieldMappings, + astToTransform, + standardMappings, globalOptionsMixin, iteratorOptions ); - // *** Add logging before visit *** - console.log("\n[DEBUG] Inspecting AST before visit call:"); - try { - // Attempt to find the specific paragraph node expected to have analysisResult - // NOTE: This relies on index/structure and might be brittle - const potentialNode = astToTransform.children?.find((node: any) => - node.type === 'paragraph' && - node.children?.[0]?.value?.includes("results") - ); - if (potentialNode) { - console.log("[DEBUG] Found potential node:", JSON.stringify(potentialNode, null, 2)); - console.log(`[DEBUG] Does potential node have analysisResult? ${potentialNode.hasOwnProperty('analysisResult')}`); - } else { - console.log("[DEBUG] Could not find the target paragraph node directly before visit."); + // 5. Perform Analysis Manually (if node was identified) + console.log("\nPerforming manual analysis..."); + let analysisData: any = null; // Store successfully parsed result here + let rawAnalysisResult: string | null = null; // Store raw LLM result + + if (identifiedNodeForAnalysis) { + try { + let nodeText: string = ''; + // Extract text from the (now potentially modified) node + if (identifiedNodeForAnalysis.children?.[0]?.type === 'text') { + nodeText = identifiedNodeForAnalysis.children[0].value || ''; + } + logger.info(`[Manual Analysis] Found node with tempId. Extracted text: "${nodeText.substring(0, 50)}..."`); + + if (nodeText) { + // Define analysis task options + const analysisTaskOptions: IKBotTask = { + ...globalOptionsMixin, + // Explicitly request ONLY JSON matching the schema + prompt: `Analyze the following text and extract keywords and sentiment. Respond ONLY with a valid JSON object matching the schema provided in the 'format' field. Do not include any other text, explanations, or markdown formatting.\n\nText to analyze:\n"${nodeText}"`, + format: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "neutral", "negative"] }, + keywords: { type: "array", items: { type: "string" }, maxItems: 5 } + }, + required: ["sentiment", "keywords"] + }, + mode: E_Mode.COMPLETION + }; + + // Call the run command + logger.info(`[Manual Analysis] Calling run command with format option...`); + const results = await run(analysisTaskOptions); + + // Process the result (run should return the parsed object) + if (results && results.length > 0) { + const rawResult = results[0]; + logger.info(`[Manual Analysis] Raw result from run: ${JSON.stringify(rawResult)}`); + + if (typeof rawResult === 'object') { + // If it's already an object, use it directly + analysisData = rawResult; + logger.info(`[Manual Analysis] 'run' returned an object.`); + } else if (typeof rawResult === 'string') { + // If it's a string, try to parse it as JSON + logger.info(`[Manual Analysis] 'run' returned a string, attempting JSON parse...`); + try { + // Basic cleanup: Remove potential markdown like ```json ... ``` wrapper + const cleanedString = rawResult.replace(/^```json\s*|```$/g, '').trim(); + analysisData = JSON.parse(cleanedString); + logger.info(`[Manual Analysis] Successfully parsed string result.`); + } catch (parseError) { + // Corrected logger calls + logger.error(`[Manual Analysis] Failed to parse string result from 'run': ${parseError instanceof Error ? parseError.message : parseError}`); + logger.error(`[Manual Analysis] Raw string was: ${rawResult}`); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'parse failed', raw: rawResult }; // Store error/raw + } + } else { + // Unexpected type + logger.warn(`[Manual Analysis] 'run' returned unexpected type: ${typeof rawResult}`); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'unexpected result type', raw: rawResult }; + } + + // If parsing succeeded and we have data, attach it + if (analysisData) { + identifiedNodeForAnalysis.manualAnalysisResult = analysisData; // Attach parsed data + logger.info(`[Manual Analysis] Successfully attached analysis result object.`); + logger.info(`[Manual Analysis] Result Content: ${JSON.stringify(analysisData, null, 2)}`); + } else { + logger.warn(`[Manual Analysis] 'run' command returned empty or unexpected result: ${JSON.stringify(results)}`); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'empty or unexpected result', raw: results }; + } + } + + // Clean up tempId after processing + if (identifiedNodeForAnalysis && identifiedNodeForAnalysis.tempId) { + delete identifiedNodeForAnalysis.tempId; + logger.info(`[Manual Analysis] Removed tempId.`); + } + + } else { + logger.warn("[Manual Analysis] Node text was empty, skipping analysis LLM call."); + identifiedNodeForAnalysis.manualAnalysisResult = { error: 'empty source text' }; + } + + } catch (manualAnalysisError) { + logger.error("Error during manual analysis step:", manualAnalysisError); + // Attempt to clean up tempId even if analysis failed + if (identifiedNodeForAnalysis && identifiedNodeForAnalysis.tempId) { + delete identifiedNodeForAnalysis.tempId; + } } - console.log("---------------------------------------"); + } else { + logger.warn("[Manual Analysis] Target node for analysis was not identified in pre-processing. Skipping analysis."); + } + + // --- Debug log checks for manualAnalysisResult --- + console.log("\n[DEBUG] Inspecting AST after all transforms, before visit call:"); + try { + const potentialNode = astToTransform.children?.[16]; + if (potentialNode && potentialNode.type === 'paragraph') { + const hasManualProp = potentialNode.hasOwnProperty('manualAnalysisResult'); + console.log(`[DEBUG] Does potential node have manualAnalysisResult? ${hasManualProp}`); + if (hasManualProp) { + console.log("[DEBUG] manualAnalysisResult Content:", (potentialNode as any).manualAnalysisResult); + } + } else { + console.log("[DEBUG] Could not find the target paragraph node at index 16."); + } + console.log("---------------------------------------"); } catch (e) { console.error("[DEBUG] Error during pre-visit inspection:", e); } + + // Log the manually obtained result (if parsed successfully) + if (analysisData) { + console.log("\nStructured Analysis Result (Manually Obtained):"); + console.log(JSON.stringify(analysisData, null, 2)); + } else { + console.log("\nStructured Analysis Result was not successfully obtained."); + // Raw result might be in the error object attached to the node now + } - // Retrieve the structured analysis result (if any) - // Note: The 'analysisResult' was added as a new property to the AST node by the mapping. - // We need to find that node again to see the result. This is clumsy. - let analysisData: any = null; + // 6. Save the transformed AST to JSON (will include manualAnalysisResult) + console.log(`\nWriting transformed AST to JSON: ${OUTPUT_JSON_PATH}`); try { - visit(astToTransform, 'paragraph', (node: any) => { - if (node.analysisResult) { - analysisData = node.analysisResult; - // Optional: Remove the temporary property from the AST before stringifying - // delete node.analysisResult; - } - }); - if (analysisData) { - console.log("\nStructured Analysis Result:"); - // The LLM might return a stringified JSON, try parsing it - try { - const parsedResult = typeof analysisData === 'string' ? JSON.parse(analysisData) : analysisData; - console.log(JSON.stringify(parsedResult, null, 2)); - } catch (e) { - console.log("Analysis result (raw):", analysisData); - } - } else { - console.log("\nStructured Analysis Result not found on AST (check targetPath logic and LLM response)."); - } - } catch (e) { - logger.error("Error visiting AST for analysis result retrieval:", e) + const outputJsonDir = path.dirname(OUTPUT_JSON_PATH); + if (!fs.existsSync(outputJsonDir)) { fs.mkdirSync(outputJsonDir, { recursive: true }); } + fs.writeFileSync(OUTPUT_JSON_PATH, JSON.stringify(astToTransform, null, 2)); + console.log("AST JSON saved successfully."); + } catch (error) { + logger.error("Failed to write AST JSON:", error); } + // 7. Stringify the modified AST back to Markdown + console.log("\nStringifying transformed AST to Markdown..."); + try { + const processorStringify = unified().use(remarkStringify); + const outputMarkdown = processorStringify.stringify(astToTransform as any); - // 5. Stringify the modified AST back to Markdown - console.log("\nStringifying transformed AST..."); - const processorStringify = unified().use(remarkStringify); - // Stringify might fail if the AST structure became invalid during transformation - const outputMarkdown = processorStringify.stringify(astToTransform as any); - - // 6. Write the output file - console.log(`Writing output to: ${OUTPUT_MD_PATH}`); - const outputDir = path.dirname(OUTPUT_MD_PATH); - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); + // 8. Write the Markdown output file + console.log(`Writing output Markdown to: ${OUTPUT_MD_PATH}`); + const outputMdDir = path.dirname(OUTPUT_MD_PATH); + if (!fs.existsSync(outputMdDir)) { fs.mkdirSync(outputMdDir, { recursive: true }); } + write(OUTPUT_MD_PATH, outputMarkdown); + console.log("Markdown file saved successfully."); + } catch (stringifyError) { + logger.error("Failed to stringify or write Markdown output:", stringifyError); } - write(OUTPUT_MD_PATH, outputMarkdown); - console.log("Markdown transformation complete."); - return astToTransform; // Return the transformed AST + return astToTransform; } catch (error) { logger.error("ERROR during Markdown transformation:", error);