Privoid-ai's picture
Privoid Action-1B experimental preview (LoRA adapter, validator, API, locked test results)
b68d688 verified
|
Raw History Blame Contribute Delete
7.13 kB
# Privoid Action — local inference API
A minimal FastAPI service (`privoid_action_api/`) that serves the Action adapter on the original Granite base, or a
validated merged model, from **local files only**.
## Run
```powershell
cd Privoid-Action-1B
copy api\config.example.json api\config.json # then set adapter_path to this repository's root folder
python -m privoid_action_api --config api\config.json --check-config
python -m privoid_action_api --config api\config.json
```
* Binds to `127.0.0.1:8095` by default. Any non-loopback host is refused unless `allow_non_loopback: true` is set.
* Startup sets `HF_HUB_OFFLINE=1` / `TRANSFORMERS_OFFLINE=1` before any Hugging Face import and loads with
`local_files_only=True`. It never downloads, trains or prepares data. If artifacts are missing or invalid, the server
still starts, `/health` reports `model_state: "unavailable"`, and `/extract` returns 503.
* The loader refuses adapters whose base is not Granite 4.0 1B.
* The model loads at startup (`load_on_startup: true`). One generation runs at a time (`max_concurrent: 1`, sized for
a 12 GB GPU), with up to `max_queue` requests waiting.
## Endpoints
### `GET /health`
Process health. Never loads or downloads anything.
```json
{"status": "ok", "model_state": "ready", "request_id": "3f1c0a9e5b7d2c41"}
```
`model_state` is one of `not_loaded`, `loading`, `ready`, `unavailable`.
### `GET /model-info`
Model identity (base ID and revision, adapter folder name and a short weight hash), schema version, and limits
(`max_prompt_tokens`, `max_new_tokens`, `max_body_bytes`, timeout, concurrency, contract limits). Filesystem paths are
not exposed.
### `POST /extract`
Body: one transcript segment in the input schema (`docs/SCHEMA.md` §1). Example requests are in `examples/`:
```powershell
curl.exe -s -X POST http://127.0.0.1:8095/extract -H "content-type: application/json" `
--data-binary "@examples/request_accepted_request.json"
```
Response 200 (abridged; the full illustrative response is in `examples/response_accepted_request.json`. It was produced
by the deterministic finalizer from a hand-written output, **not** by the model):
```json
{
"request_id": "…", "schema_version": "1.0",
"result": {
"schema_version": "1.0", "meeting_id": "meeting-001",
"actions": [{
"id": "A1", "task": "Send the revised proposal.", "owner_speaker_ids": ["S2"], "owner_text": "Omar",
"assignment_status": "accepted", "deadline_text": "Friday", "conditions": [], "status": "active",
"evidence": [{"turn_id": "T1", "quote": "Omar, can you send the revised proposal by Friday?"},
{"turn_id": "T2", "quote": "Yes, I will send it by Friday."}],
"needs_review": false, "review_reasons": [], "pipeline_flags": ["deadline_not_normalized"],
"owner_names": ["Omar"], "deadline_normalized": null,
"deadline_normalization": {"rule": null, "reason": "bare_weekday_ambiguous"}}],
"decisions": [], "unresolved_requests": [], "rejected_records": []},
"validation": {"raw_output_fully_valid": true, "rejected_record_count": 0, "grounded_record_count": 0},
"usage": {"prompt_tokens": 882, "seconds": 1.9},
"model": {"backend": "transformers", "base_model_id": "ibm-granite/granite-4.0-1b", "…": "…"},
"notice": "Records are extracted from the transcript for human review. They do not create tasks, …"
}
```
(The `seconds` value above is a placeholder, not a measurement.)
### `POST /extract-meeting` (experimental, disabled by default)
Enable with `enable_meeting_endpoint: true`. The endpoint cuts the meeting into whole-turn segments
(`max_segment_tokens`, `segment_overlap_turns`), extracts each segment, and reconciles the results with the documented
rules (`docs/SCHEMA.md` §5). The response is marked `"experimental": true` and includes `segments`,
`reconciliation_log`, `limitations`, `segment_failures` and `complete`. Its quality is measured separately (notebook
§10.5). Cross-segment cancellations are flagged, not applied.
## Errors
All errors are JSON: `{"error": code, "message": text, "request_id": id, …}`. They never contain stack traces or
filesystem paths.
| Status | `error` | When |
|---|---|---|
| 400 | `invalid_json` | Body is not JSON |
| 413 | `input_too_large` | Body > `max_body_bytes`, a contract size limit, or prompt tokens > `max_prompt_tokens`. **Nothing is truncated**; split the transcript. |
| 422 | `invalid_input` | Schema problems (unknown speaker, duplicate turn ID, invalid date or timezone, control sequences …), listed in `problems` |
| 429 | `busy` | All slots busy and the wait queue is full |
| 502 | `model_output_invalid` | The model output was not one schema-valid JSON object. No records are returned and none are invented. |
| 503 | `model_unavailable` | Model not loaded (see the server log) |
| 504 | `timeout` | Generation exceeded `request_timeout_s`. Generation is also bounded by `max_new_tokens` and a matching `max_time`, and the slot stays occupied until the worker thread really finishes. |
| 500 | `internal_error` | Unexpected failure (details only in the server log, keyed by request ID) |
A record that fails validation (quote not found, unknown speaker, inconsistent status) is returned in
`result.rejected_records` with its errors, and `validation.raw_output_fully_valid` is `false`. Valid records are still
returned.
With `ground_evidence: true` (the default), quotes that the model tidied (fillers or repeated words dropped) or
attached to a neighbouring turn are first re-anchored to the transcript's verbatim words; see `docs/SCHEMA.md` §2.5.
Every record changed this way carries a pipeline flag, and `validation.grounded_record_count` counts them. Set
`ground_evidence: false` to reject such records instead.
## Privacy behaviour
* Transcript bodies are never logged and never written to disk. Log lines carry the request ID, path, status, latency,
turn count, prompt-token count and validation counts only. Uvicorn's access log is disabled.
* No cloud fallback exists. The only network listener is the configured local address.
* Validation, and any constrained decoding a client might add, ensures the output is well-formed. It is **not proof of
semantic correctness**: owners, statuses and deadlines must still be reviewed by a human before anyone acts on them.
* Extracted records never send messages, create tasks in external systems, or commit participants to anything.
Privacy of a deployment also depends on the surrounding application, its logs and storage, the host OS and its
telemetry, and the network configuration. Those are outside this API.
## Tests
`python -m unittest discover -s tests -t .` runs the API tests against a **mocked** backend. They cover every route,
error code, oversize rejection, invalid-output rejection, record-level rejection, timeouts, the concurrency bound, a full
queue (429), hidden internal errors, the loopback-only default, no transcript text in logs, and a check that importing
the API does not import torch/transformers/peft/trl. Real-model checks are in notebook §12 (`ALLOW_API_CHECKS`).