feat: 添加飞书用户模块和订阅功能支持 - 新增feishu_users模块用于处理飞书用户身份验证和权限管理 - 新增subscriptions模块用于处理订阅相关功能 - 新增personalization模块用于个性化服务 - 在alembic迁移配置中注册新的模型模块 - 在API路由器中添加feishu_users和subscriptions路由 - 实现事件调度服务的改进,包括错误处理和状态更新优化 - 添加飞书命令处理的权限检查机制 - 实现飞书应用票据事件处理 - 改进审计日志记录功能 ```
110 lines
3.4 KiB
Python
110 lines
3.4 KiB
Python
import json
|
|
from typing import Any
|
|
|
|
from fastapi import HTTPException, status
|
|
|
|
from app.modules.ai_agent.constants import (
|
|
COMPANY_MANAGEMENT_SYSTEM_INSTRUCTIONS,
|
|
UNEXPECTED_HERMES_RESPONSE,
|
|
AIChatRole,
|
|
AIContextKey,
|
|
AIErrorKey,
|
|
AIHttpPayloadKey,
|
|
AIResponseKey,
|
|
)
|
|
|
|
|
|
def _error_detail(exc: Exception) -> Any:
|
|
if isinstance(exc, HTTPException):
|
|
return exc.detail
|
|
return {AIResponseKey.TYPE: type(exc).__name__, AIResponseKey.MESSAGE: str(exc)}
|
|
|
|
|
|
def _response_payload(response: Any) -> dict[str, Any]:
|
|
try:
|
|
data = response.json()
|
|
except ValueError:
|
|
data = {AIResponseKey.TEXT: response.text}
|
|
return {AIResponseKey.STATUS_CODE: response.status_code, AIResponseKey.DATA: data}
|
|
|
|
|
|
def _chat_completion_payload(response: Any, error_key: AIErrorKey) -> dict[str, Any]:
|
|
try:
|
|
return response.json()
|
|
except ValueError as exc:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
detail={
|
|
error_key: UNEXPECTED_HERMES_RESPONSE,
|
|
AIResponseKey.RAW: {AIResponseKey.TEXT: response.text},
|
|
},
|
|
) from exc
|
|
|
|
|
|
def _chat_messages(prompt: str, context: dict[str, Any] | None = None) -> list[dict[str, str]]:
|
|
request_context = context or {}
|
|
return [
|
|
{
|
|
AIHttpPayloadKey.ROLE: AIChatRole.SYSTEM,
|
|
AIHttpPayloadKey.CONTENT: COMPANY_MANAGEMENT_SYSTEM_INSTRUCTIONS,
|
|
},
|
|
{
|
|
AIHttpPayloadKey.ROLE: AIChatRole.USER,
|
|
AIHttpPayloadKey.CONTENT: _ordered_context(prompt, request_context),
|
|
},
|
|
]
|
|
|
|
|
|
def _ordered_context(prompt: str, context: dict[str, Any]) -> str:
|
|
"""Serialize trusted personalization layers in their required precedence."""
|
|
|
|
company_rules = context.get(AIContextKey.COMPANY_RULES)
|
|
if company_rules is None:
|
|
company_rules = context.get(AIContextKey.USER_RULES) or []
|
|
controlled_keys = {
|
|
AIContextKey.USER_RULES,
|
|
AIContextKey.COMPANY_RULES,
|
|
AIContextKey.PERSONAL_RULES,
|
|
AIContextKey.PREFERENCES,
|
|
AIContextKey.INTERESTS,
|
|
AIContextKey.LOCAL_MEMORY,
|
|
AIContextKey.CONVERSATION_HISTORY,
|
|
AIContextKey.PROVIDER_SESSION_ID,
|
|
AIContextKey.ALLOW_PROVIDER_MEMORY,
|
|
}
|
|
current_context = {
|
|
str(key): value for key, value in context.items() if key not in controlled_keys
|
|
}
|
|
sections = [
|
|
("公司规则", company_rules),
|
|
("个人规则", context.get(AIContextKey.PERSONAL_RULES) or []),
|
|
(
|
|
"当前请求",
|
|
{
|
|
"prompt": prompt,
|
|
"context": current_context,
|
|
},
|
|
),
|
|
(
|
|
"个人偏好与兴趣",
|
|
{
|
|
"preferences": context.get(AIContextKey.PREFERENCES) or [],
|
|
"interests": context.get(AIContextKey.INTERESTS) or [],
|
|
},
|
|
),
|
|
("个人相关记忆", context.get(AIContextKey.LOCAL_MEMORY) or []),
|
|
("当前会话历史", context.get(AIContextKey.CONVERSATION_HISTORY) or []),
|
|
]
|
|
return "\n\n".join(
|
|
f"{title}:\n{json.dumps(value, ensure_ascii=False, default=str)}"
|
|
for title, value in sections
|
|
)
|
|
|
|
|
|
def _service_root(base_url: str, suffix: str) -> str:
|
|
root = base_url.rstrip("/")
|
|
normalized_suffix = suffix.rstrip("/")
|
|
if root.endswith(normalized_suffix):
|
|
root = root[: -len(normalized_suffix)]
|
|
return root.rstrip("/")
|