Access the 'PnotP' platform < https://pnotp.ai> from R and 'RStudio'. One account API key drives data-compliant large language model chat, where every response carries a retention receipt; model training with pause, resume and cancel; model deployments and live inference; and hosted agents. Includes 'RStudio' addins to chat with the language model from the IDE, and a bridge to 'ellmer' for tool calling and streaming.
R client for the P¬P platform, built for RStudio. One
account API key (pnotp_sk_...) drives the whole platform from R:
data-compliant LLM chat with a retention receipt, model training with pause /
resume / cancel, deployments and live inference, and hosted agents.
install.packages("pnotp")
That is all. Two optional extras are offered for installation the first time
you need them: shiny + miniUI for the RStudio chat addin, and ellmer for
tool calling and streaming.
Mint a key in Studio → Settings → API keys, then:
library(pnotp)
pnotp_set_key() # asks for it, masked; or Addins → "Set PnotP API key"
pnotp_me()$email # is the key working?
To keep it across sessions, put PNOTP_API_KEY=pnotp_sk_... in ~/.Renviron
(usethis::edit_r_environ()). pnotp_set_key() never writes that file.
out <- pnotp_chat("In one sentence, what does a p-value measure?")
out
#> (illustrative output)
#> A p-value measures how surprising your data would be if the null hypothesis were true.
#> -- pnotp-compliant | 14 in / 21 out tokens | quoted cost $0.000010 | zero_retention: TRUE (zero_retention)
out$receipt # keep this as the audit artifact
P¬P stores neither the prompt nor the completion. Whether the whole path
is zero-retention depends on the model that served the call, and the receipt
says so (zero_retention, retention_basis, served_by); the print method
shows what the receipt states and never assumes it.
Conversations can be a list of messages or a data.frame; images and documents attach directly:
pnotp_chat(data.frame(role = c("system", "user"),
content = c("Answer in Spanish.", "What does dplyr::across() do?")))
pnotp_chat("What does this figure show?", image = "figure.png")
pnotp_chat("Summarise this report", file = "report.pdf")
Calls are metered per token against your credits. Rates need no key:
pnotp_llm_pricing().
pnotp_ellmer_chat() returns an ellmer chat
pointed at P¬P's OpenAI-compatible endpoint, so R functions become tools and
answers stream:
chat <- pnotp_ellmer_chat(system_prompt = "You are a terse R expert.")
chat$register_tool(ellmer::tool(
function(n) summary(rnorm(n)),
name = "simulate", description = "Summarise n standard-normal draws.",
arguments = list(n = ellmer::type_integer())
))
chat$chat("Simulate 1000 draws and tell me the median.")
After installing, the Addins menu has:
| Addin | What it does |
|---|---|
| Chat with PnotP | Chat in the Viewer pane. Selected code is pre-filled; every answer shows its receipt line; Insert answer writes it below your selection (its code, or the answer as # comments). The conversation lives only in your R session. |
| Explain selection with PnotP | Explains the selected code in the console. |
| Set PnotP API key | Asks for the key (masked) and checks it against the API. |
Bind any of them to a shortcut under Tools → Modify Keyboard Shortcuts.
A tabular model trains straight from a data.frame:
p <- pnotp_project_create("Iris", task_type = "multiclass_classification")
m <- pnotp_model_create(p, "iris-v1", pnotp_arch_tabular_classifier(n_features = 4, num_classes = 3))
job <- pnotp_train(m, iris, target_column = "Species", epochs = 20)
job <- pnotp_wait(job) # Esc stops waiting; the job keeps running
plot(pnotp_progress(job)) # train_loss / val_accuracy per epoch
Image models train on a .zip of class-named folders
(pnotp_arch_image_classifier()). pnotp_job_pause(), pnotp_job_resume()
and pnotp_job_cancel() control a run; compute = "gpu" trains on a GPU,
billed per second (see pnotp_compute_options()).
dep <- pnotp_deploy_upload(p, "prod", "model.pt", compliant = TRUE)
pnotp_predict(dep, "xray.jpg")
det <- pnotp_deploy_open_source(p, "yolox_s", confidence_threshold = 0.4)
pnotp_predict(det, "street.jpg", confidence_threshold = 0.6) # per-call override
key <- pnotp_deployment_key_create(dep)$api_key # for an external consumer
pnotp_predict(dep, "xray.jpg", api_key = key) # no account key needed
compliant = TRUE makes the endpoint data-compliant: inputs are never
persisted, errors are kept out of monitoring, and every response carries a
compliance receipt. It is a platform capability, so it fails with a 409 when
not enabled; check with pnotp_compliance_available().
a <- pnotp_agent_create("Advisor")
pnotp_agent_publish(a, model = "pnotp-compliant", tools = "programs_db")
run <- pnotp_agent_run(a, "Which programs fit someone who loves maths?", wait = TRUE)
pnotp_agent_answer(run)
pnotp_agent_steps(run)$steps # per-step cost and latency
pnotp_toby("How do I deploy a model and call it?") # P¬P's hosted assistant
pnotp_toby() never raises: without a key, credits or network it returns an
offline answer that says why. Errors are classed conditions (?pnotp_errors)
carrying the API's machine-readable error fields:
tryCatch(
pnotp_chat(long_prompt),
pnotp_insufficient_credits_error = function(e) message("Add credits in Studio"),
pnotp_api_error = function(e) {
if (identical(e$code, "context_length_exceeded")) message("Trim to ", e$info$max_context)
else if (isTRUE(e$retryable)) message("Retry later")
else stop(e)
}
)
Listings (pnotp_projects(), pnotp_deployments(), pnotp_jobs(), ...) come
back as data.frames, so View() works. Anything returned by the API composes
as an id: pnotp_models(pnotp_project_create("x")).
?pnotp_chat).pnotp_private_available() reports
whether the platform offers it, but pnotp_predict() has no private mode.receipt the endpoint returns; use pnotp_chat() when you must keep them.