rubyllm-semantic_router 0.1.0 → 0.4.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 +4 -4
- data/.gitignore +4 -0
- data/ARCHITECTURE.md +329 -0
- data/CHANGELOG.md +98 -0
- data/CONTRIBUTING.md +103 -0
- data/Gemfile.lock +10 -10
- data/README.md +136 -179
- data/lib/rubyllm/semantic_router/configuration.rb +101 -5
- data/lib/rubyllm/semantic_router/embedding_cache.rb +74 -0
- data/lib/rubyllm/semantic_router/errors.rb +7 -0
- data/lib/rubyllm/semantic_router/router.rb +178 -32
- data/lib/rubyllm/semantic_router/strategies/semantic.rb +11 -13
- data/lib/rubyllm/semantic_router/utils.rb +51 -0
- data/lib/rubyllm/semantic_router/version.rb +1 -1
- data/lib/rubyllm/semantic_router.rb +2 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4aecffcf2d9ae00e08f10c2f606ca51ef8d3a7fd0fc7616b6e98c0826d85649c
|
|
4
|
+
data.tar.gz: 2b50357814fa44df4aad83c63de2418e0ac78581de10eb3ccd975a91b4e76c86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9dcf6225321e5dde48d54fec9df1c8ca64e111ec948ed35ede94434f1a27bbcaec39b73c22c2d8b078926ae1f1171d1538f0d83133aa5e0febef5db032784d1
|
|
7
|
+
data.tar.gz: 0d3620664177877adf2518ffd22766d6a831e14afde7501a7864c53cd219dc6b2b4a525a7d5939c3a7ff9a961eb0fe29cfd5f0db52d3b88619ec8b0a97dc4cd9
|
data/.gitignore
CHANGED
data/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This document describes the architecture and design decisions of RubyLLM Semantic Router.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Router │
|
|
10
|
+
│ - Manages agents and conversation state │
|
|
11
|
+
│ - Delegates routing to strategy │
|
|
12
|
+
│ - Handles agent switching and chat │
|
|
13
|
+
│ - Provides ask, ask_batch, match, debug_routing APIs │
|
|
14
|
+
└─────────────────────────────────────────────────────────────┘
|
|
15
|
+
│ │ │
|
|
16
|
+
│ │ │
|
|
17
|
+
▼ ▼ ▼
|
|
18
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
19
|
+
│ EmbeddingCache │ │ Logger │ │ Retry Logic │
|
|
20
|
+
│ (optional TTL) │ │ (optional) │ │ (exp. backoff) │
|
|
21
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
22
|
+
│
|
|
23
|
+
▼
|
|
24
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ Strategies::Semantic │
|
|
26
|
+
│ - Generates embeddings via RubyLLM │
|
|
27
|
+
│ - Finds nearest neighbors (kNN) │
|
|
28
|
+
│ - Returns RoutingDecision │
|
|
29
|
+
└─────────────────────────────────────────────────────────────┘
|
|
30
|
+
│
|
|
31
|
+
▼
|
|
32
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
33
|
+
│ Example Storage │
|
|
34
|
+
│ - In-memory arrays │
|
|
35
|
+
│ - ActiveRecord with neighbor gem │
|
|
36
|
+
│ - Custom vector databases via find_examples │
|
|
37
|
+
└─────────────────────────────────────────────────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Core Components
|
|
41
|
+
|
|
42
|
+
### Router (`lib/rubyllm/semantic_router/router.rb`)
|
|
43
|
+
|
|
44
|
+
The main entry point that orchestrates routing and agent management.
|
|
45
|
+
|
|
46
|
+
**Responsibilities:**
|
|
47
|
+
- Accept and normalize agent configurations
|
|
48
|
+
- Store and manage routing examples
|
|
49
|
+
- Delegate routing decisions to the strategy
|
|
50
|
+
- Maintain conversation state and agent switching
|
|
51
|
+
- Provide the `ask`, `ask_batch`, `match`, and `debug_routing` APIs
|
|
52
|
+
- Manage embedding cache (if configured)
|
|
53
|
+
- Handle retry logic for transient failures
|
|
54
|
+
- Emit debug logs (if logger configured)
|
|
55
|
+
|
|
56
|
+
**Key Design Decisions:**
|
|
57
|
+
- Accepts RubyLLM chat objects directly for ergonomic API
|
|
58
|
+
- Extracts configuration from chat objects (instructions, tools, model)
|
|
59
|
+
- Maintains single chat instance, switching agent config on route changes
|
|
60
|
+
- Preserves full conversation history across agent switches
|
|
61
|
+
|
|
62
|
+
### Strategy (`lib/rubyllm/semantic_router/strategies/`)
|
|
63
|
+
|
|
64
|
+
Pluggable routing strategies following the Strategy pattern.
|
|
65
|
+
|
|
66
|
+
**Base Class:** `Strategies::Base`
|
|
67
|
+
- Defines the `#route` interface
|
|
68
|
+
- Provides shared `apply_fallback` logic
|
|
69
|
+
|
|
70
|
+
**Semantic Strategy:** `Strategies::Semantic`
|
|
71
|
+
- Generates embeddings using RubyLLM.embed
|
|
72
|
+
- Performs kNN search against examples
|
|
73
|
+
- Supports multiple storage backends via duck typing
|
|
74
|
+
- Returns `RoutingDecision` with agent, confidence, and reason
|
|
75
|
+
|
|
76
|
+
### RoutingDecision (`lib/rubyllm/semantic_router/routing_decision.rb`)
|
|
77
|
+
|
|
78
|
+
Value object representing a routing decision.
|
|
79
|
+
|
|
80
|
+
**Attributes:**
|
|
81
|
+
- `agent` - Target agent name (symbol)
|
|
82
|
+
- `confidence` - Match confidence (0.0-1.0)
|
|
83
|
+
- `matched_example` - The example text that matched
|
|
84
|
+
- `reason` - Why this decision was made (`:semantic_match`, `:fallback`, etc.)
|
|
85
|
+
- `inject_instruction` - Optional instruction for clarification flow
|
|
86
|
+
|
|
87
|
+
### Configuration (`lib/rubyllm/semantic_router/configuration.rb`)
|
|
88
|
+
|
|
89
|
+
Global configuration with validation. All setters validate input and raise `ConfigurationError` for invalid values.
|
|
90
|
+
|
|
91
|
+
**Routing Options:**
|
|
92
|
+
- `default_embedding_model` - Model for generating embeddings
|
|
93
|
+
- `default_similarity_threshold` - Minimum confidence for routing (0.0-1.0)
|
|
94
|
+
- `default_k_neighbors` - Number of neighbors for kNN
|
|
95
|
+
- `default_fallback` - Behavior when no match found
|
|
96
|
+
- `default_max_words` - Message truncation limit
|
|
97
|
+
|
|
98
|
+
**Reliability Options:**
|
|
99
|
+
- `logger` - Logger instance for debug output (default: nil)
|
|
100
|
+
- `cache_ttl` - Embedding cache TTL in seconds (default: nil = no caching)
|
|
101
|
+
- `max_retries` - Maximum retry attempts for embedding failures (default: 3)
|
|
102
|
+
- `retry_base_delay` - Base delay for exponential backoff in seconds (default: 0.5)
|
|
103
|
+
|
|
104
|
+
### Utils (`lib/rubyllm/semantic_router/utils.rb`)
|
|
105
|
+
|
|
106
|
+
Shared utility functions.
|
|
107
|
+
|
|
108
|
+
- `cosine_distance(a, b)` - Calculate cosine distance between vectors
|
|
109
|
+
- `cosine_similarity(a, b)` - Calculate cosine similarity
|
|
110
|
+
- `truncate_to_max_words(text, max_words)` - Truncate text by word count
|
|
111
|
+
|
|
112
|
+
### EmbeddingCache (`lib/rubyllm/semantic_router/embedding_cache.rb`)
|
|
113
|
+
|
|
114
|
+
Thread-safe in-memory cache for embeddings with TTL support.
|
|
115
|
+
|
|
116
|
+
**Purpose:** Reduce API calls and latency when the same text is embedded multiple times (e.g., adding the same example after clearing, or routing identical messages).
|
|
117
|
+
|
|
118
|
+
**Features:**
|
|
119
|
+
- TTL-based expiration
|
|
120
|
+
- Thread-safe with Mutex
|
|
121
|
+
- Simple get/set/fetch interface
|
|
122
|
+
- Automatic cleanup of expired entries
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# Internal structure
|
|
126
|
+
CacheEntry = Struct.new(:embedding, :expires_at)
|
|
127
|
+
|
|
128
|
+
# Usage (internal to Router)
|
|
129
|
+
@embedding_cache.fetch(text) { generate_embedding_api_call(text) }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Note:** The cache is per-router instance. Each router maintains its own cache.
|
|
133
|
+
|
|
134
|
+
### Errors (`lib/rubyllm/semantic_router/errors.rb`)
|
|
135
|
+
|
|
136
|
+
Custom exception hierarchy for clear error handling.
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Error (base)
|
|
140
|
+
├── AgentNotFoundError
|
|
141
|
+
├── NoDefaultAgentError
|
|
142
|
+
├── NoAgentsError
|
|
143
|
+
├── NoRoutingExamplesError
|
|
144
|
+
├── EmbeddingError
|
|
145
|
+
├── InvalidFallbackError
|
|
146
|
+
├── InvalidAgentError
|
|
147
|
+
└── ConfigurationError
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Storage Backends
|
|
151
|
+
|
|
152
|
+
The router supports multiple storage backends through duck typing:
|
|
153
|
+
|
|
154
|
+
### In-Memory
|
|
155
|
+
|
|
156
|
+
Default storage using simple arrays of `InMemoryExample` structs.
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# Internal structure
|
|
160
|
+
InMemoryExample = Struct.new(:agent_name, :example_text, :embedding)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### ActiveRecord with neighbor gem
|
|
164
|
+
|
|
165
|
+
Expects model with `has_neighbors :embedding` and `agent_name`, `example_text` columns.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# Detection
|
|
169
|
+
examples.respond_to?(:nearest_neighbors) # Use neighbor gem's kNN
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Custom Vector Database
|
|
173
|
+
|
|
174
|
+
Accepts a `find_examples` callable for custom search:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
find_examples: ->(embedding, limit:) {
|
|
178
|
+
# Return array of hashes or objects with:
|
|
179
|
+
# - agent_name (required)
|
|
180
|
+
# - example_text (optional)
|
|
181
|
+
# - distance OR score (optional, defaults to 0)
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Routing Flow
|
|
186
|
+
|
|
187
|
+
1. **Message received** via `router.ask(message)`
|
|
188
|
+
2. **Log** routing attempt (if logger configured)
|
|
189
|
+
3. **Check cache** for existing embedding (if cache enabled)
|
|
190
|
+
4. **Generate embedding** for the message using configured model
|
|
191
|
+
- On failure: retry with exponential backoff (up to `max_retries`)
|
|
192
|
+
- Cache result (if cache enabled)
|
|
193
|
+
5. **Find nearest neighbors** from examples (k = `k_neighbors`)
|
|
194
|
+
6. **Calculate confidence** from cosine distance of best match
|
|
195
|
+
7. **Apply threshold** - if below `similarity_threshold`, use fallback
|
|
196
|
+
8. **Log** routing decision (if logger configured)
|
|
197
|
+
9. **Return decision** with target agent and metadata
|
|
198
|
+
10. **Switch agent** if target differs from current
|
|
199
|
+
11. **Send message** to current agent's chat and return response
|
|
200
|
+
|
|
201
|
+
## Batch Routing Flow
|
|
202
|
+
|
|
203
|
+
For `router.ask_batch(messages)`:
|
|
204
|
+
|
|
205
|
+
1. **Generate embeddings** for all messages in single API call
|
|
206
|
+
2. **Route each message** using its pre-computed embedding
|
|
207
|
+
3. **Return array** of `RoutingDecision` objects
|
|
208
|
+
4. **Note:** Does not send messages or switch agents - only returns decisions
|
|
209
|
+
|
|
210
|
+
## Reliability Features
|
|
211
|
+
|
|
212
|
+
### Logging
|
|
213
|
+
|
|
214
|
+
When a logger is configured, the router emits debug information:
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
[SemanticRouter] Router initialized with agents: product, support
|
|
218
|
+
[SemanticRouter] Routing message: Show me laptops...
|
|
219
|
+
[SemanticRouter] Cache hit for embedding
|
|
220
|
+
[SemanticRouter] Routed to :product (confidence: 0.892, reason: semantic_match)
|
|
221
|
+
[SemanticRouter] Switching from :support to :product
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Log levels used:
|
|
225
|
+
- `debug` - Detailed operation info (routing attempts, cache hits, agent switches)
|
|
226
|
+
- `info` - Routing decisions
|
|
227
|
+
- `warn` - Retry attempts
|
|
228
|
+
- `error` - Final failures after retries exhausted
|
|
229
|
+
|
|
230
|
+
### Retry with Exponential Backoff
|
|
231
|
+
|
|
232
|
+
Embedding API calls are retried on failure:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
Attempt 1: fail → wait 0.5s
|
|
236
|
+
Attempt 2: fail → wait 1.0s
|
|
237
|
+
Attempt 3: fail → wait 2.0s
|
|
238
|
+
Attempt 4: fail → raise EmbeddingError
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Formula: `delay = retry_base_delay * (2 ** attempt_number)`
|
|
242
|
+
|
|
243
|
+
### Embedding Cache
|
|
244
|
+
|
|
245
|
+
Reduces API calls by caching embeddings:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
# First call - generates embedding, stores in cache
|
|
249
|
+
router.add_example("Show products", agent: :product)
|
|
250
|
+
|
|
251
|
+
# Later - same text uses cached embedding
|
|
252
|
+
router.clear_examples!
|
|
253
|
+
router.add_example("Show products", agent: :product) # Cache hit
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Cache is keyed by the truncated text (after `max_words` applied).
|
|
257
|
+
|
|
258
|
+
## Design Principles
|
|
259
|
+
|
|
260
|
+
### 1. Duck Typing for Flexibility
|
|
261
|
+
|
|
262
|
+
The router uses duck typing to support multiple storage backends without requiring specific interfaces:
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# ActiveRecord detection
|
|
266
|
+
if examples.respond_to?(:nearest_neighbors)
|
|
267
|
+
# Use neighbor gem
|
|
268
|
+
elsif examples.respond_to?(:where)
|
|
269
|
+
# Use ActiveRecord scoping
|
|
270
|
+
else
|
|
271
|
+
# Use array operations
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 2. Sensible Defaults
|
|
276
|
+
|
|
277
|
+
All configuration has reasonable defaults that work out of the box:
|
|
278
|
+
- Embedding model: `text-embedding-3-small` (fast, cheap)
|
|
279
|
+
- Threshold: `0.7` (balanced precision/recall)
|
|
280
|
+
- Fallback: `:default_agent` (predictable behavior)
|
|
281
|
+
|
|
282
|
+
### 3. Validation at Boundaries
|
|
283
|
+
|
|
284
|
+
Input validation occurs at configuration and router initialization, failing fast with clear error messages rather than producing undefined behavior during routing.
|
|
285
|
+
|
|
286
|
+
### 4. Strategy Pattern for Routing
|
|
287
|
+
|
|
288
|
+
Using the strategy pattern allows for future alternative routing implementations (e.g., keyword-based, LLM-based) without changing the Router class.
|
|
289
|
+
|
|
290
|
+
### 5. Value Objects for Decisions
|
|
291
|
+
|
|
292
|
+
`RoutingDecision` is immutable with clear semantics, making it easy to inspect, log, and test routing behavior.
|
|
293
|
+
|
|
294
|
+
## Extension Points
|
|
295
|
+
|
|
296
|
+
### Custom Strategies
|
|
297
|
+
|
|
298
|
+
Create new routing strategies by extending `Strategies::Base`:
|
|
299
|
+
|
|
300
|
+
```ruby
|
|
301
|
+
class MyStrategy < RubyLLM::SemanticRouter::Strategies::Base
|
|
302
|
+
def route(message, agents:, examples:, current_agent:, config:, find_examples: nil)
|
|
303
|
+
# Custom routing logic
|
|
304
|
+
RoutingDecision.new(agent: :some_agent, confidence: 0.9, reason: :custom)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
router = Router.new(agents: {...}, strategy: MyStrategy.new, ...)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Callbacks
|
|
312
|
+
|
|
313
|
+
Register callbacks for routing events:
|
|
314
|
+
|
|
315
|
+
```ruby
|
|
316
|
+
router.on(:on_route) do |decision|
|
|
317
|
+
Rails.logger.info("Routed to #{decision.agent} with confidence #{decision.confidence}")
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Performance Considerations
|
|
322
|
+
|
|
323
|
+
- **Embedding generation** is the main latency source (~50-200ms per call)
|
|
324
|
+
- **In-memory kNN** is O(n) - fine for hundreds of examples
|
|
325
|
+
- **neighbor gem** uses database indexes for O(log n) performance
|
|
326
|
+
- **Batch imports** (`import_examples`) reduce embedding API calls
|
|
327
|
+
- **Batch routing** (`ask_batch`) generates all embeddings in one API call
|
|
328
|
+
- **Embedding cache** eliminates redundant API calls for repeated text
|
|
329
|
+
- **max_words** truncation reduces embedding costs for long messages
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.4.0] - 2026-05-29
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Verified compatibility with RubyLLM 1.15.0 (tested with real OpenAI API)
|
|
12
|
+
- Lowered default `similarity_threshold` from 0.7 to 0.3 to match real-world embedding similarity ranges
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Integration test suite (`spec/integration/`) for testing against real OpenAI API
|
|
16
|
+
|
|
17
|
+
## [0.3.0] - 2025-01-24
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Shared `Utils` module with `cosine_distance`, `cosine_similarity`, and `truncate_to_max_words` methods
|
|
21
|
+
- Configuration validation for `similarity_threshold` (must be 0.0-1.0), `k_neighbors` (must be positive integer), `max_words` (must be nil or positive integer), and `fallback` (must be valid symbol)
|
|
22
|
+
- `ConfigurationError` exception for invalid configuration values
|
|
23
|
+
- Debug logging support with configurable `logger` option
|
|
24
|
+
- Embedding cache with TTL support via `cache_ttl` option
|
|
25
|
+
- `ask_batch` method for routing multiple messages efficiently
|
|
26
|
+
- Retry logic with exponential backoff via `max_retries` and `retry_base_delay` options
|
|
27
|
+
- Comprehensive test suite for Utils, Configuration, and edge cases
|
|
28
|
+
- CHANGELOG.md, CONTRIBUTING.md, and ARCHITECTURE.md documentation
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- Extracted duplicate `cosine_distance` and `truncate_to_max_words` implementations into shared Utils module
|
|
32
|
+
- Improved error messages for configuration validation
|
|
33
|
+
|
|
34
|
+
### Breaking Changes
|
|
35
|
+
- Configuration now validates values and raises `ConfigurationError` for invalid settings:
|
|
36
|
+
- `similarity_threshold` must be between 0.0 and 1.0
|
|
37
|
+
- `k_neighbors` must be a positive integer
|
|
38
|
+
- `max_words` must be nil or a positive integer
|
|
39
|
+
- `fallback` must be one of `:default_agent`, `:keep_current`, `:ask_clarification`
|
|
40
|
+
- Previously, invalid values were silently accepted but could cause unexpected behavior. If your code was using invalid configuration values, you will now receive clear error messages indicating what needs to be fixed.
|
|
41
|
+
|
|
42
|
+
## [0.2.0] - 2025-01-21
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
- `max_words` option to truncate messages before embedding generation
|
|
46
|
+
- Global `default_max_words` configuration option
|
|
47
|
+
- Message truncation applied consistently to both example import and routing
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- Updated README to be database-agnostic
|
|
51
|
+
|
|
52
|
+
## [0.1.3] - 2025-01-20
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- Custom vector search support via `find_examples` callback
|
|
56
|
+
- Support for both `distance` (lower=better) and `score` (higher=better) in custom search results
|
|
57
|
+
- Documented custom vector database integration (Pinecone, Qdrant, OpenSearch, etc.)
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- Dependency versions and file permissions
|
|
61
|
+
|
|
62
|
+
## [0.1.2] - 2025-01-19
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
- Simplified API to accept `RubyLLM.chat` objects directly as agents
|
|
66
|
+
- Agents now extract configuration from chat objects automatically
|
|
67
|
+
|
|
68
|
+
## [0.1.1] - 2025-01-18
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
- ActiveRecord + pgvector example to README
|
|
72
|
+
- Installation instructions
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
- Simplified README structure
|
|
76
|
+
|
|
77
|
+
## [0.1.0] - 2025-01-17
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
- Initial implementation of semantic routing for RubyLLM
|
|
81
|
+
- Core `Router` class for managing multiple agents
|
|
82
|
+
- `Semantic` routing strategy using embeddings and kNN search
|
|
83
|
+
- Support for multiple fallback behaviors: `:default_agent`, `:keep_current`, `:ask_clarification`
|
|
84
|
+
- In-memory example storage with `add_example` and `import_examples`
|
|
85
|
+
- External example sources via `with_examples` (ActiveRecord compatible)
|
|
86
|
+
- Scoped examples for multi-tenant applications
|
|
87
|
+
- Routing callbacks via `on(:on_route)`
|
|
88
|
+
- Debug routing with `match` and `debug_routing` methods
|
|
89
|
+
- Global configuration via `RubyLLM::SemanticRouter.configure`
|
|
90
|
+
- Comprehensive test suite
|
|
91
|
+
|
|
92
|
+
[0.4.0]: https://github.com/khasinski/ruby_llm-semantic_router/compare/v0.3.0...v0.4.0
|
|
93
|
+
[0.3.0]: https://github.com/khasinski/ruby_llm-semantic_router/compare/v0.2.0...v0.3.0
|
|
94
|
+
[0.2.0]: https://github.com/khasinski/ruby_llm-semantic_router/compare/v0.1.3...v0.2.0
|
|
95
|
+
[0.1.3]: https://github.com/khasinski/ruby_llm-semantic_router/compare/v0.1.2...v0.1.3
|
|
96
|
+
[0.1.2]: https://github.com/khasinski/ruby_llm-semantic_router/compare/v0.1.1...v0.1.2
|
|
97
|
+
[0.1.1]: https://github.com/khasinski/ruby_llm-semantic_router/compare/v0.1.0...v0.1.1
|
|
98
|
+
[0.1.0]: https://github.com/khasinski/ruby_llm-semantic_router/releases/tag/v0.1.0
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Contributing to RubyLLM Semantic Router
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! This document provides guidelines for contributing to the project.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
1. Fork the repository
|
|
8
|
+
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/rubyllm-semantic_router.git`
|
|
9
|
+
3. Install dependencies: `bundle install`
|
|
10
|
+
4. Run tests: `bundle exec rspec`
|
|
11
|
+
|
|
12
|
+
## Development Setup
|
|
13
|
+
|
|
14
|
+
### Requirements
|
|
15
|
+
|
|
16
|
+
- Ruby 3.1+
|
|
17
|
+
- Bundler 2.0+
|
|
18
|
+
|
|
19
|
+
### Running Tests
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Run all tests
|
|
23
|
+
bundle exec rspec
|
|
24
|
+
|
|
25
|
+
# Run specific test file
|
|
26
|
+
bundle exec rspec spec/rubyllm/semantic_router/router_spec.rb
|
|
27
|
+
|
|
28
|
+
# Run with verbose output
|
|
29
|
+
bundle exec rspec --format documentation
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Making Changes
|
|
33
|
+
|
|
34
|
+
### Code Style
|
|
35
|
+
|
|
36
|
+
- Use frozen string literals (`# frozen_string_literal: true`)
|
|
37
|
+
- Follow existing code patterns and naming conventions
|
|
38
|
+
- Keep methods focused and under 20 lines when possible
|
|
39
|
+
- Add YARD documentation for public methods
|
|
40
|
+
|
|
41
|
+
### Testing
|
|
42
|
+
|
|
43
|
+
- Write tests for all new functionality
|
|
44
|
+
- Maintain or improve existing test coverage
|
|
45
|
+
- Tests should be fast and not require external API calls
|
|
46
|
+
- Use the mock RubyLLM classes in `spec/spec_helper.rb`
|
|
47
|
+
|
|
48
|
+
### Commit Messages
|
|
49
|
+
|
|
50
|
+
- Use clear, descriptive commit messages
|
|
51
|
+
- Start with a verb (Add, Fix, Update, Remove, Refactor)
|
|
52
|
+
- Keep the first line under 72 characters
|
|
53
|
+
- Reference issues when applicable: `Fix #123`
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
```
|
|
57
|
+
Add batch routing support with ask_batch method
|
|
58
|
+
Fix configuration validation for edge cases
|
|
59
|
+
Update README with multi-tenant scoping docs
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Pull Request Process
|
|
63
|
+
|
|
64
|
+
1. Create a feature branch: `git checkout -b feature/my-feature`
|
|
65
|
+
2. Make your changes with tests
|
|
66
|
+
3. Run the test suite: `bundle exec rspec`
|
|
67
|
+
4. Update CHANGELOG.md with your changes under `[Unreleased]`
|
|
68
|
+
5. Push to your fork and create a Pull Request
|
|
69
|
+
|
|
70
|
+
### PR Checklist
|
|
71
|
+
|
|
72
|
+
- [ ] Tests pass locally
|
|
73
|
+
- [ ] New functionality has tests
|
|
74
|
+
- [ ] CHANGELOG.md updated
|
|
75
|
+
- [ ] Documentation updated (if applicable)
|
|
76
|
+
- [ ] Code follows existing style
|
|
77
|
+
|
|
78
|
+
## Reporting Issues
|
|
79
|
+
|
|
80
|
+
When reporting issues, please include:
|
|
81
|
+
|
|
82
|
+
- Ruby version (`ruby -v`)
|
|
83
|
+
- Gem version
|
|
84
|
+
- Minimal reproduction steps
|
|
85
|
+
- Expected vs actual behavior
|
|
86
|
+
- Error messages with backtraces
|
|
87
|
+
|
|
88
|
+
## Feature Requests
|
|
89
|
+
|
|
90
|
+
Feature requests are welcome! Please:
|
|
91
|
+
|
|
92
|
+
- Check existing issues first
|
|
93
|
+
- Describe the use case
|
|
94
|
+
- Explain why existing functionality doesn't solve it
|
|
95
|
+
- Consider if it fits the gem's scope
|
|
96
|
+
|
|
97
|
+
## Architecture
|
|
98
|
+
|
|
99
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for an overview of the codebase structure and design decisions.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
By contributing, you agree that your contributions will be licensed under the MIT License.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rubyllm-semantic_router (0.
|
|
4
|
+
rubyllm-semantic_router (0.4.0)
|
|
5
5
|
ruby_llm (~> 1.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
@@ -10,19 +10,19 @@ GEM
|
|
|
10
10
|
base64 (0.3.0)
|
|
11
11
|
diff-lcs (1.6.2)
|
|
12
12
|
event_stream_parser (1.0.0)
|
|
13
|
-
faraday (2.14.
|
|
13
|
+
faraday (2.14.2)
|
|
14
14
|
faraday-net_http (>= 2.0, < 3.5)
|
|
15
15
|
json
|
|
16
16
|
logger
|
|
17
17
|
faraday-multipart (1.2.0)
|
|
18
18
|
multipart-post (~> 2.0)
|
|
19
|
-
faraday-net_http (3.4.
|
|
19
|
+
faraday-net_http (3.4.3)
|
|
20
20
|
net-http (~> 0.5)
|
|
21
21
|
faraday-retry (2.4.0)
|
|
22
22
|
faraday (~> 2.0)
|
|
23
|
-
json (2.
|
|
23
|
+
json (2.19.7)
|
|
24
24
|
logger (1.7.0)
|
|
25
|
-
marcel (1.1
|
|
25
|
+
marcel (1.2.1)
|
|
26
26
|
multipart-post (2.4.1)
|
|
27
27
|
net-http (0.9.1)
|
|
28
28
|
uri (>= 0.11.1)
|
|
@@ -40,19 +40,19 @@ GEM
|
|
|
40
40
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
41
41
|
rspec-support (~> 3.13.0)
|
|
42
42
|
rspec-support (3.13.6)
|
|
43
|
-
ruby_llm (1.
|
|
43
|
+
ruby_llm (1.15.0)
|
|
44
44
|
base64
|
|
45
45
|
event_stream_parser (~> 1)
|
|
46
46
|
faraday (>= 1.10.0)
|
|
47
47
|
faraday-multipart (>= 1)
|
|
48
48
|
faraday-net_http (>= 1)
|
|
49
49
|
faraday-retry (>= 1)
|
|
50
|
-
marcel (~> 1
|
|
51
|
-
ruby_llm-schema (~> 0
|
|
50
|
+
marcel (~> 1)
|
|
51
|
+
ruby_llm-schema (~> 0)
|
|
52
52
|
zeitwerk (~> 2)
|
|
53
|
-
ruby_llm-schema (0.
|
|
53
|
+
ruby_llm-schema (0.4.0)
|
|
54
54
|
uri (1.1.1)
|
|
55
|
-
zeitwerk (2.
|
|
55
|
+
zeitwerk (2.8.2)
|
|
56
56
|
|
|
57
57
|
PLATFORMS
|
|
58
58
|
arm64-darwin-25
|