fix: 中转服务增加 OpenAI->Dify 请求格式转换和响应格式转换
This commit is contained in:
100
digital-human.py
100
digital-human.py
@@ -133,53 +133,101 @@ async def health_check():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
# Dify 响应格式转 OpenAI 兼容格式
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
def dify_to_openai(dify_resp: Dict[str, Any], model: str = "dify") -> 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": model,
|
||||||
|
"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")
|
@app.post("/v1/chat/completions")
|
||||||
async def chat_completions(request: Request):
|
async def chat_completions(request: Request):
|
||||||
"""
|
"""
|
||||||
转发 chat/completions 请求到 Dify。
|
接收 OpenAI 兼容格式请求,转换为 Dify /chat-messages 格式后转发。
|
||||||
支持缓存(可选):相同请求体会命中 Redis 缓存。
|
OpenAI 格式: { model, messages: [{role, content}], stream }
|
||||||
|
Dify 格式: { query, user, response_mode, inputs }
|
||||||
"""
|
"""
|
||||||
# 读取请求体
|
|
||||||
body = await request.json()
|
body = await request.json()
|
||||||
|
|
||||||
# 检查 Redis 缓存
|
# ── 格式转换: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:
|
try:
|
||||||
r = get_redis()
|
cached = get_redis().get(cache_key)
|
||||||
cache_key = get_cache_key(body)
|
|
||||||
cached = r.get(cache_key)
|
|
||||||
if cached:
|
if cached:
|
||||||
logger.info(f"缓存命中: {cache_key}")
|
logger.info(f"缓存命中: {cache_key}")
|
||||||
return json.loads(cached)
|
return dify_to_openai(json.loads(cached), model=body.get("model", "dify"))
|
||||||
except RedisConnectionError:
|
except RedisConnectionError:
|
||||||
logger.warning("Redis 不可用,跳过缓存读取")
|
logger.warning("Redis 不可用,跳过缓存读取")
|
||||||
|
|
||||||
# 构建转发请求头
|
# ── 转发到 Dify ─────────────────────────────────────────
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {DIFY_API_KEY}",
|
"Authorization": f"Bearer {DIFY_API_KEY}",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
# 转发到 Dify(Dify 原生端点 /chat-messages)
|
|
||||||
# 确保 body 包含 inputs 字段(Dify 应用可能要求此字段)
|
|
||||||
if "inputs" not in body:
|
|
||||||
body["inputs"] = {}
|
|
||||||
|
|
||||||
dify_endpoint = f"{DIFY_URL.rstrip('/')}/chat-messages"
|
dify_endpoint = f"{DIFY_URL.rstrip('/')}/chat-messages"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
async with httpx.AsyncClient(timeout=120.0) as client:
|
||||||
resp = await client.post(
|
resp = await client.post(dify_endpoint, json=dify_payload, headers=headers)
|
||||||
dify_endpoint,
|
|
||||||
json=body,
|
|
||||||
headers=headers,
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
result = resp.json()
|
result = resp.json()
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
logger.error(f"Dify 返回错误状态码 {e.response.status_code}: {e.response.text}")
|
logger.error(f"Dify 返回错误 {e.response.status_code}: {e.response.text}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=e.response.status_code,
|
status_code=e.response.status_code,
|
||||||
detail=f"Dify 请求失败: {e.response.text}",
|
detail=f"Dify 请求失败: {e.response.text}",
|
||||||
@@ -188,14 +236,14 @@ async def chat_completions(request: Request):
|
|||||||
logger.error(f"无法连接 Dify 服务: {e}")
|
logger.error(f"无法连接 Dify 服务: {e}")
|
||||||
raise HTTPException(status_code=502, detail=f"无法连接 Dify 服务: {e}")
|
raise HTTPException(status_code=502, detail=f"无法连接 Dify 服务: {e}")
|
||||||
|
|
||||||
# 写入 Redis 缓存(TTL = 300 秒)
|
# ── 写入缓存(存 Dify 原始格式)───────────────────────────
|
||||||
try:
|
try:
|
||||||
r = get_redis()
|
get_redis().setex(cache_key, 300, json.dumps(result))
|
||||||
r.setex(cache_key, 300, json.dumps(result))
|
|
||||||
except RedisConnectionError:
|
except RedisConnectionError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return result
|
# ── 响应转 OpenAI 兼容格式 ─────────────────────────────────
|
||||||
|
return dify_to_openai(result, model=body.get("model", "dify"))
|
||||||
|
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user