Files
ai-video-fullstack/backend/services/brains/dify_brain.py
T

81 lines
2.9 KiB
Python

"""Dify-hosted brain: prompt, workflow, tools, and context live in Dify."""
from __future__ import annotations
from uuid import uuid4
from dify_client import AsyncClient
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 BaseBrain, BrainSpec
from services.brains.dify_llm import DifyLLMService, normalize_api_base
class DifyBrain(BaseBrain):
spec = BrainSpec(
type="dify",
supported_runtime_modes=frozenset({"pipeline"}),
owns_context=False,
)
def __init__(self):
self._user_id = f"ai-video-{uuid4().hex}"
self._client: AsyncClient | None = None
self._llm: DifyLLMService | None = None
self._restored_conversation_id = ""
def _get_client(self, cfg: AssistantConfig) -> AsyncClient:
if self._client is None:
if not cfg.dify_api_key:
raise ValueError("缺少 Dify Agent apiKey")
self._client = AsyncClient(
api_key=cfg.dify_api_key,
api_base=normalize_api_base(cfg.dify_api_url),
)
return self._client
async def greeting(self, cfg: AssistantConfig) -> str:
"""Use Dify's opening statement, with the local greeting as fallback."""
try:
api_base = normalize_api_base(cfg.dify_api_url)
response = await self._get_client(cfg).arequest(
f"{api_base}/parameters",
"GET",
params={"user": self._user_id},
timeout=15.0,
)
opening = str(response.json().get("opening_statement") or "").strip()
return opening or cfg.greeting
except ValueError:
raise
except Exception as exc: # noqa: BLE001 - greeting failure should not block a call
logger.warning(f"Dify 获取开场白失败,回退 cfg.greeting: {exc}")
return cfg.greeting
def build_llm(self, cfg: AssistantConfig, context: LLMContext) -> FrameProcessor:
self._llm = DifyLLMService(
cfg,
client=self._get_client(cfg),
user_id=self._user_id,
conversation_id=self._restored_conversation_id,
)
return self._llm
def restore_text_state(self, snapshot: dict) -> None:
if snapshot.get("type") != "dify":
raise ValueError("Responses runtime state does not belong to Dify")
self._user_id = str(snapshot.get("user_id") or self._user_id)
self._restored_conversation_id = str(snapshot.get("conversation_id") or "")
def text_state_snapshot(self) -> dict:
return {
"type": "dify",
"user_id": self._user_id,
"conversation_id": (
self._llm.conversation_id if self._llm is not None else ""
),
}