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)
});
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
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
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.
Automatic fallbacks:
const response = await llm.complete({
prompt: userInput,
fallback: 'Sorry, that was too complex. Try again?'
});
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'
});
Same prompt within the cache window? Instant response, no API call, no cost.
How to Get Started Right Now
- Pick your LLM provider (Claude, OpenAI, whatever)
- Create a utility module with a simple class that wraps their API
- Add error handling, retry logic, and rate limiting
- 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
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)