Add client-mode FastGPT routing
This commit is contained in:
+32
-10
@@ -10,21 +10,43 @@ class Config:
|
||||
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./test.db")
|
||||
DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "t")
|
||||
|
||||
# FastGPT Configuration
|
||||
FASTGPT_API_KEY = os.getenv("ANALYSIS_AUTH_TOKEN")
|
||||
FASTGPT_BASE_URL = os.getenv("ANALYSIS_SERVICE_URL")
|
||||
FASTGPT_APP_ID = os.getenv("APP_ID")
|
||||
DELETE_SESSION_URL = os.getenv("DELETE_SESSION_URL")
|
||||
DELETE_CHAT_URL = os.getenv("DELETE_CHAT_URL")
|
||||
GET_CHAT_RECORDS_URL = os.getenv("GET_CHAT_RECORDS_URL")
|
||||
# FastGPT Configuration. Direct mode falls back to the legacy environment
|
||||
# variable names so existing deployments keep working unchanged.
|
||||
FASTGPT_DIRECT_API_KEY = (
|
||||
os.getenv("FASTGPT_DIRECT_API_KEY")
|
||||
or os.getenv("ANALYSIS_AUTH_TOKEN")
|
||||
)
|
||||
FASTGPT_DIRECT_BASE_URL = (
|
||||
os.getenv("FASTGPT_DIRECT_BASE_URL")
|
||||
or os.getenv("ANALYSIS_SERVICE_URL")
|
||||
)
|
||||
FASTGPT_DIRECT_APP_ID = (
|
||||
os.getenv("FASTGPT_DIRECT_APP_ID")
|
||||
or os.getenv("APP_ID")
|
||||
)
|
||||
|
||||
FASTGPT_BROWSER_ADDON_API_KEY = os.getenv("FASTGPT_BROWSER_ADDON_API_KEY")
|
||||
FASTGPT_BROWSER_ADDON_BASE_URL = (
|
||||
os.getenv("FASTGPT_BROWSER_ADDON_BASE_URL")
|
||||
or FASTGPT_DIRECT_BASE_URL
|
||||
)
|
||||
FASTGPT_BROWSER_ADDON_APP_ID = os.getenv("FASTGPT_BROWSER_ADDON_APP_ID")
|
||||
|
||||
# Backward-compatible aliases for code outside the mode-aware API layer.
|
||||
FASTGPT_API_KEY = FASTGPT_DIRECT_API_KEY
|
||||
FASTGPT_BASE_URL = FASTGPT_DIRECT_BASE_URL
|
||||
FASTGPT_APP_ID = FASTGPT_DIRECT_APP_ID
|
||||
|
||||
@classmethod
|
||||
def validate(cls):
|
||||
"""Validate required configuration."""
|
||||
required = [
|
||||
("FASTGPT_API_KEY", cls.FASTGPT_API_KEY),
|
||||
("FASTGPT_APP_ID", cls.FASTGPT_APP_ID),
|
||||
("FASTGPT_DIRECT_BASE_URL (or ANALYSIS_SERVICE_URL)", cls.FASTGPT_DIRECT_BASE_URL),
|
||||
("FASTGPT_DIRECT_API_KEY (or ANALYSIS_AUTH_TOKEN)", cls.FASTGPT_DIRECT_API_KEY),
|
||||
("FASTGPT_DIRECT_APP_ID (or APP_ID)", cls.FASTGPT_DIRECT_APP_ID),
|
||||
("FASTGPT_BROWSER_ADDON_API_KEY", cls.FASTGPT_BROWSER_ADDON_API_KEY),
|
||||
("FASTGPT_BROWSER_ADDON_APP_ID", cls.FASTGPT_BROWSER_ADDON_APP_ID),
|
||||
]
|
||||
missing = [name for name, value in required if not value]
|
||||
if missing:
|
||||
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
||||
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
||||
|
||||
+64
-27
@@ -1,39 +1,76 @@
|
||||
"""FastGPT client dependency injection."""
|
||||
from contextlib import asynccontextmanager
|
||||
from contextlib import AsyncExitStack, asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from fastapi import FastAPI
|
||||
from fastgpt_client import AsyncChatClient
|
||||
from .config import Config
|
||||
|
||||
# Global client instance
|
||||
_fastgpt_client: AsyncChatClient | None = None
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FastGPTProfile:
|
||||
"""FastGPT client and app metadata for one client mode."""
|
||||
|
||||
client: AsyncChatClient
|
||||
app_id: str
|
||||
|
||||
|
||||
_fastgpt_profiles: dict[str, FastGPTProfile] = {}
|
||||
|
||||
|
||||
def _profile_settings() -> dict[str, dict[str, str | None]]:
|
||||
return {
|
||||
"direct": {
|
||||
"api_key": Config.FASTGPT_DIRECT_API_KEY,
|
||||
"base_url": Config.FASTGPT_DIRECT_BASE_URL,
|
||||
"app_id": Config.FASTGPT_DIRECT_APP_ID,
|
||||
},
|
||||
"browser_addon": {
|
||||
"api_key": Config.FASTGPT_BROWSER_ADDON_API_KEY,
|
||||
"base_url": Config.FASTGPT_BROWSER_ADDON_BASE_URL,
|
||||
"app_id": Config.FASTGPT_BROWSER_ADDON_APP_ID,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Manage FastGPT client lifecycle."""
|
||||
global _fastgpt_client
|
||||
"""Manage all mode-specific FastGPT clients."""
|
||||
Config.validate()
|
||||
|
||||
# Initialize client
|
||||
_fastgpt_client = AsyncChatClient(
|
||||
api_key=Config.FASTGPT_API_KEY,
|
||||
base_url=Config.FASTGPT_BASE_URL,
|
||||
timeout=60.0,
|
||||
max_retries=3,
|
||||
retry_delay=1.0,
|
||||
enable_logging=Config.DEBUG,
|
||||
)
|
||||
await _fastgpt_client.__aenter__()
|
||||
|
||||
yield
|
||||
|
||||
# Cleanup
|
||||
if _fastgpt_client:
|
||||
await _fastgpt_client.__aexit__(None, None, None)
|
||||
|
||||
async with AsyncExitStack() as stack:
|
||||
for client_mode, settings in _profile_settings().items():
|
||||
client = AsyncChatClient(
|
||||
api_key=settings["api_key"],
|
||||
base_url=settings["base_url"],
|
||||
timeout=60.0,
|
||||
max_retries=3,
|
||||
retry_delay=1.0,
|
||||
enable_logging=Config.DEBUG,
|
||||
)
|
||||
await stack.enter_async_context(client)
|
||||
_fastgpt_profiles[client_mode] = FastGPTProfile(
|
||||
client=client,
|
||||
app_id=settings["app_id"],
|
||||
)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_fastgpt_profiles.clear()
|
||||
|
||||
|
||||
def get_fastgpt_client() -> AsyncChatClient:
|
||||
"""Get the FastGPT client instance."""
|
||||
if _fastgpt_client is None:
|
||||
raise RuntimeError("FastGPT client not initialized")
|
||||
return _fastgpt_client
|
||||
def get_fastgpt_profile(client_mode: str) -> FastGPTProfile:
|
||||
"""Get the initialized FastGPT profile for a client mode."""
|
||||
try:
|
||||
return _fastgpt_profiles[client_mode]
|
||||
except KeyError as exc:
|
||||
if client_mode not in _profile_settings():
|
||||
raise ValueError(f"Unsupported clientMode: {client_mode}") from exc
|
||||
raise RuntimeError(
|
||||
f"FastGPT profile is not initialized for clientMode={client_mode}"
|
||||
) from exc
|
||||
|
||||
|
||||
def get_fastgpt_client(client_mode: str = "direct") -> AsyncChatClient:
|
||||
"""Get a FastGPT client, defaulting to the legacy direct mode."""
|
||||
return get_fastgpt_profile(client_mode).client
|
||||
|
||||
Reference in New Issue
Block a user