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
- The SDK generates source from the live module, including its LM configuration, signature and types.
POST /api/v1/deploymentscreates the deployment and returns an upload URL.- The SDK uploads the source ZIP to that URL. The bytes go to the worker.
- The worker's parser extracts the program and its LM configuration.
- The SDK polls until the status is
ready, reads the artifact metadata and returns theDeployedProgram.
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 setapi_key,api_baseorbase_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)
| File | Size | Uploaded | |
|---|---|---|---|
| cmpnd-0.39.1.tar.gz | 1.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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