"""FastGPT 大脑:外部托管,context/KB/tools 全在 FastGPT 服务端。 cascade-only(realtime 不兼容外部大脑)。每通电话持有一个稳定 chatId: greeting(get_chat_init)与后续每轮推理共用它,保证服务端上下文连续。 """ from __future__ import annotations from typing import Any from uuid import uuid4 from fastgpt_client import AsyncChatClient from loguru import logger from models import AssistantConfig from pipecat.processors.aggregators.llm_context import LLMContext from pipecat.processors.frame_processor import FrameProcessor from services.brains.base import BrainSpec from services.brains.fastgpt_llm import FastGPTLLMService, normalize_base_url def _extract_welcome(payload: Any) -> str: """从 get_chat_init 响应里取开场白(welcomeText),多层兜底。""" if not isinstance(payload, dict): return "" data = payload.get("data") if isinstance(payload.get("data"), dict) else payload app = data.get("app") if isinstance(data.get("app"), dict) else {} chat_config = app.get("chatConfig") if isinstance(app.get("chatConfig"), dict) else {} for value in ( chat_config.get("welcomeText"), app.get("welcomeText"), data.get("welcomeText"), data.get("opener"), app.get("opener"), ): if isinstance(value, str) and value.strip(): return value.strip() return "" class FastGPTBrain: def __init__(self): self.spec = BrainSpec( type="fastgpt", supported_runtime_modes=frozenset({"pipeline"}), owns_context=False, ) self._chat_id = uuid4().hex async def greeting(self, cfg: AssistantConfig) -> str: """优先用 FastGPT 后台配置的开场白;无 app_id 或取不到时回退 cfg.greeting。""" if not cfg.fastgpt_app_id: return cfg.greeting try: client = AsyncChatClient( api_key=cfg.fastgpt_api_key, base_url=normalize_base_url(cfg.fastgpt_api_url), ) response = await client.get_chat_init(cfg.fastgpt_app_id, self._chat_id) welcome = _extract_welcome(response.json()) return welcome or cfg.greeting except Exception as exc: # noqa: BLE001 - 拉取失败不应阻断通话 logger.warning(f"FastGPT get_chat_init 失败,回退 cfg.greeting: {exc}") return cfg.greeting def build_llm(self, cfg: AssistantConfig, context: LLMContext) -> FrameProcessor: return FastGPTLLMService(cfg, chat_id=self._chat_id)