Files
aiagent/backend/app/services/fingerprint_engine.py
renjianbo ab1589921a fix: 修复35个安全与功能缺陷,补全知识进化/数字孪生/行为采集模块
## 安全修复 (12项)
- Webhook接口添加全局Token认证,过滤敏感请求头
- 修复JWT Base64 padding公式,防止签名验证绕过
- 数据库密码/飞书Token从源码移除,改为环境变量
- 工作流引擎添加路径遍历防护 (_resolve_safe_path)
- eval()添加模板长度上限检查
- 审批API添加认证依赖
- 前端v-html增强XSS转义,console.log仅开发模式输出
- 500错误不再暴露内部异常详情

## Agent运行时修复 (7项)
- 删除_inject_knowledge_context中未定义db变量的finally块
- 工具执行添加try/except保护,异常不崩溃Agent
- LLM重试计入budget计数器
- self_review异常时passed=False
- max_iterations截断标记success=False
- 工具参数JSON解析失败时记录警告日志
- run()开始时重置_llm_invocations计数器

## 配置与基础设施
- DEBUG默认False,SQL_ECHO独立配置项
- init_db()补全13个缺失模型导入
- 新增WEBHOOK_AUTH_TOKEN/SQL_ECHO配置项
- 新增.env.example模板文件

## 前端修复 (12项)
- 登录改用URLSearchParams替代FormData
- 401拦截器通过Pinia store统一清理状态
- SSE流超时从60s延长至300s
- final/error事件时清除streamTimeout
- localStorage聊天记录添加24h TTL
- safeParseArgCount替代模板中裸JSON.parse
- fetchUser 401时同时清除user对象

## 新增模块
- 知识进化: knowledge_extractor/retriever/tasks
- 数字孪生: shadow_executor/comparison模型
- 行为采集: behavior_middleware/collector/fingerprint_engine
- 代码审查: code_review_agent/document_review_agent
- 反馈学习: feedback_learner
- 瓶颈检测/优化引擎/成本估算/需求估算
- 速率限制器 (rate_limiter)
- Alembic迁移 015-020

## 文档
- 商业化落地计划
- 8篇docs文档 (架构/API/部署/开发/贡献等)
- Docker Compose生产配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-10 19:50:20 +08:00

222 lines
8.4 KiB
Python

"""
用户行为指纹引擎 — 从行为日志中学习偏好权重和决策规则
"""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from collections import Counter
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.core.database import SessionLocal
from app.models.user_behavior import UserBehaviorLog, BehaviorCategory
from app.models.user_fingerprint import UserFingerprint
logger = logging.getLogger(__name__)
DEFAULT_WEIGHTS = {
"code_review": {"security": 0.3, "performance": 0.25, "readability": 0.25, "style": 0.2},
"document": {"structure": 0.3, "clarity": 0.3, "completeness": 0.25, "style": 0.15},
"decision": {"data_driven": 0.4, "risk_averse": 0.3, "speed": 0.3},
"email": {"formality": 0.3, "conciseness": 0.35, "responsiveness": 0.35},
}
class FingerprintEngine:
"""从行为日志学习用户行为指纹"""
def compute_fingerprint(self, user_id: str) -> Optional[Dict[str, Any]]:
"""计算用户行为指纹。"""
db: Optional[Session] = None
try:
db = SessionLocal()
total = db.query(func.count(UserBehaviorLog.id)).filter(
UserBehaviorLog.user_id == user_id
).scalar() or 0
if total < 10:
logger.info("用户 %s 行为数据不足 (%d 条)", user_id, total)
return None
by_category = {}
for cat in BehaviorCategory:
count = db.query(func.count(UserBehaviorLog.id)).filter(
UserBehaviorLog.user_id == user_id,
UserBehaviorLog.category == cat.value,
).scalar() or 0
by_category[cat.value] = count
preference_weights = self._extract_preferences(db, user_id)
decision_rules = self._extract_rules(db, user_id)
behaviors = (
db.query(UserBehaviorLog)
.filter(UserBehaviorLog.user_id == user_id)
.order_by(UserBehaviorLog.created_at.desc())
.limit(100)
.all()
)
durations = []
for b in behaviors:
if b.result and isinstance(b.result, dict):
d = b.result.get("duration_ms")
if d:
durations.append(d)
avg_response = int(sum(durations) / len(durations)) if durations else None
return {
"user_id": user_id,
"preference_weights": preference_weights,
"decision_rules": decision_rules,
"total_behaviors": total,
"behaviors_by_category": by_category,
"avg_response_time_ms": avg_response,
}
except Exception as e:
logger.error("指纹计算失败: %s", e)
return None
finally:
if db:
try:
db.close()
except Exception:
pass
def _extract_preferences(self, db: Session, user_id: str) -> Dict[str, Any]:
weights = dict(DEFAULT_WEIGHTS)
recent = (
db.query(UserBehaviorLog)
.filter(UserBehaviorLog.user_id == user_id)
.order_by(UserBehaviorLog.created_at.desc())
.limit(200)
.all()
)
for b in recent:
if b.result and isinstance(b.result, dict):
priority = b.result.get("priority")
if priority and isinstance(priority, dict):
cat = b.category
if cat in weights:
for k, v in priority.items():
if k in weights[cat]:
weights[cat][k] = weights[cat][k] * 0.9 + v * 0.1
return weights
def _extract_rules(self, db: Session, user_id: str) -> List[Dict[str, Any]]:
rules = []
recent = (
db.query(UserBehaviorLog)
.filter(UserBehaviorLog.user_id == user_id)
.order_by(UserBehaviorLog.created_at.desc())
.limit(300)
.all()
)
action_counts = Counter(b.action for b in recent)
for action, count in action_counts.most_common(20):
if count >= 5:
actions_of_type = [b for b in recent if b.action == action]
status_codes = Counter()
for b in actions_of_type:
if b.result and isinstance(b.result, dict):
sc = b.result.get("status_code")
if sc:
status_codes[sc] += 1
if status_codes and status_codes.most_common(1)[0][1] / count > 0.8:
rules.append({
"action": action,
"expected_outcome": status_codes.most_common(1)[0][0],
"confidence": round(status_codes.most_common(1)[0][1] / count, 2),
"sample_count": count,
})
return rules[:50]
def save_or_update(self, user_id: str, fingerprint: Dict[str, Any]) -> Optional[str]:
db: Optional[Session] = None
try:
db = SessionLocal()
existing = db.query(UserFingerprint).filter(
UserFingerprint.user_id == user_id
).first()
if existing:
existing.preference_weights = fingerprint["preference_weights"]
existing.decision_rules = fingerprint["decision_rules"]
existing.total_behaviors = fingerprint["total_behaviors"]
existing.behaviors_by_category = fingerprint["behaviors_by_category"]
existing.avg_response_time_ms = fingerprint.get("avg_response_time_ms")
existing.model_version = str(float(existing.model_version or "1.0") + 0.1)
existing.last_trained_at = func.now()
fid = str(existing.id)
else:
entry = UserFingerprint(
user_id=user_id,
preference_weights=fingerprint["preference_weights"],
decision_rules=fingerprint["decision_rules"],
total_behaviors=fingerprint["total_behaviors"],
behaviors_by_category=fingerprint["behaviors_by_category"],
avg_response_time_ms=fingerprint.get("avg_response_time_ms"),
)
db.add(entry)
db.flush()
fid = str(entry.id)
db.commit()
return fid
except Exception as e:
logger.error("保存指纹失败: %s", e)
if db:
try:
db.rollback()
except Exception:
pass
return None
finally:
if db:
try:
db.close()
except Exception:
pass
def train(self, user_id: str) -> Optional[Dict[str, Any]]:
fingerprint = self.compute_fingerprint(user_id)
if fingerprint:
self.save_or_update(user_id, fingerprint)
logger.info("用户 %s 指纹训练完成: %d 条行为", user_id, fingerprint["total_behaviors"])
return fingerprint
def get_fingerprint(self, user_id: str) -> Optional[Dict[str, Any]]:
db: Optional[Session] = None
try:
db = SessionLocal()
fp = db.query(UserFingerprint).filter(
UserFingerprint.user_id == user_id
).first()
if not fp:
return None
return {
"user_id": fp.user_id,
"preference_weights": fp.preference_weights,
"decision_rules": fp.decision_rules,
"total_behaviors": fp.total_behaviors,
"behaviors_by_category": fp.behaviors_by_category,
"avg_response_time_ms": fp.avg_response_time_ms,
"model_version": fp.model_version,
"last_trained_at": fp.last_trained_at.isoformat() if fp.last_trained_at else None,
}
except Exception as e:
logger.error("获取指纹失败: %s", e)
return None
finally:
if db:
try:
db.close()
except Exception:
pass
fingerprint_engine = FingerprintEngine()