DEV Community

Learn AI Resource
Learn AI Resource

Posted on • Originally published at learnairesource.com

Stop Writing Boilerplate: How I Automated My Entire Workflow with LLM APIs

Stop Writing Boilerplate: How I Automated My Entire Workflow with LLM APIs

You know that feeling when you're about to write the same prompt-engineering code for the third time? Yeah, that sucks. I spent way too long copy-pasting LLM API calls until I realized I could just... automate the whole thing.

Here's what I built and how you can steal my setup.

The Problem: Repetitive LLM Glue Code

Every project that uses Claude, GPT, or any LLM usually needs the same scaffolding:

  • Loading API keys from environment files (securely, hopefully)
  • Handling rate limits and retries
  • Streaming responses without blocking the UI
  • Parsing structured output from JSON-ish responses
  • Logging for debugging (because of course your prompts broke in production)

I was writing this by hand every single time. Different versions. Different bugs. Different mistakes.

The Solution: Build Once, Use Everywhere

I created a simple abstraction layer that handles all the boring stuff. Nothing fancy—just a utility module that wraps the API calls and gives me a clean interface.

Here's the gist:

// Load key once, reuse everywhere
const llm = new LLMClient({
  provider: 'claude',
  apiKey: process.env.ANTHROPIC_API_KEY,
  model: 'claude-3-5-sonnet-20241022',
  maxRetries: 3
});

// Simple method signature
const response = await llm.complete({
  prompt: 'Write a function that does X',
  temperature: 0.7,
  maxTokens: 1024
});

// Handles streaming, errors, and retries automatically
const stream = await llm.stream({
  prompt: 'Generate a blog post about...',
  onChunk: (chunk) => console.log(chunk)
});
Enter fullscreen mode Exit fullscreen mode

No more boilerplate. No more "did I handle the 429 error correctly?" anxiety.

Real Example: Turning Unstructured Notes into Tasks

I use this constantly for converting my messy voice notes into structured todo items. Here's the actual code:

const notes = `
  Remember to fix the login bug, also need to update docs
  and someone said we should add caching somewhere maybe
`;

const tasks = await llm.complete({
  prompt: `Convert these notes into actionable tasks:\\n${notes}`,
  jsonOutput: true,
  schema: {
    tasks: [
      {
        title: 'string',
        priority: 'high|medium|low',
        estimatedTime: 'number'
      }
    ]
  }
});

// Returns properly typed JSON without manual parsing
console.log(tasks.tasks[0].title); // No errors, it just works
Enter fullscreen mode Exit fullscreen mode

The jsonOutput flag tells the client to enforce structured responses, handle parsing errors, and retry if the LLM returns malformed JSON. You never see that mess.

Another Real One: Batch Processing with Proper Rate Limiting

Processing 500 customer feedback entries? Here's how you do it without getting rate limited:

const feedbackItems = await loadAllFeedback();

const processor = llm.createBatcher({
  batchSize: 10,
  delayMs: 1000, // Smart delay between batches
  parallelRequests: 3
});

const analyzed = await processor.batch(feedbackItems, async (item) => {
  return llm.complete({
    prompt: `Analyze this feedback and extract sentiment:\\n${item.text}`
  });
});

// Automatically handles retries, rate limits, failures
// Returns results in the same order as input
Enter fullscreen mode Exit fullscreen mode

No more "oops, I hit the rate limit on request 247" debugging sessions.

The Developer Quality of Life Stuff

What really sold me on this approach:

Error messages that actually help:

LLMError: API rate limited. Retrying request 2/3 in 2s...
Provider response was invalid JSON. Original: {"incomplete": true...
Context length exceeded. Prompt was 8500 tokens, limit is 8192.
Enter fullscreen mode Exit fullscreen mode

Automatic fallbacks:

const response = await llm.complete({
  prompt: userInput,
  fallback: 'Sorry, that was too complex. Try again?'
});
Enter fullscreen mode Exit fullscreen mode

If the LLM request fails after retries, you get your fallback instead of a crashed application.

Built-in caching (optional):

const cached = await llm.complete({
  prompt: 'What is the capital of France?',
  cache: true, // Caches for 1 hour by default
  cacheKeyPrefix: 'geography'
});
Enter fullscreen mode Exit fullscreen mode

Same prompt within the cache window? Instant response, no API call, no cost.

How to Get Started Right Now

  1. Pick your LLM provider (Claude, OpenAI, whatever)
  2. Create a utility module with a simple class that wraps their API
  3. Add error handling, retry logic, and rate limiting
  4. Use it everywhere instead of raw API calls

You don't need anything fancy. A solid 200-line module saves you hundreds of lines of boilerplate across all your projects.

The real win is consistency—same error handling, same logging, same rate limit behavior everywhere. When something breaks, you fix it once and it's fixed everywhere.

Quick Tip: Store Your API Keys Right

Use environment files with proper permissions:

# .env.local (never commit this)
ANTHROPIC_API_KEY=sk-ant-xxxx
OPENAI_API_KEY=sk-xxxx
Enter fullscreen mode Exit fullscreen mode

Load them on startup, never pass them in code. Your future self (and security team) will appreciate it.


Want to level up your AI workflow even more? Check out LearnAI Weekly newsletter—it's got practical tips for building with modern AI tools, roundups of what's new, and solutions to problems you'll actually run into.

Stop rebuilding the same utilities. Automate the automation. Ship faster.

Top comments (0)