ARCANA Service#
- saia_python.arcana.extract_arcana_name(id_or_name: str) str[source]#
Extract the arcana name from a full ID or plain name.
The ARCANA chat endpoint uses the full
owner/nameformat, while management endpoints (get,upload) use just thename. This function accepts either and returns the name part.- Parameters:
id_or_name – Either
"owner/name"or just"name".- Returns:
The name portion (everything after the first
/, or the input unchanged if there is no/).
- class saia_python.arcana.ArcanaService(session: Session, base_url: str, api_key: str, *, timeout: float | tuple[float, float] | None = (10.0, 60.0), retry: RetryPolicy | bool | None = None)[source]#
Access the ARCANA/RAG endpoints.
The ARCANA API uses a different auth scheme (plain key, no
Bearerprefix) and a different URL path. This is handled automatically.- Parameters:
session – A
requests.Session(auth header will be overridden per-request).base_url – The SAIA API base URL (e.g.
https://chat-ai.academiccloud.de/v1).api_key – The raw API key (needed because ARCANA omits the
Bearerprefix).timeout – Default
(connect, read)timeout in seconds applied to every ARCANA management request that does not set its own. Guards against the server accepting a request but never responding (e.g. while an arcana is locked mid-(re)index), which would otherwise block forever on the socket read. A singlefloatapplies to both phases; passNoneto disable. Defaults to(10, 60). The long-running chat path (chat()) is exempt.
- version() str[source]#
Return the ARCANA API version string.
Calls
GET /arcanas/api/v1/version.- Returns:
The version string (e.g.
"0.4.16").
- heartbeat() bool[source]#
Check whether the ARCANA service is alive.
Calls
GET /arcanas/api/v1/heartbeat. ReturnsTrueif the service responds with 204,Falseotherwise (including transport errors — it never raises).
- user_info() dict[source]#
Return the current user’s profile and arcana statistics.
Calls
GET /user/me. Returns username, email, name, arcana count, file count, and registration date.- Returns:
A dict with user profile fields.
- user_summary() str[source]#
Return a formatted overview of the user’s account and all arcanas.
Combines
user_info()andlist()into a single human-readable string.- Returns:
A multi-line summary.
- create(name: str, *, append_uuid: bool = True, update_toml: bool = False, toml_label: str | None = None) dict[source]#
Create a new arcana.
- Parameters:
name – Name for the new arcana (1–100 characters).
append_uuid – If
True(default), append a UUID4 suffix to the name (e.g.MyArcana-a1b2c3d4-...). This mirrors the behavior of the SAIA web UI and avoids name collisions.update_toml – If
True, add the new arcana toconfig.tomlafter creation.toml_label – Label under
[saia.arcana.labels]in config.toml. If omitted, the ID is appended to theidsarray.
- Returns:
A dict with the created arcana name and the full ID (
owner/name).
- delete(name: str, *, update_toml: bool = False) dict | None[source]#
Delete an arcana entirely.
Accepts either the plain name or the full
owner/nameID.- Parameters:
name – The arcana name or full
owner/nameID.update_toml – If
True, remove the arcana fromconfig.tomlafter deletion.
- Returns:
The API response, or
Noneif the response has no body.
- recreate(name: str, *, update_toml: bool = False) dict[source]#
Delete an arcana and recreate it empty with the same ID.
The minimal-call way to wipe an entire arcana: two requests (
delete()+create()) regardless of how many files it holds, versus onedelete_file()per file (thousands of calls, each its own read-timeout risk while the arcana is busy). The name — including any UUID suffix — is preserved verbatim viacreate(..., append_uuid=False), so a downstream pin on the fullowner/name-uuidID stays valid.Trade-offs versus emptying file-by-file (
delete_file()in a loop, which keeps the container): there is a brief window between the two calls where the arcana does not exist, and the recreated arcana is brand-new —created_at, sharing/permissions and any other container settings reset to defaults. Only the name/ID carries across.- Parameters:
name – The arcana name or full
owner/nameID to recreate.update_toml – Forwarded to
create().
- Returns:
The
create()result ({"name", "id", "message"}).- Raises:
APIError – If recreation fails after the delete already succeeded — the arcana is then gone. The message says so explicitly so the operator recreates it before any consumer (adapter / manifest pin) points at the ID.
- summary(*, arcana_ids: dict[str, str] | None = None) str[source]#
Return a formatted summary of configured and available arcanas.
Combines information from
load_arcana_ids()(configured IDs) andlist()(server-side arcanas) into a single human-readable string.- Parameters:
arcana_ids – Optional pre-loaded dict from
load_arcana_ids(). If omitted, loaded automatically.- Returns:
A multi-line summary string.
- get(name: str) dict[source]#
Retrieve details of a specific arcana.
Accepts either the plain name or the full
owner/nameID — the owner prefix is stripped automatically.- Parameters:
name – The arcana name or full
owner/nameID.- Returns:
Arcana details dict.
- info(name: str | None = None, *, data: dict | None = None, verbose: bool = False) str[source]#
Return a formatted summary of an arcana’s details.
- Parameters:
name – The arcana name or full
owner/nameID. Can be omitted ifdatais provided.data – Optional pre-fetched arcana dict (from
get()). Avoids a redundant API call when you already have the data.verbose – If
True, include additional fields (CLI version, vector DB version, error message if present).
- Returns:
A human-readable multi-line string.
- upload(name: str, file_path: str | Path, *, overwrite: bool = False) dict | None[source]#
Upload a file to an arcana for indexing.
Supported formats: PDF, text, markdown. Accepts either the plain name or the full
owner/nameID.- Parameters:
name – The arcana name or full
owner/nameID.file_path – Path to the file to upload.
overwrite – If
True, replace an existing file with the same name (uses PUT instead of POST).
- Returns:
The API response (may be
Noneon success per the API spec).
- upload_directory(name: str, directory: str | Path, *, pattern: str = '*', recursive: bool = False, overwrite: bool = False, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) list[dict][source]#
Upload all files in a directory to an arcana.
- Parameters:
name – The arcana name or full
owner/nameID.directory – Path to the directory containing files to upload.
pattern – Glob pattern to filter files (default
"*"— all files). For example,"*.pdf"to upload only PDFs.recursive – If
True, search subdirectories recursively (uses**/<pattern>).overwrite – If
True, replace existing files with the same name.verbose – If
True, print per-file upload status.on_result – Optional callback invoked as
on_result(local_path, entry)after each file (entryis that file’s{"file", "status", ["error"]}dict), for inline per-file provenance / transaction logging.
- Returns:
A list of dicts with keys
"file"(filename only),"status"("uploaded"or"failed"), and"error"(message string, only present on failure).
- upload_files(name: str, paths: Iterable[str | Path], *, overwrite: bool = True, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) list[dict][source]#
Upload an explicit, caller-chosen list of files to an arcana.
Unlike
upload_directory()(which globs a whole directory), this uploads exactly thepathsyou pass — so the selection of what to (re)upload is entirely the caller’s decision (e.g. the result of your own changed-file / checksum comparison). Pair it withlist_files()(whose dicts carry per-fileindex_info) and a singlegenerate_index()afterwards.- Parameters:
name – The arcana name or full
owner/nameID.paths – An iterable of paths to upload.
overwrite – If
True(default), replace existing files (PUT); ifFalse, create new files (POST). Defaults toTruebecause the typical caller has already decided these files are new or changed.verbose – If
True, print per-file upload status.on_result – Optional callback invoked as
on_result(local_path, entry)after each file (entryis that file’s{"file", "status", ["error"]}dict). Lets a caller record per-file provenance (e.g. a git SHA) or a transaction-log entry as each upload completes, without reimplementing this loop.
- Returns:
A list of
{"file", "status", ["error"]}dicts — the same shape asupload_directory().
- list_files(name: str) list[dict][source]#
List all files in an arcana.
Accepts either the plain name or the full
owner/nameID.- Parameters:
name – The arcana name or full
owner/nameID.- Returns:
name(str): file name — use withdownload_file(),delete_file(), or anoverwriteupload.size(int): size in bytes.owner_user_name(str): the owning user.created_at/updated_at(str): ISO-8601 timestamps.index_info(dict | None): per-file index state, orNoneif the file has never been indexed. When present it holdsindex_status(str — e.g."INDEXED","NOT_INDEXED","ERROR") andchunks_indexed(int — number of embedding chunks produced for the file).related_files(list | None): nested entries of the same shape, when the server groups derived files together.
The per-file
index_infolets callers see which files are already indexed without doing any work. Indexing itself is triggered per-arcana viagenerate_index()— the ARCANA API has no per-file index call.- Return type:
A list of file dicts (the API
FileOutSchema). Each entry has
- delete_file(name: str, file_name: str) dict | None[source]#
Delete a file from an arcana.
Accepts either the plain name or the full
owner/nameID.- Parameters:
name – The arcana name or full
owner/nameID.file_name – The name of the file to delete (as returned by
list_files()).
- Returns:
The API response, or
Noneif the response has no body.
- delete_files(name: str, file_names: Iterable[str], *, verbose: bool = False, on_result: Callable[[str, dict], None] | None = None) list[dict][source]#
Delete an explicit list of files from an arcana, by name.
The batch counterpart to
delete_file(): hand it the file names (as returned bylist_files()) and it deletes each one, capturing a per-file outcome instead of aborting on the first failure. Unlikedelete_directory()no local directory is consulted — the names come straight from the caller — so it can target files that no longer exist on disk (e.g. a name list from a CSV). Pairs with a thin CLI front-end that resolves which names to delete; this method only does the deleting.- Parameters:
name – The arcana name or full
owner/nameID.file_names – The file names to delete (flat names, as listed by
list_files()). Order is preserved; a repeated name is attempted each time (a second delete just reports the server’s response / 404).verbose – If
True, print per-file deletion status.on_result – Optional callback invoked as
on_result(file_name, entry)after each file (entryis that file’s{"file", "status", ["error"]}dict), for inline per-file logging.
- Returns:
A list of dicts with keys
"file"(the name),"status"("deleted"or"failed"), and"error"(only on failure) — the same shape every batch op returns.
- download_file(name: str, file_name: str, output_path: str | Path) Path[source]#
Download a file from an arcana to a local path.
Accepts either the plain name or the full
owner/nameID.- Parameters:
name – The arcana name or full
owner/nameID.file_name – The name of the file to download (as returned by
list_files()).output_path – Local path to save the file to.
- Returns:
The path the file was written to.
- delete_directory(name: str, directory: str | Path, *, pattern: str = '*', recursive: bool = False, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) list[dict][source]#
Delete files from an arcana that match filenames in a local directory.
Finds all files in
directorymatchingpattern, then deletes files with the same name from the arcana. Useful for removing a batch of files that were previously uploaded withupload_directory().- Parameters:
name – The arcana name or full
owner/nameID.directory – Local directory whose filenames to match.
pattern – Glob pattern to filter files (default
"*").recursive – If
True, search subdirectories recursively.verbose – If
True, print per-file deletion status.on_result – Optional callback invoked as
on_result(local_path, entry)after each file (entryis that file’s{"file", "status", ["error"]}dict), for inline per-file logging.
- Returns:
A list of dicts with keys
"file"(filename only),"status"("deleted"or"failed"), and"error"(only present on failure).
- sync_directory(name: str, directory: str | Path, *, select: Callable[[Path, dict | None], str], pattern: str = '*', recursive: bool = False, prune: bool = False, index: bool = True, index_wait: bool = True, verbose: bool = False, on_result: Callable[[Path, dict], None] | None = None) dict[source]#
Sync a local directory into an arcana under caller-defined rules.
The policy — which files to upload, replace, or skip — stays entirely outside the package: you supply a
selectcallback that decides per file. This method only does the plumbing: glob the directory, fetch the remote listing, apply your decisions, and (optionally) trigger a single index pass. ARCANA stores no content hash, so any content-change detection (e.g. SHA-256 against your own manifest) belongs inselect.The single index pass is efficient because the server only (re)embeds files that are not already
INDEXED— seegenerate_index().- Parameters:
name – The arcana name or full
owner/nameID.directory – Local directory to sync from.
select – Called once per local file as
select(local_path, remote)wherelocal_pathis apathlib.Pathandremoteis the matching file dict fromlist_files()(matched by name) orNoneif the file is not in the arcana yet. Must return"upload"(POST new),"replace"(PUT over an existing file), or"skip".pattern – Glob pattern for local files (default
"*").recursive – If
True, recurse into subdirectories.prune – If
True, delete remote files that have no local counterpart by name. Defaults toFalse(never deletes implicitly).index – If
True(default), callgenerate_index()once after the sync — but only when something actually changed.index_wait – Forwarded to
generate_index()aswait.verbose – If
True, print per-file actions and a summary.on_result – Optional callback invoked as
on_result(local_path, entry)for each local file as it is uploaded, replaced, or skipped (entrymirrors the batch-helper shape:{"file", "status", ["error"]}, wherestatusis one of"uploaded"/"replaced"/"skipped"/"failed"). Lets callers record per-file provenance / transaction logs inline. Not called forprunedeletions (those are remote-only).
- Returns:
A report
dictwith keys"uploaded","replaced","skipped","deleted"(lists of file names),"failed"(list of{"file", "error"}) and"index"(thegenerate_index()result, orNoneif indexing was skipped).- Raises:
ValueError – If
selectreturns anything other than"upload","replace", or"skip".
- generate_index(name: str, *, wait: bool = True, timeout: int = 600, poll_interval: int = 5) dict | None[source]#
Trigger index generation for an arcana.
By default this blocks until indexing completes (synchronous). For large arcanas the server may time out (504). Use
wait=Falseto fire the request and return immediately, then poll withinfo()to check the index status.- Parameters:
name – The arcana name or full
owner/nameID.wait – If
True(default), poll until indexing finishes. IfFalse, fire the request and return immediately.timeout – Maximum seconds to wait when
wait=True. Defaults to 600 (10 minutes).poll_interval – Seconds between status checks when
wait=True. Defaults to 5.
- Returns:
The arcana details dict (from
get()) whenwait=Trueand indexing completed, orNonewhenwait=Falseor on timeout.- Raises:
TimeoutError – If
wait=Trueand indexing does not complete withintimeoutseconds.
Note
Indexing is incremental on the server: files already at
index_status == "INDEXED"are skipped, so only files added or replaced since the last index (an upload resets a file toNOT_INDEXED) are (re)embedded. This is why the efficient pattern is upload only the changed files, then call this once — the single whole-arcana trigger re-embeds just those. The library relies on this skip-INDEXEDbehavior; if the server ever stops skipping, the call stays correct but re-embeds the whole arcana.
- delete_index(name: str) dict | None[source]#
Delete the index of an arcana.
Accepts either the plain name or the full
owner/nameID.- Parameters:
name – The arcana name or full
owner/nameID.- Returns:
The API response, or
Noneif the response has no body.
- setup_from_directory(name: str, source_dir: str | Path, *, pattern: str = '*.md', append_uuid: bool = True, update_toml: bool = False, toml_label: str | None = None, wait_for_index: bool = True, index_timeout: int = 600, verbose: bool = True) dict[source]#
End-to-end: create an arcana, upload a directory, build the index.
Composes
create(),upload_directory(), andgenerate_index()into a single call. The arcana name passed to upload + index is the one returned bycreate— i.e. with the UUID suffix whenappend_uuid=True(default), so the composition stays correct without the caller having to remember the renaming.- Parameters:
name – Display name for the new arcana (UUID suffix appended when
append_uuid=True).source_dir – Directory whose matching files should be uploaded to the new arcana.
pattern – Glob pattern passed to
upload_directory(). Defaults to"*.md".append_uuid – Forwarded to
create(). IfTrue(default), the UUID suffix avoids name collisions and mirrors the SAIA web UI behaviour.update_toml – Forwarded to
create(). IfTrue, add the new arcana toconfig.tomlafter creation.toml_label – Label under
[saia.arcana.labels]. Ignored whenupdate_tomlisFalse.wait_for_index – Forwarded to
generate_index()aswait. IfTrue(default), block until the index reachesINDEXED(or fails / times out).index_timeout – Forwarded to
generate_index()astimeout(seconds). Defaults to 600.verbose – Forwarded to
upload_directory(). Controls the per-file progress bar.
- Returns:
"arcana"(the result fromcreate()),"uploads"(the list fromupload_directory()), and"index"(the result fromgenerate_index()). Callers can inspect any step.- Return type:
A dict with three keys
Example:
result = client.arcana.setup_from_directory( "MyKB", "./markdown/", pattern="**/*.md", update_toml=True, toml_label="my_kb", ) print(result["arcana"]["id"]) # owner/MyKB-<uuid> print(len(result["uploads"])) # files uploaded print(result["index"]) # index_status
- chat(model: str, messages: list[dict], arcana_id: str, *, temperature: float | None = None, max_tokens: int | None = None, stream: bool = False, retry: RetryPolicy | bool | None = None, **kwargs) dict | SSEStream[source]#
Chat with RAG context from an arcana.
This uses the standard
/chat/completionsendpoint with arcana parameters injected.- Parameters:
model – Model identifier.
messages – Chat messages.
arcana_id – The arcana ID to use for retrieval.
stream – If
True, return a generator yielding chunks.**kwargs – Additional parameters forwarded to the API.
- Returns:
the API response dict, with an extra
"_rate_limits"key (a JSON-serializable dict; seeRateLimitInfo). Whenstream=True: anSSEStreamwhoserate_limitsattribute exposes the same dict.- Return type:
When
stream=False