hazo_llm_api 1.0.4 → 1.0.6
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/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +7 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/layout/index.d.ts +7 -0
- package/dist/components/layout/index.d.ts.map +1 -0
- package/dist/components/layout/index.js +7 -0
- package/dist/components/layout/index.js.map +1 -0
- package/dist/components/layout/layout.d.ts +21 -0
- package/dist/components/layout/layout.d.ts.map +1 -0
- package/dist/components/layout/layout.js +18 -0
- package/dist/components/layout/layout.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config/config_parser.d.ts +131 -0
- package/dist/lib/config/config_parser.d.ts.map +1 -0
- package/dist/lib/config/config_parser.js +297 -0
- package/dist/lib/config/config_parser.js.map +1 -0
- package/dist/lib/config/index.d.ts +8 -0
- package/dist/lib/config/index.d.ts.map +1 -0
- package/dist/lib/config/index.js +22 -0
- package/dist/lib/config/index.js.map +1 -0
- package/dist/lib/config/provider_loader.d.ts +113 -0
- package/dist/lib/config/provider_loader.d.ts.map +1 -0
- package/dist/lib/config/provider_loader.js +169 -0
- package/dist/lib/config/provider_loader.js.map +1 -0
- package/dist/lib/database/index.d.ts +8 -0
- package/dist/lib/database/index.d.ts.map +1 -0
- package/dist/lib/database/index.js +10 -0
- package/dist/lib/database/index.js.map +1 -0
- package/dist/lib/database/init_database.d.ts +118 -0
- package/dist/lib/database/init_database.d.ts.map +1 -0
- package/dist/lib/database/init_database.js +591 -0
- package/dist/lib/database/init_database.js.map +1 -0
- package/dist/lib/database/utils.d.ts +53 -0
- package/dist/lib/database/utils.d.ts.map +1 -0
- package/dist/lib/database/utils.js +87 -0
- package/dist/lib/database/utils.js.map +1 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +17 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/llm_api/chain_helpers.d.ts +117 -0
- package/dist/lib/llm_api/chain_helpers.d.ts.map +1 -0
- package/dist/lib/llm_api/chain_helpers.js +445 -0
- package/dist/lib/llm_api/chain_helpers.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_image_image.d.ts +26 -0
- package/dist/lib/llm_api/hazo_llm_image_image.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_image_image.js +94 -0
- package/dist/lib/llm_api/hazo_llm_image_image.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_image_image_text.d.ts +26 -0
- package/dist/lib/llm_api/hazo_llm_image_image_text.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_image_image_text.js +222 -0
- package/dist/lib/llm_api/hazo_llm_image_image_text.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_image_text.d.ts +20 -0
- package/dist/lib/llm_api/hazo_llm_image_text.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_image_text.js +78 -0
- package/dist/lib/llm_api/hazo_llm_image_text.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_prompt_chain.d.ts +20 -0
- package/dist/lib/llm_api/hazo_llm_prompt_chain.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_prompt_chain.js +368 -0
- package/dist/lib/llm_api/hazo_llm_prompt_chain.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_text_image.d.ts +20 -0
- package/dist/lib/llm_api/hazo_llm_text_image.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_text_image.js +69 -0
- package/dist/lib/llm_api/hazo_llm_text_image.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_text_image_text.d.ts +26 -0
- package/dist/lib/llm_api/hazo_llm_text_image_text.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_text_image_text.js +154 -0
- package/dist/lib/llm_api/hazo_llm_text_image_text.js.map +1 -0
- package/dist/lib/llm_api/hazo_llm_text_text.d.ts +20 -0
- package/dist/lib/llm_api/hazo_llm_text_text.d.ts.map +1 -0
- package/dist/lib/llm_api/hazo_llm_text_text.js +91 -0
- package/dist/lib/llm_api/hazo_llm_text_text.js.map +1 -0
- package/dist/lib/llm_api/index.d.ts +223 -0
- package/dist/lib/llm_api/index.d.ts.map +1 -0
- package/dist/lib/llm_api/index.js +1220 -0
- package/dist/lib/llm_api/index.js.map +1 -0
- package/dist/lib/llm_api/provider_helper.d.ts +163 -0
- package/dist/lib/llm_api/provider_helper.d.ts.map +1 -0
- package/dist/lib/llm_api/provider_helper.js +346 -0
- package/dist/lib/llm_api/provider_helper.js.map +1 -0
- package/dist/lib/llm_api/types.d.ts +667 -0
- package/dist/lib/llm_api/types.d.ts.map +1 -0
- package/dist/lib/llm_api/types.js +49 -0
- package/dist/lib/llm_api/types.js.map +1 -0
- package/dist/lib/prompts/get_prompt.d.ts +76 -0
- package/dist/lib/prompts/get_prompt.d.ts.map +1 -0
- package/dist/lib/prompts/get_prompt.js +342 -0
- package/dist/lib/prompts/get_prompt.js.map +1 -0
- package/dist/lib/prompts/index.d.ts +9 -0
- package/dist/lib/prompts/index.d.ts.map +1 -0
- package/dist/lib/prompts/index.js +9 -0
- package/dist/lib/prompts/index.js.map +1 -0
- package/dist/lib/prompts/prompt_cache.d.ts +151 -0
- package/dist/lib/prompts/prompt_cache.d.ts.map +1 -0
- package/dist/lib/prompts/prompt_cache.js +276 -0
- package/dist/lib/prompts/prompt_cache.js.map +1 -0
- package/dist/lib/prompts/substitute_variables.d.ts +38 -0
- package/dist/lib/prompts/substitute_variables.d.ts.map +1 -0
- package/dist/lib/prompts/substitute_variables.js +171 -0
- package/dist/lib/prompts/substitute_variables.js.map +1 -0
- package/dist/lib/providers/gemini/gemini_client.d.ts +25 -0
- package/dist/lib/providers/gemini/gemini_client.d.ts.map +1 -0
- package/dist/lib/providers/gemini/gemini_client.js +235 -0
- package/dist/lib/providers/gemini/gemini_client.js.map +1 -0
- package/dist/lib/providers/gemini/gemini_provider.d.ts +111 -0
- package/dist/lib/providers/gemini/gemini_provider.d.ts.map +1 -0
- package/dist/lib/providers/gemini/gemini_provider.js +431 -0
- package/dist/lib/providers/gemini/gemini_provider.js.map +1 -0
- package/dist/lib/providers/gemini/index.d.ts +8 -0
- package/dist/lib/providers/gemini/index.d.ts.map +1 -0
- package/dist/lib/providers/gemini/index.js +8 -0
- package/dist/lib/providers/gemini/index.js.map +1 -0
- package/dist/lib/providers/index.d.ts +8 -0
- package/dist/lib/providers/index.d.ts.map +1 -0
- package/dist/lib/providers/index.js +8 -0
- package/dist/lib/providers/index.js.map +1 -0
- package/dist/lib/providers/qwen/index.d.ts +8 -0
- package/dist/lib/providers/qwen/index.d.ts.map +1 -0
- package/dist/lib/providers/qwen/index.js +8 -0
- package/dist/lib/providers/qwen/index.js.map +1 -0
- package/dist/lib/providers/qwen/qwen_client.d.ts +154 -0
- package/dist/lib/providers/qwen/qwen_client.d.ts.map +1 -0
- package/dist/lib/providers/qwen/qwen_client.js +1002 -0
- package/dist/lib/providers/qwen/qwen_client.js.map +1 -0
- package/dist/lib/providers/qwen/qwen_provider.d.ts +139 -0
- package/dist/lib/providers/qwen/qwen_provider.d.ts.map +1 -0
- package/dist/lib/providers/qwen/qwen_provider.js +304 -0
- package/dist/lib/providers/qwen/qwen_provider.js.map +1 -0
- package/dist/lib/providers/registry.d.ts +66 -0
- package/dist/lib/providers/registry.d.ts.map +1 -0
- package/dist/lib/providers/registry.js +158 -0
- package/dist/lib/providers/registry.js.map +1 -0
- package/dist/lib/providers/types.d.ts +145 -0
- package/dist/lib/providers/types.d.ts.map +1 -0
- package/dist/lib/providers/types.js +37 -0
- package/dist/lib/providers/types.js.map +1 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +50 -0
- package/dist/server.js.map +1 -0
- package/package.json +12 -1
|
@@ -0,0 +1,1220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM API Module
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the LLM API functionality.
|
|
5
|
+
* Provides initialization and specialized LLM functions:
|
|
6
|
+
* - hazo_llm_text_text: Text input → Text output
|
|
7
|
+
* - hazo_llm_image_text: Image input → Text output
|
|
8
|
+
* - hazo_llm_text_image: Text input → Image output
|
|
9
|
+
* - hazo_llm_image_image: Image input → Image output
|
|
10
|
+
* - hazo_llm_text_image_text: Text → Image → Text (chained)
|
|
11
|
+
* - hazo_llm_image_image_text: Images → Image → Text (chained)
|
|
12
|
+
*
|
|
13
|
+
* Database is auto-initialized on module import using config defaults.
|
|
14
|
+
*/
|
|
15
|
+
import { LLM_ERROR_CODES } from './types.js';
|
|
16
|
+
import { initialize_database, get_database } from '../database/init_database.js';
|
|
17
|
+
import { hazo_llm_text_text as hazo_llm_text_text_internal } from './hazo_llm_text_text.js';
|
|
18
|
+
import { hazo_llm_image_text as hazo_llm_image_text_internal } from './hazo_llm_image_text.js';
|
|
19
|
+
import { hazo_llm_text_image as hazo_llm_text_image_internal } from './hazo_llm_text_image.js';
|
|
20
|
+
import { hazo_llm_image_image as hazo_llm_image_image_internal } from './hazo_llm_image_image.js';
|
|
21
|
+
import { hazo_llm_text_image_text as hazo_llm_text_image_text_internal } from './hazo_llm_text_image_text.js';
|
|
22
|
+
import { hazo_llm_image_image_text as hazo_llm_image_image_text_internal } from './hazo_llm_image_image_text.js';
|
|
23
|
+
import { hazo_llm_prompt_chain as hazo_llm_prompt_chain_internal } from './hazo_llm_prompt_chain.js';
|
|
24
|
+
import { register_provider, set_enabled_llms, set_primary_llm, get_primary_llm, get_registered_providers, get_provider, } from '../providers/registry.js';
|
|
25
|
+
import { GeminiProvider } from '../providers/gemini/index.js';
|
|
26
|
+
import { QwenProvider } from '../providers/qwen/index.js';
|
|
27
|
+
import { SERVICE_TYPES } from '../providers/types.js';
|
|
28
|
+
import * as fs from 'fs';
|
|
29
|
+
import * as path from 'path';
|
|
30
|
+
import * as ini from 'ini';
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Module State
|
|
33
|
+
// =============================================================================
|
|
34
|
+
let initialized = false;
|
|
35
|
+
let db_auto_initialized = false;
|
|
36
|
+
let current_config = null;
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Default Logger
|
|
39
|
+
// =============================================================================
|
|
40
|
+
/**
|
|
41
|
+
* Default console logger used when no custom logger is provided
|
|
42
|
+
* Can be used directly or as a fallback in functions
|
|
43
|
+
*/
|
|
44
|
+
export const default_logger = {
|
|
45
|
+
error: (message, meta) => {
|
|
46
|
+
console.error(`[HAZO_LLM_API ERROR] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
|
|
47
|
+
},
|
|
48
|
+
info: (message, meta) => {
|
|
49
|
+
console.log(`[HAZO_LLM_API INFO] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
|
|
50
|
+
},
|
|
51
|
+
warn: (message, meta) => {
|
|
52
|
+
console.warn(`[HAZO_LLM_API WARN] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
|
|
53
|
+
},
|
|
54
|
+
debug: (message, meta) => {
|
|
55
|
+
console.debug(`[HAZO_LLM_API DEBUG] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Stored logger instance - set during initialization
|
|
60
|
+
*/
|
|
61
|
+
let stored_logger = default_logger;
|
|
62
|
+
/**
|
|
63
|
+
* Stored hooks instance - set during initialization
|
|
64
|
+
*/
|
|
65
|
+
let stored_hooks = {};
|
|
66
|
+
/**
|
|
67
|
+
* Get the current logger instance
|
|
68
|
+
* Returns the stored logger (set during initialization) or default logger
|
|
69
|
+
*
|
|
70
|
+
* @returns Current logger instance
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { get_logger } from 'hazo_llm_api/server';
|
|
75
|
+
*
|
|
76
|
+
* const logger = get_logger();
|
|
77
|
+
* logger.info('My message');
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function get_logger() {
|
|
81
|
+
return stored_logger;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Set the logger instance
|
|
85
|
+
* Called internally during initialization, but can also be called directly
|
|
86
|
+
*
|
|
87
|
+
* @param logger - Logger instance to use
|
|
88
|
+
*/
|
|
89
|
+
export function set_logger(logger) {
|
|
90
|
+
stored_logger = logger;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get the current hooks configuration
|
|
94
|
+
*
|
|
95
|
+
* @returns Current hooks configuration
|
|
96
|
+
*/
|
|
97
|
+
export function get_hooks() {
|
|
98
|
+
return stored_hooks;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Set the hooks configuration
|
|
102
|
+
* Called internally during initialization, but can also be called directly
|
|
103
|
+
*
|
|
104
|
+
* @param hooks - Hooks configuration to use
|
|
105
|
+
*/
|
|
106
|
+
export function set_hooks(hooks) {
|
|
107
|
+
stored_hooks = hooks;
|
|
108
|
+
}
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// Config Reader
|
|
111
|
+
// =============================================================================
|
|
112
|
+
/**
|
|
113
|
+
* Find the config file path
|
|
114
|
+
* Searches in config/ subdirectory and parent directories
|
|
115
|
+
* @returns The path to the config file or null if not found
|
|
116
|
+
*/
|
|
117
|
+
function find_config_file() {
|
|
118
|
+
const config_filename = 'hazo_llm_api_config.ini';
|
|
119
|
+
// Search paths: config/ in current dir, parent dir, grandparent dir
|
|
120
|
+
const search_paths = [
|
|
121
|
+
path.join(process.cwd(), 'config', config_filename),
|
|
122
|
+
path.join(process.cwd(), '..', 'config', config_filename),
|
|
123
|
+
path.join(process.cwd(), '..', '..', 'config', config_filename),
|
|
124
|
+
];
|
|
125
|
+
for (const config_path of search_paths) {
|
|
126
|
+
try {
|
|
127
|
+
if (fs.existsSync(config_path)) {
|
|
128
|
+
return config_path;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Continue to next path
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Read sqlite_path from hazo_llm_api_config.ini file
|
|
139
|
+
* Searches in current directory and parent directories
|
|
140
|
+
*/
|
|
141
|
+
function get_sqlite_path_from_config() {
|
|
142
|
+
const default_path = 'prompt_library.sqlite';
|
|
143
|
+
const config_path = find_config_file();
|
|
144
|
+
if (config_path) {
|
|
145
|
+
try {
|
|
146
|
+
const config_content = fs.readFileSync(config_path, 'utf-8');
|
|
147
|
+
const config = ini.parse(config_content);
|
|
148
|
+
const sqlite_path = config.llm?.sqlite_path;
|
|
149
|
+
if (sqlite_path) {
|
|
150
|
+
default_logger.debug('Found sqlite_path in config', {
|
|
151
|
+
file: 'index.ts',
|
|
152
|
+
line: 137,
|
|
153
|
+
data: { config_path, sqlite_path },
|
|
154
|
+
});
|
|
155
|
+
return sqlite_path;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Use default
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
default_logger.debug('Using default sqlite_path', {
|
|
163
|
+
file: 'index.ts',
|
|
164
|
+
line: 150,
|
|
165
|
+
data: { default_path },
|
|
166
|
+
});
|
|
167
|
+
return default_path;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Parse a generation config section from the ini file
|
|
171
|
+
* Only includes parameters that are explicitly set (not commented out)
|
|
172
|
+
* @param section - The parsed ini section object
|
|
173
|
+
* @returns GeminiGenerationConfig or undefined if no params set
|
|
174
|
+
*/
|
|
175
|
+
function parse_generation_config(section) {
|
|
176
|
+
if (!section) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const config = {};
|
|
180
|
+
let has_params = false;
|
|
181
|
+
// Parse temperature (number)
|
|
182
|
+
if (section.temperature !== undefined) {
|
|
183
|
+
const temp = parseFloat(section.temperature);
|
|
184
|
+
if (!isNaN(temp)) {
|
|
185
|
+
config.temperature = temp;
|
|
186
|
+
has_params = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Parse maxOutputTokens / max_output_tokens (number)
|
|
190
|
+
const max_tokens = section.maxOutputTokens || section.max_output_tokens;
|
|
191
|
+
if (max_tokens !== undefined) {
|
|
192
|
+
const tokens = parseInt(max_tokens, 10);
|
|
193
|
+
if (!isNaN(tokens)) {
|
|
194
|
+
config.max_output_tokens = tokens;
|
|
195
|
+
has_params = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Parse topP / top_p (number)
|
|
199
|
+
const top_p = section.topP || section.top_p;
|
|
200
|
+
if (top_p !== undefined) {
|
|
201
|
+
const p = parseFloat(top_p);
|
|
202
|
+
if (!isNaN(p)) {
|
|
203
|
+
config.top_p = p;
|
|
204
|
+
has_params = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Parse topK / top_k (number)
|
|
208
|
+
const top_k = section.topK || section.top_k;
|
|
209
|
+
if (top_k !== undefined) {
|
|
210
|
+
const k = parseInt(top_k, 10);
|
|
211
|
+
if (!isNaN(k)) {
|
|
212
|
+
config.top_k = k;
|
|
213
|
+
has_params = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Parse candidateCount / candidate_count (number)
|
|
217
|
+
const candidate_count = section.candidateCount || section.candidate_count;
|
|
218
|
+
if (candidate_count !== undefined) {
|
|
219
|
+
const count = parseInt(candidate_count, 10);
|
|
220
|
+
if (!isNaN(count)) {
|
|
221
|
+
config.candidate_count = count;
|
|
222
|
+
has_params = true;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Parse stopSequences / stop_sequences (JSON array string)
|
|
226
|
+
const stop_sequences = section.stopSequences || section.stop_sequences;
|
|
227
|
+
if (stop_sequences !== undefined) {
|
|
228
|
+
try {
|
|
229
|
+
const sequences = JSON.parse(stop_sequences);
|
|
230
|
+
if (Array.isArray(sequences) && sequences.length > 0) {
|
|
231
|
+
config.stop_sequences = sequences;
|
|
232
|
+
has_params = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Invalid JSON, skip
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Parse responseMimeType / response_mime_type (string)
|
|
240
|
+
const response_mime_type = section.responseMimeType || section.response_mime_type;
|
|
241
|
+
if (response_mime_type !== undefined) {
|
|
242
|
+
config.response_mime_type = response_mime_type;
|
|
243
|
+
has_params = true;
|
|
244
|
+
}
|
|
245
|
+
return has_params ? config : undefined;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Parse generation config from a section with optional prefix
|
|
249
|
+
* Supports prefixed configs like "text_temperature" or "image_temperature"
|
|
250
|
+
* @param section - The parsed ini section object
|
|
251
|
+
* @param prefix - Optional prefix to filter keys (e.g., "text_" or "image_")
|
|
252
|
+
* @returns GeminiGenerationConfig or undefined if no params set
|
|
253
|
+
*/
|
|
254
|
+
function parse_prefixed_generation_config(section, prefix) {
|
|
255
|
+
if (!section) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
const config = {};
|
|
259
|
+
let has_params = false;
|
|
260
|
+
// Helper to get value with or without prefix
|
|
261
|
+
const get_value = (key) => {
|
|
262
|
+
if (prefix) {
|
|
263
|
+
return section[`${prefix}${key}`];
|
|
264
|
+
}
|
|
265
|
+
return section[key];
|
|
266
|
+
};
|
|
267
|
+
// Parse temperature (number)
|
|
268
|
+
const temp = get_value('temperature');
|
|
269
|
+
if (temp !== undefined) {
|
|
270
|
+
const temp_val = parseFloat(temp);
|
|
271
|
+
if (!isNaN(temp_val)) {
|
|
272
|
+
config.temperature = temp_val;
|
|
273
|
+
has_params = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Parse maxOutputTokens (number)
|
|
277
|
+
const max_tokens = get_value('maxOutputTokens') || get_value('max_output_tokens');
|
|
278
|
+
if (max_tokens !== undefined) {
|
|
279
|
+
const tokens = parseInt(max_tokens, 10);
|
|
280
|
+
if (!isNaN(tokens)) {
|
|
281
|
+
config.max_output_tokens = tokens;
|
|
282
|
+
has_params = true;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Parse topP (number)
|
|
286
|
+
const top_p = get_value('topP') || get_value('top_p');
|
|
287
|
+
if (top_p !== undefined) {
|
|
288
|
+
const p = parseFloat(top_p);
|
|
289
|
+
if (!isNaN(p)) {
|
|
290
|
+
config.top_p = p;
|
|
291
|
+
has_params = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Parse topK (number)
|
|
295
|
+
const top_k = get_value('topK') || get_value('top_k');
|
|
296
|
+
if (top_k !== undefined) {
|
|
297
|
+
const k = parseInt(top_k, 10);
|
|
298
|
+
if (!isNaN(k)) {
|
|
299
|
+
config.top_k = k;
|
|
300
|
+
has_params = true;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Parse candidateCount (number)
|
|
304
|
+
const candidate_count = get_value('candidateCount') || get_value('candidate_count');
|
|
305
|
+
if (candidate_count !== undefined) {
|
|
306
|
+
const count = parseInt(candidate_count, 10);
|
|
307
|
+
if (!isNaN(count)) {
|
|
308
|
+
config.candidate_count = count;
|
|
309
|
+
has_params = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Parse stopSequences (JSON array string)
|
|
313
|
+
const stop_sequences = get_value('stopSequences') || get_value('stop_sequences');
|
|
314
|
+
if (stop_sequences !== undefined) {
|
|
315
|
+
try {
|
|
316
|
+
const sequences = JSON.parse(stop_sequences);
|
|
317
|
+
if (Array.isArray(sequences) && sequences.length > 0) {
|
|
318
|
+
config.stop_sequences = sequences;
|
|
319
|
+
has_params = true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// Invalid JSON, skip
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Parse responseMimeType (string)
|
|
327
|
+
const response_mime_type = get_value('responseMimeType') || get_value('response_mime_type');
|
|
328
|
+
if (response_mime_type !== undefined) {
|
|
329
|
+
config.response_mime_type = response_mime_type;
|
|
330
|
+
has_params = true;
|
|
331
|
+
}
|
|
332
|
+
return has_params ? config : undefined;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Parse capabilities from config value (JSON array or comma-separated)
|
|
336
|
+
* @param value - Capabilities value from config
|
|
337
|
+
* @returns Array of ServiceType or empty array
|
|
338
|
+
*/
|
|
339
|
+
function parse_capabilities(value) {
|
|
340
|
+
if (!value) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
// Try parsing as JSON array first
|
|
345
|
+
const parsed = JSON.parse(value);
|
|
346
|
+
if (Array.isArray(parsed)) {
|
|
347
|
+
return parsed.filter(cap => Object.values(SERVICE_TYPES).includes(cap));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Not JSON, try comma-separated
|
|
352
|
+
const caps = value.split(',').map(c => c.trim()).filter(Boolean);
|
|
353
|
+
return caps.filter(cap => Object.values(SERVICE_TYPES).includes(cap));
|
|
354
|
+
}
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Parse enabled_llms from config (JSON array or comma-separated)
|
|
359
|
+
* @param value - Enabled LLMs value from config
|
|
360
|
+
* @returns Array of LLM names
|
|
361
|
+
*/
|
|
362
|
+
function parse_enabled_llms(value) {
|
|
363
|
+
if (!value) {
|
|
364
|
+
return ['gemini']; // Default to gemini
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
// Try parsing as JSON array first
|
|
368
|
+
const parsed = JSON.parse(value);
|
|
369
|
+
if (Array.isArray(parsed)) {
|
|
370
|
+
return parsed.filter((name) => typeof name === 'string' && name.length > 0);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// Not JSON, try comma-separated
|
|
375
|
+
return value.split(',').map(name => name.trim()).filter(Boolean);
|
|
376
|
+
}
|
|
377
|
+
return [];
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Load API key from environment variable
|
|
381
|
+
* @param provider_name - Provider name (e.g., "gemini")
|
|
382
|
+
* @returns API key or undefined if not found
|
|
383
|
+
*/
|
|
384
|
+
function load_api_key_from_env(provider_name) {
|
|
385
|
+
// Try provider-specific env var: GEMINI_API_KEY, OPENAI_API_KEY, etc.
|
|
386
|
+
const env_var_name = `${provider_name.toUpperCase()}_API_KEY`;
|
|
387
|
+
return process.env[env_var_name];
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Read LLM global config from [llm] section
|
|
391
|
+
* @returns Object with enabled_llms and primary_llm
|
|
392
|
+
*/
|
|
393
|
+
function get_llm_global_config() {
|
|
394
|
+
const config_path = find_config_file();
|
|
395
|
+
const default_enabled = ['gemini'];
|
|
396
|
+
const default_primary = 'gemini';
|
|
397
|
+
const default_sqlite = 'prompt_library.sqlite';
|
|
398
|
+
if (!config_path) {
|
|
399
|
+
return {
|
|
400
|
+
enabled_llms: default_enabled,
|
|
401
|
+
primary_llm: default_primary,
|
|
402
|
+
sqlite_path: default_sqlite,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
const config_content = fs.readFileSync(config_path, 'utf-8');
|
|
407
|
+
const config = ini.parse(config_content);
|
|
408
|
+
const llm_section = config.llm || {};
|
|
409
|
+
const enabled_llms = parse_enabled_llms(llm_section.enabled_llms);
|
|
410
|
+
const primary_llm = llm_section.primary_llm || default_primary;
|
|
411
|
+
const sqlite_path = llm_section.sqlite_path || default_sqlite;
|
|
412
|
+
return {
|
|
413
|
+
enabled_llms: enabled_llms.length > 0 ? enabled_llms : default_enabled,
|
|
414
|
+
primary_llm,
|
|
415
|
+
sqlite_path,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
return {
|
|
420
|
+
enabled_llms: default_enabled,
|
|
421
|
+
primary_llm: default_primary,
|
|
422
|
+
sqlite_path: default_sqlite,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Load and initialize Gemini provider from config
|
|
428
|
+
* @param logger - Logger instance
|
|
429
|
+
* @returns GeminiProvider instance or null if config invalid
|
|
430
|
+
*/
|
|
431
|
+
function load_gemini_provider_from_config(logger) {
|
|
432
|
+
const config_path = find_config_file();
|
|
433
|
+
if (!config_path) {
|
|
434
|
+
logger.warn('Config file not found, cannot load Gemini provider', {
|
|
435
|
+
file: 'index.ts',
|
|
436
|
+
line: 340,
|
|
437
|
+
});
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const config_content = fs.readFileSync(config_path, 'utf-8');
|
|
442
|
+
const config = ini.parse(config_content);
|
|
443
|
+
const gemini_section = config.llm_gemini || {};
|
|
444
|
+
// Support custom env var name via api_key_env config option
|
|
445
|
+
const env_var_name = gemini_section.api_key_env || 'GEMINI_API_KEY';
|
|
446
|
+
const api_key = process.env[env_var_name];
|
|
447
|
+
if (!api_key) {
|
|
448
|
+
logger.error(`${env_var_name} not found in environment variables`, {
|
|
449
|
+
file: 'index.ts',
|
|
450
|
+
line: 352,
|
|
451
|
+
data: { config_path, env_var_name },
|
|
452
|
+
});
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
// Parse capabilities
|
|
456
|
+
const capabilities = parse_capabilities(gemini_section.capabilities);
|
|
457
|
+
// Parse generation configs with prefixes
|
|
458
|
+
const text_config = parse_prefixed_generation_config(gemini_section, 'text_');
|
|
459
|
+
const image_config = parse_prefixed_generation_config(gemini_section, 'image_');
|
|
460
|
+
const provider_config = {
|
|
461
|
+
api_key,
|
|
462
|
+
api_url: gemini_section.api_url,
|
|
463
|
+
api_url_image: gemini_section.api_url_image,
|
|
464
|
+
model_text_text: gemini_section.model_text_text,
|
|
465
|
+
model_image_text: gemini_section.model_image_text,
|
|
466
|
+
model_text_image: gemini_section.model_text_image,
|
|
467
|
+
model_image_image: gemini_section.model_image_image,
|
|
468
|
+
text_config,
|
|
469
|
+
image_config,
|
|
470
|
+
capabilities: capabilities.length > 0 ? capabilities : undefined,
|
|
471
|
+
logger,
|
|
472
|
+
};
|
|
473
|
+
return new GeminiProvider(provider_config);
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
477
|
+
logger.error('Failed to load Gemini provider from config', {
|
|
478
|
+
file: 'index.ts',
|
|
479
|
+
line: 378,
|
|
480
|
+
data: { error: error_message, config_path },
|
|
481
|
+
});
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Parse Qwen generation config from a section with optional prefix
|
|
487
|
+
* Supports prefixed configs like "text_temperature" or "image_temperature"
|
|
488
|
+
* @param section - The parsed ini section object
|
|
489
|
+
* @param prefix - Optional prefix to filter keys (e.g., "text_" or "image_")
|
|
490
|
+
* @returns QwenGenerationConfig or undefined if no params set
|
|
491
|
+
*/
|
|
492
|
+
function parse_prefixed_qwen_generation_config(section, prefix) {
|
|
493
|
+
if (!section) {
|
|
494
|
+
return undefined;
|
|
495
|
+
}
|
|
496
|
+
const config = {};
|
|
497
|
+
let has_params = false;
|
|
498
|
+
// Helper to get value with or without prefix
|
|
499
|
+
const get_value = (key) => {
|
|
500
|
+
if (prefix) {
|
|
501
|
+
return section[`${prefix}${key}`];
|
|
502
|
+
}
|
|
503
|
+
return section[key];
|
|
504
|
+
};
|
|
505
|
+
// Parse temperature (number)
|
|
506
|
+
const temp = get_value('temperature');
|
|
507
|
+
if (temp !== undefined) {
|
|
508
|
+
const temp_val = parseFloat(temp);
|
|
509
|
+
if (!isNaN(temp_val)) {
|
|
510
|
+
config.temperature = temp_val;
|
|
511
|
+
has_params = true;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Parse max_tokens (number)
|
|
515
|
+
const max_tokens = get_value('max_tokens');
|
|
516
|
+
if (max_tokens !== undefined) {
|
|
517
|
+
const tokens = parseInt(max_tokens, 10);
|
|
518
|
+
if (!isNaN(tokens)) {
|
|
519
|
+
config.max_tokens = tokens;
|
|
520
|
+
has_params = true;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Parse top_p (number)
|
|
524
|
+
const top_p = get_value('top_p');
|
|
525
|
+
if (top_p !== undefined) {
|
|
526
|
+
const p = parseFloat(top_p);
|
|
527
|
+
if (!isNaN(p)) {
|
|
528
|
+
config.top_p = p;
|
|
529
|
+
has_params = true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// Parse top_k (number)
|
|
533
|
+
const top_k = get_value('top_k');
|
|
534
|
+
if (top_k !== undefined) {
|
|
535
|
+
const k = parseInt(top_k, 10);
|
|
536
|
+
if (!isNaN(k)) {
|
|
537
|
+
config.top_k = k;
|
|
538
|
+
has_params = true;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Parse n (number)
|
|
542
|
+
const n = get_value('n');
|
|
543
|
+
if (n !== undefined) {
|
|
544
|
+
const n_val = parseInt(n, 10);
|
|
545
|
+
if (!isNaN(n_val)) {
|
|
546
|
+
config.n = n_val;
|
|
547
|
+
has_params = true;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Parse stop (JSON array string)
|
|
551
|
+
const stop = get_value('stop');
|
|
552
|
+
if (stop !== undefined) {
|
|
553
|
+
try {
|
|
554
|
+
const sequences = JSON.parse(stop);
|
|
555
|
+
if (Array.isArray(sequences) && sequences.length > 0) {
|
|
556
|
+
config.stop = sequences;
|
|
557
|
+
has_params = true;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
// Invalid JSON, skip
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Parse presence_penalty (number)
|
|
565
|
+
const presence_penalty = get_value('presence_penalty');
|
|
566
|
+
if (presence_penalty !== undefined) {
|
|
567
|
+
const penalty = parseFloat(presence_penalty);
|
|
568
|
+
if (!isNaN(penalty)) {
|
|
569
|
+
config.presence_penalty = penalty;
|
|
570
|
+
has_params = true;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
// Parse frequency_penalty (number)
|
|
574
|
+
const frequency_penalty = get_value('frequency_penalty');
|
|
575
|
+
if (frequency_penalty !== undefined) {
|
|
576
|
+
const penalty = parseFloat(frequency_penalty);
|
|
577
|
+
if (!isNaN(penalty)) {
|
|
578
|
+
config.frequency_penalty = penalty;
|
|
579
|
+
has_params = true;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return has_params ? config : undefined;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Load and initialize Qwen provider from config
|
|
586
|
+
* @param logger - Logger instance
|
|
587
|
+
* @returns QwenProvider instance or null if config invalid
|
|
588
|
+
*/
|
|
589
|
+
function load_qwen_provider_from_config(logger) {
|
|
590
|
+
const config_path = find_config_file();
|
|
591
|
+
if (!config_path) {
|
|
592
|
+
logger.warn('Config file not found, cannot load Qwen provider', {
|
|
593
|
+
file: 'index.ts',
|
|
594
|
+
line: 500,
|
|
595
|
+
});
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
const config_content = fs.readFileSync(config_path, 'utf-8');
|
|
600
|
+
const config = ini.parse(config_content);
|
|
601
|
+
const qwen_section = config.llm_qwen || {};
|
|
602
|
+
// Support custom env var name via api_key_env config option
|
|
603
|
+
const env_var_name = qwen_section.api_key_env || 'QWEN_API_KEY';
|
|
604
|
+
const api_key = process.env[env_var_name];
|
|
605
|
+
if (!api_key) {
|
|
606
|
+
logger.error(`${env_var_name} not found in environment variables`, {
|
|
607
|
+
file: 'index.ts',
|
|
608
|
+
line: 512,
|
|
609
|
+
data: { config_path, env_var_name },
|
|
610
|
+
});
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
// Parse capabilities
|
|
614
|
+
const capabilities = parse_capabilities(qwen_section.capabilities);
|
|
615
|
+
// Parse generation configs with prefixes
|
|
616
|
+
const text_config = parse_prefixed_qwen_generation_config(qwen_section, 'text_');
|
|
617
|
+
const image_config = parse_prefixed_qwen_generation_config(qwen_section, 'image_');
|
|
618
|
+
const provider_config = {
|
|
619
|
+
api_key,
|
|
620
|
+
api_url: qwen_section.api_url,
|
|
621
|
+
model_text_text: qwen_section.model_text_text,
|
|
622
|
+
model_image_text: qwen_section.model_image_text,
|
|
623
|
+
model_text_image: qwen_section.model_text_image,
|
|
624
|
+
model_image_image: qwen_section.model_image_image,
|
|
625
|
+
api_url_text_text: qwen_section.api_url_text_text,
|
|
626
|
+
api_url_image_text: qwen_section.api_url_image_text,
|
|
627
|
+
api_url_text_image: qwen_section.api_url_text_image,
|
|
628
|
+
api_url_image_image: qwen_section.api_url_image_image,
|
|
629
|
+
system_instruction: qwen_section.system_instruction,
|
|
630
|
+
text_config,
|
|
631
|
+
image_config,
|
|
632
|
+
capabilities: capabilities.length > 0 ? capabilities : undefined,
|
|
633
|
+
logger,
|
|
634
|
+
};
|
|
635
|
+
return new QwenProvider(provider_config);
|
|
636
|
+
}
|
|
637
|
+
catch (error) {
|
|
638
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
639
|
+
logger.error('Failed to load Qwen provider from config', {
|
|
640
|
+
file: 'index.ts',
|
|
641
|
+
line: 543,
|
|
642
|
+
data: { error: error_message, config_path },
|
|
643
|
+
});
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Load and register all enabled providers from config file
|
|
649
|
+
* @param logger - Logger instance
|
|
650
|
+
*/
|
|
651
|
+
function load_and_register_providers(logger) {
|
|
652
|
+
const global_config = get_llm_global_config();
|
|
653
|
+
// Set enabled LLMs and primary LLM in registry
|
|
654
|
+
set_enabled_llms(global_config.enabled_llms);
|
|
655
|
+
set_primary_llm(global_config.primary_llm);
|
|
656
|
+
logger.info('Loading LLM providers from config', {
|
|
657
|
+
file: 'index.ts',
|
|
658
|
+
line: 395,
|
|
659
|
+
data: {
|
|
660
|
+
enabled_llms: global_config.enabled_llms,
|
|
661
|
+
primary_llm: global_config.primary_llm,
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
// Load each enabled provider
|
|
665
|
+
for (const llm_name of global_config.enabled_llms) {
|
|
666
|
+
if (llm_name.toLowerCase() === 'gemini') {
|
|
667
|
+
const provider = load_gemini_provider_from_config(logger);
|
|
668
|
+
if (provider) {
|
|
669
|
+
register_provider(provider);
|
|
670
|
+
logger.info('Registered Gemini provider', {
|
|
671
|
+
file: 'index.ts',
|
|
672
|
+
line: 636,
|
|
673
|
+
data: {
|
|
674
|
+
capabilities: Array.from(provider.get_capabilities()),
|
|
675
|
+
},
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
logger.warn('Gemini provider is enabled in config but failed to load. Check GEMINI_API_KEY in environment variables.', {
|
|
680
|
+
file: 'index.ts',
|
|
681
|
+
line: 716,
|
|
682
|
+
data: { llm_name: llm_name.toLowerCase() },
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else if (llm_name.toLowerCase() === 'qwen') {
|
|
687
|
+
const provider = load_qwen_provider_from_config(logger);
|
|
688
|
+
if (provider) {
|
|
689
|
+
register_provider(provider);
|
|
690
|
+
logger.info('Registered Qwen provider', {
|
|
691
|
+
file: 'index.ts',
|
|
692
|
+
line: 646,
|
|
693
|
+
data: {
|
|
694
|
+
capabilities: Array.from(provider.get_capabilities()),
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
logger.warn('Qwen provider is enabled in config but failed to load. Check QWEN_API_KEY in environment variables.', {
|
|
700
|
+
file: 'index.ts',
|
|
701
|
+
line: 728,
|
|
702
|
+
data: { llm_name: llm_name.toLowerCase() },
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// Future: Add other providers here (OpenAI, Anthropic, etc.)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// =============================================================================
|
|
710
|
+
// Auto-initialization on Import
|
|
711
|
+
// =============================================================================
|
|
712
|
+
/**
|
|
713
|
+
* Auto-initialize database when module is imported
|
|
714
|
+
* Uses config file defaults, does not require API key
|
|
715
|
+
*/
|
|
716
|
+
async function auto_initialize_database() {
|
|
717
|
+
if (db_auto_initialized) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const file_name = 'index.ts (llm_api)';
|
|
721
|
+
try {
|
|
722
|
+
const sqlite_path = get_sqlite_path_from_config();
|
|
723
|
+
default_logger.info('Auto-initializing database on module import', {
|
|
724
|
+
file: file_name,
|
|
725
|
+
line: 110,
|
|
726
|
+
data: { sqlite_path },
|
|
727
|
+
});
|
|
728
|
+
await initialize_database(sqlite_path, default_logger);
|
|
729
|
+
db_auto_initialized = true;
|
|
730
|
+
default_logger.info('Database auto-initialized successfully', {
|
|
731
|
+
file: file_name,
|
|
732
|
+
line: 118,
|
|
733
|
+
data: { sqlite_path },
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
738
|
+
default_logger.error('Failed to auto-initialize database', {
|
|
739
|
+
file: file_name,
|
|
740
|
+
line: 124,
|
|
741
|
+
data: { error: error_message },
|
|
742
|
+
});
|
|
743
|
+
// Don't throw - allow manual initialization later
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// Trigger auto-initialization when module is imported
|
|
747
|
+
// Using void to handle the promise without blocking
|
|
748
|
+
void auto_initialize_database();
|
|
749
|
+
// =============================================================================
|
|
750
|
+
// Initialization Function
|
|
751
|
+
// =============================================================================
|
|
752
|
+
/**
|
|
753
|
+
* Initialize the LLM API with the given configuration
|
|
754
|
+
* Creates/connects to the database and prepares the client for use
|
|
755
|
+
*
|
|
756
|
+
* @param config - Configuration options for the LLM API (all fields optional)
|
|
757
|
+
* @returns Initialized LLM API client
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```typescript
|
|
761
|
+
* // Minimal initialization (uses defaults)
|
|
762
|
+
* const api = await initialize_llm_api({});
|
|
763
|
+
*
|
|
764
|
+
* // With custom logger
|
|
765
|
+
* const api = await initialize_llm_api({ logger: myLogger });
|
|
766
|
+
*
|
|
767
|
+
* // With custom database path
|
|
768
|
+
* const api = await initialize_llm_api({ sqlite_path: '~/data/prompts.db' });
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
export async function initialize_llm_api(config = {}) {
|
|
772
|
+
const file_name = 'index.ts (llm_api)';
|
|
773
|
+
// Use provided logger or default
|
|
774
|
+
const logger = config.logger || default_logger;
|
|
775
|
+
// Store the logger for use by other functions
|
|
776
|
+
set_logger(logger);
|
|
777
|
+
// Store hooks if provided
|
|
778
|
+
if (config.hooks) {
|
|
779
|
+
set_hooks(config.hooks);
|
|
780
|
+
}
|
|
781
|
+
// Get global config from file
|
|
782
|
+
const global_config = get_llm_global_config();
|
|
783
|
+
// Use provided sqlite_path or fall back to config file value
|
|
784
|
+
const sqlite_path = config.sqlite_path || global_config.sqlite_path;
|
|
785
|
+
// Load and register providers from config file
|
|
786
|
+
load_and_register_providers(logger);
|
|
787
|
+
// Validate that primary_llm is enabled
|
|
788
|
+
const primary_llm_name = get_primary_llm();
|
|
789
|
+
if (!primary_llm_name) {
|
|
790
|
+
const error_msg = 'No primary LLM configured. Set primary_llm in [llm] section of config file.';
|
|
791
|
+
logger.error(error_msg, {
|
|
792
|
+
file: file_name,
|
|
793
|
+
line: 615,
|
|
794
|
+
});
|
|
795
|
+
throw new Error(error_msg);
|
|
796
|
+
}
|
|
797
|
+
logger.info('Initializing LLM API', {
|
|
798
|
+
file: file_name,
|
|
799
|
+
line: 620,
|
|
800
|
+
data: {
|
|
801
|
+
sqlite_path,
|
|
802
|
+
enabled_llms: global_config.enabled_llms,
|
|
803
|
+
primary_llm: primary_llm_name,
|
|
804
|
+
},
|
|
805
|
+
});
|
|
806
|
+
// Set final config
|
|
807
|
+
const final_config = {
|
|
808
|
+
logger,
|
|
809
|
+
sqlite_path,
|
|
810
|
+
hooks: config.hooks,
|
|
811
|
+
};
|
|
812
|
+
// Initialize the database (async)
|
|
813
|
+
try {
|
|
814
|
+
await initialize_database(sqlite_path, logger);
|
|
815
|
+
initialized = true;
|
|
816
|
+
current_config = final_config;
|
|
817
|
+
logger.info('LLM API initialized successfully', {
|
|
818
|
+
file: file_name,
|
|
819
|
+
line: 645,
|
|
820
|
+
data: {
|
|
821
|
+
primary_llm: primary_llm_name,
|
|
822
|
+
registered_providers: get_registered_providers(),
|
|
823
|
+
},
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
828
|
+
logger.error('Failed to initialize LLM API', {
|
|
829
|
+
file: file_name,
|
|
830
|
+
line: 653,
|
|
831
|
+
data: { error: error_message },
|
|
832
|
+
});
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
// Create and return the client instance
|
|
836
|
+
const client = {
|
|
837
|
+
config: final_config,
|
|
838
|
+
db_initialized: initialized,
|
|
839
|
+
hazo_llm_text_text: async (params, llm) => {
|
|
840
|
+
return hazo_llm_text_text(params, llm);
|
|
841
|
+
},
|
|
842
|
+
hazo_llm_image_text: async (params, llm) => {
|
|
843
|
+
return hazo_llm_image_text(params, llm);
|
|
844
|
+
},
|
|
845
|
+
hazo_llm_text_image: async (params, llm) => {
|
|
846
|
+
return hazo_llm_text_image(params, llm);
|
|
847
|
+
},
|
|
848
|
+
hazo_llm_image_image: async (params, llm) => {
|
|
849
|
+
return hazo_llm_image_image(params, llm);
|
|
850
|
+
},
|
|
851
|
+
hazo_llm_text_image_text: async (params, llm) => {
|
|
852
|
+
return hazo_llm_text_image_text(params, llm);
|
|
853
|
+
},
|
|
854
|
+
hazo_llm_image_image_text: async (params, llm) => {
|
|
855
|
+
return hazo_llm_image_image_text(params, llm);
|
|
856
|
+
},
|
|
857
|
+
hazo_llm_prompt_chain: async (params, llm) => {
|
|
858
|
+
return hazo_llm_prompt_chain(params, llm);
|
|
859
|
+
},
|
|
860
|
+
};
|
|
861
|
+
return client;
|
|
862
|
+
}
|
|
863
|
+
// =============================================================================
|
|
864
|
+
// Module Level Functions
|
|
865
|
+
// =============================================================================
|
|
866
|
+
/**
|
|
867
|
+
* Helper to check full LLM API initialization (required for LLM calls)
|
|
868
|
+
* Ensures logger is always present in returned config
|
|
869
|
+
*/
|
|
870
|
+
function check_initialized() {
|
|
871
|
+
if (!initialized || !current_config) {
|
|
872
|
+
throw new Error('LLM API not initialized. Call initialize_llm_api first.');
|
|
873
|
+
}
|
|
874
|
+
// Ensure logger is always present
|
|
875
|
+
return {
|
|
876
|
+
...current_config,
|
|
877
|
+
logger: current_config.logger || get_logger(),
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Check if database has been initialized (either auto or manual)
|
|
882
|
+
* @returns true if database is ready for use
|
|
883
|
+
*/
|
|
884
|
+
export function is_database_ready() {
|
|
885
|
+
return db_auto_initialized || initialized;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Wait for auto-initialization to complete
|
|
889
|
+
* Useful if you need to ensure database is ready before operations
|
|
890
|
+
*/
|
|
891
|
+
export async function ensure_database_ready() {
|
|
892
|
+
if (db_auto_initialized || initialized) {
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
// If not yet initialized, trigger it now
|
|
896
|
+
await auto_initialize_database();
|
|
897
|
+
return db_auto_initialized;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Text input → Text output
|
|
901
|
+
* Standard text generation using LLM
|
|
902
|
+
*
|
|
903
|
+
* @param params - Text input parameters
|
|
904
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
905
|
+
* @returns LLM response with generated text
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* ```typescript
|
|
909
|
+
* import { hazo_llm_text_text, LLM_PROVIDERS } from 'hazo_llm_api/server';
|
|
910
|
+
*
|
|
911
|
+
* const response = await hazo_llm_text_text({ prompt: 'Hello' }, LLM_PROVIDERS.GEMINI);
|
|
912
|
+
* ```
|
|
913
|
+
*/
|
|
914
|
+
export async function hazo_llm_text_text(params, llm) {
|
|
915
|
+
try {
|
|
916
|
+
const config = check_initialized();
|
|
917
|
+
const db = get_database();
|
|
918
|
+
return hazo_llm_text_text_internal(params, db, config, llm);
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Image input → Text output
|
|
926
|
+
* Analyze an image and get text description
|
|
927
|
+
*
|
|
928
|
+
* @param params - Image input parameters
|
|
929
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
930
|
+
* @returns LLM response with text description
|
|
931
|
+
*/
|
|
932
|
+
export async function hazo_llm_image_text(params, llm) {
|
|
933
|
+
try {
|
|
934
|
+
const config = check_initialized();
|
|
935
|
+
const db = get_database();
|
|
936
|
+
return hazo_llm_image_text_internal(params, db, config, llm);
|
|
937
|
+
}
|
|
938
|
+
catch (error) {
|
|
939
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Text input → Image output
|
|
944
|
+
* Generate an image from text description
|
|
945
|
+
*
|
|
946
|
+
* @param params - Text input parameters for image generation
|
|
947
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
948
|
+
* @returns LLM response with generated image
|
|
949
|
+
*/
|
|
950
|
+
export async function hazo_llm_text_image(params, llm) {
|
|
951
|
+
try {
|
|
952
|
+
const config = check_initialized();
|
|
953
|
+
const db = get_database();
|
|
954
|
+
return hazo_llm_text_image_internal(params, db, config, llm);
|
|
955
|
+
}
|
|
956
|
+
catch (error) {
|
|
957
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Image input → Image output
|
|
962
|
+
* Transform/edit an image based on instructions
|
|
963
|
+
*
|
|
964
|
+
* @param params - Image input parameters with transformation instructions
|
|
965
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
966
|
+
* @returns LLM response with transformed image
|
|
967
|
+
*/
|
|
968
|
+
export async function hazo_llm_image_image(params, llm) {
|
|
969
|
+
try {
|
|
970
|
+
const config = check_initialized();
|
|
971
|
+
const db = get_database();
|
|
972
|
+
return hazo_llm_image_image_internal(params, db, config, llm);
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Text → Image → Text (Chained)
|
|
980
|
+
* Generate an image from prompt_image, then analyze it with prompt_text
|
|
981
|
+
*
|
|
982
|
+
* @param params - Parameters with two prompts: one for image gen, one for analysis
|
|
983
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
984
|
+
* @returns LLM response with generated image and analysis text
|
|
985
|
+
*/
|
|
986
|
+
export async function hazo_llm_text_image_text(params, llm) {
|
|
987
|
+
try {
|
|
988
|
+
const config = check_initialized();
|
|
989
|
+
const db = get_database();
|
|
990
|
+
return hazo_llm_text_image_text_internal(params, db, config, llm);
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Images → Image → Text (Chained)
|
|
998
|
+
* Chain multiple image transformations, then describe the final result
|
|
999
|
+
*
|
|
1000
|
+
* @param params - Parameters with images, prompts, and description prompt
|
|
1001
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
1002
|
+
* @returns LLM response with final image and description text
|
|
1003
|
+
*/
|
|
1004
|
+
export async function hazo_llm_image_image_text(params, llm) {
|
|
1005
|
+
try {
|
|
1006
|
+
const config = check_initialized();
|
|
1007
|
+
const db = get_database();
|
|
1008
|
+
return hazo_llm_image_image_text_internal(params, db, config, llm);
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Execute a chain of prompts with dynamic value resolution
|
|
1016
|
+
* Each call can reference values from previous call results
|
|
1017
|
+
*
|
|
1018
|
+
* @param params - Chain parameters including call definitions
|
|
1019
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified). Use LLM_PROVIDERS constants for type safety.
|
|
1020
|
+
* @returns Chain response with merged results and individual call outcomes
|
|
1021
|
+
*
|
|
1022
|
+
* @example
|
|
1023
|
+
* ```typescript
|
|
1024
|
+
* import { hazo_llm_prompt_chain } from 'hazo_llm_api/server';
|
|
1025
|
+
*
|
|
1026
|
+
* const response = await hazo_llm_prompt_chain({
|
|
1027
|
+
* chain_calls: [
|
|
1028
|
+
* {
|
|
1029
|
+
* prompt_area: { match_type: 'direct', value: 'document' },
|
|
1030
|
+
* prompt_key: { match_type: 'direct', value: 'initial_read' }
|
|
1031
|
+
* },
|
|
1032
|
+
* {
|
|
1033
|
+
* prompt_area: { match_type: 'direct', value: 'document' },
|
|
1034
|
+
* prompt_key: { match_type: 'direct', value: 'process_results' },
|
|
1035
|
+
* local_1: {
|
|
1036
|
+
* match_type: 'call_chain',
|
|
1037
|
+
* value: 'call[0].tax_category',
|
|
1038
|
+
* variable_name: 'previous_category'
|
|
1039
|
+
* }
|
|
1040
|
+
* }
|
|
1041
|
+
* ]
|
|
1042
|
+
* });
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
export async function hazo_llm_prompt_chain(params, llm) {
|
|
1046
|
+
try {
|
|
1047
|
+
const config = check_initialized();
|
|
1048
|
+
const db = get_database();
|
|
1049
|
+
return hazo_llm_prompt_chain_internal(params, db, config, llm);
|
|
1050
|
+
}
|
|
1051
|
+
catch (error) {
|
|
1052
|
+
return {
|
|
1053
|
+
success: false,
|
|
1054
|
+
merged_result: {},
|
|
1055
|
+
call_results: [],
|
|
1056
|
+
errors: [{ call_index: -1, error: error instanceof Error ? error.message : String(error) }],
|
|
1057
|
+
total_calls: params.chain_calls.length,
|
|
1058
|
+
successful_calls: 0,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
// =============================================================================
|
|
1063
|
+
// Streaming Functions
|
|
1064
|
+
// =============================================================================
|
|
1065
|
+
/**
|
|
1066
|
+
* Text input → Text output (Streaming)
|
|
1067
|
+
* Generate text from a prompt with streaming response
|
|
1068
|
+
*
|
|
1069
|
+
* @param params - Text input parameters
|
|
1070
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified)
|
|
1071
|
+
* @returns Async generator yielding text chunks
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* ```typescript
|
|
1075
|
+
* const stream = await hazo_llm_text_text_stream({ prompt: 'Tell me a story' });
|
|
1076
|
+
*
|
|
1077
|
+
* for await (const chunk of stream) {
|
|
1078
|
+
* if (chunk.error) {
|
|
1079
|
+
* console.error(chunk.error);
|
|
1080
|
+
* break;
|
|
1081
|
+
* }
|
|
1082
|
+
* process.stdout.write(chunk.text);
|
|
1083
|
+
* if (chunk.done) break;
|
|
1084
|
+
* }
|
|
1085
|
+
* ```
|
|
1086
|
+
*/
|
|
1087
|
+
export async function* hazo_llm_text_text_stream(params, llm) {
|
|
1088
|
+
try {
|
|
1089
|
+
const config = check_initialized();
|
|
1090
|
+
const logger = config.logger || get_logger();
|
|
1091
|
+
// Get provider
|
|
1092
|
+
const provider = get_provider(llm, logger);
|
|
1093
|
+
if (!provider) {
|
|
1094
|
+
yield {
|
|
1095
|
+
text: '',
|
|
1096
|
+
done: true,
|
|
1097
|
+
error: `Provider "${llm || 'primary'}" not found`,
|
|
1098
|
+
error_info: {
|
|
1099
|
+
code: LLM_ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
1100
|
+
message: `Provider "${llm || 'primary'}" not found`,
|
|
1101
|
+
retryable: false,
|
|
1102
|
+
},
|
|
1103
|
+
};
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
// Check if provider supports streaming
|
|
1107
|
+
if (!provider.text_text_stream) {
|
|
1108
|
+
yield {
|
|
1109
|
+
text: '',
|
|
1110
|
+
done: true,
|
|
1111
|
+
error: `Provider "${provider.get_name()}" does not support streaming`,
|
|
1112
|
+
error_info: {
|
|
1113
|
+
code: LLM_ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
|
|
1114
|
+
message: `Provider "${provider.get_name()}" does not support streaming for text_text`,
|
|
1115
|
+
retryable: false,
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
// Call streaming method
|
|
1121
|
+
const stream = await provider.text_text_stream(params, logger);
|
|
1122
|
+
yield* stream;
|
|
1123
|
+
}
|
|
1124
|
+
catch (error) {
|
|
1125
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
1126
|
+
yield {
|
|
1127
|
+
text: '',
|
|
1128
|
+
done: true,
|
|
1129
|
+
error: error_message,
|
|
1130
|
+
error_info: {
|
|
1131
|
+
code: LLM_ERROR_CODES.UNKNOWN,
|
|
1132
|
+
message: error_message,
|
|
1133
|
+
retryable: false,
|
|
1134
|
+
},
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Image input → Text output (Streaming)
|
|
1140
|
+
* Analyze an image and stream text description
|
|
1141
|
+
*
|
|
1142
|
+
* @param params - Image input parameters
|
|
1143
|
+
* @param llm - Optional LLM provider name (uses primary LLM if not specified)
|
|
1144
|
+
* @returns Async generator yielding text chunks
|
|
1145
|
+
*/
|
|
1146
|
+
export async function* hazo_llm_image_text_stream(params, llm) {
|
|
1147
|
+
try {
|
|
1148
|
+
const config = check_initialized();
|
|
1149
|
+
const logger = config.logger || get_logger();
|
|
1150
|
+
// Get provider
|
|
1151
|
+
const provider = get_provider(llm, logger);
|
|
1152
|
+
if (!provider) {
|
|
1153
|
+
yield {
|
|
1154
|
+
text: '',
|
|
1155
|
+
done: true,
|
|
1156
|
+
error: `Provider "${llm || 'primary'}" not found`,
|
|
1157
|
+
error_info: {
|
|
1158
|
+
code: LLM_ERROR_CODES.PROVIDER_NOT_FOUND,
|
|
1159
|
+
message: `Provider "${llm || 'primary'}" not found`,
|
|
1160
|
+
retryable: false,
|
|
1161
|
+
},
|
|
1162
|
+
};
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
// Check if provider supports streaming
|
|
1166
|
+
if (!provider.image_text_stream) {
|
|
1167
|
+
yield {
|
|
1168
|
+
text: '',
|
|
1169
|
+
done: true,
|
|
1170
|
+
error: `Provider "${provider.get_name()}" does not support streaming`,
|
|
1171
|
+
error_info: {
|
|
1172
|
+
code: LLM_ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
|
|
1173
|
+
message: `Provider "${provider.get_name()}" does not support streaming for image_text`,
|
|
1174
|
+
retryable: false,
|
|
1175
|
+
},
|
|
1176
|
+
};
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
// Call streaming method
|
|
1180
|
+
const stream = await provider.image_text_stream(params, logger);
|
|
1181
|
+
yield* stream;
|
|
1182
|
+
}
|
|
1183
|
+
catch (error) {
|
|
1184
|
+
const error_message = error instanceof Error ? error.message : String(error);
|
|
1185
|
+
yield {
|
|
1186
|
+
text: '',
|
|
1187
|
+
done: true,
|
|
1188
|
+
error: error_message,
|
|
1189
|
+
error_info: {
|
|
1190
|
+
code: LLM_ERROR_CODES.UNKNOWN,
|
|
1191
|
+
message: error_message,
|
|
1192
|
+
retryable: false,
|
|
1193
|
+
},
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
// =============================================================================
|
|
1198
|
+
// Utility Functions
|
|
1199
|
+
// =============================================================================
|
|
1200
|
+
/**
|
|
1201
|
+
* Check if the LLM API has been initialized
|
|
1202
|
+
* @returns true if initialized
|
|
1203
|
+
*/
|
|
1204
|
+
export function is_initialized() {
|
|
1205
|
+
return initialized;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Get the current configuration (without sensitive logger)
|
|
1209
|
+
* @returns Current configuration or null if not initialized
|
|
1210
|
+
*/
|
|
1211
|
+
export function get_current_config() {
|
|
1212
|
+
if (!current_config) {
|
|
1213
|
+
return null;
|
|
1214
|
+
}
|
|
1215
|
+
return {
|
|
1216
|
+
sqlite_path: current_config.sqlite_path,
|
|
1217
|
+
hooks: current_config.hooks,
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
//# sourceMappingURL=index.js.map
|