@agilesyndrome/cf-genai-llm 0.1.8 → 0.1.11
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.
- package/package.json +1 -1
- package/src/index.js +16 -3
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@agilesyndrome/cf-genai-llm","version":"0.1.
|
|
1
|
+
{"name":"@agilesyndrome/cf-genai-llm","version":"0.1.11","description":"Composable Cloudflare Worker LLM generation and review client.","type":"module","exports":{".":"./src/index.js"},"files":["src","tests","README.md","CONTRACT.md","LICENSE"],"scripts":{"check":"node --check src/index.js","test":"node --test tests/*.test.mjs","build":"npm run check && npm test && npm pack --dry-run"},"license":"MIT","publishConfig":{"access":"public","provenance":true},"repository":{"type":"git","url":"git+https://github.com/agilesyndrome/cf-genai-llm.git"},"homepage":"https://github.com/agilesyndrome/cf-genai-llm#readme","dependencies":{"@agilesyndrome/cf-genai-base":"1.0.5"}}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
const DEFAULT_ENDPOINT = "https://api.openai.com/v1/responses";
|
|
2
2
|
const DEFAULT_MODEL = "gpt-5.4";
|
|
3
3
|
|
|
4
|
+
const DEFAULT_MODELS_ENDPOINT = "https://api.openai.com/v1/models";
|
|
5
|
+
export const PACKAGE_NAME = "@agilesyndrome/cf-genai-llm";
|
|
6
|
+
export const VERSION = "0.1.11";
|
|
7
|
+
export class LLMCircuitBreakerError extends Error { constructor(message = "LLM generation is temporarily unavailable") { super(message); this.name = "LLMCircuitBreakerError"; this.code = "circuit_breaker_open"; this.circuitBreakerOpen = true; } }
|
|
8
|
+
|
|
9
|
+
import { getCircuitBreaker, registerCircuitBreaker, registerHealthcheck, setCircuitBreaker } from "@agilesyndrome/cf-genai-base";
|
|
4
10
|
export class LLMResponseError extends Error {
|
|
5
11
|
constructor(message, llmResponse, cause) {
|
|
6
12
|
super(message, { cause });
|
|
@@ -18,6 +24,9 @@ export function createLLM(options = {}) {
|
|
|
18
24
|
const model = options.model || ((env) => env?.OPENAI_MODEL || DEFAULT_MODEL);
|
|
19
25
|
const defaults = options.metadata || {};
|
|
20
26
|
const debugLogging = options.debugLogging === true;
|
|
27
|
+
const featureName = options.feature || options.featureName || "cf-genai-llm";
|
|
28
|
+
const breakerId = options.breakerId || featureName + ":openai-models";
|
|
29
|
+
const healthcheckId = options.healthcheckId || featureName + ":openai-models";
|
|
21
30
|
if (typeof fetcher !== "function") throw new TypeError("createLLM requires fetch");
|
|
22
31
|
|
|
23
32
|
const log = (level, event) => {
|
|
@@ -25,7 +34,11 @@ export function createLLM(options = {}) {
|
|
|
25
34
|
try { (logger[level] || logger.info || (() => {})).call(logger, JSON.stringify({ source: "cf-genai-llm", ...event })); } catch { /* logging cannot break a request */ }
|
|
26
35
|
};
|
|
27
36
|
|
|
28
|
-
async function
|
|
37
|
+
async function assertAvailable(requestOptions = {}) { const env = requestOptions.env || options.env; if (!env || !env.DB || requestOptions.allowWhenCircuitTripped) return; const breaker = await getCircuitBreaker(env, breakerId, { who: requestOptions.who || "system:read" }).catch(() => null); if (breaker && breaker.state !== "on") throw new LLMCircuitBreakerError(); }
|
|
38
|
+
|
|
39
|
+
async function listModels(requestOptions = {}) { const env = requestOptions.env || options.env; const apiKey = requestOptions.apiKey || options.apiKey || (env && env.OPENAI_API_KEY); if (!apiKey) throw new Error("OPENAI_API_KEY is not configured"); const modelsEndpoint = requestOptions.modelsEndpoint || options.modelsEndpoint || (typeof endpoint === "string" && endpoint.endsWith("/responses") ? endpoint.slice(0, -10) + "models" : DEFAULT_MODELS_ENDPOINT); try { const response = await fetcher(typeof modelsEndpoint === "function" ? modelsEndpoint(env) : modelsEndpoint, { method: "GET", headers: { Authorization: "Bearer " + apiKey }, signal: requestOptions.signal }); const payload = await response.json(); if (!response.ok) throw new LLMResponseError("OpenAI model list failed (" + response.status + ")", payload); if (env && env.DB) { await registerHealthcheck(env, { id: healthcheckId, feature: featureName, component: "openai-models", displayName: "OpenAI model availability", state: "green", metadata: { count: Array.isArray(payload.data) ? payload.data.length : 0 } }, { who: requestOptions.who || "system:update" }); await registerCircuitBreaker(env, { id: breakerId, feature: featureName, name: "openai-models", displayName: "OpenAI model access", state: "on", allowSelfHealing: true, healthchecks: [healthcheckId] }, { who: requestOptions.who || "system:update" }); await setCircuitBreaker(env, breakerId, "on", { who: requestOptions.who || "system:update", automated: true }).catch(() => {}); } return payload.data || []; } catch (error) { if (env && env.DB) { await registerHealthcheck(env, { id: healthcheckId, feature: featureName, component: "openai-models", displayName: "OpenAI model availability", state: "red", metadata: { error: error.message } }, { who: requestOptions.who || "system:update" }).catch(() => {}); await registerCircuitBreaker(env, { id: breakerId, feature: featureName, name: "openai-models", displayName: "OpenAI model access", state: "on", allowSelfHealing: true, healthchecks: [healthcheckId] }, { who: requestOptions.who || "system:update" }).then(() => setCircuitBreaker(env, breakerId, "tripped", { who: requestOptions.who || "system:update", automated: true })).catch(() => {}); } throw error; } }
|
|
40
|
+
|
|
41
|
+
async function request(prompt, schema, requestOptions = {}) { await assertAvailable(requestOptions);
|
|
29
42
|
const started = Date.now();
|
|
30
43
|
const requestId = requestOptions.requestId || crypto.randomUUID();
|
|
31
44
|
const env = requestOptions.env || options.env;
|
|
@@ -93,7 +106,7 @@ export function createLLM(options = {}) {
|
|
|
93
106
|
}));
|
|
94
107
|
}
|
|
95
108
|
|
|
96
|
-
return { generate, generateMulti, generateWithSchema: generate, generateMultiWithSchema: generateMulti, review, reviewMulti, reviewWithSchema: review, reviewMultiWithSchema: reviewMulti };
|
|
109
|
+
return { listModels, generate, generateMulti, generateWithSchema: generate, generateMultiWithSchema: generateMulti, review, reviewMulti, reviewWithSchema: review, reviewMultiWithSchema: reviewMulti };
|
|
97
110
|
}
|
|
98
111
|
|
|
99
112
|
function normalizeGenerateArgs(promptOrRequest, schemaOrOptions, maybeOptions) {
|
|
@@ -120,4 +133,4 @@ function validate(value, schema, path) {
|
|
|
120
133
|
}
|
|
121
134
|
function normalizeUsage(usage = {}) { return { inputTokens: usage.input_tokens ?? usage.prompt_tokens ?? 0, outputTokens: usage.output_tokens ?? usage.completion_tokens ?? 0, totalTokens: usage.total_tokens ?? 0 }; }
|
|
122
135
|
|
|
123
|
-
export function createFeature(options = {}) { const name = options.name || "cf-genai-llm"; return { name, middleware: async (request, env, ctx, next, state) => { if (options.boot) await options.boot(env, { request, ctx, state }); return options.handle ? options.handle(request, env, ctx, next, state) : next(); } }; }
|
|
136
|
+
export function createFeature(options = {}) { const name = options.name || "cf-genai-llm"; const client = createLLM({ ...options, feature: name }); return { name, packageName: PACKAGE_NAME, version: VERSION, healthcheck: async (env) => { if (!env || !env.OPENAI_API_KEY) return [{ feature: name, component: "configuration", displayName: "OpenAI configuration", state: "red" }]; try { await client.listModels({ env, who: "system:update" }); return [{ feature: name, component: "configuration", displayName: "OpenAI configuration", state: "green" }]; } catch { return [{ feature: name, component: "configuration", displayName: "OpenAI configuration", state: "yellow" }]; } }, healthchecks: [{ feature: name, component: "openai-models", displayName: "OpenAI model availability", state: "yellow" }], circuitBreakers: [{ id: name + ":openai-models", feature: name, name: "openai-models", displayName: "OpenAI model access", state: "on", allowSelfHealing: true, healthchecks: [name + ":openai-models"] }], middleware: async (request, env, ctx, next, state) => { if (options.boot) await options.boot(env, { request, ctx, state }); return options.handle ? options.handle(request, env, ctx, next, state) : next(); } }; }
|