robot_lab-document_store 0.1.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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +90 -0
- data/Rakefile +8 -0
- data/docs/index.md +58 -0
- data/examples/26_document_store/api_versioning_adr.md +52 -0
- data/examples/26_document_store/incident_postmortem.md +46 -0
- data/examples/26_document_store/postgres_runbook.md +49 -0
- data/examples/26_document_store/redis_caching_guide.md +48 -0
- data/examples/26_document_store/sidekiq_guide.md +51 -0
- data/examples/26_document_store.rb +146 -0
- data/lib/robot_lab/document_store/version.rb +7 -0
- data/lib/robot_lab/document_store.rb +144 -0
- data/mkdocs.yml +113 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fe6aeaf2a0fd6a0c1dd5b827a9382e3804cf9cf6e8573fdd9c75063fccd87a36
|
|
4
|
+
data.tar.gz: 350a44adfb40048c467166a1bfc37dd169941a35c4f5e3dd56ace185e39ba27b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 05bc51a7b278d57d7bf8b1bb8535d5520ac41093112b0080c0f2f6e7eb554101d212b8c3065564f25a031ce3439f7b8ad9562fa93a9ef9a4091d78f3d20bdea2
|
|
7
|
+
data.tar.gz: 62694a390bbcd9a2492466eef6102fd821ae670ca30b694283d25f73f4fa7c504e9c62e2e32509ebc2f3f7700d63499e74153111bd781bf190ea39c76f714137
|
data/.envrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export RR=`pwd`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Deploy Documentation to GitHub Pages
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
- develop
|
|
7
|
+
paths:
|
|
8
|
+
- "docs/**"
|
|
9
|
+
- "mkdocs.yml"
|
|
10
|
+
- ".github/workflows/deploy-github-pages.yml"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout code
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- name: Setup Python
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: 3.x
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: |
|
|
34
|
+
pip install mkdocs
|
|
35
|
+
pip install mkdocs-material
|
|
36
|
+
pip install mkdocs-macros-plugin
|
|
37
|
+
pip install mike
|
|
38
|
+
|
|
39
|
+
- name: Configure Git
|
|
40
|
+
run: |
|
|
41
|
+
git config --local user.email "action@github.com"
|
|
42
|
+
git config --local user.name "GitHub Action"
|
|
43
|
+
|
|
44
|
+
- name: Build MkDocs site
|
|
45
|
+
run: mkdocs build
|
|
46
|
+
|
|
47
|
+
- name: Deploy to GitHub Pages
|
|
48
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
49
|
+
with:
|
|
50
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
publish_dir: ./site
|
|
52
|
+
keep_files: true
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dewayne VanHoozer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# robot_lab-document_store
|
|
2
|
+
|
|
3
|
+
Embedding-based semantic document search for the [RobotLab](https://github.com/MadBomber/robot_lab) LLM agent framework.
|
|
4
|
+
|
|
5
|
+
> [!CAUTION]
|
|
6
|
+
> This gem is under active development. APIs may change without notice.
|
|
7
|
+
|
|
8
|
+
## What it provides
|
|
9
|
+
|
|
10
|
+
`RobotLab::DocumentStore` is a thread-safe, in-memory vector store backed by [fastembed](https://github.com/Anush008/fastembed-ruby) embeddings and cosine similarity search. It supports:
|
|
11
|
+
|
|
12
|
+
- **`store(key, text)`** — embed and store a document under a symbol key
|
|
13
|
+
- **`search(query, limit:)`** — return the top-N most similar documents by cosine similarity
|
|
14
|
+
- **`delete(key)`** / **`clear`** — remove individual entries or wipe the store
|
|
15
|
+
- **Asymmetric embedding** — passage embeddings for storage, query embeddings for retrieval
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "robot_lab-document_store"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Example
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require "robot_lab/document_store"
|
|
29
|
+
|
|
30
|
+
store = RobotLab::DocumentStore.new
|
|
31
|
+
|
|
32
|
+
store.store(:alpha, "Ruby is a dynamic, open source programming language.")
|
|
33
|
+
store.store(:beta, "Python is widely used in data science and machine learning.")
|
|
34
|
+
store.store(:gamma, "JavaScript runs in the browser and on Node.js servers.")
|
|
35
|
+
|
|
36
|
+
results = store.search("What language is popular for AI?", limit: 2)
|
|
37
|
+
results.each do |r|
|
|
38
|
+
puts "#{r[:key]} (score: #{"%.3f" % r[:score]})"
|
|
39
|
+
end
|
|
40
|
+
# => beta (score: 0.872)
|
|
41
|
+
# => alpha (score: 0.641)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Custom Model
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
store = RobotLab::DocumentStore.new(
|
|
48
|
+
model_name: "BAAI/bge-small-en-v1.5"
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The default model is `"BAAI/bge-base-en-v1.5"`.
|
|
53
|
+
|
|
54
|
+
## Using with RobotLab Robots
|
|
55
|
+
|
|
56
|
+
`DocumentStore` works well as in-memory retrieval for RAG (retrieval-augmented generation) workflows. Load documents at startup and pass relevant excerpts into robot context:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
require "robot_lab"
|
|
60
|
+
require "robot_lab/document_store"
|
|
61
|
+
|
|
62
|
+
store = RobotLab::DocumentStore.new
|
|
63
|
+
store.store(:faq_1, "Our return policy allows returns within 30 days.")
|
|
64
|
+
store.store(:faq_2, "Shipping typically takes 3-5 business days.")
|
|
65
|
+
|
|
66
|
+
robot = RobotLab.build(
|
|
67
|
+
name: "support",
|
|
68
|
+
system_prompt: "You are a support agent. Use provided context to answer questions."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
query = "How long do I have to return an item?"
|
|
72
|
+
chunks = store.search(query, limit: 2).map { |r| r[:text] }.join("\n")
|
|
73
|
+
|
|
74
|
+
result = robot.run("Context:\n#{chunks}\n\nQuestion: #{query}")
|
|
75
|
+
puts result.last_text_content
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Links
|
|
79
|
+
|
|
80
|
+
- [RobotLab Core](https://github.com/MadBomber/robot_lab)
|
|
81
|
+
- [fastembed-ruby](https://github.com/Anush008/fastembed-ruby)
|
|
82
|
+
- [RubyGems](https://rubygems.org/gems/robot_lab-document_store)
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT License - Copyright (c) 2025 Dewayne VanHoozer
|
|
87
|
+
|
|
88
|
+
## Contributing
|
|
89
|
+
|
|
90
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/MadBomber/robot_lab-document_store.
|
data/Rakefile
ADDED
data/docs/index.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# robot_lab-document_store
|
|
2
|
+
|
|
3
|
+
Embedding-based semantic document search for the [RobotLab](https://github.com/MadBomber/robot_lab) LLM agent framework.
|
|
4
|
+
|
|
5
|
+
> [!CAUTION]
|
|
6
|
+
> This gem is under active development. APIs may change without notice.
|
|
7
|
+
|
|
8
|
+
## What it provides
|
|
9
|
+
|
|
10
|
+
`RobotLab::DocumentStore` is a thread-safe, in-memory vector store backed by [fastembed](https://github.com/Anush008/fastembed-ruby) embeddings and cosine similarity search. It supports:
|
|
11
|
+
|
|
12
|
+
- **`store(key, text)`** — embed and store a document under a symbol key
|
|
13
|
+
- **`search(query, limit:)`** — return the top-N most similar documents by cosine similarity
|
|
14
|
+
- **`delete(key)`** / **`clear`** — remove individual entries or wipe the store
|
|
15
|
+
- **Asymmetric embedding** — passage embeddings for storage, query embeddings for retrieval
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Add to your Gemfile:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "robot_lab-document_store"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Example
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require "robot_lab/document_store"
|
|
29
|
+
|
|
30
|
+
store = RobotLab::DocumentStore.new
|
|
31
|
+
|
|
32
|
+
store.store(:alpha, "Ruby is a dynamic, open source programming language.")
|
|
33
|
+
store.store(:beta, "Python is widely used in data science and machine learning.")
|
|
34
|
+
store.store(:gamma, "JavaScript runs in the browser and on Node.js servers.")
|
|
35
|
+
|
|
36
|
+
results = store.search("What language is popular for AI?", limit: 2)
|
|
37
|
+
results.each do |r|
|
|
38
|
+
puts "#{r[:key]} (score: #{"%.3f" % r[:score]})"
|
|
39
|
+
end
|
|
40
|
+
# => beta (score: 0.872)
|
|
41
|
+
# => alpha (score: 0.641)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Custom Model
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
store = RobotLab::DocumentStore.new(
|
|
48
|
+
model_name: "BAAI/bge-small-en-v1.5"
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The default model is `"BAAI/bge-base-en-v1.5"`.
|
|
53
|
+
|
|
54
|
+
## Links
|
|
55
|
+
|
|
56
|
+
- [RobotLab Core](https://github.com/MadBomber/robot_lab)
|
|
57
|
+
- [fastembed-ruby](https://github.com/Anush008/fastembed-ruby)
|
|
58
|
+
- [RubyGems](https://rubygems.org/gems/robot_lab-document_store)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Architecture Decision Record #047 — API Versioning Strategy
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted (2024-11-12)
|
|
4
|
+
**Deciders:** Platform team, Mobile team, Partner integrations team
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The v1 API has accumulated 23 breaking changes held back by an informal freeze
|
|
9
|
+
while three external partners built integrations. The mobile apps ship on a
|
|
10
|
+
4-week release cycle and cannot deploy hotfixes to force users to upgrade. We
|
|
11
|
+
need a versioning strategy that allows the backend to evolve without coordinated
|
|
12
|
+
lockstep releases across all consumers.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
We adopt URI-based versioning (/api/v2/, /api/v3/) rather than header-based
|
|
17
|
+
(Accept: application/vnd.company.v2+json) for the following reasons:
|
|
18
|
+
|
|
19
|
+
- URI versioning is visible in logs, dashboards, and browser dev tools.
|
|
20
|
+
- Proxy and CDN rules can target specific version prefixes.
|
|
21
|
+
- Internal clients are all first-party and can be updated in lockstep.
|
|
22
|
+
|
|
23
|
+
Header-based versioning is reserved for minor non-breaking variants (e.g.,
|
|
24
|
+
adding optional fields) using the Prefer header.
|
|
25
|
+
|
|
26
|
+
## Support Lifecycle
|
|
27
|
+
|
|
28
|
+
Each major version is supported for 18 months from GA. Deprecation notices are
|
|
29
|
+
added to response headers (Sunset: date) 6 months before EOL. The deprecation
|
|
30
|
+
dashboard tracks call volume per version per consumer; we do not retire a
|
|
31
|
+
version with > 100 calls/day without direct partner outreach.
|
|
32
|
+
|
|
33
|
+
## Backwards Compatibility Rules
|
|
34
|
+
|
|
35
|
+
Within a version, we **may**:
|
|
36
|
+
- Add new fields to responses.
|
|
37
|
+
- Add new optional request parameters.
|
|
38
|
+
- Add new endpoints.
|
|
39
|
+
- Add new enum values (consumers must ignore unknown values).
|
|
40
|
+
|
|
41
|
+
We **must not**:
|
|
42
|
+
- Remove or rename fields.
|
|
43
|
+
- Change field types.
|
|
44
|
+
- Change HTTP status codes for existing success cases.
|
|
45
|
+
- Remove endpoints.
|
|
46
|
+
|
|
47
|
+
## Migration Tooling
|
|
48
|
+
|
|
49
|
+
A version compatibility shim layer translates v1 requests to v2 internal
|
|
50
|
+
representations and back-translates responses. This allows v1 to remain
|
|
51
|
+
operational without duplicating business logic. The shim is tested with a
|
|
52
|
+
contract test suite against recorded v1 response fixtures.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Incident Postmortem — INC-2024-089
|
|
2
|
+
|
|
3
|
+
**Date:** 2024-10-03
|
|
4
|
+
**Duration:** 47 minutes
|
|
5
|
+
**Severity:** P1
|
|
6
|
+
**Affected:** API gateway, order processing, checkout flows
|
|
7
|
+
|
|
8
|
+
## Timeline
|
|
9
|
+
|
|
10
|
+
| Time | Event |
|
|
11
|
+
|-------|-------|
|
|
12
|
+
| 14:23 | Automated alert fires: p99 API latency exceeds 5 seconds |
|
|
13
|
+
| 14:25 | On-call engineer pages in; confirms checkout error rate at 34% |
|
|
14
|
+
| 14:31 | Identified spike in slow queries on orders table in Datadog APM |
|
|
15
|
+
| 14:38 | Root cause confirmed: migration added non-concurrent index at peak traffic |
|
|
16
|
+
| 14:44 | DBA kills the migration process; index creation aborted |
|
|
17
|
+
| 14:48 | Query latency returns to baseline; error rate drops to 0.2% |
|
|
18
|
+
| 15:10 | Full recovery confirmed; incident closed |
|
|
19
|
+
|
|
20
|
+
## Root Cause
|
|
21
|
+
|
|
22
|
+
An engineer ran a schema migration that created an index on orders.status
|
|
23
|
+
without the CONCURRENTLY keyword. Postgres acquired an AccessExclusiveLock on
|
|
24
|
+
the orders table for the duration of the index build (11 minutes). All queries
|
|
25
|
+
touching the orders table queued behind the lock, exhausting the PgBouncer
|
|
26
|
+
connection pool within 3 minutes.
|
|
27
|
+
|
|
28
|
+
## Contributing Factors
|
|
29
|
+
|
|
30
|
+
1. Migration review checklist did not include "concurrent index" verification.
|
|
31
|
+
2. The migration was run manually during business hours, not via the deploy pipeline.
|
|
32
|
+
3. No automated linting (strong_migrations) was enforced in CI.
|
|
33
|
+
|
|
34
|
+
## Remediation (Completed)
|
|
35
|
+
|
|
36
|
+
- `strong_migrations` gem added to Gemfile; CI fails on unsafe migration patterns.
|
|
37
|
+
- Runbook updated: all migrations that touch tables > 1M rows require DBA review.
|
|
38
|
+
- Index creation added to the concurrent-operations checklist.
|
|
39
|
+
- PgBouncer max_client_conn increased from 150 to 300 as a buffer.
|
|
40
|
+
|
|
41
|
+
## Lessons Learned
|
|
42
|
+
|
|
43
|
+
Lock acquisition during index creation is silent in application logs — the first
|
|
44
|
+
visible symptom is connection pool exhaustion, not a database error.
|
|
45
|
+
Instrumenting pg_locks with an alert on long-held AccessExclusiveLocks would
|
|
46
|
+
have cut detection time from 8 minutes to under 1 minute.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# PostgreSQL Operations Runbook — v3.1
|
|
2
|
+
|
|
3
|
+
## Slow Query Investigation
|
|
4
|
+
|
|
5
|
+
When a query exceeds 1 second, start with pg_stat_statements:
|
|
6
|
+
|
|
7
|
+
SELECT query, mean_exec_time, calls, total_exec_time
|
|
8
|
+
FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 20;
|
|
9
|
+
|
|
10
|
+
Use EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) on the top offenders.
|
|
11
|
+
Look for Sequential Scans on large tables (> 50k rows) and Hash Joins on
|
|
12
|
+
unindexed foreign keys. Missing index candidates appear as "rows removed by
|
|
13
|
+
filter" values that are an order of magnitude larger than the rows returned.
|
|
14
|
+
|
|
15
|
+
## Connection Pool Exhaustion
|
|
16
|
+
|
|
17
|
+
PgBouncer pools connections at the transaction level. When all connections are
|
|
18
|
+
in use, new queries queue until pool_size is reached, at which point clients
|
|
19
|
+
receive "too many clients" errors. Mitigate by:
|
|
20
|
+
1. Reducing max_connections per Rails process via database.yml pool setting.
|
|
21
|
+
2. Increasing server_pool_size in pgbouncer.ini incrementally.
|
|
22
|
+
3. Identifying and killing idle-in-transaction connections:
|
|
23
|
+
|
|
24
|
+
SELECT pid, state, query, now() - query_start AS duration
|
|
25
|
+
FROM pg_stat_activity WHERE state = 'idle in transaction'
|
|
26
|
+
AND query_start < now() - interval '30 seconds';
|
|
27
|
+
|
|
28
|
+
## Table Bloat and Vacuum
|
|
29
|
+
|
|
30
|
+
High update/delete workloads generate table bloat. Check with:
|
|
31
|
+
|
|
32
|
+
SELECT relname, n_dead_tup, n_live_tup,
|
|
33
|
+
round(n_dead_tup::numeric / nullif(n_live_tup, 0) * 100, 1) AS dead_pct
|
|
34
|
+
FROM pg_stat_user_tables ORDER BY dead_pct DESC;
|
|
35
|
+
|
|
36
|
+
If dead_pct exceeds 20% on a hot table, trigger VACUUM ANALYZE manually. For
|
|
37
|
+
severe bloat, schedule an off-hours VACUUM FULL (acquires exclusive lock).
|
|
38
|
+
Autovacuum scale factor defaults to 0.2; reduce to 0.05 on high-churn tables.
|
|
39
|
+
|
|
40
|
+
## Replication Lag
|
|
41
|
+
|
|
42
|
+
Monitor standby lag with:
|
|
43
|
+
|
|
44
|
+
SELECT client_addr, write_lag, flush_lag, replay_lag
|
|
45
|
+
FROM pg_stat_replication;
|
|
46
|
+
|
|
47
|
+
Lag above 30 seconds indicates the replica is falling behind writes. Common
|
|
48
|
+
causes: long-running VACUUM on primary holding WAL files, network saturation
|
|
49
|
+
between primary and replica, or index builds on the replica.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Redis Caching Patterns — Implementation Guide
|
|
2
|
+
|
|
3
|
+
## Cache Key Design
|
|
4
|
+
|
|
5
|
+
Keys must encode every dimension that affects the cached value. For a
|
|
6
|
+
user-scoped collection: `orders:user_USER_ID:page_PAGE:v2`. Always include a
|
|
7
|
+
version suffix (v2) so a code deploy can invalidate globally by bumping the
|
|
8
|
+
version, without a manual cache flush. Avoid encoding mutable data (e.g.,
|
|
9
|
+
user.plan) directly in the key; use separate keys and join at read time,
|
|
10
|
+
or accept stale reads.
|
|
11
|
+
|
|
12
|
+
## TTL Strategy
|
|
13
|
+
|
|
14
|
+
Set TTLs based on acceptable staleness, not on intuition:
|
|
15
|
+
|
|
16
|
+
- User session data: 24h (refreshed on activity)
|
|
17
|
+
- API response cache (authenticated): 5 minutes
|
|
18
|
+
- API response cache (public, CDN-backed): 60 seconds
|
|
19
|
+
- Computed aggregates (dashboards): 15 minutes with background refresh
|
|
20
|
+
- Feature flags: 30 seconds (fast propagation of flag changes)
|
|
21
|
+
|
|
22
|
+
Always set a TTL. Unbounded keys are a production outage waiting to happen
|
|
23
|
+
when a runaway process fills the Redis instance.
|
|
24
|
+
|
|
25
|
+
## Cache Invalidation
|
|
26
|
+
|
|
27
|
+
Explicit invalidation is more reliable than TTL-only for write-heavy data. Use
|
|
28
|
+
after_commit callbacks to delete or update cache entries when records change.
|
|
29
|
+
For collections, track the latest updated_at timestamp as the cache key
|
|
30
|
+
component (Russian doll caching). When multiple cache entries must be
|
|
31
|
+
invalidated atomically, use a Redis pipeline or Lua script.
|
|
32
|
+
|
|
33
|
+
## Redis Memory Pressure
|
|
34
|
+
|
|
35
|
+
When Redis hits maxmemory, it evicts keys according to the eviction policy. Use
|
|
36
|
+
`allkeys-lru` for pure cache workloads. Monitor `evicted_keys` in Redis INFO; a
|
|
37
|
+
non-zero and growing value means your cache is too small for the working set.
|
|
38
|
+
Separate cache and session data into different Redis instances (or databases)
|
|
39
|
+
so session eviction cannot be triggered by cache pressure.
|
|
40
|
+
|
|
41
|
+
## Stampede Protection
|
|
42
|
+
|
|
43
|
+
Under high read concurrency, a cache miss causes multiple processes to
|
|
44
|
+
simultaneously recompute the same expensive value — the cache stampede.
|
|
45
|
+
Mitigate with probabilistic early expiration: recompute when TTL drops below a
|
|
46
|
+
random fraction of the original TTL. Alternatively, use a distributed lock
|
|
47
|
+
(Redlock or a simple SET NX PX lock key) to allow only one process to recompute
|
|
48
|
+
while others wait briefly on the stale value.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Background Job Processing with Sidekiq — Engineering Guide
|
|
2
|
+
|
|
3
|
+
## Job Design Principles
|
|
4
|
+
|
|
5
|
+
Every Sidekiq job must be idempotent: running it twice with the same arguments
|
|
6
|
+
must produce the same outcome. This is non-negotiable because Sidekiq retries
|
|
7
|
+
failed jobs and at-least-once delivery is guaranteed, not exactly-once. Achieve
|
|
8
|
+
idempotency by checking preconditions (has this invoice already been generated?),
|
|
9
|
+
using database unique constraints on job output records, and passing Stripe
|
|
10
|
+
idempotency keys.
|
|
11
|
+
|
|
12
|
+
## Retry Configuration
|
|
13
|
+
|
|
14
|
+
The default retry count is 25, which provides backoff up to ~21 days. For
|
|
15
|
+
time-sensitive jobs (send_welcome_email) reduce to 3. For financial jobs
|
|
16
|
+
(charge_subscription) raise to 15 to survive multi-hour outages.
|
|
17
|
+
|
|
18
|
+
Configure per-job: `sidekiq_options retry: 10`
|
|
19
|
+
|
|
20
|
+
Customize backoff with sidekiq_retry_in:
|
|
21
|
+
|
|
22
|
+
sidekiq_retry_in { |count| (count ** 4) + 15 + rand(30) * count }
|
|
23
|
+
|
|
24
|
+
This gives approximately: 15s, 1m, 5m, 17m, 34m for the first 5 retries.
|
|
25
|
+
|
|
26
|
+
## Circuit Breaker Pattern
|
|
27
|
+
|
|
28
|
+
When a downstream service (Stripe, SendGrid) is degraded, jobs fail rapidly and
|
|
29
|
+
fill the retry queue, creating a thundering-herd effect when the service
|
|
30
|
+
recovers. Use a circuit breaker backed by Redis:
|
|
31
|
+
|
|
32
|
+
- Set `stripe:circuit_open` in Redis when 3 consecutive failures occur.
|
|
33
|
+
- In a job middleware, check the flag; if open, re-enqueue with 5-minute delay.
|
|
34
|
+
- Auto-clear the flag after 10 minutes using Redis TTL.
|
|
35
|
+
|
|
36
|
+
This converts retry churn into scheduled bursts.
|
|
37
|
+
|
|
38
|
+
## Dead Queue Management
|
|
39
|
+
|
|
40
|
+
Jobs reach the dead queue after exhausting all retries. Never bulk-retry
|
|
41
|
+
blindly. Group dead jobs by error class, inspect a sample for root cause,
|
|
42
|
+
fix the underlying issue, then use a Rake task to re-enqueue in batches of 50
|
|
43
|
+
with a 1-second inter-batch sleep to avoid overwhelming the recovered service.
|
|
44
|
+
Log each re-enqueue with original args and failure reason.
|
|
45
|
+
|
|
46
|
+
## Queue Priority and Latency Budgets
|
|
47
|
+
|
|
48
|
+
Define at least three queues: critical (< 1s SLA: auth, payments), default
|
|
49
|
+
(< 30s: email, webhooks), and bulk (< 1h: exports, reports). Run dedicated
|
|
50
|
+
Sidekiq processes per queue tier. Never mix critical and bulk work in the same
|
|
51
|
+
process — a spike of bulk jobs will starve critical work if they share a queue.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 26: Embedding-Based Document Store
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates Memory#store_document and Memory#search_documents — a
|
|
7
|
+
# lightweight RAG store backed by fastembed (BAAI/bge-small-en-v1.5).
|
|
8
|
+
#
|
|
9
|
+
# Documents are multi-paragraph engineering guides stored as Markdown files in:
|
|
10
|
+
# examples/26_document_store/
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# ruby examples/26_document_store.rb
|
|
14
|
+
# (Downloads the ~23 MB ONNX model on first run; cached afterwards.)
|
|
15
|
+
|
|
16
|
+
require "robot_lab"
|
|
17
|
+
require "robot_lab/document_store"
|
|
18
|
+
|
|
19
|
+
puts "=" * 60
|
|
20
|
+
puts "Example 26: Embedding-Based Document Store"
|
|
21
|
+
puts "=" * 60
|
|
22
|
+
puts
|
|
23
|
+
puts "Note: First run downloads the fastembed model (~23 MB, cached)."
|
|
24
|
+
puts
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Load documents from the companion directory
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
DOC_DIR = File.join(__dir__, "26_document_store")
|
|
30
|
+
|
|
31
|
+
DOCUMENTS = Dir[File.join(DOC_DIR, "*.md")].sort.each_with_object({}) do |path, h|
|
|
32
|
+
key = File.basename(path, ".md").to_sym
|
|
33
|
+
h[key] = File.read(path)
|
|
34
|
+
end.freeze
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Store into a standalone DocumentStore
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
store = RobotLab::DocumentStore.new
|
|
40
|
+
|
|
41
|
+
print "Storing #{DOCUMENTS.size} documents... "
|
|
42
|
+
DOCUMENTS.each { |key, text| store.store(key, text) }
|
|
43
|
+
puts "done"
|
|
44
|
+
puts
|
|
45
|
+
DOCUMENTS.each { |key, text| puts " #{key.to_s.ljust(24)} #{text.split.size} words" }
|
|
46
|
+
puts
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Queries — each phrased differently from the document content
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
QUERIES = [
|
|
52
|
+
{
|
|
53
|
+
label: "Database query performance",
|
|
54
|
+
query: "Why is my Postgres query slow and how do I investigate it?",
|
|
55
|
+
want: :postgres_runbook
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: "Background job failures during outage",
|
|
59
|
+
query: "Jobs keep failing when Stripe is down. How do I stop them piling up?",
|
|
60
|
+
want: :sidekiq_guide
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
label: "API breaking changes policy",
|
|
64
|
+
query: "Can I rename a response field in the API without breaking clients?",
|
|
65
|
+
want: :api_versioning_adr
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
label: "Cache expiry and memory pressure",
|
|
69
|
+
query: "Redis is evicting keys unexpectedly and the cache hit rate has dropped.",
|
|
70
|
+
want: :redis_caching_guide
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: "Production outage from table lock",
|
|
74
|
+
query: "We had an outage caused by a database lock during a migration. What happened?",
|
|
75
|
+
want: :incident_postmortem
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
label: "Semantic gap — no shared keywords",
|
|
79
|
+
query: "Connection pool is full and new requests are being rejected.",
|
|
80
|
+
want: :postgres_runbook
|
|
81
|
+
},
|
|
82
|
+
].freeze
|
|
83
|
+
|
|
84
|
+
QUERIES.each do |q|
|
|
85
|
+
results = store.search(q[:query], limit: 3)
|
|
86
|
+
top = results.first
|
|
87
|
+
verdict = top[:key] == q[:want] ? "✓ correct" : "✗ expected #{q[:want]}"
|
|
88
|
+
|
|
89
|
+
puts "── #{q[:label]}"
|
|
90
|
+
puts " Query: \"#{q[:query]}\""
|
|
91
|
+
puts " Top result: #{top[:key]} (#{format("%.3f", top[:score])}) — #{verdict}"
|
|
92
|
+
puts " Ranking: " + results.map { |r| "#{r[:key]} #{format("%.3f", r[:score])}" }.join(" | ")
|
|
93
|
+
puts
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Delete and verify
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
puts "── Delete :redis_caching_guide, re-run cache query"
|
|
100
|
+
store.delete(:redis_caching_guide)
|
|
101
|
+
results = store.search("Redis evicting keys unexpectedly", limit: 2)
|
|
102
|
+
puts " Remaining keys: #{store.keys.inspect}"
|
|
103
|
+
puts " Top result after deletion: #{results.first[:key]}"
|
|
104
|
+
puts
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Memory integration
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
puts "── Memory integration"
|
|
110
|
+
memory = RobotLab::Memory.new(enable_cache: false)
|
|
111
|
+
|
|
112
|
+
DOCUMENTS.each { |key, text| memory.store_document(key, text) }
|
|
113
|
+
puts " Stored #{memory.document_keys.size} documents via memory.store_document"
|
|
114
|
+
|
|
115
|
+
hits = memory.search_documents("slow query bloat vacuum autovacuum", limit: 2)
|
|
116
|
+
puts " Search 'slow query bloat vacuum autovacuum':"
|
|
117
|
+
hits.each { |h| puts " #{h[:key]} (#{format("%.3f", h[:score])})" }
|
|
118
|
+
|
|
119
|
+
memory.delete_document(:postgres_runbook)
|
|
120
|
+
puts " After delete, keys: #{memory.document_keys.inspect}"
|
|
121
|
+
puts
|
|
122
|
+
|
|
123
|
+
# ---------------------------------------------------------------------------
|
|
124
|
+
# RAG pattern
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
puts "=" * 60
|
|
127
|
+
puts "RAG Pattern: retrieve relevant docs, then generate with LLM"
|
|
128
|
+
puts "=" * 60
|
|
129
|
+
puts
|
|
130
|
+
|
|
131
|
+
rag_query = "Our Sidekiq jobs exhaust retries and land in the dead queue after a Stripe outage."
|
|
132
|
+
|
|
133
|
+
hits = store.search(rag_query, limit: 2)
|
|
134
|
+
context = hits.map { |h| h[:text] }.join("\n\n---\n\n")
|
|
135
|
+
|
|
136
|
+
puts "User question:"
|
|
137
|
+
puts " \"#{rag_query}\""
|
|
138
|
+
puts
|
|
139
|
+
puts "Retrieved #{hits.size} document(s) — #{context.split.size} words of context:"
|
|
140
|
+
hits.each { |h| puts " #{h[:key]} (score #{format("%.3f", h[:score])})" }
|
|
141
|
+
puts
|
|
142
|
+
puts "LLM call would be:"
|
|
143
|
+
puts ' robot.run("Use the following docs:\n#{context}\n\nQuestion: #{rag_query}")'
|
|
144
|
+
puts
|
|
145
|
+
|
|
146
|
+
puts "Done."
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fastembed"
|
|
4
|
+
require_relative "document_store/version"
|
|
5
|
+
|
|
6
|
+
module RobotLab
|
|
7
|
+
# Embedding-based document store for semantic search over arbitrary text.
|
|
8
|
+
#
|
|
9
|
+
# Documents are embedded using fastembed (BAAI/bge-small-en-v1.5 by default)
|
|
10
|
+
# and stored in memory. Queries are embedded the same way, then compared by
|
|
11
|
+
# cosine similarity to find the closest documents.
|
|
12
|
+
#
|
|
13
|
+
# The embedding model is initialised lazily on first use — the ONNX model
|
|
14
|
+
# file is downloaded on that first call (cached locally afterwards).
|
|
15
|
+
#
|
|
16
|
+
# @example Standalone
|
|
17
|
+
# store = RobotLab::DocumentStore.new
|
|
18
|
+
# store.store(:q4_report, "Q4 revenue came in at $4.2M, up 18% YoY…")
|
|
19
|
+
# store.store(:q3_report, "Q3 showed 15% growth, driven by APAC…")
|
|
20
|
+
#
|
|
21
|
+
# results = store.search("revenue growth", limit: 2)
|
|
22
|
+
# results.each { |r| puts "#{r[:key]} (#{r[:score].round(3)}): #{r[:text][0..60]}" }
|
|
23
|
+
#
|
|
24
|
+
# @example With robot_lab Memory
|
|
25
|
+
# memory.store_document(:readme, File.read("README.md"))
|
|
26
|
+
# memory.search_documents("how to configure redis", limit: 3)
|
|
27
|
+
#
|
|
28
|
+
class DocumentStore
|
|
29
|
+
# Default embedding model used when none is specified.
|
|
30
|
+
DEFAULT_MODEL = "BAAI/bge-small-en-v1.5"
|
|
31
|
+
|
|
32
|
+
# @param model_name [String] fastembed model name
|
|
33
|
+
def initialize(model_name: DEFAULT_MODEL)
|
|
34
|
+
@model_name = model_name
|
|
35
|
+
@documents = {} # key (Symbol) => { text: String, vector: Array<Float> }
|
|
36
|
+
@mutex = Mutex.new
|
|
37
|
+
@model = nil # lazy: initialised on first embed call
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Embed +text+ and store it under +key+.
|
|
41
|
+
#
|
|
42
|
+
# If a document already exists under +key+ it is replaced.
|
|
43
|
+
#
|
|
44
|
+
# @param key [Symbol, String] identifier for this document
|
|
45
|
+
# @param text [String] the document text to embed and store
|
|
46
|
+
# @return [self]
|
|
47
|
+
def store(key, text)
|
|
48
|
+
key = key.to_sym
|
|
49
|
+
vector = passage_vector(text)
|
|
50
|
+
@mutex.synchronize { @documents[key] = { text: text, vector: vector } }
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Search for documents semantically similar to +query+.
|
|
55
|
+
#
|
|
56
|
+
# @param query [String] natural-language search query
|
|
57
|
+
# @param limit [Integer] maximum number of results (default 5)
|
|
58
|
+
# @return [Array<Hash>] results sorted by score descending.
|
|
59
|
+
# Each hash contains +:key+, +:text+, and +:score+ (Float 0.0..1.0).
|
|
60
|
+
def search(query, limit: 5)
|
|
61
|
+
return [] if empty?
|
|
62
|
+
|
|
63
|
+
query_vec = query_vector(query)
|
|
64
|
+
results = []
|
|
65
|
+
|
|
66
|
+
@mutex.synchronize do
|
|
67
|
+
@documents.each do |key, doc|
|
|
68
|
+
score = cosine_similarity(query_vec, doc[:vector])
|
|
69
|
+
results << { key: key, text: doc[:text], score: score }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
results.sort_by { |r| -r[:score] }.first(limit)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Number of stored documents.
|
|
77
|
+
# @return [Integer]
|
|
78
|
+
def size
|
|
79
|
+
@mutex.synchronize { @documents.size }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Keys of all stored documents.
|
|
83
|
+
# @return [Array<Symbol>]
|
|
84
|
+
def keys
|
|
85
|
+
@mutex.synchronize { @documents.keys }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Whether the store contains no documents.
|
|
89
|
+
# @return [Boolean]
|
|
90
|
+
def empty?
|
|
91
|
+
@mutex.synchronize { @documents.empty? }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Remove the document stored under +key+.
|
|
95
|
+
# @param key [Symbol, String]
|
|
96
|
+
# @return [self]
|
|
97
|
+
def delete(key)
|
|
98
|
+
@mutex.synchronize { @documents.delete(key.to_sym) }
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Remove all stored documents.
|
|
103
|
+
# @return [self]
|
|
104
|
+
def clear
|
|
105
|
+
@mutex.synchronize { @documents.clear }
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def model
|
|
112
|
+
@model ||= Fastembed::TextEmbedding.new(model_name: @model_name, show_progress: false)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def passage_vector(text)
|
|
116
|
+
model.passage_embed([text]).to_a.first
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def query_vector(text)
|
|
120
|
+
model.query_embed([text]).to_a.first
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def cosine_similarity(vec_a, vec_b)
|
|
124
|
+
return 0.0 unless vec_a && vec_b
|
|
125
|
+
return 0.0 if vec_a.empty? || vec_b.empty?
|
|
126
|
+
return 0.0 if vec_a.length != vec_b.length
|
|
127
|
+
|
|
128
|
+
dot = 0.0
|
|
129
|
+
norm_a = 0.0
|
|
130
|
+
norm_b = 0.0
|
|
131
|
+
|
|
132
|
+
vec_a.each_with_index do |a, i|
|
|
133
|
+
b = vec_b[i]
|
|
134
|
+
dot += a * b
|
|
135
|
+
norm_a += a * a
|
|
136
|
+
norm_b += b * b
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
return 0.0 if norm_a.zero? || norm_b.zero?
|
|
140
|
+
|
|
141
|
+
dot / (Math.sqrt(norm_a) * Math.sqrt(norm_b))
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
data/mkdocs.yml
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
site_name: robot_lab-document_store
|
|
2
|
+
site_description: Embedding-based semantic document search for the RobotLab LLM agent framework
|
|
3
|
+
site_author: Dewayne VanHoozer
|
|
4
|
+
site_url: https://madbomber.github.io/robot_lab-document_store
|
|
5
|
+
copyright: Copyright © 2025 Dewayne VanHoozer
|
|
6
|
+
|
|
7
|
+
repo_name: MadBomber/robot_lab-document_store
|
|
8
|
+
repo_url: https://github.com/MadBomber/robot_lab-document_store
|
|
9
|
+
edit_uri: edit/main/docs/
|
|
10
|
+
|
|
11
|
+
theme:
|
|
12
|
+
name: material
|
|
13
|
+
|
|
14
|
+
palette:
|
|
15
|
+
- scheme: default
|
|
16
|
+
primary: blue
|
|
17
|
+
accent: amber
|
|
18
|
+
toggle:
|
|
19
|
+
icon: material/brightness-7
|
|
20
|
+
name: Switch to dark mode
|
|
21
|
+
|
|
22
|
+
- scheme: slate
|
|
23
|
+
primary: blue
|
|
24
|
+
accent: amber
|
|
25
|
+
toggle:
|
|
26
|
+
icon: material/brightness-4
|
|
27
|
+
name: Switch to light mode
|
|
28
|
+
|
|
29
|
+
font:
|
|
30
|
+
text: Roboto
|
|
31
|
+
code: Roboto Mono
|
|
32
|
+
|
|
33
|
+
icon:
|
|
34
|
+
repo: fontawesome/brands/github
|
|
35
|
+
logo: material/database-search
|
|
36
|
+
|
|
37
|
+
features:
|
|
38
|
+
- navigation.instant
|
|
39
|
+
- navigation.tracking
|
|
40
|
+
- navigation.tabs
|
|
41
|
+
- navigation.tabs.sticky
|
|
42
|
+
- navigation.path
|
|
43
|
+
- navigation.indexes
|
|
44
|
+
- navigation.top
|
|
45
|
+
- navigation.footer
|
|
46
|
+
- toc.follow
|
|
47
|
+
- search.suggest
|
|
48
|
+
- search.highlight
|
|
49
|
+
- search.share
|
|
50
|
+
- header.autohide
|
|
51
|
+
- content.code.copy
|
|
52
|
+
- content.code.annotate
|
|
53
|
+
- content.tabs.link
|
|
54
|
+
- content.tooltips
|
|
55
|
+
- content.action.edit
|
|
56
|
+
- content.action.view
|
|
57
|
+
|
|
58
|
+
plugins:
|
|
59
|
+
- search:
|
|
60
|
+
separator: '[\s\-,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
|
|
61
|
+
|
|
62
|
+
markdown_extensions:
|
|
63
|
+
- abbr
|
|
64
|
+
- admonition
|
|
65
|
+
- attr_list
|
|
66
|
+
- def_list
|
|
67
|
+
- footnotes
|
|
68
|
+
- md_in_html
|
|
69
|
+
- tables
|
|
70
|
+
- toc:
|
|
71
|
+
permalink: true
|
|
72
|
+
title: On this page
|
|
73
|
+
- pymdownx.betterem:
|
|
74
|
+
smart_enable: all
|
|
75
|
+
- pymdownx.caret
|
|
76
|
+
- pymdownx.details
|
|
77
|
+
- pymdownx.emoji:
|
|
78
|
+
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
|
79
|
+
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
|
80
|
+
- pymdownx.highlight:
|
|
81
|
+
anchor_linenums: true
|
|
82
|
+
line_spans: __span
|
|
83
|
+
pygments_lang_class: true
|
|
84
|
+
- pymdownx.inlinehilite
|
|
85
|
+
- pymdownx.magiclink:
|
|
86
|
+
repo_url_shorthand: true
|
|
87
|
+
user: MadBomber
|
|
88
|
+
repo: robot_lab-document_store
|
|
89
|
+
normalize_issue_symbols: true
|
|
90
|
+
- pymdownx.mark
|
|
91
|
+
- pymdownx.smartsymbols
|
|
92
|
+
- pymdownx.superfences:
|
|
93
|
+
custom_fences:
|
|
94
|
+
- name: mermaid
|
|
95
|
+
class: mermaid
|
|
96
|
+
format: !!python/name:pymdownx.superfences.fence_code_format
|
|
97
|
+
- pymdownx.tabbed:
|
|
98
|
+
alternate_style: true
|
|
99
|
+
- pymdownx.tasklist:
|
|
100
|
+
custom_checkbox: true
|
|
101
|
+
- pymdownx.tilde
|
|
102
|
+
|
|
103
|
+
extra:
|
|
104
|
+
social:
|
|
105
|
+
- icon: fontawesome/brands/github
|
|
106
|
+
link: https://github.com/MadBomber/robot_lab-document_store
|
|
107
|
+
name: robot_lab-document_store on GitHub
|
|
108
|
+
- icon: fontawesome/solid/gem
|
|
109
|
+
link: https://rubygems.org/gems/robot_lab-document_store
|
|
110
|
+
name: robot_lab-document_store on RubyGems
|
|
111
|
+
|
|
112
|
+
nav:
|
|
113
|
+
- Home: index.md
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: robot_lab-document_store
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dewayne VanHoozer
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fastembed
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Provides RobotLab::DocumentStore — a thread-safe, in-memory semantic
|
|
27
|
+
search store backed by fastembed (BAAI/bge-small-en-v1.5). Store text documents
|
|
28
|
+
by key and retrieve the closest matches to a natural-language query using cosine
|
|
29
|
+
similarity. Works standalone or as a drop-in extension for robot_lab agents and
|
|
30
|
+
networks.
|
|
31
|
+
email:
|
|
32
|
+
- dvanhoozer@gmail.com
|
|
33
|
+
executables: []
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- ".envrc"
|
|
38
|
+
- ".github/workflows/deploy-github-pages.yml"
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- LICENSE.txt
|
|
41
|
+
- README.md
|
|
42
|
+
- Rakefile
|
|
43
|
+
- docs/index.md
|
|
44
|
+
- examples/26_document_store.rb
|
|
45
|
+
- examples/26_document_store/api_versioning_adr.md
|
|
46
|
+
- examples/26_document_store/incident_postmortem.md
|
|
47
|
+
- examples/26_document_store/postgres_runbook.md
|
|
48
|
+
- examples/26_document_store/redis_caching_guide.md
|
|
49
|
+
- examples/26_document_store/sidekiq_guide.md
|
|
50
|
+
- lib/robot_lab/document_store.rb
|
|
51
|
+
- lib/robot_lab/document_store/version.rb
|
|
52
|
+
- mkdocs.yml
|
|
53
|
+
homepage: https://github.com/madbomber/robot_lab-document_store
|
|
54
|
+
licenses:
|
|
55
|
+
- MIT
|
|
56
|
+
metadata:
|
|
57
|
+
homepage_uri: https://github.com/madbomber/robot_lab-document_store
|
|
58
|
+
source_code_uri: https://github.com/madbomber/robot_lab-document_store
|
|
59
|
+
changelog_uri: https://github.com/madbomber/robot_lab-document_store/blob/main/CHANGELOG.md
|
|
60
|
+
rubygems_mfa_required: 'true'
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 3.2.0
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 4.0.11
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: Embedding-based semantic document store for RobotLab agents
|
|
78
|
+
test_files: []
|