Skip to main content
Package: runapprentice. Import name: runapprentice. This page covers the current surface; pip install -U runapprentice for it. Two rules hold across the SDK:
  • Control-plane calls (create, upload, optimize, report) raise ApprenticeError on failure. The caller is a developer who needs to know.
  • Capture calls never raise into your app. They run in your request path and fail open.

Client

client.close() flushes buffered traces and closes the HTTP client.

Tasks

A task is one repeatable LLM job. Create a separate task per job you want to improve.
See Metrics for the accepted metric values.

Datasets

datasets.upload() replaces prior rows for this task where the source is an upload and the tier is silver or raw. Gold rows and SDK-captured traces stay intact. Omitting prompt or passing None clears the task’s stored baseline prompt. Read status.replaced_rows to see how many rows were removed. Upload each complete replacement set in one call; this is not an append operation. Provide exactly one of path (a CSV) or rows (a list of dicts). Rows use either of these shapes:
For RAG, context must be exactly what the model saw. The optimizer cannot improve a prompt against context the model never received. Row tiers. Uploaded rows are silver (you curated them). Captured traces are raw (live, unverified). Human-verified rows are gold. Optimization uses verified rows, which is gold plus silver. Eval gates and model promotion use gold only. Silver can help you optimize, but it cannot certify quality. DatasetStatus fields: task, gold, silver, raw, ready_for_optimization, replaced_rows. Status reads and older servers default replaced_rows to 0.

Prompts

Use messages(), not string formatting. best.text is instruction text, not a template. It holds no input placeholder and often contains literal JSON braces, so best.text.format(input=...) raises KeyError and best.text.replace(...) drops the input with no error at all. messages() renders the artifact the backend recorded when it scored this version, so the messages you send are the messages that produced score. It raises ValueError rather than dropping an input the prompt does not declare. messages() covers the messages, not the whole request. The scored run also set response_format={"type": "json_object"} for JSON metrics and used the student model named in report.detail["student_model"]. Match both if you want the score to describe your call. On the Responses API that setting is text={"format": {"type": "json_object"}}; there is no response_format field. The keyword names come from the artifact’s input_variables: input for a plain extraction task, question and context for RAG, and your own variable names when you registered a template. The artifact is frozen per version, so editing the task’s baseline prompt never changes what an already-stored version executes. Versions optimized before artifacts were recorded have artifact = None; those fall back to the task’s current baseline, which does move, so re-run optimize if you need one of them to be reproducible. For the Responses API, pass that role/content list as input=version.messages(**inputs); Responses has no messages request field. The raw template artifact is a dict with format, messages, and input_variables. Only f-string templates are supported. PromptVersion fields: task, version, text, score, artifact (the recorded message shape), baseline_prompt (the task’s current baseline, a fallback for versions stored before artifacts were recorded).

Optimize jobs

Job properties: job_id, status (queued, running, succeeded, failed). OptimizationReport fields: task, baseline_score, optimized_score, examples_used, optimized_prompt, optimized_template, detail. Scores can be None when the run did not produce a comparable number, so check before formatting.

Capture

The capture path logs one completed call. It never raises into your application.
For RAG, pass inputs={"question": ..., "context": exact_context}, not a single rendered prompt string. Do not wrap capture in a try/except to protect your app, it is already fail-open.

Feedback

good=True and good=False are stored as a score of 1.0 and 0.0, so both forms end up on the same scale. Send it whenever your app learns whether an answer worked: a ticket resolved, an extraction that passed your validator, a user who clicked the retry button. Feedback is what the task’s Drift tab charts. Without it the console still shows how much traffic you captured, but it cannot show quality slipping in production, and it is the earliest warning you get. Send only a signal your app already has. Do not add a second model to grade the first one and send its verdict: that is an opinion, not a signal, and this score is what decides when a retrain is worth doing. If you have no real signal, send nothing. Captured rows still become gold when a human verifies them. An unrated row is honest; a guessed one is not.

LangChain callback

Metrics

Judge-scored metrics are advisory estimates, not deterministic truth. RAG rows auto-route to semantic_f1; pass metric="rag_composite" when you want grounding and refusal optimized together.

Errors and debugging

ApprenticeError is raised for control-plane failures, with a message that tells you what to do. Turn on request logging with: