llm_meta_client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c97f3a183b70b928d55677581100d6774fbf6400140ff9604f883136f4c92c7e
4
- data.tar.gz: 5d6078eabeb97d52b07980cb452cdbbf64ce65599d494c662e875249cd6e6f39
3
+ metadata.gz: b3b7edb6ffb37242cabcfed5f765bd3337565c2e55e1fcce5a6508bb07a53cdf
4
+ data.tar.gz: c1a289098044870e43c86d2df34b223a33af2fd10f59dceaadef38ae0df68800
5
5
  SHA512:
6
- metadata.gz: 268562c7e43e59fd50e3cf38dc06d0164e461f98b3e650df580443de556681573dccdb5db5d82963237d230e150bd238ad20a3d03d21d58337f95c2a00247cd1
7
- data.tar.gz: 6c7baa9d2c77789861727acbd5f1d40ea391a3045df9b3422a780fbe7978d60473db22d6a2d69bc81b7cb48b207e2a664ba2d31f05b6d4372d802c985860e879
6
+ metadata.gz: f1fdb013fca840d2ca0762ba8861902402e80282e58491b95b339fcfce1770203d1035af454f6bcaaa36d56e511df45ed2fb92a1e5d602dae769805b031e040d
7
+ data.tar.gz: 31f06f8324a6626b2b54fe86ef3cb88453ced0c18cccea24e0696721382f793bbee849f6f5505e054188432a5d93df918d77005038c546e6280ec5481fa76418
data/README.md CHANGED
@@ -2,16 +2,48 @@
2
2
 
3
3
  A Rails Engine for integrating multiple LLM providers into your application. Provides scaffold and authentication generators to quickly build LLM-powered chat applications with support for OpenAI, Anthropic, Google, and Ollama.
4
4
 
5
+ ## Architecture
6
+
7
+ This gem is a **Rails frontend client** that delegates all LLM provider interactions to an external backend service. The gem itself does not implement any LLM provider SDKs directly — instead, it communicates with the external service via REST API to manage API keys, models, and chat completions.
8
+
9
+ ```
10
+ ┌─────────────────────┐ REST API ┌─────────────────────┐
11
+ │ Your Rails App │ ───────────────────> │ External LLM │
12
+ │ + llm_meta_client │ <─────────────────── │ Service Backend │
13
+ └─────────────────────┘ └─────────────────────┘
14
+
15
+ ├── OpenAI API
16
+ ├── Anthropic API
17
+ ├── Google API
18
+ └── Ollama (local)
19
+ ```
20
+
5
21
  ## Features
6
22
 
7
23
  - **Scaffold generator** — Generates models, controllers, views, and JavaScript controllers for a full chat UI with Turbo Stream support
8
24
  - **Authentication generator** — Sets up Devise with Google OAuth2 for user authentication
9
25
  - **Core modules** — `ServerQuery`, `ServerResource`, `Helpers`, `ChatManageable`, `HistoryManageable`, and custom exception classes
26
+ - **Hotwire integration** — Real-time UI updates via Turbo Stream with Stimulus controllers
27
+ - **Guest user support** — Ollama can be used without authentication; Google Sign-In is optional
28
+ - **Conversation branching** — Branch from a previous message to explore alternative conversation paths
29
+ - **CSV export** — Download chat history as CSV (single chat or all chats)
30
+ - **Auto-generated titles** — Chat titles are automatically generated by the LLM from the conversation content
31
+ - **Context summarization** — Conversation history is automatically summarized to keep context concise
10
32
 
11
33
  ## Requirements
12
34
 
13
35
  - Ruby >= 3.4
14
36
  - Rails >= 8.1.1
37
+ - An external LLM service backend (see [External LLM Service](#external-llm-service))
38
+
39
+ ## Dependencies
40
+
41
+ | Gem | Version | Purpose |
42
+ |---|---|---|
43
+ | `rails` | `~> 8.1, >= 8.1.1` | Rails framework |
44
+ | `httparty` | `~> 0.22` | HTTP client for external service communication |
45
+ | `prompt_navigator` | `~> 0.1` | Prompt execution management and history |
46
+ | `chat_manager` | `~> 0.1` | Chat sidebar, title generation, CSV export |
15
47
 
16
48
  ## Installation
17
49
 
@@ -27,11 +59,9 @@ And then execute:
27
59
  $ bundle install
28
60
  ```
29
61
 
30
- ## Usage
62
+ ## Quick Start
31
63
 
32
- ### Scaffold Generator
33
-
34
- Generates a complete chat interface with LLM integration:
64
+ ### 1. Run the Scaffold Generator
35
65
 
36
66
  ```bash
37
67
  $ rails generate llm_meta_client:scaffold
@@ -47,9 +77,9 @@ This creates:
47
77
  - **Migrations:** `create_chats`, `create_messages`
48
78
  - **Routes:** Chats resource with `clear`, `start_new`, `download_all_csv`, `download_csv`, `update_title` actions
49
79
 
50
- ### Authentication Generator
80
+ ### 2. (Optional) Run the Authentication Generator
51
81
 
52
- Sets up user authentication with Devise and Google OAuth2:
82
+ If you want user authentication with Google OAuth2:
53
83
 
54
84
  ```bash
55
85
  $ rails generate llm_meta_client:authentication
@@ -64,16 +94,199 @@ This creates:
64
94
  - **Migration:** `create_users`
65
95
  - **Routes:** Devise routes with OmniAuth callbacks
66
96
 
97
+ This generator also adds the following gems to your Gemfile:
98
+
99
+ - `devise`
100
+ - `omniauth`
101
+ - `omniauth-google-oauth2`
102
+ - `omniauth-rails_csrf_protection`
103
+
104
+ Run `bundle install` again after the authentication generator.
105
+
106
+ ### 3. Run Migrations
107
+
108
+ ```bash
109
+ $ rails db:migrate
110
+ ```
111
+
112
+ ### 4. Configure Rails Credentials
113
+
114
+ This gem uses **Rails credentials** (`rails credentials:edit`) instead of environment variables for configuration management.
115
+
116
+ ```bash
117
+ $ EDITOR="vim" bin/rails credentials:edit
118
+ ```
119
+
120
+ Add the following entries to your credentials file:
121
+
122
+ ```yaml
123
+ # Required
124
+ llm_service:
125
+ base_url: "http://localhost:3000" # URL of your external LLM service
126
+
127
+ # Optional
128
+ summarize_conversation_count: 10 # Number of recent messages for context (default: 10)
129
+
130
+ # Required only if using the authentication generator
131
+ google:
132
+ client_id: "your-google-client-id"
133
+ client_secret: "your-google-client-secret"
134
+ ```
135
+
136
+ ### 5. Start the Application
137
+
138
+ Ensure your external LLM service is running, then:
139
+
140
+ ```bash
141
+ $ rails server -p 3001
142
+ ```
143
+
67
144
  ## Configuration
68
145
 
69
- The following environment variables are used:
146
+ All configuration values are managed via **Rails credentials** (`rails credentials:edit`).
147
+
148
+ | Credential Key | Default | Description |
149
+ |---|---|---|
150
+ | `llm_service.base_url` | `http://localhost:3000` | Base URL of the external LLM service backend |
151
+ | `llm_service.summarize_conversation_count` | `10` | Number of recent messages to include in conversation context |
152
+ | `google.client_id` | — | Google OAuth2 client ID (authentication generator) |
153
+ | `google.client_secret` | — | Google OAuth2 client secret (authentication generator) |
154
+
155
+ These values are referenced in the generated initializers `config/initializers/llm_service.rb` and `config/initializers/devise.rb`.
156
+
157
+ ## External LLM Service
158
+
159
+ This gem requires an external LLM service backend that provides the following REST API endpoints:
160
+
161
+ | Method | Endpoint | Auth | Description |
162
+ |---|---|---|---|
163
+ | `GET` | `/api/llms` | None | List all available LLMs (returns Ollama instances, etc.) |
164
+ | `GET` | `/api/llm_api_keys` | Bearer JWT | List API keys for the authenticated user |
165
+ | `POST` | `/api/llm_api_keys/:uuid/models/:model_id/chats` | Bearer JWT (optional) | Send a prompt and receive an LLM response |
166
+
167
+ ### Expected Response Formats
168
+
169
+ **`GET /api/llms`**
170
+
171
+ ```json
172
+ {
173
+ "llms": [
174
+ {
175
+ "uuid": "...",
176
+ "description": "Ollama Local",
177
+ "family": "ollama",
178
+ "llm_type": "ollama",
179
+ "available_models": ["llama3", "mistral"]
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ **`GET /api/llm_api_keys`**
186
+
187
+ ```json
188
+ {
189
+ "llm_api_keys": [
190
+ {
191
+ "uuid": "...",
192
+ "description": "My OpenAI Key",
193
+ "llm_type": "openai",
194
+ "available_models": ["gpt-4", "gpt-3.5-turbo"]
195
+ }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ **`POST /api/llm_api_keys/:uuid/models/:model_id/chats`**
201
+
202
+ Request body:
203
+ ```json
204
+ {
205
+ "prompt": "Context:..., User Prompt: ..."
206
+ }
207
+ ```
208
+
209
+ Response body:
210
+ ```json
211
+ {
212
+ "response": {
213
+ "message": "The assistant's response text"
214
+ }
215
+ }
216
+ ```
217
+
218
+ ## Core Classes
219
+
220
+ ### `LlmMetaClient::ServerQuery`
221
+
222
+ Sends chat prompts to the external LLM service and returns responses. Used internally by the generated `Chat` model.
223
+
224
+ ```ruby
225
+ query = LlmMetaClient::ServerQuery.new
226
+ response = query.call(jwt_token, api_key_uuid, model_id, context, user_content)
227
+ # => "The assistant's response text"
228
+ ```
229
+
230
+ - HTTP timeout: 5 minutes (300 seconds)
231
+ - Responses are synchronous (no streaming)
232
+
233
+ ### `LlmMetaClient::ServerResource`
234
+
235
+ Fetches available LLM providers and models from the external service.
236
+
237
+ ```ruby
238
+ # Get a flat list of available LLM options
239
+ options = LlmMetaClient::ServerResource.available_llm_options(jwt_token)
240
+ # => [{uuid:, description:, llm_type:, available_models:}, ...]
241
+
242
+ # Get options grouped by provider family
243
+ families = LlmMetaClient::ServerResource.available_llm_families(jwt_token)
244
+ # => [{name: "OpenAI", llm_type: "openai", api_keys: [...]}, ...]
245
+ ```
246
+
247
+ - Guest users (`jwt_token` is `nil` or blank) receive only Ollama options
248
+ - Logged-in users receive their API keys plus Ollama (if available)
249
+
250
+ ### Concerns
251
+
252
+ | Module | Purpose |
253
+ |---|---|
254
+ | `LlmMetaClient::Helpers` | View helpers (included automatically via Engine) |
255
+ | `LlmMetaClient::HistoryManageable` | Controller concern for prompt history navigation |
256
+ | `LlmMetaClient::ChatManageable` | Controller concern for chat sidebar management |
257
+
258
+ ## Exception Classes
259
+
260
+ | Exception | Raised When |
261
+ |---|---|
262
+ | `LlmMetaClient::Exceptions::ServerError` | External LLM service returns a non-2xx HTTP status |
263
+ | `LlmMetaClient::Exceptions::InvalidResponseError` | Response from the LLM service is not valid JSON |
264
+ | `LlmMetaClient::Exceptions::EmptyResponseError` | LLM service returns an empty message |
265
+ | `LlmMetaClient::Exceptions::OllamaUnavailableError` | No Ollama instances are registered in the LLM service |
266
+
267
+ ## How It Works
268
+
269
+ Each time a user sends a message, the following happens:
270
+
271
+ 1. A `PromptExecution` record and a user `Message` are created
272
+ 2. If conversation history exists, it is **summarized by the LLM** to keep context concise
273
+ 3. The summarized context + user prompt are sent to the external LLM service
274
+ 4. The assistant response is saved as a new `Message`
275
+ 5. A chat title is auto-generated by the LLM (if not already set)
276
+ 6. Turbo Stream updates the UI in real-time
277
+
278
+ This means each user message may trigger **up to 3 HTTP requests** to the external LLM service (context summarization, main response, title generation), each with a 5-minute timeout.
279
+
280
+ **Note:** Streaming is not currently supported. All LLM responses are synchronous.
281
+
282
+ ## Supported Providers
70
283
 
71
- | Variable | Description | Default |
284
+ | Provider | `llm_type` | Notes |
72
285
  |---|---|---|
73
- | `LLM_SERVICE_BASE_URL` | Base URL for the external LLM service | `http://localhost:3000` |
74
- | `SUMMARIZE_CONVERSATION_COUNT` | Number of messages to include in conversation context | `10` |
75
- | `GOOGLE_CLIENT_ID` | Google OAuth2 client ID (authentication generator) | — |
76
- | `GOOGLE_CLIENT_SECRET` | Google OAuth2 client secret (authentication generator) | |
286
+ | OpenAI | `openai` | Requires API key via external service |
287
+ | Anthropic | `anthropic` | Requires API key via external service |
288
+ | Google | `google` | Requires API key via external service |
289
+ | Ollama | `ollama` | No API key required; usable by guest users |
77
290
 
78
291
  ## Contributing
79
292
 
@@ -304,8 +304,8 @@ Devise.setup do |config|
304
304
  # access_type: 'offline' - Allows the application to receive refresh tokens
305
305
  # include_granted_scopes: true - Enables incremental authorization
306
306
  config.omniauth :google_oauth2,
307
- ENV["GOOGLE_CLIENT_ID"],
308
- ENV["GOOGLE_CLIENT_SECRET"],
307
+ Rails.application.credentials.dig(:google, :client_id),
308
+ Rails.application.credentials.dig(:google, :client_secret),
309
309
  {
310
310
  scope: "email,profile,openid",
311
311
  prompt: "select_account",
@@ -4,7 +4,7 @@
4
4
  # External LLM service base URL for API key and model management
5
5
  Rails.application.configure do
6
6
  # Base URL for LLM service
7
- # Retrieved from environment variable LLM_SERVICE_BASE_URL, uses default value if not set
8
- config.llm_service_base_url = ENV.fetch("LLM_SERVICE_BASE_URL", "http://localhost:3000")
9
- config.summarize_conversation_count = ENV.fetch("SUMMARIZE_CONVERSATION_COUNT", 10).to_i
7
+ # Retrieved from Rails credentials, uses default value if not set
8
+ config.llm_service_base_url = Rails.application.credentials.dig(:llm_service, :base_url) || "http://localhost:3000"
9
+ config.summarize_conversation_count = (Rails.application.credentials.dig(:llm_service, :summarize_conversation_count) || 10).to_i
10
10
  end
@@ -1,3 +1,3 @@
1
1
  module LlmMetaClient
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm_meta_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dhq_boiler