Prompt Versioning: Create, Label, and Serve Prompt Versions

Create prompt templates, commit numbered versions, assign labels like production, and serve the right version at runtime via SDK or dashboard.

📝
TL;DR

Create a prompt template, commit it as v1, serve it in your app, then commit a v2 with a different model configuration, evaluate it, promote it to production, and roll back, all through fi.prompt.

Open in ColabGitHub
TimeDifficultyPackage
15 minBeginnerfutureagi + ai-evaluation
Prerequisites
  • Future AGI account → app.futureagi.com
  • API keys: FI_API_KEY and FI_SECRET_KEY (see Get your API keys)
  • An OPENAI_API_KEY (litellm routes the gpt-4o-mini calls through it)
  • Python 3.11+

Install

pip install futureagi ai-evaluation litellm
export FI_API_KEY="your-api-key"
export FI_SECRET_KEY="your-secret-key"
export OPENAI_API_KEY="your-openai-key"

Tutorial

Create a prompt via SDK

import os
from fi.prompt import Prompt
from fi.prompt.types import PromptTemplate, SystemMessage, UserMessage, ModelConfig

prompt_client = Prompt(
    template=PromptTemplate(
        name="support-response",
        messages=[
            SystemMessage(
                content="You are a helpful customer support agent for TechStore. "
                        "Answer the customer's question clearly and professionally."
            ),
            UserMessage(
                content="Customer question: {{question}}"
            ),
        ],
        model_configuration=ModelConfig(
            model_name="gpt-4o-mini",
            temperature=0.7,
            max_tokens=1000,
        ),
    ),
    fi_api_key=os.environ["FI_API_KEY"],
    fi_secret_key=os.environ["FI_SECRET_KEY"],
)

# Create the prompt as a draft and commit it as v1
prompt_client.create()
prompt_client.commit_current_version(
    message="Initial support prompt",
    label="production",
)

print(f"Created: {prompt_client.template.name} ({prompt_client.template.version})")

You should see:

Created: support-response (v1)

Check the dashboard: Prompts (left sidebar) → open support-response → click History → the History drawer lists v1 with the production label.

Serve the prompt in your application

import os
import litellm
from fi.prompt import Prompt


def answer_question(question: str) -> str:
    prompt = Prompt.get_template_by_name(
        name="support-response",
        label="production",
        fi_api_key=os.environ["FI_API_KEY"],
        fi_secret_key=os.environ["FI_SECRET_KEY"],
    )

    # compile() returns a list of message dicts; pass to any LLM
    messages = prompt.compile(question=question)

    response = litellm.completion(
        model="gpt-4o-mini",  # swap for any litellm-supported model
        messages=messages,
    )
    return response.choices[0].message.content


print(answer_question("What is your return policy?"))

You should see a support-style answer printed to the terminal. compile() returns standard [{"role": "system", "content": "..."}, ...] message dicts, so it works with any litellm-supported model: swap in "groq/llama-3.3-70b-versatile" or "anthropic/claude-sonnet-4-20250514" and keep the rest of the function unchanged.

Create v2 with chain-of-thought reasoning

Each version can have its own model configuration. This v2 uses a lower temperature for more deterministic chain-of-thought responses.

from fi.prompt.types import PromptTemplate, SystemMessage, UserMessage, ModelConfig

# Create a new version with updated messages and model config
prompt_client.create_new_version(
    template=PromptTemplate(
        name="support-response",
        messages=[
            SystemMessage(
                content="You are a precise customer support agent for TechStore.\n\n"
                        "Think through the customer's question step by step before answering:\n"
                        "1. What is the customer asking?\n"
                        "2. What information do I have that directly addresses this?\n"
                        "3. What is the clearest, most helpful response?"
            ),
            UserMessage(
                content="Customer question: {{question}}\n\nAnswer:"
            ),
        ],
        model_configuration=ModelConfig(
            model_name="gpt-4o-mini",
            temperature=0.3,
            max_tokens=1000,
        ),
    ),
    commit_message="Add chain-of-thought reasoning",
)

# Save and commit v2
prompt_client.save_current_draft()
prompt_client.commit_current_version(message="v2: chain-of-thought prompt")

print(f"v2 created: {prompt_client.template.version}")

You should see:

v2 created: v2

v2 exists as a committed version but carries no label yet, so Step 2’s get_template_by_name(label="production") still serves v1.

Test v2 before promoting it

Use is_concise here: for a support agent, concise answers are a key quality signal. Swap in any of the 72+ built-in eval metrics like groundedness, tone, completeness, or instruction_adherence depending on what you want to measure. Score v1 alongside v2 so you have something to compare before promoting.

import litellm
from fi.evals import evaluate

test_cases = [
    "What is your return policy?",
    "How long does standard shipping take?",
    "Can I exchange a product instead of returning it?",
]

v1_prompt = Prompt.get_template_by_name(
    name="support-response",
    version="v1",
    fi_api_key=os.environ["FI_API_KEY"],
    fi_secret_key=os.environ["FI_SECRET_KEY"],
)
v2_prompt = Prompt.get_template_by_name(
    name="support-response",
    version="v2",
    fi_api_key=os.environ["FI_API_KEY"],
    fi_secret_key=os.environ["FI_SECRET_KEY"],
)

print(f"{'Question':<45} {'v1':>8} {'v2':>8}")
print("-" * 63)

v1_pass = v2_pass = 0
for question in test_cases:
    row = [question[:43]]
    for label, prompt in (("v1", v1_prompt), ("v2", v2_prompt)):
        messages = prompt.compile(question=question)
        response = litellm.completion(model="gpt-4o-mini", messages=messages)
        output = response.choices[0].message.content

        result = evaluate("is_concise", output=output, model="turing_small")
        row.append(str(result.passed))
        if label == "v1" and result.passed:
            v1_pass += 1
        if label == "v2" and result.passed:
            v2_pass += 1
    print(f"{row[0]:<45} {row[1]:>8} {row[2]:>8}")

print(f"\nv1: {v1_pass}/3 concise   v2: {v2_pass}/3 concise")

You should see (illustrative, your model’s phrasing will vary):

Question                                            v1       v2
---------------------------------------------------------------
What is your return policy?                       True     True
How long does standard shipping take?             True     True
Can I exchange a product instead of returni      False     True

v1: 2/3 concise   v2: 3/3 concise

result.score is a float in [0, 1]. result.passed is the boolean pass/fail derived from it (score >= 0.5). Here v1’s answer to the exchange question ran long and failed is_concise, while v2’s step-by-step instruction kept all three answers concise. That 2/3 → 3/3 delta is the evidence for promoting v2, not just “it should work better.”

Promote v2 to production

Prompt.assign_label_to_template_version(
    template_name="support-response",
    version="v2",
    label="production",
    fi_api_key=os.environ["FI_API_KEY"],
    fi_secret_key=os.environ["FI_SECRET_KEY"],
)

print("v2 is now live in production.")

You should see v2 is now live in production.

Your application now serves v2 on the next request, no redeploy. The get_template_by_name(label="production") call in Step 2 automatically picks up the new version.

Roll back to v1

If v2 causes issues, reassign the production label back to v1. Your app picks up the change on the next request.

Prompt.assign_label_to_template_version(
    template_name="support-response",
    version="v1",
    label="production",
    fi_api_key=os.environ["FI_API_KEY"],
    fi_secret_key=os.environ["FI_SECRET_KEY"],
)

print("Rolled back to v1.")

You should see Rolled back to v1., and the next call to answer_question() serves v1 again.

View version history

versions = prompt_client.list_template_versions()

for v in versions:
    draft = "draft" if v.get("isDraft") else "committed"
    print(f"  {v['templateVersion']}  {draft}  {v['createdAt']}")

You should see both versions listed (illustrative: list_template_versions() returns the backend history verbatim, so order isn’t guaranteed and your timestamps will match when you ran this tutorial):

  v2  committed  2026-08-18T09:15:00Z
  v1  committed  2026-08-18T09:10:00Z

Troubleshooting

SymptomCauseFix
AuthenticationError on prompt_client.create()FI_API_KEY or FI_SECRET_KEY missing or unexportedRe-run the export block in the current shell, then re-run the script
litellm.AuthenticationError on litellm.completion(...)OPENAI_API_KEY not setExport OPENAI_API_KEY, or pass a different model string litellm can route with a key you have
ModuleNotFoundError: No module named 'fi.prompt'futureagi not installed, or an unrelated fi package shadows itpip install futureagi, and check pip show fi doesn’t point at a different package
get_template_by_name(label="production") raises a not-found errorNo version carries the production label yetCommit at least one version with label="production" or call assign_label_to_template_version() first
get_template_by_name(version="v2") raises a not-found errorTypo in the version string, or v2 hasn’t been committed yetCall list_template_versions() to see the exact committed version strings
evaluate("is_concise", ...) raises an unknown-model errormodel isn’t a valid Turing model nameUse turing_small or another name from the built-in eval metrics list
v2 changes don’t show up in answer_question()v2 was committed but not promoted, or the label was assigned to a different versionConfirm the label with list_template_versions(), then re-run assign_label_to_template_version()

Next: compare prompt variants side by side in Experimentation.

Was this page helpful?

Questions & Discussion