Upload 7 files
Browse files- .gitignore +39 -0
- Procfile +1 -0
- README.md +52 -11
- app.py +101 -0
- config.ini +11 -0
- dify_api_client.py +424 -0
- requirements.txt +6 -0
.gitignore
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Python
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
*.so
|
6 |
+
.Python
|
7 |
+
env/
|
8 |
+
build/
|
9 |
+
develop-eggs/
|
10 |
+
dist/
|
11 |
+
downloads/
|
12 |
+
eggs/
|
13 |
+
.eggs/
|
14 |
+
lib/
|
15 |
+
lib64/
|
16 |
+
parts/
|
17 |
+
sdist/
|
18 |
+
var/
|
19 |
+
*.egg-info/
|
20 |
+
.installed.cfg
|
21 |
+
*.egg
|
22 |
+
|
23 |
+
# Virtual Environment
|
24 |
+
venv/
|
25 |
+
ENV/
|
26 |
+
|
27 |
+
# Logs
|
28 |
+
logs/
|
29 |
+
*.log
|
30 |
+
|
31 |
+
# IDE
|
32 |
+
.idea/
|
33 |
+
.vscode/
|
34 |
+
*.swp
|
35 |
+
*.swo
|
36 |
+
|
37 |
+
# OS specific
|
38 |
+
.DS_Store
|
39 |
+
Thumbs.db
|
Procfile
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
web: uvicorn app:app --host 0.0.0.0 --port $PORT
|
README.md
CHANGED
@@ -1,11 +1,52 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dify API 中转服务
|
2 |
+
|
3 |
+
这是一个简单的API中转服务,用于将请求转发到Dify API。
|
4 |
+
|
5 |
+
## API文档
|
6 |
+
|
7 |
+
部署后,可以通过 `/docs` 或 `/redoc` 路径访问API文档。
|
8 |
+
|
9 |
+
## 使用方法
|
10 |
+
|
11 |
+
### 健康检查
|
12 |
+
|
13 |
+
```
|
14 |
+
GET /health
|
15 |
+
```
|
16 |
+
|
17 |
+
返回服务状态。
|
18 |
+
|
19 |
+
### 中转API
|
20 |
+
|
21 |
+
```
|
22 |
+
POST /dify_api
|
23 |
+
```
|
24 |
+
|
25 |
+
#### 请求头
|
26 |
+
|
27 |
+
- `dify_url`: Dify API的基础URL (必需)
|
28 |
+
- `dify_key`: Dify API密钥 (必需)
|
29 |
+
|
30 |
+
#### 请求体
|
31 |
+
|
32 |
+
```json
|
33 |
+
{
|
34 |
+
"query": "您的问题或指令",
|
35 |
+
"conversation_id": "可选的会话ID",
|
36 |
+
"user": "用户标识,默认为'user'",
|
37 |
+
"response_mode": "请求模式,'blocking'或'streaming',默认为'blocking'"
|
38 |
+
}
|
39 |
+
```
|
40 |
+
|
41 |
+
#### 示例请求
|
42 |
+
|
43 |
+
```bash
|
44 |
+
curl -X POST "https://your-app-url/dify_api" \
|
45 |
+
-H "Content-Type: application/json" \
|
46 |
+
-H "dify_url: https://api.dify.ai/v1" \
|
47 |
+
-H "dify_key: your-dify-api-key" \
|
48 |
+
-d '{
|
49 |
+
"query": "请告诉我关于人工智能的信息",
|
50 |
+
"response_mode": "blocking"
|
51 |
+
}'
|
52 |
+
```
|
app.py
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python
|
2 |
+
# -*- coding: utf-8 -*-
|
3 |
+
|
4 |
+
"""
|
5 |
+
Dify API 中转服务
|
6 |
+
提供REST API端点,转发请求到Dify API
|
7 |
+
"""
|
8 |
+
|
9 |
+
import os
|
10 |
+
import json
|
11 |
+
import logging
|
12 |
+
from typing import Dict, Any, Optional
|
13 |
+
from fastapi import FastAPI, Header, Request, HTTPException, Response
|
14 |
+
from fastapi.middleware.cors import CORSMiddleware
|
15 |
+
from pydantic import BaseModel
|
16 |
+
import uvicorn
|
17 |
+
import configparser
|
18 |
+
from dify_api_client import DifyAPIClient, setup_logging
|
19 |
+
|
20 |
+
# 创建FastAPI应用
|
21 |
+
app = FastAPI(
|
22 |
+
title="Dify API 中转服务",
|
23 |
+
description="提供REST API端点,转发请求到Dify API",
|
24 |
+
version="1.0.0"
|
25 |
+
)
|
26 |
+
|
27 |
+
# 添加CORS中间件
|
28 |
+
app.add_middleware(
|
29 |
+
CORSMiddleware,
|
30 |
+
allow_origins=["*"], # 允许所有来源
|
31 |
+
allow_credentials=True,
|
32 |
+
allow_methods=["*"], # 允许所有方法
|
33 |
+
allow_headers=["*"], # 允许所有头
|
34 |
+
)
|
35 |
+
|
36 |
+
# 配置日志
|
37 |
+
setup_logging(debug=False)
|
38 |
+
|
39 |
+
# 请求模型
|
40 |
+
class QueryRequest(BaseModel):
|
41 |
+
query: str
|
42 |
+
conversation_id: Optional[str] = None
|
43 |
+
user: Optional[str] = "user"
|
44 |
+
response_mode: Optional[str] = "blocking" # 'blocking' 或 'streaming'
|
45 |
+
|
46 |
+
# 健康检查端点
|
47 |
+
@app.get("/health")
|
48 |
+
def health_check():
|
49 |
+
return {"status": "ok"}
|
50 |
+
|
51 |
+
# Dify API中转端点
|
52 |
+
@app.post("/dify_api")
|
53 |
+
async def dify_api(
|
54 |
+
request: Request,
|
55 |
+
query_request: QueryRequest,
|
56 |
+
dify_url: Optional[str] = Header(None),
|
57 |
+
dify_key: Optional[str] = Header(None)
|
58 |
+
):
|
59 |
+
# 检查必要的头信息
|
60 |
+
if not dify_url:
|
61 |
+
raise HTTPException(status_code=400, detail="缺少必要的请求头: dify_url")
|
62 |
+
if not dify_key:
|
63 |
+
raise HTTPException(status_code=400, detail="缺少必要的请求头: dify_key")
|
64 |
+
|
65 |
+
logging.info(f"接收到API请求,目标URL: {dify_url}")
|
66 |
+
|
67 |
+
try:
|
68 |
+
# 创建Dify客户端
|
69 |
+
client = DifyAPIClient(api_key=dify_key, base_url=dify_url)
|
70 |
+
|
71 |
+
# 根据请求模式处理
|
72 |
+
if query_request.response_mode == "streaming":
|
73 |
+
# 对于流式响应,我们需要使用StreamingResponse
|
74 |
+
# 但由于原始客户端不支持异步流式返回,这里我们先返回完整响应
|
75 |
+
# 在实际生产环境中,应该重构客户端代码以支持异步流式响应
|
76 |
+
response_text = client.send_request_streaming(
|
77 |
+
content=query_request.query,
|
78 |
+
conversation_id=query_request.conversation_id,
|
79 |
+
user=query_request.user
|
80 |
+
)
|
81 |
+
return {"answer": response_text}
|
82 |
+
else:
|
83 |
+
# 阻塞模式
|
84 |
+
response_data = client.send_request_blocking(
|
85 |
+
content=query_request.query,
|
86 |
+
conversation_id=query_request.conversation_id,
|
87 |
+
user=query_request.user
|
88 |
+
)
|
89 |
+
return response_data
|
90 |
+
|
91 |
+
except Exception as e:
|
92 |
+
logging.error(f"处理请求时出错: {str(e)}", exc_info=True)
|
93 |
+
raise HTTPException(status_code=500, detail=f"处理请求时出错: {str(e)}")
|
94 |
+
|
95 |
+
# 主函数
|
96 |
+
def main():
|
97 |
+
# 在本地运行时使用
|
98 |
+
uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
|
99 |
+
|
100 |
+
if __name__ == "__main__":
|
101 |
+
main()
|
config.ini
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[API]
|
2 |
+
# 替换为您的Dify API密钥
|
3 |
+
api_key = app-besh0Rt34xVTMiKWoZeKD1HN
|
4 |
+
# Dify API基础URL
|
5 |
+
base_url = http://114.132.220.127:8088/v1
|
6 |
+
|
7 |
+
[SETTINGS]
|
8 |
+
# 默认请求模式:streaming(流式) 或 blocking(阻塞)
|
9 |
+
default_mode = streaming
|
10 |
+
# 默认用户标识
|
11 |
+
default_user = user
|
dify_api_client.py
ADDED
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python
|
2 |
+
# -*- coding: utf-8 -*-
|
3 |
+
|
4 |
+
"""
|
5 |
+
Dify API 客户端
|
6 |
+
从本地txt文件读取内容,发送到Dify API
|
7 |
+
支持阻塞模式和流式模式
|
8 |
+
"""
|
9 |
+
|
10 |
+
import os
|
11 |
+
import sys
|
12 |
+
import json
|
13 |
+
import argparse
|
14 |
+
import requests
|
15 |
+
import sseclient
|
16 |
+
import logging
|
17 |
+
import configparser
|
18 |
+
from typing import Dict, Any, Optional, Union
|
19 |
+
from datetime import datetime
|
20 |
+
|
21 |
+
|
22 |
+
# 配置日志
|
23 |
+
def setup_logging(debug: bool = False) -> None:
|
24 |
+
"""
|
25 |
+
设置日志记录
|
26 |
+
|
27 |
+
Args:
|
28 |
+
debug: 是否启用调试模式
|
29 |
+
"""
|
30 |
+
log_level = logging.DEBUG if debug else logging.INFO
|
31 |
+
log_format = '%(asctime)s - %(levelname)s - %(message)s'
|
32 |
+
|
33 |
+
# 创建logs目录(如果不存在)
|
34 |
+
os.makedirs('logs', exist_ok=True)
|
35 |
+
|
36 |
+
# 设置文件日志
|
37 |
+
log_file = f'logs/dify_api_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log'
|
38 |
+
|
39 |
+
# 配置日志记录器
|
40 |
+
logging.basicConfig(
|
41 |
+
level=log_level,
|
42 |
+
format=log_format,
|
43 |
+
handlers=[
|
44 |
+
logging.FileHandler(log_file, encoding='utf-8'),
|
45 |
+
logging.StreamHandler()
|
46 |
+
]
|
47 |
+
)
|
48 |
+
|
49 |
+
# 加载配置文件
|
50 |
+
def load_config(config_path='config.ini'):
|
51 |
+
"""
|
52 |
+
从配置文件加载设置
|
53 |
+
|
54 |
+
Args:
|
55 |
+
config_path: 配置文件路径
|
56 |
+
|
57 |
+
Returns:
|
58 |
+
配置对象
|
59 |
+
"""
|
60 |
+
config = configparser.ConfigParser()
|
61 |
+
|
62 |
+
# 检查配置文件是否存在
|
63 |
+
if os.path.exists(config_path):
|
64 |
+
config.read(config_path, encoding='utf-8')
|
65 |
+
logging.debug(f"已加载配置文件: {config_path}")
|
66 |
+
else:
|
67 |
+
logging.warning(f"配置文件不存在: {config_path}, 将使用默认值或命令行参数")
|
68 |
+
|
69 |
+
return config
|
70 |
+
|
71 |
+
|
72 |
+
class DifyAPIClient:
|
73 |
+
"""Dify API 客户端类"""
|
74 |
+
|
75 |
+
DEFAULT_BASE_URL = "http://114.132.220.127:8088/v1"
|
76 |
+
|
77 |
+
def __init__(self, api_key: str, base_url: str = None):
|
78 |
+
"""
|
79 |
+
初始化Dify API客户端
|
80 |
+
|
81 |
+
Args:
|
82 |
+
api_key: API密钥
|
83 |
+
base_url: API基础URL,如果未提供则使用默认值
|
84 |
+
"""
|
85 |
+
self.api_key = api_key
|
86 |
+
self.base_url = base_url or self.DEFAULT_BASE_URL
|
87 |
+
self.headers = {
|
88 |
+
"Authorization": f"Bearer {api_key}",
|
89 |
+
"Content-Type": "application/json"
|
90 |
+
}
|
91 |
+
logging.debug(f"初始化Dify客户端,BASE_URL: {self.base_url}")
|
92 |
+
|
93 |
+
def read_content_from_file(self, file_path: str) -> str:
|
94 |
+
"""
|
95 |
+
从文件中读取内容
|
96 |
+
|
97 |
+
Args:
|
98 |
+
file_path: 文件路径
|
99 |
+
|
100 |
+
Returns:
|
101 |
+
文件内容
|
102 |
+
"""
|
103 |
+
try:
|
104 |
+
logging.info(f"正在读取文件: {file_path}")
|
105 |
+
with open(file_path, 'r', encoding='utf-8') as file:
|
106 |
+
content = file.read().strip()
|
107 |
+
logging.debug(f"成功读取文件,内容长度: {len(content)} 字符")
|
108 |
+
return content
|
109 |
+
except FileNotFoundError:
|
110 |
+
logging.error(f"文件不存在: {file_path}")
|
111 |
+
print(f"错误: 文件 '{file_path}' 不存在")
|
112 |
+
sys.exit(1)
|
113 |
+
except PermissionError:
|
114 |
+
logging.error(f"没有权限访问文件: {file_path}")
|
115 |
+
print(f"错误: 没有权限读取文件 '{file_path}'")
|
116 |
+
sys.exit(1)
|
117 |
+
except Exception as e:
|
118 |
+
logging.error(f"读取文件时出错: {str(e)}", exc_info=True)
|
119 |
+
print(f"读取文件时出错: {str(e)}")
|
120 |
+
sys.exit(1)
|
121 |
+
|
122 |
+
def send_request_blocking(self, content: str, conversation_id: str = None, user: str = "user") -> Dict[str, Any]:
|
123 |
+
"""
|
124 |
+
发送阻塞模式请求
|
125 |
+
|
126 |
+
Args:
|
127 |
+
content: 请求内容
|
128 |
+
conversation_id: 会话ID
|
129 |
+
user: 用户标识
|
130 |
+
|
131 |
+
Returns:
|
132 |
+
API响应
|
133 |
+
"""
|
134 |
+
url = f"{self.base_url}/chat-messages"
|
135 |
+
|
136 |
+
payload = {
|
137 |
+
"query": content,
|
138 |
+
"inputs": {},
|
139 |
+
"response_mode": "blocking",
|
140 |
+
"user": user
|
141 |
+
}
|
142 |
+
|
143 |
+
if conversation_id:
|
144 |
+
payload["conversation_id"] = conversation_id
|
145 |
+
|
146 |
+
logging.info(f"发送阻塞模式请求,用户: {user}")
|
147 |
+
if conversation_id:
|
148 |
+
logging.info(f"会话ID: {conversation_id}")
|
149 |
+
|
150 |
+
logging.debug(f"请求URL: {url}")
|
151 |
+
logging.debug(f"请求负载: {json.dumps(payload, ensure_ascii=False)}")
|
152 |
+
|
153 |
+
try:
|
154 |
+
response = requests.post(url, headers=self.headers, json=payload)
|
155 |
+
response.raise_for_status()
|
156 |
+
response_data = response.json()
|
157 |
+
logging.info("API请求成功")
|
158 |
+
logging.debug(f"API响应: {json.dumps(response_data, ensure_ascii=False)}")
|
159 |
+
return response_data
|
160 |
+
except requests.exceptions.HTTPError as e:
|
161 |
+
logging.error(f"HTTP错误: {e}")
|
162 |
+
if hasattr(response, 'text'):
|
163 |
+
logging.error(f"API响应内容: {response.text}")
|
164 |
+
print(f"HTTP错误: {e}")
|
165 |
+
if hasattr(response, 'text') and response.text:
|
166 |
+
print(f"API响应: {response.text}")
|
167 |
+
sys.exit(1)
|
168 |
+
except requests.exceptions.ConnectionError:
|
169 |
+
logging.error("连接错误: 无法连接到API服务器")
|
170 |
+
print("连接错误: 无法连接到API服务器")
|
171 |
+
sys.exit(1)
|
172 |
+
except requests.exceptions.Timeout:
|
173 |
+
logging.error("超时错误: API请求超时")
|
174 |
+
print("超时错误: API请求超时")
|
175 |
+
sys.exit(1)
|
176 |
+
except requests.exceptions.RequestException as e:
|
177 |
+
logging.error(f"请求错误: {str(e)}", exc_info=True)
|
178 |
+
print(f"请求错误: {e}")
|
179 |
+
sys.exit(1)
|
180 |
+
except json.JSONDecodeError:
|
181 |
+
logging.error("解析错误: API返回的响应不是有效的JSON格式")
|
182 |
+
if hasattr(response, 'text'):
|
183 |
+
logging.error(f"API响应内容: {response.text}")
|
184 |
+
print("解析错误: API返回的响应不是有效的JSON格式")
|
185 |
+
if hasattr(response, 'text'):
|
186 |
+
print(f"API响应: {response.text}")
|
187 |
+
sys.exit(1)
|
188 |
+
except Exception as e:
|
189 |
+
logging.error(f"未知错误: {str(e)}", exc_info=True)
|
190 |
+
print(f"发送请求时发生未知错误: {str(e)}")
|
191 |
+
sys.exit(1)
|
192 |
+
|
193 |
+
def send_request_streaming(self, content: str, conversation_id: str = None, user: str = "user") -> str:
|
194 |
+
"""
|
195 |
+
发送流式模式请求
|
196 |
+
|
197 |
+
Args:
|
198 |
+
content: 请求内容
|
199 |
+
conversation_id: 会话ID
|
200 |
+
user: 用户标识
|
201 |
+
|
202 |
+
Returns:
|
203 |
+
完整的响应文本
|
204 |
+
"""
|
205 |
+
url = f"{self.base_url}/chat-messages"
|
206 |
+
|
207 |
+
payload = {
|
208 |
+
"query": content,
|
209 |
+
"inputs": {},
|
210 |
+
"response_mode": "streaming",
|
211 |
+
"user": user
|
212 |
+
}
|
213 |
+
|
214 |
+
if conversation_id:
|
215 |
+
payload["conversation_id"] = conversation_id
|
216 |
+
|
217 |
+
logging.info(f"发送流式模式请求,用户: {user}")
|
218 |
+
if conversation_id:
|
219 |
+
logging.info(f"会话ID: {conversation_id}")
|
220 |
+
|
221 |
+
logging.debug(f"请求URL: {url}")
|
222 |
+
logging.debug(f"请求负载: {json.dumps(payload, ensure_ascii=False)}")
|
223 |
+
|
224 |
+
try:
|
225 |
+
# 使用流式请求
|
226 |
+
response = requests.post(url, headers=self.headers, json=payload, stream=True)
|
227 |
+
response.raise_for_status()
|
228 |
+
|
229 |
+
# 创建SSE客户端
|
230 |
+
client = sseclient.SSEClient(response)
|
231 |
+
|
232 |
+
# 初始化变量,用于累积回答
|
233 |
+
full_answer = ""
|
234 |
+
conversation_id = None
|
235 |
+
|
236 |
+
# 处理每个事件
|
237 |
+
for event in client.events():
|
238 |
+
try:
|
239 |
+
# 解析事件数据
|
240 |
+
data = json.loads(event.data)
|
241 |
+
event_type = data.get("event")
|
242 |
+
|
243 |
+
# 记录会话ID(如果存在且我们尚未捕获它)
|
244 |
+
if not conversation_id and "conversation_id" in data:
|
245 |
+
conversation_id = data["conversation_id"]
|
246 |
+
logging.debug(f"获取到会话ID: {conversation_id}")
|
247 |
+
|
248 |
+
if event_type == "message":
|
249 |
+
# 提取回答文本并打印
|
250 |
+
answer_chunk = data.get("answer", "")
|
251 |
+
full_answer += answer_chunk
|
252 |
+
print(answer_chunk, end="", flush=True)
|
253 |
+
logging.debug(f"接收到消息事件: {answer_chunk}")
|
254 |
+
|
255 |
+
elif event_type == "message_end":
|
256 |
+
# 消息结束事件
|
257 |
+
print("\n\n--- 消息结束 ---")
|
258 |
+
logging.info("消息流结束")
|
259 |
+
|
260 |
+
# 输出元数据
|
261 |
+
if "metadata" in data:
|
262 |
+
usage = data["metadata"].get("usage", {})
|
263 |
+
if usage:
|
264 |
+
print(f"\n使用情况统计:")
|
265 |
+
print(f" 总Token数: {usage.get('total_tokens', 'N/A')}")
|
266 |
+
print(f" 提示Token数: {usage.get('prompt_tokens', 'N/A')}")
|
267 |
+
print(f" 补全Token数: {usage.get('completion_tokens', 'N/A')}")
|
268 |
+
print(f" 总费用: {usage.get('total_price', 'N/A')} {usage.get('currency', 'USD')}")
|
269 |
+
|
270 |
+
logging.info(f"使用统计 - 总Token数: {usage.get('total_tokens', 'N/A')}, "
|
271 |
+
f"总费用: {usage.get('total_price', 'N/A')} {usage.get('currency', 'USD')}")
|
272 |
+
|
273 |
+
elif event_type == "workflow_started":
|
274 |
+
logging.info("工作流开始")
|
275 |
+
|
276 |
+
elif event_type == "node_started":
|
277 |
+
node_id = data.get("data", {}).get("node_id", "unknown")
|
278 |
+
logging.debug(f"节点开始执行: {node_id}")
|
279 |
+
|
280 |
+
elif event_type == "node_finished":
|
281 |
+
node_id = data.get("data", {}).get("node_id", "unknown")
|
282 |
+
status = data.get("data", {}).get("status", "unknown")
|
283 |
+
logging.debug(f"节点执行完成: {node_id}, 状态: {status}")
|
284 |
+
|
285 |
+
elif event_type == "workflow_finished":
|
286 |
+
status = data.get("data", {}).get("status", "unknown")
|
287 |
+
logging.info(f"工作流完成,状态: {status}")
|
288 |
+
|
289 |
+
elif event_type == "error":
|
290 |
+
# 错误事件
|
291 |
+
error_message = data.get("message", "未知错误")
|
292 |
+
logging.error(f"API错误: {error_message}")
|
293 |
+
print(f"\n错误: {error_message}")
|
294 |
+
break
|
295 |
+
|
296 |
+
except json.JSONDecodeError:
|
297 |
+
logging.error(f"解析错误: 无法解析事件数据: {event.data}")
|
298 |
+
print(f"\n解析错误: 无法解析事件数据: {event.data}")
|
299 |
+
except Exception as e:
|
300 |
+
logging.error(f"处理事件时出错: {str(e)}", exc_info=True)
|
301 |
+
print(f"\n处理事件时出错: {str(e)}")
|
302 |
+
|
303 |
+
# 如果获取到了会话ID,显示给用户
|
304 |
+
if conversation_id:
|
305 |
+
print(f"\n会话ID: {conversation_id}")
|
306 |
+
|
307 |
+
return full_answer
|
308 |
+
|
309 |
+
except requests.exceptions.HTTPError as e:
|
310 |
+
logging.error(f"HTTP错误: {e}")
|
311 |
+
if hasattr(response, 'text'):
|
312 |
+
logging.error(f"API响应内容: {response.text}")
|
313 |
+
print(f"HTTP错误: {e}")
|
314 |
+
if hasattr(response, 'text') and response.text:
|
315 |
+
print(f"API响应: {response.text}")
|
316 |
+
sys.exit(1)
|
317 |
+
except requests.exceptions.ConnectionError:
|
318 |
+
logging.error("连接错误: 无法连接到API服务器")
|
319 |
+
print("连接错误: 无法连接到API服务器")
|
320 |
+
sys.exit(1)
|
321 |
+
except requests.exceptions.Timeout:
|
322 |
+
logging.error("超时错误: API请求超时")
|
323 |
+
print("超时错误: API请求超时")
|
324 |
+
sys.exit(1)
|
325 |
+
except requests.exceptions.RequestException as e:
|
326 |
+
logging.error(f"请求错误: {str(e)}", exc_info=True)
|
327 |
+
print(f"请求错误: {e}")
|
328 |
+
sys.exit(1)
|
329 |
+
except Exception as e:
|
330 |
+
logging.error(f"处理流式响应时出错: {str(e)}", exc_info=True)
|
331 |
+
print(f"处理流式响应时出错: {str(e)}")
|
332 |
+
sys.exit(1)
|
333 |
+
|
334 |
+
|
335 |
+
def main():
|
336 |
+
"""主函数"""
|
337 |
+
# 加载配置文件
|
338 |
+
config = load_config()
|
339 |
+
|
340 |
+
# 从配置文件获取默认值
|
341 |
+
default_api_key = config.get('API', 'api_key', fallback=None)
|
342 |
+
default_mode = config.get('SETTINGS', 'default_mode', fallback='streaming')
|
343 |
+
default_user = config.get('SETTINGS', 'default_user', fallback='user')
|
344 |
+
default_base_url = config.get('API', 'base_url', fallback=None)
|
345 |
+
|
346 |
+
# 设置默认文件
|
347 |
+
default_file = 'example_query.txt'
|
348 |
+
|
349 |
+
parser = argparse.ArgumentParser(description='Dify API 客户端 - 从本地txt文件读取内容发送到API')
|
350 |
+
parser.add_argument('file_path', nargs='?', default=default_file,
|
351 |
+
help=f'本地txt文件路径 (默认: {default_file})')
|
352 |
+
parser.add_argument('--api-key', help='Dify API密钥(如果未指定,将从config.ini读取)')
|
353 |
+
parser.add_argument('--base-url', help='Dify API基础URL(如果未指定,将从config.ini读取)')
|
354 |
+
parser.add_argument('--mode', choices=['blocking', 'streaming'], default=default_mode,
|
355 |
+
help=f'请求模式: blocking(阻塞) 或 streaming(流式), 默认为 {default_mode}')
|
356 |
+
parser.add_argument('--conversation-id', help='会话ID,用于继续已有对话')
|
357 |
+
parser.add_argument('--user', default=default_user, help=f'用户标识, 默认为 {default_user}')
|
358 |
+
parser.add_argument('--debug', action='store_true', help='启用调试日志')
|
359 |
+
|
360 |
+
args = parser.parse_args()
|
361 |
+
|
362 |
+
# 设置日志
|
363 |
+
setup_logging(args.debug)
|
364 |
+
|
365 |
+
logging.info("Dify API客户端启动")
|
366 |
+
logging.info(f"模式: {args.mode}")
|
367 |
+
|
368 |
+
# 确定API密钥
|
369 |
+
api_key = args.api_key or default_api_key
|
370 |
+
|
371 |
+
if api_key is None or api_key == 'your_api_key_here':
|
372 |
+
logging.error("未提供API密钥,请在命令行参数中使用--api-key指定,或在config.ini中配置")
|
373 |
+
print("错误: 未提供API密钥,请使用--api-key参数提供,或在config.ini中配置")
|
374 |
+
sys.exit(1)
|
375 |
+
|
376 |
+
# 确定API基础URL
|
377 |
+
base_url = args.base_url or default_base_url
|
378 |
+
|
379 |
+
try:
|
380 |
+
# 创建客户端实例
|
381 |
+
client = DifyAPIClient(api_key, base_url)
|
382 |
+
|
383 |
+
# 从文件读取内容
|
384 |
+
content = client.read_content_from_file(args.file_path)
|
385 |
+
|
386 |
+
if not content:
|
387 |
+
logging.error("文件内容为空")
|
388 |
+
print("错误: 文件内容为空")
|
389 |
+
sys.exit(1)
|
390 |
+
|
391 |
+
print(f"正在使用 {args.mode} 模式发送请求...")
|
392 |
+
|
393 |
+
# 根据模式发送请求
|
394 |
+
if args.mode == 'blocking':
|
395 |
+
response = client.send_request_blocking(content, args.conversation_id, args.user)
|
396 |
+
print("\n响应:")
|
397 |
+
print(f"{response['answer']}")
|
398 |
+
|
399 |
+
if 'metadata' in response and 'usage' in response['metadata']:
|
400 |
+
usage = response['metadata']['usage']
|
401 |
+
print(f"\n使用情况统计:")
|
402 |
+
print(f" 总Token数: {usage.get('total_tokens', 'N/A')}")
|
403 |
+
print(f" 提示Token数: {usage.get('prompt_tokens', 'N/A')}")
|
404 |
+
print(f" 补全Token数: {usage.get('completion_tokens', 'N/A')}")
|
405 |
+
print(f" 总费用: {usage.get('total_price', 'N/A')} {usage.get('currency', 'USD')}")
|
406 |
+
|
407 |
+
print(f"\n会话ID: {response.get('conversation_id', 'N/A')}")
|
408 |
+
else:
|
409 |
+
client.send_request_streaming(content, args.conversation_id, args.user)
|
410 |
+
|
411 |
+
logging.info("请求执行完毕")
|
412 |
+
|
413 |
+
except KeyboardInterrupt:
|
414 |
+
logging.info("用户中断操作")
|
415 |
+
print("\n操作被用户中断")
|
416 |
+
sys.exit(0)
|
417 |
+
except Exception as e:
|
418 |
+
logging.error(f"未捕获的异常: {str(e)}", exc_info=True)
|
419 |
+
print(f"发生未知错误: {str(e)}")
|
420 |
+
sys.exit(1)
|
421 |
+
|
422 |
+
|
423 |
+
if __name__ == "__main__":
|
424 |
+
main()
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
requests>=2.28.0
|
2 |
+
sseclient-py>=1.7.2
|
3 |
+
fastapi>=0.95.0
|
4 |
+
uvicorn>=0.21.0
|
5 |
+
python-multipart>=0.0.6
|
6 |
+
pydantic>=1.10.7
|