111 lines
4.9 KiB
Python
111 lines
4.9 KiB
Python
"""One persistent persona for all configured chat/agent providers on this AI Core."""
|
|
from contextlib import closing
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
from app.database.db import connect
|
|
|
|
|
|
class DialoguePair(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
user: str = Field(default="", max_length=8000)
|
|
assistant: str = Field(default="", max_length=8000)
|
|
|
|
|
|
class PersonaSettings(BaseModel):
|
|
model_config = ConfigDict(extra="forbid")
|
|
version: int = Field(default=0, ge=0, le=9007199254740991)
|
|
revision: str = Field(default="", pattern=r"^(?:[0-9a-f]{64})?$")
|
|
name: str = Field(default="", max_length=128)
|
|
system_prompt: str = Field(default="", max_length=16000)
|
|
dialogue_pairs: list[DialoguePair] = Field(default_factory=list, max_length=20)
|
|
|
|
|
|
def connection():
|
|
conn = connect()
|
|
conn.execute("CREATE TABLE IF NOT EXISTS global_persona (id INTEGER PRIMARY KEY CHECK(id=1), data TEXT NOT NULL)")
|
|
return conn
|
|
|
|
|
|
def _desktop():
|
|
from app.config import get_settings
|
|
return get_settings().environment == 'desktop'
|
|
|
|
|
|
def load_persona():
|
|
if _desktop():
|
|
from app.services.desktop_notes import call
|
|
document = call('persona.get', id='default')
|
|
if document is None:
|
|
return PersonaSettings()
|
|
return PersonaSettings.model_validate({**document['record']['data'], 'revision': document['hash']})
|
|
with closing(connection()) as conn:
|
|
row = conn.execute("SELECT data FROM global_persona WHERE id=1").fetchone()
|
|
return PersonaSettings.model_validate_json(row[0]) if row else PersonaSettings()
|
|
|
|
|
|
def legacy_persona_preview():
|
|
"""Explicit read-only import source; no automatic Vault ownership inference."""
|
|
from app.errors import ApiError
|
|
from app.services.desktop_notes import call
|
|
if not _desktop():
|
|
raise ApiError(404, 'RESOURCE_NOT_FOUND', '此入口仅用于桌面人设导入。')
|
|
call('persona.get', id='default') # Revalidate the authenticated Vault at Host.
|
|
with closing(connection()) as conn:
|
|
row = conn.execute("SELECT data FROM global_persona WHERE id=1").fetchone()
|
|
if not row:
|
|
return {'available': False, 'persona': None}
|
|
source = PersonaSettings.model_validate_json(row[0])
|
|
return {'available': True, 'persona': source.model_dump(exclude={'revision'})}
|
|
|
|
|
|
def save_persona(settings):
|
|
if _desktop():
|
|
from uuid import uuid4
|
|
from app import host_bridge
|
|
from app.services.desktop_notes import call
|
|
from app.errors import ApiError
|
|
if settings.version >= 9007199254740991:
|
|
raise ApiError(409, 'PERSONA_VERSION_EXHAUSTED', '人设版本已达到上限。')
|
|
data = settings.model_dump(exclude={'revision'})
|
|
data['version'] += 1
|
|
operation = host_bridge.operation_id.get() or str(uuid4())
|
|
try:
|
|
receipt = call('persona.write', record={'schema': 1, 'kind': 'persona', 'id': 'default', 'data': data},
|
|
expected=settings.revision, operation_id=operation)
|
|
except ApiError as error:
|
|
if error.code == 'REVISION_CONFLICT':
|
|
raise ApiError(409, 'PERSONA_VERSION_CONFLICT', '当前工作区人设已被修改,请重新打开表单后保存。') from None
|
|
raise
|
|
return PersonaSettings.model_validate({**receipt['record']['data'], 'revision': receipt['hash']})
|
|
from app.errors import ApiError
|
|
with closing(connection()) as conn:
|
|
conn.execute("BEGIN IMMEDIATE")
|
|
try:
|
|
row = conn.execute("SELECT data FROM global_persona WHERE id=1").fetchone()
|
|
current = PersonaSettings.model_validate_json(row[0]) if row else PersonaSettings()
|
|
if current.version != settings.version:
|
|
raise ApiError(409, "PERSONA_VERSION_CONFLICT", "全局人设已被修改,请重新打开表单后保存。")
|
|
updated = settings.model_copy(update={"version": current.version + 1})
|
|
conn.execute("INSERT OR REPLACE INTO global_persona(id,data) VALUES(1,?)", (updated.model_dump_json(),))
|
|
conn.commit()
|
|
return updated
|
|
except BaseException:
|
|
conn.rollback()
|
|
raise
|
|
|
|
|
|
def apply_global_persona(request):
|
|
settings = load_persona()
|
|
parts = [request.system or ""]
|
|
if settings.system_prompt.strip():
|
|
parts.append("全局人设 / Global persona\n" + settings.system_prompt.strip())
|
|
examples = []
|
|
for pair in settings.dialogue_pairs:
|
|
lines = []
|
|
if pair.user.strip(): lines.append("User: " + pair.user.strip())
|
|
if pair.assistant.strip(): lines.append("Assistant: " + pair.assistant.strip())
|
|
if lines: examples.append("\n".join(lines))
|
|
if examples:
|
|
parts.append("预设对话示例 / Example dialogue\n" + "\n\n".join(examples))
|
|
system = "\n\n".join(part for part in parts if part.strip())
|
|
return request.model_copy(update={"system": system or None})
|