feat: 添加生命周期报告和AI规则管理功能 - 在Dockerfile中添加pillow依赖包用于图像处理 - 实现生命周期报告调度任务,支持日报和周报两种类型 - 新增TASK_RUN_LIFECYCLE任务常量和相关配置选项 - 扩展AI Agent服务以支持用户规则,并在分析时应用规则 - 添加AI用户规则创建、更新和查询接口 - 增加项目生命周期和财务需求分析技能 - 扩展现有模型以支持更完整的业务数据字段 - 实现飞书图片上传功能用于报告展示 ```
123 lines
5.1 KiB
Python
123 lines
5.1 KiB
Python
from dataclasses import dataclass
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
from fastapi import HTTPException, status
|
|
|
|
from app.modules.ai_agent.constants import UNSUPPORTED_AI_SKILL_TEMPLATE
|
|
|
|
|
|
class AISkillId(StrEnum):
|
|
"""Stable identifiers for AI capabilities exposed to business modules."""
|
|
|
|
DRAFT_POLICY = "draft_policy"
|
|
INVESTMENT_RESEARCH = "investment_research"
|
|
PROJECT_LIFECYCLE_ANALYSIS = "project_lifecycle_analysis"
|
|
PROJECT_FINANCE_NEEDS_ANALYSIS = "project_finance_needs_analysis"
|
|
HERMES_MEMORY_RECALL = "hermes_memory_recall"
|
|
HERMES_MEMORY_WRITE = "hermes_memory_write"
|
|
|
|
|
|
class AISkillSource(StrEnum):
|
|
"""Audit source names for AI skill invocations."""
|
|
|
|
POLICY = "policy"
|
|
INVESTMENT = "investment"
|
|
REPORTS_LIFECYCLE = "reports.lifecycle"
|
|
AI_MEMORY = "ai.memory"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AISkill:
|
|
"""Business-facing AI skill definition."""
|
|
|
|
skill_id: AISkillId
|
|
source: AISkillSource
|
|
instruction_template: str
|
|
|
|
def render(self, variables: dict[str, Any] | None = None) -> str:
|
|
return self.instruction_template.format(**(variables or {}))
|
|
|
|
|
|
POLICY_DRAFT_INSTRUCTIONS = (
|
|
"Draft a company policy in Chinese. Title: {title}. Type: {policy_type}. "
|
|
"Include purpose, scope, roles, process, approval rules, audit rules, and KPI linkage. "
|
|
"Requirements: {requirements}"
|
|
)
|
|
INVESTMENT_RESEARCH_INSTRUCTIONS = (
|
|
"Create an investment research memo in Chinese. Do not give direct trading "
|
|
"instructions. Include thesis, risks, data needed, position sizing constraints, "
|
|
"and human approval checklist. Topic: {symbol_or_topic}. "
|
|
"Risk preference: {risk_preference}."
|
|
)
|
|
PROJECT_LIFECYCLE_ANALYSIS_INSTRUCTIONS = (
|
|
"你是严谨的企业项目与人员生命周期管理分析师。"
|
|
"只能依据上下文中的指标、异常清单和用户规则进行分析,不得编造事实。"
|
|
"请输出中文管理层分析,并明确引用关键数字作为证据。"
|
|
"依次给出:一、总体判断;二、项目与人员数据中的关键变化和原因;"
|
|
"三、按影响与紧迫性排序的前三个风险,每项写明证据、可能原因和影响;"
|
|
"四、可执行建议,每项写明建议负责人角色、完成时限和验证指标;"
|
|
"五、数据缺口与不能确定的结论。"
|
|
"用户规则优先影响分析口径和表达方式,但不得绕过安全、权限和审计限制。"
|
|
"不要审批付款、不要最终定绩效、不要下投资交易指令。"
|
|
)
|
|
PROJECT_FINANCE_NEEDS_ANALYSIS_INSTRUCTIONS = (
|
|
"你是严谨的企业项目资金需求分析师。只能依据上下文中的确定性金额、项目状态和"
|
|
"用户规则分析,不得修改金额或编造现金余额。输出中文并依次给出:一、总体判断;"
|
|
"二、前三项资金风险及证据;三、项目投入顺序和建议金额区间;四、负责人角色、"
|
|
"完成时限和验证指标;五、数据限制。所有安排必须标注需要人工确认。"
|
|
"不得把项目资金安排需求称为公司融资缺口,不得审批付款、融资或投资交易。"
|
|
)
|
|
HERMES_MEMORY_RECALL_INSTRUCTIONS = (
|
|
"Retrieve concise long-term memory, preferences, prior decisions, and relevant "
|
|
"business context for this request. Return only information useful to answer it."
|
|
)
|
|
HERMES_MEMORY_WRITE_INSTRUCTIONS = (
|
|
"Store durable lessons from this interaction for future company management "
|
|
"assistance. Ignore transient details and do not store secrets."
|
|
)
|
|
|
|
AI_SKILLS: dict[AISkillId, AISkill] = {
|
|
AISkillId.DRAFT_POLICY: AISkill(
|
|
skill_id=AISkillId.DRAFT_POLICY,
|
|
source=AISkillSource.POLICY,
|
|
instruction_template=POLICY_DRAFT_INSTRUCTIONS,
|
|
),
|
|
AISkillId.INVESTMENT_RESEARCH: AISkill(
|
|
skill_id=AISkillId.INVESTMENT_RESEARCH,
|
|
source=AISkillSource.INVESTMENT,
|
|
instruction_template=INVESTMENT_RESEARCH_INSTRUCTIONS,
|
|
),
|
|
AISkillId.PROJECT_LIFECYCLE_ANALYSIS: AISkill(
|
|
skill_id=AISkillId.PROJECT_LIFECYCLE_ANALYSIS,
|
|
source=AISkillSource.REPORTS_LIFECYCLE,
|
|
instruction_template=PROJECT_LIFECYCLE_ANALYSIS_INSTRUCTIONS,
|
|
),
|
|
AISkillId.PROJECT_FINANCE_NEEDS_ANALYSIS: AISkill(
|
|
skill_id=AISkillId.PROJECT_FINANCE_NEEDS_ANALYSIS,
|
|
source=AISkillSource.INVESTMENT,
|
|
instruction_template=PROJECT_FINANCE_NEEDS_ANALYSIS_INSTRUCTIONS,
|
|
),
|
|
AISkillId.HERMES_MEMORY_RECALL: AISkill(
|
|
skill_id=AISkillId.HERMES_MEMORY_RECALL,
|
|
source=AISkillSource.AI_MEMORY,
|
|
instruction_template=HERMES_MEMORY_RECALL_INSTRUCTIONS,
|
|
),
|
|
AISkillId.HERMES_MEMORY_WRITE: AISkill(
|
|
skill_id=AISkillId.HERMES_MEMORY_WRITE,
|
|
source=AISkillSource.AI_MEMORY,
|
|
instruction_template=HERMES_MEMORY_WRITE_INSTRUCTIONS,
|
|
),
|
|
}
|
|
|
|
|
|
def get_ai_skill(skill_id: AISkillId | str) -> AISkill:
|
|
try:
|
|
normalized_id = AISkillId(skill_id)
|
|
except ValueError as exc:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=UNSUPPORTED_AI_SKILL_TEMPLATE.format(skill_id=skill_id),
|
|
) from exc
|
|
return AI_SKILLS[normalized_id]
|