LangSmith — Prompt Management¶
Prompts live in the Prompt & Context Hub (the Prompts section of a workspace): versioned templates you edit in the Playground and pull into code with Client.pull_prompt(). The Hub also stores contexts (skills and agent instructions) with the same commit / environment model; this page covers prompts.
Concepts¶
| Concept | What it is |
|---|---|
| Prompt | A named template: chat (list of role messages, recommended) or completion (one string), optionally with tools, an output schema and a model configuration |
| Commit | Every save creates an immutable commit with a hash; history, diff and revert are available |
| Commit tag | A movable label pointing at exactly one commit (v1, ready-for-review) |
| Environment | staging and production — reserved tags managed through Promote (with a per-environment history and rollback) |
| Prompt owners | Optional "owners only" mode: only owners can tag, promote or delete; default is any user with the prompts:tag permission |
| Webhook | One per workspace; fires on every prompt commit with the manifest |
| Template format | f-string {variable} (default) or mustache {{variable}} (loops, conditionals, nested keys) |
Prompt identifiers used by the SDK:
| Identifier | Resolves to |
|---|---|
ticket-triage |
Latest commit of your prompt |
ticket-triage:production |
Commit the tag or environment points at |
ticket-triage:12344e88 |
A fixed commit hash |
owner/name |
Public Hub prompt — requires dangerously_pull_public_prompt=True |
Push¶
from langchain_core.prompts import ChatPromptTemplate
from langsmith import Client
client = Client()
v1 = ChatPromptTemplate.from_messages([
("system", "You triage support tickets for {product}."),
("human", "{ticket}"),
])
url = client.push_prompt("ticket-triage", object=v1, commit_tags=["v1"], description="Ticket triage")
v2 = ChatPromptTemplate.from_messages([
("system", "You triage support tickets for {product}. Answer with JSON: priority and team."),
("human", "{ticket}"),
])
client.push_prompt("ticket-triage", object=v2, commit_tags=["v2"], commit_description="Force JSON output")
push_promptcreates the prompt if it does not exist, then adds a commit on top ofparent_commit_hash="latest".is_public,description,readmeandtags(prompt-level tags) update metadata.- Push
prompt | model(aRunnableSequence) to store the model configuration with the prompt; the provider must be supported by the Playground. StructuredPrompt.from_messages_and_schema(messages, schema=Model.model_json_schema())stores a template plus output schema.- Reserved
staging/productionare assigned with Promote in the UI; keep immutable release tags (v1,v2) for the SDK. - Other calls:
list_prompts(query="triage", is_public=False),list_prompt_commits(name),get_prompt,update_prompt,delete_prompt,like_prompt.
Pull¶
prompt = client.pull_prompt("ticket-triage:production")
messages = prompt.invoke({"product": "WebShop", "ticket": "Checkout returns 500"}).to_messages()
chain = client.pull_prompt("ticket-triage-with-model:production", include_model=True) # prompt | model
chain.invoke({"product": "WebShop", "ticket": "Checkout returns 500"})
With LangChain the result is a runnable: prompt | ChatOpenAI(model="gpt-4o-mini"). Without LangChain, convert the rendered prompt into a provider payload:
from openai import OpenAI
from langsmith.client import convert_prompt_to_openai_format # needs langchain-openai installed
payload = convert_prompt_to_openai_format(prompt.invoke({"product": "WebShop", "ticket": "500 on checkout"}))
resp = OpenAI().chat.completions.create(**{**payload, "model": "gpt-4o-mini"}) # payload carries a placeholder model
convert_prompt_to_anthropic_format is the Anthropic counterpart. Prompts pulled with include_model=True or from public owners can instantiate models and carry configuration: review them before use.
Versions vs Environments¶
| Mechanism | Moves? | Use for |
|---|---|---|
| Commit hash | Never | Reproducing an exact experiment or incident |
Release tag (v1, v2) |
Manually, via SDK or UI | Stable named versions referenced from tests |
staging / production |
Promote / Rollback in the UI | What each deployment pulls without a code change |
Promoting a commit to Production does not remove it from Staging. Typical flow:
- Edit in the Playground (or push from code), commit.
- Run a dataset experiment against the commit (04); compare with the current production commit.
- Promote to Staging; CI and pre-production pull
ticket-triage:staging. - Promote to Production after the gate passes; the app pulls
ticket-triage:production. - Roll back from the environment's rollback history if online evaluators or alerts regress.
Code should reference the environment, never a hash, in production paths.
Caching¶
pull_prompt uses a process-wide in-memory cache with stale-while-revalidate: callers always get a fast answer and the entry refreshes in the background.
| Setting | Default | Meaning |
|---|---|---|
max_size |
100 | Prompts kept in the cache |
ttl_seconds |
300 | Age after which an entry is stale and refreshed in the background |
refresh_interval_seconds |
60 | How often stale entries are checked |
from langsmith import Client
from langsmith.prompt_cache import configure_global_prompt_cache, prompt_cache_singleton
configure_global_prompt_cache(max_size=200, ttl_seconds=7200, refresh_interval_seconds=600) # before creating clients
client = Client()
client.pull_prompt("ticket-triage:production") # network
client.pull_prompt("ticket-triage:production") # cache hit
client.pull_prompt("ticket-triage:production", skip_cache=True) # force a fresh fetch
print(prompt_cache_singleton.metrics.hits, prompt_cache_singleton.metrics.misses)
Client(disable_prompt_cache=True) # no caching for this client
- Tests that edit a prompt and expect the change immediately must use
skip_cache=Trueorprompt_cache_singleton.invalidate("ticket-triage:staging"). - Offline / air-gapped runs:
prompt_cache_singleton.dump("prompts_cache.json")while online; laterconfigure_global_prompt_cache(ttl_seconds=None)+prompt_cache_singleton.load("prompts_cache.json")serves pulls without network access. prompt_cache_singleton.clear()and.reset_metrics()reset state between tests.
Playground and Prompt Canvas¶
| Feature | Notes |
|---|---|
| Playground | Change model, template, output schema and tools; enter variables; run and compare several prompts side by side |
| Test over dataset | Select a dataset, press Start: runs the prompt over every example and records an experiment; dataset input keys must match the prompt variables (max 15 variables) |
| + Evaluator | Attach LLM-as-judge or code evaluators to the Playground experiment |
| Prompt canvas | Click the wand on any message: chat-driven rewrite, quick actions (reading level, length), custom quick actions, diff, Use this Version |
| Chat | AI assistance in the Playground for prompts, tools and output schemas |
Commit the prompt before starting an experiment so the run references a stable version.
Testing Prompts in Code¶
# tests/test_prompt_contract.py
import pytest
from langsmith import Client
@pytest.fixture(scope="module")
def prompt():
return Client().pull_prompt("ticket-triage:staging", skip_cache=True)
def test_prompt_variables(prompt):
assert set(prompt.input_variables) == {"product", "ticket"} # callers pass exactly these
def test_prompt_renders(prompt):
messages = prompt.invoke({"product": "WebShop", "ticket": "Checkout returns 500"}).to_messages()
assert messages[-1].content == "Checkout returns 500"
assert "JSON" in messages[0].content # output-format instruction survives edits
Behavior testing runs the pulled prompt through the model over a dataset and records which prompt version produced the experiment:
from langsmith import Client, wrappers
from openai import OpenAI
client = Client()
oai = wrappers.wrap_openai(OpenAI())
PROMPT_ID = "ticket-triage:staging"
def target(inputs: dict) -> dict:
prompt = client.pull_prompt(PROMPT_ID, skip_cache=True)
messages = prompt.invoke({"product": "WebShop", **inputs}).to_messages()
resp = oai.chat.completions.create(
model="gpt-4o-mini", temperature=0, response_format={"type": "json_object"},
messages=[{"role": "system" if m.type == "system" else "user", "content": m.content} for m in messages],
)
return {"answer": resp.choices[0].message.content}
def valid_json(outputs: dict) -> bool:
import json
try:
json.loads(outputs["answer"])
return True
except ValueError:
return False
client.evaluate(target, data="triage-regression", evaluators=[valid_json],
experiment_prefix="prompt-staging", metadata={"prompts": [PROMPT_ID]})
The metadata["prompts"] list fills the prompt column of the experiment table. Datasets and evaluators are covered in 04.
Automation Around Commits¶
- Webhook (Prompts page → + Webhook, one per workspace): POSTs
prompt_id,prompt_name,commit_hash,created_at,created_byandmanifeston every commit. Use it to trigger a CI evaluation job or to sync manifests into Git; the GitHub sync tutorial uses a small intermediary server that holds a GitHub token. - API commits can pass
skip_webhooks(true, or a list of webhook IDs) to avoid triggering them; the Playground asks which webhooks to skip. - Per-prompt Access and Permissions → Owners only restricts who can promote to Production.
- Resource tags (key-value labels for workspace resources) are unrelated to commit tags.
Pitfalls¶
| Pitfall | Fix |
|---|---|
| Edited prompt not visible in code for minutes | Cache TTL (300 s default): skip_cache=True in tests, short TTL or invalidation elsewhere |
ValueError pulling someone/prompt |
Public prompts need dangerously_pull_public_prompt=True; read the manifest first |
| Dataset run fails on missing variable | Dataset input keys must equal the prompt's input_variables |
| Hash pinned in production code | Pull name:production and promote instead of redeploying |
Old langchainhub package |
Deprecated; use langsmith |
See also¶
- LangSmith — LLM Tracing, Prompts & Evals
- LangSmith — Datasets & Evaluations
- LangSmith — Testing, CI & Production
- LangChain — LLM Application Framework
- Langfuse — Prompt Management
- MLflow — Evaluation & Prompts