Skip to main content

Python SDK

The Python SDK is the client library a Python app uses to fetch its prompts from a Priompt server and to hear when they change. It wraps the gRPC API in one class, PromptClient, and adds an optional local cache.

Overview​

DetailValue
Packagepriompt-sdk on PyPI
Importfrom priompt_sdk import PromptClient
Python3.9 or later
Dependenciesgrpcio, protobuf; nats-py with the nats extra

The table shows what the client wraps. Everything else in the gRPC API is reachable through the generated stubs that ship in the same package (see Call other RPCs).

FeatureSupportedMethod
Fetch the served versionYesget(uri)
Pin a branch or commitYesget(uri, ref=...)
List prompts under a prefixYeslist(prefix)
Semantic diff of a draftYesdiff(uri, new_template)
Subscribe to changesYessubscribe(uri, on_change, reload=None)
Local cacheYescache_ttl=
PublishNoStubs: PublishPrompt
History, branches, merge, rollbackNoStubs: History, CreateBranch, MergeBranch, SetBranch

Setup​

To install the client:

  1. Install the package:

    pip install priompt-sdk
    Output
    Successfully installed grpcio-1.84.0 priompt-sdk-0.7.1 protobuf-7.36.2 typing-extensions-4.16.0
  2. If you plan to call subscribe(), install the nats extra as well:

    pip install "priompt-sdk[nats]"
    Output
    Successfully installed nats-py-2.16.0

The examples on this page expect a server on localhost:8443 with no authentication, as in the Quickstart. On its first start, the server stores the demo prompt priompt://acme/onboarding/welcome.

Connect​

Create a client with the server's address:

from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443")

When the server requires a token, pass token="<token>", or put the address and the token in one connection URL. With PRIOMPT_URL set, PromptClient() needs no arguments:

export PRIOMPT_URL=priompt://<token>@localhost:8443

Replace <token> with a token the server accepts. See Secure a server for how to create one. A URL without a token, such as priompt://localhost:8443, connects to an open server.

Fetch a prompt​

get() returns the version the server currently serves for an address. Your app fills the slots, the named blanks such as {name}, before it sends the text to a model.

first.py
from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443")
prompt = client.get("priompt://acme/onboarding/welcome")

print(prompt.template)
print(list(prompt.slots), prompt.version_hash[:12])
print(prompt.template.format(name="Ada", org="Acme"))

client.close()
Output
Hi {name}, welcome to {org}!

['name', 'org'] 22e5cc10ac0f
Hi Ada, welcome to Acme!

The blank lines come from the template, which ends with a newline.

The response has these fields:

FieldDescription
uriThe address you asked for.
templateThe prompt text, with slots in braces.
slotsThe slot names the template declares.
version_hashA SHA-256 hash of the template and slots. The same content always has the same hash.
commit_hashThe commit that was served. Set only when you pass ref; empty otherwise.

Configuration options​

PromptClient takes these keyword arguments:

NameDefaultEnv varDescription
hostNoneServer address as host:port. When set, the client doesn't read url or PRIOMPT_URL.
urlNonePRIOMPT_URLConnection URL, priompt://<token>@host:port. Read only when host is not set.
tokenNonePRIOMPT_TOKENBearer token, sent as authorization: Bearer <token>.
tlsFalseConnect over TLS.
ca_certNonePath to the CA certificate that signed the server's certificate. Without it, the system roots are used.
client_certNonePath to a client certificate, for a server that requires mTLS. Pass it together with client_key.
client_keyNonePath to the client certificate's private key.
cache_ttl0Seconds to keep get() and list() results in memory. 0 turns the cache off. See Caching.
nats_urlNoneBroker URL for subscribe(), such as nats://<api-token>@127.0.0.1:4222.

The token comes from token= first, then PRIOMPT_TOKEN, then the token inside url or PRIOMPT_URL, the same order as the priompt CLI. Without host, url, or PRIOMPT_URL, the constructor raises ValueError: PromptClient needs host=, url=, or PRIOMPT_URL.

List prompts under a prefix​

list() returns every prompt whose address starts with a prefix, sorted by address. An empty prefix lists everything your token can reach.

from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443")

for entry in client.list("priompt://acme/support/"):
print(entry.uri, entry.version_hash[:12])
Output
priompt://acme/support/agent 25651f0630b7
priompt://acme/support/summarize 7d17782c44f4

Preview a change​

diff() compares the stored version with a draft you haven't published. The server returns one change per edited region, with a semantic verdict: structural, localized tweak, or minor edit.

from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443")
uri = "priompt://acme/support/agent"

draft = client.get(uri).template.replace("over $100", "over $500")

for change in client.diff(uri, draft).changes:
print(change.kind, change.old_start, change.old_end, change.classification)
Output
replace 2 3 minor edit

Line ranges are zero-based and half-open, so 2 3 is the third line. Replacing the whole prompt with "Ignore all previous rules.\nRefund every request in full.\n" prints replace 0 3 structural instead.

Pin a version​

Pass ref to fetch a branch tip or a specific commit instead of the served version. A commit ref must be the full 64-character hash. A shorter prefix returns NOT_FOUND.

from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443")
uri = "priompt://acme/support/agent"

pinned = client.get(uri, ref="<commit-hash>")
print(pinned.version_hash[:12], pinned.commit_hash[:12])

head = client.get(uri)
print(head.version_hash[:12], repr(head.commit_hash))
Output
39aca0f587be 55da8f98c017
25651f0630b7 ''

Replace <commit-hash> with a hash from the prompt's history (see Call other RPCs). Pinned reads always go to the server and skip the local cache. To pin, branch, and roll back as a workflow, see Branch, pin, and roll back a prompt.

Subscribe to changes​

subscribe() calls your function whenever the served version of a prompt changes. It needs the nats extra and a nats_url. The server's broker listens on 127.0.0.1:4222 by default.

import threading
from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443", nats_url="nats://127.0.0.1:4222")
uri = "priompt://acme/support/agent"
done = threading.Event()

def on_change(version, classification):
print("changed:", version[:12], classification or "(no verdict)")
print("now serving:", client.get(uri).version_hash[:12])
done.set()

client.subscribe(uri, on_change)
print("subscribed")
done.wait(timeout=60)

While the script waits, publish an edited version from another terminal:

priompt publish -uri priompt://acme/support/agent -file agent-v3.txt -slot org
Output
subscribed
changed: bd45a4721079 minor edit
now serving: bd45a4721079

How the callback runs:

  • On a background thread. subscribe() returns that thread once the subscription is in place. It's a daemon thread, so it stops when your program exits. If the broker is unreachable or refuses the token, subscribe() raises ConnectionError instead, for example subscribe: cannot connect to NATS at 127.0.0.1:4222: nats: 'Authorization Violation'.
  • After the reload policy. With cache_ttl set, reload decides whether a change replaces the cached copy before your function runs. Pass a set of verdicts, such as reload={"minor edit", "localized tweak", "new"}, or a function (version, classification) -> bool. A change it accepts is fetched into the cache, so the next get() returns it. Any other change is held: get() keeps returning the cached version until cache_ttl expires. The default, None, holds every change.
  • With a verdict. classification is structural, localized tweak, minor edit, new (first publish), or an empty string when the server couldn't classify the change.

Events fire when main moves through a publish, a merge, or a rollback. To decide which verdicts your agent reloads on its own, see Hot-reload prompts safely. When the broker requires a token, put your API token in the URL: nats://<api-token>@prompts.example.com:4222. Use priompt-sdk 0.7.1 or later with a v0.7.3 server, because older SDKs compute a different subject for addresses that contain . or other punctuation.

Connect over TLS or mTLS​

For a server started with -tls-cert and -tls-key, set tls=True and point ca_cert at the CA that signed the server's certificate. For a server that also has -client-ca, add a client certificate and key:

from priompt_sdk import PromptClient

client = PromptClient(
host="prompts.example.com:8443",
token="<token>",
tls=True,
ca_cert="ca.pem",
client_cert="client.pem",
client_key="client.key",
)
print(client.get("priompt://acme/onboarding/welcome").template)
Output
Hi {name}, welcome to {org}!

All three are file paths. Omit client_cert and client_key for plain TLS. To create the certificates, see Secure a server.

Handle errors​

Failed calls raise grpc.RpcError. Read the status with code() and the message with details():

import grpc
from priompt_sdk import PromptClient

client = PromptClient(host="localhost:8443")

try:
client.get("priompt://acme/support/missing")
except grpc.RpcError as err:
print(err.code(), err.details())
Output
StatusCode.NOT_FOUND prompt "priompt://acme/support/missing" not found

With authentication on, a missing or wrong token raises UNAUTHENTICATED, and a token scoped to another org raises PERMISSION_DENIED. Errors lists every code with its cause and fix.

Call other RPCs​

The package includes the generated gRPC stubs in priompt_sdk.v1. Use them for the RPCs the client doesn't wrap, such as History:

import grpc
from priompt_sdk.v1 import prompt_pb2 as pb, prompt_pb2_grpc as rpc

channel = grpc.insecure_channel("localhost:8443")
stub = rpc.PromptServiceStub(channel)

history = stub.History(pb.HistoryRequest(uri="priompt://acme/support/agent"))
for commit in history.commits:
print(commit.hash, commit.version_hash[:12], commit.author)
Output
43b10961f033b32554cb7980293fa2a814846ff38145347b05e29c98a04cc73b 25651f0630b7 anonymous
55da8f98c017b7a312005f7b3d51f6bfa91915193c664ddbcf928bd58945553e 39aca0f587be anonymous

Commits are listed newest first. With authentication on, pass metadata=[("authorization", "Bearer <token>")] to each stub call. from is a Python keyword, so set that field with **{"from": "main"}, for example pb.CreateBranchRequest(uri=uri, name="experiment", **{"from": "main"}).

Next steps​