Release v0.5.0

Merged
LC
3mo ago
+1696-17

Release v0.5.0

Bumps version from 0.4.0 to 0.5.0.

Changelog

Added

  • Input preparation: upload_file and prepare_inputs (hosted upload capability). The Python counterpart of @pipelex/sdk's uploadFile / prepareInputs, in parity. client.upload_file(source, *, filename=None, content_type=None) uploads one local asset — a filesystem path (str/Path) or raw bytes — and returns an UploadRecord (uri, content_type, size, filename) assembled client-side. client.prepare_inputs(*, files, pipe_ref=None, inputs) resolves the target pipe's declared signature from the explicit inputs template, interprets the caller's compact inputs top-down against it (the file signal is the canonical Image/Document content shape — a {"url": …} dict — mirroring the runtime's input_normalizer), uploads the file-bearing values, and returns PreparedInputs: a copy-on-write rewrite of inputs with each asset reference replaced by canonical content carrying pipelex-storage:// in url, plus one UploadRecord per prepared asset. HTTP(S) URLs and existing pipelex-storage:// URIs pass through unchanged; data URLs and local/byte sources are uploaded; the same source referenced twice uploads once (dedup by source identity); all failures are raised before any run is created. Failures are typed per category: InvalidLocalSourceError, RejectedAssetError, UnsupportedUploadCapabilityError, UploadAuthenticationError, UploadTransportError (all extend InputPreparationError). prepare_inputs takes the method closure as inline files; catalog method_id resolution and opt-in http(s) ingest are deferred and additive. See docs/input-preparation.md.

  • build_inputs route (POST /v1/build/inputs). Closes the /v1/build/* parity gap the Python SDK had — client.build_inputs(BuildInputsRequest) projects a pipe's declared inputs as a fill-in template, returning a 200 verdict discriminated on is_valid (BuildInputsValidReport | CrateInvalidReport); a no-verdict condition throws ApiResponseError. It is the signature source prepare_inputs reads (with explicit=True). Models live in pipelex_sdk/build_models.py.

  • Typed run usage: RunResults.tokens_usages + RunResults.usage_assembly_error. The per-call usage records a run produces — token counts by category, the server-computed cost in USD, model name and id, the pipe that made the call, job-kind fields and timing, for LLM and img-gen/extract/search calls alike — are now first-class typed fields instead of riding model_extra. Records validate into a new TokensUsageRecord model (pipelex_sdk/runs.py) mirroring the wire contract specified in the MTHDS protocol spec. Both paths populate the pair: the hosted durable path reads it off GET /v1/runs/{id}/results (which unpacks the runner's tokens_usages.json artifact), and the blocking fallback lifts the same pair out of the execute response's extension-open pipe_output — so result.tokens_usages reads the same regardless of which path ran.

    Note that the rate table (unit_costs) no longer crosses the wire: a record now carries the computed cost for the call instead, which is None when the model has no rate table at all (own-GPU, mock, dry run) and 0 when a rate table priced it at zero. There is no run-level aggregate — sum the records.

    tokens_usages is None whenever usage assembly produced no list (it was off, it broke, or the run was delivered before the artifact existed) and [] when assembly ran and no inference happened; usage_assembly_error is the only field separating a broken assembly from an off one. TokensUsageRecord keeps every field optional and is extension-open, so durable artifacts written before the contract shipped — relayed verbatim, never migrated — still parse: cost and pipe_code come back None, and the legacy job_metadata / unit_costs survive in model_extra. Enum-ish fields (model_type, job_category, unit_job_id) are open sets typed as plain str, so runtime enum churn stays non-breaking.

Changed

  • RunResults.pipe_output is now DictPipeOutputAbstract | None, was dict[str, Any] | None (breaking). The blocking path already parsed the protocol model and then threw the types away with a .model_dump() round-trip; it now carries the parsed model straight through. Read the working memory as attributes — result.pipe_output.working_memory.root["out"].content — rather than nested dict keys. The durable path still leaves it None.

🤖 Generated with Claude Code


Summary by cubic

Release v0.5.0 adds hosted input preparation (upload_file, prepare_inputs), introduces build_inputs for signature templates, and exposes typed run-usage records on results. It also changes RunResults.pipe_output to a typed model. Adds a WIP doc with deferred review findings (no code changes).

  • New Features

    • Input preparation: client.upload_file(source, filename?, content_type?) and client.prepare_inputs(files, pipe_ref?, inputs) rewrite file inputs to pipelex-storage:// URLs, pass through http(s)/existing URIs, dedup repeated sources, and raise clear typed errors. Mirrors @pipelex/sdk.
    • build_inputs support: client.build_inputs(BuildInputsRequest) returns the explicit inputs template with an is_valid verdict; used by prepare_inputs.
    • Typed run usage: RunResults.tokens_usages and usage_assembly_error validate into TokensUsageRecord and are available on both hosted and blocking paths; each record includes per-call token counts and computed USD cost.
  • Migration

    • RunResults.pipe_output is now DictPipeOutputAbstract | None (was dict). Access working memory via attributes, e.g. result.pipe_output.working_memory.root["out"].content.