hazo_llm_api 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +536 -0
  2. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.d.ts +16 -0
  3. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.d.ts.map +1 -0
  4. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.js +258 -0
  5. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.js.map +1 -0
  6. package/dist/components/hazo_llm_prompt_config/index.d.ts +8 -0
  7. package/dist/components/hazo_llm_prompt_config/index.d.ts.map +1 -0
  8. package/dist/components/hazo_llm_prompt_config/index.js +7 -0
  9. package/dist/components/hazo_llm_prompt_config/index.js.map +1 -0
  10. package/dist/components/hazo_llm_prompt_config/types.d.ts +74 -0
  11. package/dist/components/hazo_llm_prompt_config/types.d.ts.map +1 -0
  12. package/dist/components/hazo_llm_prompt_config/types.js +8 -0
  13. package/dist/components/hazo_llm_prompt_config/types.js.map +1 -0
  14. package/dist/components/index.d.ts +7 -0
  15. package/dist/components/index.d.ts.map +1 -0
  16. package/dist/components/index.js +7 -0
  17. package/dist/components/index.js.map +1 -0
  18. package/dist/components/layout/index.d.ts +7 -0
  19. package/dist/components/layout/index.d.ts.map +1 -0
  20. package/dist/components/layout/index.js +7 -0
  21. package/dist/components/layout/index.js.map +1 -0
  22. package/dist/components/layout/layout.d.ts +21 -0
  23. package/dist/components/layout/layout.d.ts.map +1 -0
  24. package/dist/components/layout/layout.js +18 -0
  25. package/dist/components/layout/layout.js.map +1 -0
  26. package/dist/index.d.ts +11 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +13 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/lib/config/config_parser.d.ts +131 -0
  31. package/dist/lib/config/config_parser.d.ts.map +1 -0
  32. package/dist/lib/config/config_parser.js +297 -0
  33. package/dist/lib/config/config_parser.js.map +1 -0
  34. package/dist/lib/config/index.d.ts +8 -0
  35. package/dist/lib/config/index.d.ts.map +1 -0
  36. package/dist/lib/config/index.js +22 -0
  37. package/dist/lib/config/index.js.map +1 -0
  38. package/dist/lib/config/provider_loader.d.ts +113 -0
  39. package/dist/lib/config/provider_loader.d.ts.map +1 -0
  40. package/dist/lib/config/provider_loader.js +169 -0
  41. package/dist/lib/config/provider_loader.js.map +1 -0
  42. package/dist/lib/database/index.d.ts +8 -0
  43. package/dist/lib/database/index.d.ts.map +1 -0
  44. package/dist/lib/database/index.js +8 -0
  45. package/dist/lib/database/index.js.map +1 -0
  46. package/dist/lib/database/init_database.d.ts +62 -0
  47. package/dist/lib/database/init_database.d.ts.map +1 -0
  48. package/dist/lib/database/init_database.js +436 -0
  49. package/dist/lib/database/init_database.js.map +1 -0
  50. package/dist/lib/database/utils.d.ts +50 -0
  51. package/dist/lib/database/utils.d.ts.map +1 -0
  52. package/dist/lib/database/utils.js +78 -0
  53. package/dist/lib/database/utils.js.map +1 -0
  54. package/dist/lib/index.d.ts +14 -0
  55. package/dist/lib/index.d.ts.map +1 -0
  56. package/dist/lib/index.js +17 -0
  57. package/dist/lib/index.js.map +1 -0
  58. package/dist/lib/llm_api/hazo_llm_image_image.d.ts +26 -0
  59. package/dist/lib/llm_api/hazo_llm_image_image.d.ts.map +1 -0
  60. package/dist/lib/llm_api/hazo_llm_image_image.js +92 -0
  61. package/dist/lib/llm_api/hazo_llm_image_image.js.map +1 -0
  62. package/dist/lib/llm_api/hazo_llm_image_image_text.d.ts +26 -0
  63. package/dist/lib/llm_api/hazo_llm_image_image_text.d.ts.map +1 -0
  64. package/dist/lib/llm_api/hazo_llm_image_image_text.js +220 -0
  65. package/dist/lib/llm_api/hazo_llm_image_image_text.js.map +1 -0
  66. package/dist/lib/llm_api/hazo_llm_image_text.d.ts +20 -0
  67. package/dist/lib/llm_api/hazo_llm_image_text.d.ts.map +1 -0
  68. package/dist/lib/llm_api/hazo_llm_image_text.js +76 -0
  69. package/dist/lib/llm_api/hazo_llm_image_text.js.map +1 -0
  70. package/dist/lib/llm_api/hazo_llm_text_image.d.ts +20 -0
  71. package/dist/lib/llm_api/hazo_llm_text_image.d.ts.map +1 -0
  72. package/dist/lib/llm_api/hazo_llm_text_image.js +67 -0
  73. package/dist/lib/llm_api/hazo_llm_text_image.js.map +1 -0
  74. package/dist/lib/llm_api/hazo_llm_text_image_text.d.ts +26 -0
  75. package/dist/lib/llm_api/hazo_llm_text_image_text.d.ts.map +1 -0
  76. package/dist/lib/llm_api/hazo_llm_text_image_text.js +152 -0
  77. package/dist/lib/llm_api/hazo_llm_text_image_text.js.map +1 -0
  78. package/dist/lib/llm_api/hazo_llm_text_text.d.ts +20 -0
  79. package/dist/lib/llm_api/hazo_llm_text_text.d.ts.map +1 -0
  80. package/dist/lib/llm_api/hazo_llm_text_text.js +89 -0
  81. package/dist/lib/llm_api/hazo_llm_text_text.js.map +1 -0
  82. package/dist/lib/llm_api/index.d.ts +99 -0
  83. package/dist/lib/llm_api/index.d.ts.map +1 -0
  84. package/dist/lib/llm_api/index.js +952 -0
  85. package/dist/lib/llm_api/index.js.map +1 -0
  86. package/dist/lib/llm_api/provider_helper.d.ts +107 -0
  87. package/dist/lib/llm_api/provider_helper.d.ts.map +1 -0
  88. package/dist/lib/llm_api/provider_helper.js +159 -0
  89. package/dist/lib/llm_api/provider_helper.js.map +1 -0
  90. package/dist/lib/llm_api/types.d.ts +352 -0
  91. package/dist/lib/llm_api/types.d.ts.map +1 -0
  92. package/dist/lib/llm_api/types.js +8 -0
  93. package/dist/lib/llm_api/types.js.map +1 -0
  94. package/dist/lib/prompts/get_prompt.d.ts +50 -0
  95. package/dist/lib/prompts/get_prompt.d.ts.map +1 -0
  96. package/dist/lib/prompts/get_prompt.js +232 -0
  97. package/dist/lib/prompts/get_prompt.js.map +1 -0
  98. package/dist/lib/prompts/index.d.ts +9 -0
  99. package/dist/lib/prompts/index.d.ts.map +1 -0
  100. package/dist/lib/prompts/index.js +9 -0
  101. package/dist/lib/prompts/index.js.map +1 -0
  102. package/dist/lib/prompts/prompt_cache.d.ts +151 -0
  103. package/dist/lib/prompts/prompt_cache.d.ts.map +1 -0
  104. package/dist/lib/prompts/prompt_cache.js +276 -0
  105. package/dist/lib/prompts/prompt_cache.js.map +1 -0
  106. package/dist/lib/prompts/substitute_variables.d.ts +38 -0
  107. package/dist/lib/prompts/substitute_variables.d.ts.map +1 -0
  108. package/dist/lib/prompts/substitute_variables.js +175 -0
  109. package/dist/lib/prompts/substitute_variables.js.map +1 -0
  110. package/dist/lib/providers/gemini/gemini_client.d.ts +25 -0
  111. package/dist/lib/providers/gemini/gemini_client.d.ts.map +1 -0
  112. package/dist/lib/providers/gemini/gemini_client.js +235 -0
  113. package/dist/lib/providers/gemini/gemini_client.js.map +1 -0
  114. package/dist/lib/providers/gemini/gemini_provider.d.ts +111 -0
  115. package/dist/lib/providers/gemini/gemini_provider.d.ts.map +1 -0
  116. package/dist/lib/providers/gemini/gemini_provider.js +431 -0
  117. package/dist/lib/providers/gemini/gemini_provider.js.map +1 -0
  118. package/dist/lib/providers/gemini/index.d.ts +8 -0
  119. package/dist/lib/providers/gemini/index.d.ts.map +1 -0
  120. package/dist/lib/providers/gemini/index.js +8 -0
  121. package/dist/lib/providers/gemini/index.js.map +1 -0
  122. package/dist/lib/providers/index.d.ts +8 -0
  123. package/dist/lib/providers/index.d.ts.map +1 -0
  124. package/dist/lib/providers/index.js +8 -0
  125. package/dist/lib/providers/index.js.map +1 -0
  126. package/dist/lib/providers/qwen/index.d.ts +8 -0
  127. package/dist/lib/providers/qwen/index.d.ts.map +1 -0
  128. package/dist/lib/providers/qwen/index.js +8 -0
  129. package/dist/lib/providers/qwen/index.js.map +1 -0
  130. package/dist/lib/providers/qwen/qwen_client.d.ts +154 -0
  131. package/dist/lib/providers/qwen/qwen_client.d.ts.map +1 -0
  132. package/dist/lib/providers/qwen/qwen_client.js +1002 -0
  133. package/dist/lib/providers/qwen/qwen_client.js.map +1 -0
  134. package/dist/lib/providers/qwen/qwen_provider.d.ts +139 -0
  135. package/dist/lib/providers/qwen/qwen_provider.d.ts.map +1 -0
  136. package/dist/lib/providers/qwen/qwen_provider.js +304 -0
  137. package/dist/lib/providers/qwen/qwen_provider.js.map +1 -0
  138. package/dist/lib/providers/registry.d.ts +66 -0
  139. package/dist/lib/providers/registry.d.ts.map +1 -0
  140. package/dist/lib/providers/registry.js +158 -0
  141. package/dist/lib/providers/registry.js.map +1 -0
  142. package/dist/lib/providers/types.d.ts +95 -0
  143. package/dist/lib/providers/types.d.ts.map +1 -0
  144. package/dist/lib/providers/types.js +19 -0
  145. package/dist/lib/providers/types.js.map +1 -0
  146. package/dist/server.d.ts +21 -0
  147. package/dist/server.d.ts.map +1 -0
  148. package/dist/server.js +32 -0
  149. package/dist/server.js.map +1 -0
  150. package/package.json +68 -0
  151. package/techdoc.md +857 -0
package/techdoc.md ADDED
@@ -0,0 +1,857 @@
1
+ # hazo_llm_api - Technical Documentation
2
+
3
+ ## Architecture Overview
4
+
5
+ ### Package Structure
6
+
7
+ The `hazo_llm_api` package is an ES module package providing:
8
+ - **Multi-Provider LLM System** with centralized registry and provider abstraction
9
+ - **LLM API wrappers** for multiple providers (Gemini, Qwen, extensible to OpenAI, Anthropic, etc.)
10
+ - **Prompt management system** with SQLite storage, LRU caching, and variable substitution
11
+ - **React components** for UI (Layout)
12
+ - **Configuration system** using INI files with environment variable support
13
+
14
+ ```
15
+ src/
16
+ ├── index.ts # Client entry point (components + types)
17
+ ├── server.ts # Server entry point (LLM APIs, database)
18
+ ├── components/
19
+ │ └── layout/ # React UI components
20
+ └── lib/
21
+ ├── llm_api/ # Core LLM service functions
22
+ │ ├── index.ts # Module exports and initialization
23
+ │ ├── provider_helper.ts # Provider lookup/validation helpers
24
+ │ ├── hazo_llm_*.ts # Service functions (text_text, image_text, etc.)
25
+ │ └── types.ts # Type definitions
26
+ ├── providers/ # LLM provider implementations
27
+ │ ├── registry.ts # Provider registration and lookup
28
+ │ ├── types.ts # Provider interface definitions
29
+ │ ├── gemini/ # Gemini provider
30
+ │ └── qwen/ # Qwen provider
31
+ ├── config/ # Configuration utilities
32
+ │ ├── config_parser.ts # INI file parsing, generation config
33
+ │ └── provider_loader.ts # Provider factory and loading
34
+ ├── database/ # SQLite database layer
35
+ │ ├── init_database.ts # Database initialization and CRUD
36
+ │ └── utils.ts # Shared database utilities
37
+ └── prompts/ # Prompt management
38
+ ├── get_prompt.ts # Prompt retrieval
39
+ ├── substitute_variables.ts # Variable substitution
40
+ └── prompt_cache.ts # LRU prompt caching
41
+ ```
42
+
43
+ ### Entry Points
44
+
45
+ The package has two entry points for proper client/server separation:
46
+
47
+ | Entry Point | Import Path | Purpose |
48
+ |-------------|-------------|---------|
49
+ | Client | `hazo_llm_api` | React components, types (browser-safe) |
50
+ | Server | `hazo_llm_api/server` | LLM APIs, database ops (Node.js only) |
51
+
52
+ ```typescript
53
+ // Client-side imports
54
+ import { Layout } from 'hazo_llm_api';
55
+ import type { LLMResponse } from 'hazo_llm_api';
56
+
57
+ // Server-side imports
58
+ import { initialize_llm_api, hazo_llm_text_text } from 'hazo_llm_api/server';
59
+ ```
60
+
61
+ ### Module System
62
+
63
+ The package uses ES modules with explicit file paths (`.js` extensions) in export statements, as required by ES module bundlers:
64
+
65
+ ```typescript
66
+ // ✅ CORRECT
67
+ export * from './components/index.js';
68
+
69
+ // ❌ WRONG
70
+ export * from './components';
71
+ ```
72
+
73
+ ### TypeScript Configuration
74
+
75
+ - **Package**: Uses `tsconfig.json` for development and `tsconfig.build.json` for production builds
76
+ - **Module Resolution**: `node16` for package builds (not `bundler`)
77
+ - **Module System**: `ESNext` for modern ES module output
78
+ - **Type**: Set to `"module"` in package.json
79
+
80
+ ### Build Process
81
+
82
+ 1. TypeScript compilation using `tsc`
83
+ 2. Source files from `src/` are compiled to `dist/`
84
+ 3. Declaration files (`.d.ts`) are generated
85
+ 4. Source maps are included for debugging
86
+
87
+ ## Test Application Architecture
88
+
89
+ ### Technology Stack
90
+
91
+ - **Framework**: Next.js 14 (App Router)
92
+ - **Language**: TypeScript
93
+ - **Styling**: TailwindCSS
94
+ - **UI Components**: Shadcn/UI
95
+ - **Icons**: Lucide React
96
+
97
+ ### Project Structure
98
+
99
+ ```
100
+ test-app/
101
+ ├── app/ # Next.js App Router directory
102
+ │ ├── layout.tsx # Root layout
103
+ │ ├── page.tsx # Home page
104
+ │ └── globals.css # Global styles
105
+ ├── components/
106
+ │ └── sidebar.tsx # Sidebar navigation component
107
+ ├── lib/
108
+ │ └── utils.ts # Utility functions
109
+ ├── next.config.js # Next.js configuration
110
+ ├── tsconfig.json # TypeScript configuration
111
+ ├── tailwind.config.ts # TailwindCSS configuration
112
+ └── components.json # Shadcn/UI configuration
113
+ ```
114
+
115
+ ### Next.js Configuration
116
+
117
+ The test-app uses:
118
+ - React Server Components (RSC) enabled
119
+ - Package transpilation for `hazo_llm_api`
120
+ - App Router structure
121
+
122
+ ```javascript
123
+ transpilePackages: ['hazo_llm_api']
124
+ ```
125
+
126
+ This ensures the local package is properly transpiled by Next.js.
127
+
128
+ ### Styling System
129
+
130
+ TailwindCSS with Shadcn/UI theming:
131
+ - CSS variables for theming
132
+ - Dark mode support
133
+ - Custom color palette
134
+ - Responsive design utilities
135
+
136
+ ## Component Details
137
+
138
+ ### Layout Component
139
+
140
+ **Location**: `src/components/layout/layout.tsx`
141
+
142
+ **Purpose**: Provides a consistent page structure with optional sidebar and header.
143
+
144
+ **Structure**:
145
+ ```tsx
146
+ <Layout>
147
+ <Sidebar />
148
+ <Header />
149
+ <MainContent />
150
+ </Layout>
151
+ ```
152
+
153
+ **Implementation Details**:
154
+ - Flexbox-based layout
155
+ - Full viewport height
156
+ - Sidebar on the left (optional)
157
+ - Header at the top (optional)
158
+ - Scrollable main content area
159
+
160
+ **CSS Classes**:
161
+ - `cls_layout_container` - Main container
162
+ - `cls_layout_sidebar` - Sidebar wrapper
163
+ - `cls_layout_main` - Main content wrapper
164
+ - `cls_layout_header` - Header wrapper
165
+ - `cls_layout_content` - Content area
166
+
167
+ ### Sidebar Component (Test App)
168
+
169
+ **Location**: `test-app/components/sidebar.tsx`
170
+
171
+ **Purpose**: Navigation sidebar for the test application.
172
+
173
+ **Features**:
174
+ - Active route highlighting
175
+ - Icon support via Lucide React
176
+ - Responsive design
177
+ - Client-side navigation
178
+
179
+ ## Configuration System
180
+
181
+ ### Configuration File
182
+
183
+ **File**: `hazo_llm_api_config.ini`
184
+
185
+ **Format**: INI format with sections
186
+
187
+ **Sections**:
188
+ - `[llm]` - Global LLM configuration (enabled_llms, primary_llm, sqlite_path)
189
+ - `[llm_gemini]` - Gemini provider configuration
190
+ - `[llm_qwen]` - Qwen provider configuration
191
+ - `[llm_<provider>]` - Custom provider configurations
192
+ - `[logging]` - Log file paths and settings
193
+ - `[package]` - Build configuration
194
+ - `[test_app]` - Test app settings
195
+ - `[ui]` - UI defaults
196
+ - `[database]` - Database configuration
197
+
198
+ ### Global LLM Configuration
199
+
200
+ The `[llm]` section controls which providers are available:
201
+
202
+ ```ini
203
+ [llm]
204
+ # JSON array or comma-separated list of enabled providers
205
+ enabled_llms=["gemini", "qwen"]
206
+ # Default provider when not specified in API calls
207
+ primary_llm=gemini
208
+ # SQLite database path (relative to app root)
209
+ sqlite_path=prompt_library.sqlite
210
+ ```
211
+
212
+ ### Provider Configuration Sections
213
+
214
+ Each provider has its own `[llm_<name>]` section:
215
+
216
+ ```ini
217
+ [llm_gemini]
218
+ # API endpoints
219
+ api_url=https://generativelanguage.googleapis.com/v1/models/gemini-2.5-flash:generateContent
220
+ api_url_image=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent
221
+
222
+ # Optional per-service model overrides
223
+ model_text_text=gemini-2.5-flash
224
+ model_image_text=gemini-2.5-flash
225
+ model_text_image=gemini-2.5-flash-image
226
+ model_image_image=gemini-2.5-flash-image
227
+
228
+ # Capabilities this provider supports (JSON array)
229
+ capabilities=["text_text", "image_text", "text_image", "image_image"]
230
+
231
+ # Generation parameters (prefixed by service type)
232
+ text_temperature=0.7
233
+ text_maxOutputTokens=1024
234
+ text_topP=0.95
235
+ image_temperature=0.1
236
+ image_topK=20
237
+ ```
238
+
239
+ ### Generation Parameter Prefixes
240
+
241
+ Use `text_` or `image_` prefixes to configure parameters per service type:
242
+
243
+ | Parameter | Type | Description | Example |
244
+ |-----------|------|-------------|---------|
245
+ | `temperature` | number | Randomness (0.0-2.0) | `text_temperature=0.7` |
246
+ | `maxOutputTokens` | number | Max response length | `text_maxOutputTokens=1024` |
247
+ | `topP` | number | Nucleus sampling (0.0-1.0) | `text_topP=0.95` |
248
+ | `topK` | number | Top-k sampling | `image_topK=20` |
249
+ | `stopSequences` | JSON array | Stop sequences | `text_stopSequences=["END"]` |
250
+ | `responseMimeType` | string | Response format | `text_responseMimeType=text/plain` |
251
+
252
+ **Qwen-specific parameters:**
253
+ - `max_tokens` instead of `maxOutputTokens`
254
+ - `top_p` instead of `topP`
255
+ - `top_k` instead of `topK`
256
+ - `stop` instead of `stopSequences`
257
+ - `presence_penalty`, `frequency_penalty`, `n`
258
+
259
+ ### Environment Variables
260
+
261
+ **File**: `.env.local` (not committed to git)
262
+
263
+ **Usage**: Store sensitive API keys
264
+
265
+ **Format**:
266
+ ```bash
267
+ # Provider API keys (uppercase provider name + _API_KEY)
268
+ GEMINI_API_KEY=your_gemini_api_key_here
269
+ QWEN_API_KEY=your_qwen_api_key_here
270
+ OPENAI_API_KEY=your_openai_api_key_here
271
+ ```
272
+
273
+ **Important**:
274
+ - Never commit `.env.local` to git
275
+ - Add to `.gitignore`
276
+ - API key environment variable naming: `<PROVIDER_NAME>_API_KEY`
277
+
278
+ ## Development Workflow
279
+
280
+ ### Package Development
281
+
282
+ 1. Make changes in `src/` directory
283
+ 2. Run `npm run build` to compile
284
+ 3. Test in test-app with `npm run dev:test-app`
285
+ 4. Package uses local symlink in test-app
286
+
287
+ ### Test App Development
288
+
289
+ 1. Make changes in `test-app/` directory
290
+ 2. Changes are hot-reloaded by Next.js
291
+ 3. Access at `http://localhost:3000` (default)
292
+
293
+ ### Building for Production
294
+
295
+ ```bash
296
+ # Build package
297
+ npm run build
298
+
299
+ # Build test-app
300
+ npm run build:test-app
301
+
302
+ # Start test-app
303
+ npm run start:test-app
304
+ ```
305
+
306
+ ## Export System
307
+
308
+ ### Package Exports
309
+
310
+ The package.json defines exports:
311
+
312
+ ```json
313
+ {
314
+ "exports": {
315
+ ".": {
316
+ "import": "./dist/index.js",
317
+ "types": "./dist/index.d.ts"
318
+ }
319
+ }
320
+ }
321
+ ```
322
+
323
+ ### Import Patterns
324
+
325
+ Consumers of the package can import:
326
+
327
+ ```typescript
328
+ // Default import
329
+ import { Layout } from 'hazo_llm_api';
330
+
331
+ // Named imports
332
+ import { Layout } from 'hazo_llm_api';
333
+ ```
334
+
335
+ ## File Naming Conventions
336
+
337
+ - **Components**: `snake_case` (e.g., `layout.tsx`)
338
+ - **Directories**: `snake_case`
339
+ - **CSS Classes**: Prefixed with `cls_` for easy identification
340
+ - **Configuration**: `snake_case` for INI keys
341
+
342
+ ## Code Standards
343
+
344
+ ### Comments
345
+
346
+ - File-level comments describing purpose
347
+ - Function-level comments describing functionality
348
+ - Major section comments for code organization
349
+
350
+ ### TypeScript
351
+
352
+ - Strict mode enabled
353
+ - Explicit types preferred
354
+ - React 18+ types used
355
+
356
+ ### React
357
+
358
+ - Functional components only
359
+ - Hooks for state management
360
+ - Client components marked with `'use client'`
361
+
362
+ ## Dependencies
363
+
364
+ ### Package Dependencies
365
+
366
+ - **Peer Dependencies**: React 18+, React DOM 18+
367
+ - **Dev Dependencies**: TypeScript, type definitions
368
+
369
+ ### Test App Dependencies
370
+
371
+ - **Runtime**: Next.js, React, React DOM
372
+ - **Styling**: TailwindCSS, PostCSS, Autoprefixer
373
+ - **UI**: Shadcn/UI components, Lucide icons
374
+ - **Utilities**: clsx, tailwind-merge
375
+
376
+ ## Logging
377
+
378
+ Logging configuration is stored in `hazo_llm_api_config.ini`:
379
+
380
+ ```ini
381
+ [logging]
382
+ logfile=logs/hazo_llm_api.log
383
+ ```
384
+
385
+ Logs should be:
386
+ - JSON formatted
387
+ - Pretty printed
388
+ - Include filename, line number, message, and relevant data
389
+ - Rotated daily
390
+
391
+ ## LLM Provider System
392
+
393
+ ### Architecture Overview
394
+
395
+ The provider system uses a **centralized registry** pattern:
396
+
397
+ ```
398
+ ┌─────────────────────┐
399
+ │ Service Functions │ (hazo_llm_text_text, etc.)
400
+ └──────────┬──────────┘
401
+
402
+ v
403
+ ┌─────────────────────┐
404
+ │ Provider Registry │ (lookup, validation, capability checking)
405
+ └──────────┬──────────┘
406
+
407
+ v
408
+ ┌─────────────────────┐
409
+ │ Provider Instance │ (GeminiProvider, QwenProvider, etc.)
410
+ └─────────────────────┘
411
+ ```
412
+
413
+ **Key components:**
414
+ 1. **Registry** (`lib/providers/registry.ts`): Central provider management
415
+ 2. **Provider Interface** (`lib/providers/types.ts`): Contract all providers must implement
416
+ 3. **Provider Implementations** (`lib/providers/gemini/`, `lib/providers/qwen/`): Specific LLM integrations
417
+ 4. **Configuration Loader** (`lib/llm_api/index.ts`): Reads config and initializes providers
418
+
419
+ ### Provider Interface
420
+
421
+ All LLM providers implement the `LLMProvider` interface:
422
+
423
+ ```typescript
424
+ interface LLMProvider {
425
+ // Identification and capabilities
426
+ get_name(): string;
427
+ get_capabilities(): Set<ServiceType>;
428
+ get_model_for_service(service_type: ServiceType): string | undefined;
429
+
430
+ // Service implementations
431
+ text_text(params: TextTextParams, logger: Logger): Promise<LLMResponse>;
432
+ image_text(params: ImageTextParams, logger: Logger): Promise<LLMResponse>;
433
+ text_image(params: TextImageParams, logger: Logger): Promise<LLMResponse>;
434
+ image_image(params: ImageImageParams, logger: Logger): Promise<LLMResponse>;
435
+ }
436
+ ```
437
+
438
+ ### Service Types
439
+
440
+ | Service | Constant | Description |
441
+ |---------|----------|-------------|
442
+ | `text_text` | `SERVICE_TYPES.TEXT_TEXT` | Text input → Text output |
443
+ | `image_text` | `SERVICE_TYPES.IMAGE_TEXT` | Image input → Text output (analysis) |
444
+ | `text_image` | `SERVICE_TYPES.TEXT_IMAGE` | Text input → Image output (generation) |
445
+ | `image_image` | `SERVICE_TYPES.IMAGE_IMAGE` | Image input → Image output (transformation) |
446
+
447
+ ### Provider Registry
448
+
449
+ The registry manages all active providers:
450
+
451
+ ```typescript
452
+ // Registration
453
+ register_provider(new GeminiProvider(config));
454
+ register_provider(new QwenProvider(config));
455
+
456
+ // Lookup
457
+ const provider = get_provider('gemini', logger);
458
+
459
+ // Capability validation
460
+ if (validate_capability(provider, 'text_image', logger)) {
461
+ // Provider supports text → image generation
462
+ }
463
+
464
+ // Check enabled status
465
+ if (is_llm_enabled('gemini')) {
466
+ // Provider is enabled in config
467
+ }
468
+ ```
469
+
470
+ **Registry Functions:**
471
+ - `register_provider(provider)` - Add a provider to the registry
472
+ - `get_provider(name, logger)` - Get provider by name (returns primary if name is null)
473
+ - `set_enabled_llms(names)` - Set which providers are enabled
474
+ - `set_primary_llm(name)` - Set the default provider
475
+ - `get_primary_llm()` - Get the default provider name
476
+ - `validate_capability(provider, service_type, logger)` - Check if provider supports a service
477
+ - `clear_registry()` - Clear all providers (for testing)
478
+
479
+ ### Adding a New LLM Provider
480
+
481
+ To add a new provider (e.g., OpenAI), follow these steps:
482
+
483
+ #### Step 1: Create Provider Directory
484
+
485
+ ```
486
+ src/lib/providers/openai/
487
+ ├── index.ts # Exports and provider class
488
+ ├── openai_provider.ts # Provider implementation
489
+ └── openai_client.ts # API client (optional)
490
+ ```
491
+
492
+ #### Step 2: Implement the Provider
493
+
494
+ ```typescript
495
+ // src/lib/providers/openai/openai_provider.ts
496
+ import type { LLMProvider, ServiceType, LLMCapabilities } from '../types.js';
497
+ import type {
498
+ Logger,
499
+ LLMResponse,
500
+ TextTextParams,
501
+ ImageTextParams,
502
+ TextImageParams,
503
+ ImageImageParams,
504
+ } from '../../llm_api/types.js';
505
+
506
+ /**
507
+ * Configuration interface for OpenAI provider
508
+ */
509
+ export interface OpenAIProviderConfig {
510
+ api_key: string;
511
+ api_url?: string;
512
+ model_text_text?: string;
513
+ model_image_text?: string;
514
+ model_text_image?: string;
515
+ capabilities?: ServiceType[];
516
+ logger: Logger;
517
+ // Add any provider-specific config here
518
+ }
519
+
520
+ /**
521
+ * OpenAI LLM Provider Implementation
522
+ */
523
+ export class OpenAIProvider implements LLMProvider {
524
+ private config: OpenAIProviderConfig;
525
+ private capabilities: LLMCapabilities;
526
+
527
+ constructor(config: OpenAIProviderConfig) {
528
+ this.config = config;
529
+ // Default capabilities if not specified
530
+ this.capabilities = new Set(
531
+ config.capabilities || ['text_text', 'image_text']
532
+ );
533
+ }
534
+
535
+ get_name(): string {
536
+ return 'openai';
537
+ }
538
+
539
+ get_capabilities(): LLMCapabilities {
540
+ return this.capabilities;
541
+ }
542
+
543
+ get_model_for_service(service_type: ServiceType): string | undefined {
544
+ const model_map: Record<ServiceType, string | undefined> = {
545
+ text_text: this.config.model_text_text || 'gpt-4',
546
+ image_text: this.config.model_image_text || 'gpt-4-vision-preview',
547
+ text_image: this.config.model_text_image || 'dall-e-3',
548
+ image_image: undefined, // Not supported
549
+ };
550
+ return model_map[service_type];
551
+ }
552
+
553
+ async text_text(params: TextTextParams, logger: Logger): Promise<LLMResponse> {
554
+ try {
555
+ const model = this.get_model_for_service('text_text');
556
+ const response = await fetch(
557
+ this.config.api_url || 'https://api.openai.com/v1/chat/completions',
558
+ {
559
+ method: 'POST',
560
+ headers: {
561
+ 'Authorization': `Bearer ${this.config.api_key}`,
562
+ 'Content-Type': 'application/json',
563
+ },
564
+ body: JSON.stringify({
565
+ model,
566
+ messages: [{ role: 'user', content: params.prompt }],
567
+ }),
568
+ }
569
+ );
570
+
571
+ const data = await response.json();
572
+
573
+ if (!response.ok) {
574
+ logger.error('OpenAI API error', {
575
+ file: 'openai_provider.ts',
576
+ line: 65,
577
+ data: { error: data.error, status: response.status },
578
+ });
579
+ return {
580
+ success: false,
581
+ error: data.error?.message || 'API call failed',
582
+ };
583
+ }
584
+
585
+ return {
586
+ success: true,
587
+ text: data.choices?.[0]?.message?.content,
588
+ raw_response: data,
589
+ };
590
+ } catch (error) {
591
+ const error_message = error instanceof Error ? error.message : String(error);
592
+ logger.error('OpenAI text_text failed', {
593
+ file: 'openai_provider.ts',
594
+ line: 78,
595
+ data: { error: error_message },
596
+ });
597
+ return { success: false, error: error_message };
598
+ }
599
+ }
600
+
601
+ async image_text(params: ImageTextParams, logger: Logger): Promise<LLMResponse> {
602
+ // Implement GPT-4 Vision API call
603
+ return { success: false, error: 'image_text not yet implemented' };
604
+ }
605
+
606
+ async text_image(params: TextImageParams, logger: Logger): Promise<LLMResponse> {
607
+ // Implement DALL-E API call if needed
608
+ return { success: false, error: 'text_image not yet implemented' };
609
+ }
610
+
611
+ async image_image(params: ImageImageParams, logger: Logger): Promise<LLMResponse> {
612
+ // OpenAI doesn't support image-to-image transformation
613
+ return { success: false, error: 'image_image not supported by OpenAI provider' };
614
+ }
615
+ }
616
+ ```
617
+
618
+ #### Step 3: Add Provider Loader to `lib/llm_api/index.ts`
619
+
620
+ Add a loader function similar to `load_gemini_provider_from_config` and `load_qwen_provider_from_config`:
621
+
622
+ ```typescript
623
+ // In lib/llm_api/index.ts, add this function:
624
+
625
+ function load_openai_provider_from_config(logger: Logger): OpenAIProvider | null {
626
+ const config_path = find_config_file();
627
+ if (!config_path) {
628
+ logger.warn('Config file not found, cannot load OpenAI provider', {
629
+ file: 'index.ts',
630
+ line: 540,
631
+ });
632
+ return null;
633
+ }
634
+
635
+ try {
636
+ const config_content = fs.readFileSync(config_path, 'utf-8');
637
+ const config = ini.parse(config_content);
638
+ const openai_section = config.llm_openai || {};
639
+
640
+ // Load API key from environment
641
+ const api_key = load_api_key_from_env('openai');
642
+ if (!api_key) {
643
+ logger.error('OPENAI_API_KEY not found in environment variables', {
644
+ file: 'index.ts',
645
+ line: 552,
646
+ data: { config_path },
647
+ });
648
+ return null;
649
+ }
650
+
651
+ // Parse capabilities
652
+ const capabilities = parse_capabilities(openai_section.capabilities);
653
+
654
+ const provider_config: OpenAIProviderConfig = {
655
+ api_key,
656
+ api_url: openai_section.api_url,
657
+ model_text_text: openai_section.model_text_text,
658
+ model_image_text: openai_section.model_image_text,
659
+ model_text_image: openai_section.model_text_image,
660
+ capabilities: capabilities.length > 0 ? capabilities : undefined,
661
+ logger,
662
+ };
663
+
664
+ return new OpenAIProvider(provider_config);
665
+ } catch (error) {
666
+ const error_message = error instanceof Error ? error.message : String(error);
667
+ logger.error('Failed to load OpenAI provider from config', {
668
+ file: 'index.ts',
669
+ line: 570,
670
+ data: { error: error_message, config_path },
671
+ });
672
+ return null;
673
+ }
674
+ }
675
+
676
+ // Then add to load_and_register_providers function:
677
+ function load_and_register_providers(logger: Logger): void {
678
+ // ... existing code ...
679
+
680
+ for (const llm_name of global_config.enabled_llms) {
681
+ if (llm_name.toLowerCase() === 'gemini') {
682
+ // ... existing Gemini code ...
683
+ } else if (llm_name.toLowerCase() === 'qwen') {
684
+ // ... existing Qwen code ...
685
+ } else if (llm_name.toLowerCase() === 'openai') {
686
+ const provider = load_openai_provider_from_config(logger);
687
+ if (provider) {
688
+ register_provider(provider);
689
+ logger.info('Registered OpenAI provider', {
690
+ file: 'index.ts',
691
+ line: 595,
692
+ data: {
693
+ capabilities: Array.from(provider.get_capabilities()),
694
+ },
695
+ });
696
+ } else {
697
+ logger.warn('OpenAI provider is enabled in config but failed to load', {
698
+ file: 'index.ts',
699
+ line: 602,
700
+ });
701
+ }
702
+ }
703
+ }
704
+ }
705
+ ```
706
+
707
+ #### Step 4: Add to Config File
708
+
709
+ ```ini
710
+ [llm]
711
+ enabled_llms=["gemini", "qwen", "openai"]
712
+ primary_llm=gemini
713
+
714
+ [llm_openai]
715
+ api_url=https://api.openai.com/v1/chat/completions
716
+ model_text_text=gpt-4
717
+ model_image_text=gpt-4-vision-preview
718
+ model_text_image=dall-e-3
719
+ capabilities=["text_text", "image_text", "text_image"]
720
+ text_temperature=0.7
721
+ ```
722
+
723
+ #### Step 5: Add API Key to Environment
724
+
725
+ ```bash
726
+ # .env.local
727
+ OPENAI_API_KEY=your_api_key_here
728
+ ```
729
+
730
+ #### Step 6: Export Provider
731
+
732
+ ```typescript
733
+ // src/lib/providers/openai/index.ts
734
+ export { OpenAIProvider, type OpenAIProviderConfig } from './openai_provider.js';
735
+
736
+ // src/lib/providers/index.ts
737
+ export * from './openai/index.js';
738
+ ```
739
+
740
+ ### Provider Configuration Parameters
741
+
742
+ Common configuration parameters supported by the config parser:
743
+
744
+ | Parameter | Type | Description |
745
+ |-----------|------|-------------|
746
+ | `temperature` | number | Controls randomness (0.0-2.0) |
747
+ | `max_tokens` | number | Maximum response tokens |
748
+ | `top_p` | number | Nucleus sampling (0.0-1.0) |
749
+ | `top_k` | number | Top-k sampling |
750
+ | `stop_sequences` | array | Stop generation sequences |
751
+
752
+ Use prefixes for service-specific config: `text_temperature`, `image_temperature`
753
+
754
+ ## Prompt Management
755
+
756
+ ### Database Schema
757
+
758
+ ```sql
759
+ CREATE TABLE prompts_library (
760
+ uuid TEXT PRIMARY KEY,
761
+ prompt_area TEXT NOT NULL,
762
+ prompt_key TEXT NOT NULL,
763
+ prompt_text TEXT NOT NULL,
764
+ prompt_variables TEXT DEFAULT '[]',
765
+ prompt_notes TEXT DEFAULT '',
766
+ created_at TEXT DEFAULT (datetime('now')),
767
+ changed_by TEXT DEFAULT NULL
768
+ );
769
+
770
+ CREATE INDEX idx_prompts_area_key ON prompts_library(prompt_area, prompt_key);
771
+ ```
772
+
773
+ ### Variable Substitution
774
+
775
+ Use `$variable_name` syntax in prompts:
776
+
777
+ ```typescript
778
+ // Prompt text: "Hello $name, your order #$order_id is ready."
779
+ const response = await hazo_llm_text_text({
780
+ prompt_area: 'notifications',
781
+ prompt_key: 'order_ready',
782
+ prompt_variables: {
783
+ name: 'John',
784
+ order_id: '12345',
785
+ },
786
+ });
787
+ ```
788
+
789
+ ### Prompt Caching
790
+
791
+ The `PromptCache` class implements an LRU (Least Recently Used) cache with TTL support:
792
+
793
+ **Features:**
794
+ - Time-based expiration (TTL)
795
+ - LRU eviction when cache is full
796
+ - Per-entry access tracking
797
+ - Cache statistics (hits, misses, hit rate)
798
+ - Configurable size and TTL
799
+
800
+ **Usage:**
801
+
802
+ ```typescript
803
+ import { PromptCache } from 'hazo_llm_api/server';
804
+
805
+ // Create cache instance
806
+ const cache = new PromptCache({
807
+ ttl_ms: 300000, // 5 minutes
808
+ max_size: 100, // Maximum entries
809
+ enabled: true, // Enable/disable caching
810
+ });
811
+
812
+ // Get from cache
813
+ const prompt = cache.get('marketing', 'greeting');
814
+ if (prompt) {
815
+ // Cache hit - use cached prompt
816
+ } else {
817
+ // Cache miss - fetch from database and cache
818
+ const db_prompt = await get_prompt_by_area_and_key(...);
819
+ if (db_prompt) {
820
+ cache.set(db_prompt);
821
+ }
822
+ }
823
+
824
+ // Invalidate specific entry after updates
825
+ cache.invalidate('marketing', 'greeting');
826
+
827
+ // Clear entire cache
828
+ cache.clear();
829
+
830
+ // Get cache statistics
831
+ const stats = cache.get_stats();
832
+ console.log(`Hit rate: ${stats.hit_rate}%`);
833
+ console.log(`Cache size: ${stats.size}/${stats.max_size}`);
834
+ ```
835
+
836
+ **Cache Key Format:**
837
+ - Format: `{prompt_area}:{prompt_key}`
838
+ - Example: `marketing:greeting`, `notifications:order_ready`
839
+
840
+ **Eviction Policy:**
841
+ - **TTL Expiration**: Entries older than `ttl_ms` are removed
842
+ - **LRU Eviction**: When cache is full, least recently accessed entry is removed
843
+ - **Manual**: Call `invalidate()` or `clear()`
844
+
845
+ **Default Configuration:**
846
+ - TTL: 5 minutes (300,000 ms)
847
+ - Max Size: 100 entries
848
+ - Enabled: true
849
+
850
+ ## Future Considerations
851
+
852
+ - Additional LLM providers (Anthropic, Cohere, etc.)
853
+ - Streaming response support
854
+ - Rate limiting and retry logic
855
+ - Response caching
856
+ - Testing infrastructure
857
+