feat(models): 组织、用户、角色、客户、会话与消息模型
按组织边界定义数据关系,补齐仓储层与通用工具,迁移脚本可重复执行。
This commit is contained in:
1 parent
2aee6286d6
commit
5e0615f36d
126 files changed
+17436
No files matched your search
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Copyright 2024-2026 ChatterMate
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional, Sequence
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import func
|
||||
|
||||
from app.core.logger import get_logger
|
||||
from app.database import SessionLocal
|
||||
from app.models.guardrail_event import GuardrailEvent
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def record_guardrail_events(
|
||||
*,
|
||||
rules: Sequence[str],
|
||||
surface: str,
|
||||
layer: str,
|
||||
action: str,
|
||||
org_id: Optional[str] = None,
|
||||
agent_id: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
score: Optional[float] = None,
|
||||
char_len: Optional[int] = None,
|
||||
excerpt: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Best-effort insert of one row per triggered rule, in a single session.
|
||||
|
||||
Opens its OWN session: callers may hold no session at all (the workflow
|
||||
LLM path), and a guardrail write must never poison a caller's transaction.
|
||||
Swallows every exception — an event that fails to record must not affect
|
||||
the reply.
|
||||
|
||||
`matched` stores the rule ids that fired together, never the matched text:
|
||||
that text is the visitor's own words and this column is unencrypted, so
|
||||
reviewable content belongs in `excerpt` instead.
|
||||
"""
|
||||
if not rules:
|
||||
return
|
||||
try:
|
||||
with SessionLocal() as db:
|
||||
for rule in rules:
|
||||
db.add(
|
||||
GuardrailEvent(
|
||||
organization_id=org_id,
|
||||
agent_id=agent_id,
|
||||
session_id=session_id,
|
||||
surface=surface,
|
||||
layer=layer,
|
||||
rule=rule,
|
||||
action=action,
|
||||
score=score,
|
||||
matched=list(rules),
|
||||
char_len=char_len,
|
||||
excerpt=excerpt,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to record guardrail events {list(rules)}/{action}: {e}")
|
||||
|
||||
|
||||
class GuardrailEventRepository:
|
||||
"""Query/review side, used by analysis and tests."""
|
||||
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
|
||||
def counts_by_rule(
|
||||
self,
|
||||
start: datetime,
|
||||
end: datetime,
|
||||
organization_id: Optional[UUID] = None,
|
||||
) -> Dict[str, int]:
|
||||
"""Events per rule id in the half-open window [start, end)."""
|
||||
query = (
|
||||
self.db.query(GuardrailEvent.rule, func.count(GuardrailEvent.id))
|
||||
.filter(GuardrailEvent.created_at >= start)
|
||||
.filter(GuardrailEvent.created_at < end)
|
||||
)
|
||||
if organization_id is not None:
|
||||
query = query.filter(GuardrailEvent.organization_id == organization_id)
|
||||
return dict(query.group_by(GuardrailEvent.rule).all())
|
||||
|
||||
def mark_false_positive(self, event_id: UUID) -> Optional[GuardrailEvent]:
|
||||
"""Sole writer of the review flags: marks an event reviewed AND a
|
||||
false positive (the precision gate for strict mode)."""
|
||||
event = self.db.query(GuardrailEvent).filter(GuardrailEvent.id == event_id).first()
|
||||
if not event:
|
||||
return None
|
||||
event.reviewed = True
|
||||
event.false_positive = True
|
||||
self.db.commit()
|
||||
self.db.refresh(event)
|
||||
return event
|
||||
Reference in new issue
Block a user