Files
aiagent/backend/app/services/shadow_executor.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

217 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
影子模式执行引擎 — 数字分身生成建议但不执行,对比人类决策
"""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func, desc
from app.core.database import SessionLocal
from app.models.shadow_comparison import ShadowComparison
from app.services.fingerprint_engine import fingerprint_engine
logger = logging.getLogger(__name__)
class ShadowExecutor:
"""影子模式执行器 — 观察学习阶段的核心引擎"""
def __init__(self):
self._unlock_thresholds = {
"code_review": 0.85,
"email": 0.90,
"document": 0.85,
"decision": 0.90,
}
def generate_suggestion(self, user_id: str, category: str, context: Dict[str, Any]) -> Dict[str, Any]:
"""基于用户指纹生成影子建议不使用LLM纯规则+指纹推断)。"""
fp = fingerprint_engine.get_fingerprint(user_id)
preference = (fp.get("preference_weights", {}).get(category) if fp else None) or {}
rules = fp.get("decision_rules", []) if fp else []
# 匹配决策规则
matched_rules = []
for rule in rules:
if rule.get("action", "").startswith(category):
matched_rules.append(rule)
suggestion = {
"category": category,
"based_on": "fingerprint" if fp else "default",
"preference_applied": preference,
"matched_rules": matched_rules[:5],
"suggested_actions": self._generate_actions(category, context, preference, matched_rules),
"confidence": min(0.5 + 0.05 * len(matched_rules), 0.95),
}
return suggestion
def _generate_actions(self, category: str, context: Dict[str, Any],
preference: Dict[str, Any], rules: List[Dict]) -> List[Dict[str, Any]]:
"""根据分类和偏好生成建议动作。"""
actions = []
if category == "code_review":
if preference.get("security", 0.3) > 0.3:
actions.append({"priority": "high", "action": "检查安全漏洞", "detail": "SQL注入/XSS/权限校验"})
if preference.get("performance", 0.25) > 0.25:
actions.append({"priority": "medium", "action": "检查性能", "detail": "N+1查询/内存泄漏/大循环"})
if preference.get("readability", 0.25) > 0.25:
actions.append({"priority": "low", "action": "检查可读性", "detail": "命名/注释/函数长度"})
elif category == "document":
actions.append({"priority": "medium", "action": "结构检查", "detail": "章节完整性/逻辑连贯性"})
actions.append({"priority": "low", "action": "风格统一", "detail": "术语一致性/格式规范"})
elif category == "decision":
actions.append({"priority": "high", "action": "数据验证", "detail": "检查决策依据是否充分"})
actions.append({"priority": "medium", "action": "风险评估", "detail": "识别潜在风险和副作用"})
elif category == "email":
actions.append({"priority": "medium", "action": "语气检查", "detail": "与收件人关系的匹配度"})
actions.append({"priority": "low", "action": "完整性检查", "detail": "回复是否涵盖所有要点"})
return actions
def compare(self, user_id: str, shadow_suggestion: Dict[str, Any],
user_decision: Dict[str, Any], user_action: str) -> Dict[str, Any]:
"""对比影子建议与用户实际决策。"""
matched = 0
diverged = 0
suggested_actions = shadow_suggestion.get("suggested_actions", [])
if user_action == "accept":
match_score = 1.0
matched = len(suggested_actions)
elif user_action == "modify":
# 部分匹配
if user_decision.get("modified_actions"):
user_actions = {a.get("action", "") for a in user_decision["modified_actions"]}
shadow_actions = {a.get("action", "") for a in suggested_actions}
matched = len(user_actions & shadow_actions)
diverged = len(user_actions - shadow_actions)
match_score = 0.5 if len(suggested_actions) > 0 else 0.5
elif user_action == "reject":
match_score = 0.0
diverged = len(suggested_actions)
else: # ignore
match_score = 0.0
return {
"match_score": round(match_score, 2),
"matched_points": matched,
"diverged_points": diverged,
}
def record_comparison(self, user_id: str, category: str,
shadow_suggestion: Dict[str, Any],
user_decision: Dict[str, Any],
user_action: str,
context: Optional[Dict[str, Any]] = None) -> Optional[str]:
"""记录一次影子对比。"""
comparison = self.compare(user_id, shadow_suggestion, user_decision, user_action)
db: Optional[Session] = None
try:
db = SessionLocal()
entry = ShadowComparison(
user_id=user_id,
category=category,
shadow_suggestion=shadow_suggestion,
shadow_confidence=shadow_suggestion.get("confidence", 0.5),
user_decision=user_decision,
user_action=user_action,
match_score=comparison["match_score"],
match_detail=comparison,
context=context,
)
db.add(entry)
db.commit()
db.refresh(entry)
return str(entry.id)
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 get_accuracy(self, user_id: str, category: Optional[str] = None, days: int = 30) -> Dict[str, Any]:
"""获取影子模式准确率统计。"""
db: Optional[Session] = None
try:
db = SessionLocal()
from datetime import datetime, timedelta
since = datetime.now() - timedelta(days=days)
q = db.query(ShadowComparison).filter(
ShadowComparison.user_id == user_id,
ShadowComparison.created_at >= since,
)
if category:
q = q.filter(ShadowComparison.category == category)
records = q.all()
total = len(records)
if total == 0:
return {"total_comparisons": 0, "message": "暂无数据"}
avg_score = sum(r.match_score or 0 for r in records) / total
accepted = sum(1 for r in records if r.user_action == "accept")
rejected = sum(1 for r in records if r.user_action == "reject")
modified = sum(1 for r in records if r.user_action == "modify")
by_category = {}
for r in records:
cat = r.category
if cat not in by_category:
by_category[cat] = {"total": 0, "sum_score": 0.0}
by_category[cat]["total"] += 1
by_category[cat]["sum_score"] += (r.match_score or 0)
cat_accuracy = {
cat: round(d["sum_score"] / d["total"], 3)
for cat, d in by_category.items()
}
unlocked = {
cat: acc >= self._unlock_thresholds.get(cat, 0.90)
for cat, acc in cat_accuracy.items()
}
return {
"total_comparisons": total,
"average_accuracy": round(avg_score, 3),
"accepted": accepted,
"rejected": rejected,
"modified": modified,
"by_category": cat_accuracy,
"unlocked_categories": unlocked,
"period_days": days,
}
except Exception as e:
logger.error("获取准确率失败: %s", e)
return {"error": str(e)}
finally:
if db:
try:
db.close()
except Exception:
pass
shadow_executor = ShadowExecutor()