feat(ai_agent): 完善AI适配器和服务功能

- 添加OpenClaw和Hermes健康检查接口
- 实现OpenClaw工具调用功能
- 重构AI适配器使用常量定义
- 增加AI技能系统支持
- 更新配置文件中的默认模型提供者设置

refactor(scheduler): 使用常量替换硬编码值

- 将硬编码的actor值替换为ActorValue常量
- 将receive_id_type替换为FeishuReceiveIdType枚举

refactor(audit): 统一审计日志常量使用

- 将硬编码的actor、source、risk_level等值替换为对应常量
- 更新审核服务中的状态和操作常量引用

refactor(approvals): 标准化审批模块常量使用

- 将applicant默认值替换为ActorValue.API常量
- 使用ApprovalStatus常量替代硬编码状态值
- 更新审核操作常量引用
```
This commit is contained in:
2026-07-06 00:02:03 +08:00
parent d82116d637
commit aa81fc5321
36 changed files with 2328 additions and 337 deletions

View File

@@ -4,12 +4,24 @@ from typing import Any
from sqlalchemy.orm import Session
from app.core.constants import ActorValue
from app.core.config import get_settings
from app.modules.ai_agent.service import AIService
from app.modules.ai_agent.constants import AIResponseKey
from app.modules.audit.constants import AuditSource
from app.modules.feishu.constants import FeishuCommandKey
from app.modules.feishu.service import FeishuService
from app.modules.reports.service import ReportService
from app.modules.risk.service import RiskService
DAILY_REPORT_KEYWORDS = ("日报", "晨报", "经营日报", "经营晨报")
PROJECT_WEEKLY_KEYWORDS = ("周报", "项目周报")
ATTENDANCE_KEYWORDS = ("打卡", "考勤", "attendance")
RISK_KEYWORDS = ("风险", "预警", "risk")
AI_COMMAND_PREFIXES = ("", "ai ", "AI ", "/ask ")
RISK_TITLE = "风险预警"
DEFAULT_AI_PROMPT = "请说明你能做什么。"
def _parse_content_text(content: Any) -> str:
"""Extract plain command text from a Feishu message content payload."""
@@ -52,25 +64,25 @@ class FeishuCommandService:
return None
sender = event.get("sender") or {}
sender_id = sender.get("sender_id") or {}
actor = sender_id.get("open_id") or sender_id.get("user_id") or "feishu"
actor = sender_id.get("open_id") or sender_id.get("user_id") or ActorValue.FEISHU
return {
"text": text,
"chat_id": message.get("chat_id"),
"actor": actor,
FeishuCommandKey.TEXT: text,
FeishuCommandKey.CHAT_ID: message.get(FeishuCommandKey.CHAT_ID),
FeishuCommandKey.ACTOR: actor,
}
def handle_text(
self,
text: str,
chat_id: str | None = None,
actor: str = "feishu",
actor: str = ActorValue.FEISHU,
auto_reply: bool = True,
) -> dict[str, Any]:
command_text = _clean_command_text(text)
lowered = command_text.lower()
provider_response: dict[str, Any] | None = None
if any(keyword in command_text for keyword in ["日报", "晨报", "经营日报", "经营晨报"]):
if any(keyword in command_text for keyword in DAILY_REPORT_KEYWORDS):
report = ReportService(self.db).daily_brief()
result = {
"command": "daily_brief",
@@ -89,7 +101,7 @@ class FeishuCommandService:
result["provider_response"] = provider_response
return result
if any(keyword in command_text for keyword in ["周报", "项目周报"]):
if any(keyword in command_text for keyword in PROJECT_WEEKLY_KEYWORDS):
report = ReportService(self.db).project_weekly()
result = {
"command": "project_weekly",
@@ -108,7 +120,7 @@ class FeishuCommandService:
result["provider_response"] = provider_response
return result
if any(keyword in command_text for keyword in ["打卡", "考勤", "attendance"]):
if any(keyword in command_text for keyword in ATTENDANCE_KEYWORDS):
report = ReportService(self.db).attendance_summary()
result = {
"command": "attendance_summary",
@@ -127,7 +139,7 @@ class FeishuCommandService:
result["provider_response"] = provider_response
return result
if any(keyword in command_text for keyword in ["风险", "预警", "risk"]):
if any(keyword in command_text for keyword in RISK_KEYWORDS):
summary = RiskService(self.db).summary()
lines = [
f"- 综合风险等级:{summary['risk_level']}",
@@ -142,25 +154,38 @@ class FeishuCommandService:
result = {
"command": "risk_summary",
"reply_type": "card",
"title": "风险预警",
"title": RISK_TITLE,
"content": "\n".join(lines),
"lines": lines,
}
if auto_reply:
provider_response = self._send_card_if_configured(chat_id, "风险预警", lines, actor)
provider_response = self._send_card_if_configured(
chat_id,
RISK_TITLE,
lines,
actor,
)
result["provider_response"] = provider_response
return result
prompt = command_text
for prefix in ["", "ai ", "AI ", "/ask "]:
for prefix in AI_COMMAND_PREFIXES:
if command_text.startswith(prefix):
prompt = command_text[len(prefix) :].strip()
break
if not prompt:
prompt = "请说明你能做什么。"
ai_result = AIService(self.db).ask(prompt, context={}, actor=actor, source="feishu")
content = ai_result["answer"]
is_explicit_ai = lowered.startswith(("ai ", "/ask")) or command_text.startswith("")
prompt = DEFAULT_AI_PROMPT
ai_result = AIService(self.db).ask(
prompt,
context={},
actor=actor,
source=AuditSource.FEISHU,
)
content = ai_result[AIResponseKey.ANSWER]
is_explicit_ai = any(
command_text.startswith(prefix) or lowered.startswith(prefix)
for prefix in AI_COMMAND_PREFIXES
)
result = {
"command": "ai_ask" if is_explicit_ai else "fallback_ai",
"reply_type": "text",