Skip to main content

cmpnd

The cmpnd package traces DSPy programs and deploys and optimizes them on the cmpnd platform. One call turns on tracing, and one more deploys a program.

Installation

uv add cmpnd      # or: pip install cmpnd

Quick start

import cmpnd
import dspy

cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
cmpnd.auto_instrument()

dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))

cot = dspy.ChainOfThought("question -> answer")
result = cot(question="What is DSPy?")   # the trace goes to cmpnd

cmpnd.trace_id(result)  # "a1b2c3d4-...", or None outside instrumentation

cmpnd.trace_id() links a result to its platform trace, for example to attach feedback later.

Configuration

cmpnd.configure() reads CMPND_API_KEY and CMPND_ENDPOINT when you pass no arguments. The endpoint defaults to https://platform.cmpnd.ai.

export CMPND_API_KEY="ck_your_api_key"
export CMPND_ENDPOINT="https://platform.cmpnd.ai"  # optional
export CMPND_PROGRAM_ID="p_0123456789Ab"            # optional: the program these traces belong to
cmpnd.configure(
    api_key="ck_xxx",                      # required, or CMPND_API_KEY
    endpoint="https://platform.cmpnd.ai",  # optional, or CMPND_ENDPOINT
    project_tags="my-project",             # optional: one tag or a list of tags
    program_id="p_0123456789Ab",           # optional, or CMPND_PROGRAM_ID: the id `cmpnd programs create` prints
    batch_size=100,                        # optional: spans per export batch
    flush_interval_seconds=5.0,            # optional: seconds between flushes
    capture_inputs=True,                   # optional: record span inputs
    capture_outputs=True,                  # optional: record span outputs
    default_tags={"env": "prod"},          # optional: tags on every trace
)

project is a deprecated alias for project_tags. Passing both raises ValueError. CMPND_MAX_INPUT_SIZE and CMPND_MAX_OUTPUT_SIZE set the captured input and output size limits, 65,536 bytes each by default.

Deployment

Every deployment belongs to a program, and the platform issues each program an id. Create a program first. The command prints its id:

cmpnd programs create --name qa

Then deploy a DSPy module under that program for server-side execution:

import cmpnd
import dspy

cmpnd.configure(api_key="ck_xxx", program_id="p_0123456789Ab")
dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))

module = dspy.Predict("question -> answer")
deployed = cmpnd.deploy(module)

result = deployed(question="What is DSPy?")  # runs server-side
print(result.answer)

deploy() packages the module's source into a ZIP, uploads it and polls until the server-side parser finishes. It then reads the deployment's persisted interface and returns a DeployedProgram. Call the program to execute it server-side. str(deployed) and deployed.deployment_id return the deployment ID. deployed.artifact_metadata holds the typed artifact record when the platform has inspected it. An unknown interface leaves input_fields and output_fields as None. A known interface with no fields returns empty lists.

cmpnd.deploy(module, program_id="p_…") names the program for one call. Without a configured or passed id, deploy raises ValueError before it packages or sends anything.

To deploy a source file, run cmpnd deploy program.py --program-id p_…, or set CMPND_PROGRAM_ID. The CLI uploads the file's exact bytes and does not reconstruct the source from a live object. Use --source-root and repeated --include flags for sibling modules or data, and cmpnd inspect <id> --source to read the deployed entry file back. docs/cli.md covers the CLI.

Options

cmpnd.deploy(
    module,            # required: a DSPy module
    metric=None,       # optional: a metric callable, packaged for optimization
    timeout=300,       # optional: seconds to wait for the parser after the upload
    metadata=None,     # optional: a JSON-serializable dict, at most 64 KiB, stored with the deployment
    program_id="p_…",  # required unless configured: the program the deployment belongs to
)

How it works

  1. The SDK generates source from the live module, including its LM configuration, signature and types.
  2. POST /api/v1/deployments creates the deployment and returns an upload URL.
  3. The SDK uploads the source ZIP to that URL. The bytes go to the worker.
  4. The worker's parser extracts the program and its LM configuration.
  5. The SDK polls until the status is ready, reads the artifact metadata and returns the DeployedProgram.

The parser reads the model string, such as openai/gpt-6-luna, and its sampling settings from the source.

Optimization

cmpnd.optimize() optimizes a program through the platform. Like deploy(), it returns a DeployedProgram that you call directly, and the program's score is the best score found.

import cmpnd
import dspy

cmpnd.configure(api_key="ck_xxx", program_id="p_0123456789Ab")  # from `cmpnd programs create`
dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))

program = dspy.ChainOfThought("topic -> haiku")

# GEPA needs a five-argument feedback metric that returns dspy.Prediction(score=, feedback=)
def metric(example, pred, trace=None, pred_name=None, pred_trace=None):
    ok = example.season.lower() not in pred.haiku.lower()
    return dspy.Prediction(score=float(ok),
                           feedback=None if ok else "Don't name the season.")

best = cmpnd.optimize(
    program,
    optimizer="gepa",
    trainset=train,
    valset=val,
    metric=metric,
    reflection_lm=dspy.LM("openai/gpt-6-luna"),
    auto="light",
)

print(best.score)
print(best(topic="late autumn").haiku)

reflection_lm is the model that proposes each new instruction. The search is only as good as this model, so it is usually a strong model and not the one being optimized. A dspy.LM passes its sampling settings to the provider. A bare model name gives the same model with no sampling settings. The platform supplies the credential and the endpoint, so the SDK refuses an api_key, api_base or base_url on the LM before any network call. On a hosted org, both live on the provider credential (Account → Model Providers), keyed by the model's protocol. One openai credential serves every openai/* model.

The SDK checks the metric before any network call. GEPA writes each new instruction from the textual feedback the metric returns, so it needs a five-argument feedback metric. The SDK refuses a metric with fewer than five positional arguments, because such a run would report success and learn nothing. A *args metric cannot be checked, and the SDK accepts it with a warning.

gepa is the only optimizer the platform runs. optimize() raises ValueError for the retired bootstrap_fewshot and random_search before any network call, and serve answers them with a 404 that names gepa.

A metric with its own model

A metric reaches a model the same way the program's predictors do. A metric that builds a dspy.Predict runs on the program's own model. A metric that needs a different model, such as a stronger judge, declares it in the optimize call's lms argument and selects it by name through a sixth parameter, lms:

def faithfulness(gold, pred, trace, pred_name, pred_trace, lms=None):
    """Does the summary say only what the document says?"""
    verdict = dspy.Predict("document, summary -> score: float, feedback: str")(
        document=gold.document,
        summary=pred.summary,
        lm=lms["critic"],
    )
    return dspy.Prediction(score=verdict.score, feedback=verdict.feedback)


best = cmpnd.optimize(
    program, optimizer="gepa", trainset=train, valset=val, metric=faithfulness,
    reflection_lm=dspy.LM("openai/gpt-6-luna"),
    lms={"critic": dspy.LM("openai/gpt-6-luna")},
    max_metric_calls=300,
)

lms declares any model that is not one of the program's predictors. The rules:

  • A name the run did not declare raises at the metric and lists the declared names.
  • "default-lm" is reserved for the program's own model, which comes from the deployment. Declaring it raises an error that names it.
  • Serve refuses a model your org holds no credential for when it dispatches the run, before the run starts. These LMs, like reflection_lm, must not set api_key, api_base or base_url.

The lms parameter does not change the arity check. Five or more positional arguments make a feedback metric, and a metric without lms never receives it.

A run can also bring its own instruction proposer through proposer. docs/custom-proposer.md describes it.

Reusing a deployment

Pass the deployment_id of a ready deployment to skip the initial deploy:

best = cmpnd.optimize(program, optimizer="gepa",
                      trainset=train, valset=val, metric=metric,
                      reflection_lm="openai/gpt-6-luna", auto="light",
                      deployment_id=deployed.deployment_id)

Options

cmpnd.optimize(
    program,                 # required: the DSPy module to optimize
    optimizer="gepa",        # required: the only optimizer the platform runs
    trainset=train,          # required: training examples
    valset=val,              # required: validation examples
    metric=metric,           # required: the metric, arity-checked
    proposer=None,           # optional: the run's own instruction proposer
    config=None,             # optional: optimizer config, allow-listed by serve
    deployment_id=None,      # optional: a ready deployment to start from
    reflection_lm=None,      # required: a dspy.LM or a bare model name
    lms=None,                # optional: name -> dspy.LM, the run's named models
    max_metric_calls=None,   # budget: the metric-call ceiling for one step
    auto=None,               # budget: "light", "medium" or "heavy"
    may_author_code=None,    # optional: False keeps each dspy.Flex at its deployed source
    max_steps=10,            # optional: the most optimize steps
    patience=2,              # optional: stop after this many steps without improvement
    seed=0,                  # optional: the starting seed, advanced each step
    poll_interval=3.0,       # optional: seconds between status polls
    timeout=600.0,           # optional: seconds to wait for each step
)

Pass exactly one budget: max_metric_calls, auto or config["max_full_evals"]. The SDK raises before any network call when the budget is missing.

optimize() raises cmpnd.OptimizeError when serve answers a step's initiation with a 4xx, such as a 409 for missing data or a metric mismatch. It raises cmpnd.OptimizeFailed when a step reaches failed.

From the CLI

cmpnd optimize gepa task.py --program-id p_0123456789Ab --reflection-model openai/gpt-6-luna --auto light

The file, or - for stdin, binds program, trainset, valset and metric. The CLI prints the best deployment ID. The CLI deploys the file's program first, so it needs --program-id or CMPND_PROGRAM_ID. With --deployment-id, the program id is optional.

Custom spans

Trace non-DSPy code with the decorator:

from cmpnd import trace, SpanType

@trace(name="fetch_documents", span_type=SpanType.RETRIEVE)
def fetch_documents(query: str) -> list[str]:
    return documents

Or with the context manager:

from cmpnd import start_span, SpanType

def run_pipeline(query: str):
    with start_span("vector_search", span_type=SpanType.RETRIEVE) as span:
        span.set_attribute("index", "my-faiss-index")
        docs = search(query)
        span.set_outputs({"doc_count": len(docs)})

    return generate(query, docs)

What gets traced

  • Modules: the module type, signature name and instructions, input and output field names, and demo count.
  • LM calls: the model name and provider, token usage and the request and response content.
  • Adapters: format and parse operations.
  • Tools: the tool name and description and each invocation's inputs and outputs.
  • Evaluations: scores and the program under evaluation.

Span types

SpanType has these members: UNKNOWN, MODULE, PREDICT, CHAIN_OF_THOUGHT, REACT, RLM, RETRIEVE, LM_CALL, ADAPTER_FORMAT, ADAPTER_PARSE, TOOL, INTERPRETER_EXECUTE, SIGNATURE_VALIDATE, DEMO_SELECT, OPTIMIZATION and EVALUATION.

API reference

cmpnd.configure()

Sets the API key, endpoint and options.

cmpnd.auto_instrument()

Registers the callback with DSPy.

cmpnd.CmpndCallback

The callback class, for manual registration:

import dspy
import cmpnd

cmpnd.configure(api_key="ck_xxx")
dspy.configure(callbacks=[cmpnd.CmpndCallback()])

cmpnd.deploy()

Deploys a DSPy module under a program id and returns a callable DeployedProgram. It uses program_id=, else the configured id.

cmpnd.optimize()

Optimizes a program through the platform. It returns the best DeployedProgram, whose .score is the best score found. It raises cmpnd.OptimizeError or cmpnd.OptimizeFailed on failure.

cmpnd.execute()

Submits one durable run by deployment_id, waits for the result within the local timeout and returns a dspy.Prediction. A DeployedProgram call goes through this function. The SDK sends Prefer: wait=5, shortened to fit the remaining budget, so a fast result arrives without polling. It never repeats the submission POST.

timeout is the whole submit-and-wait budget in seconds, 30 by default, or None for no local limit. program_name and lm select a module and model and travel beside the inputs. metadata and tags take dict[str, str] values that serve applies to the run's root trace. Serve-owned names such as deployment_run_id raise TypeError before anything is sent. The errors:

  • A refused request raises httpx.HTTPStatusError.
  • A lost or failed submission reply raises RunSubmissionUncertain.
  • A spent budget raises RunWaitTimeout, which holds the run ID.
  • An answer that expired before it was read raises RunResultExpired.

cmpnd.deployment_run_id(result) returns the run ID of a result, or of an exception when serve had assigned a run.

cmpnd.submit_deployment_run(id, inputs) returns the accepted handle without waiting. cmpnd.list_deployment_runs(), get_deployment_run(id), get_deployment_run_result(id) and wait_deployment_run(id) find, inspect and resume runs. list_deployment_runs(deployment_id=...) limits the list to one deployment. RunSubmissionUncertain names the list command to check before you send new work.

cmpnd.DeployedProgram

The callable program that deploy() and optimize() return. Call it with the deployed artifact's input fields. str() and .deployment_id return its ID. .score is its score when it came from optimize(), and None otherwise. .deployment_signature holds the typed deployment response, and .artifact_metadata holds its metadata when available.

cmpnd.trace()

A decorator that traces a function.

cmpnd.start_span()

A context manager that opens a span.

cmpnd.get_current_trace()

Returns the current trace, or None.

cmpnd.get_current_span()

Returns the current span, or None.

cmpnd.trace_id()

Returns the platform trace ID stamped on a prediction from an instrumented program, or None when the prediction came from outside instrumentation. Use it to attach feedback through POST /api/v1/traces/{cmpnd.trace_id(pred)}/metadata.

cmpnd.flush_exporter()

Publishes everything the background exporter has queued and leaves the exporter running. Call it before a reader fetches a trace the program just produced. It returns True when the drain finished within the timeout, 5 seconds by default, and False when it timed out or no exporter is running.

cmpnd.shutdown_exporter()

Flushes and stops the background exporter.

Metadata

Release files for cmpnd 0.39.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cmpnd 0.39.1
File Size Uploaded
cmpnd-0.39.1.tar.gz 1.7 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for cmpnd 0.39.1
File Interpreter ABI Platform
cmpnd-0.39.1-py3-none-any.whl Python 3 none any Details

Total release size: 2.1 MB

Release files / cmpnd-0.39.1.tar.gz

Download URL cmpnd-0.39.1.tar.gz
Size 1.7 MB
Tags Source
SHA-256 checksum
How to use checksums
b5792fae2fef52431248f0b61ef4dfec2cffdafecc158df0a5f5286d24aea59a
BLAKE2b-256 checksum
How to use checksums
374430c8fbbd4141519d4ca7471355e97c9a3b6f4bc9ce30d2f0d90b078360ba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release files / cmpnd-0.39.1-py3-none-any.whl

Download URL cmpnd-0.39.1-py3-none-any.whl
Size 382.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f54f474ad43d3bd1b207e83bc217f14ba171e43428edb852d03b056c1112b022
BLAKE2b-256 checksum
How to use checksums
4ce5a471cb1c83534d4b2bb058079f0062c8c6631bb87d710e5a86481e33cc31
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.39.1 This release

2 release files

0.39.0

2 release files

0.33.9

2 release files

0.33.8

2 release files

0.32.0

2 release files

0.31.1

2 release files

0.31.0

2 release files

0.30.1

2 release files

0.30.0

2 release files

0.29.0

2 release files

0.28.0

2 release files

0.27.9

2 release files

0.27.8

2 release files

0.27.7

2 release files

0.27.6

2 release files

0.27.5

2 release files

0.27.4

2 release files

0.27.3

2 release files

0.27.2

2 release files

0.27.1

2 release files

0.27.0

2 release files

0.26.1

2 release files

0.26.0

2 release files

0.25.1

2 release files

0.25.0

2 release files

0.24.1

2 release files

0.24.0

2 release files

0.17.1

2 release files

0.17.0

2 release files

0.16.0

2 release files

0.15.0

2 release files

0.14.0

2 release files

0.13.1

2 release files

0.13.0

2 release files

0.12.0

2 release files

0.11.0

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.10

2 release files

0.8.9

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page