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

210 lines
7.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.
"""
用户反馈学习服务 — 采集反馈信号,自动调整 Agent 策略
"""
from __future__ import annotations
import logging
from typing import Any, Dict, List, Optional
from collections import Counter, defaultdict
from sqlalchemy import func, desc
from sqlalchemy.orm import Session
from app.core.database import SessionLocal
from app.models.feedback_record import FeedbackRecord
from app.models.agent_execution_log import AgentExecutionLog
logger = logging.getLogger(__name__)
class FeedbackLearner:
"""从用户反馈中学习,自动调整 Agent 策略"""
def record_feedback(
self,
user_id: str,
signal_type: str,
*,
execution_log_id: Optional[str] = None,
agent_name: Optional[str] = None,
task_id: Optional[str] = None,
original_output: Optional[str] = None,
user_correction: Optional[str] = None,
feedback_context: Optional[Dict[str, Any]] = None,
) -> Optional[str]:
"""记录一条用户反馈。"""
db: Optional[Session] = None
try:
db = SessionLocal()
severity = 0.5
if signal_type == "reject_approval":
severity = 0.9
elif signal_type == "thumbs_down":
severity = 0.7
elif signal_type == "manual_edit":
severity = 0.6
elif signal_type == "retry_command":
severity = 0.4
entry = FeedbackRecord(
user_id=user_id,
signal_type=signal_type,
severity=severity,
execution_log_id=execution_log_id,
agent_name=agent_name,
task_id=task_id,
original_output=original_output[:5000] if original_output else None,
user_correction=user_correction[:5000] if user_correction else None,
feedback_context=feedback_context,
)
db.add(entry)
db.commit()
db.refresh(entry)
# 标记相关执行日志的反馈
if execution_log_id:
exec_log = db.query(AgentExecutionLog).filter(
AgentExecutionLog.id == execution_log_id
).first()
if exec_log:
exec_log.user_rating = 1 if signal_type in ("thumbs_down", "reject_approval") else 3
exec_log.user_feedback = signal_type
db.commit()
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 analyze_feedback_patterns(self, agent_name: Optional[str] = None, days: int = 7) -> Dict[str, Any]:
"""分析反馈模式,识别需要调整的策略。"""
db: Optional[Session] = None
try:
db = SessionLocal()
from datetime import datetime, timedelta
since = datetime.now() - timedelta(days=days)
q = db.query(FeedbackRecord).filter(FeedbackRecord.created_at >= since)
if agent_name:
q = q.filter(FeedbackRecord.agent_name == agent_name)
records = q.all()
if not records:
return {"total_feedback": 0, "message": "近期无反馈"}
# 统计信号类型
signal_dist = Counter(r.signal_type for r in records)
# 按 Agent 分组
by_agent = defaultdict(lambda: {"total": 0, "negative": 0, "patterns": []})
for r in records:
name = r.agent_name or "unknown"
by_agent[name]["total"] += 1
if r.signal_type in ("thumbs_down", "reject_approval"):
by_agent[name]["negative"] += 1
# 生成策略建议
strategy_advice = []
total = len(records)
negative_rate = (signal_dist.get("thumbs_down", 0) + signal_dist.get("reject_approval", 0)) / total if total > 0 else 0
if negative_rate > 0.3:
strategy_advice.append({
"type": "adjust_temperature",
"reason": f"负面反馈率 {negative_rate:.1%},建议降低 temperature",
"action": "temperature -= 0.1",
})
if signal_dist.get("retry_command", 0) / total > 0.2 if total > 0 else False:
strategy_advice.append({
"type": "enhance_prompt",
"reason": "用户频繁要求重试,输出可能不够精准",
"action": "在 system prompt 中增加更具体的输出要求",
})
if signal_dist.get("manual_edit", 0) / total > 0.2 if total > 0 else False:
strategy_advice.append({
"type": "suggest_review",
"reason": "输出频繁被手动修改,建议开启 self_review",
"action": "开启输出质量自检",
})
# 推荐有问题的 Agent
problematic_agents = [
{"agent": name, "negative_rate": round(data["negative"] / data["total"], 2)}
for name, data in by_agent.items()
if data["total"] >= 3 and data["negative"] / data["total"] > 0.3
]
return {
"total_feedback": total,
"period_days": days,
"signal_distribution": dict(signal_dist),
"overall_negative_rate": round(negative_rate, 3),
"problematic_agents": problematic_agents,
"strategy_advice": strategy_advice,
}
except Exception as e:
logger.error("分析反馈模式失败: %s", e)
return {"error": str(e)}
finally:
if db:
try:
db.close()
except Exception:
pass
def generate_negative_examples(self, agent_name: str, limit: int = 5) -> List[Dict[str, Any]]:
"""为 Agent 生成反例(用于更新 system prompt"""
db: Optional[Session] = None
try:
db = SessionLocal()
records = (
db.query(FeedbackRecord)
.filter(
FeedbackRecord.agent_name == agent_name,
FeedbackRecord.original_output.isnot(None),
FeedbackRecord.user_correction.isnot(None),
FeedbackRecord.signal_type.in_(["thumbs_down", "manual_edit"]),
)
.order_by(desc(FeedbackRecord.created_at))
.limit(limit)
.all()
)
examples = []
for r in records:
examples.append({
"original": (r.original_output or "")[:500],
"corrected": (r.user_correction or "")[:500],
"signal": r.signal_type,
})
return examples
except Exception as e:
logger.error("生成反例失败: %s", e)
return []
finally:
if db:
try:
db.close()
except Exception:
pass
feedback_learner = FeedbackLearner()