From 9d867520e8ebbf20e5fbac745c276b7272230c0e Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 24 Apr 2026 10:17:38 +0800 Subject: [PATCH] update --- docs/华为云MetaStudio交互数字人配置指南.md | 4 +- old/digital-human2.py | 258 --------------------- 2 files changed, 1 insertion(+), 261 deletions(-) delete mode 100644 old/digital-human2.py diff --git a/docs/华为云MetaStudio交互数字人配置指南.md b/docs/华为云MetaStudio交互数字人配置指南.md index fe34af8..8b7bf01 100644 --- a/docs/华为云MetaStudio交互数字人配置指南.md +++ b/docs/华为云MetaStudio交互数字人配置指南.md @@ -2,8 +2,6 @@ 本文档记录基于华为云 MetaStudio 智能交互 + 第三方大脑(华为云官方称为"第三方大模型")的交互数字人配置方案,智能大脑使用 Dify 平台。 -> **重要公告**:华为云 MetaStudio **3D 数字人产品已停售**;**Flexus 分身数字人仅支持视频制作,不支持智能交互**。本文档智能交互部分仅涵盖 **分身数字人** 相关配置。 - --- ## 目录 @@ -334,7 +332,7 @@ Dify 是交互数字人的"大脑",负责知识库检索和大语言模型对 | 分身数字人 | 基于真人形象采集打造,交互自然,推荐正式客服场景 | | 照片数字人 | 上传照片即可生成,适合快速验证 | -> **重要**:Flexus 分身数字人仅支持视频制作,**不支持智能交互**,详见 [Flexus 分身数字人限制说明](https://support.huaweicloud.com/productdesc-metastudio/metastudio_01_0022.html)。3D 数字人产品已停售,均不在可选范围内。 +> **重要**:Flexus 分身数字人仅支持视频制作,**不支持智能交互**,详见 [Flexus 分身数字人限制说明](https://support.huaweicloud.com/productdesc-metastudio/metastudio_01_0022.html)。 --- diff --git a/old/digital-human2.py b/old/digital-human2.py deleted file mode 100644 index 7c69a1b..0000000 --- a/old/digital-human2.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -数字人对接中转服务 ------------------ -作为客户端与 Dify LLM 平台之间的中间转发层,处理请求路由、 -认证转发及 Redis 缓存,提升响应效率并保护后端 API Key。 - -环境变量: - DIFY_URL - Dify 服务地址,如 https://atwdify.antiwearvalve.com/v1 - DIFY_API_KEY - Dify API 密钥,如 app-PDkSHiHZsZIluoZF7N2OlNZM - REDIS_HOST - Redis 主机地址,默认 localhost - REDIS_PORT - Redis 端口,默认 6379 -""" - -import os -import json -import hashlib -import logging -from contextlib import asynccontextmanager -from typing import Any, Dict, Optional - -import httpx -import redis -from redis.exceptions import ConnectionError as RedisConnectionError -from fastapi import FastAPI, HTTPException, Request -from fastapi.responses import JSONResponse - -# ────────────────────────────────────────────────────────────── -# 日志配置 -# ────────────────────────────────────────────────────────────── -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s [%(levelname)s] %(name)s - %(message)s", -) -logger = logging.getLogger("digital-human") - -# ────────────────────────────────────────────────────────────── -# 应用配置(从环境变量读取) -# ────────────────────────────────────────────────────────────── -DIFY_URL: str = os.getenv("DIFY_URL", "") -DIFY_API_KEY: str = os.getenv("DIFY_API_KEY", "") -REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost") -REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379")) - -# 验证必填配置 -if not DIFY_URL or not DIFY_API_KEY: - logger.warning( - "DIFY_URL 或 DIFY_API_KEY 未配置," - "部分功能可能不可用。请通过环境变量设置。" - ) - -# ────────────────────────────────────────────────────────────── -# Redis 客户端(懒加载) -# ────────────────────────────────────────────────────────────── -_redis_client: Optional[redis.Redis] = None - - -def get_redis() -> redis.Redis: - """获取 Redis 客户端实例(延迟初始化)。""" - global _redis_client - if _redis_client is None: - _redis_client = redis.Redis( - host=REDIS_HOST, - port=REDIS_PORT, - decode_responses=True, - socket_connect_timeout=5, - ) - return _redis_client - - -def get_cache_key(payload: Dict[str, Any]) -> str: - """根据请求体生成 Redis 缓存键。""" - raw = json.dumps(payload, sort_keys=True) - return f"dh:cache:{hashlib.md5(raw.encode()).hexdigest()}" - - -# ────────────────────────────────────────────────────────────── -# FastAPI 应用生命周期 -# ────────────────────────────────────────────────────────────── -@asynccontextmanager -async def lifespan(app: FastAPI): - """应用启动 / 关闭钩子。""" - logger.info("数字人对接服务启动中...") - logger.info(f" Dify URL : {DIFY_URL}") - logger.info(f" Redis : {REDIS_HOST}:{REDIS_PORT}") - - # 尝试连接 Redis(仅记录,不阻塞启动) - try: - get_redis().ping() - logger.info("Redis 连接成功") - except RedisConnectionError as e: - logger.warning(f"Redis 连接失败(服务将继续运行): {e}") - - yield - - logger.info("数字人对接服务已关闭") - - -app = FastAPI( - title="数字人对接中转服务", - version="1.0.0", - description="对接 Dify LLM 平台的数字人应用中转服务", - lifespan=lifespan, -) - - -# ────────────────────────────────────────────────────────────── -# 健康检查 -# ────────────────────────────────────────────────────────────── -@app.get("/") -async def root(): - """服务健康检查端点。""" - return { - "status": "ok", - "service": "digital-human-relay", - "version": "1.0.0", - } - - -@app.get("/health") -async def health_check(): - """详细健康检查(包含 Redis 状态)。""" - redis_ok = False - try: - get_redis().ping() - redis_ok = True - except Exception: - pass - - return { - "status": "healthy" if redis_ok else "degraded", - "redis": "connected" if redis_ok else "disconnected", - "dify_configured": bool(DIFY_URL and DIFY_API_KEY), - } - - -# ────────────────────────────────────────────────────────────── -# Dify 响应格式转 OpenAI 兼容格式 -# ────────────────────────────────────────────────────────────── -def dify_to_openai(dify_resp: Dict[str, Any]) -> Dict[str, Any]: - """ - 将 Dify /chat-messages 的响应转换为 OpenAI /chat/completions 兼容格式。 - Dify 响应示例: { answer: "...", conversation_id: "...", task_id: "..." } - OpenAI 目标格式: { id, model, choices: [{ message: { role: "assistant", content: "..." } }] } - """ - return { - "id": f"chatcmpl-{dify_resp.get('task_id', 'unknown')}", - "object": "chat.completion", - "created": 0, - "model": "dify", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": dify_resp.get("answer", ""), - }, - "finish_reason": "stop", - } - ], - "usage": { - "prompt_tokens": 0, - "completion_tokens": 0, - "total_tokens": 0, - }, - } - - -# ────────────────────────────────────────────────────────────── -# 核心转发接口 -# ────────────────────────────────────────────────────────────── -@app.post("/v1/chat/completions") -async def chat_completions(request: Request): - """ - 接收 OpenAI 兼容格式请求,转换为 Dify /chat-messages 格式后转发。 - OpenAI 格式: { model, messages: [{role, content}], stream } - Dify 格式: { query, user, response_mode, inputs } - """ - body = await request.json() - - # ── 格式转换:OpenAI -> Dify ────────────────────────────── - # 提取用户最新消息 - user_content = "" - for msg in reversed(body.get("messages", [])): - if msg.get("role") == "user": - user_content = msg.get("content", "") - break - - if not user_content: - raise HTTPException(status_code=400, detail="未找到用户消息") - - # 生成或复用 user 标识(用于 Dify 会话上下文) - dify_user = body.get("user", "anonymous") - response_mode = "blocking" if not body.get("stream", False) else "streaming" - - # 构建 Dify 请求体 - dify_payload = { - "query": user_content, - "user": dify_user, - "response_mode": response_mode, - "inputs": body.get("inputs", {}), - } - - # 若应用要求 inputs 字段但为空,补上空对象 - if "inputs" not in dify_payload or dify_payload["inputs"] is None: - dify_payload["inputs"] = {} - - # ── 检查 Redis 缓存(基于 OpenAI 原始请求体)────────────── - cache_key = get_cache_key(body) - try: - cached = get_redis().get(cache_key) - if cached: - logger.info(f"缓存命中: {cache_key}") - return dify_to_openai(json.loads(cached)) - except RedisConnectionError: - logger.warning("Redis 不可用,跳过缓存读取") - - # ── 转发到 Dify ───────────────────────────────────────── - headers = { - "Authorization": f"Bearer {DIFY_API_KEY}", - "Content-Type": "application/json", - } - dify_endpoint = f"{DIFY_URL.rstrip('/')}/chat-messages" - - try: - async with httpx.AsyncClient(timeout=120.0) as client: - resp = await client.post(dify_endpoint, json=dify_payload, headers=headers) - resp.raise_for_status() - result = resp.json() - except httpx.HTTPStatusError as e: - logger.error(f"Dify 返回错误 {e.response.status_code}: {e.response.text}") - raise HTTPException( - status_code=e.response.status_code, - detail=f"Dify 请求失败: {e.response.text}", - ) - except httpx.RequestError as e: - logger.error(f"无法连接 Dify 服务: {e}") - raise HTTPException(status_code=502, detail=f"无法连接 Dify 服务: {e}") - - # ── 写入缓存(存 Dify 原始格式)─────────────────────────── - try: - get_redis().setex(cache_key, 300, json.dumps(result)) - except RedisConnectionError: - pass - - # ── 响应转 OpenAI 兼容格式 ───────────────────────────────── - return dify_to_openai(result) - - -# ────────────────────────────────────────────────────────────── -# 错误处理器 -# ────────────────────────────────────────────────────────────── -@app.exception_handler(Exception) -async def global_exception_handler(request: Request, exc: Exception): - logger.exception(f"未处理的异常: {exc}") - return JSONResponse( - status_code=500, - content={"error": "Internal Server Error", "detail": str(exc)}, - )