Files
company-ai-platform/app/modules/ai_agent/skills.py
JiuContinent 9cf7c44393 ```
feat: 添加生命周期报告和AI规则管理功能

- 在Dockerfile中添加pillow依赖包用于图像处理
- 实现生命周期报告调度任务,支持日报和周报两种类型
- 新增TASK_RUN_LIFECYCLE任务常量和相关配置选项
- 扩展AI Agent服务以支持用户规则,并在分析时应用规则
- 添加AI用户规则创建、更新和查询接口
- 增加项目生命周期和财务需求分析技能
- 扩展现有模型以支持更完整的业务数据字段
- 实现飞书图片上传功能用于报告展示
```
2026-07-12 17:44:49 +08:00

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]