```
refactor(api): 使用常量替代硬编码字符串 - 在health_check接口中使用ApiResponseKey.STATUS和ApiStatus.OK常量 - 替换硬编码的状态返回值为枚举常量 refactor(core): 配置模块错误信息统一使用常量 - 从constants模块导入ConfigErrorDetail并替换CORS_ORIGINS和LEGACY_ALLOWED_QUERIES的验证错误信息 - 配置类中的默认值使用constants中定义的常量 feat(constants): 添加API响应、安全错误和配置错误常量类 - 新增ApiResponseKey用于API状态键名 - 新增ApiStatus用于API状态值 - 新增SecurityErrorDetail用于安全认证错误详情 - 新增ConfigErrorDetail用于配置验证错误详情 - 添加DEFAULT_MODEL_PROVIDER和DEFAULT_OPENCLAW_ACTION_JSON常量 refactor(security): 安全认证模块使用错误常量 - 将硬编码的安全错误信息替换为SecurityErrorDetail常量 - 包括API密钥、审批密钥和审计密钥的相关错误信息 refactor(ai-agent): AI代理适配器改进错误处理 - 将HTTP状态码替换为FastAPI状态常量 - 添加OpenClaw工具和操作的错误常量 - 修复健康检查和工具调用中的状态码比较逻辑 - 添加AIToolAuditKey用于工具审计键名 feat(ai-agent): 扩展AI代理常量定义 - 新增AIToolAuditKey用于工具审计字段 - 添加OpenClaw相关的错误常量如OPENCLAW_CHAT_PROVIDER_REQUIRED等 - 添加UNSUPPORTED_AI_SKILL_TEMPLATE模板字符串 refactor(approvals): 审批模块常量化重构 - 新增ApprovalPayloadKey用于审批载荷字段 - 添加approval_action函数和APPROVAL_ACTION_SEPARATOR分隔符 - 使用常量替换字面量值 feat(audit): 审计模块新增飞书事件动作类型 - 添加FEISHU_WEBHOOK_EVENT和FEISHU_LONG_CONNECTION_EVENT审计动作 refactor(business): 业务模块全面常量化 - 新增BusinessDomain枚举包含所有业务域 - 添加BusinessResponseKey、BusinessPayloadKey等常量类 - 重构DOMAIN_MODELS为frozenset以提高性能 - 添加normalize_domain等辅助函数用于域标准化 - 使用常量替换路由和业务服务中的硬编码字符串 - 添加业务错误常量和字段验证模板 refactor(feishu): 飞书客户端错误处理优化 - 将HTTP状态码替换为FastAPI标准状态常量 - 改进错误处理的一致性 refactor(approvals): 审批服务使用新常量结构 - 使用ApprovalPayloadKey常量重构载荷字段 - 使用approval_action函数统一动作命名格式 - 优化高风险域判断逻辑 ```
This commit is contained in:
@@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from fastapi import HTTPException
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from app.core.config import Settings, get_settings
|
||||
from app.modules.ai_agent.constants import (
|
||||
@@ -11,7 +11,10 @@ from app.modules.ai_agent.constants import (
|
||||
COMPANY_MANAGEMENT_SYSTEM_INSTRUCTIONS,
|
||||
DIRECT_LLM_API_KEY_MISSING,
|
||||
NOOP_PROVIDER_ANSWER,
|
||||
OPENCLAW_ACTION_NOT_ALLOWED,
|
||||
OPENCLAW_CHAT_PROVIDER_REQUIRED,
|
||||
OPENCLAW_HERMES_PIPELINE,
|
||||
OPENCLAW_TOOL_NOT_ALLOWED,
|
||||
OPENCLAW_TOOL_COMPLETED_ANSWER,
|
||||
UNEXPECTED_HERMES_RESPONSE,
|
||||
AIDefault,
|
||||
@@ -67,12 +70,8 @@ class OpenClawAdapter(AIAdapter):
|
||||
tool = context.get(AIContextKey.OPENCLAW_TOOL)
|
||||
if not tool:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
"OpenClaw Gateway is not configured as a chat provider. "
|
||||
"Provide context.openclaw_tool for /tools/invoke, or use "
|
||||
"MODEL_PROVIDER=hermes/openclaw_hermes for AI answers."
|
||||
),
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=OPENCLAW_CHAT_PROVIDER_REQUIRED,
|
||||
)
|
||||
result = self.invoke_tool(
|
||||
tool=str(tool),
|
||||
@@ -96,7 +95,10 @@ class OpenClawAdapter(AIAdapter):
|
||||
healthz = client.get(f"{base_url}{AIHttpPath.HEALTHZ}", headers=headers)
|
||||
readyz = client.get(f"{base_url}{AIHttpPath.READYZ}", headers=headers)
|
||||
return {
|
||||
AIResponseKey.OK: healthz.status_code < 400 and readyz.status_code < 400,
|
||||
AIResponseKey.OK: (
|
||||
healthz.status_code < status.HTTP_400_BAD_REQUEST
|
||||
and readyz.status_code < status.HTTP_400_BAD_REQUEST
|
||||
),
|
||||
AIResponseKey.BASE_URL: base_url,
|
||||
AIResponseKey.HEALTHZ: _response_payload(healthz),
|
||||
AIResponseKey.READYZ: _response_payload(readyz),
|
||||
@@ -121,8 +123,11 @@ class OpenClawAdapter(AIAdapter):
|
||||
url = f"{self._base_url()}{AIHttpPath.TOOLS_INVOKE}"
|
||||
with httpx.Client(timeout=120, trust_env=False) as client:
|
||||
response = client.post(url, json=payload, headers=self._headers())
|
||||
if response.status_code >= 400:
|
||||
raise HTTPException(status_code=502, detail={AIErrorKey.OPENCLAW: response.text})
|
||||
if response.status_code >= status.HTTP_400_BAD_REQUEST:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={AIErrorKey.OPENCLAW: response.text},
|
||||
)
|
||||
return _response_payload(response)
|
||||
|
||||
def _base_url(self) -> str:
|
||||
@@ -138,9 +143,15 @@ class OpenClawAdapter(AIAdapter):
|
||||
|
||||
def _ensure_tool_allowed(self, tool: str, action: str) -> None:
|
||||
if tool not in set(self.settings.openclaw_allowed_tools):
|
||||
raise HTTPException(status_code=403, detail="OpenClaw tool is not allowed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=OPENCLAW_TOOL_NOT_ALLOWED,
|
||||
)
|
||||
if action not in set(self.settings.openclaw_allowed_actions):
|
||||
raise HTTPException(status_code=403, detail="OpenClaw action is not allowed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=OPENCLAW_ACTION_NOT_ALLOWED,
|
||||
)
|
||||
|
||||
|
||||
class HermesAdapter(AIAdapter):
|
||||
@@ -167,8 +178,11 @@ class HermesAdapter(AIAdapter):
|
||||
}
|
||||
with httpx.Client(timeout=300, trust_env=False) as client:
|
||||
response = client.post(url, json=payload, headers=headers)
|
||||
if response.status_code >= 400:
|
||||
raise HTTPException(status_code=502, detail={AIErrorKey.HERMES: response.text})
|
||||
if response.status_code >= status.HTTP_400_BAD_REQUEST:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={AIErrorKey.HERMES: response.text},
|
||||
)
|
||||
data = _chat_completion_payload(response, AIErrorKey.HERMES)
|
||||
try:
|
||||
answer = data[AIHttpPayloadKey.CHOICES][0][AIHttpPayloadKey.MESSAGE][
|
||||
@@ -176,7 +190,7 @@ class HermesAdapter(AIAdapter):
|
||||
]
|
||||
except (KeyError, IndexError, TypeError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={
|
||||
AIErrorKey.HERMES: UNEXPECTED_HERMES_RESPONSE,
|
||||
AIResponseKey.RAW: data,
|
||||
@@ -196,7 +210,7 @@ class HermesAdapter(AIAdapter):
|
||||
with httpx.Client(timeout=5, trust_env=False) as client:
|
||||
response = client.get(url, headers=headers)
|
||||
return {
|
||||
AIResponseKey.OK: response.status_code < 400,
|
||||
AIResponseKey.OK: response.status_code < status.HTTP_400_BAD_REQUEST,
|
||||
AIResponseKey.BASE_URL: self.settings.hermes_base_url.rstrip("/"),
|
||||
AIResponseKey.HEALTH: _response_payload(response),
|
||||
}
|
||||
@@ -267,7 +281,7 @@ class OpenClawHermesAdapter(AIAdapter):
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={AIErrorKey.OPENCLAW: _error_detail(exc)},
|
||||
) from exc
|
||||
return result
|
||||
@@ -334,7 +348,10 @@ class DirectLLMAdapter(AIAdapter):
|
||||
|
||||
def ask(self, prompt: str, context: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
if not self.settings.direct_llm_api_key:
|
||||
raise HTTPException(status_code=503, detail=DIRECT_LLM_API_KEY_MISSING)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=DIRECT_LLM_API_KEY_MISSING,
|
||||
)
|
||||
url = f"{self.settings.direct_llm_base_url.rstrip('/')}{AIHttpPath.CHAT_COMPLETIONS}"
|
||||
headers = {
|
||||
AIHttpHeader.AUTHORIZATION: AUTHORIZATION_BEARER_TEMPLATE.format(
|
||||
@@ -347,8 +364,11 @@ class DirectLLMAdapter(AIAdapter):
|
||||
}
|
||||
with httpx.Client(timeout=60, trust_env=False) as client:
|
||||
response = client.post(url, json=payload, headers=headers)
|
||||
if response.status_code >= 400:
|
||||
raise HTTPException(status_code=502, detail={AIErrorKey.DIRECT_LLM: response.text})
|
||||
if response.status_code >= status.HTTP_400_BAD_REQUEST:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={AIErrorKey.DIRECT_LLM: response.text},
|
||||
)
|
||||
data = _chat_completion_payload(response, AIErrorKey.DIRECT_LLM)
|
||||
try:
|
||||
answer = data[AIHttpPayloadKey.CHOICES][0][AIHttpPayloadKey.MESSAGE][
|
||||
@@ -356,7 +376,7 @@ class DirectLLMAdapter(AIAdapter):
|
||||
]
|
||||
except (KeyError, IndexError, TypeError) as exc:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={
|
||||
AIErrorKey.DIRECT_LLM: UNEXPECTED_HERMES_RESPONSE,
|
||||
AIResponseKey.RAW: data,
|
||||
@@ -404,7 +424,7 @@ def _chat_completion_payload(response: httpx.Response, error_key: AIErrorKey) ->
|
||||
return response.json()
|
||||
except ValueError as exc:
|
||||
raise HTTPException(
|
||||
status_code=502,
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail={
|
||||
error_key: UNEXPECTED_HERMES_RESPONSE,
|
||||
AIResponseKey.RAW: {AIResponseKey.TEXT: response.text},
|
||||
|
||||
Reference in New Issue
Block a user