feat(agent): Introduce identity profile for agents with normalization and default values
This commit is contained in:
@@ -38,6 +38,27 @@ OFFLINE_AFTER = timedelta(minutes=10)
|
||||
AGENT_SESSION_PREFIX = "agent"
|
||||
|
||||
|
||||
def _normalize_identity_profile(
|
||||
profile: dict[str, object] | None,
|
||||
) -> dict[str, str] | None:
|
||||
if not profile:
|
||||
return None
|
||||
normalized: dict[str, str] = {}
|
||||
for key, raw in profile.items():
|
||||
if raw is None:
|
||||
continue
|
||||
if isinstance(raw, list):
|
||||
parts = [str(item).strip() for item in raw if str(item).strip()]
|
||||
if not parts:
|
||||
continue
|
||||
normalized[key] = ", ".join(parts)
|
||||
continue
|
||||
value = str(raw).strip()
|
||||
if value:
|
||||
normalized[key] = value
|
||||
return normalized or None
|
||||
|
||||
|
||||
def _slugify(value: str) -> str:
|
||||
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
||||
return slug or uuid4().hex
|
||||
@@ -176,6 +197,9 @@ async def create_agent(
|
||||
data["identity_template"] = None
|
||||
if data.get("soul_template") == "":
|
||||
data["soul_template"] = None
|
||||
data["identity_profile"] = _normalize_identity_profile(
|
||||
data.get("identity_profile")
|
||||
)
|
||||
agent = Agent.model_validate(data)
|
||||
agent.status = "provisioning"
|
||||
raw_token = generate_agent_token()
|
||||
@@ -267,6 +291,10 @@ async def update_agent(
|
||||
updates["identity_template"] = None
|
||||
if updates.get("soul_template") == "":
|
||||
updates["soul_template"] = None
|
||||
if "identity_profile" in updates:
|
||||
updates["identity_profile"] = _normalize_identity_profile(
|
||||
updates.get("identity_profile")
|
||||
)
|
||||
if not updates:
|
||||
return _with_computed_status(agent)
|
||||
if "board_id" in updates:
|
||||
|
||||
@@ -18,6 +18,7 @@ class Agent(SQLModel, table=True):
|
||||
openclaw_session_id: str | None = Field(default=None, index=True)
|
||||
agent_token_hash: str | None = Field(default=None, index=True)
|
||||
heartbeat_config: dict[str, Any] | None = Field(default=None, sa_column=Column(JSON))
|
||||
identity_profile: dict[str, Any] | None = Field(default=None, sa_column=Column(JSON))
|
||||
identity_template: str | None = Field(default=None, sa_column=Column(Text))
|
||||
soul_template: str | None = Field(default=None, sa_column=Column(Text))
|
||||
provision_requested_at: datetime | None = Field(default=None)
|
||||
|
||||
@@ -12,6 +12,7 @@ class AgentBase(SQLModel):
|
||||
name: str
|
||||
status: str = "provisioning"
|
||||
heartbeat_config: dict[str, Any] | None = None
|
||||
identity_profile: dict[str, Any] | None = None
|
||||
identity_template: str | None = None
|
||||
soul_template: str | None = None
|
||||
|
||||
@@ -25,6 +26,7 @@ class AgentUpdate(SQLModel):
|
||||
name: str | None = None
|
||||
status: str | None = None
|
||||
heartbeat_config: dict[str, Any] | None = None
|
||||
identity_profile: dict[str, Any] | None = None
|
||||
identity_template: str | None = None
|
||||
soul_template: str | None = None
|
||||
|
||||
@@ -44,4 +46,3 @@ class AgentHeartbeat(SQLModel):
|
||||
class AgentHeartbeatCreate(AgentHeartbeat):
|
||||
name: str
|
||||
board_id: UUID | None = None
|
||||
|
||||
|
||||
@@ -17,6 +17,17 @@ from app.models.gateways import Gateway
|
||||
from app.models.users import User
|
||||
|
||||
DEFAULT_HEARTBEAT_CONFIG = {"every": "10m", "target": "none"}
|
||||
DEFAULT_IDENTITY_PROFILE = {
|
||||
"role": "Generalist",
|
||||
"communication_style": "direct, concise, practical",
|
||||
"emoji": ":gear:",
|
||||
}
|
||||
|
||||
IDENTITY_PROFILE_FIELDS = {
|
||||
"role": "identity_role",
|
||||
"communication_style": "identity_communication_style",
|
||||
"emoji": "identity_emoji",
|
||||
}
|
||||
|
||||
DEFAULT_GATEWAY_FILES = frozenset(
|
||||
{
|
||||
@@ -94,6 +105,26 @@ def _build_context(
|
||||
session_key = agent.openclaw_session_id or ""
|
||||
base_url = settings.base_url or "REPLACE_WITH_BASE_URL"
|
||||
main_session_key = gateway.main_session_key
|
||||
identity_profile: dict[str, Any] = {}
|
||||
if isinstance(agent.identity_profile, dict):
|
||||
identity_profile = agent.identity_profile
|
||||
normalized_identity: dict[str, str] = {}
|
||||
for key, value in identity_profile.items():
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, list):
|
||||
parts = [str(item).strip() for item in value if str(item).strip()]
|
||||
if not parts:
|
||||
continue
|
||||
normalized_identity[key] = ", ".join(parts)
|
||||
continue
|
||||
text = str(value).strip()
|
||||
if text:
|
||||
normalized_identity[key] = text
|
||||
identity_context = {
|
||||
context_key: normalized_identity.get(field, DEFAULT_IDENTITY_PROFILE[field])
|
||||
for field, context_key in IDENTITY_PROFILE_FIELDS.items()
|
||||
}
|
||||
return {
|
||||
"agent_name": agent.name,
|
||||
"agent_id": agent_id,
|
||||
@@ -110,6 +141,7 @@ def _build_context(
|
||||
"user_timezone": (user.timezone or "") if user else "",
|
||||
"user_notes": (user.notes or "") if user else "",
|
||||
"user_context": (user.context or "") if user else "",
|
||||
**identity_context,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user