Rebuilds the agent registration form into 7 governance sections (Identity, Classification, Autonomy, IP, Tech Stack, Data Safety, Performance, Declarations) and introduces a completion flow for agents that come in via the LibreChat collector without the new required fields. - New form fields: business_entity, client_scope, agent_classification, autonomy_level, ip_ownership, foundation_model, validated_by/date, evals_method, plus nested safety / pii / declarations objects and a registration_complete flag. - registration_complete defaults to true for form-submitted agents and false for collector-created ones; existing agents are grandfathered via a startup migration. - Owner-by-email lookup so LibreChat-synced agents surface in the user's "My Agents" view with an Incomplete badge and Complete CTA. Submitting the completion form reassigns created_by from the collector marker to the user. - Daily APScheduler job sends a digest reminder email per owner with a 7-day cooldown and 4-nudge cap (configurable). Manual trigger via POST /api/admin/completion-reminders/send. - Admin banner + modal for collector agents whose contact email doesn't match an active user, with one-click reassignment. - Gemini audit extended to also return agent_classification and an autonomy hint; applied to agents on next batch run alongside discipline/department. - New filter dimensions on agent management + admin dashboard: Business Entity, Agent Type, Autonomy, plus a Compliance Risks quick toggle. - CSV export/import gains 21 columns covering all governance fields. - Discipline 'Optimization' renamed to 'Optimisation' with idempotent startup migration; Gemini system prompt and template dropdowns updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
330 lines
14 KiB
Python
330 lines
14 KiB
Python
from pydantic import BaseModel, EmailStr, Field
|
|
from typing import Optional, List
|
|
|
|
|
|
class UsageTimelineEntry(BaseModel):
|
|
date: str = Field(..., description="Date in YYYY-MM-DD format")
|
|
message_count: int = Field(..., ge=0, description="Number of messages on this date")
|
|
token_count: int = Field(0, ge=0, description="Number of tokens consumed on this date")
|
|
|
|
|
|
# Closed-list values for the new governance/registration fields. Kept as module-level
|
|
# constants so the form, API, and migrations all reference the same source of truth.
|
|
BUSINESS_ENTITIES = ["OLIVER", "DARE", "Brandtech Group", "Pencil", "Jellyfish", "Adjust", "Other"]
|
|
CLIENT_SCOPES = ["internal", "all", "specific"]
|
|
AGENT_CLASSIFICATIONS = ["Utility", "Functional", "Supervisory", "Guardian"]
|
|
AUTONOMY_LEVELS = ["Human-Led", "Hybrid", "Autopilot"]
|
|
IP_OWNERSHIPS = ["Brandtech IP", "Client IP", "Shared/TBD"]
|
|
DISCIPLINES = [
|
|
"Strategy",
|
|
"Creative",
|
|
"Oversight including delivery",
|
|
"Optimisation",
|
|
"Back Office including operations",
|
|
"Pencil Agents",
|
|
]
|
|
|
|
|
|
class AgentSafety(BaseModel):
|
|
off_switch_confirmed: Optional[bool] = None
|
|
access_rights_confirmed: Optional[bool] = None
|
|
|
|
|
|
class AgentPII(BaseModel):
|
|
handles_pii: Optional[bool] = None
|
|
legal_ref: Optional[str] = None
|
|
data_types: Optional[str] = None
|
|
consent_recorded: Optional[bool] = None
|
|
|
|
|
|
class AgentDeclarations(BaseModel):
|
|
governance: Optional[bool] = None
|
|
accuracy: Optional[bool] = None
|
|
upkeep: Optional[bool] = None
|
|
|
|
|
|
class AiAgent(BaseModel):
|
|
agent_id: int
|
|
agent_name: str
|
|
agent_tool: str | None = Field(default=None, title="The tool or platform where the agent operates", max_length=100)
|
|
agent_description: str | None = Field(default=None, title="The description of the agent", max_length=300)
|
|
agent_purpose: str | None = Field(default=None, title="The purpose of the agent", max_length=200)
|
|
agent_version: str | None = Field(default=None, title="The version of the agent", max_length=100)
|
|
agent_status: str | None = Field(default=None, title="The status of the agent", max_length=100, enum=['Active', 'Inactive', 'Deprecated', 'Development'])
|
|
agent_location: str | None = Field(default=None, title="The location of the agent", max_length=100)
|
|
agent_department: str | None = Field(default=None, title="The department of the agent", max_length=100)
|
|
agent_contact_person: str | None = Field(default=None, title="The contact person for the agent", max_length=100)
|
|
agent_created_at: str | None = Field(default=None, title="The creation date of the agent", max_length=100)
|
|
agent_updated_at: str | None = Field(default=None, title="The last update date of the agent", max_length=100)
|
|
agent_tags: list[str] | None = Field(default=None, title="Tags associated with the agent", max_length=100)
|
|
agent_metadata: dict[str, str] | None = Field(default=None, title="Metadata associated with the agent")
|
|
agent_userbase: list[str] | None = Field(default=None, title="Userbase associated with the agent")
|
|
agent_capabilities: list[str] | None = Field(default=None, title="Capabilities of the agent")
|
|
url: str | None = Field(default=None, title="Direct link to create a conversation with this agent")
|
|
quality_audit_status: bool | None = Field(default=False, title="Quality audit status")
|
|
quality_audit_updated_by: str | None = Field(default=None, title="Admin user ID who updated quality audit")
|
|
quality_audit_updated_at: str | None = Field(default=None, title="Quality audit last update timestamp")
|
|
quality_audit_updated_by_name: str | None = Field(default=None, title="Admin user name who updated quality audit")
|
|
risk_factor: int | None = Field(default=None, title="Risk factor rating (1-5)", ge=1, le=5)
|
|
last_edited_by: str | None = Field(default=None, title="Email of user who last edited this agent")
|
|
discipline: str | None = Field(default=None, title="Business discipline/category", max_length=100)
|
|
rating: float | None = Field(default=None, title="Star rating (1-5)", ge=1, le=5)
|
|
instructions: str | None = Field(default=None, title="System prompt / instructions from LibreChat")
|
|
|
|
# Governance / registration fields (introduced 2026-05)
|
|
business_entity: Optional[str] = Field(default=None, title="Group company the agent sits under")
|
|
client_scope: Optional[str] = Field(default=None, title="internal / all / specific")
|
|
agent_classification: Optional[str] = Field(default=None, title="Utility / Functional / Supervisory / Guardian")
|
|
autonomy_level: Optional[str] = Field(default=None, title="Human-Led / Hybrid / Autopilot")
|
|
ip_ownership: Optional[str] = Field(default=None, title="Brandtech IP / Client IP / Shared/TBD")
|
|
foundation_model: Optional[str] = Field(default=None, title="Primary LLM provider/model")
|
|
safety: Optional[AgentSafety] = None
|
|
pii: Optional[AgentPII] = None
|
|
validated_by: Optional[str] = None
|
|
validation_date: Optional[str] = None
|
|
evals_method: Optional[str] = None
|
|
declarations: Optional[AgentDeclarations] = None
|
|
registration_complete: Optional[bool] = Field(default=None, title="Whether all required registration fields are populated")
|
|
|
|
|
|
|
|
|
|
# User Base Model
|
|
class UserCreate(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
full_name: Optional[str] = None
|
|
|
|
class UserLogin(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
|
|
class UserResponse(BaseModel):
|
|
email: EmailStr
|
|
full_name: Optional[str] = None
|
|
is_active: bool
|
|
is_admin: bool
|
|
role: Optional[str] = "user"
|
|
auth_provider: Optional[str] = "local"
|
|
|
|
class UserUpdate(BaseModel):
|
|
full_name: Optional[str] = None
|
|
is_active: Optional[bool] = None
|
|
is_admin: Optional[bool] = None
|
|
role: Optional[str] = Field(default=None, pattern="^(user|admin|readonly_admin)$")
|
|
|
|
class Token(BaseModel):
|
|
access_token: str
|
|
token_type: str = "bearer"
|
|
|
|
# Admin user management models
|
|
class AdminUserCreate(BaseModel):
|
|
email: EmailStr
|
|
full_name: Optional[str] = None
|
|
password: str = Field(..., min_length=8)
|
|
is_admin: bool = False
|
|
|
|
class AdminPasswordReset(BaseModel):
|
|
new_password: str = Field(..., min_length=8)
|
|
|
|
class PasswordChange(BaseModel):
|
|
current_password: str
|
|
new_password: str = Field(..., min_length=8)
|
|
|
|
# Agent models for creation and response
|
|
class AiAgentCreate(BaseModel):
|
|
agent_name: str
|
|
agent_tool: str
|
|
agent_description: Optional[str] = None
|
|
agent_purpose: Optional[str] = None
|
|
agent_version: Optional[str] = None
|
|
agent_status: Optional[str] = "Development"
|
|
agent_location: Optional[str] = None
|
|
agent_department: Optional[str] = None
|
|
agent_contact_person: Optional[str] = None
|
|
agent_tags: Optional[list[str]] = None
|
|
agent_metadata: Optional[dict[str, str]] = None
|
|
agent_userbase: Optional[list[str]] = None
|
|
agent_capabilities: Optional[list[str]] = None
|
|
url: Optional[str] = None
|
|
quality_audit_status: Optional[bool] = False
|
|
quality_audit_updated_by: Optional[str] = None
|
|
quality_audit_updated_at: Optional[str] = None
|
|
quality_audit_updated_by_name: Optional[str] = None
|
|
risk_factor: Optional[int] = Field(default=None, ge=1, le=5)
|
|
last_edited_by: Optional[str] = None
|
|
discipline: Optional[str] = None
|
|
rating: Optional[float] = Field(default=None, ge=1, le=5)
|
|
client: Optional[str] = None
|
|
client_name: Optional[str] = None
|
|
studio_name: Optional[str] = None
|
|
instructions: Optional[str] = None
|
|
|
|
# Governance / registration fields (introduced 2026-05)
|
|
business_entity: Optional[str] = None
|
|
client_scope: Optional[str] = Field(default=None, pattern="^(internal|all|specific)$")
|
|
agent_classification: Optional[str] = Field(default=None, pattern="^(Utility|Functional|Supervisory|Guardian)$")
|
|
autonomy_level: Optional[str] = Field(default=None, pattern="^(Human-Led|Hybrid|Autopilot)$")
|
|
ip_ownership: Optional[str] = None
|
|
foundation_model: Optional[str] = None
|
|
safety: Optional[AgentSafety] = None
|
|
pii: Optional[AgentPII] = None
|
|
validated_by: Optional[str] = None
|
|
validation_date: Optional[str] = None
|
|
evals_method: Optional[str] = None
|
|
declarations: Optional[AgentDeclarations] = None
|
|
registration_complete: Optional[bool] = None
|
|
|
|
class AiAgentResponse(BaseModel):
|
|
agent_id: str
|
|
agent_name: str
|
|
agent_tool: Optional[str] = None
|
|
agent_description: Optional[str] = None
|
|
agent_purpose: Optional[str] = None
|
|
agent_version: Optional[str] = None
|
|
agent_status: Optional[str] = None
|
|
agent_location: Optional[str] = None
|
|
agent_department: Optional[str] = None
|
|
agent_contact_person: Optional[str] = None
|
|
agent_created_at: Optional[str] = None
|
|
agent_updated_at: Optional[str] = None
|
|
agent_tags: Optional[list[str]] = None
|
|
agent_metadata: Optional[dict[str, str]] = None
|
|
agent_userbase: Optional[list[str]] = None
|
|
agent_capabilities: Optional[list[str]] = None
|
|
url: Optional[str] = None
|
|
quality_audit_status: Optional[bool] = None
|
|
quality_audit_updated_by: Optional[str] = None
|
|
quality_audit_updated_at: Optional[str] = None
|
|
quality_audit_updated_by_name: Optional[str] = None
|
|
risk_factor: Optional[int] = None
|
|
last_edited_by: Optional[str] = None
|
|
discipline: Optional[str] = None
|
|
rating: Optional[float] = None
|
|
rating_count: Optional[int] = None
|
|
client: Optional[str] = None
|
|
client_name: Optional[str] = None
|
|
studio_name: Optional[str] = None
|
|
verification_status: Optional[str] = None
|
|
verified_by: Optional[str] = None
|
|
verified_date: Optional[str] = None
|
|
instructions: Optional[str] = None
|
|
audit_status: Optional[str] = None
|
|
audit_date: Optional[str] = None
|
|
audit_category: Optional[str] = None
|
|
audit_risk_level: Optional[str] = None
|
|
created_by: str
|
|
|
|
# Usage tracking fields (new)
|
|
usage_timeline: Optional[List[dict]] = None
|
|
conversation_count: Optional[int] = None
|
|
unique_users: Optional[int] = None
|
|
total_messages: Optional[int] = None
|
|
first_used: Optional[str] = None
|
|
last_used: Optional[str] = None
|
|
total_tokens: Optional[int] = None
|
|
prompt_tokens: Optional[int] = None
|
|
completion_tokens: Optional[int] = None
|
|
|
|
# Governance / registration fields (introduced 2026-05)
|
|
business_entity: Optional[str] = None
|
|
client_scope: Optional[str] = None
|
|
agent_classification: Optional[str] = None
|
|
autonomy_level: Optional[str] = None
|
|
ip_ownership: Optional[str] = None
|
|
foundation_model: Optional[str] = None
|
|
safety: Optional[AgentSafety] = None
|
|
pii: Optional[AgentPII] = None
|
|
validated_by: Optional[str] = None
|
|
validation_date: Optional[str] = None
|
|
evals_method: Optional[str] = None
|
|
declarations: Optional[AgentDeclarations] = None
|
|
registration_complete: Optional[bool] = None
|
|
|
|
# Agent Collector API Models (for compatibility with agent_collector app)
|
|
class AgentCollectorCreate(BaseModel):
|
|
name: str = Field(min_length=1)
|
|
description: str = Field(min_length=1)
|
|
purpose: str = Field(min_length=1)
|
|
tool: str = Field(min_length=1)
|
|
location: Optional[str] = None
|
|
userbase: Optional[list[str]] = None
|
|
version: Optional[str] = None
|
|
creation_date: Optional[str] = None # ISO 8601 datetime string
|
|
last_updated: Optional[str] = None # ISO 8601 datetime string
|
|
capabilities: Optional[list[str]] = None
|
|
status: Optional[str] = Field(default="development", pattern="^(?i)(active|inactive|deprecated|development)$")
|
|
department: Optional[str] = None
|
|
contact_person: Optional[str] = None
|
|
tags: Optional[list[str]] = None
|
|
metadata: Optional[dict] = None
|
|
url: Optional[str] = None
|
|
discipline: Optional[str] = None
|
|
client: Optional[str] = None
|
|
client_name: Optional[str] = None
|
|
studio_name: Optional[str] = None
|
|
instructions: Optional[str] = None
|
|
|
|
# Governance / registration fields — accepted optionally so the collector
|
|
# can opt into sending them later without an API change.
|
|
business_entity: Optional[str] = None
|
|
client_scope: Optional[str] = None
|
|
agent_classification: Optional[str] = None
|
|
autonomy_level: Optional[str] = None
|
|
ip_ownership: Optional[str] = None
|
|
foundation_model: Optional[str] = None
|
|
safety: Optional[AgentSafety] = None
|
|
pii: Optional[AgentPII] = None
|
|
validated_by: Optional[str] = None
|
|
validation_date: Optional[str] = None
|
|
evals_method: Optional[str] = None
|
|
declarations: Optional[AgentDeclarations] = None
|
|
|
|
# Usage tracking fields (new)
|
|
usage_timeline: Optional[List[UsageTimelineEntry]] = None
|
|
conversation_count: Optional[int] = Field(default=None, ge=0)
|
|
unique_users: Optional[int] = Field(default=None, ge=0)
|
|
total_messages: Optional[int] = Field(default=None, ge=0)
|
|
first_used: Optional[str] = None # ISO 8601 datetime string
|
|
last_used: Optional[str] = None # ISO 8601 datetime string
|
|
total_tokens: Optional[int] = Field(default=None, ge=0)
|
|
prompt_tokens: Optional[int] = Field(default=None, ge=0)
|
|
completion_tokens: Optional[int] = Field(default=None, ge=0)
|
|
|
|
class AgentCollectorResponse(BaseModel):
|
|
status: str = "success"
|
|
message: str = "Agent data collected successfully"
|
|
agent_id: str
|
|
|
|
class HealthCheckResponse(BaseModel):
|
|
status: str
|
|
message: str
|
|
timestamp: str
|
|
database: dict
|
|
|
|
class AgentUsageTrackingResponse(BaseModel):
|
|
status: str = "usage_logged"
|
|
message: str = "Agent already exists, usage tracked"
|
|
agent_name: str
|
|
|
|
class AgentUsageRecord(BaseModel):
|
|
agent_name: str
|
|
agent_data: dict
|
|
timestamp: str
|
|
usage_count: Optional[int] = None
|
|
|
|
class AuditReviewRequest(BaseModel):
|
|
audit_status: str = Field(..., pattern="^(flagged|reviewed|cleared)$")
|
|
reviewer_notes: Optional[str] = None
|
|
|
|
class AgentUsageStatsResponse(BaseModel):
|
|
agent_name: str
|
|
total_usage_count: int
|
|
first_usage: Optional[str] = None
|
|
last_usage: Optional[str] = None
|
|
usage_by_period: dict
|
|
conversation_count: Optional[int] = None
|
|
unique_users: Optional[int] = None
|
|
total_tokens: Optional[int] = None
|
|
prompt_tokens: Optional[int] = None
|
|
completion_tokens: Optional[int] = None
|