Scope monitoring TG by department
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"""Runtime-editable LLM system prompts, persisted in app_settings.
|
||||
|
||||
Three resolution levels with fallback (more specific → less specific):
|
||||
1. `llm_system_prompt:<vertical>:<section_slug>` — section override
|
||||
2. `llm_system_prompt:<vertical>` — vertical override
|
||||
1. `llm_system_prompt:<department_id>:<vertical>:<section_slug>` — section override
|
||||
2. `llm_system_prompt:<department_id>:<vertical>` — department vertical override
|
||||
3. built-in DEFAULT_RE_SYSTEM_PROMPT / DEFAULT_HR_SYSTEM_PROMPT
|
||||
|
||||
The prompt is read on every classification call but cached for a short
|
||||
@@ -27,10 +27,15 @@ _CACHE_TTL_S = 5.0
|
||||
_cache: dict[str, tuple[float, str | None]] = {}
|
||||
|
||||
|
||||
def _key(vertical: Vertical, section_slug: str | None = None) -> str:
|
||||
def _key(
|
||||
vertical: Vertical,
|
||||
department_id: str | None,
|
||||
section_slug: str | None = None,
|
||||
) -> str:
|
||||
dept = department_id or "global"
|
||||
if section_slug:
|
||||
return f"{_KEY_PREFIX}{vertical}:{section_slug}"
|
||||
return f"{_KEY_PREFIX}{vertical}"
|
||||
return f"{_KEY_PREFIX}{dept}:{vertical}:{section_slug}"
|
||||
return f"{_KEY_PREFIX}{dept}:{vertical}"
|
||||
|
||||
|
||||
async def _load(key: str) -> str | None:
|
||||
@@ -52,7 +57,7 @@ async def _load(key: str) -> str | None:
|
||||
|
||||
|
||||
async def resolve(
|
||||
vertical: Vertical, section_slug: str | None, default: str
|
||||
vertical: Vertical, department_id: str | None, section_slug: str | None, default: str
|
||||
) -> str:
|
||||
"""Pick the most specific prompt available, falling back to `default`.
|
||||
|
||||
@@ -60,39 +65,39 @@ async def resolve(
|
||||
the classifier uses for every message.
|
||||
"""
|
||||
if section_slug:
|
||||
text = await _load(_key(vertical, section_slug))
|
||||
text = await _load(_key(vertical, department_id, section_slug))
|
||||
if text is not None:
|
||||
return text
|
||||
text = await _load(_key(vertical))
|
||||
text = await _load(_key(vertical, department_id))
|
||||
if text is not None:
|
||||
return text
|
||||
return default
|
||||
|
||||
|
||||
async def get(
|
||||
vertical: Vertical, section_slug: str | None, default: str
|
||||
vertical: Vertical, department_id: str | None, section_slug: str | None, default: str
|
||||
) -> tuple[str, str]:
|
||||
"""For the settings UI: return (text, source) where source is one of
|
||||
'section' | 'vertical' | 'default'. Lets the editor show which override
|
||||
is currently active without a second round-trip.
|
||||
"""
|
||||
if section_slug:
|
||||
text = await _load(_key(vertical, section_slug))
|
||||
text = await _load(_key(vertical, department_id, section_slug))
|
||||
if text is not None:
|
||||
return text, "section"
|
||||
text = await _load(_key(vertical))
|
||||
text = await _load(_key(vertical, department_id))
|
||||
if text is not None:
|
||||
return text, "vertical"
|
||||
return default, "default"
|
||||
|
||||
|
||||
async def set_prompt(
|
||||
vertical: Vertical, section_slug: str | None, text: str
|
||||
vertical: Vertical, department_id: str | None, section_slug: str | None, text: str
|
||||
) -> None:
|
||||
"""Save a new prompt at the given level (section or vertical)."""
|
||||
if not isinstance(text, str) or not text.strip():
|
||||
raise ValueError("prompt must be a non-empty string")
|
||||
key = _key(vertical, section_slug)
|
||||
key = _key(vertical, department_id, section_slug)
|
||||
async with session_scope() as session:
|
||||
stmt = (
|
||||
pg_insert(AppSetting)
|
||||
@@ -105,9 +110,11 @@ async def set_prompt(
|
||||
invalidate(key)
|
||||
|
||||
|
||||
async def reset(vertical: Vertical, section_slug: str | None) -> None:
|
||||
async def reset(
|
||||
vertical: Vertical, department_id: str | None, section_slug: str | None
|
||||
) -> None:
|
||||
"""Drop the override at the given level."""
|
||||
key = _key(vertical, section_slug)
|
||||
key = _key(vertical, department_id, section_slug)
|
||||
async with session_scope() as session:
|
||||
await session.execute(
|
||||
AppSetting.__table__.delete().where(AppSetting.key == key)
|
||||
@@ -123,8 +130,8 @@ def invalidate(key: str | None = None) -> None:
|
||||
|
||||
|
||||
async def is_overridden(
|
||||
vertical: Vertical, section_slug: str | None = None
|
||||
vertical: Vertical, department_id: str | None, section_slug: str | None = None
|
||||
) -> bool:
|
||||
"""True iff a custom prompt is stored at this exact level."""
|
||||
text = await _load(_key(vertical, section_slug))
|
||||
text = await _load(_key(vertical, department_id, section_slug))
|
||||
return text is not None
|
||||
|
||||
Reference in New Issue
Block a user