Skip to content

Commit 953f883

Browse files
refactor: restructure codebase in lib folder with index at root (#23)
* refactor: restructure codebase in lib folder with index at root * chore: script for sanity test example with latesst local lib code * fix: remove old packages for example test tot work * refactor: cleanup example html
1 parent 4da0067 commit 953f883

21 files changed

+253
-21
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ResilientLLM
1+
# Resilient LLM
22
[![npm version](https://img.shields.io/npm/v/resilient-llm.svg)](https://www.npmjs.com/package/resilient-llm) [![license](https://img.shields.io/npm/l/resilient-llm.svg)](LICENSE)
33

44
A minimalist but robust LLM integration layer designed to ensure reliable, seamless interactions across multiple LLM providers by intelligently handling failures and rate limits.
@@ -8,9 +8,11 @@ A minimalist but robust LLM integration layer designed to ensure reliable, seaml
88
ResilientLLM makes your AI Agents or LLM apps production-ready by dealing with challenges such as:
99

1010
- ❌ Unstable network conditions
11-
- ⚠️ Inconsistent error handling
11+
- ⚠️ Inconsistent errors
1212
- ⏳ Unpredictable LLM API rate limit errors
1313

14+
Check out [examples](./examples/), ready to ship.
15+
1416
### Key Features
1517

1618
- **Token Estimation**: You don’t need to calculate LLM tokens, they are estimated for each request
@@ -26,7 +28,7 @@ npm i resilient-llm
2628
## Quickstart
2729

2830
```javascript
29-
import ResilientLLM from 'resilient-llm';
31+
import { ResilientLLM } from 'resilient-llm';
3032

3133
const llm = new ResilientLLM({
3234
aiService: 'openai', // or 'anthropic', 'gemini', 'ollama'
@@ -56,6 +58,12 @@ const conversationHistory = [
5658
})();
5759
```
5860

61+
## Examples and playground
62+
63+
Complete working projects using Resilient LLM as core library to call LLM APIs with resilience.
64+
65+
- [Minimal AI Chat](./examples/chat-basic/)
66+
5967
## Motivation
6068

6169
ResilientLLM is a resilient, unified LLM interface featuring circuit breaker, token bucket rate limiting, caching, and adaptive retry with dynamic backoff support.

examples/chat-basic/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ Uses `ResilientLLM` in `server/app.js` as following:
9090

9191
**1. Initialize ResilientLLM:**
9292
```javascript
93+
import { ResilientLLM } from 'resilient-llm';
94+
9395
const llm = new ResilientLLM({
9496
aiService: process.env.AI_SERVICE || 'openai',
9597
model: process.env.AI_MODEL || 'gpt-4o-mini',

examples/chat-basic/client/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@
4040
4141
</button>
4242
</div>
43+
44+
<div class="library-footer" id="libraryFooter">
45+
<span class="library-footer-text">
46+
Made with <a href="https://github.com/gitcommitshow/resilient-llm" target="_blank" rel="noopener noreferrer" class="library-name-link"><strong>ResilientLLM</strong></a> <span id="libraryVersion">v1.1.0</span>
47+
<a href="#" id="librarySourceLink" class="library-source-link" target="_blank" rel="noopener noreferrer">
48+
<span id="librarySource">npm</span>
49+
</a>
50+
</span>
51+
</div>
4352
</div>
4453

4554
<!-- Markdown rendering library -->

examples/chat-basic/client/styles.css

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,69 @@ body {
2424
overflow: hidden;
2525
}
2626

27+
.library-footer {
28+
background: white;
29+
padding: 12px 20px;
30+
border-top: 1px solid #e4e4e7;
31+
font-size: 12px;
32+
color: #71717a;
33+
text-align: center;
34+
flex-shrink: 0;
35+
}
36+
37+
.library-footer-text {
38+
display: inline-flex;
39+
align-items: center;
40+
gap: 4px;
41+
}
42+
43+
.library-footer-text strong {
44+
color: #09090b;
45+
font-weight: 600;
46+
}
47+
48+
.library-name-link {
49+
color: #09090b;
50+
text-decoration: none;
51+
transition: color 0.2s;
52+
}
53+
54+
.library-name-link:hover {
55+
color: #71717a;
56+
text-decoration: underline;
57+
}
58+
59+
.library-footer-text #libraryVersion {
60+
color: #71717a;
61+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
62+
margin: 0 4px;
63+
}
64+
65+
.library-source-link {
66+
color: #71717a;
67+
text-decoration: none;
68+
margin-left: 4px;
69+
transition: color 0.2s;
70+
}
71+
72+
.library-source-link:hover {
73+
color: #09090b;
74+
text-decoration: underline;
75+
}
76+
77+
.library-source {
78+
color: #71717a;
79+
}
80+
81+
.library-source.local {
82+
color: #059669;
83+
font-weight: 500;
84+
}
85+
86+
.library-source.npm {
87+
color: #71717a;
88+
}
89+
2790
.chat-header {
2891
background: white;
2992
color: #09090b;

examples/chat-basic/client/ui.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,43 @@ function autoResizeTextarea(messageInput) {
5454
messageInput.style.height = messageInput.scrollHeight + 'px';
5555
}
5656

57+
/**
58+
* Load and display library info (version and source)
59+
*/
60+
async function loadLibraryInfo() {
61+
try {
62+
const response = await fetch('/api/library-info');
63+
const info = await response.json();
64+
65+
const versionEl = document.getElementById('libraryVersion');
66+
const sourceEl = document.getElementById('librarySource');
67+
const sourceLinkEl = document.getElementById('librarySourceLink');
68+
69+
if (versionEl) {
70+
versionEl.textContent = `v${info.version}`;
71+
}
72+
73+
if (sourceEl) {
74+
sourceEl.textContent = info.source;
75+
sourceEl.className = `library-source ${info.source}`;
76+
}
77+
78+
if (sourceLinkEl && info.sourcePath) {
79+
sourceLinkEl.href = info.sourcePath;
80+
if (info.source === 'local') {
81+
sourceLinkEl.removeAttribute('target');
82+
sourceLinkEl.removeAttribute('rel');
83+
}
84+
}
85+
} catch (error) {
86+
console.error('Error loading library info:', error);
87+
}
88+
}
89+
90+
// Load library info on page load
91+
if (document.readyState === 'loading') {
92+
document.addEventListener('DOMContentLoaded', loadLibraryInfo);
93+
} else {
94+
loadLibraryInfo();
95+
}
96+

examples/chat-basic/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/chat-basic/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"dependencies": {
1212
"express": "^4.18.2",
13-
"resilient-llm": "^1.0.0"
13+
"resilient-llm": "^1.1.0"
1414
},
1515
"devDependencies": {
1616
"nodemon": "^3.0.2"
@@ -19,4 +19,3 @@
1919
"node": ">=20.0.0"
2020
}
2121
}
22-

examples/chat-basic/server/app.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import express from 'express';
22
import { fileURLToPath } from 'url';
33
import { dirname, join } from 'path';
44
import { ResilientLLM } from 'resilient-llm';
5+
import { getLibraryInfo } from './devutility.js';
56

67
const __filename = fileURLToPath(import.meta.url);
78
const __dirname = dirname(__filename);
@@ -60,11 +61,15 @@ app.get('/api/health', (req, res) => {
6061
res.json({ status: 'ok' });
6162
});
6263

64+
// Library info endpoint
65+
app.get('/api/library-info', (req, res) => {
66+
res.json(getLibraryInfo());
67+
});
68+
6369
app.listen(PORT, () => {
6470
console.log(`Server running on http://localhost:${PORT}`);
6571
console.log(`Make sure to set your API key in environment variables:`);
6672
console.log(` - OPENAI_API_KEY (for OpenAI)`);
6773
console.log(` - ANTHROPIC_API_KEY (for Anthropic)`);
6874
console.log(` - GEMINI_API_KEY (for Gemini)`);
69-
});
70-
75+
});
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { readFileSync, realpathSync, readlinkSync, lstatSync } from 'fs';
2+
import { createRequire } from 'module';
3+
import { dirname, resolve } from 'path';
4+
5+
/**
6+
* Get library version and source info
7+
* @returns {Object} { version: string, source: 'local'|'npm', sourcePath: string }
8+
*/
9+
export function getLibraryInfo() {
10+
try {
11+
const require = createRequire(import.meta.url);
12+
let packagePath = require.resolve('resilient-llm/package.json');
13+
14+
// Simple check: use env var if set, otherwise check if path contains node_modules
15+
const isLocal = process.env.RESILIENT_LLM_SOURCE === 'local' ||
16+
!packagePath.includes('node_modules');
17+
18+
// For local installs, ALWAYS resolve the symlink to get the actual source package.json
19+
// This ensures we read the latest version from the source, not a cached copy
20+
if (isLocal) {
21+
try {
22+
packagePath = realpathSync(packagePath);
23+
} catch (error) {
24+
// If realpath fails, try to resolve the symlink manually
25+
const packageDir = dirname(packagePath);
26+
try {
27+
const stats = lstatSync(packageDir);
28+
if (stats.isSymbolicLink()) {
29+
const symlinkTarget = readlinkSync(packageDir);
30+
const resolvedTarget = symlinkTarget.startsWith('/')
31+
? symlinkTarget
32+
: resolve(packageDir, symlinkTarget);
33+
packagePath = resolve(resolvedTarget, 'package.json');
34+
}
35+
} catch {
36+
// Keep original path
37+
}
38+
}
39+
}
40+
41+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
42+
const version = packageJson.version;
43+
44+
// Get source path for link
45+
let sourcePath = 'https://www.npmjs.com/package/resilient-llm';
46+
if (isLocal) {
47+
// For local, resolve the actual path (follow symlinks) to get the real source folder
48+
try {
49+
const packageDir = dirname(packagePath);
50+
51+
// Try to resolve the symlink chain
52+
// realpathSync follows all symlinks to the final destination
53+
const realPath = realpathSync(packagePath);
54+
const libDir = dirname(realPath);
55+
56+
// If the resolved path is still in node_modules, it means the symlink
57+
// points to another location. Let's check the symlink directly.
58+
if (libDir.includes('node_modules')) {
59+
// Check if packageDir itself is a symlink
60+
try {
61+
const stats = lstatSync(packageDir);
62+
if (stats.isSymbolicLink()) {
63+
const symlinkTarget = readlinkSync(packageDir);
64+
// Resolve the symlink target
65+
const resolvedTarget = symlinkTarget.startsWith('/')
66+
? symlinkTarget
67+
: resolve(packageDir, symlinkTarget);
68+
sourcePath = `file://${resolvedTarget}`;
69+
} else {
70+
// Not a direct symlink, but realpath resolved to node_modules
71+
// This shouldn't happen for local installs, but use the resolved path
72+
sourcePath = `file://${libDir}`;
73+
}
74+
} catch {
75+
sourcePath = `file://${libDir}`;
76+
}
77+
} else {
78+
// Resolved to a path outside node_modules - this is the source!
79+
sourcePath = `file://${libDir}`;
80+
}
81+
} catch (error) {
82+
// Fallback
83+
const libDir = dirname(packagePath);
84+
sourcePath = `file://${libDir}`;
85+
}
86+
}
87+
88+
return {
89+
version,
90+
source: isLocal ? 'local' : 'npm',
91+
sourcePath
92+
};
93+
} catch (error) {
94+
return {
95+
version: 'unknown',
96+
source: 'unknown',
97+
sourcePath: '#'
98+
};
99+
}
100+
}
101+

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
import ResilientLLM from "./ResilientLLM.js";
1+
import ResilientLLM from "./lib/ResilientLLM.js";
22

33
export { ResilientLLM };

0 commit comments

Comments
 (0)