forked from xiaohei/taiji-AI-PAD
== Heicode integration (~41 endpoints across 5 modules) ==
- §2 ResourceBinding (5 endpoints) — resources.py / resource_grants.py
- §4 NewAPI metadata proxy (4 endpoints) — heicode_proxy.py + heicode_client.py
- §5 Agnet platform stub (12 endpoints, in-memory mock) — agnet_stub.py
- §6 Task orchestration (5 endpoints + 3 extension endpoints) — heicode_tasks.py
6.1-6.5: intent / list / get / answer / messages
6.6-6.8: execution / delivery / audit?tab=... (Slice 8/9/10)
- §7 SSE single channel + approvals (4 endpoints + 5 event types) —
heicode_events.py + event_bus.py
- §7.8.1 internal billing-provider PUT endpoint — auth.py (routes)
== Schema changes ==
- migrations/026 heicode_tasks (orchestration state)
- migrations/027 users.billing_provider (litellm | newapi switch)
- migrations/028 heicode_approvals (high-risk approval queue)
== Register transaction hardening (P0 + P1 + P2) ==
routes/auth.py register():
- Pre-existing P0: failed register returned IntegrityError str verbatim
(leaking SQL params + ~50 plaintext LiteLLM keys per attempt).
Now logs exc_info, returns {code: REGISTER_FAILED, message: ...}.
- Pre-existing P0: model dedupe — two ModelProvider rows with overlapping
supported_models (e.g. taiji/gpt-4o-mini in both taiji and azure providers)
collide on uq_tenant_model. seen_models set deduplicates within the loop.
- New P1: track created_litellm_keys; on any failure call delete_key() for
each — prevents remote orphan keys when DB rollback fires.
- New P1: replace verify_code with peek_verification_code at the start;
only call verify_code (which consumes) after commit succeeds. Failed
registrations no longer burn the user's one-shot code.
- New P2: narrow inner `except (LiteLLMClientError, Exception)` to just
LiteLLMClientError so SQLAlchemy errors bubble to the outer rollback
instead of being silently swallowed into a half-allocated 200 response.
- New P2: same narrowing on outer `except (AgentManagerError, Exception)`.
== Auth middleware ==
- app/auth.py: allow /api/auth/internal/billing-provider and
/api/auth/internal/approvals to bypass user JWT (service-token auth
via HEICODE_INTERNAL_SERVICE_TOKEN, validated in-route).
== Docs ==
- Heicode-接口契约文档.md v2.2 (41 endpoints + SSE schema + 6.6-6.8)
- Heicode-对接进度与待办.md (through §7.14 SSE + 7.8.2 delivery回执)
- Heicode-完整调用流程图.md (sequence + routing diagrams)
- Agent-Manager-Heicode对接需求文档.md
- HEICODE_API_INTEGRATION.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
200 lines
7.2 KiB
Python
200 lines
7.2 KiB
Python
"""
|
||
配置管理
|
||
"""
|
||
|
||
import os
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
from pydantic import AliasChoices, Field, field_validator
|
||
from pydantic_settings import BaseSettings
|
||
|
||
|
||
BASE_DIR = Path(__file__).resolve().parent
|
||
|
||
|
||
class Settings(BaseSettings):
|
||
"""应用配置"""
|
||
|
||
# 应用设置
|
||
app_name: str = "taiji-AI-PAD MCP Server"
|
||
debug: bool = False
|
||
secret_key: str = "zsbgnw" # 从需求文档
|
||
|
||
# 数据库设置(Azure Database for PostgreSQL)
|
||
database_url: str = Field(
|
||
default="",
|
||
validation_alias=AliasChoices("ASYNC_DATABASE_URL", "DATABASE_URL")
|
||
)
|
||
|
||
# Redis设置(Azure Cache for Redis)
|
||
# 注意:Redis 已迁移到 taiji2026 实例
|
||
redis_url: str = "rediss://:PzmWkM6CwfRrJTB1d2xLRxE9pzT7JKgvVAzCaEehmFE=@taiji2026.southeastasia.redis.azure.net:10000/0?ssl_cert_reqs=none"
|
||
redis_max_connections: int = 20
|
||
redis_retry_on_timeout: bool = True
|
||
|
||
# NATS设置
|
||
nats_url: str = os.getenv("NATS_URL", "nats://nats:4222")
|
||
nats_max_reconnect_attempts: int = 10
|
||
|
||
# LiteLLM网关设置
|
||
litellm_url: str = os.getenv("LITELLM_URL", "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io")
|
||
# LLM_BASE_URL - Agent 必传的固定参数(用于平台 Agent 和自定义 Agent)
|
||
llm_base_url: str = os.getenv("LLM_BASE_URL", "https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io")
|
||
# litellm_api_key 优先使用 LITELLM_MASTER_KEY,兼容 LITELLM_API_KEY
|
||
litellm_api_key: str = os.getenv("LITELLM_MASTER_KEY") or os.getenv("LITELLM_API_KEY", "sk-taiji-prod-2026")
|
||
litellm_master_key: str = os.getenv("LITELLM_MASTER_KEY", "sk-taiji-prod-2026")
|
||
|
||
# LiteLLM Key 加密密钥(用于加密存储租户的 API Key)
|
||
# 必须是 32 字节的 base64 编码字符串,用于 Fernet 加密
|
||
litellm_key_encryption_key: str = os.getenv(
|
||
"LITELLM_KEY_ENCRYPTION_KEY",
|
||
"dGFpamktYWktcGFkLWxpdGVsbG0ta2V5LWVuY3J5cHQ=" # 默认密钥,生产环境必须更换
|
||
)
|
||
|
||
# MCP协议设置
|
||
mcp_timeout: int = 30 # 秒
|
||
mcp_max_retries: int = 3
|
||
mcp_retry_delay: float = 1.0 # 秒
|
||
|
||
# Agent设置
|
||
max_agents_per_user: int = 100
|
||
agent_execution_timeout: int = 300 # 秒
|
||
agent_memory_limit: str = "512MB"
|
||
agent_cpu_limit: float = 1.0 # CPU核数
|
||
|
||
# AI Agent Manager API 设置(K8s Pod 管理)
|
||
agent_manager_url: str = os.getenv("AGENT_MANAGER_URL", "http://localhost:8000")
|
||
agent_manager_timeout: float = 30.0 # 秒
|
||
agent_default_cpu_request: str = "100m"
|
||
agent_default_cpu_limit: str = "500m"
|
||
agent_default_memory_request: str = "128Mi"
|
||
agent_default_memory_limit: str = "512Mi"
|
||
agent_k8s_namespace: str = os.getenv("AGENT_K8S_NAMESPACE", "ai-agents")
|
||
|
||
# 工具设置
|
||
max_tools_per_agent: int = 50
|
||
tool_execution_timeout: int = 60 # 秒
|
||
allowed_tool_domains: list = [
|
||
"rapidapi.com",
|
||
"api.openai.com",
|
||
"api.anthropic.com"
|
||
]
|
||
|
||
# 缓存设置
|
||
cache_ttl: int = 3600 # 秒
|
||
cache_max_size: int = 1000
|
||
|
||
# 日志设置
|
||
log_level: str = "INFO"
|
||
log_format: str = "json"
|
||
log_file: Optional[str] = "/app/logs/mcp-server.log"
|
||
|
||
# 安全设置
|
||
cors_origins: list = ["*"]
|
||
jwt_algorithm: str = "HS256"
|
||
jwt_expire_minutes: int = 1440 # 24小时(从需求文档)
|
||
|
||
# 加密设置(从需求文档)
|
||
encryption_key: str = "zsbgnw"
|
||
|
||
# 监控设置
|
||
enable_metrics: bool = True
|
||
metrics_port: int = 8001
|
||
health_check_interval: int = 30 # 秒
|
||
|
||
# 支付设置(可选)
|
||
alipay_app_id: str = os.getenv("ALIPAY_APP_ID", "")
|
||
wechat_pay_app_id: str = os.getenv("WECHAT_PAY_APP_ID", "")
|
||
|
||
# PayPal 支付配置
|
||
paypal_client_id: str = os.getenv("PAYPAL_CLIENT_ID", "")
|
||
paypal_client_secret: str = os.getenv("PAYPAL_CLIENT_SECRET", "")
|
||
paypal_environment: str = os.getenv("PAYPAL_ENVIRONMENT", "sandbox") # sandbox 或 production
|
||
paypal_webhook_id: str = os.getenv("PAYPAL_WEBHOOK_ID", "") # 用于验证 Webhook 签名
|
||
|
||
@property
|
||
def paypal_api_base(self) -> str:
|
||
"""获取 PayPal API 基础 URL"""
|
||
if self.paypal_environment == "production":
|
||
return "https://api-m.paypal.com"
|
||
return "https://api-m.sandbox.paypal.com"
|
||
|
||
# Heicode NewAPI 集成(P4 — Manager 控制台展示模型/余额/用量元数据)
|
||
# 详见 Docs/Heicode-对接进度与待办.md §2.3
|
||
heicode_newapi_base_url: str = os.getenv(
|
||
"HEICODE_NEWAPI_BASE_URL", "https://code.xinghanlab.com"
|
||
)
|
||
# 由 Heicode 团队提供:mcp-server-service 用户的 admin access token
|
||
heicode_newapi_admin_token: str = os.getenv("HEICODE_NEWAPI_SERVICE_TOKEN", "")
|
||
# admin token 对应用户的 user_id(NewAPI UserAuth 强制 New-Api-User 头与 token 用户匹配)
|
||
heicode_newapi_admin_user_id: str = os.getenv("HEICODE_NEWAPI_ADMIN_USER_ID", "")
|
||
# 调用超时(秒)
|
||
heicode_newapi_timeout: int = int(os.getenv("HEICODE_NEWAPI_TIMEOUT", "10"))
|
||
|
||
# Heicode 后端→mcp-server 的反向调用用的共享密钥(§7.8.1 内部 set-provider 端点)
|
||
# Heicode 后端在 syncLocalUserFromAgnet 时调 PUT /api/auth/internal/billing-provider
|
||
# 用 Authorization: Bearer <这个值> 鉴权
|
||
heicode_internal_service_token: str = os.getenv("HEICODE_INTERNAL_SERVICE_TOKEN", "")
|
||
|
||
# 云存储设置(Azure Blob Storage)
|
||
azure_storage_connection_string: str = os.getenv("AZURE_STORAGE_CONNECTION_STRING", "")
|
||
s3_bucket: str = os.getenv("S3_BUCKET", "taiji-ai-exports")
|
||
aws_access_key_id: str = os.getenv("AWS_ACCESS_KEY_ID", "")
|
||
aws_secret_access_key: str = os.getenv("AWS_SECRET_ACCESS_KEY", "")
|
||
|
||
# 开发设置
|
||
reload: bool = False
|
||
workers: int = 4 # 生产环境使用4个worker
|
||
|
||
class Config:
|
||
env_file = ".env"
|
||
env_file_encoding = "utf-8"
|
||
case_sensitive = False
|
||
extra = "ignore"
|
||
|
||
@field_validator("database_url", mode="after")
|
||
@classmethod
|
||
def ensure_async_driver(cls, value: str) -> str:
|
||
if not value:
|
||
raise ValueError("database_url 必须通过环境变量 DATABASE_URL 或 ASYNC_DATABASE_URL 设置")
|
||
if value.startswith("postgresql://") and "+asyncpg" not in value:
|
||
return value.replace("postgresql://", "postgresql+asyncpg://", 1)
|
||
return value
|
||
|
||
@field_validator("redis_url", mode="after")
|
||
@classmethod
|
||
def ensure_redis_env(cls, value: str) -> str:
|
||
# 优先使用环境变量
|
||
return os.getenv("REDIS_URL", value)
|
||
|
||
|
||
class DevelopmentSettings(Settings):
|
||
"""开发环境配置"""
|
||
debug: bool = True
|
||
reload: bool = True
|
||
log_level: str = "DEBUG"
|
||
|
||
|
||
class ProductionSettings(Settings):
|
||
"""生产环境配置"""
|
||
debug: bool = False
|
||
reload: bool = False
|
||
workers: int = 4
|
||
log_level: str = "INFO"
|
||
|
||
|
||
def get_settings() -> Settings:
|
||
"""根据环境变量获取相应的配置"""
|
||
environment = os.getenv("ENVIRONMENT", "development").lower()
|
||
|
||
if environment == "production":
|
||
return ProductionSettings()
|
||
else:
|
||
# 默认使用开发环境配置,不再支持测试环境配置
|
||
return DevelopmentSettings()
|
||
|
||
|
||
# 全局配置实例
|
||
settings = get_settings()
|
||
|