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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b3b7edb6ffb37242cabcfed5f765bd3337565c2e55e1fcce5a6508bb07a53cdf
|
|
4
|
+
data.tar.gz: c1a289098044870e43c86d2df34b223a33af2fd10f59dceaadef38ae0df68800
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
284
|
+
| Provider | `llm_type` | Notes |
|
|
72
285
|
|---|---|---|
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
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
|
-
|
|
308
|
-
|
|
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
|
|
8
|
-
config.llm_service_base_url =
|
|
9
|
-
config.summarize_conversation_count =
|
|
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
|