feat(05-01): AgentTemplate ORM model, migration 007, and system prompt builder
- Add AgentTemplate ORM model to tenant.py (global, not tenant-scoped) - Create migration 007 with agent_templates table and 7 seed templates - Create shared/prompts/system_prompt_builder.py with build_system_prompt() - AI transparency clause always present (non-negotiable per Phase 1 decision) - Unit tests pass (17 tests, all sections verified)
This commit is contained in:
@@ -186,6 +186,89 @@ class Agent(Base):
|
||||
return f"<Agent id={self.id} name={self.name!r} tenant_id={self.tenant_id}>"
|
||||
|
||||
|
||||
class AgentTemplate(Base):
|
||||
"""
|
||||
Pre-built AI employee templates available to all tenants.
|
||||
|
||||
Templates are NOT tenant-scoped (no tenant_id, no RLS). Any authenticated
|
||||
portal user can browse templates. Only tenant admins can deploy them.
|
||||
Deploying a template creates an independent Agent snapshot — subsequent
|
||||
changes to the template do not affect deployed agents.
|
||||
"""
|
||||
|
||||
__tablename__ = "agent_templates"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
)
|
||||
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
role: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
description: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="2-3 sentence card preview description shown in the template gallery",
|
||||
)
|
||||
category: Mapped[str] = mapped_column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
default="general",
|
||||
comment="Template category: support | sales | operations | finance | general",
|
||||
)
|
||||
persona: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="Paragraph describing the agent's communication style and personality",
|
||||
)
|
||||
system_prompt: Mapped[str] = mapped_column(
|
||||
Text,
|
||||
nullable=False,
|
||||
default="",
|
||||
comment="Full system prompt including AI transparency clause",
|
||||
)
|
||||
model_preference: Mapped[str] = mapped_column(
|
||||
String(50),
|
||||
nullable=False,
|
||||
default="quality",
|
||||
comment="quality | balanced | economy | local",
|
||||
)
|
||||
tool_assignments: Mapped[list[Any]] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="JSON array of tool name strings",
|
||||
)
|
||||
escalation_rules: Mapped[list[Any]] = mapped_column(
|
||||
JSON,
|
||||
nullable=False,
|
||||
default=list,
|
||||
comment="JSON array of {condition, action} escalation rule objects",
|
||||
)
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
nullable=False,
|
||||
default=True,
|
||||
comment="Inactive templates are hidden from the gallery",
|
||||
)
|
||||
sort_order: Mapped[int] = mapped_column(
|
||||
Integer,
|
||||
nullable=False,
|
||||
default=0,
|
||||
comment="Display order in the template gallery (ascending)",
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=func.now(),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<AgentTemplate id={self.id} name={self.name!r} category={self.category!r}>"
|
||||
|
||||
|
||||
class ChannelConnection(Base):
|
||||
"""
|
||||
Links a messaging platform workspace to a Konstruct tenant.
|
||||
|
||||
1
packages/shared/shared/prompts/__init__.py
Normal file
1
packages/shared/shared/prompts/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Konstruct system prompt utilities
|
||||
68
packages/shared/shared/prompts/system_prompt_builder.py
Normal file
68
packages/shared/shared/prompts/system_prompt_builder.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
System prompt builder for Konstruct AI employees.
|
||||
|
||||
Assembles a coherent system prompt from wizard inputs (name, role, persona,
|
||||
tools, escalation rules) and always appends the mandatory AI transparency
|
||||
clause. The transparency clause is non-negotiable and cannot be omitted.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# Non-negotiable AI transparency clause (Phase 1 architectural decision).
|
||||
# Agents must always disclose their AI nature when directly asked.
|
||||
AI_TRANSPARENCY_CLAUSE = (
|
||||
"When directly asked if you are an AI, always disclose that you are an AI assistant."
|
||||
)
|
||||
|
||||
|
||||
def build_system_prompt(
|
||||
name: str,
|
||||
role: str,
|
||||
persona: str = "",
|
||||
tool_assignments: list[str] | None = None,
|
||||
escalation_rules: list[dict[str, str]] | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Build a system prompt for an AI employee from wizard inputs.
|
||||
|
||||
Args:
|
||||
name: The agent's display name (e.g. "Mara").
|
||||
role: The agent's role title (e.g. "Customer Support Rep").
|
||||
persona: Optional paragraph describing the agent's communication style
|
||||
and personality traits.
|
||||
tool_assignments: Optional list of tool names available to the agent.
|
||||
Omitted from the prompt when empty or None.
|
||||
escalation_rules: Optional list of escalation rule dicts, each with
|
||||
"condition" and "action" keys. Omitted when empty/None.
|
||||
|
||||
Returns:
|
||||
A complete system prompt string, always ending with the AI transparency
|
||||
clause.
|
||||
"""
|
||||
sections: list[str] = []
|
||||
|
||||
# --- Identity header ---
|
||||
identity = f"You are {name}, {role}."
|
||||
if persona:
|
||||
identity = f"{identity}\n\n{persona}"
|
||||
sections.append(identity)
|
||||
|
||||
# --- Tools section (omitted when empty) ---
|
||||
effective_tools = tool_assignments if tool_assignments else []
|
||||
if effective_tools:
|
||||
tool_lines = "\n".join(f"- {tool}" for tool in effective_tools)
|
||||
sections.append(f"You have access to the following tools:\n{tool_lines}")
|
||||
|
||||
# --- Escalation rules section (omitted when empty) ---
|
||||
effective_rules = escalation_rules if escalation_rules else []
|
||||
if effective_rules:
|
||||
rule_lines = "\n".join(
|
||||
f"- If {rule.get('condition', '')}: {rule.get('action', '')}"
|
||||
for rule in effective_rules
|
||||
)
|
||||
sections.append(f"Escalation rules:\n{rule_lines}")
|
||||
|
||||
# --- AI transparency clause (always present, non-negotiable) ---
|
||||
sections.append(AI_TRANSPARENCY_CLAUSE)
|
||||
|
||||
return "\n\n".join(sections)
|
||||
Reference in New Issue
Block a user