```
feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
This commit is contained in:
191
app/modules/personalization/services/context.py
Normal file
191
app/modules/personalization/services/context.py
Normal file
@@ -0,0 +1,191 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.constants import ActorValue
|
||||
from app.modules.ai_memory.constants import AIMemoryScope
|
||||
from app.modules.ai_memory.service import AIMemoryService
|
||||
from app.modules.business.models import MarketWatchlist
|
||||
from app.modules.personalization.constants import (
|
||||
PersonalizationContextKey,
|
||||
PreferenceCategory,
|
||||
)
|
||||
from app.modules.personalization.services.conversations import ConversationService
|
||||
from app.modules.personalization.services.preferences import PreferenceService
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PersonalizationContext:
|
||||
"""Ordered, provider-neutral context sections for one AI request."""
|
||||
|
||||
system_constraints: str
|
||||
company_rules: list[dict[str, Any]]
|
||||
personal_rules: list[dict[str, Any]]
|
||||
current_request: str
|
||||
preferences: list[dict[str, Any]]
|
||||
interests: list[dict[str, Any]]
|
||||
personal_memory: list[dict[str, Any]]
|
||||
conversation_history: list[dict[str, Any]]
|
||||
provider_session_id: str | None = field(default=None)
|
||||
|
||||
def as_ordered_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
PersonalizationContextKey.SYSTEM_CONSTRAINTS: self.system_constraints,
|
||||
PersonalizationContextKey.COMPANY_RULES: self.company_rules,
|
||||
PersonalizationContextKey.PERSONAL_RULES: self.personal_rules,
|
||||
PersonalizationContextKey.CURRENT_REQUEST: self.current_request,
|
||||
PersonalizationContextKey.PREFERENCES: self.preferences,
|
||||
PersonalizationContextKey.INTERESTS: self.interests,
|
||||
PersonalizationContextKey.PERSONAL_MEMORY: self.personal_memory,
|
||||
PersonalizationContextKey.CONVERSATION_HISTORY: self.conversation_history,
|
||||
}
|
||||
|
||||
|
||||
class PersonalizationContextService:
|
||||
"""Load only the explicitly requested company and owner-scoped context layers."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.memory = AIMemoryService(db)
|
||||
self.preferences = PreferenceService(db)
|
||||
self.conversations = ConversationService(db)
|
||||
|
||||
def build(
|
||||
self,
|
||||
*,
|
||||
owner_id: int | None,
|
||||
request: str,
|
||||
system_constraints: str,
|
||||
chat_type: str | None = None,
|
||||
chat_key: str | None = None,
|
||||
scope: str = AIMemoryScope.GLOBAL,
|
||||
subject: str | None = None,
|
||||
actor: str = ActorValue.SYSTEM,
|
||||
include_company_rules: bool = True,
|
||||
include_personal_context: bool = True,
|
||||
include_history: bool = True,
|
||||
) -> PersonalizationContext:
|
||||
company_rules = (
|
||||
self.memory.active_rules(
|
||||
scope=scope,
|
||||
subject=subject,
|
||||
owner_id=None,
|
||||
)
|
||||
if include_company_rules
|
||||
else []
|
||||
)
|
||||
personal_rules: list[dict[str, Any]] = []
|
||||
preference_items: list[dict[str, Any]] = []
|
||||
interests: list[dict[str, Any]] = []
|
||||
personal_memory: list[dict[str, Any]] = []
|
||||
history: list[dict[str, Any]] = []
|
||||
provider_session_id: str | None = None
|
||||
|
||||
if owner_id is not None and include_personal_context:
|
||||
personal_rules = self.memory.active_rules(
|
||||
scope=scope,
|
||||
subject=subject,
|
||||
owner_id=owner_id,
|
||||
)
|
||||
all_preferences = self.preferences.list_preferences(owner_id)
|
||||
interest_categories = {
|
||||
PreferenceCategory.TOPIC,
|
||||
PreferenceCategory.INTEREST,
|
||||
}
|
||||
for preference in all_preferences:
|
||||
if preference["category"] in interest_categories:
|
||||
interests.append(preference)
|
||||
else:
|
||||
preference_items.append(preference)
|
||||
interests.extend(self._watchlist_interests(owner_id))
|
||||
personal_memory = self.memory.recall(
|
||||
query=request,
|
||||
scope=scope,
|
||||
subject=subject,
|
||||
actor=actor,
|
||||
owner_id=owner_id,
|
||||
)
|
||||
if include_history and chat_type and chat_key:
|
||||
history = self.conversations.history(owner_id, chat_type, chat_key)
|
||||
provider_session_id = self.conversations.provider_session_id(
|
||||
owner_id,
|
||||
chat_type,
|
||||
chat_key,
|
||||
)
|
||||
|
||||
return PersonalizationContext(
|
||||
system_constraints=system_constraints,
|
||||
company_rules=company_rules,
|
||||
personal_rules=personal_rules,
|
||||
current_request=request,
|
||||
preferences=preference_items,
|
||||
interests=interests,
|
||||
personal_memory=personal_memory,
|
||||
conversation_history=history,
|
||||
provider_session_id=provider_session_id,
|
||||
)
|
||||
|
||||
def build_private_scheduled(
|
||||
self,
|
||||
*,
|
||||
owner_id: int,
|
||||
request: str,
|
||||
system_constraints: str,
|
||||
scope: str = AIMemoryScope.GLOBAL,
|
||||
subject: str | None = None,
|
||||
actor: str = ActorValue.SYSTEM,
|
||||
) -> PersonalizationContext:
|
||||
"""Build private scheduled context without company data or conversation history."""
|
||||
|
||||
return self.build(
|
||||
owner_id=owner_id,
|
||||
request=request,
|
||||
system_constraints=system_constraints,
|
||||
scope=scope,
|
||||
subject=subject,
|
||||
actor=actor,
|
||||
include_company_rules=False,
|
||||
include_personal_context=True,
|
||||
include_history=False,
|
||||
)
|
||||
|
||||
def build_group_scheduled(
|
||||
self,
|
||||
*,
|
||||
request: str,
|
||||
system_constraints: str,
|
||||
scope: str = AIMemoryScope.GLOBAL,
|
||||
subject: str | None = None,
|
||||
) -> PersonalizationContext:
|
||||
"""Build group scheduled context without any creator profile."""
|
||||
|
||||
return self.build(
|
||||
owner_id=None,
|
||||
request=request,
|
||||
system_constraints=system_constraints,
|
||||
scope=scope,
|
||||
subject=subject,
|
||||
include_company_rules=True,
|
||||
include_personal_context=False,
|
||||
include_history=False,
|
||||
)
|
||||
|
||||
def _watchlist_interests(self, owner_id: int) -> list[dict[str, Any]]:
|
||||
symbols = self.db.execute(
|
||||
select(MarketWatchlist.symbol)
|
||||
.where(
|
||||
MarketWatchlist.owner_id == owner_id,
|
||||
MarketWatchlist.enabled.is_(True),
|
||||
)
|
||||
.order_by(MarketWatchlist.symbol.asc())
|
||||
).scalars()
|
||||
return [
|
||||
{
|
||||
"category": "watchlist",
|
||||
"value": symbol,
|
||||
"source": "market_watchlist",
|
||||
}
|
||||
for symbol in symbols
|
||||
]
|
||||
Reference in New Issue
Block a user