parse-stack-next 5.7.6 → 5.8.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/CHANGELOG.md +830 -0
- data/README.md +14 -4
- data/docs/TEST_SERVER.md +2 -2
- data/docs/acl_clp_guide.md +7 -0
- data/docs/atlas_vector_search_guide.md +181 -13
- data/docs/client_sdk_guide.md +11 -0
- data/docs/mcp_guide.md +317 -6
- data/docs/mongodb_direct_guide.md +27 -0
- data/docs/usage_guide.md +38 -0
- data/docs/webhooks_guide.md +74 -17
- data/lib/parse/acl_scope.rb +159 -41
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/constraint_translator.rb +42 -15
- data/lib/parse/agent/describe.rb +3 -1
- data/lib/parse/agent/field_names.rb +53 -0
- data/lib/parse/agent/field_policy.rb +74 -0
- data/lib/parse/agent/mcp_deployments.rb +426 -0
- data/lib/parse/agent/mcp_rack_app.rb +424 -45
- data/lib/parse/agent/mcp_server.rb +23 -1
- data/lib/parse/agent/mcp_subscriptions.rb +124 -6
- data/lib/parse/agent/metadata_registry.rb +67 -8
- data/lib/parse/agent/prompt_hardening.rb +9 -3
- data/lib/parse/agent/tools.rb +378 -29
- data/lib/parse/agent.rb +93 -1
- data/lib/parse/api/batch.rb +10 -1
- data/lib/parse/api/schema.rb +23 -4
- data/lib/parse/api/sessions.rb +6 -2
- data/lib/parse/api/users.rb +88 -14
- data/lib/parse/atlas_search/protected_paths.rb +236 -0
- data/lib/parse/atlas_search.rb +95 -23
- data/lib/parse/authorization.rb +54 -1
- data/lib/parse/client/batch.rb +231 -35
- data/lib/parse/client/body_builder.rb +21 -0
- data/lib/parse/client/caching.rb +371 -27
- data/lib/parse/client/request.rb +26 -14
- data/lib/parse/client/response.rb +49 -6
- data/lib/parse/client.rb +201 -38
- data/lib/parse/clp_scope.rb +281 -23
- data/lib/parse/console.rb +2 -2
- data/lib/parse/embeddings/voyage.rb +181 -17
- data/lib/parse/graphql/type_generator.rb +3 -0
- data/lib/parse/model/acl.rb +119 -21
- data/lib/parse/model/associations/belongs_to.rb +25 -3
- data/lib/parse/model/associations/collection_proxy.rb +138 -17
- data/lib/parse/model/associations/has_many.rb +38 -9
- data/lib/parse/model/associations/has_one.rb +3 -1
- data/lib/parse/model/associations/pointer_collection_proxy.rb +109 -17
- data/lib/parse/model/associations/relation_collection_proxy.rb +134 -28
- data/lib/parse/model/bytes.rb +13 -5
- data/lib/parse/model/classes/role.rb +72 -0
- data/lib/parse/model/classes/session.rb +43 -0
- data/lib/parse/model/classes/user.rb +78 -3
- data/lib/parse/model/core/actions.rb +269 -67
- data/lib/parse/model/core/builder.rb +100 -8
- data/lib/parse/model/core/create_lock.rb +27 -2
- data/lib/parse/model/core/describe.rb +2 -0
- data/lib/parse/model/core/fetching.rb +21 -3
- data/lib/parse/model/core/pluralized_aliases.rb +8 -4
- data/lib/parse/model/core/properties.rb +488 -39
- data/lib/parse/model/core/querying.rb +7 -0
- data/lib/parse/model/core/schema.rb +5 -3
- data/lib/parse/model/core/search_indexing.rb +63 -0
- data/lib/parse/model/core/vector_searchable.rb +35 -6
- data/lib/parse/model/file.rb +9 -2
- data/lib/parse/model/geopoint.rb +61 -13
- data/lib/parse/model/model.rb +160 -9
- data/lib/parse/model/object.rb +265 -17
- data/lib/parse/model/phone.rb +54 -5
- data/lib/parse/model/pointer.rb +40 -6
- data/lib/parse/mongodb.rb +170 -60
- data/lib/parse/pipeline_security.rb +415 -26
- data/lib/parse/query/constraint.rb +30 -0
- data/lib/parse/query/constraints.rb +58 -32
- data/lib/parse/query/cursor.rb +3 -1
- data/lib/parse/query/operation.rb +62 -8
- data/lib/parse/query/ordering.rb +34 -6
- data/lib/parse/query.rb +1100 -134
- data/lib/parse/retrieval/agent_tool.rb +225 -8
- data/lib/parse/retrieval/benchmark.rb +149 -0
- data/lib/parse/retrieval/profiles.rb +320 -0
- data/lib/parse/retrieval/retriever.rb +10 -1
- data/lib/parse/retrieval.rb +2 -0
- data/lib/parse/schema/search_index_migrator.rb +23 -5
- data/lib/parse/schema.rb +74 -18
- data/lib/parse/stack/tasks.rb +6 -4
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +72 -14
- data/lib/parse/two_factor_auth/user_extension.rb +14 -2
- data/lib/parse/two_factor_auth.rb +11 -0
- data/lib/parse/vector_search/hybrid.rb +36 -18
- data/lib/parse/vector_search/index_definition.rb +237 -0
- data/lib/parse/vector_search.rb +46 -17
- data/lib/parse/webhooks/payload.rb +93 -6
- data/lib/parse/webhooks/replay_protection.rb +58 -20
- data/lib/parse/webhooks.rb +412 -40
- metadata +8 -1
data/README.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
A full-featured Ruby client SDK for [Parse Server](http://parseplatform.org/). [parse-stack-next](https://github.com/neurosynq/parse-stack-next) is a Ruby client SDK, REST client, and Active Model ORM for [Parse Server](http://parseplatform.org/), combining a low-level API client, a query engine, an object-relational mapper (ORM), and a Cloud Code Webhooks rack application in a single gem.
|
|
6
6
|
|
|
7
|
+
## What's new in 5.8
|
|
8
|
+
|
|
9
|
+
- **5.8.0: MCP deployments can expose less than their users can read.** `Parse::Agent.new(fields: { Customer => %i[display_name timezone] })` narrows a class's `agent_fields` for one agent, so a user-facing assistant and an analytics endpoint in one process expose different subsets of the same model. It applies to queries, includes, aggregation, search, schema output, exports, and `semantic_search` text, and `query_class`/`count_objects`/`export_data` now refuse filtering or sorting on a hidden field. See [CHANGELOG.md](./CHANGELOG.md)
|
|
10
|
+
- **5.8.0: Supported deployment patterns.** `MCPRackApp.user_scoped` serves signed-in application users (no master-key fallback, server-pinned identity and tenant), and `MCPRackApp.master_analytics` serves a shared read-only analytics endpoint that requires an operator `principal_resolver`. Cancellations and approval replies are now bound to the session's owner, revocation intervals are documented, and orphaned subscriptions are reaped. See [CHANGELOG.md](./CHANGELOG.md)
|
|
11
|
+
- **5.8.0: Retrieval profiles for `semantic_search`.** Profiles you register on the server (for example `fast`, `balanced`, `precise`; none ship by default) compose hybrid search and reranking with budgets, spend accounting, and observable fallback; each call emits a sanitized `parse.retrieval.search` event, and `Parse::Retrieval::Benchmark` measures profiles on labeled cases. See [CHANGELOG.md](./CHANGELOG.md)
|
|
12
|
+
- **5.8.0: Vector index definitions from the model, with optional quantization.** `Parse::VectorSearch::IndexDefinition` generates the Atlas definition from your declarations (filters and tenant field included) with preview and diff, and `quantization: :scalar`/`:binary` on a `:vector` property shrinks index memory without changing stored data. Contextualized embedding batches now adapt to provider size limits. See [CHANGELOG.md](./CHANGELOG.md)
|
|
13
|
+
- **5.8.0: Two silent mismatches fixed; check whether you need to act.** Queries now send a property's declared `field:` name exactly as declared (`property :account_id, :string, field: :account_id` queries `account_id`, no longer `accountId`), so queries that matched nothing against underscore or mixed-case columns now return rows. Vector search, index auto-discovery, and drift checks now use the stored column of a multi-word `:vector` property: an Atlas vector index whose path is the Ruby name (`body_embedding`) must be recreated on the stored column (`bodyEmbedding`). Drift detection reports the mismatch. See [CHANGELOG.md](./CHANGELOG.md)
|
|
14
|
+
|
|
15
|
+
See [CHANGELOG.md](./CHANGELOG.md) for the full 5.8 entry.
|
|
16
|
+
|
|
7
17
|
## What's new in 5.7
|
|
8
18
|
|
|
9
19
|
- **5.7.6: `semantic_search` respects `agent_fields` for chunk content.** A class that embedded a field hidden by its `agent_fields` allowlist returned that field's text as chunk content. Chunk text now comes only from embedded fields the agent may read; a hidden text source is refused with `:field_denied` before the search runs. See [CHANGELOG.md](./CHANGELOG.md)
|
|
@@ -92,7 +102,7 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full 5.1 entry, including breaking ch
|
|
|
92
102
|
- **MCP transport hardening** — Streamable HTTP `Mcp-Session-Id` header (renamed from `X-MCP-Session-Id`, **breaking**), `MCP-Protocol-Version` validation, `DELETE /` session termination, structured-content (`outputSchema`) on built-in tools, optional `health_path:` liveness probe
|
|
93
103
|
- **`Parse::GraphQL::TypeGenerator`** — generate `graphql-ruby` types directly from your `Parse::Object` subclasses (no Parse Server round-trip), with `:vector` columns surfaced as `[Float]` and association registries (`has_one_associations`, `has_many_associations`) populated at DSL time
|
|
94
104
|
- **LiveQuery promoted to stable** — the experimental warning is removed; `Parse.live_query_enabled = true` is retained as a network-egress safety toggle, not a stability gate
|
|
95
|
-
- **Server-version deprecation warning
|
|
105
|
+
- **Server-version deprecation warning**: a one-shot warning when connecting to a Parse Server older than the configured threshold (default `7.0.0`, override with `PARSE_DEPRECATED_SERVER_VERSION_BELOW`); silence with `Parse.suppress_server_version_warning = true`. The **supported baseline is Parse Server 9.x** (the SDK is developed and tested against a pinned `parse-server:9.10.3`); the default warning threshold is intentionally conservative so older deployments only get an advisory, not a hard break.
|
|
96
106
|
- **`mongo_relation_index :field, dedup: true`** — register a compound `{owningId, relatedId}` UNIQUE on relation join collections to prevent duplicate-pair subscriptions without breaking `has_many` semantics
|
|
97
107
|
|
|
98
108
|
See [CHANGELOG.md](./CHANGELOG.md) for the full 5.0 entry, including security-hardening notes and Ruby 3.x cleanup.
|
|
@@ -916,8 +926,8 @@ view = Parse.client.sdk_cache
|
|
|
916
926
|
store.verify_upstream_isolation!
|
|
917
927
|
```
|
|
918
928
|
|
|
919
|
-
**The two URLs must address different Redis databases.**
|
|
920
|
-
|
|
929
|
+
**The two URLs must address different Redis databases.** Through at least Parse
|
|
930
|
+
Server 9.10.3, a `_Role` write clears the cache with `FLUSHDB`, which on a shared
|
|
921
931
|
database deletes the SDK's cached responses and its create-locks along with it
|
|
922
932
|
([parse-server#10617](https://github.com/parse-community/parse-server/issues/10617)).
|
|
923
933
|
`verify_upstream_isolation!` detects this by scanning the SDK's own database
|
|
@@ -6473,7 +6483,7 @@ export PARSE_TEST_SERVER_URL=http://localhost:29337/parse
|
|
|
6473
6483
|
export PARSE_TEST_APP_ID=psnextItAppId
|
|
6474
6484
|
export PARSE_TEST_API_KEY=psnext-it-rest-key
|
|
6475
6485
|
export PARSE_TEST_MASTER_KEY=psnextItMasterKey
|
|
6476
|
-
export PARSE_TEST_MONGO_URI="mongodb://admin:password@localhost:29017/parse_stack_next_it?authSource=admin"
|
|
6486
|
+
export PARSE_TEST_MONGO_URI="mongodb://admin:password@localhost:29017/parse_stack_next_it?authSource=admin&directConnection=true"
|
|
6477
6487
|
export PARSE_TEST_REDIS_URL=redis://localhost:29379/0
|
|
6478
6488
|
export PARSE_TEST_LIVE_QUERY_URL=ws://localhost:29337
|
|
6479
6489
|
export ATLAS_URI="mongodb://localhost:29020/parse_atlas_test?directConnection=true"
|
data/docs/TEST_SERVER.md
CHANGED
|
@@ -62,8 +62,8 @@ default.
|
|
|
62
62
|
- **`PSNEXT_PREFIX`** (default `psnext-it`) names the Compose project and every
|
|
63
63
|
container. Set it (e.g. `PSNEXT_PREFIX=psnext-ci`) to run a second, fully
|
|
64
64
|
separate copy.
|
|
65
|
-
- **Versions**: Parse Server is pinned to `parseplatform/parse-server:9.
|
|
66
|
-
(see `scripts/docker/Dockerfile.parse`), MongoDB `mongo:
|
|
65
|
+
- **Versions**: Parse Server is pinned to `parseplatform/parse-server:9.10.3`
|
|
66
|
+
(see `scripts/docker/Dockerfile.parse`), MongoDB `mongo:9` (`MONGO_VERSION`), Redis
|
|
67
67
|
`redis:7-alpine`, Dashboard `parseplatform/parse-dashboard:9`.
|
|
68
68
|
- **Database**: Parse uses `parse_stack_next_it`.
|
|
69
69
|
|
data/docs/acl_clp_guide.md
CHANGED
|
@@ -491,6 +491,13 @@ described in §7. From a security standpoint, an Atlas Search call
|
|
|
491
491
|
with a session token is treated like a `Parse::Query` with a session
|
|
492
492
|
token — same scoping, same field stripping.
|
|
493
493
|
|
|
494
|
+
Stripping a protected field from results does not stop it from
|
|
495
|
+
deciding which documents match. A scoped `Parse::AtlasSearch.search`
|
|
496
|
+
is therefore refused (`Parse::CLPScope::Denied`) when `fields:` names a
|
|
497
|
+
field protected for the caller, or when it names no fields (a search
|
|
498
|
+
over every column) while the caller has any protected fields. Pass
|
|
499
|
+
`fields:` with the fields to search.
|
|
500
|
+
|
|
494
501
|
The `$search` stage itself runs on the Atlas Search index and is not
|
|
495
502
|
filtered by ACL. The ACL filter is applied as a `$match` stage by
|
|
496
503
|
`Parse::ACLScope` after `$search`, before results are returned. If
|
|
@@ -209,7 +209,7 @@ Parse::AtlasSearch::IndexCatalog.create_index(
|
|
|
209
209
|
fields: [
|
|
210
210
|
{
|
|
211
211
|
type: "vector",
|
|
212
|
-
path: "
|
|
212
|
+
path: "bodyEmbedding", # the STORED column, not the Ruby name
|
|
213
213
|
numDimensions: 1536,
|
|
214
214
|
similarity: "cosine",
|
|
215
215
|
},
|
|
@@ -221,6 +221,12 @@ Parse::AtlasSearch::IndexCatalog.create_index(
|
|
|
221
221
|
)
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
The vector `path` is the column the property is stored under:
|
|
225
|
+
`property :body_embedding, :vector` is saved as `bodyEmbedding` (or under an
|
|
226
|
+
explicit `field:` alias). `find_similar`, hybrid search, index discovery, and
|
|
227
|
+
drift checks all use that stored name; `Parse::VectorSearch::IndexDefinition`
|
|
228
|
+
generates it for you.
|
|
229
|
+
|
|
224
230
|
Including `_rperm` as a filter field lets the per-row ACL match
|
|
225
231
|
short-circuit at the index level — strongly recommended for any
|
|
226
232
|
field that ACL-scoped agents will search against.
|
|
@@ -241,6 +247,91 @@ indexes for one whose definition covers the requested `path`. The
|
|
|
241
247
|
first match wins; pass `index:` explicitly when you have more than
|
|
242
248
|
one covering index and want a specific one.
|
|
243
249
|
|
|
250
|
+
### Generating the definition from the model
|
|
251
|
+
|
|
252
|
+
Writing the definition by hand lets it drift from the model: a changed
|
|
253
|
+
`dimensions:`, a new `agent_searchable` filter field, or a tenant scope
|
|
254
|
+
the index does not cover. `Parse::VectorSearch::IndexDefinition` derives
|
|
255
|
+
the definition from the model's own declarations instead:
|
|
256
|
+
|
|
257
|
+
* the `:vector` property's path, `dimensions:`, `similarity:` (`cosine`
|
|
258
|
+
when undeclared), and optional `quantization:`;
|
|
259
|
+
* every `agent_searchable filter_fields:` entry as a `type: "filter"` path
|
|
260
|
+
(pointer fields use their `_p_<column>` storage path);
|
|
261
|
+
* the `agent_tenant_scope` field, which retrieval folds into
|
|
262
|
+
`$vectorSearch.filter` on every scoped query.
|
|
263
|
+
|
|
264
|
+
Output is deterministic (vector entry first, filters sorted by path), so
|
|
265
|
+
it diffs cleanly in review.
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
class Document < Parse::Object
|
|
269
|
+
property :category, :string
|
|
270
|
+
property :embedding, :vector, dimensions: 1024, similarity: :dotProduct
|
|
271
|
+
agent_searchable field: :embedding, filter_fields: %i[category]
|
|
272
|
+
|
|
273
|
+
# Declare the index; its definition is generated, not written by hand.
|
|
274
|
+
vector_search_index "document_vec"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Preview without touching Atlas.
|
|
278
|
+
Parse::VectorSearch::IndexDefinition.build(Document)
|
|
279
|
+
Parse::Schema.vector_index_definition(Document) # same thing
|
|
280
|
+
Parse::VectorSearch::IndexDefinition.preview(Document, name: "document_vec")
|
|
281
|
+
|
|
282
|
+
# Compare against what is deployed (a definition or a $listSearchIndexes entry).
|
|
283
|
+
live = Parse::AtlasSearch::IndexCatalog.find_vector_index("Document", field: :embedding)
|
|
284
|
+
Parse::VectorSearch::IndexDefinition.diff(Parse::Schema.vector_index_definition(Document), live)
|
|
285
|
+
# => { in_sync: false,
|
|
286
|
+
# vector: { "numDimensions" => { declared: 1024, live: 1536 } },
|
|
287
|
+
# filters_missing: ["category"], filters_extra: [] }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Applying stays explicit. `vector_search_index` declarations join the
|
|
291
|
+
model's `mongo_search_index` declarations in
|
|
292
|
+
`Parse::Schema::SearchIndexMigrator`, which plans first and only mutates
|
|
293
|
+
Atlas when asked; a drifted index is reported, not rebuilt, unless you
|
|
294
|
+
pass `update: true`. The definition is generated when the migrator plans,
|
|
295
|
+
so `agent_searchable` and `agent_tenant_scope` can be declared in any
|
|
296
|
+
order.
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
Document.search_indexes_plan # :to_create / :in_sync / :drifted / :orphans
|
|
300
|
+
Document.apply_search_indexes! # creates missing indexes only
|
|
301
|
+
Document.apply_search_indexes!(update: true) # also rebuilds drifted ones
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Index-side quantization
|
|
305
|
+
|
|
306
|
+
Atlas can quantize a float vector field when it builds the index, which
|
|
307
|
+
shrinks the index Atlas keeps in memory. Declare it per property; it is
|
|
308
|
+
off by default:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
property :embedding, :vector, dimensions: 1024, quantization: :scalar # or :binary
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The generated definition then carries `"quantization": "scalar"` (or
|
|
315
|
+
`"binary"`) on the vector field. Only the index changes: stored vectors and
|
|
316
|
+
the SDK write path stay full precision, so turning it on or off is an index
|
|
317
|
+
rebuild, not a re-embed.
|
|
318
|
+
|
|
319
|
+
| Setting | Index memory | Recall |
|
|
320
|
+
|---------|--------------|--------|
|
|
321
|
+
| none (default) | full float vectors | baseline |
|
|
322
|
+
| `:scalar` | roughly 4x smaller | small loss for most embedding models |
|
|
323
|
+
| `:binary` | roughly 32x smaller | larger loss; Atlas rescoring recovers part of it |
|
|
324
|
+
|
|
325
|
+
Measure recall on your own queries before adopting `:binary`; the right
|
|
326
|
+
choice depends on the embedding model and the collection. For small
|
|
327
|
+
collections the memory saving rarely matters.
|
|
328
|
+
|
|
329
|
+
First-query drift verification also checks quantization: an index whose
|
|
330
|
+
`quantization` differs from the property's declaration (an absent value
|
|
331
|
+
counts as none on either side) is reported under
|
|
332
|
+
`Parse::VectorSearch.index_drift_policy` like a dimension or similarity
|
|
333
|
+
mismatch.
|
|
334
|
+
|
|
244
335
|
---
|
|
245
336
|
|
|
246
337
|
## Running similarity queries: `find_similar`
|
|
@@ -302,6 +393,9 @@ index's `latestDefinition` against the model declaration:
|
|
|
302
393
|
(usually an index that predates a model change).
|
|
303
394
|
* `similarity` vs the property's declared `similarity:` (checked only
|
|
304
395
|
when both sides declare one).
|
|
396
|
+
* `quantization` vs the property's declared `quantization:` (absent on
|
|
397
|
+
either side means none, so an index quantized without a declaration
|
|
398
|
+
is drift too).
|
|
305
399
|
* When the class registers an `agent_tenant_scope`, the scope field
|
|
306
400
|
must appear among the index's `type: "filter"` paths — without it,
|
|
307
401
|
every tenant-scoped `$vectorSearch.filter` fails Atlas-side at
|
|
@@ -505,18 +599,27 @@ Two things to know:
|
|
|
505
599
|
one-chunk document. Registering a context model as an `embed` provider
|
|
506
600
|
works, but the stored vectors carry no surrounding-document context.
|
|
507
601
|
Use `embed_chunks` when chunk-level context is the point.
|
|
508
|
-
* **Request sizing is
|
|
509
|
-
at 1,000 documents, 16,000 chunks, and 120k tokens.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
602
|
+
* **Request sizing adapts; it is not exact token counting.** Voyage caps
|
|
603
|
+
one request at 1,000 documents, 16,000 chunks, and 120k input tokens.
|
|
604
|
+
`embed_chunks` (and `embed_text` on a context model) packs whole
|
|
605
|
+
documents into requests that stay within the document cap, a chunk
|
|
606
|
+
count whose **response** fits the SDK's response-size cap, and an
|
|
607
|
+
**estimated** 120k-token budget. The SDK has no tokenizer, so the
|
|
608
|
+
estimate assumes three bytes per token, which over-counts typical
|
|
609
|
+
English text and packs conservatively. A document's chunks always
|
|
610
|
+
travel together.
|
|
611
|
+
When Voyage still rejects a request as too large (its "batch size" or
|
|
612
|
+
"max allowed tokens per submitted batch" errors, or an HTTP 413), the
|
|
613
|
+
SDK halves that request by document and resends each half, keeping
|
|
614
|
+
the returned vectors aligned with your input. Only those positively
|
|
615
|
+
identified size errors trigger a split; any other 400 is raised as is,
|
|
616
|
+
with the provider's message on `BadRequestError#detail`. A document
|
|
617
|
+
that is rejected as too large even on its own raises a
|
|
618
|
+
`BadRequestError` naming its index, since no split can fix it: break
|
|
619
|
+
it into fewer or shorter chunks. A single chunk longer than the
|
|
620
|
+
model's context window ("tokens in an example exceeds the context
|
|
621
|
+
length") is also raised directly. Context models default to
|
|
622
|
+
`embed_batch_size: 32` for `embed_text` batches.
|
|
520
623
|
|
|
521
624
|
---
|
|
522
625
|
|
|
@@ -678,6 +781,71 @@ adapters implement only the network call (`#rerank_scores`).
|
|
|
678
781
|
> rate-limited tool error). Admin agents are exempt; direct
|
|
679
782
|
> `find_similar` / `retrieve` callers are not metered.
|
|
680
783
|
|
|
784
|
+
### Retrieval profiles for `semantic_search` (5.8)
|
|
785
|
+
|
|
786
|
+
An agent can choose among a few server-configured retrieval strategies by
|
|
787
|
+
name, without ever choosing a provider, endpoint, or credential. Register
|
|
788
|
+
rerankers by name, then profiles that reference them:
|
|
789
|
+
|
|
790
|
+
```ruby
|
|
791
|
+
Parse::Retrieval.register_reranker(:voyage,
|
|
792
|
+
Parse::Retrieval::Reranker::Voyage.new(api_key: ENV.fetch("VOYAGE_API_KEY"), model: "rerank-3-lite"))
|
|
793
|
+
|
|
794
|
+
Parse::Retrieval::Profiles.register(:fast, k: 5, max_k: 10)
|
|
795
|
+
Parse::Retrieval::Profiles.register(:balanced, k: 8, hybrid: true)
|
|
796
|
+
Parse::Retrieval::Profiles.register(:precise,
|
|
797
|
+
k: 8, reranker: :voyage,
|
|
798
|
+
rerank_candidates: 30, # documents retrieved and sent to the reranker (max 100)
|
|
799
|
+
rerank_top_n: 8, # documents kept after reranking
|
|
800
|
+
rerank_max_document_chars: 4_000, # each document's text is cut before it leaves the process
|
|
801
|
+
rerank_timeout: 5, # seconds
|
|
802
|
+
on_rerank_failure: :fallback, # or :raise
|
|
803
|
+
max_total_tokens: 8_000) # default response budget
|
|
804
|
+
|
|
805
|
+
agent.execute(:semantic_search, class_name: "Article", query: "refund policy", profile: "precise")
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
* **Validated at registration.** An unknown option, an unregistered reranker,
|
|
809
|
+
`k` above `max_k`, or a non-positive budget raises `ArgumentError` at boot.
|
|
810
|
+
An unknown profile name at call time is refused with the list of available
|
|
811
|
+
profiles.
|
|
812
|
+
* **Field-safe reranking.** The reranker receives the same text source as
|
|
813
|
+
chunk content, which `semantic_search` restricts to fields the agent may
|
|
814
|
+
read (its effective `agent_fields`, including any per-agent `fields:`
|
|
815
|
+
narrowing), cut to `rerank_max_document_chars`.
|
|
816
|
+
* **Spend.** Estimated rerank tokens are charged to the same per-tenant
|
|
817
|
+
`SpendCap` budget as the query embedding (admin agents are exempt).
|
|
818
|
+
* **Fallback is observable.** On a reranker timeout or provider error,
|
|
819
|
+
`:fallback` keeps the retrieval order and adds `rerank_fallback: true` and
|
|
820
|
+
`rerank_fallback_reason` to the result; `:raise` fails the call.
|
|
821
|
+
* **Defaults are unchanged.** Without `profile:` the tool behaves as before.
|
|
822
|
+
|
|
823
|
+
Each call emits one `parse.retrieval.search` notification with the profile,
|
|
824
|
+
`k`, candidate count, rerank stats (`used`, `documents`, `chars`,
|
|
825
|
+
`tokens_estimated`, `duration_ms`, `fallback`, `fallback_reason`), chunk and
|
|
826
|
+
document counts, budget drops, and timings. It never carries the query,
|
|
827
|
+
document text, field values, URLs, or credentials, and `tokens_estimated` is
|
|
828
|
+
the SDK's estimate, not provider-reported usage.
|
|
829
|
+
|
|
830
|
+
**Measuring profiles.** `Parse::Retrieval::Benchmark` scores profiles on a
|
|
831
|
+
labeled case set (recall@k, MRR, hit rate, mean and p95 latency, estimated
|
|
832
|
+
rerank tokens), overall and per tag. Cases may list `forbidden` ids that must
|
|
833
|
+
never be returned (restrictive ACLs, other tenants); any such hit is reported
|
|
834
|
+
as a violation.
|
|
835
|
+
|
|
836
|
+
```ruby
|
|
837
|
+
cases = Parse::Retrieval::Benchmark.load_cases("eval/cases.json")
|
|
838
|
+
runner = Parse::Retrieval::Benchmark.semantic_search_runner(agent, class_name: "Article")
|
|
839
|
+
# nil is the baseline (no profile); its results are reported under "default".
|
|
840
|
+
report = Parse::Retrieval::Benchmark.run(cases: cases, profiles: [nil, "fast", "precise"], runner: runner)
|
|
841
|
+
report["default"] # => the baseline, for comparison
|
|
842
|
+
report["precise"] # => { recall_at_k:, mrr:, hit_rate:, violations:, mean_ms:, p95_ms:, ... }
|
|
843
|
+
report["precise"][:by_tag]["long_document"]
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
Run it against your own data and Atlas index before recommending a profile;
|
|
847
|
+
the SDK does not ship measured defaults.
|
|
848
|
+
|
|
681
849
|
### Chunkers
|
|
682
850
|
|
|
683
851
|
The default is a fixed-size sliding window with overlap. Subclass
|
data/docs/client_sdk_guide.md
CHANGED
|
@@ -1176,6 +1176,17 @@ server-side on every event before it goes out the WebSocket — Bob will
|
|
|
1176
1176
|
not receive an event for an ACL-private row Alice creates, even if his
|
|
1177
1177
|
subscription matches the `where` clause.
|
|
1178
1178
|
|
|
1179
|
+
> **Role-based CLP grants need a server option.** Parse Server does not
|
|
1180
|
+
> resolve the subscriber's roles when LiveQuery evaluates Class Level
|
|
1181
|
+
> Permissions unless `enableLiveQueryClassLevelPermissionRoles: true` is
|
|
1182
|
+
> set (env `PARSE_SERVER_ENABLE_LIVE_QUERY_CLASS_LEVEL_PERMISSION_ROLES`,
|
|
1183
|
+
> available from Parse Server 9.10.3, default `false`). With the default,
|
|
1184
|
+
> a CLP such as `"find" => { "role:Editor" => true }` admits role members
|
|
1185
|
+
> over REST but rejects their LiveQuery subscription. Deployments that
|
|
1186
|
+
> grant LiveQuery access through `role:` entries in CLP must turn it on.
|
|
1187
|
+
> Object ACL `role:` entries are honored either way. The SDK test stack
|
|
1188
|
+
> enables it in `scripts/start-parse.sh`.
|
|
1189
|
+
|
|
1179
1190
|
> **Master-key authorization is per-CONNECTION, not per-subscription.**
|
|
1180
1191
|
> Parse Server resolves master-key (ACL/CLP-bypass) authorization once,
|
|
1181
1192
|
> from the connect frame; once set, EVERY subscription on that socket
|