Files
aiagent/backend/app/services/scene_contract_service.py
renjianbo beff3fac8d fix: delete agent 500 error + dynamic personality + deployment guide
- Fix delete agent 500: clean up FK records (agent_llm_logs, permissions,
  schedules, executions, team_members) and unbind goals/tasks before delete
- Remove hardcoded personality templates in Android, replace with dynamic
  system prompt generation from name + description
- Set promptSectionsEnabled=false to bypass PromptComposer for personality
- Add Tencent Cloud Linux deployment guide (Docker Compose)
- Accumulated backend service updates, frontend UI fixes, Android app changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-29 01:17:21 +08:00

514 lines
21 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.
"""
场景契约服务 — 统一 DSL 的提示词生成、输入验证、验收评估
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field
@dataclass
class ContractPromptConfig:
"""由契约生成提示词时的可选配置"""
include_role: bool = True
include_constraints: bool = True
include_deliverables: bool = True
include_examples: bool = True
include_acceptance: bool = False # 验收标准通常不暴露给 Agent
extra_instructions: str = ""
temperature: float = 0.3
def build_system_prompt_from_contract(
contract: Dict[str, Any],
config: Optional[ContractPromptConfig] = None,
) -> str:
"""
从场景契约生成结构化 system prompt。
Args:
contract: 契约字典(含 goal/role/constraints/deliverables/acceptance_criteria/examples
config: 可选生成配置
Returns:
结构化的 system prompt 字符串
"""
cfg = config or ContractPromptConfig()
parts: List[str] = []
# 角色定义
if cfg.include_role and contract.get("role"):
parts.append(f"# 角色\n{contract['role']}")
# 目标
goal = contract.get("goal", "")
if goal:
parts.append(f"# 目标\n{goal}")
# 输入说明
input_desc = contract.get("input_description", "")
if input_desc:
parts.append(f"# 输入说明\n{input_desc}\n用户输入将包含在 {{input}} 中。")
# 约束条件
if cfg.include_constraints:
constraints = contract.get("constraints") or []
if constraints:
lines = ["# 约束条件"]
for i, c in enumerate(constraints, 1):
lines.append(f"{i}. {c}")
parts.append("\n".join(lines))
forbidden = contract.get("forbidden_actions") or []
if forbidden:
lines = ["# 禁止事项"]
for i, f in enumerate(forbidden, 1):
lines.append(f"{i}. {f}")
parts.append("\n".join(lines))
# 产出物
if cfg.include_deliverables:
deliverables = contract.get("deliverables") or []
if deliverables:
lines = ["# 产出物要求"]
for d in deliverables:
name = d.get("name", "")
fmt = d.get("format", "")
desc = d.get("description", "")
line = f"- **{name}**"
if fmt:
line += f"(格式: {fmt}"
if desc:
line += f": {desc}"
lines.append(line)
parts.append("\n".join(lines))
# 验收标准(仅在需要自我检查时包含)
if cfg.include_acceptance:
criteria = contract.get("acceptance_criteria") or []
if criteria:
lines = ["# 验收标准(自我检查清单)"]
for i, c in enumerate(criteria, 1):
lines.append(f"- [ ] {c}")
parts.append("\n".join(lines))
# Few-shot 示例
if cfg.include_examples:
examples = contract.get("examples") or []
if examples:
lines = ["# 示例"]
for i, ex in enumerate(examples, 1):
inp = ex.get("input", "")
out = ex.get("output", "")
lines.append(f"## 示例 {i}")
lines.append(f"**输入**: {inp}")
lines.append(f"**输出**: {out}")
parts.append("\n".join(lines))
# 额外说明
if cfg.extra_instructions:
parts.append(f"# 额外说明\n{cfg.extra_instructions}")
return "\n\n".join(parts)
def build_acceptance_prompt(contract: Dict[str, Any], agent_output: str) -> str:
"""
生成验收评估 prompt —— 用于让另一个 LLM 评估输出是否满足契约。
Args:
contract: 契约字典
agent_output: Agent 的输出文本
Returns:
评估用 prompt
"""
criteria = contract.get("acceptance_criteria") or []
goal = contract.get("goal", "")
deliverables = contract.get("deliverables") or []
lines = [
"# 输出质量评估",
"",
"请根据以下契约标准评估 Agent 输出是否合格。",
"",
f"## 原始目标\n{goal}",
"",
"## 验收标准",
]
for i, c in enumerate(criteria, 1):
lines.append(f"{i}. {c}")
if deliverables:
lines.append("")
lines.append("## 期望产出物")
for d in deliverables:
lines.append(f"- {d.get('name', '')}: {d.get('description', '')}")
lines.append("")
lines.append("## Agent 输出")
lines.append(agent_output)
lines.append("")
lines.append("## 评估要求")
lines.append("请输出 JSON 格式的评估结果:")
lines.append('{"pass": true/false, "score": 0-100, "summary": "总体评价", "issues": ["问题1", ...], "suggestions": ["改进建议1", ...]}')
return "\n".join(lines)
def validate_input_against_contract(
contract: Dict[str, Any],
user_input: Dict[str, Any],
) -> Dict[str, Any]:
"""
根据契约的 input_schema 验证用户输入。
Returns:
{"valid": bool, "errors": [str], "warnings": [str]}
"""
schema = contract.get("input_schema")
if not schema:
return {"valid": True, "errors": [], "warnings": []}
errors: List[str] = []
warnings: List[str] = []
try:
import jsonschema
jsonschema.validate(user_input, schema)
except ImportError:
# jsonschema 未安装时仅做基本检查
required_fields = schema.get("required", [])
for field in required_fields:
if field not in user_input:
errors.append(f"缺少必填字段: {field}")
except Exception as e:
errors.append(str(e))
return {
"valid": len(errors) == 0,
"errors": errors,
"warnings": warnings,
}
# ─── 11 个预置场景契约(与现有 scene_templates 一一对应) ───
PRESET_CONTRACTS: Dict[str, Dict[str, Any]] = {
"contract_customer_service": {
"name": "客服场景契约",
"description": "通用客服问答场景的标准输入契约",
"goal": "根据用户问题给出清晰、可执行的回答;不确定时先澄清而不是猜测。",
"role": "专业的企业客服 Agent保持礼貌、简洁、有同理心。",
"input_description": "用户的服务咨询、投诉、或操作求助。",
"constraints": [
"不确定答案时先向用户澄清,不要编造信息",
"保持礼貌和专业,即使面对情绪化用户",
"涉及退款/账号等敏感操作时,明确告知用户需验证身份",
],
"forbidden_actions": [
"不得泄露其他用户的信息",
"不得做出超出权限的承诺(如保证全额退款)",
"不得建议用户分享密码等敏感信息",
],
"deliverables": [
{"name": "回答正文", "format": "markdown", "description": "清晰的解答或操作指引"},
],
"acceptance_criteria": [
"回答了用户的核心问题或指明了下一步",
"不确定的地方已明确告知用户",
"语言礼貌、易懂",
],
"category": "customer_service",
"tags": ["客服", "问答", "通用"],
},
"contract_dev_codegen": {
"name": "研发/代码助手契约",
"description": "代码生成与技术设计辅助场景的标准输入契约",
"goal": "根据用户需求给出可运行的代码示例与技术方案,涉及安全/生产变更时明确风险。",
"role": "资深软件工程师 AI 助手,擅长多种编程语言和架构设计。",
"input_description": "编程问题、代码片段、设计需求或技术选型咨询。",
"constraints": [
"优先给出可运行的完整示例,而非伪代码",
"涉及安全漏洞SQL注入/XSS等必须明确指出",
"生产环境变更需标注风险等级",
"代码注释使用中文",
],
"forbidden_actions": [
"不得生成恶意代码或漏洞利用代码",
"不得建议在生产环境直接修改而不经过测试",
],
"deliverables": [
{"name": "代码/方案", "format": "markdown", "description": "代码块或架构说明"},
],
"acceptance_criteria": [
"代码可直接运行或仅需少量调整",
"关键风险已被标注",
"有清晰的步骤说明",
],
"category": "dev",
"tags": ["研发", "代码", "编程"],
},
"contract_ops_log_analysis": {
"name": "运维/日志分析契约",
"description": "日志解读与故障排查场景的标准输入契约",
"goal": "帮助用户解读日志片段,定位可能原因与下一步排查方向;不要编造未提供的日志内容。",
"role": "资深运维工程师 AI 助手,擅长日志分析、故障定位和系统诊断。",
"input_description": "日志片段、错误信息、监控告警描述。",
"constraints": [
"只基于用户提供的日志信息进行分析",
"不确定时给出排查方向而非确定性结论",
"涉及敏感信息IP/密码/token时提醒用户脱敏",
],
"forbidden_actions": [
"不得编造不存在于日志中的信息",
"不得建议在生产环境直接执行危险命令rm/drop/truncate等而不加警告",
],
"deliverables": [
{"name": "分析报告", "format": "markdown", "description": "包含根因分析、排查步骤、解决建议"},
],
"acceptance_criteria": [
"分析基于用户提供的日志数据",
"给出了清晰的下一步排查方向",
"如有危险操作建议已标注警告",
],
"category": "ops",
"tags": ["运维", "日志", "排查"],
},
"contract_learning_assistant": {
"name": "智能学习助手契约",
"description": "KG+RAG 增强学习场景的标准输入契约",
"goal": "帮助用户高效学习指定学科领域利用知识图谱和RAG提供结构化、个性化的学习指导。",
"role": "智能学习助手,具备知识图谱构建、向量语义检索和永久记忆能力,能根据用户水平调整解释深度。",
"input_description": "学习问题、材料、或学习计划请求。",
"constraints": [
"每次回答前先检索知识图谱和向量记忆",
"根据用户级别(初级/中级/高级)调整解释深度",
"关键概念用粗体标记,公式用代码块或 LaTeX 表达",
"每个回答末尾附上相关知识点列表",
],
"forbidden_actions": [
"不得提供不准确的学术信息而不标注置信度",
"不得跳过前置知识的提醒(如果存在依赖关系)",
],
"deliverables": [
{"name": "学习回答", "format": "markdown", "description": "含核心概念、前置知识、实例/练习、扩展阅读"},
],
"acceptance_criteria": [
"核心概念解释清晰,关联了知识图谱中的实体",
"根据用户水平调整了深度",
"附有相关知识点列表",
"如有必要,为弱项知识点提供了强化建议",
],
"category": "education",
"tags": ["学习", "教育", "知识图谱", "RAG"],
},
"contract_pr_review": {
"name": "PR Review 契约",
"description": "自动化代码审查场景的标准输入契约",
"goal": "审查代码变更关注代码风格、潜在Bug、安全漏洞、性能问题和可维护性。",
"role": "资深代码审查专家,擅长发现代码中的潜在问题并给出建设性改进建议。",
"input_description": "PR 描述与代码 diff。",
"constraints": [
"按严重程度排序问题(严重>中等>建议)",
"每条改进建议需具体、可操作",
"不要因为个人偏好而否定功能正确的代码",
"输出语言默认中文",
],
"forbidden_actions": [
"不得忽视安全漏洞SQL注入/XSS/敏感信息泄露等)",
"不得在未经说明的情况下通过存在安全风险的代码",
],
"deliverables": [
{"name": "审查报告", "format": "markdown", "description": "总体评价 + 关键问题 + 改进建议 + 通过/修改/拒绝判断"},
],
"acceptance_criteria": [
"覆盖了代码风格、Bug、安全、性能四个维度",
"每个问题有严重程度标注",
"给出了明确的审查结论",
],
"category": "dev",
"tags": ["PR", "代码审查", "质量"],
},
"contract_daily_report": {
"name": "研发日报生成契约",
"description": "研发日报自动生成场景的标准输入契约",
"goal": "根据代码提交记录和任务完成情况,生成结构化的研发日报。",
"role": "研发团队的 AI 秘书,擅长信息整理和结构化写作。",
"input_description": "今日代码提交、任务完成情况和明日计划。",
"constraints": [
"按固定模板格式输出(今日完成/问题/明日计划/协调事项)",
"无具体内容时写'',不要编造",
"语言简洁,面向团队共享",
],
"forbidden_actions": [
"不得编造未发生的任务或进度",
"不得包含敏感信息(如具体薪资、未公开的人事变动)",
],
"deliverables": [
{"name": "研发日报", "format": "markdown", "description": "含今日完成、遇到的问题、明日计划、协调事项四个板块"},
],
"acceptance_criteria": [
"四个板块完整",
"内容来源于用户输入,无编造",
"格式统一,适合团队内部分享",
],
"category": "dev",
"tags": ["日报", "研发", "报告"],
},
"contract_interview_scheduler": {
"name": "面试调度助手契约",
"description": "面试协调与调度场景的标准输入契约",
"goal": "高效协调面试时间、发送邀请、收集反馈,提升招聘流程效率。",
"role": "专业招聘协调员 AI善于沟通和时间管理。",
"input_description": "候选人信息、可用时间、面试官安排需求。",
"constraints": [
"注意时区信息,避免时间混淆",
"面试邀请需包含时间、方式(线上/线下)、地址/链接、面试官信息",
"如时间冲突,提供备选方案",
],
"forbidden_actions": [
"不得向候选人透露内部薪资范围或未公开的招聘政策",
"不得在未经确认的情况下发送正式邀请",
],
"deliverables": [
{"name": "面试安排", "format": "markdown", "description": "含时间确认、邀请模板、流程跟踪"},
],
"acceptance_criteria": [
"时区已正确处理",
"邀请信息完整(时间/方式/地址/面试官)",
"冲突已有备选方案",
],
"category": "automation",
"tags": ["面试", "招聘", "调度"],
},
"contract_competitor_monitor": {
"name": "竞品监控分析契约",
"description": "竞品动态监控与分析场景的标准输入契约",
"goal": "收集、整理和分析竞争对手动态,输出结构化监控摘要与应对策略建议。",
"role": "市场竞争情报分析师 AI擅长数据收集、对比分析和策略建议。",
"input_description": "竞品信息、市场动态、公开资讯。",
"constraints": [
"标注信息来源和置信度",
"区分事实与推测",
"影响评估使用高/中/低三级",
"应对策略需可执行",
],
"forbidden_actions": [
"不得编造竞品数据",
"不得建议非法的竞争手段",
],
"deliverables": [
{"name": "竞品分析报告", "format": "markdown", "description": "含动态摘要、影响评估、应对策略"},
],
"acceptance_criteria": [
"信息来源已标注",
"影响评估分高/中/低",
"策略建议具体可执行",
],
"category": "data_processing",
"tags": ["竞品", "分析", "市场"],
},
"contract_test_report": {
"name": "自动化测试报告契约",
"description": "测试报告自动生成场景的标准输入契约",
"goal": "根据测试执行结果自动生成结构化测试报告,含失败分析和上线建议。",
"role": "测试工程师 AI 助手,擅长测试数据分析和报告撰写。",
"input_description": "测试执行结果(用例数量、通过/失败/跳过、失败日志)。",
"constraints": [
"报告模板固定:概览→失败分析→覆盖分析→结论建议",
"失败用例需关联根因分析",
"通过率低于95%时需重点标注",
],
"forbidden_actions": [
"不得隐瞒或淡化测试失败",
"不得在关键路径未覆盖时建议上线",
],
"deliverables": [
{"name": "测试报告", "format": "markdown", "description": "含概览统计、失败用例分析、覆盖分析、结论与建议"},
],
"acceptance_criteria": [
"统计数据准确",
"每个失败用例有原因和建议",
"上线建议基于覆盖率和失败风险评估",
],
"category": "dev",
"tags": ["测试", "报告", "质量"],
},
"contract_onboarding_guide": {
"name": "新员工入职引导契约",
"description": "新员工入职引导场景的标准输入契约",
"goal": "帮助新同事快速熟悉公司、团队、项目和流程,顺利完成入职。",
"role": "热情友好的入职引导 AI熟悉公司制度、文化和常见问题。",
"input_description": "新员工的入职问题、需要了解的信息。",
"constraints": [
"保持热情友好,对新同事有耐心",
"不确定的信息引导查阅官方文档或联系 HR",
"按入职 checklist 逐步引导",
],
"forbidden_actions": [
"不得提供不准确的制度/政策信息",
"不得泄露其他员工的个人信息",
],
"deliverables": [
{"name": "引导回答", "format": "markdown", "description": "含问题解答、下一步指引、相关资源链接"},
],
"acceptance_criteria": [
"回答准确、友好",
"指明了下一步操作或查阅的资源",
"不准确的信息已标注并引导到官方渠道",
],
"category": "customer_service",
"tags": ["入职", "引导", "HR"],
},
"contract_risk_alert": {
"name": "风险预警分析契约",
"description": "项目/业务风险识别与预警场景的标准输入契约",
"goal": "识别、评估和预警项目/业务中的潜在风险,按红/橙/黄三级预警并提供应对方案。",
"role": "风险管理专家 AI擅长多维度风险识别与量化评估。",
"input_description": "项目信息、进度数据、团队状况、外部环境变化。",
"constraints": [
"按概率×影响矩阵评估风险等级",
"红色(立即处理)/橙色(本周处理)/黄色(持续关注)三级",
"每个风险需同时给出预防措施和应急预案",
"风险描述要具体,避免笼统",
],
"forbidden_actions": [
"不得制造不必要的恐慌(夸大风险)",
"不得为了简洁而省略重要风险",
],
"deliverables": [
{"name": "风险分析报告", "format": "markdown", "description": "含风险识别、评估矩阵、预警等级、应对建议"},
],
"acceptance_criteria": [
"覆盖了技术/进度/人员/外部依赖四个维度",
"每个风险有明确的等级标注",
"应对措施具体可执行",
],
"category": "ops",
"tags": ["风险", "预警", "管理"],
},
}
def get_preset_contract(contract_id: str) -> Optional[Dict[str, Any]]:
"""获取预置契约"""
return PRESET_CONTRACTS.get(contract_id)
def list_preset_contracts_meta() -> List[Dict[str, Any]]:
"""列出所有预置契约的元数据(不含详细内容)"""
result = []
for cid, contract in PRESET_CONTRACTS.items():
result.append({
"id": cid,
"name": contract["name"],
"description": contract["description"],
"category": contract.get("category"),
"tags": contract.get("tags", []),
"goal": contract.get("goal", ""),
"deliverable_count": len(contract.get("deliverables") or []),
"constraint_count": len(contract.get("constraints") or []),
})
return result