- 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>
514 lines
21 KiB
Python
514 lines
21 KiB
Python
"""
|
||
场景契约服务 — 统一 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
|