Thanks for 1,000+ downloads! VernLLM just hit v1.0.0, and it comes with one breaking change worth knowing about if you're using a custom cache adapter, plus a handful of smaller improvements that landed alongside it.
What is VernLLM?
VernLLM is a lightweight resilience layer for LLM chat completions: retries, timeouts, caching, circuit breaking, structured output, and usage tracking. All behind one typed interface, with adapters for OpenAI-compatible providers (OpenAI, Groq, Mistral, DeepSeek, Cerebras, Together AI, Fireworks, Ollama), plus Anthropic, Gemini, and AWS Bedrock.
import OpenAI from 'openai';
import { VernLLM } from 'vern-llm';
const llm = new VernLLM({
client: new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
model: 'gpt-4o',
maxRetries: 3,
timeoutMs: 10_000,
circuitBreaker: true,
});
const result = await llm.call({
systemPrompt: 'Return JSON: { "skills": string[] }',
userContent: 'Extract skills from: ...',
});
Enter fullscreen mode Exit fullscreen mode
The breaking change: CacheAdapter.get()
Previously, CacheAdapter.get() returned Promise<T | null>. The problem: there was no way to tell the difference between "nothing is cached for this key" and "the cached value legitimately is null." Both looked identical, so a real null result got treated as a cache miss and silently re-triggered another LLM call every time.
In v1.0.0, get() now returns:
Promise<{ hit: boolean; value: T | null }>
Enter fullscreen mode Exit fullscreen mode
hit reflects whether the key existed in the underlying store, independent of what the value was. { hit: true, value: null } means "we have a cached result, and it's null." { hit: false, value: null } means "nothing is cached."
If you're using the built-in InMemoryCacheAdapter, you don't need to do anything, it's already updated.
If you've written a custom adapter (Redis, Upstash, etc.), you'll need to update get():
// Before
async get(key: string): Promise<MyValue | null> {
const raw = await redis.get(key);
return raw ? JSON.parse(raw) : null;
}
// After
async get(key: string): Promise<{ hit: boolean; value: MyValue | null }> {
const raw = await redis.get(key);
if (raw === null) {
return { hit: false, value: null };
}
return { hit: true, value: JSON.parse(raw) };
}
Enter fullscreen mode Exit fullscreen mode
Most stores already give you this signal for free — Redis's GET returning null for "key doesn't exist" is distinguishable from a null you stored yourself, so the fix is usually just an existence check rather than a value !== null check.
If you'd rather not bother with the distinction and are fine with null results never being served from cache, there's also a one-line shim:
return { hit: value !== null, value };
Enter fullscreen mode Exit fullscreen mode
Also in this release
-
CallParams.systemPromptis now optional — omit it and no system message is sent at all, instead of forcing an empty string through. -
AnthropicClient,GeminiClient, andBedrockConverseClientare now exported as public types, so you can type your own client instances against them without reaching into internals. - New
adaptersbarrel export —import { fromAnthropic, fromGemini, fromBedrock, fromFetch, fromOpenAICompatible } from 'vern-llm/adapters'instead of digging through individual files. - In-memory cache now has a size limit to prevent unbounded growth, evicting the oldest entries once the limit is hit.
- Internal types got refactored into focused modules, and test coverage was extended for optional system prompts, adapter payloads, and cache adapter edge cases (custom adapters, size bounds, failure handling).
Upgrading
npm install vern-llm@latest
Enter fullscreen mode Exit fullscreen mode
If you're only using the built-in InMemoryCacheAdapter (the default), this is a drop-in upgrade. If you've implemented a custom CacheAdapter, update its get() method per the migration guide above before upgrading in production.
Full changelog and docs: vernllm.vercel.app · GitHub
Thanks for using VernLLM, and for the 1,000+ downloads. If you hit any issues with the migration, open an issue on GitHub.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.