Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion infra/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,14 +428,38 @@ def seed_demo_agent(project, user, log=logger.info) -> dict[str, str]:
tool.external_id = tool_external_id
tool.save(update_fields=["external_id", "updated_at"])

# Seed a predefined system prompt (v1) at /prompts/ using the demo agent's
# instructions as its v1 content, then point the agent at that same text.
# Because the agent's text matches the version exactly, the agent editor's
# picker auto-preselects it. Idempotent: reuses an existing same-named prompt.
from prompts.models import SystemPrompt
from prompts.services import create_prompt

meta = DEMO_AGENT
# Seed a predefined system prompt (v1) at /prompts/ using the demo agent's
# instructions as its v1 content, then point the agent at that same text.
# Because the agent's text matches the version exactly, the agent editor's
# picker auto-preselects it. Idempotent: reuses an existing same-named prompt.
prompt_name = "Acme Retail Support Refund Assistant"
prompt = SystemPrompt.objects.filter(project=project, name=prompt_name).first()
if prompt is None or not prompt.latest:
prompt = create_prompt(
project=project,
name=prompt_name,
description="Instructions for the demo Support Refund Assistant agent.",
content=meta["system_prompt"],
note="Seeded",
user=user,
)
log(f"Seeded system prompt '{prompt.name}' (v1).")

agent, created = Agent.objects.get_or_create(
project=project,
name=meta["name"],
defaults={
"description": meta["description"],
"base_model": base_model,
"system_prompt": meta["system_prompt"],
"system_prompt": prompt.latest.content,
"retrieval_settings": {
"search_mode": "hybrid",
"top_k": 5,
Expand Down
19 changes: 19 additions & 0 deletions infra/tests/test_seed_demo_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ def test_is_idempotent(self):
self.assertEqual(Tool.objects.filter(project=self.project).count(), 1)
self.assertEqual(Agent.objects.get(project=self.project).retrieval_settings["top_k"], 5)

def test_seeds_a_versioned_prompt_the_agent_uses(self):
from prompts.models import SystemPrompt

seed_demo_agent(self.project, self.user)

# A predefined prompt (v1) exists at /prompts/.
prompt = SystemPrompt.objects.get(
project=self.project, name="Acme Retail Support Refund Assistant"
)
self.assertEqual(prompt.latest.version, 1)
# The agent's instructions match that version exactly, so the picker
# auto-preselects it and deletion of the prompt is blocked while in use.
agent = Agent.objects.get(project=self.project, name=DEMO_AGENT["name"])
self.assertEqual(agent.system_prompt, prompt.latest.content)
# Idempotent: re-running does not create a second prompt or bump v2.
seed_demo_agent(self.project, self.user)
self.assertEqual(SystemPrompt.objects.filter(project=self.project).count(), 1)
self.assertEqual(prompt.versions.count(), 1)

def test_skips_when_no_model_exists(self):
self.model.delete()
status = seed_demo_agent(self.project, self.user)
Expand Down
7 changes: 6 additions & 1 deletion templates/agents/agent_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -471,15 +471,20 @@ <h2 class="text-base font-semibold text-gray-100">Expose <span id="expose-model-
if (!fieldset || !ta) return;
const allRadios = () => [...fieldset.querySelectorAll('input[name="system_prompt_version"]')];
function syncAll() {
const checked = allRadios().find(r => r.checked);
fieldset.querySelectorAll('[data-prompt-row]').forEach(row => {
const rs = [...row.querySelectorAll('input[name="system_prompt_version"]')];
const rowChecked = rs.find(r => r.checked);
const nameRadio = row.querySelector('[data-row-radio]');
const summary = row.querySelector('[data-prompt-summary]');
if (!rs.length) return;
// The name radio means "latest": keep it checked whenever the row's pick
// is the latest version, regardless of which element the browser marked.
if (nameRadio) nameRadio.checked = !!rowChecked && rowChecked.hasAttribute('data-latest');
if (!summary) return;
if (rowChecked) summary.textContent = `v${rowChecked.dataset.version}` + (rowChecked.hasAttribute('data-latest') ? ' · latest' : ' · older version');
else { const latest = rs.find(r => 'latest' in r.dataset); summary.textContent = latest ? `v${latest.dataset.version} · latest` : ''; }
});
const checked = allRadios().find(r => r.checked);
if (checked && ta.value !== checked.dataset.content) ta.value = checked.dataset.content || '';
}
allRadios().forEach(r => r.addEventListener('change', syncAll));
Expand Down
7 changes: 6 additions & 1 deletion templates/partials/run_settings_js.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,23 @@
const allRadios = () => [...fieldset.querySelectorAll('input[name="system_prompt_version"]')];

function syncAll() {
const checked = allRadios().find(r => r.checked);
fieldset.querySelectorAll('[data-prompt-row]').forEach(row => {
const rs = [...row.querySelectorAll('input[name="system_prompt_version"]')];
const rowChecked = rs.find(r => r.checked);
const nameRadio = row.querySelector('[data-row-radio]');
const summary = row.querySelector('[data-prompt-summary]');
if (!rs.length) return;
// The name radio means "latest". It stays checked whenever the row's pick
// is the latest version, regardless of which element the browser marked.
if (nameRadio) nameRadio.checked = !!rowChecked && rowChecked.hasAttribute('data-latest');
if (!summary) return;
if (rowChecked) summary.textContent = `v${rowChecked.dataset.version}` + (rowChecked.hasAttribute('data-latest') ? ' · latest' : ' · older version');
else {
const latest = rs.find(r => 'latest' in r.dataset);
summary.textContent = latest ? `v${latest.dataset.version} · latest` : '';
}
});
const checked = allRadios().find(r => r.checked);
if (checked && ta.value !== checked.dataset.content) ta.value = checked.dataset.content || '';
}

Expand Down
Loading