# Dify 代理服务后端对接文档 ## 📋 文档说明 本文档描述了前端 Remix 应用如何通过 FastAPI 后端代理访问 Dify AI 服务,以及后端需要实现的接口规范。 **更新时间**: 2025-01-XX **前端修改版本**: v2.0 - JWT 认证版本 --- ## 🎯 架构变更 ### **旧架构** ``` 前端 Remix App → 直接调用 Dify API (使用 API KEY) ``` ### **新架构** ``` 前端 Remix App → FastAPI 后端 /dify 路由 (使用 JWT) → Dify API (使用 API KEY) ``` --- ## 🔐 认证流程说明 ### **前端侧** 1. 用户登录后获得 `frontendJWT` (前端JWT) 2. 所有 Dify 相关请求携带 `Authorization: Bearer {frontendJWT}` 3. JWT 由 OAuth2.0 + 自定义签名生成,包含用户信息 ### **后端侧** 1. 接收前端请求,验证 `frontendJWT` 是否有效 2. JWT 验证通过后,使用配置的 `DIFY_API_KEY` 调用真正的 Dify API 3. 将 Dify API 的响应原样返回给前端 ### **关键配置迁移** 前端原本使用的配置: ```bash # .env (前端配置 - 已不再使用) NEXT_PUBLIC_APP_ID=http://nas.7bm.co:12980/app/46539478-3281-4e98-a445-6da9dc078e95/configuration NEXT_PUBLIC_APP_KEY=app-N3su9tKyMMnqxt2EMgOkVof7 ``` **现在这两个配置应该移到 FastAPI 后端**: ```python # FastAPI 配置 (后端配置) DIFY_API_URL = "http://nas.7bm.co:12980/v1" # Dify API 基础URL DIFY_API_KEY = "app-N3su9tKyMMnqxt2EMgOkVof7" # Dify API Key DIFY_APP_ID = "46539478-3281-4e98-a445-6da9dc078e95" # Dify App ID ``` --- ## 🛣️ API 路由规范 ### **基础路由前缀** ``` http://{FASTAPI_HOST}:{PORT}/dify ``` ### **需要实现的路由列表** | 路由 | 方法 | 说明 | 前端调用频率 | |------|------|------|--------------| | `/dify/parameters` | GET | 获取应用参数 | 初始化时 | | `/dify/conversations` | GET | 获取会话列表 | 每次打开聊天 | | `/dify/messages` | GET | 获取会话消息历史 | 切换会话时 | | `/dify/chat-messages` | POST | 发送聊天消息 | 用户发送消息时 | | `/dify/conversations/{id}/name` | POST | 重命名会话 | 用户重命名时 | | `/dify/conversations/{id}` | DELETE | 删除会话 | 用户删除时 | | `/dify/messages/{id}/feedbacks` | POST | 消息反馈(点赞/点踩) | 用户反馈时 | --- ## 📡 详细接口规范 ### **1. 获取应用参数** #### 前端请求 ```http GET http://{FASTAPI_HOST}/dify/parameters Authorization: Bearer {frontendJWT} ``` #### 后端处理逻辑 ```python @app.get("/dify/parameters") async def get_parameters( authorization: str = Header(None) ): # 1. 验证 JWT jwt_token = authorization.replace("Bearer ", "") user_info = verify_jwt(jwt_token) # 你们的JWT验证函数 if not user_info: raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录") # 2. 调用 Dify API headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" } response = requests.get( f"{DIFY_API_URL}/parameters", headers=headers ) # 3. 返回 Dify 响应 return response.json() ``` #### Dify API 响应示例 ```json { "opening_statement": "你好!我是AI助手...", "suggested_questions": ["问题1", "问题2"], "speech_to_text": { "enabled": false }, "retriever_resource": { "enabled": false }, "annotation_reply": { "enabled": false }, "user_input_form": [], "file_upload": { "image": { "enabled": false, "number_limits": 3, "transfer_methods": ["remote_url", "local_file"] } }, "system_parameters": {} } ``` --- ### **2. 获取会话列表** #### 前端请求 ```http GET http://{FASTAPI_HOST}/dify/conversations?user={user_id}&limit=100&first_id= Authorization: Bearer {frontendJWT} ``` **查询参数**: - `user`: 用户标识(从 JWT 中可提取) - `limit`: 返回数量限制(默认100) - `first_id`: 分页游标(可选) #### 后端处理逻辑 ```python @app.get("/dify/conversations") async def get_conversations( user: str, limit: int = 100, first_id: str = "", authorization: str = Header(None) ): # 1. 验证 JWT jwt_token = authorization.replace("Bearer ", "") user_info = verify_jwt(jwt_token) if not user_info: raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录") # 2. 调用 Dify API headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" } params = { "user": user, "limit": limit, "first_id": first_id } response = requests.get( f"{DIFY_API_URL}/conversations", headers=headers, params=params ) # 3. 返回 Dify 响应 return response.json() ``` #### Dify API 响应示例 ```json { "data": [ { "id": "conv-123456", "name": "会话标题", "inputs": {}, "status": "normal", "introduction": "", "created_at": 1234567890, "updated_at": 1234567890 } ], "has_more": false, "limit": 100 } ``` --- ### **3. 获取会话消息历史** #### 前端请求 ```http GET http://{FASTAPI_HOST}/dify/messages?user={user_id}&conversation_id={conv_id}&limit=20&last_id= Authorization: Bearer {frontendJWT} ``` **查询参数**: - `user`: 用户标识 - `conversation_id`: 会话ID(必填) - `limit`: 返回数量限制(默认20) - `last_id`: 分页游标(可选) #### 后端处理逻辑 ```python @app.get("/dify/messages") async def get_messages( user: str, conversation_id: str, limit: int = 20, last_id: str = "", authorization: str = Header(None) ): # 验证 JWT 和调用 Dify API(同上) # ... params = { "user": user, "conversation_id": conversation_id, "limit": limit, "last_id": last_id } response = requests.get( f"{DIFY_API_URL}/messages", headers=headers, params=params ) return response.json() ``` --- ### **4. 发送聊天消息(流式响应)⭐** #### 前端请求 ```http POST http://{FASTAPI_HOST}/dify/chat-messages Authorization: Bearer {frontendJWT} Content-Type: application/json { "inputs": {}, "query": "用户的问题", "user": "user_app-id:session-id", "response_mode": "streaming", "conversation_id": "conv-123456", "files": [] } ``` **请求体参数**: - `inputs`: 输入变量(对象) - `query`: 用户消息内容(必填) - `user`: 用户标识(必填) - `response_mode`: 响应模式,`"streaming"` 或 `"blocking"` - `conversation_id`: 会话ID(可选,不传则创建新会话) - `files`: 上传的文件列表(可选) #### 后端处理逻辑(重要!) ```python from fastapi import StreamingResponse import httpx @app.post("/dify/chat-messages") async def create_chat_message( request: Request, authorization: str = Header(None) ): # 1. 验证 JWT jwt_token = authorization.replace("Bearer ", "") user_info = verify_jwt(jwt_token) if not user_info: raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录") # 2. 获取请求体 body = await request.json() response_mode = body.get("response_mode", "streaming") # 3. 调用 Dify API headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" } # 4. 根据响应模式处理 if response_mode == "streaming": # 流式响应 - 直接转发流 async def stream_response(): async with httpx.AsyncClient() as client: async with client.stream( "POST", f"{DIFY_API_URL}/chat-messages", headers=headers, json=body, timeout=60.0 ) as response: async for chunk in response.aiter_bytes(): yield chunk return StreamingResponse( stream_response(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive", "Access-Control-Allow-Origin": "*" } ) else: # 非流式响应 response = requests.post( f"{DIFY_API_URL}/chat-messages", headers=headers, json=body ) return response.json() ``` #### Dify API 流式响应格式 ``` data: {"event": "message", "message_id": "msg-123", "conversation_id": "conv-123", "answer": "你好"} data: {"event": "message", "message_id": "msg-123", "conversation_id": "conv-123", "answer": "!"} data: {"event": "message_end", "id": "msg-123", "metadata": {...}} data: {"event": "workflow_finished", "data": {...}} ``` --- ### **5. 重命名会话** #### 前端请求 ```http POST http://{FASTAPI_HOST}/dify/conversations/{conversation_id}/name Authorization: Bearer {frontendJWT} Content-Type: application/json { "name": "新的会话名称", "auto_generate": false, "user": "user_app-id:session-id" } ``` #### 后端处理逻辑 ```python @app.post("/dify/conversations/{conversation_id}/name") async def rename_conversation( conversation_id: str, request: Request, authorization: str = Header(None) ): # 验证 JWT jwt_token = authorization.replace("Bearer ", "") user_info = verify_jwt(jwt_token) if not user_info: raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录") # 获取请求体 body = await request.json() # 调用 Dify API headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" } response = requests.post( f"{DIFY_API_URL}/conversations/{conversation_id}/name", headers=headers, json=body ) return response.json() ``` --- ### **6. 删除会话** #### 前端请求 ```http DELETE http://{FASTAPI_HOST}/dify/conversations/{conversation_id} Authorization: Bearer {frontendJWT} Content-Type: application/json { "user": "user_app-id:session-id" } ``` #### 后端处理逻辑 ```python @app.delete("/dify/conversations/{conversation_id}") async def delete_conversation( conversation_id: str, request: Request, authorization: str = Header(None) ): # 验证 JWT jwt_token = authorization.replace("Bearer ", "") user_info = verify_jwt(jwt_token) if not user_info: raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录") # 获取请求体 body = await request.json() # 调用 Dify API headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" } response = requests.delete( f"{DIFY_API_URL}/conversations/{conversation_id}", headers=headers, json=body ) return response.json() ``` --- ### **7. 消息反馈(点赞/点踩)** #### 前端请求 ```http POST http://{FASTAPI_HOST}/dify/messages/{message_id}/feedbacks Authorization: Bearer {frontendJWT} Content-Type: application/json { "rating": "like", "user": "user_app-id:session-id" } ``` **请求体参数**: - `rating`: 评价类型,`"like"` | `"dislike"` | `null` - `user`: 用户标识 --- ## 🔧 完整后端实现示例(FastAPI) ```python from fastapi import FastAPI, Header, HTTPException, Request from fastapi.responses import StreamingResponse import httpx import requests from typing import Optional app = FastAPI() # 配置 DIFY_API_URL = "http://nas.7bm.co:12980/v1" DIFY_API_KEY = "app-N3su9tKyMMnqxt2EMgOkVof7" DIFY_APP_ID = "46539478-3281-4e98-a445-6da9dc078e95" # JWT 验证函数(使用你们现有的JWT验证逻辑) def verify_jwt(token: str) -> Optional[dict]: """ 验证 JWT Token 返回: 用户信息字典,如果验证失败返回 None """ try: # 这里使用你们现有的 JWT 验证逻辑 # payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) # return payload pass except Exception as e: print(f"JWT 验证失败: {e}") return None # 通用 Dify 请求包装器 async def dify_request( method: str, endpoint: str, authorization: str, params: dict = None, json_data: dict = None, stream: bool = False ): """通用 Dify API 请求处理""" # 验证 JWT jwt_token = authorization.replace("Bearer ", "") if authorization else None if not jwt_token: raise HTTPException(status_code=401, detail="缺少认证令牌") user_info = verify_jwt(jwt_token) if not user_info: raise HTTPException(status_code=401, detail="JWT认证失败,请重新登录") # 构建 Dify 请求头 headers = { "Authorization": f"Bearer {DIFY_API_KEY}", "Content-Type": "application/json" } url = f"{DIFY_API_URL}/{endpoint.lstrip('/')}" if stream: # 流式响应 async def stream_response(): async with httpx.AsyncClient() as client: async with client.stream( method, url, headers=headers, params=params, json=json_data, timeout=60.0 ) as response: async for chunk in response.aiter_bytes(): yield chunk return StreamingResponse( stream_response(), media_type="text/event-stream", headers={ "Cache-Control": "no-cache", "Connection": "keep-alive" } ) else: # 普通响应 response = requests.request( method, url, headers=headers, params=params, json=json_data ) if response.status_code >= 400: raise HTTPException( status_code=response.status_code, detail=response.text ) return response.json() # 路由实现 @app.get("/dify/parameters") async def get_parameters(authorization: str = Header(None)): return await dify_request("GET", "/parameters", authorization) @app.get("/dify/conversations") async def get_conversations( user: str, limit: int = 100, first_id: str = "", authorization: str = Header(None) ): params = {"user": user, "limit": limit, "first_id": first_id} return await dify_request("GET", "/conversations", authorization, params=params) @app.get("/dify/messages") async def get_messages( user: str, conversation_id: str, limit: int = 20, last_id: str = "", authorization: str = Header(None) ): params = { "user": user, "conversation_id": conversation_id, "limit": limit, "last_id": last_id } return await dify_request("GET", "/messages", authorization, params=params) @app.post("/dify/chat-messages") async def create_chat_message( request: Request, authorization: str = Header(None) ): body = await request.json() response_mode = body.get("response_mode", "streaming") stream = (response_mode == "streaming") return await dify_request( "POST", "/chat-messages", authorization, json_data=body, stream=stream ) @app.post("/dify/conversations/{conversation_id}/name") async def rename_conversation( conversation_id: str, request: Request, authorization: str = Header(None) ): body = await request.json() return await dify_request( "POST", f"/conversations/{conversation_id}/name", authorization, json_data=body ) @app.delete("/dify/conversations/{conversation_id}") async def delete_conversation( conversation_id: str, request: Request, authorization: str = Header(None) ): body = await request.json() return await dify_request( "DELETE", f"/conversations/{conversation_id}", authorization, json_data=body ) @app.post("/dify/messages/{message_id}/feedbacks") async def message_feedback( message_id: str, request: Request, authorization: str = Header(None) ): body = await request.json() return await dify_request( "POST", f"/messages/{message_id}/feedbacks", authorization, json_data=body ) ``` --- ## ⚠️ 重要注意事项 ### **1. JWT 验证** - 必须验证 JWT 的签名和过期时间 - JWT 验证失败时返回 **401 状态码** - 错误信息格式:`{"error": "JWT认证失败,请重新登录"}` ### **2. 流式响应处理** - `/chat-messages` 接口必须支持流式响应(SSE) - 响应头必须包含: ``` Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive ``` - 不要缓冲流式响应,直接转发给前端 ### **3. 错误处理** 所有错误响应应遵循统一格式: ```json { "error": "错误描述信息" } ``` 常见错误码: - `400`: 请求参数错误 - `401`: JWT 认证失败 - `403`: 权限不足 - `500`: 服务器内部错误 ### **4. CORS 配置** 如果前后端分离部署,需要配置 CORS: ```python from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173", "http://your-frontend-domain"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ``` ### **5. 日志记录** 建议记录以下信息: - 请求时间、用户ID、接口路径 - JWT 验证结果 - Dify API 调用结果 - 错误信息和堆栈 --- ## 🧪 测试验证 ### **测试工具** 推荐使用以下工具测试: - **Postman** - API 接口测试 - **curl** - 命令行测试 - **httpie** - 友好的命令行工具 ### **测试步骤** #### 1. 获取 JWT Token 从前端登录后,在浏览器开发者工具中获取 `frontendJWT`: ```javascript // 在浏览器控制台执行 console.log(document.cookie); ``` #### 2. 测试基础接口 ```bash # 测试获取参数 curl -X GET "http://localhost:8000/dify/parameters" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" # 测试获取会话列表 curl -X GET "http://localhost:8000/dify/conversations?user=test_user&limit=10" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` #### 3. 测试流式聊天 ```bash # 测试发送消息(流式) curl -X POST "http://localhost:8000/dify/chat-messages" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "inputs": {}, "query": "你好", "user": "test_user", "response_mode": "streaming" }' ``` --- ## 📞 联系方式 如有疑问或需要协助,请联系: - **前端开发**: [你的联系方式] - **文档位置**: `E:\A_Wrok\Porject\docreview\docs\dify-proxy-backend-integration.md` - **前端代码仓库**: [仓库地址] --- ## 📚 附录 ### **相关文档** - [Dify 官方 API 文档](https://docs.dify.ai/v/zh-hans/guides/application-publishing/developing-with-apis) - [JWT 认证实现文档](./JWT_IMPLEMENTATION.md) - [FastAPI 官方文档](https://fastapi.tiangolo.com/zh/) ### **配置清单** #### 后端需要的环境变量 ```bash # Dify API 配置 DIFY_API_URL=http://nas.7bm.co:12980/v1 DIFY_API_KEY=app-N3su9tKyMMnqxt2EMgOkVof7 DIFY_APP_ID=46539478-3281-4e98-a445-6da9dc078e95 # JWT 验证配置(使用现有的) JWT_SECRET=your-jwt-secret JWT_ALGORITHM=HS256 ``` #### 前端配置(已修改) ```bash # API Base URL - 指向 FastAPI 后端 API_BASE_URL=http://172.16.0.55:8000 # Dify 配置已移除,不再使用 # NEXT_PUBLIC_APP_ID=... (已废弃) # NEXT_PUBLIC_APP_KEY=... (已废弃) ``` --- ## ✅ 检查清单 后端实现完成后,请确认: - [ ] 所有 7 个 Dify API 路由已实现 - [ ] JWT 验证逻辑已集成 - [ ] 流式响应(SSE)正常工作 - [ ] 错误处理返回正确的状态码 - [ ] CORS 配置已启用(如需要) - [ ] 日志记录已完善 - [ ] 已使用 Postman/curl 测试所有接口 - [ ] 与前端联调测试通过 --- **祝开发顺利!** 🎉