llm-assert 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +335 -0
  3. package/dist/cache/cache-manager.d.ts +15 -0
  4. package/dist/cache/cache-manager.d.ts.map +1 -0
  5. package/dist/cache/cache-manager.js +39 -0
  6. package/dist/cache/cache-manager.js.map +1 -0
  7. package/dist/cache/file-cache.d.ts +13 -0
  8. package/dist/cache/file-cache.d.ts.map +1 -0
  9. package/dist/cache/file-cache.js +90 -0
  10. package/dist/cache/file-cache.js.map +1 -0
  11. package/dist/config/config-loader.d.ts +6 -0
  12. package/dist/config/config-loader.d.ts.map +1 -0
  13. package/dist/config/config-loader.js +46 -0
  14. package/dist/config/config-loader.js.map +1 -0
  15. package/dist/config/defaults.d.ts +15 -0
  16. package/dist/config/defaults.d.ts.map +1 -0
  17. package/dist/config/defaults.js +15 -0
  18. package/dist/config/defaults.js.map +1 -0
  19. package/dist/core/assertion-result.d.ts +26 -0
  20. package/dist/core/assertion-result.d.ts.map +1 -0
  21. package/dist/core/assertion-result.js +38 -0
  22. package/dist/core/assertion-result.js.map +1 -0
  23. package/dist/core/assertion-runner.d.ts +29 -0
  24. package/dist/core/assertion-runner.d.ts.map +1 -0
  25. package/dist/core/assertion-runner.js +82 -0
  26. package/dist/core/assertion-runner.js.map +1 -0
  27. package/dist/core/llm-expect.d.ts +16 -0
  28. package/dist/core/llm-expect.d.ts.map +1 -0
  29. package/dist/core/llm-expect.js +111 -0
  30. package/dist/core/llm-expect.js.map +1 -0
  31. package/dist/index.d.ts +11 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +20 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/integrations/jest-matchers.d.ts +18 -0
  36. package/dist/integrations/jest-matchers.d.ts.map +1 -0
  37. package/dist/integrations/jest-matchers.js +54 -0
  38. package/dist/integrations/jest-matchers.js.map +1 -0
  39. package/dist/integrations/vitest-matchers.d.ts +2 -0
  40. package/dist/integrations/vitest-matchers.d.ts.map +1 -0
  41. package/dist/integrations/vitest-matchers.js +54 -0
  42. package/dist/integrations/vitest-matchers.js.map +1 -0
  43. package/dist/matchers/factual.d.ts +2 -0
  44. package/dist/matchers/factual.d.ts.map +1 -0
  45. package/dist/matchers/factual.js +26 -0
  46. package/dist/matchers/factual.js.map +1 -0
  47. package/dist/matchers/hallucination.d.ts +2 -0
  48. package/dist/matchers/hallucination.d.ts.map +1 -0
  49. package/dist/matchers/hallucination.js +28 -0
  50. package/dist/matchers/hallucination.js.map +1 -0
  51. package/dist/matchers/index.d.ts +8 -0
  52. package/dist/matchers/index.d.ts.map +1 -0
  53. package/dist/matchers/index.js +18 -0
  54. package/dist/matchers/index.js.map +1 -0
  55. package/dist/matchers/relevance.d.ts +2 -0
  56. package/dist/matchers/relevance.d.ts.map +1 -0
  57. package/dist/matchers/relevance.js +23 -0
  58. package/dist/matchers/relevance.js.map +1 -0
  59. package/dist/matchers/safety.d.ts +2 -0
  60. package/dist/matchers/safety.d.ts.map +1 -0
  61. package/dist/matchers/safety.js +25 -0
  62. package/dist/matchers/safety.js.map +1 -0
  63. package/dist/matchers/satisfy.d.ts +2 -0
  64. package/dist/matchers/satisfy.d.ts.map +1 -0
  65. package/dist/matchers/satisfy.js +23 -0
  66. package/dist/matchers/satisfy.js.map +1 -0
  67. package/dist/matchers/sentiment.d.ts +2 -0
  68. package/dist/matchers/sentiment.d.ts.map +1 -0
  69. package/dist/matchers/sentiment.js +24 -0
  70. package/dist/matchers/sentiment.js.map +1 -0
  71. package/dist/matchers/tone.d.ts +2 -0
  72. package/dist/matchers/tone.d.ts.map +1 -0
  73. package/dist/matchers/tone.js +24 -0
  74. package/dist/matchers/tone.js.map +1 -0
  75. package/dist/providers/anthropic-provider.d.ts +8 -0
  76. package/dist/providers/anthropic-provider.d.ts.map +1 -0
  77. package/dist/providers/anthropic-provider.js +100 -0
  78. package/dist/providers/anthropic-provider.js.map +1 -0
  79. package/dist/providers/base-provider.d.ts +23 -0
  80. package/dist/providers/base-provider.d.ts.map +1 -0
  81. package/dist/providers/base-provider.js +22 -0
  82. package/dist/providers/base-provider.js.map +1 -0
  83. package/dist/providers/index.d.ts +4 -0
  84. package/dist/providers/index.d.ts.map +1 -0
  85. package/dist/providers/index.js +23 -0
  86. package/dist/providers/index.js.map +1 -0
  87. package/dist/providers/ollama-provider.d.ts +9 -0
  88. package/dist/providers/ollama-provider.d.ts.map +1 -0
  89. package/dist/providers/ollama-provider.js +67 -0
  90. package/dist/providers/ollama-provider.js.map +1 -0
  91. package/dist/providers/openai-provider.d.ts +8 -0
  92. package/dist/providers/openai-provider.d.ts.map +1 -0
  93. package/dist/providers/openai-provider.js +106 -0
  94. package/dist/providers/openai-provider.js.map +1 -0
  95. package/dist/utils/hash.d.ts +2 -0
  96. package/dist/utils/hash.d.ts.map +1 -0
  97. package/dist/utils/hash.js +12 -0
  98. package/dist/utils/hash.js.map +1 -0
  99. package/dist/utils/logger.d.ts +6 -0
  100. package/dist/utils/logger.d.ts.map +1 -0
  101. package/dist/utils/logger.js +18 -0
  102. package/dist/utils/logger.js.map +1 -0
  103. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 llm-assert contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,335 @@
1
+ # llm-assert
2
+
3
+ > Semantic AI assertions for Jest and Vitest. Test LLM outputs with `expect()`-style syntax.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/llm-assert.svg)](https://www.npmjs.com/package/llm-assert)
6
+ [![license](https://img.shields.io/npm/l/llm-assert.svg)](https://github.com/llm-assert/llm-assert/blob/main/LICENSE)
7
+
8
+ ```typescript
9
+ import { llmExpect } from "llm-assert";
10
+
11
+ test("customer service reply is relevant and professional", async () => {
12
+ const reply = await getAIResponse("How do I get a refund?");
13
+ await llmExpect(reply).toBeRelevantTo("refund policy");
14
+ await llmExpect(reply).toMatchTone("professional and empathetic");
15
+ await llmExpect(reply).not.toContainHallucination({ context: refundDocs });
16
+ });
17
+ ```
18
+
19
+ ## Why llm-assert?
20
+
21
+ Testing AI outputs is hard. You can't use `expect(output).toBe("exact string")` when the response varies every time. Existing tools are either Python-only, platform-specific, or require a separate CLI workflow.
22
+
23
+ **llm-assert** drops into your existing Jest or Vitest test suite with zero friction:
24
+
25
+ - **Familiar API** -- if you know `expect().toBe()`, you know `llmExpect().toBeRelevantTo()`
26
+ - **Zero config to start** -- set `OPENAI_API_KEY` and go
27
+ - **No vendor lock-in** -- supports OpenAI, Anthropic, and free local models via Ollama
28
+ - **Cost-aware** -- built-in caching so you don't burn API credits on every test run
29
+ - **Production-ready** -- clear error messages, timeout handling, per-assertion overrides
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install llm-assert --save-dev
35
+ ```
36
+
37
+ Set your API key:
38
+
39
+ ```bash
40
+ export OPENAI_API_KEY=sk-...
41
+ ```
42
+
43
+ Or use Ollama for free local inference (no API key needed):
44
+
45
+ ```bash
46
+ # Install Ollama from https://ollama.ai, then:
47
+ ollama pull llama3
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ `llmExpect()` works with any test runner. It throws on failure, which Jest/Vitest catch automatically:
53
+
54
+ ```typescript
55
+ import { llmExpect } from "llm-assert";
56
+
57
+ test("AI summary is relevant", async () => {
58
+ const summary = await myAI.summarize(article);
59
+ await llmExpect(summary).toBeRelevantTo("climate change");
60
+ });
61
+ ```
62
+
63
+ No setup file needed. No custom matchers to register. Just import and use.
64
+
65
+ ## All Assertions
66
+
67
+ ### `toBeRelevantTo(topic: string)`
68
+
69
+ Checks if the text is semantically relevant to a topic.
70
+
71
+ ```typescript
72
+ await llmExpect("We process refunds within 5-7 days.").toBeRelevantTo("refund policy");
73
+ // PASSES
74
+
75
+ await llmExpect("Our office is in New York.").toBeRelevantTo("refund policy");
76
+ // FAILS -- not relevant
77
+ ```
78
+
79
+ ### `toMatchTone(tone: string)`
80
+
81
+ Checks if the text matches an expected tone.
82
+
83
+ ```typescript
84
+ await llmExpect("Dear customer, we sincerely apologize.").toMatchTone("professional, empathetic");
85
+ // PASSES
86
+
87
+ await llmExpect("lol idk just deal with it").toMatchTone("professional");
88
+ // FAILS
89
+ ```
90
+
91
+ ### `toContainHallucination(options?)`
92
+
93
+ Checks for fabricated information. Typically used with `.not`:
94
+
95
+ ```typescript
96
+ const context = "Our company was founded in 2015 in Austin, Texas.";
97
+ const response = "The company was founded in 2015 in Austin, Texas by John Smith.";
98
+
99
+ await llmExpect(response).not.toContainHallucination({ context });
100
+ // FAILS -- "John Smith" is hallucinated
101
+ ```
102
+
103
+ ### `toBeFactuallyCorrect(options?)`
104
+
105
+ Checks factual accuracy against context or general knowledge.
106
+
107
+ ```typescript
108
+ await llmExpect("Water boils at 100°C at sea level.").toBeFactuallyCorrect();
109
+ // PASSES
110
+ ```
111
+
112
+ ### `toSatisfy(criteria: string)`
113
+
114
+ The flexible escape hatch. Pass any natural language criteria:
115
+
116
+ ```typescript
117
+ await llmExpect(response).toSatisfy("contains a call-to-action and mentions pricing");
118
+ await llmExpect(response).toSatisfy("is written in valid markdown format");
119
+ await llmExpect(response).toSatisfy("answers the question without being condescending");
120
+ ```
121
+
122
+ ### `toHaveSentiment(sentiment: string)`
123
+
124
+ Checks the emotional sentiment.
125
+
126
+ ```typescript
127
+ await llmExpect("This product is amazing!").toHaveSentiment("positive");
128
+ await llmExpect("I'm very disappointed.").toHaveSentiment("negative");
129
+ ```
130
+
131
+ ### `toBeSafe(options?)`
132
+
133
+ Checks for harmful content (hate speech, PII leakage, prompt injection, etc.):
134
+
135
+ ```typescript
136
+ await llmExpect(response).toBeSafe();
137
+
138
+ // With custom categories:
139
+ await llmExpect(response).toBeSafe({
140
+ categories: ["pii_leakage", "prompt_injection"],
141
+ });
142
+ ```
143
+
144
+ ## Negation
145
+
146
+ All matchers support `.not`:
147
+
148
+ ```typescript
149
+ await llmExpect(response).not.toContainHallucination({ context });
150
+ await llmExpect(response).not.toMatchTone("aggressive");
151
+ ```
152
+
153
+ ## Configuration
154
+
155
+ ### Programmatic (recommended for test setups)
156
+
157
+ ```typescript
158
+ // jest.setup.ts or vitest.setup.ts
159
+ import { configureLLMAssert } from "llm-assert";
160
+
161
+ configureLLMAssert({
162
+ provider: "openai", // "openai" | "anthropic" | "ollama"
163
+ model: "gpt-4o-mini",
164
+ cache: true,
165
+ verbose: false,
166
+ });
167
+ ```
168
+
169
+ ### Environment variables
170
+
171
+ ```bash
172
+ LLM_ASSERT_PROVIDER=openai
173
+ LLM_ASSERT_MODEL=gpt-4o-mini
174
+ OPENAI_API_KEY=sk-...
175
+ ANTHROPIC_API_KEY=sk-ant-...
176
+ LLM_ASSERT_CACHE=true
177
+ LLM_ASSERT_VERBOSE=false
178
+ ```
179
+
180
+ ### Per-assertion overrides
181
+
182
+ Every matcher accepts an options object:
183
+
184
+ ```typescript
185
+ await llmExpect(text).toBeRelevantTo("topic", {
186
+ threshold: 0.9, // stricter than default 0.7
187
+ model: "gpt-4o", // use a better model for this check
188
+ provider: "anthropic", // use a different provider
189
+ timeout: 60000, // longer timeout
190
+ cache: false, // skip cache for this assertion
191
+ });
192
+ ```
193
+
194
+ ## Providers
195
+
196
+ ### OpenAI (default)
197
+
198
+ Uses `gpt-4o-mini` by default. Set `OPENAI_API_KEY` or pass via config.
199
+
200
+ ```typescript
201
+ configureLLMAssert({ provider: "openai", model: "gpt-4o" });
202
+ ```
203
+
204
+ ### Anthropic
205
+
206
+ Uses `claude-sonnet-4-20250514` by default. Set `ANTHROPIC_API_KEY`.
207
+
208
+ ```typescript
209
+ configureLLMAssert({ provider: "anthropic" });
210
+ ```
211
+
212
+ ### Ollama (free, local)
213
+
214
+ No API key needed. Requires [Ollama](https://ollama.ai) running locally.
215
+
216
+ ```typescript
217
+ configureLLMAssert({
218
+ provider: "ollama",
219
+ model: "llama3",
220
+ ollamaBaseUrl: "http://localhost:11434", // default
221
+ });
222
+ ```
223
+
224
+ ## Caching
225
+
226
+ By default, `llm-assert` caches LLM responses to avoid redundant API calls. Cache is stored in `.llm-assert-cache/` and keyed on: assertion type + input text + criteria + model name.
227
+
228
+ Add `.llm-assert-cache/` to your `.gitignore`.
229
+
230
+ Cache entries expire after 7 days. Clear manually:
231
+
232
+ ```typescript
233
+ import { clearLLMAssertCache } from "llm-assert";
234
+ await clearLLMAssertCache();
235
+ ```
236
+
237
+ ## Jest Integration
238
+
239
+ ### Option 1: Use `llmExpect()` directly (no setup needed)
240
+
241
+ ```typescript
242
+ import { llmExpect } from "llm-assert";
243
+
244
+ test("AI response quality", async () => {
245
+ await llmExpect(response).toBeRelevantTo("topic");
246
+ });
247
+ ```
248
+
249
+ ### Option 2: Extend Jest's `expect` with `toLLMMatch`
250
+
251
+ ```typescript
252
+ // jest.setup.ts
253
+ import "llm-assert/jest";
254
+ ```
255
+
256
+ ```javascript
257
+ // jest.config.js
258
+ module.exports = {
259
+ setupFilesAfterSetup: ["./jest.setup.ts"],
260
+ };
261
+ ```
262
+
263
+ ```typescript
264
+ // In your tests:
265
+ await expect(response).toLLMMatch({
266
+ relevantTo: "refund policy",
267
+ tone: "professional",
268
+ });
269
+ ```
270
+
271
+ ## Vitest Integration
272
+
273
+ ```typescript
274
+ // vitest.setup.ts
275
+ import "llm-assert/vitest";
276
+ ```
277
+
278
+ ## Error Messages
279
+
280
+ Failed assertions produce clear, actionable errors:
281
+
282
+ ```
283
+ LLMAssertionError: toBeRelevantTo
284
+
285
+ Expected: text to be relevant to "refund policy"
286
+ Received: "Our office hours are 9am to 5pm Monday through Friday."
287
+
288
+ Score: 0.15 / 0.70 (threshold)
289
+ Reasoning: The text discusses office hours, which is unrelated to
290
+ refund policies, return processes, or money-back guarantees.
291
+
292
+ Provider: openai (gpt-4o-mini)
293
+ Cached: false
294
+ ```
295
+
296
+ ## API Reference
297
+
298
+ ### Functions
299
+
300
+ | Function | Description |
301
+ |----------|-------------|
302
+ | `llmExpect(text)` | Create a semantic assertion on text |
303
+ | `configureLLMAssert(config)` | Set global configuration |
304
+ | `defineConfig(config)` | Helper for config files |
305
+ | `clearLLMAssertCache()` | Clear all cached results |
306
+
307
+ ### Types
308
+
309
+ ```typescript
310
+ interface LLMAssertConfig {
311
+ provider: string; // "openai" | "anthropic" | "ollama"
312
+ model: string; // model name
313
+ apiKey?: string; // API key
314
+ ollamaBaseUrl?: string; // Ollama URL (default: http://localhost:11434)
315
+ defaultThreshold: number; // global threshold (default: 0.7)
316
+ timeout: number; // ms (default: 30000)
317
+ maxRetries: number; // retry count (default: 2)
318
+ cache: boolean; // enable caching (default: true)
319
+ cacheDir: string; // cache directory (default: .llm-assert-cache)
320
+ cacheTTL: number; // cache TTL in ms (default: 7 days)
321
+ verbose: boolean; // verbose logging (default: false)
322
+ }
323
+
324
+ interface AssertionOptions {
325
+ threshold?: number;
326
+ model?: string;
327
+ provider?: string;
328
+ cache?: boolean;
329
+ timeout?: number;
330
+ }
331
+ ```
332
+
333
+ ## License
334
+
335
+ MIT
@@ -0,0 +1,15 @@
1
+ export declare class CacheManager {
2
+ private fileCache;
3
+ constructor();
4
+ get(assertionType: string, actual: string, criteria: string, model: string): Promise<{
5
+ score: number;
6
+ pass: boolean;
7
+ reasoning: string;
8
+ [key: string]: unknown;
9
+ } | null>;
10
+ set(assertionType: string, actual: string, criteria: string, model: string, data: Record<string, unknown>): Promise<void>;
11
+ clear(): Promise<void>;
12
+ }
13
+ export declare function getCacheManager(): CacheManager;
14
+ export declare function clearLLMAssertCache(): Promise<void>;
15
+ //# sourceMappingURL=cache-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.d.ts","sourceRoot":"","sources":["../../src/cache/cache-manager.ts"],"names":[],"mappings":"AAKA,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAAY;;IAMvB,GAAG,CACP,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAOxF,GAAG,CACP,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC;IAKV,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED,wBAAgB,eAAe,IAAI,YAAY,CAK9C;AAED,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAGzD"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CacheManager = void 0;
4
+ exports.getCacheManager = getCacheManager;
5
+ exports.clearLLMAssertCache = clearLLMAssertCache;
6
+ const file_cache_1 = require("./file-cache");
7
+ const hash_1 = require("../utils/hash");
8
+ let cacheInstance = null;
9
+ class CacheManager {
10
+ constructor() {
11
+ this.fileCache = new file_cache_1.FileCache();
12
+ }
13
+ async get(assertionType, actual, criteria, model) {
14
+ const key = (0, hash_1.computeHash)(assertionType, actual, criteria, model);
15
+ const entry = await this.fileCache.get(key);
16
+ if (!entry)
17
+ return null;
18
+ return entry.data;
19
+ }
20
+ async set(assertionType, actual, criteria, model, data) {
21
+ const key = (0, hash_1.computeHash)(assertionType, actual, criteria, model);
22
+ await this.fileCache.set(key, data);
23
+ }
24
+ async clear() {
25
+ await this.fileCache.clear();
26
+ }
27
+ }
28
+ exports.CacheManager = CacheManager;
29
+ function getCacheManager() {
30
+ if (!cacheInstance) {
31
+ cacheInstance = new CacheManager();
32
+ }
33
+ return cacheInstance;
34
+ }
35
+ async function clearLLMAssertCache() {
36
+ const cache = getCacheManager();
37
+ await cache.clear();
38
+ }
39
+ //# sourceMappingURL=cache-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-manager.js","sourceRoot":"","sources":["../../src/cache/cache-manager.ts"],"names":[],"mappings":";;;AAwCA,0CAKC;AAED,kDAGC;AAlDD,6CAAyC;AACzC,wCAA4C;AAE5C,IAAI,aAAa,GAAwB,IAAI,CAAC;AAE9C,MAAa,YAAY;IAGvB;QACE,IAAI,CAAC,SAAS,GAAG,IAAI,sBAAS,EAAE,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,GAAG,CACP,aAAqB,EACrB,MAAc,EACd,QAAgB,EAChB,KAAa;QAEb,MAAM,GAAG,GAAG,IAAA,kBAAW,EAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO,KAAK,CAAC,IAAmF,CAAC;IACnG,CAAC;IAED,KAAK,CAAC,GAAG,CACP,aAAqB,EACrB,MAAc,EACd,QAAgB,EAChB,KAAa,EACb,IAA6B;QAE7B,MAAM,GAAG,GAAG,IAAA,kBAAW,EAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAChE,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;CACF;AAjCD,oCAiCC;AAED,SAAgB,eAAe;IAC7B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,aAAa,GAAG,IAAI,YAAY,EAAE,CAAC;IACrC,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAEM,KAAK,UAAU,mBAAmB;IACvC,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;AACtB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface CacheEntry {
2
+ timestamp: number;
3
+ data: Record<string, unknown>;
4
+ }
5
+ export declare class FileCache {
6
+ private getDir;
7
+ private ensureDir;
8
+ private filePath;
9
+ get(key: string): Promise<CacheEntry | null>;
10
+ set(key: string, data: Record<string, unknown>): Promise<void>;
11
+ clear(): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=file-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-cache.d.ts","sourceRoot":"","sources":["../../src/cache/file-cache.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,QAAQ;IAIV,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAoB5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAW7B"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FileCache = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const config_loader_1 = require("../config/config-loader");
40
+ class FileCache {
41
+ getDir() {
42
+ return path.resolve((0, config_loader_1.getConfig)().cacheDir);
43
+ }
44
+ ensureDir() {
45
+ const dir = this.getDir();
46
+ if (!fs.existsSync(dir)) {
47
+ fs.mkdirSync(dir, { recursive: true });
48
+ }
49
+ }
50
+ filePath(key) {
51
+ return path.join(this.getDir(), `${key}.json`);
52
+ }
53
+ async get(key) {
54
+ const fp = this.filePath(key);
55
+ try {
56
+ if (!fs.existsSync(fp))
57
+ return null;
58
+ const raw = fs.readFileSync(fp, 'utf-8');
59
+ const entry = JSON.parse(raw);
60
+ // Check TTL
61
+ const ttl = (0, config_loader_1.getConfig)().cacheTTL;
62
+ if (Date.now() - entry.timestamp > ttl) {
63
+ fs.unlinkSync(fp);
64
+ return null;
65
+ }
66
+ return entry;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ async set(key, data) {
73
+ this.ensureDir();
74
+ const entry = { timestamp: Date.now(), data };
75
+ fs.writeFileSync(this.filePath(key), JSON.stringify(entry, null, 2));
76
+ }
77
+ async clear() {
78
+ const dir = this.getDir();
79
+ if (fs.existsSync(dir)) {
80
+ const files = fs.readdirSync(dir);
81
+ for (const file of files) {
82
+ if (file.endsWith('.json')) {
83
+ fs.unlinkSync(path.join(dir, file));
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ exports.FileCache = FileCache;
90
+ //# sourceMappingURL=file-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-cache.js","sourceRoot":"","sources":["../../src/cache/file-cache.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAC7B,2DAAoD;AAOpD,MAAa,SAAS;IACZ,MAAM;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,IAAA,yBAAS,GAAE,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAEO,SAAS;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,GAAW;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAC;YACpC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACzC,MAAM,KAAK,GAAe,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAE1C,YAAY;YACZ,MAAM,GAAG,GAAG,IAAA,yBAAS,GAAE,CAAC,QAAQ,CAAC;YACjC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;gBACvC,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBAClB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,IAA6B;QAClD,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,MAAM,KAAK,GAAe,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YAClC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3B,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF;AArDD,8BAqDC"}
@@ -0,0 +1,6 @@
1
+ import { LLMAssertConfig } from './defaults';
2
+ export declare function configureLLMAssert(config: Partial<LLMAssertConfig>): void;
3
+ export declare function defineConfig(config: Partial<LLMAssertConfig>): Partial<LLMAssertConfig>;
4
+ export declare function getConfig(): LLMAssertConfig;
5
+ export declare function resetConfig(): void;
6
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAI7D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAEzE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAEvF;AAED,wBAAgB,SAAS,IAAI,eAAe,CAQ3C;AAED,wBAAgB,WAAW,IAAI,IAAI,CAElC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configureLLMAssert = configureLLMAssert;
4
+ exports.defineConfig = defineConfig;
5
+ exports.getConfig = getConfig;
6
+ exports.resetConfig = resetConfig;
7
+ const defaults_1 = require("./defaults");
8
+ let userConfig = {};
9
+ function configureLLMAssert(config) {
10
+ userConfig = { ...config };
11
+ }
12
+ function defineConfig(config) {
13
+ return config;
14
+ }
15
+ function getConfig() {
16
+ // Priority: programmatic > env vars > defaults
17
+ const envConfig = loadEnvConfig();
18
+ return {
19
+ ...defaults_1.DEFAULT_CONFIG,
20
+ ...envConfig,
21
+ ...userConfig,
22
+ };
23
+ }
24
+ function resetConfig() {
25
+ userConfig = {};
26
+ }
27
+ function loadEnvConfig() {
28
+ const env = {};
29
+ if (process.env.LLM_ASSERT_PROVIDER) {
30
+ env.provider = process.env.LLM_ASSERT_PROVIDER;
31
+ }
32
+ if (process.env.LLM_ASSERT_MODEL) {
33
+ env.model = process.env.LLM_ASSERT_MODEL;
34
+ }
35
+ if (process.env.LLM_ASSERT_API_KEY) {
36
+ env.apiKey = process.env.LLM_ASSERT_API_KEY;
37
+ }
38
+ if (process.env.LLM_ASSERT_CACHE) {
39
+ env.cache = process.env.LLM_ASSERT_CACHE === 'true';
40
+ }
41
+ if (process.env.LLM_ASSERT_VERBOSE) {
42
+ env.verbose = process.env.LLM_ASSERT_VERBOSE === 'true';
43
+ }
44
+ return env;
45
+ }
46
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":";;AAIA,gDAEC;AAED,oCAEC;AAED,8BAQC;AAED,kCAEC;AAxBD,yCAA6D;AAE7D,IAAI,UAAU,GAA6B,EAAE,CAAC;AAE9C,SAAgB,kBAAkB,CAAC,MAAgC;IACjE,UAAU,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,SAAgB,YAAY,CAAC,MAAgC;IAC3D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAgB,SAAS;IACvB,+CAA+C;IAC/C,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,OAAO;QACL,GAAG,yBAAc;QACjB,GAAG,SAAS;QACZ,GAAG,UAAU;KACd,CAAC;AACJ,CAAC;AAED,SAAgB,WAAW;IACzB,UAAU,GAAG,EAAE,CAAC;AAClB,CAAC;AAED,SAAS,aAAa;IACpB,MAAM,GAAG,GAA6B,EAAE,CAAC;IAEzC,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;QACpC,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACjD,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC3C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;QACnC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACjC,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM,CAAC;IACtD,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;QACnC,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,MAAM,CAAC;IAC1D,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface LLMAssertConfig {
2
+ provider: string;
3
+ model: string;
4
+ apiKey?: string;
5
+ ollamaBaseUrl?: string;
6
+ defaultThreshold: number;
7
+ timeout: number;
8
+ maxRetries: number;
9
+ cache: boolean;
10
+ cacheDir: string;
11
+ cacheTTL: number;
12
+ verbose: boolean;
13
+ }
14
+ export declare const DEFAULT_CONFIG: LLMAssertConfig;
15
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,cAAc,EAAE,eAU5B,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ exports.DEFAULT_CONFIG = {
5
+ provider: 'openai',
6
+ model: 'gpt-4o-mini',
7
+ defaultThreshold: 0.7,
8
+ timeout: 30000,
9
+ maxRetries: 2,
10
+ cache: true,
11
+ cacheDir: '.llm-assert-cache',
12
+ cacheTTL: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
13
+ verbose: false,
14
+ };
15
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":";;;AAca,QAAA,cAAc,GAAoB;IAC7C,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,aAAa;IACpB,gBAAgB,EAAE,GAAG;IACrB,OAAO,EAAE,KAAK;IACd,UAAU,EAAE,CAAC;IACb,KAAK,EAAE,IAAI;IACX,QAAQ,EAAE,mBAAmB;IAC7B,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,eAAe;IAClD,OAAO,EAAE,KAAK;CACf,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface AssertionResult {
2
+ assertionType: string;
3
+ score: number;
4
+ pass: boolean;
5
+ reasoning: string;
6
+ threshold: number;
7
+ actual: string;
8
+ expected: string;
9
+ provider: string;
10
+ model: string;
11
+ cached: boolean;
12
+ extra?: Record<string, unknown>;
13
+ }
14
+ export declare class LLMAssertionError extends Error {
15
+ readonly assertionType: string;
16
+ readonly score: number;
17
+ readonly threshold: number;
18
+ readonly reasoning: string;
19
+ readonly actual: string;
20
+ readonly expected: string;
21
+ readonly provider: string;
22
+ readonly model: string;
23
+ readonly cached: boolean;
24
+ constructor(result: AssertionResult);
25
+ }
26
+ //# sourceMappingURL=assertion-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assertion-result.d.ts","sourceRoot":"","sources":["../../src/core/assertion-result.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,SAAgB,aAAa,EAAE,MAAM,CAAC;IACtC,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IACjC,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,SAAgB,MAAM,EAAE,OAAO,CAAC;gBAEpB,MAAM,EAAE,eAAe;CAcpC"}