Files
taiji-AI-PAD/services/mcp-server/config.py
T
chenchenandClaude Opus 4.7 610fde5d03 feat(mcp-server): Heicode integration + register transaction hardening
== 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>
2026-05-12 15:43:10 +08:00

200 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
配置管理
"""
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()