Prompt Management
Fetch versioned prompts from Waxell, render them with variables, and use them in your agents. Auto-instrumentation captures the LLM calls automatically -- no manual recording required.
Environment variables
This example requires OPENAI_API_KEY, WAXELL_API_KEY, and WAXELL_API_URL.
Pattern 1: @observe decorator (recommended)
The decorator path is the simplest way to combine managed prompts with auto-instrumented LLM calls.
import asyncio
import waxell_observe as waxell
from waxell_observe import WaxellObserveClient
waxell.init()
from openai import OpenAI
client = WaxellObserveClient()
openai_client = OpenAI()
@waxell.observe(agent_name="prompt-demo")
async def summarize(topic: str, length: str = "brief") -> str:
# Fetch a text prompt by label
prompt = await client.get_prompt("summarizer", label="production")
# Compile with variables (replaces {{variable}} placeholders)
rendered = prompt.compile(topic=topic, length=length)
# LLM call -- auto-captured by waxell.init()
response = openai_client.chat.completions.create(
model=prompt.config.get("model", "gpt-4o"),
messages=[{"role": "user", "content": rendered}],
temperature=prompt.config.get("temperature", 0.7),
)
waxell.metadata("prompt_version", prompt.version)
waxell.score("quality", 0.9)
return response.choices[0].message.content
@waxell.observe(agent_name="support-bot")
async def answer_support_query(user_query: str) -> str:
# Chat prompts compile to a list of {role, content} dicts
chat_prompt = await client.get_prompt("support-assistant", label="production")
messages = chat_prompt.compile(user_query=user_query)
response = openai_client.chat.completions.create(
model=chat_prompt.config.get("model", "gpt-4o"),
messages=messages,
)
waxell.metadata("prompt_version", chat_prompt.version)
return response.choices[0].message.content
asyncio.run(summarize("AI safety"))
Pattern 2: WaxellContext (advanced)
Use WaxellContext directly when you need explicit control over the async trace lifecycle -- for example, when wrapping a block of code that isn't naturally a single function.
import asyncio
import waxell_observe as waxell
from waxell_observe import WaxellObserveClient, WaxellContext
waxell.init()
from openai import OpenAI
client = WaxellObserveClient()
openai_client = OpenAI()
async def use_managed_prompts():
async with WaxellContext(agent_name="prompt-demo") as ctx:
# Fetch a text prompt by label
prompt = await client.get_prompt("summarizer", label="production")
# Compile with variables
rendered = prompt.compile(topic="AI safety", length="brief")
# LLM call -- auto-captured
response = openai_client.chat.completions.create(
model=prompt.config.get("model", "gpt-4o"),
messages=[{"role": "user", "content": rendered}],
temperature=prompt.config.get("temperature", 0.7),
)
ctx.record_step("generate", output={"prompt_version": prompt.version})
# Fetch a chat prompt
chat_prompt = await client.get_prompt("support-assistant", label="production")
messages = chat_prompt.compile(user_query="How do I reset my password?")
chat_response = openai_client.chat.completions.create(
model=chat_prompt.config.get("model", "gpt-4o"),
messages=messages,
)
ctx.record_step("chat", output={"prompt_version": chat_prompt.version})
ctx.set_result({"summary": response.choices[0].message.content})
asyncio.run(use_managed_prompts())
Accessing context with get_context()
Use get_context() to access the current WaxellContext from anywhere in the call stack -- no need to pass it as a parameter.
import asyncio
import waxell_observe as waxell
@waxell.observe(agent_name="context-demo")
async def my_agent(query: str):
# Get the current context from anywhere in the call stack
ctx = waxell.get_context()
if ctx:
print(f"Run ID: {ctx.run_id}")
ctx.set_tag("source", "api")
# Works from nested functions too
await nested_function()
async def nested_function():
ctx = waxell.get_context()
if ctx:
ctx.record_step("nested_work")
asyncio.run(my_agent("What is the meaning of life?"))
What this demonstrates
get_prompt()-- fetch versioned prompts by name and label from Waxell.PromptInfo.compile()-- render{{variable}}placeholders into final prompt text or chat messages.- Text vs chat prompts -- text prompts compile to a string, chat prompts compile to a list of
{role, content}dicts. @waxell.observe-- declarative trace lifecycle (Pattern 1). Auto-instrumentation captures every LLM call inside the function.WaxellContext-- explicit async trace lifecycle (Pattern 2). Use when you need fine-grained control.get_context()-- access the activeWaxellContextfrom nested functions without threading it through parameters.- Prompt config -- use
prompt.configto drive model selection, temperature, and other API parameters.
Run it
export OPENAI_API_KEY="sk-..."
export WAXELL_API_KEY="your-waxell-api-key"
export WAXELL_API_URL="https://api.waxell.ai"
python prompt_management.py