@mindstudio-ai/remy 0.1.335 → 0.1.337
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.
|
@@ -22,6 +22,20 @@ result.content; // step-specific output
|
|
|
22
22
|
result.$billingCost; // cost in credits (if applicable)
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Where files an action produces end up
|
|
26
|
+
|
|
27
|
+
Any action that returns a file URL writes to the shared **public** MindStudio CDN by default: unlisted, but readable by anyone holding the URL, and it never expires. Pass a file store as `store` in the options object to write into the app's own storage instead — a private store means the URL only loads for an authorized app session.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
const Reports = files.defineStore('reports', { access: 'private' });
|
|
31
|
+
|
|
32
|
+
const { imageUrl } = await mindstudio.generateImage({ prompt }, { store: Reports });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Use a store whenever the file belongs to a specific user or is confidential** (a client document, a participant recording, anything the app wouldn't publish). The public default is for marketing images and other genuinely world-readable assets. See the `files` skill.
|
|
36
|
+
|
|
37
|
+
Inputs are safe either way: a private file or a `shareUrl` handed to an action is read where it sits, and any intermediate the platform needs (transcoding a video, rasterizing a PDF) is staged privately and deleted when the call finishes.
|
|
38
|
+
|
|
25
39
|
## Available Actions
|
|
26
40
|
|
|
27
41
|
### AI Generation
|
|
@@ -20,6 +20,7 @@ Don't use one when: the data is structured (`db`), you only need to store files
|
|
|
20
20
|
- **Ingest is async.** `add()` returns once queued; poll `documents()`, or use `--wait` from the CLI.
|
|
21
21
|
- **Reprocessing costs real money**, so changing how a corpus is built is always explicit.
|
|
22
22
|
- **Limits apply**: 25 data sources per app, 10,000 chunks per document, 300 searches/minute, and 5,000 documents per source when documents are added one at a time (bulk jobs and connectors, below, are how a corpus grows past that). Well clear of normal use — but **source names must be fixed, not computed per user or per request**, since referencing one creates it. Partition inside a source with document metadata instead: tag at add time (`add(bytes, { filename, metadata: { userId } })`), narrow at search time (`search(q, { filter: { metadata: { userId } } })`).
|
|
23
|
+
- **Whole documents, not excerpts.** Nothing needs trimming before it is added: a document becomes as many chunks as it needs, up to the 10,000 per document above (about 20M characters). Truncating to keep chunks manageable throws away retrievable text, and is never the fix for an ingest problem.
|
|
23
24
|
- **Credentials never appear in code or in chat.** A bucket's keys are app secrets (`remy-admin secrets set NAME --prod <value>`, or the dashboard); everything else refers to them by NAME. If the user pastes a key into the conversation, set it as a secret for them to use moving forward.
|
|
24
25
|
|
|
25
26
|
## Defining and searching
|
|
@@ -46,7 +47,7 @@ Created on first use, so searching a source the build hasn't populated returns n
|
|
|
46
47
|
|
|
47
48
|
Search is deterministic for a fixed corpus and configuration, so eval sets and regression checks are meaningful — key them on `(documentId, chunkIndex)` rather than on chunk text.
|
|
48
49
|
|
|
49
|
-
**Debugging retrieval.** Two opt-in options, neither of which changes the results or their order: `explain: true` adds `explain.{dense, lexical, matchedVia}` (which half of hybrid found each hit; costs two extra round trips), and `expand: 1` adds `neighbors.{before, after}` for surrounding context. When a document never comes back
|
|
50
|
+
**Debugging retrieval.** Two opt-in options, neither of which changes the results or their order: `explain: true` adds `explain.{dense, lexical, matchedVia}` (which half of hybrid found each hit; costs two extra round trips), and `expand: 1` adds `neighbors.{before, after}` for surrounding context. **When a document never comes back, or its chunk count looks wrong, read the split before theorising about the file.** `Policies.chunks(documentId)` shows exactly how that document was divided and `Policies.stats()` reports the config actually in effect; from the CLI, `datasources status` gives per-document state with its ingest error. One chunk for a long document, or a chunk much larger than the configured `--max-chars`, is a fact about the split, not about the file.
|
|
50
51
|
|
|
51
52
|
**A cold index (shared capacity).** By default a data source lives on shared retrieval capacity: its vectors sit in their own isolated partition of a pool many apps share, and the pool keeps only a working set resident. A source nobody has searched for a while is unloaded to make room and reloaded from durable storage on the next search. A small corpus reloads inside that search and nobody notices; a large one (hundreds of thousands of chunks) reloads in the background for a minute or two, and `search()` throws `index_warming` (HTTP 503) until it lands. That means *loading*, never *empty*: catch it, tell the user the knowledge base is warming up, and retry shortly. Before a demo, `remy-admin datasources hydrate --source <slug> --wait` reloads it ahead of time. The way out of the cycle is dedicated capacity (below): a source on its own provisioned retrieval is never unloaded. It has one pause of its own instead: after a large load the index catches up behind the writes, and until it has, `search()` throws `index_building` (HTTP 503) with "N of M vectors indexed" and the time left in the message. Same handling as `index_warming`: the knowledge base is being built, never empty. `remy-admin datasources list` shows the index as `building` or `ready`, with its rate and time left while it builds. A resource that resumes from hibernation, or whose instance the platform replaced, rebuilds every source placed on it from durable storage in the background; searches on those sources answer `index_warming` with the rebuild's progress until it lands.
|
|
52
53
|
|
|
@@ -118,7 +119,7 @@ for await (const doc of Policies.allDocuments()) { /* ... */ } // walk a corp
|
|
|
118
119
|
await Policies.remove(documentId);
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
Formats: pdf, docx, pptx, xlsx, odt, rtf, epub, images, txt, md, json, csv, tsv, log, html. When the file is not the document (a JSON record, a JSONL bundle of articles, a kill notice), the source needs a mapper — see below.
|
|
122
|
+
Formats: pdf, docx, pptx, xlsx, odt, rtf, epub, images, txt, md, json, csv, tsv, log, html. **Add the source bytes, not your own rendering of them** — built-in extraction reads every one of these, HTML included, so fetching a page and converting it to markdown yourself swaps a tested extractor for an untested one and makes its quirks the corpus's quirks. When the file is not the document (a JSON record, a JSONL bundle of articles, a kill notice), the source needs a mapper — see below.
|
|
122
123
|
|
|
123
124
|
Removing many documents at once: `Policies.removeWhere({ metadata: { year: 2019 } })` or `{ externalIdPrefix: 'archive/2019/' }` (the key a job or connector recorded) removes every match, vectors and bytes included, in pages of a thousand. From the CLI, `datasources rm --source policies --filter year=2019`. An empty filter is refused; deleting a whole source is `datasources delete`, never something app code does.
|
|
124
125
|
|
|
@@ -114,13 +114,15 @@ remy-admin files rm --store handoff --key <key> --private # revoke
|
|
|
114
114
|
|
|
115
115
|
## Generated assets
|
|
116
116
|
|
|
117
|
-
MindStudio SDK
|
|
117
|
+
Every MindStudio SDK Action that produces a file (`generateImage`, `generateVideo`, `generateSpeech`, `generatePdf`, `upscaleImage`, `trimMedia`, `convertPdfToImages`, `screenshotUrl`, `getGmailAttachments`, …) can write straight into a store — pass the handle as `store` in the options object:
|
|
118
118
|
|
|
119
119
|
```typescript
|
|
120
120
|
const { imageUrl } = await mindstudio.generateImage({ prompt }, { store: Assets });
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
If omitted,
|
|
123
|
+
If omitted, the file goes to the shared public MindStudio CDN, where the URL is unlisted but readable by anyone who has it, forever. **For anything confidential, pass a private store.** That's the one thing to get right here — the default is fine for marketing images and wrong for client material.
|
|
124
|
+
|
|
125
|
+
**Actions never publish what you give them.** An input that's already one of the app's own files — a private object, a `shareUrl` link, a `/_/files/...` path — is read where it sits, and anything the platform has to convert first (a non-mp4 video, the pages of a PDF, a document an extraction model must fetch) is staged privately and discarded when the call finishes. So handing `extractText` a private contract, or `trimMedia` a private `.mov`, leaves no public copy behind.
|
|
124
126
|
|
|
125
127
|
## When public vs private
|
|
126
128
|
|