graphile-llm 0.7.2 → 0.8.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.
- package/__tests__/graphile-llm.test.js +6 -4
- package/chat.d.ts +5 -5
- package/chat.js +8 -16
- package/config-cache.d.ts +77 -0
- package/config-cache.js +148 -0
- package/embedder.d.ts +5 -5
- package/embedder.js +8 -16
- package/env.d.ts +31 -0
- package/env.js +52 -0
- package/esm/__tests__/graphile-llm.test.js +6 -4
- package/esm/chat.d.ts +5 -5
- package/esm/chat.js +8 -16
- package/esm/config-cache.d.ts +77 -0
- package/esm/config-cache.js +143 -0
- package/esm/embedder.d.ts +5 -5
- package/esm/embedder.js +8 -16
- package/esm/env.d.ts +31 -0
- package/esm/env.js +49 -0
- package/esm/index.d.ts +10 -1
- package/esm/index.js +11 -1
- package/esm/metering.d.ts +114 -0
- package/esm/metering.js +358 -0
- package/esm/plugins/agent-discovery-plugin.d.ts +29 -0
- package/esm/plugins/agent-discovery-plugin.js +65 -0
- package/esm/plugins/llm-module-plugin.d.ts +10 -1
- package/esm/plugins/llm-module-plugin.js +11 -3
- package/esm/plugins/metering-plugin.d.ts +42 -0
- package/esm/plugins/metering-plugin.js +175 -0
- package/esm/plugins/text-mutation-plugin.d.ts +4 -0
- package/esm/plugins/text-mutation-plugin.js +11 -1
- package/esm/plugins/text-search-plugin.d.ts +4 -0
- package/esm/plugins/text-search-plugin.js +13 -1
- package/esm/preset.d.ts +21 -1
- package/esm/preset.js +29 -2
- package/esm/types.d.ts +47 -6
- package/index.d.ts +10 -1
- package/index.js +23 -2
- package/metering.d.ts +114 -0
- package/metering.js +365 -0
- package/package.json +15 -15
- package/plugins/agent-discovery-plugin.d.ts +29 -0
- package/plugins/agent-discovery-plugin.js +69 -0
- package/plugins/llm-module-plugin.d.ts +10 -1
- package/plugins/llm-module-plugin.js +11 -3
- package/plugins/metering-plugin.d.ts +42 -0
- package/plugins/metering-plugin.js +178 -0
- package/plugins/text-mutation-plugin.d.ts +4 -0
- package/plugins/text-mutation-plugin.js +11 -1
- package/plugins/text-search-plugin.d.ts +4 -0
- package/plugins/text-search-plugin.js +13 -1
- package/preset.d.ts +21 -1
- package/preset.js +29 -2
- package/types.d.ts +47 -6
|
@@ -109,11 +109,12 @@ describe('Embedder abstraction', () => {
|
|
|
109
109
|
afterEach(() => {
|
|
110
110
|
process.env = originalEnv;
|
|
111
111
|
});
|
|
112
|
-
it('returns
|
|
112
|
+
it('returns default ollama embedder when EMBEDDER_PROVIDER is not set', () => {
|
|
113
113
|
process.env = { ...originalEnv };
|
|
114
114
|
delete process.env.EMBEDDER_PROVIDER;
|
|
115
115
|
const embedder = (0, embedder_1.buildEmbedderFromEnv)();
|
|
116
|
-
expect(embedder).toBeNull();
|
|
116
|
+
expect(embedder).not.toBeNull();
|
|
117
|
+
expect(typeof embedder).toBe('function');
|
|
117
118
|
});
|
|
118
119
|
it('builds embedder from environment variables', () => {
|
|
119
120
|
process.env = {
|
|
@@ -420,11 +421,12 @@ describe('Chat completion abstraction', () => {
|
|
|
420
421
|
afterEach(() => {
|
|
421
422
|
process.env = originalEnv;
|
|
422
423
|
});
|
|
423
|
-
it('returns
|
|
424
|
+
it('returns default ollama chat completer when CHAT_PROVIDER is not set', () => {
|
|
424
425
|
process.env = { ...originalEnv };
|
|
425
426
|
delete process.env.CHAT_PROVIDER;
|
|
426
427
|
const chat = (0, chat_1.buildChatCompleterFromEnv)();
|
|
427
|
-
expect(chat).toBeNull();
|
|
428
|
+
expect(chat).not.toBeNull();
|
|
429
|
+
expect(typeof chat).toBe('function');
|
|
428
430
|
});
|
|
429
431
|
it('builds chat completer from environment variables', () => {
|
|
430
432
|
process.env = {
|
package/chat.d.ts
CHANGED
|
@@ -26,12 +26,12 @@ export declare function buildChatCompleter(config: ChatConfig): ChatFunction | n
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function buildChatCompleterFromModule(data: LlmModuleData): ChatFunction | null;
|
|
28
28
|
/**
|
|
29
|
-
* Resolve a chat completer from environment variables
|
|
29
|
+
* Resolve a chat completer from environment variables.
|
|
30
30
|
* This is a fallback for development when no llm_module or defaultChatCompleter is configured.
|
|
31
31
|
*
|
|
32
|
-
* Environment variables (
|
|
33
|
-
* CHAT_PROVIDER
|
|
34
|
-
* CHAT_MODEL
|
|
35
|
-
* CHAT_BASE_URL
|
|
32
|
+
* Environment variables (with defaults from env.ts):
|
|
33
|
+
* CHAT_PROVIDER - Provider name (default: 'ollama')
|
|
34
|
+
* CHAT_MODEL - Model identifier (default: 'llama3')
|
|
35
|
+
* CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
|
|
36
36
|
*/
|
|
37
37
|
export declare function buildChatCompleterFromEnv(): ChatFunction | null;
|
package/chat.js
CHANGED
|
@@ -20,7 +20,7 @@ exports.buildChatCompleter = buildChatCompleter;
|
|
|
20
20
|
exports.buildChatCompleterFromModule = buildChatCompleterFromModule;
|
|
21
21
|
exports.buildChatCompleterFromEnv = buildChatCompleterFromEnv;
|
|
22
22
|
const ollama_1 = __importDefault(require("@agentic-kit/ollama"));
|
|
23
|
-
const
|
|
23
|
+
const env_1 = require("./env");
|
|
24
24
|
// ─── Built-in Providers ─────────────────────────────────────────────────────
|
|
25
25
|
/**
|
|
26
26
|
* Create an Ollama-based chat completion function.
|
|
@@ -82,26 +82,18 @@ function buildChatCompleterFromModule(data) {
|
|
|
82
82
|
provider: data.chat_provider,
|
|
83
83
|
model: data.chat_model,
|
|
84
84
|
baseUrl: data.chat_base_url,
|
|
85
|
-
apiKey: data.api_key_ref,
|
|
86
85
|
});
|
|
87
86
|
}
|
|
88
87
|
/**
|
|
89
|
-
* Resolve a chat completer from environment variables
|
|
88
|
+
* Resolve a chat completer from environment variables.
|
|
90
89
|
* This is a fallback for development when no llm_module or defaultChatCompleter is configured.
|
|
91
90
|
*
|
|
92
|
-
* Environment variables (
|
|
93
|
-
* CHAT_PROVIDER
|
|
94
|
-
* CHAT_MODEL
|
|
95
|
-
* CHAT_BASE_URL
|
|
91
|
+
* Environment variables (with defaults from env.ts):
|
|
92
|
+
* CHAT_PROVIDER - Provider name (default: 'ollama')
|
|
93
|
+
* CHAT_MODEL - Model identifier (default: 'llama3')
|
|
94
|
+
* CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
|
|
96
95
|
*/
|
|
97
96
|
function buildChatCompleterFromEnv() {
|
|
98
|
-
const {
|
|
99
|
-
|
|
100
|
-
if (!provider)
|
|
101
|
-
return null;
|
|
102
|
-
return buildChatCompleter({
|
|
103
|
-
provider,
|
|
104
|
-
model: llm?.chat?.model,
|
|
105
|
-
baseUrl: llm?.chat?.baseUrl,
|
|
106
|
-
});
|
|
97
|
+
const { chat } = (0, env_1.getLlmEnvOptions)();
|
|
98
|
+
return buildChatCompleter(chat);
|
|
107
99
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-cache — Per-database LLM billing configuration cache
|
|
3
|
+
*
|
|
4
|
+
* Caches resolved billing function names per database_id.
|
|
5
|
+
* Uses an LRU cache with TTL so config changes propagate within a bounded window
|
|
6
|
+
* without requiring a server restart.
|
|
7
|
+
*
|
|
8
|
+
* Resolution flow:
|
|
9
|
+
* Billing config from `metaschema_modules_public.billing_module`
|
|
10
|
+
* (schema name + function names for record_usage, check_billing_quota)
|
|
11
|
+
*
|
|
12
|
+
* All queries run through the Graphile `withPgClient` callback, which gives us
|
|
13
|
+
* a client connected to the tenant database with proper role settings.
|
|
14
|
+
*
|
|
15
|
+
* The LLM module config (provider, model, etc.) is already resolved by the
|
|
16
|
+
* LlmModulePlugin at schema-build time. This cache handles the runtime-only
|
|
17
|
+
* billing piece.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Generic pg client interface matching what Graphile's withPgClient provides.
|
|
21
|
+
* Avoids a hard dependency on the `pg` package.
|
|
22
|
+
*/
|
|
23
|
+
export interface PgClient {
|
|
24
|
+
query(sql: string, values?: unknown[]): Promise<{
|
|
25
|
+
rows: Record<string, unknown>[];
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Billing function metadata resolved from the billing_module metaschema table.
|
|
30
|
+
*/
|
|
31
|
+
export interface BillingConfig {
|
|
32
|
+
/** Private schema containing the billing functions */
|
|
33
|
+
privateSchema: string;
|
|
34
|
+
/** Name of the record_usage function */
|
|
35
|
+
recordUsageFunction: string;
|
|
36
|
+
/** Name of the check_billing_quota function */
|
|
37
|
+
checkBillingQuotaFunction: string;
|
|
38
|
+
/** Public schema containing meters table */
|
|
39
|
+
publicSchema: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Inference log table metadata resolved from the inference_log_module.
|
|
43
|
+
*/
|
|
44
|
+
export interface InferenceLogConfig {
|
|
45
|
+
/** Schema containing the usage_log_inference table */
|
|
46
|
+
schema: string;
|
|
47
|
+
/** Name of the inference log table */
|
|
48
|
+
tableName: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Per-database cached configuration for the LLM billing integration.
|
|
52
|
+
*/
|
|
53
|
+
export interface LlmBillingCacheEntry {
|
|
54
|
+
/** Billing function references (null if billing_module not provisioned) */
|
|
55
|
+
billing: BillingConfig | null;
|
|
56
|
+
/** Inference log table references (null if inference_log_module not provisioned) */
|
|
57
|
+
inferenceLog: InferenceLogConfig | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve billing config for a database.
|
|
61
|
+
* Results are cached per database_id with a 5-minute TTL.
|
|
62
|
+
*
|
|
63
|
+
* @param pgClient - A client connected to the tenant database (from withPgClient)
|
|
64
|
+
* @param databaseId - The database UUID
|
|
65
|
+
*/
|
|
66
|
+
export declare function getLlmBillingConfig(pgClient: PgClient, databaseId: string): Promise<LlmBillingCacheEntry>;
|
|
67
|
+
/**
|
|
68
|
+
* Invalidate the cached config for a specific database (or all).
|
|
69
|
+
*/
|
|
70
|
+
export declare function invalidateLlmBillingConfig(databaseId?: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Get cache stats for diagnostics.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getLlmBillingCacheStats(): {
|
|
75
|
+
size: number;
|
|
76
|
+
max: number;
|
|
77
|
+
};
|
package/config-cache.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* config-cache — Per-database LLM billing configuration cache
|
|
4
|
+
*
|
|
5
|
+
* Caches resolved billing function names per database_id.
|
|
6
|
+
* Uses an LRU cache with TTL so config changes propagate within a bounded window
|
|
7
|
+
* without requiring a server restart.
|
|
8
|
+
*
|
|
9
|
+
* Resolution flow:
|
|
10
|
+
* Billing config from `metaschema_modules_public.billing_module`
|
|
11
|
+
* (schema name + function names for record_usage, check_billing_quota)
|
|
12
|
+
*
|
|
13
|
+
* All queries run through the Graphile `withPgClient` callback, which gives us
|
|
14
|
+
* a client connected to the tenant database with proper role settings.
|
|
15
|
+
*
|
|
16
|
+
* The LLM module config (provider, model, etc.) is already resolved by the
|
|
17
|
+
* LlmModulePlugin at schema-build time. This cache handles the runtime-only
|
|
18
|
+
* billing piece.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.getLlmBillingConfig = getLlmBillingConfig;
|
|
22
|
+
exports.invalidateLlmBillingConfig = invalidateLlmBillingConfig;
|
|
23
|
+
exports.getLlmBillingCacheStats = getLlmBillingCacheStats;
|
|
24
|
+
const graphile_cache_1 = require("graphile-cache");
|
|
25
|
+
// ─── SQL Queries ────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Check if the billing_module table exists before querying it.
|
|
28
|
+
* This prevents hard errors on databases that don't have the billing
|
|
29
|
+
* module provisioned (the metaschema_modules_public schema or the
|
|
30
|
+
* billing_module table might not exist at all).
|
|
31
|
+
*/
|
|
32
|
+
const BILLING_MODULE_SQL = `
|
|
33
|
+
SELECT
|
|
34
|
+
s.schema_name AS public_schema,
|
|
35
|
+
ps.schema_name AS private_schema,
|
|
36
|
+
bm.record_usage_function
|
|
37
|
+
FROM metaschema_modules_public.billing_module bm
|
|
38
|
+
JOIN metaschema_public.schema s ON bm.schema_id = s.id
|
|
39
|
+
JOIN metaschema_public.schema ps ON bm.private_schema_id = ps.id
|
|
40
|
+
WHERE bm.database_id = $1
|
|
41
|
+
LIMIT 1
|
|
42
|
+
`;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the inference log module's schema and table name.
|
|
45
|
+
*/
|
|
46
|
+
const INFERENCE_LOG_MODULE_SQL = `
|
|
47
|
+
SELECT
|
|
48
|
+
s.schema_name AS schema,
|
|
49
|
+
ilm.inference_log_table_name AS table_name
|
|
50
|
+
FROM metaschema_modules_public.inference_log_module ilm
|
|
51
|
+
JOIN metaschema_public.schema s ON ilm.schema_id = s.id
|
|
52
|
+
WHERE ilm.database_id = $1
|
|
53
|
+
LIMIT 1
|
|
54
|
+
`;
|
|
55
|
+
// ─── Cache ──────────────────────────────────────────────────────────────────
|
|
56
|
+
const billingCache = new graphile_cache_1.ModuleConfigCache({
|
|
57
|
+
name: 'billing-config',
|
|
58
|
+
ttlMs: 5 * 60 * 1000, // 5 minutes
|
|
59
|
+
max: 50,
|
|
60
|
+
});
|
|
61
|
+
// ─── Resolution Functions ───────────────────────────────────────────────────
|
|
62
|
+
/**
|
|
63
|
+
* SQL to check if a schema exists. Used as a guard before querying
|
|
64
|
+
* metaschema tables that may not be provisioned.
|
|
65
|
+
*/
|
|
66
|
+
const SCHEMA_EXISTS_SQL = `
|
|
67
|
+
SELECT 1 FROM information_schema.schemata WHERE schema_name = $1 LIMIT 1
|
|
68
|
+
`;
|
|
69
|
+
async function resolveInferenceLogConfig(pgClient, databaseId) {
|
|
70
|
+
try {
|
|
71
|
+
const schemaCheck = await pgClient.query(SCHEMA_EXISTS_SQL, ['metaschema_modules_public']);
|
|
72
|
+
if (schemaCheck.rows.length === 0)
|
|
73
|
+
return null;
|
|
74
|
+
const result = await pgClient.query(INFERENCE_LOG_MODULE_SQL, [databaseId]);
|
|
75
|
+
const row = result.rows[0];
|
|
76
|
+
if (!row?.schema || !row?.table_name)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
schema: row.schema,
|
|
80
|
+
tableName: row.table_name,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function resolveBillingConfig(pgClient, databaseId) {
|
|
88
|
+
try {
|
|
89
|
+
// Guard: check if the metaschema_modules_public schema exists.
|
|
90
|
+
// If the database doesn't have the billing module provisioned,
|
|
91
|
+
// this schema (or the billing_module table) won't exist.
|
|
92
|
+
const schemaCheck = await pgClient.query(SCHEMA_EXISTS_SQL, ['metaschema_modules_public']);
|
|
93
|
+
if (schemaCheck.rows.length === 0)
|
|
94
|
+
return null;
|
|
95
|
+
const result = await pgClient.query(BILLING_MODULE_SQL, [databaseId]);
|
|
96
|
+
const row = result.rows[0];
|
|
97
|
+
if (!row?.record_usage_function)
|
|
98
|
+
return null;
|
|
99
|
+
return {
|
|
100
|
+
publicSchema: row.public_schema,
|
|
101
|
+
privateSchema: row.private_schema,
|
|
102
|
+
recordUsageFunction: row.record_usage_function,
|
|
103
|
+
// The check_billing_quota function name follows the inflection pattern
|
|
104
|
+
checkBillingQuotaFunction: 'check_billing_quota',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Schema/table doesn't exist or query failed — billing not available
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
113
|
+
/**
|
|
114
|
+
* Resolve billing config for a database.
|
|
115
|
+
* Results are cached per database_id with a 5-minute TTL.
|
|
116
|
+
*
|
|
117
|
+
* @param pgClient - A client connected to the tenant database (from withPgClient)
|
|
118
|
+
* @param databaseId - The database UUID
|
|
119
|
+
*/
|
|
120
|
+
async function getLlmBillingConfig(pgClient, databaseId) {
|
|
121
|
+
const cached = billingCache.get(databaseId);
|
|
122
|
+
if (cached)
|
|
123
|
+
return cached;
|
|
124
|
+
const [billing, inferenceLog] = await Promise.all([
|
|
125
|
+
resolveBillingConfig(pgClient, databaseId),
|
|
126
|
+
resolveInferenceLogConfig(pgClient, databaseId),
|
|
127
|
+
]);
|
|
128
|
+
const entry = { billing, inferenceLog };
|
|
129
|
+
billingCache.set(databaseId, entry);
|
|
130
|
+
return entry;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Invalidate the cached config for a specific database (or all).
|
|
134
|
+
*/
|
|
135
|
+
function invalidateLlmBillingConfig(databaseId) {
|
|
136
|
+
if (databaseId) {
|
|
137
|
+
billingCache.delete(databaseId);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
billingCache.clear();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get cache stats for diagnostics.
|
|
145
|
+
*/
|
|
146
|
+
function getLlmBillingCacheStats() {
|
|
147
|
+
return { size: billingCache.size, max: 50 };
|
|
148
|
+
}
|
package/embedder.d.ts
CHANGED
|
@@ -24,12 +24,12 @@ export declare function buildEmbedder(config: EmbedderConfig): EmbedderFunction
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function buildEmbedderFromModule(data: LlmModuleData): EmbedderFunction | null;
|
|
26
26
|
/**
|
|
27
|
-
* Resolve an embedder from environment variables
|
|
27
|
+
* Resolve an embedder from environment variables.
|
|
28
28
|
* This is a fallback for development when no llm_module or defaultEmbedder is configured.
|
|
29
29
|
*
|
|
30
|
-
* Environment variables (
|
|
31
|
-
* EMBEDDER_PROVIDER
|
|
32
|
-
* EMBEDDER_MODEL
|
|
33
|
-
* EMBEDDER_BASE_URL
|
|
30
|
+
* Environment variables (with defaults from env.ts):
|
|
31
|
+
* EMBEDDER_PROVIDER - Provider name (default: 'ollama')
|
|
32
|
+
* EMBEDDER_MODEL - Model identifier (default: 'nomic-embed-text')
|
|
33
|
+
* EMBEDDER_BASE_URL - Provider base URL (default: 'http://localhost:11434')
|
|
34
34
|
*/
|
|
35
35
|
export declare function buildEmbedderFromEnv(): EmbedderFunction | null;
|
package/embedder.js
CHANGED
|
@@ -18,7 +18,7 @@ exports.buildEmbedder = buildEmbedder;
|
|
|
18
18
|
exports.buildEmbedderFromModule = buildEmbedderFromModule;
|
|
19
19
|
exports.buildEmbedderFromEnv = buildEmbedderFromEnv;
|
|
20
20
|
const ollama_1 = __importDefault(require("@agentic-kit/ollama"));
|
|
21
|
-
const
|
|
21
|
+
const env_1 = require("./env");
|
|
22
22
|
// ─── Built-in Providers ─────────────────────────────────────────────────────
|
|
23
23
|
/**
|
|
24
24
|
* Create an Ollama-based embedder function.
|
|
@@ -56,26 +56,18 @@ function buildEmbedderFromModule(data) {
|
|
|
56
56
|
provider: data.embedding_provider,
|
|
57
57
|
model: data.embedding_model,
|
|
58
58
|
baseUrl: data.embedding_base_url,
|
|
59
|
-
apiKey: data.api_key_ref,
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
/**
|
|
63
|
-
* Resolve an embedder from environment variables
|
|
62
|
+
* Resolve an embedder from environment variables.
|
|
64
63
|
* This is a fallback for development when no llm_module or defaultEmbedder is configured.
|
|
65
64
|
*
|
|
66
|
-
* Environment variables (
|
|
67
|
-
* EMBEDDER_PROVIDER
|
|
68
|
-
* EMBEDDER_MODEL
|
|
69
|
-
* EMBEDDER_BASE_URL
|
|
65
|
+
* Environment variables (with defaults from env.ts):
|
|
66
|
+
* EMBEDDER_PROVIDER - Provider name (default: 'ollama')
|
|
67
|
+
* EMBEDDER_MODEL - Model identifier (default: 'nomic-embed-text')
|
|
68
|
+
* EMBEDDER_BASE_URL - Provider base URL (default: 'http://localhost:11434')
|
|
70
69
|
*/
|
|
71
70
|
function buildEmbedderFromEnv() {
|
|
72
|
-
const {
|
|
73
|
-
|
|
74
|
-
if (!provider)
|
|
75
|
-
return null;
|
|
76
|
-
return buildEmbedder({
|
|
77
|
-
provider,
|
|
78
|
-
model: llm?.embedder?.model,
|
|
79
|
-
baseUrl: llm?.embedder?.baseUrl,
|
|
80
|
-
});
|
|
71
|
+
const { embedding } = (0, env_1.getLlmEnvOptions)();
|
|
72
|
+
return buildEmbedder(embedding);
|
|
81
73
|
}
|
package/env.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Environment Configuration
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all LLM-related environment variables and defaults.
|
|
5
|
+
* Every other module in graphile-llm imports from here — no direct process.env
|
|
6
|
+
* reads or scattered null coalescing elsewhere.
|
|
7
|
+
*
|
|
8
|
+
* Environment variables:
|
|
9
|
+
* EMBEDDER_PROVIDER - Embedding provider name ('ollama')
|
|
10
|
+
* EMBEDDER_MODEL - Embedding model (default: 'nomic-embed-text')
|
|
11
|
+
* EMBEDDER_BASE_URL - Embedding provider URL (default: 'http://localhost:11434')
|
|
12
|
+
* CHAT_PROVIDER - Chat provider name ('ollama')
|
|
13
|
+
* CHAT_MODEL - Chat model (default: 'llama3')
|
|
14
|
+
* CHAT_BASE_URL - Chat provider URL (default: 'http://localhost:11434')
|
|
15
|
+
*/
|
|
16
|
+
export interface LlmProviderConfig {
|
|
17
|
+
provider: string;
|
|
18
|
+
model: string;
|
|
19
|
+
baseUrl: string;
|
|
20
|
+
}
|
|
21
|
+
export interface LlmEnvOptions {
|
|
22
|
+
embedding: LlmProviderConfig;
|
|
23
|
+
chat: LlmProviderConfig;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve LLM configuration from environment variables with sensible defaults.
|
|
27
|
+
*
|
|
28
|
+
* Call this once and pass the result around — never read process.env directly
|
|
29
|
+
* in plugin code.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getLlmEnvOptions(): LlmEnvOptions;
|
package/env.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LLM Environment Configuration
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for all LLM-related environment variables and defaults.
|
|
6
|
+
* Every other module in graphile-llm imports from here — no direct process.env
|
|
7
|
+
* reads or scattered null coalescing elsewhere.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables:
|
|
10
|
+
* EMBEDDER_PROVIDER - Embedding provider name ('ollama')
|
|
11
|
+
* EMBEDDER_MODEL - Embedding model (default: 'nomic-embed-text')
|
|
12
|
+
* EMBEDDER_BASE_URL - Embedding provider URL (default: 'http://localhost:11434')
|
|
13
|
+
* CHAT_PROVIDER - Chat provider name ('ollama')
|
|
14
|
+
* CHAT_MODEL - Chat model (default: 'llama3')
|
|
15
|
+
* CHAT_BASE_URL - Chat provider URL (default: 'http://localhost:11434')
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.getLlmEnvOptions = getLlmEnvOptions;
|
|
19
|
+
// ─── Defaults ───────────────────────────────────────────────────────────────
|
|
20
|
+
const LLM_DEFAULTS = {
|
|
21
|
+
embedding: {
|
|
22
|
+
provider: 'ollama',
|
|
23
|
+
model: 'nomic-embed-text',
|
|
24
|
+
baseUrl: 'http://localhost:11434',
|
|
25
|
+
},
|
|
26
|
+
chat: {
|
|
27
|
+
provider: 'ollama',
|
|
28
|
+
model: 'llama3',
|
|
29
|
+
baseUrl: 'http://localhost:11434',
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
// ─── Resolution ─────────────────────────────────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Resolve LLM configuration from environment variables with sensible defaults.
|
|
35
|
+
*
|
|
36
|
+
* Call this once and pass the result around — never read process.env directly
|
|
37
|
+
* in plugin code.
|
|
38
|
+
*/
|
|
39
|
+
function getLlmEnvOptions() {
|
|
40
|
+
return {
|
|
41
|
+
embedding: {
|
|
42
|
+
provider: process.env.EMBEDDER_PROVIDER ?? LLM_DEFAULTS.embedding.provider,
|
|
43
|
+
model: process.env.EMBEDDER_MODEL ?? LLM_DEFAULTS.embedding.model,
|
|
44
|
+
baseUrl: process.env.EMBEDDER_BASE_URL ?? LLM_DEFAULTS.embedding.baseUrl,
|
|
45
|
+
},
|
|
46
|
+
chat: {
|
|
47
|
+
provider: process.env.CHAT_PROVIDER ?? LLM_DEFAULTS.chat.provider,
|
|
48
|
+
model: process.env.CHAT_MODEL ?? LLM_DEFAULTS.chat.model,
|
|
49
|
+
baseUrl: process.env.CHAT_BASE_URL ?? LLM_DEFAULTS.chat.baseUrl,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -71,11 +71,12 @@ describe('Embedder abstraction', () => {
|
|
|
71
71
|
afterEach(() => {
|
|
72
72
|
process.env = originalEnv;
|
|
73
73
|
});
|
|
74
|
-
it('returns
|
|
74
|
+
it('returns default ollama embedder when EMBEDDER_PROVIDER is not set', () => {
|
|
75
75
|
process.env = { ...originalEnv };
|
|
76
76
|
delete process.env.EMBEDDER_PROVIDER;
|
|
77
77
|
const embedder = buildEmbedderFromEnv();
|
|
78
|
-
expect(embedder).toBeNull();
|
|
78
|
+
expect(embedder).not.toBeNull();
|
|
79
|
+
expect(typeof embedder).toBe('function');
|
|
79
80
|
});
|
|
80
81
|
it('builds embedder from environment variables', () => {
|
|
81
82
|
process.env = {
|
|
@@ -382,11 +383,12 @@ describe('Chat completion abstraction', () => {
|
|
|
382
383
|
afterEach(() => {
|
|
383
384
|
process.env = originalEnv;
|
|
384
385
|
});
|
|
385
|
-
it('returns
|
|
386
|
+
it('returns default ollama chat completer when CHAT_PROVIDER is not set', () => {
|
|
386
387
|
process.env = { ...originalEnv };
|
|
387
388
|
delete process.env.CHAT_PROVIDER;
|
|
388
389
|
const chat = buildChatCompleterFromEnv();
|
|
389
|
-
expect(chat).toBeNull();
|
|
390
|
+
expect(chat).not.toBeNull();
|
|
391
|
+
expect(typeof chat).toBe('function');
|
|
390
392
|
});
|
|
391
393
|
it('builds chat completer from environment variables', () => {
|
|
392
394
|
process.env = {
|
package/esm/chat.d.ts
CHANGED
|
@@ -26,12 +26,12 @@ export declare function buildChatCompleter(config: ChatConfig): ChatFunction | n
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function buildChatCompleterFromModule(data: LlmModuleData): ChatFunction | null;
|
|
28
28
|
/**
|
|
29
|
-
* Resolve a chat completer from environment variables
|
|
29
|
+
* Resolve a chat completer from environment variables.
|
|
30
30
|
* This is a fallback for development when no llm_module or defaultChatCompleter is configured.
|
|
31
31
|
*
|
|
32
|
-
* Environment variables (
|
|
33
|
-
* CHAT_PROVIDER
|
|
34
|
-
* CHAT_MODEL
|
|
35
|
-
* CHAT_BASE_URL
|
|
32
|
+
* Environment variables (with defaults from env.ts):
|
|
33
|
+
* CHAT_PROVIDER - Provider name (default: 'ollama')
|
|
34
|
+
* CHAT_MODEL - Model identifier (default: 'llama3')
|
|
35
|
+
* CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
|
|
36
36
|
*/
|
|
37
37
|
export declare function buildChatCompleterFromEnv(): ChatFunction | null;
|
package/esm/chat.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 3. Environment variables (CHAT_PROVIDER, CHAT_MODEL, CHAT_BASE_URL)
|
|
13
13
|
*/
|
|
14
14
|
import OllamaClient from '@agentic-kit/ollama';
|
|
15
|
-
import {
|
|
15
|
+
import { getLlmEnvOptions } from './env';
|
|
16
16
|
// ─── Built-in Providers ─────────────────────────────────────────────────────
|
|
17
17
|
/**
|
|
18
18
|
* Create an Ollama-based chat completion function.
|
|
@@ -74,26 +74,18 @@ export function buildChatCompleterFromModule(data) {
|
|
|
74
74
|
provider: data.chat_provider,
|
|
75
75
|
model: data.chat_model,
|
|
76
76
|
baseUrl: data.chat_base_url,
|
|
77
|
-
apiKey: data.api_key_ref,
|
|
78
77
|
});
|
|
79
78
|
}
|
|
80
79
|
/**
|
|
81
|
-
* Resolve a chat completer from environment variables
|
|
80
|
+
* Resolve a chat completer from environment variables.
|
|
82
81
|
* This is a fallback for development when no llm_module or defaultChatCompleter is configured.
|
|
83
82
|
*
|
|
84
|
-
* Environment variables (
|
|
85
|
-
* CHAT_PROVIDER
|
|
86
|
-
* CHAT_MODEL
|
|
87
|
-
* CHAT_BASE_URL
|
|
83
|
+
* Environment variables (with defaults from env.ts):
|
|
84
|
+
* CHAT_PROVIDER - Provider name (default: 'ollama')
|
|
85
|
+
* CHAT_MODEL - Model identifier (default: 'llama3')
|
|
86
|
+
* CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
|
|
88
87
|
*/
|
|
89
88
|
export function buildChatCompleterFromEnv() {
|
|
90
|
-
const {
|
|
91
|
-
|
|
92
|
-
if (!provider)
|
|
93
|
-
return null;
|
|
94
|
-
return buildChatCompleter({
|
|
95
|
-
provider,
|
|
96
|
-
model: llm?.chat?.model,
|
|
97
|
-
baseUrl: llm?.chat?.baseUrl,
|
|
98
|
-
});
|
|
89
|
+
const { chat } = getLlmEnvOptions();
|
|
90
|
+
return buildChatCompleter(chat);
|
|
99
91
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config-cache — Per-database LLM billing configuration cache
|
|
3
|
+
*
|
|
4
|
+
* Caches resolved billing function names per database_id.
|
|
5
|
+
* Uses an LRU cache with TTL so config changes propagate within a bounded window
|
|
6
|
+
* without requiring a server restart.
|
|
7
|
+
*
|
|
8
|
+
* Resolution flow:
|
|
9
|
+
* Billing config from `metaschema_modules_public.billing_module`
|
|
10
|
+
* (schema name + function names for record_usage, check_billing_quota)
|
|
11
|
+
*
|
|
12
|
+
* All queries run through the Graphile `withPgClient` callback, which gives us
|
|
13
|
+
* a client connected to the tenant database with proper role settings.
|
|
14
|
+
*
|
|
15
|
+
* The LLM module config (provider, model, etc.) is already resolved by the
|
|
16
|
+
* LlmModulePlugin at schema-build time. This cache handles the runtime-only
|
|
17
|
+
* billing piece.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Generic pg client interface matching what Graphile's withPgClient provides.
|
|
21
|
+
* Avoids a hard dependency on the `pg` package.
|
|
22
|
+
*/
|
|
23
|
+
export interface PgClient {
|
|
24
|
+
query(sql: string, values?: unknown[]): Promise<{
|
|
25
|
+
rows: Record<string, unknown>[];
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Billing function metadata resolved from the billing_module metaschema table.
|
|
30
|
+
*/
|
|
31
|
+
export interface BillingConfig {
|
|
32
|
+
/** Private schema containing the billing functions */
|
|
33
|
+
privateSchema: string;
|
|
34
|
+
/** Name of the record_usage function */
|
|
35
|
+
recordUsageFunction: string;
|
|
36
|
+
/** Name of the check_billing_quota function */
|
|
37
|
+
checkBillingQuotaFunction: string;
|
|
38
|
+
/** Public schema containing meters table */
|
|
39
|
+
publicSchema: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Inference log table metadata resolved from the inference_log_module.
|
|
43
|
+
*/
|
|
44
|
+
export interface InferenceLogConfig {
|
|
45
|
+
/** Schema containing the usage_log_inference table */
|
|
46
|
+
schema: string;
|
|
47
|
+
/** Name of the inference log table */
|
|
48
|
+
tableName: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Per-database cached configuration for the LLM billing integration.
|
|
52
|
+
*/
|
|
53
|
+
export interface LlmBillingCacheEntry {
|
|
54
|
+
/** Billing function references (null if billing_module not provisioned) */
|
|
55
|
+
billing: BillingConfig | null;
|
|
56
|
+
/** Inference log table references (null if inference_log_module not provisioned) */
|
|
57
|
+
inferenceLog: InferenceLogConfig | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve billing config for a database.
|
|
61
|
+
* Results are cached per database_id with a 5-minute TTL.
|
|
62
|
+
*
|
|
63
|
+
* @param pgClient - A client connected to the tenant database (from withPgClient)
|
|
64
|
+
* @param databaseId - The database UUID
|
|
65
|
+
*/
|
|
66
|
+
export declare function getLlmBillingConfig(pgClient: PgClient, databaseId: string): Promise<LlmBillingCacheEntry>;
|
|
67
|
+
/**
|
|
68
|
+
* Invalidate the cached config for a specific database (or all).
|
|
69
|
+
*/
|
|
70
|
+
export declare function invalidateLlmBillingConfig(databaseId?: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Get cache stats for diagnostics.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getLlmBillingCacheStats(): {
|
|
75
|
+
size: number;
|
|
76
|
+
max: number;
|
|
77
|
+
};
|