/** * Simple Chain Pipeline — generated by aiwg nlp new * Pattern: simple-chain * Dependencies: @anthropic-ai/sdk * * Install: npm install @anthropic-ai/sdk */ import Anthropic from "@anthropic-ai/sdk"; import * as fs from "fs"; import * as path from "path"; // --------------------------------------------------------------------------- // Configuration // --------------------------------------------------------------------------- const PROMPTS_DIR = path.join(__dirname, "..", "prompts"); const MODEL = "claude-haiku-4-5"; const MAX_TOKENS = 512; const TIMEOUT_MS = 30_000; const MAX_RETRIES = 3; const RETRY_ON_STATUS = new Set([429, 502, 503]); const BACKOFF_MS = 1_000; // --------------------------------------------------------------------------- // Prompt loading // --------------------------------------------------------------------------- interface Prompt { system: string; user: string; } function loadPrompt(filename: string, variables: Record): Prompt { const filePath = path.join(PROMPTS_DIR, filename); let content = fs.readFileSync(filePath, "utf-8"); // Strip YAML frontmatter if (content.startsWith("---")) { const parts = content.split("---"); content = parts.slice(2).join("---"); } // Parse ## System and ## User sections let system = ""; let user = ""; let currentSection: "system" | "user" | null = null; for (const line of content.split("\n")) { if (line.trim() === "## System") { currentSection = "system"; } else if (line.trim() === "## User") { currentSection = "user"; } else if (currentSection === "system") { system += line + "\n"; } else if (currentSection === "user") { user += line + "\n"; } } // Substitute variables for (const [key, value] of Object.entries(variables)) { system = system.replaceAll(`{{${key}}}`, value); user = user.replaceAll(`{{${key}}}`, value); } return { system: system.trim(), user: user.trim() }; } // --------------------------------------------------------------------------- // LLM call with retry // --------------------------------------------------------------------------- async function callLLM( client: Anthropic, system: string, user: string, model: string = MODEL, maxTokens: number = MAX_TOKENS, ): Promise { let lastError: Error | undefined; for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { try { const response = await client.messages.create({ model, max_tokens: maxTokens, system, messages: [{ role: "user", content: user }], }); const block = response.content[0]; if (block.type !== "text") throw new Error("Unexpected content block type"); return block.text; } catch (err: unknown) { const status = (err as { status?: number }).status; if (status !== undefined && RETRY_ON_STATUS.has(status)) { lastError = err as Error; await new Promise((resolve) => setTimeout(resolve, BACKOFF_MS * 2 ** attempt)); } else { throw err; } } } throw new Error(`LLM call failed after ${MAX_RETRIES} attempts: ${lastError?.message}`); } // --------------------------------------------------------------------------- // Output validation // --------------------------------------------------------------------------- function validateOutput(raw: string): T { let cleaned = raw.trim(); // Strip markdown code fences if present if (cleaned.startsWith("```")) { const lines = cleaned.split("\n"); cleaned = lines .slice(1, lines[lines.length - 1] === "```" ? -1 : undefined) .join("\n"); } try { return JSON.parse(cleaned) as T; } catch (e) { throw new Error(`Output is not valid JSON: ${e}\nRaw: ${cleaned.slice(0, 200)}`); } } // --------------------------------------------------------------------------- // Pipeline // --------------------------------------------------------------------------- interface PipelineOutput { // TODO: define your output fields here [key: string]: unknown; } export async function run(inputText: string): Promise { const client = new Anthropic({ timeout: TIMEOUT_MS }); // Step: extract const { system, user } = loadPrompt("generator.prompt.md", { input_text: inputText }); const raw = await callLLM(client, system, user); const output = validateOutput(raw); // Add additional steps here: // const { system: s2, user: u2 } = loadPrompt("step2.prompt.md", { field: output.field }); // const raw2 = await callLLM(client, s2, u2); // const output2 = validateOutput(raw2); return output; } // --------------------------------------------------------------------------- // Entry point // --------------------------------------------------------------------------- if (require.main === module) { const input = process.argv[2]; if (!input) { console.error("Usage: ts-node pipeline.ts ''"); process.exit(1); } run(input) .then((result) => console.log(JSON.stringify(result, null, 2))) .catch((err) => { console.error(err); process.exit(1); }); }