Upload 23 files
Browse files- .gitattributes +3 -0
- images/GitHub_README_cover.png +3 -0
- images/demo.png +3 -0
- images/models.png +0 -0
- images/wechat.png +3 -0
- sdks/README.md +25 -0
- sdks/nodejs-client/.gitignore +48 -0
- sdks/nodejs-client/README.md +63 -0
- sdks/nodejs-client/babel.config.json +5 -0
- sdks/nodejs-client/index.d.ts +91 -0
- sdks/nodejs-client/index.js +360 -0
- sdks/nodejs-client/index.test.js +65 -0
- sdks/nodejs-client/package.json +35 -0
- sdks/php-client/README.md +84 -0
- sdks/php-client/dify-client.php +216 -0
- sdks/python-client/LICENSE +21 -0
- sdks/python-client/MANIFEST.in +1 -0
- sdks/python-client/README.md +185 -0
- sdks/python-client/build.sh +9 -0
- sdks/python-client/dify_client/__init__.py +1 -0
- sdks/python-client/dify_client/client.py +459 -0
- sdks/python-client/setup.py +26 -0
- sdks/python-client/tests/__init__.py +0 -0
- sdks/python-client/tests/test_client.py +274 -0
.gitattributes
CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
images/demo.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
images/GitHub_README_cover.png filter=lfs diff=lfs merge=lfs -text
|
38 |
+
images/wechat.png filter=lfs diff=lfs merge=lfs -text
|
images/GitHub_README_cover.png
ADDED
![]() |
Git LFS Details
|
images/demo.png
ADDED
![]() |
Git LFS Details
|
images/models.png
ADDED
![]() |
images/wechat.png
ADDED
![]() |
Git LFS Details
|
sdks/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# SDK
|
2 |
+
|
3 |
+
## Java
|
4 |
+
|
5 |
+
https://github.com/langgenius/java-client/
|
6 |
+
|
7 |
+
## Go
|
8 |
+
|
9 |
+
https://github.com/langgenius/dify-sdk-go
|
10 |
+
|
11 |
+
## Ruby
|
12 |
+
|
13 |
+
https://github.com/langgenius/ruby-sdk
|
14 |
+
|
15 |
+
## Python
|
16 |
+
|
17 |
+
TODO move to another place
|
18 |
+
|
19 |
+
## PHP
|
20 |
+
|
21 |
+
TODO move to another place
|
22 |
+
|
23 |
+
## Node.js
|
24 |
+
|
25 |
+
TODO move to another place
|
sdks/nodejs-client/.gitignore
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.js
|
7 |
+
|
8 |
+
# testing
|
9 |
+
/coverage
|
10 |
+
|
11 |
+
# next.js
|
12 |
+
/.next/
|
13 |
+
/out/
|
14 |
+
|
15 |
+
# production
|
16 |
+
/build
|
17 |
+
|
18 |
+
# misc
|
19 |
+
.DS_Store
|
20 |
+
*.pem
|
21 |
+
|
22 |
+
# debug
|
23 |
+
npm-debug.log*
|
24 |
+
yarn-debug.log*
|
25 |
+
yarn-error.log*
|
26 |
+
.pnpm-debug.log*
|
27 |
+
|
28 |
+
# local env files
|
29 |
+
.env*.local
|
30 |
+
|
31 |
+
# vercel
|
32 |
+
.vercel
|
33 |
+
|
34 |
+
# typescript
|
35 |
+
*.tsbuildinfo
|
36 |
+
next-env.d.ts
|
37 |
+
|
38 |
+
# npm
|
39 |
+
package-lock.json
|
40 |
+
|
41 |
+
# yarn
|
42 |
+
.pnp.cjs
|
43 |
+
.pnp.loader.mjs
|
44 |
+
.yarn/
|
45 |
+
.yarnrc.yml
|
46 |
+
|
47 |
+
# pmpm
|
48 |
+
pnpm-lock.yaml
|
sdks/nodejs-client/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dify Node.js SDK
|
2 |
+
This is the Node.js SDK for the Dify API, which allows you to easily integrate Dify into your Node.js applications.
|
3 |
+
|
4 |
+
## Install
|
5 |
+
```bash
|
6 |
+
npm install dify-client
|
7 |
+
```
|
8 |
+
|
9 |
+
## Usage
|
10 |
+
After installing the SDK, you can use it in your project like this:
|
11 |
+
|
12 |
+
```js
|
13 |
+
import { DifyClient, ChatClient, CompletionClient } from 'dify-client'
|
14 |
+
|
15 |
+
const API_KEY = 'your-api-key-here'
|
16 |
+
const user = `random-user-id`
|
17 |
+
const query = 'Please tell me a short story in 10 words or less.'
|
18 |
+
const remote_url_files = [{
|
19 |
+
type: 'image',
|
20 |
+
transfer_method: 'remote_url',
|
21 |
+
url: 'your_url_address'
|
22 |
+
}]
|
23 |
+
|
24 |
+
// Create a completion client
|
25 |
+
const completionClient = new CompletionClient(API_KEY)
|
26 |
+
// Create a completion message
|
27 |
+
completionClient.createCompletionMessage({'query': query}, user)
|
28 |
+
// Create a completion message with vision model
|
29 |
+
completionClient.createCompletionMessage({'query': 'Describe the picture.'}, user, false, remote_url_files)
|
30 |
+
|
31 |
+
// Create a chat client
|
32 |
+
const chatClient = new ChatClient(API_KEY)
|
33 |
+
// Create a chat message in stream mode
|
34 |
+
const response = await chatClient.createChatMessage({}, query, user, true, null)
|
35 |
+
const stream = response.data;
|
36 |
+
stream.on('data', data => {
|
37 |
+
console.log(data);
|
38 |
+
});
|
39 |
+
stream.on('end', () => {
|
40 |
+
console.log('stream done');
|
41 |
+
});
|
42 |
+
// Create a chat message with vision model
|
43 |
+
chatClient.createChatMessage({}, 'Describe the picture.', user, false, null, remote_url_files)
|
44 |
+
// Fetch conversations
|
45 |
+
chatClient.getConversations(user)
|
46 |
+
// Fetch conversation messages
|
47 |
+
chatClient.getConversationMessages(conversationId, user)
|
48 |
+
// Rename conversation
|
49 |
+
chatClient.renameConversation(conversationId, name, user)
|
50 |
+
|
51 |
+
|
52 |
+
const client = new DifyClient(API_KEY)
|
53 |
+
// Fetch application parameters
|
54 |
+
client.getApplicationParameters(user)
|
55 |
+
// Provide feedback for a message
|
56 |
+
client.messageFeedback(messageId, rating, user)
|
57 |
+
|
58 |
+
```
|
59 |
+
|
60 |
+
Replace 'your-api-key-here' with your actual Dify API key.Replace 'your-app-id-here' with your actual Dify APP ID.
|
61 |
+
|
62 |
+
## License
|
63 |
+
This SDK is released under the MIT License.
|
sdks/nodejs-client/babel.config.json
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"presets": [
|
3 |
+
"@babel/preset-env"
|
4 |
+
]
|
5 |
+
}
|
sdks/nodejs-client/index.d.ts
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// Types.d.ts
|
2 |
+
export const BASE_URL: string;
|
3 |
+
|
4 |
+
export type RequestMethods = 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
5 |
+
|
6 |
+
interface Params {
|
7 |
+
[key: string]: any;
|
8 |
+
}
|
9 |
+
|
10 |
+
interface HeaderParams {
|
11 |
+
[key: string]: string;
|
12 |
+
}
|
13 |
+
|
14 |
+
interface User {
|
15 |
+
}
|
16 |
+
|
17 |
+
export declare class DifyClient {
|
18 |
+
constructor(apiKey: string, baseUrl?: string);
|
19 |
+
|
20 |
+
updateApiKey(apiKey: string): void;
|
21 |
+
|
22 |
+
sendRequest(
|
23 |
+
method: RequestMethods,
|
24 |
+
endpoint: string,
|
25 |
+
data?: any,
|
26 |
+
params?: Params,
|
27 |
+
stream?: boolean,
|
28 |
+
headerParams?: HeaderParams
|
29 |
+
): Promise<any>;
|
30 |
+
|
31 |
+
messageFeedback(message_id: string, rating: number, user: User): Promise<any>;
|
32 |
+
|
33 |
+
getApplicationParameters(user: User): Promise<any>;
|
34 |
+
|
35 |
+
fileUpload(data: FormData): Promise<any>;
|
36 |
+
|
37 |
+
textToAudio(text: string ,user: string, streaming?: boolean): Promise<any>;
|
38 |
+
|
39 |
+
getMeta(user: User): Promise<any>;
|
40 |
+
}
|
41 |
+
|
42 |
+
export declare class CompletionClient extends DifyClient {
|
43 |
+
createCompletionMessage(
|
44 |
+
inputs: any,
|
45 |
+
user: User,
|
46 |
+
stream?: boolean,
|
47 |
+
files?: File[] | null
|
48 |
+
): Promise<any>;
|
49 |
+
}
|
50 |
+
|
51 |
+
export declare class ChatClient extends DifyClient {
|
52 |
+
createChatMessage(
|
53 |
+
inputs: any,
|
54 |
+
query: string,
|
55 |
+
user: User,
|
56 |
+
stream?: boolean,
|
57 |
+
conversation_id?: string | null,
|
58 |
+
files?: File[] | null
|
59 |
+
): Promise<any>;
|
60 |
+
|
61 |
+
getSuggested(message_id: string, user: User): Promise<any>;
|
62 |
+
|
63 |
+
stopMessage(task_id: string, user: User) : Promise<any>;
|
64 |
+
|
65 |
+
|
66 |
+
getConversations(
|
67 |
+
user: User,
|
68 |
+
first_id?: string | null,
|
69 |
+
limit?: number | null,
|
70 |
+
pinned?: boolean | null
|
71 |
+
): Promise<any>;
|
72 |
+
|
73 |
+
getConversationMessages(
|
74 |
+
user: User,
|
75 |
+
conversation_id?: string,
|
76 |
+
first_id?: string | null,
|
77 |
+
limit?: number | null
|
78 |
+
): Promise<any>;
|
79 |
+
|
80 |
+
renameConversation(conversation_id: string, name: string, user: User,auto_generate:boolean): Promise<any>;
|
81 |
+
|
82 |
+
deleteConversation(conversation_id: string, user: User): Promise<any>;
|
83 |
+
|
84 |
+
audioToText(data: FormData): Promise<any>;
|
85 |
+
}
|
86 |
+
|
87 |
+
export declare class WorkflowClient extends DifyClient {
|
88 |
+
run(inputs: any, user: User, stream?: boolean,): Promise<any>;
|
89 |
+
|
90 |
+
stop(task_id: string, user: User): Promise<any>;
|
91 |
+
}
|
sdks/nodejs-client/index.js
ADDED
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import axios from "axios";
|
2 |
+
export const BASE_URL = "https://api.dify.ai/v1";
|
3 |
+
|
4 |
+
export const routes = {
|
5 |
+
// app's
|
6 |
+
feedback: {
|
7 |
+
method: "POST",
|
8 |
+
url: (message_id) => `/messages/${message_id}/feedbacks`,
|
9 |
+
},
|
10 |
+
application: {
|
11 |
+
method: "GET",
|
12 |
+
url: () => `/parameters`,
|
13 |
+
},
|
14 |
+
fileUpload: {
|
15 |
+
method: "POST",
|
16 |
+
url: () => `/files/upload`,
|
17 |
+
},
|
18 |
+
textToAudio: {
|
19 |
+
method: "POST",
|
20 |
+
url: () => `/text-to-audio`,
|
21 |
+
},
|
22 |
+
getMeta: {
|
23 |
+
method: "GET",
|
24 |
+
url: () => `/meta`,
|
25 |
+
},
|
26 |
+
|
27 |
+
// completion's
|
28 |
+
createCompletionMessage: {
|
29 |
+
method: "POST",
|
30 |
+
url: () => `/completion-messages`,
|
31 |
+
},
|
32 |
+
|
33 |
+
// chat's
|
34 |
+
createChatMessage: {
|
35 |
+
method: "POST",
|
36 |
+
url: () => `/chat-messages`,
|
37 |
+
},
|
38 |
+
getSuggested:{
|
39 |
+
method: "GET",
|
40 |
+
url: (message_id) => `/messages/${message_id}/suggested`,
|
41 |
+
},
|
42 |
+
stopChatMessage: {
|
43 |
+
method: "POST",
|
44 |
+
url: (task_id) => `/chat-messages/${task_id}/stop`,
|
45 |
+
},
|
46 |
+
getConversations: {
|
47 |
+
method: "GET",
|
48 |
+
url: () => `/conversations`,
|
49 |
+
},
|
50 |
+
getConversationMessages: {
|
51 |
+
method: "GET",
|
52 |
+
url: () => `/messages`,
|
53 |
+
},
|
54 |
+
renameConversation: {
|
55 |
+
method: "POST",
|
56 |
+
url: (conversation_id) => `/conversations/${conversation_id}/name`,
|
57 |
+
},
|
58 |
+
deleteConversation: {
|
59 |
+
method: "DELETE",
|
60 |
+
url: (conversation_id) => `/conversations/${conversation_id}`,
|
61 |
+
},
|
62 |
+
audioToText: {
|
63 |
+
method: "POST",
|
64 |
+
url: () => `/audio-to-text`,
|
65 |
+
},
|
66 |
+
|
67 |
+
// workflow‘s
|
68 |
+
runWorkflow: {
|
69 |
+
method: "POST",
|
70 |
+
url: () => `/workflows/run`,
|
71 |
+
},
|
72 |
+
stopWorkflow: {
|
73 |
+
method: "POST",
|
74 |
+
url: (task_id) => `/workflows/${task_id}/stop`,
|
75 |
+
}
|
76 |
+
|
77 |
+
};
|
78 |
+
|
79 |
+
export class DifyClient {
|
80 |
+
constructor(apiKey, baseUrl = BASE_URL) {
|
81 |
+
this.apiKey = apiKey;
|
82 |
+
this.baseUrl = baseUrl;
|
83 |
+
}
|
84 |
+
|
85 |
+
updateApiKey(apiKey) {
|
86 |
+
this.apiKey = apiKey;
|
87 |
+
}
|
88 |
+
|
89 |
+
async sendRequest(
|
90 |
+
method,
|
91 |
+
endpoint,
|
92 |
+
data = null,
|
93 |
+
params = null,
|
94 |
+
stream = false,
|
95 |
+
headerParams = {}
|
96 |
+
) {
|
97 |
+
const headers = {
|
98 |
+
...{
|
99 |
+
Authorization: `Bearer ${this.apiKey}`,
|
100 |
+
"Content-Type": "application/json",
|
101 |
+
},
|
102 |
+
...headerParams
|
103 |
+
};
|
104 |
+
|
105 |
+
const url = `${this.baseUrl}${endpoint}`;
|
106 |
+
let response;
|
107 |
+
if (stream) {
|
108 |
+
response = await axios({
|
109 |
+
method,
|
110 |
+
url,
|
111 |
+
data,
|
112 |
+
params,
|
113 |
+
headers,
|
114 |
+
responseType: "stream",
|
115 |
+
});
|
116 |
+
} else {
|
117 |
+
response = await axios({
|
118 |
+
method,
|
119 |
+
url,
|
120 |
+
...(method !== "GET" && { data }),
|
121 |
+
params,
|
122 |
+
headers,
|
123 |
+
responseType: "json",
|
124 |
+
});
|
125 |
+
}
|
126 |
+
|
127 |
+
return response;
|
128 |
+
}
|
129 |
+
|
130 |
+
messageFeedback(message_id, rating, user) {
|
131 |
+
const data = {
|
132 |
+
rating,
|
133 |
+
user,
|
134 |
+
};
|
135 |
+
return this.sendRequest(
|
136 |
+
routes.feedback.method,
|
137 |
+
routes.feedback.url(message_id),
|
138 |
+
data
|
139 |
+
);
|
140 |
+
}
|
141 |
+
|
142 |
+
getApplicationParameters(user) {
|
143 |
+
const params = { user };
|
144 |
+
return this.sendRequest(
|
145 |
+
routes.application.method,
|
146 |
+
routes.application.url(),
|
147 |
+
null,
|
148 |
+
params
|
149 |
+
);
|
150 |
+
}
|
151 |
+
|
152 |
+
fileUpload(data) {
|
153 |
+
return this.sendRequest(
|
154 |
+
routes.fileUpload.method,
|
155 |
+
routes.fileUpload.url(),
|
156 |
+
data,
|
157 |
+
null,
|
158 |
+
false,
|
159 |
+
{
|
160 |
+
"Content-Type": 'multipart/form-data'
|
161 |
+
}
|
162 |
+
);
|
163 |
+
}
|
164 |
+
|
165 |
+
textToAudio(text, user, streaming = false) {
|
166 |
+
const data = {
|
167 |
+
text,
|
168 |
+
user,
|
169 |
+
streaming
|
170 |
+
};
|
171 |
+
return this.sendRequest(
|
172 |
+
routes.textToAudio.method,
|
173 |
+
routes.textToAudio.url(),
|
174 |
+
data,
|
175 |
+
null,
|
176 |
+
streaming
|
177 |
+
);
|
178 |
+
}
|
179 |
+
|
180 |
+
getMeta(user) {
|
181 |
+
const params = { user };
|
182 |
+
return this.sendRequest(
|
183 |
+
routes.meta.method,
|
184 |
+
routes.meta.url(),
|
185 |
+
null,
|
186 |
+
params
|
187 |
+
);
|
188 |
+
}
|
189 |
+
}
|
190 |
+
|
191 |
+
export class CompletionClient extends DifyClient {
|
192 |
+
createCompletionMessage(inputs, user, stream = false, files = null) {
|
193 |
+
const data = {
|
194 |
+
inputs,
|
195 |
+
user,
|
196 |
+
response_mode: stream ? "streaming" : "blocking",
|
197 |
+
files,
|
198 |
+
};
|
199 |
+
return this.sendRequest(
|
200 |
+
routes.createCompletionMessage.method,
|
201 |
+
routes.createCompletionMessage.url(),
|
202 |
+
data,
|
203 |
+
null,
|
204 |
+
stream
|
205 |
+
);
|
206 |
+
}
|
207 |
+
|
208 |
+
runWorkflow(inputs, user, stream = false, files = null) {
|
209 |
+
const data = {
|
210 |
+
inputs,
|
211 |
+
user,
|
212 |
+
response_mode: stream ? "streaming" : "blocking",
|
213 |
+
};
|
214 |
+
return this.sendRequest(
|
215 |
+
routes.runWorkflow.method,
|
216 |
+
routes.runWorkflow.url(),
|
217 |
+
data,
|
218 |
+
null,
|
219 |
+
stream
|
220 |
+
);
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
export class ChatClient extends DifyClient {
|
225 |
+
createChatMessage(
|
226 |
+
inputs,
|
227 |
+
query,
|
228 |
+
user,
|
229 |
+
stream = false,
|
230 |
+
conversation_id = null,
|
231 |
+
files = null
|
232 |
+
) {
|
233 |
+
const data = {
|
234 |
+
inputs,
|
235 |
+
query,
|
236 |
+
user,
|
237 |
+
response_mode: stream ? "streaming" : "blocking",
|
238 |
+
files,
|
239 |
+
};
|
240 |
+
if (conversation_id) data.conversation_id = conversation_id;
|
241 |
+
|
242 |
+
return this.sendRequest(
|
243 |
+
routes.createChatMessage.method,
|
244 |
+
routes.createChatMessage.url(),
|
245 |
+
data,
|
246 |
+
null,
|
247 |
+
stream
|
248 |
+
);
|
249 |
+
}
|
250 |
+
|
251 |
+
getSuggested(message_id, user) {
|
252 |
+
const data = { user };
|
253 |
+
return this.sendRequest(
|
254 |
+
routes.getSuggested.method,
|
255 |
+
routes.getSuggested.url(message_id),
|
256 |
+
data
|
257 |
+
);
|
258 |
+
}
|
259 |
+
|
260 |
+
stopMessage(task_id, user) {
|
261 |
+
const data = { user };
|
262 |
+
return this.sendRequest(
|
263 |
+
routes.stopChatMessage.method,
|
264 |
+
routes.stopChatMessage.url(task_id),
|
265 |
+
data
|
266 |
+
);
|
267 |
+
}
|
268 |
+
|
269 |
+
getConversations(user, first_id = null, limit = null, pinned = null) {
|
270 |
+
const params = { user, first_id: first_id, limit, pinned };
|
271 |
+
return this.sendRequest(
|
272 |
+
routes.getConversations.method,
|
273 |
+
routes.getConversations.url(),
|
274 |
+
null,
|
275 |
+
params
|
276 |
+
);
|
277 |
+
}
|
278 |
+
|
279 |
+
getConversationMessages(
|
280 |
+
user,
|
281 |
+
conversation_id = "",
|
282 |
+
first_id = null,
|
283 |
+
limit = null
|
284 |
+
) {
|
285 |
+
const params = { user };
|
286 |
+
|
287 |
+
if (conversation_id) params.conversation_id = conversation_id;
|
288 |
+
|
289 |
+
if (first_id) params.first_id = first_id;
|
290 |
+
|
291 |
+
if (limit) params.limit = limit;
|
292 |
+
|
293 |
+
return this.sendRequest(
|
294 |
+
routes.getConversationMessages.method,
|
295 |
+
routes.getConversationMessages.url(),
|
296 |
+
null,
|
297 |
+
params
|
298 |
+
);
|
299 |
+
}
|
300 |
+
|
301 |
+
renameConversation(conversation_id, name, user, auto_generate) {
|
302 |
+
const data = { name, user, auto_generate };
|
303 |
+
return this.sendRequest(
|
304 |
+
routes.renameConversation.method,
|
305 |
+
routes.renameConversation.url(conversation_id),
|
306 |
+
data
|
307 |
+
);
|
308 |
+
}
|
309 |
+
|
310 |
+
deleteConversation(conversation_id, user) {
|
311 |
+
const data = { user };
|
312 |
+
return this.sendRequest(
|
313 |
+
routes.deleteConversation.method,
|
314 |
+
routes.deleteConversation.url(conversation_id),
|
315 |
+
data
|
316 |
+
);
|
317 |
+
}
|
318 |
+
|
319 |
+
|
320 |
+
audioToText(data) {
|
321 |
+
return this.sendRequest(
|
322 |
+
routes.audioToText.method,
|
323 |
+
routes.audioToText.url(),
|
324 |
+
data,
|
325 |
+
null,
|
326 |
+
false,
|
327 |
+
{
|
328 |
+
"Content-Type": 'multipart/form-data'
|
329 |
+
}
|
330 |
+
);
|
331 |
+
}
|
332 |
+
|
333 |
+
}
|
334 |
+
|
335 |
+
export class WorkflowClient extends DifyClient {
|
336 |
+
run(inputs,user,stream) {
|
337 |
+
const data = {
|
338 |
+
inputs,
|
339 |
+
response_mode: stream ? "streaming" : "blocking",
|
340 |
+
user
|
341 |
+
};
|
342 |
+
|
343 |
+
return this.sendRequest(
|
344 |
+
routes.runWorkflow.method,
|
345 |
+
routes.runWorkflow.url(),
|
346 |
+
data,
|
347 |
+
null,
|
348 |
+
stream
|
349 |
+
);
|
350 |
+
}
|
351 |
+
|
352 |
+
stop(task_id, user) {
|
353 |
+
const data = { user };
|
354 |
+
return this.sendRequest(
|
355 |
+
routes.stopWorkflow.method,
|
356 |
+
routes.stopWorkflow.url(task_id),
|
357 |
+
data
|
358 |
+
);
|
359 |
+
}
|
360 |
+
}
|
sdks/nodejs-client/index.test.js
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { DifyClient, BASE_URL, routes } from ".";
|
2 |
+
|
3 |
+
import axios from 'axios'
|
4 |
+
|
5 |
+
jest.mock('axios')
|
6 |
+
|
7 |
+
describe('Client', () => {
|
8 |
+
let difyClient
|
9 |
+
beforeEach(() => {
|
10 |
+
difyClient = new DifyClient('test')
|
11 |
+
})
|
12 |
+
|
13 |
+
test('should create a client', () => {
|
14 |
+
expect(difyClient).toBeDefined();
|
15 |
+
})
|
16 |
+
// test updateApiKey
|
17 |
+
test('should update the api key', () => {
|
18 |
+
difyClient.updateApiKey('test2');
|
19 |
+
expect(difyClient.apiKey).toBe('test2');
|
20 |
+
})
|
21 |
+
});
|
22 |
+
|
23 |
+
describe('Send Requests', () => {
|
24 |
+
let difyClient
|
25 |
+
|
26 |
+
beforeEach(() => {
|
27 |
+
difyClient = new DifyClient('test')
|
28 |
+
})
|
29 |
+
|
30 |
+
afterEach(() => {
|
31 |
+
jest.resetAllMocks()
|
32 |
+
})
|
33 |
+
|
34 |
+
it('should make a successful request to the application parameter', async () => {
|
35 |
+
const method = 'GET'
|
36 |
+
const endpoint = routes.application.url
|
37 |
+
const expectedResponse = { data: 'response' }
|
38 |
+
axios.mockResolvedValue(expectedResponse)
|
39 |
+
|
40 |
+
await difyClient.sendRequest(method, endpoint)
|
41 |
+
|
42 |
+
expect(axios).toHaveBeenCalledWith({
|
43 |
+
method,
|
44 |
+
url: `${BASE_URL}${endpoint}`,
|
45 |
+
params: null,
|
46 |
+
headers: {
|
47 |
+
Authorization: `Bearer ${difyClient.apiKey}`,
|
48 |
+
'Content-Type': 'application/json',
|
49 |
+
},
|
50 |
+
responseType: 'json',
|
51 |
+
})
|
52 |
+
|
53 |
+
})
|
54 |
+
|
55 |
+
it('should handle errors from the API', async () => {
|
56 |
+
const method = 'GET'
|
57 |
+
const endpoint = '/test-endpoint'
|
58 |
+
const errorMessage = 'Request failed with status code 404'
|
59 |
+
axios.mockRejectedValue(new Error(errorMessage))
|
60 |
+
|
61 |
+
await expect(difyClient.sendRequest(method, endpoint)).rejects.toThrow(
|
62 |
+
errorMessage
|
63 |
+
)
|
64 |
+
})
|
65 |
+
})
|
sdks/nodejs-client/package.json
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "dify-client",
|
3 |
+
"version": "2.3.2",
|
4 |
+
"description": "This is the Node.js SDK for the Dify.AI API, which allows you to easily integrate Dify.AI into your Node.js applications.",
|
5 |
+
"main": "index.js",
|
6 |
+
"type": "module",
|
7 |
+
"types":"index.d.ts",
|
8 |
+
"keywords": [
|
9 |
+
"Dify",
|
10 |
+
"Dify.AI",
|
11 |
+
"LLM"
|
12 |
+
],
|
13 |
+
"author": "Joel",
|
14 |
+
"contributors": [
|
15 |
+
"<crazywoola> <<[email protected]>> (https://github.com/crazywoola)"
|
16 |
+
],
|
17 |
+
"license": "MIT",
|
18 |
+
"scripts": {
|
19 |
+
"test": "jest"
|
20 |
+
},
|
21 |
+
"jest": {
|
22 |
+
"transform": {
|
23 |
+
"^.+\\.[t|j]sx?$": "babel-jest"
|
24 |
+
}
|
25 |
+
},
|
26 |
+
"dependencies": {
|
27 |
+
"axios": "^1.3.5"
|
28 |
+
},
|
29 |
+
"devDependencies": {
|
30 |
+
"@babel/core": "^7.21.8",
|
31 |
+
"@babel/preset-env": "^7.21.5",
|
32 |
+
"babel-jest": "^29.5.0",
|
33 |
+
"jest": "^29.5.0"
|
34 |
+
}
|
35 |
+
}
|
sdks/php-client/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dify PHP SDK
|
2 |
+
|
3 |
+
This is the PHP SDK for the Dify API, which allows you to easily integrate Dify into your PHP applications.
|
4 |
+
|
5 |
+
## Requirements
|
6 |
+
|
7 |
+
- PHP 7.2 or later
|
8 |
+
- Guzzle HTTP client library
|
9 |
+
|
10 |
+
## Usage
|
11 |
+
|
12 |
+
After installing the SDK, you can use it in your project like this:
|
13 |
+
|
14 |
+
```php
|
15 |
+
<?php
|
16 |
+
|
17 |
+
require 'vendor/autoload.php';
|
18 |
+
|
19 |
+
use YourVendorName\DifyPHP\DifyClient;
|
20 |
+
use YourVendorName\DifyPHP\CompletionClient;
|
21 |
+
use YourVendorName\DifyPHP\ChatClient;
|
22 |
+
|
23 |
+
$apiKey = 'your-api-key-here';
|
24 |
+
|
25 |
+
$difyClient = new DifyClient($apiKey);
|
26 |
+
|
27 |
+
// Create a completion client
|
28 |
+
$completionClient = new CompletionClient($apiKey);
|
29 |
+
$response = $completionClient->create_completion_message(array("query" => "Who are you?"), "blocking", "user_id");
|
30 |
+
|
31 |
+
// Create a chat client
|
32 |
+
$chatClient = new ChatClient($apiKey);
|
33 |
+
$response = $chatClient->create_chat_message(array(), "Who are you?", "user_id", "blocking", $conversation_id);
|
34 |
+
|
35 |
+
$fileForVision = [
|
36 |
+
[
|
37 |
+
"type" => "image",
|
38 |
+
"transfer_method" => "remote_url",
|
39 |
+
"url" => "your_image_url"
|
40 |
+
]
|
41 |
+
];
|
42 |
+
|
43 |
+
// $fileForVision = [
|
44 |
+
// [
|
45 |
+
// "type" => "image",
|
46 |
+
// "transfer_method" => "local_file",
|
47 |
+
// "url" => "your_file_id"
|
48 |
+
// ]
|
49 |
+
// ];
|
50 |
+
|
51 |
+
// Create a completion client with vision model like gpt-4-vision
|
52 |
+
$response = $completionClient->create_completion_message(array("query" => "Describe this image."), "blocking", "user_id", $fileForVision);
|
53 |
+
|
54 |
+
// Create a chat client with vision model like gpt-4-vision
|
55 |
+
$response = $chatClient->create_chat_message(array(), "Describe this image.", "user_id", "blocking", $conversation_id, $fileForVision);
|
56 |
+
|
57 |
+
// File Upload
|
58 |
+
$fileForUpload = [
|
59 |
+
[
|
60 |
+
'tmp_name' => '/path/to/file/filename.jpg',
|
61 |
+
'name' => 'filename.jpg'
|
62 |
+
]
|
63 |
+
];
|
64 |
+
$response = $difyClient->file_upload("user_id", $fileForUpload);
|
65 |
+
$result = json_decode($response->getBody(), true);
|
66 |
+
echo 'upload_file_id: ' . $result['id'];
|
67 |
+
|
68 |
+
// Fetch application parameters
|
69 |
+
$response = $difyClient->get_application_parameters("user_id");
|
70 |
+
|
71 |
+
// Provide feedback for a message
|
72 |
+
$response = $difyClient->message_feedback($message_id, $rating, "user_id");
|
73 |
+
|
74 |
+
// Other available methods:
|
75 |
+
// - get_conversation_messages()
|
76 |
+
// - get_conversations()
|
77 |
+
// - rename_conversation()
|
78 |
+
```
|
79 |
+
|
80 |
+
Replace 'your-api-key-here' with your actual Dify API key.
|
81 |
+
|
82 |
+
## License
|
83 |
+
|
84 |
+
This SDK is released under the MIT License.
|
sdks/php-client/dify-client.php
ADDED
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require 'vendor/autoload.php';
|
4 |
+
|
5 |
+
use GuzzleHttp\Client;
|
6 |
+
|
7 |
+
class DifyClient {
|
8 |
+
protected $api_key;
|
9 |
+
protected $base_url;
|
10 |
+
protected $client;
|
11 |
+
|
12 |
+
public function __construct($api_key, $base_url = null) {
|
13 |
+
$this->api_key = $api_key;
|
14 |
+
$this->base_url = $base_url ?? "https://api.dify.ai/v1/";
|
15 |
+
$this->client = new Client([
|
16 |
+
'base_uri' => $this->base_url,
|
17 |
+
'headers' => [
|
18 |
+
'Authorization' => 'Bearer ' . $this->api_key,
|
19 |
+
'Content-Type' => 'application/json',
|
20 |
+
],
|
21 |
+
]);
|
22 |
+
$this->file_client = new Client([
|
23 |
+
'base_uri' => $this->base_url,
|
24 |
+
'headers' => [
|
25 |
+
'Authorization' => 'Bearer ' . $this->api_key,
|
26 |
+
'Content-Type' => 'multipart/form-data',
|
27 |
+
],
|
28 |
+
]);
|
29 |
+
}
|
30 |
+
|
31 |
+
protected function send_request($method, $endpoint, $data = null, $params = null, $stream = false) {
|
32 |
+
$options = [
|
33 |
+
'json' => $data,
|
34 |
+
'query' => $params,
|
35 |
+
'stream' => $stream,
|
36 |
+
];
|
37 |
+
|
38 |
+
$response = $this->client->request($method, $endpoint, $options);
|
39 |
+
return $response;
|
40 |
+
}
|
41 |
+
|
42 |
+
public function message_feedback($message_id, $rating, $user) {
|
43 |
+
$data = [
|
44 |
+
'rating' => $rating,
|
45 |
+
'user' => $user,
|
46 |
+
];
|
47 |
+
return $this->send_request('POST', "messages/{$message_id}/feedbacks", $data);
|
48 |
+
}
|
49 |
+
|
50 |
+
public function get_application_parameters($user) {
|
51 |
+
$params = ['user' => $user];
|
52 |
+
return $this->send_request('GET', 'parameters', null, $params);
|
53 |
+
}
|
54 |
+
|
55 |
+
public function file_upload($user, $files) {
|
56 |
+
$data = ['user' => $user];
|
57 |
+
$options = [
|
58 |
+
'multipart' => $this->prepareMultipart($data, $files)
|
59 |
+
];
|
60 |
+
|
61 |
+
return $this->file_client->request('POST', 'files/upload', $options);
|
62 |
+
}
|
63 |
+
|
64 |
+
protected function prepareMultipart($data, $files) {
|
65 |
+
$multipart = [];
|
66 |
+
foreach ($data as $key => $value) {
|
67 |
+
$multipart[] = [
|
68 |
+
'name' => $key,
|
69 |
+
'contents' => $value
|
70 |
+
];
|
71 |
+
}
|
72 |
+
|
73 |
+
foreach ($files as $file) {
|
74 |
+
$multipart[] = [
|
75 |
+
'name' => 'file',
|
76 |
+
'contents' => fopen($file['tmp_name'], 'r'),
|
77 |
+
'filename' => $file['name']
|
78 |
+
];
|
79 |
+
}
|
80 |
+
|
81 |
+
return $multipart;
|
82 |
+
}
|
83 |
+
|
84 |
+
|
85 |
+
public function text_to_audio($text, $user, $streaming = false) {
|
86 |
+
$data = [
|
87 |
+
'text' => $text,
|
88 |
+
'user' => $user,
|
89 |
+
'streaming' => $streaming
|
90 |
+
];
|
91 |
+
|
92 |
+
return $this->send_request('POST', 'text-to-audio', $data);
|
93 |
+
}
|
94 |
+
|
95 |
+
public function get_meta($user) {
|
96 |
+
$params = [
|
97 |
+
'user' => $user
|
98 |
+
];
|
99 |
+
|
100 |
+
return $this->send_request('GET', 'meta',null, $params);
|
101 |
+
}
|
102 |
+
}
|
103 |
+
|
104 |
+
class CompletionClient extends DifyClient {
|
105 |
+
public function create_completion_message($inputs, $response_mode, $user, $files = null) {
|
106 |
+
$data = [
|
107 |
+
'inputs' => $inputs,
|
108 |
+
'response_mode' => $response_mode,
|
109 |
+
'user' => $user,
|
110 |
+
'files' => $files,
|
111 |
+
];
|
112 |
+
return $this->send_request('POST', 'completion-messages', $data, null, $response_mode === 'streaming');
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
class ChatClient extends DifyClient {
|
117 |
+
public function create_chat_message($inputs, $query, $user, $response_mode = 'blocking', $conversation_id = null, $files = null) {
|
118 |
+
$data = [
|
119 |
+
'inputs' => $inputs,
|
120 |
+
'query' => $query,
|
121 |
+
'user' => $user,
|
122 |
+
'response_mode' => $response_mode,
|
123 |
+
'files' => $files,
|
124 |
+
];
|
125 |
+
if ($conversation_id) {
|
126 |
+
$data['conversation_id'] = $conversation_id;
|
127 |
+
}
|
128 |
+
|
129 |
+
return $this->send_request('POST', 'chat-messages', $data, null, $response_mode === 'streaming');
|
130 |
+
}
|
131 |
+
|
132 |
+
public function get_suggestions($message_id, $user) {
|
133 |
+
$params = [
|
134 |
+
'user' => $user
|
135 |
+
]
|
136 |
+
return $this->send_request('GET', "messages/{$message_id}/suggested", null, $params);
|
137 |
+
}
|
138 |
+
|
139 |
+
public function stop_message($task_id, $user) {
|
140 |
+
$data = ['user' => $user];
|
141 |
+
return $this->send_request('POST', "chat-messages/{$task_id}/stop", $data);
|
142 |
+
}
|
143 |
+
|
144 |
+
public function get_conversations($user, $first_id = null, $limit = null, $pinned = null) {
|
145 |
+
$params = [
|
146 |
+
'user' => $user,
|
147 |
+
'first_id' => $first_id,
|
148 |
+
'limit' => $limit,
|
149 |
+
'pinned'=> $pinned,
|
150 |
+
];
|
151 |
+
return $this->send_request('GET', 'conversations', null, $params);
|
152 |
+
}
|
153 |
+
|
154 |
+
public function get_conversation_messages($user, $conversation_id = null, $first_id = null, $limit = null) {
|
155 |
+
$params = ['user' => $user];
|
156 |
+
|
157 |
+
if ($conversation_id) {
|
158 |
+
$params['conversation_id'] = $conversation_id;
|
159 |
+
}
|
160 |
+
if ($first_id) {
|
161 |
+
$params['first_id'] = $first_id;
|
162 |
+
}
|
163 |
+
if ($limit) {
|
164 |
+
$params['limit'] = $limit;
|
165 |
+
}
|
166 |
+
|
167 |
+
return $this->send_request('GET', 'messages', null, $params);
|
168 |
+
}
|
169 |
+
|
170 |
+
public function rename_conversation($conversation_id, $name,$auto_generate, $user) {
|
171 |
+
$data = [
|
172 |
+
'name' => $name,
|
173 |
+
'user' => $user,
|
174 |
+
'auto_generate' => $auto_generate
|
175 |
+
];
|
176 |
+
return $this->send_request('PATCH', "conversations/{$conversation_id}", $data);
|
177 |
+
}
|
178 |
+
|
179 |
+
public function delete_conversation($conversation_id, $user) {
|
180 |
+
$data = [
|
181 |
+
'user' => $user,
|
182 |
+
];
|
183 |
+
return $this->send_request('DELETE', "conversations/{$conversation_id}", $data);
|
184 |
+
}
|
185 |
+
|
186 |
+
public function audio_to_text($audio_file, $user) {
|
187 |
+
$data = [
|
188 |
+
'user' => $user,
|
189 |
+
];
|
190 |
+
$options = [
|
191 |
+
'multipart' => $this->prepareMultipart($data, $files)
|
192 |
+
];
|
193 |
+
return $this->file_client->request('POST', 'audio-to-text', $options);
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
class WorkflowClient extends DifyClient{
|
200 |
+
public function run($inputs, $response_mode, $user) {
|
201 |
+
$data = [
|
202 |
+
'inputs' => $inputs,
|
203 |
+
'response_mode' => $response_mode,
|
204 |
+
'user' => $user,
|
205 |
+
];
|
206 |
+
return $this->send_request('POST', 'workflows/run', $data);
|
207 |
+
}
|
208 |
+
|
209 |
+
public function stop($task_id, $user) {
|
210 |
+
$data = [
|
211 |
+
'user' => $user,
|
212 |
+
];
|
213 |
+
return $this->send_request('POST', "workflows/tasks/{$task_id}/stop",$data);
|
214 |
+
}
|
215 |
+
|
216 |
+
}
|
sdks/python-client/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 LangGenius
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
sdks/python-client/MANIFEST.in
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
recursive-include dify_client *.py
|
sdks/python-client/README.md
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# dify-client
|
2 |
+
|
3 |
+
A Dify App Service-API Client, using for build a webapp by request Service-API
|
4 |
+
|
5 |
+
## Usage
|
6 |
+
|
7 |
+
First, install `dify-client` python sdk package:
|
8 |
+
|
9 |
+
```
|
10 |
+
pip install dify-client
|
11 |
+
```
|
12 |
+
|
13 |
+
Write your code with sdk:
|
14 |
+
|
15 |
+
- completion generate with `blocking` response_mode
|
16 |
+
|
17 |
+
```python
|
18 |
+
from dify_client import CompletionClient
|
19 |
+
|
20 |
+
api_key = "your_api_key"
|
21 |
+
|
22 |
+
# Initialize CompletionClient
|
23 |
+
completion_client = CompletionClient(api_key)
|
24 |
+
|
25 |
+
# Create Completion Message using CompletionClient
|
26 |
+
completion_response = completion_client.create_completion_message(inputs={"query": "What's the weather like today?"},
|
27 |
+
response_mode="blocking", user="user_id")
|
28 |
+
completion_response.raise_for_status()
|
29 |
+
|
30 |
+
result = completion_response.json()
|
31 |
+
|
32 |
+
print(result.get('answer'))
|
33 |
+
```
|
34 |
+
|
35 |
+
- completion using vision model, like gpt-4-vision
|
36 |
+
|
37 |
+
```python
|
38 |
+
from dify_client import CompletionClient
|
39 |
+
|
40 |
+
api_key = "your_api_key"
|
41 |
+
|
42 |
+
# Initialize CompletionClient
|
43 |
+
completion_client = CompletionClient(api_key)
|
44 |
+
|
45 |
+
files = [{
|
46 |
+
"type": "image",
|
47 |
+
"transfer_method": "remote_url",
|
48 |
+
"url": "your_image_url"
|
49 |
+
}]
|
50 |
+
|
51 |
+
# files = [{
|
52 |
+
# "type": "image",
|
53 |
+
# "transfer_method": "local_file",
|
54 |
+
# "upload_file_id": "your_file_id"
|
55 |
+
# }]
|
56 |
+
|
57 |
+
# Create Completion Message using CompletionClient
|
58 |
+
completion_response = completion_client.create_completion_message(inputs={"query": "Describe the picture."},
|
59 |
+
response_mode="blocking", user="user_id", files=files)
|
60 |
+
completion_response.raise_for_status()
|
61 |
+
|
62 |
+
result = completion_response.json()
|
63 |
+
|
64 |
+
print(result.get('answer'))
|
65 |
+
```
|
66 |
+
|
67 |
+
- chat generate with `streaming` response_mode
|
68 |
+
|
69 |
+
```python
|
70 |
+
import json
|
71 |
+
from dify_client import ChatClient
|
72 |
+
|
73 |
+
api_key = "your_api_key"
|
74 |
+
|
75 |
+
# Initialize ChatClient
|
76 |
+
chat_client = ChatClient(api_key)
|
77 |
+
|
78 |
+
# Create Chat Message using ChatClient
|
79 |
+
chat_response = chat_client.create_chat_message(inputs={}, query="Hello", user="user_id", response_mode="streaming")
|
80 |
+
chat_response.raise_for_status()
|
81 |
+
|
82 |
+
for line in chat_response.iter_lines(decode_unicode=True):
|
83 |
+
line = line.split('data:', 1)[-1]
|
84 |
+
if line.strip():
|
85 |
+
line = json.loads(line.strip())
|
86 |
+
print(line.get('answer'))
|
87 |
+
```
|
88 |
+
|
89 |
+
- chat using vision model, like gpt-4-vision
|
90 |
+
|
91 |
+
```python
|
92 |
+
from dify_client import ChatClient
|
93 |
+
|
94 |
+
api_key = "your_api_key"
|
95 |
+
|
96 |
+
# Initialize ChatClient
|
97 |
+
chat_client = ChatClient(api_key)
|
98 |
+
|
99 |
+
files = [{
|
100 |
+
"type": "image",
|
101 |
+
"transfer_method": "remote_url",
|
102 |
+
"url": "your_image_url"
|
103 |
+
}]
|
104 |
+
|
105 |
+
# files = [{
|
106 |
+
# "type": "image",
|
107 |
+
# "transfer_method": "local_file",
|
108 |
+
# "upload_file_id": "your_file_id"
|
109 |
+
# }]
|
110 |
+
|
111 |
+
# Create Chat Message using ChatClient
|
112 |
+
chat_response = chat_client.create_chat_message(inputs={}, query="Describe the picture.", user="user_id",
|
113 |
+
response_mode="blocking", files=files)
|
114 |
+
chat_response.raise_for_status()
|
115 |
+
|
116 |
+
result = chat_response.json()
|
117 |
+
|
118 |
+
print(result.get("answer"))
|
119 |
+
```
|
120 |
+
|
121 |
+
- upload file when using vision model
|
122 |
+
|
123 |
+
```python
|
124 |
+
from dify_client import DifyClient
|
125 |
+
|
126 |
+
api_key = "your_api_key"
|
127 |
+
|
128 |
+
# Initialize Client
|
129 |
+
dify_client = DifyClient(api_key)
|
130 |
+
|
131 |
+
file_path = "your_image_file_path"
|
132 |
+
file_name = "panda.jpeg"
|
133 |
+
mime_type = "image/jpeg"
|
134 |
+
|
135 |
+
with open(file_path, "rb") as file:
|
136 |
+
files = {
|
137 |
+
"file": (file_name, file, mime_type)
|
138 |
+
}
|
139 |
+
response = dify_client.file_upload("user_id", files)
|
140 |
+
|
141 |
+
result = response.json()
|
142 |
+
print(f'upload_file_id: {result.get("id")}')
|
143 |
+
```
|
144 |
+
|
145 |
+
|
146 |
+
|
147 |
+
- Others
|
148 |
+
|
149 |
+
```python
|
150 |
+
from dify_client import ChatClient
|
151 |
+
|
152 |
+
api_key = "your_api_key"
|
153 |
+
|
154 |
+
# Initialize Client
|
155 |
+
client = ChatClient(api_key)
|
156 |
+
|
157 |
+
# Get App parameters
|
158 |
+
parameters = client.get_application_parameters(user="user_id")
|
159 |
+
parameters.raise_for_status()
|
160 |
+
|
161 |
+
print('[parameters]')
|
162 |
+
print(parameters.json())
|
163 |
+
|
164 |
+
# Get Conversation List (only for chat)
|
165 |
+
conversations = client.get_conversations(user="user_id")
|
166 |
+
conversations.raise_for_status()
|
167 |
+
|
168 |
+
print('[conversations]')
|
169 |
+
print(conversations.json())
|
170 |
+
|
171 |
+
# Get Message List (only for chat)
|
172 |
+
messages = client.get_conversation_messages(user="user_id", conversation_id="conversation_id")
|
173 |
+
messages.raise_for_status()
|
174 |
+
|
175 |
+
print('[messages]')
|
176 |
+
print(messages.json())
|
177 |
+
|
178 |
+
# Rename Conversation (only for chat)
|
179 |
+
rename_conversation_response = client.rename_conversation(conversation_id="conversation_id",
|
180 |
+
name="new_name", user="user_id")
|
181 |
+
rename_conversation_response.raise_for_status()
|
182 |
+
|
183 |
+
print('[rename result]')
|
184 |
+
print(rename_conversation_response.json())
|
185 |
+
```
|
sdks/python-client/build.sh
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
set -e
|
4 |
+
|
5 |
+
rm -rf build dist *.egg-info
|
6 |
+
|
7 |
+
pip install setuptools wheel twine
|
8 |
+
python setup.py sdist bdist_wheel
|
9 |
+
twine upload dist/*
|
sdks/python-client/dify_client/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from dify_client.client import ChatClient, CompletionClient, DifyClient
|
sdks/python-client/dify_client/client.py
ADDED
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
|
3 |
+
import requests
|
4 |
+
|
5 |
+
|
6 |
+
class DifyClient:
|
7 |
+
def __init__(self, api_key, base_url: str = "https://api.dify.ai/v1"):
|
8 |
+
self.api_key = api_key
|
9 |
+
self.base_url = base_url
|
10 |
+
|
11 |
+
def _send_request(self, method, endpoint, json=None, params=None, stream=False):
|
12 |
+
headers = {
|
13 |
+
"Authorization": f"Bearer {self.api_key}",
|
14 |
+
"Content-Type": "application/json",
|
15 |
+
}
|
16 |
+
|
17 |
+
url = f"{self.base_url}{endpoint}"
|
18 |
+
response = requests.request(
|
19 |
+
method, url, json=json, params=params, headers=headers, stream=stream
|
20 |
+
)
|
21 |
+
|
22 |
+
return response
|
23 |
+
|
24 |
+
def _send_request_with_files(self, method, endpoint, data, files):
|
25 |
+
headers = {"Authorization": f"Bearer {self.api_key}"}
|
26 |
+
|
27 |
+
url = f"{self.base_url}{endpoint}"
|
28 |
+
response = requests.request(
|
29 |
+
method, url, data=data, headers=headers, files=files
|
30 |
+
)
|
31 |
+
|
32 |
+
return response
|
33 |
+
|
34 |
+
def message_feedback(self, message_id, rating, user):
|
35 |
+
data = {"rating": rating, "user": user}
|
36 |
+
return self._send_request("POST", f"/messages/{message_id}/feedbacks", data)
|
37 |
+
|
38 |
+
def get_application_parameters(self, user):
|
39 |
+
params = {"user": user}
|
40 |
+
return self._send_request("GET", "/parameters", params=params)
|
41 |
+
|
42 |
+
def file_upload(self, user, files):
|
43 |
+
data = {"user": user}
|
44 |
+
return self._send_request_with_files(
|
45 |
+
"POST", "/files/upload", data=data, files=files
|
46 |
+
)
|
47 |
+
|
48 |
+
def text_to_audio(self, text: str, user: str, streaming: bool = False):
|
49 |
+
data = {"text": text, "user": user, "streaming": streaming}
|
50 |
+
return self._send_request("POST", "/text-to-audio", data=data)
|
51 |
+
|
52 |
+
def get_meta(self, user):
|
53 |
+
params = {"user": user}
|
54 |
+
return self._send_request("GET", "/meta", params=params)
|
55 |
+
|
56 |
+
|
57 |
+
class CompletionClient(DifyClient):
|
58 |
+
def create_completion_message(self, inputs, response_mode, user, files=None):
|
59 |
+
data = {
|
60 |
+
"inputs": inputs,
|
61 |
+
"response_mode": response_mode,
|
62 |
+
"user": user,
|
63 |
+
"files": files,
|
64 |
+
}
|
65 |
+
return self._send_request(
|
66 |
+
"POST",
|
67 |
+
"/completion-messages",
|
68 |
+
data,
|
69 |
+
stream=True if response_mode == "streaming" else False,
|
70 |
+
)
|
71 |
+
|
72 |
+
|
73 |
+
class ChatClient(DifyClient):
|
74 |
+
def create_chat_message(
|
75 |
+
self,
|
76 |
+
inputs,
|
77 |
+
query,
|
78 |
+
user,
|
79 |
+
response_mode="blocking",
|
80 |
+
conversation_id=None,
|
81 |
+
files=None,
|
82 |
+
):
|
83 |
+
data = {
|
84 |
+
"inputs": inputs,
|
85 |
+
"query": query,
|
86 |
+
"user": user,
|
87 |
+
"response_mode": response_mode,
|
88 |
+
"files": files,
|
89 |
+
}
|
90 |
+
if conversation_id:
|
91 |
+
data["conversation_id"] = conversation_id
|
92 |
+
|
93 |
+
return self._send_request(
|
94 |
+
"POST",
|
95 |
+
"/chat-messages",
|
96 |
+
data,
|
97 |
+
stream=True if response_mode == "streaming" else False,
|
98 |
+
)
|
99 |
+
|
100 |
+
def get_suggested(self, message_id, user: str):
|
101 |
+
params = {"user": user}
|
102 |
+
return self._send_request(
|
103 |
+
"GET", f"/messages/{message_id}/suggested", params=params
|
104 |
+
)
|
105 |
+
|
106 |
+
def stop_message(self, task_id, user):
|
107 |
+
data = {"user": user}
|
108 |
+
return self._send_request("POST", f"/chat-messages/{task_id}/stop", data)
|
109 |
+
|
110 |
+
def get_conversations(self, user, last_id=None, limit=None, pinned=None):
|
111 |
+
params = {"user": user, "last_id": last_id, "limit": limit, "pinned": pinned}
|
112 |
+
return self._send_request("GET", "/conversations", params=params)
|
113 |
+
|
114 |
+
def get_conversation_messages(
|
115 |
+
self, user, conversation_id=None, first_id=None, limit=None
|
116 |
+
):
|
117 |
+
params = {"user": user}
|
118 |
+
|
119 |
+
if conversation_id:
|
120 |
+
params["conversation_id"] = conversation_id
|
121 |
+
if first_id:
|
122 |
+
params["first_id"] = first_id
|
123 |
+
if limit:
|
124 |
+
params["limit"] = limit
|
125 |
+
|
126 |
+
return self._send_request("GET", "/messages", params=params)
|
127 |
+
|
128 |
+
def rename_conversation(
|
129 |
+
self, conversation_id, name, auto_generate: bool, user: str
|
130 |
+
):
|
131 |
+
data = {"name": name, "auto_generate": auto_generate, "user": user}
|
132 |
+
return self._send_request(
|
133 |
+
"POST", f"/conversations/{conversation_id}/name", data
|
134 |
+
)
|
135 |
+
|
136 |
+
def delete_conversation(self, conversation_id, user):
|
137 |
+
data = {"user": user}
|
138 |
+
return self._send_request("DELETE", f"/conversations/{conversation_id}", data)
|
139 |
+
|
140 |
+
def audio_to_text(self, audio_file, user):
|
141 |
+
data = {"user": user}
|
142 |
+
files = {"audio_file": audio_file}
|
143 |
+
return self._send_request_with_files("POST", "/audio-to-text", data, files)
|
144 |
+
|
145 |
+
|
146 |
+
class WorkflowClient(DifyClient):
|
147 |
+
def run(
|
148 |
+
self, inputs: dict, response_mode: str = "streaming", user: str = "abc-123"
|
149 |
+
):
|
150 |
+
data = {"inputs": inputs, "response_mode": response_mode, "user": user}
|
151 |
+
return self._send_request("POST", "/workflows/run", data)
|
152 |
+
|
153 |
+
def stop(self, task_id, user):
|
154 |
+
data = {"user": user}
|
155 |
+
return self._send_request("POST", f"/workflows/tasks/{task_id}/stop", data)
|
156 |
+
|
157 |
+
def get_result(self, workflow_run_id):
|
158 |
+
return self._send_request("GET", f"/workflows/run/{workflow_run_id}")
|
159 |
+
|
160 |
+
|
161 |
+
class KnowledgeBaseClient(DifyClient):
|
162 |
+
def __init__(
|
163 |
+
self,
|
164 |
+
api_key,
|
165 |
+
base_url: str = "https://api.dify.ai/v1",
|
166 |
+
dataset_id: str | None = None,
|
167 |
+
):
|
168 |
+
"""
|
169 |
+
Construct a KnowledgeBaseClient object.
|
170 |
+
|
171 |
+
Args:
|
172 |
+
api_key (str): API key of Dify.
|
173 |
+
base_url (str, optional): Base URL of Dify API. Defaults to 'https://api.dify.ai/v1'.
|
174 |
+
dataset_id (str, optional): ID of the dataset. Defaults to None. You don't need this if you just want to
|
175 |
+
create a new dataset. or list datasets. otherwise you need to set this.
|
176 |
+
"""
|
177 |
+
super().__init__(api_key=api_key, base_url=base_url)
|
178 |
+
self.dataset_id = dataset_id
|
179 |
+
|
180 |
+
def _get_dataset_id(self):
|
181 |
+
if self.dataset_id is None:
|
182 |
+
raise ValueError("dataset_id is not set")
|
183 |
+
return self.dataset_id
|
184 |
+
|
185 |
+
def create_dataset(self, name: str, **kwargs):
|
186 |
+
return self._send_request("POST", "/datasets", {"name": name}, **kwargs)
|
187 |
+
|
188 |
+
def list_datasets(self, page: int = 1, page_size: int = 20, **kwargs):
|
189 |
+
return self._send_request(
|
190 |
+
"GET", f"/datasets?page={page}&limit={page_size}", **kwargs
|
191 |
+
)
|
192 |
+
|
193 |
+
def create_document_by_text(
|
194 |
+
self, name, text, extra_params: dict | None = None, **kwargs
|
195 |
+
):
|
196 |
+
"""
|
197 |
+
Create a document by text.
|
198 |
+
|
199 |
+
:param name: Name of the document
|
200 |
+
:param text: Text content of the document
|
201 |
+
:param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
|
202 |
+
e.g.
|
203 |
+
{
|
204 |
+
'indexing_technique': 'high_quality',
|
205 |
+
'process_rule': {
|
206 |
+
'rules': {
|
207 |
+
'pre_processing_rules': [
|
208 |
+
{'id': 'remove_extra_spaces', 'enabled': True},
|
209 |
+
{'id': 'remove_urls_emails', 'enabled': True}
|
210 |
+
],
|
211 |
+
'segmentation': {
|
212 |
+
'separator': '\n',
|
213 |
+
'max_tokens': 500
|
214 |
+
}
|
215 |
+
},
|
216 |
+
'mode': 'custom'
|
217 |
+
}
|
218 |
+
}
|
219 |
+
:return: Response from the API
|
220 |
+
"""
|
221 |
+
data = {
|
222 |
+
"indexing_technique": "high_quality",
|
223 |
+
"process_rule": {"mode": "automatic"},
|
224 |
+
"name": name,
|
225 |
+
"text": text,
|
226 |
+
}
|
227 |
+
if extra_params is not None and isinstance(extra_params, dict):
|
228 |
+
data.update(extra_params)
|
229 |
+
url = f"/datasets/{self._get_dataset_id()}/document/create_by_text"
|
230 |
+
return self._send_request("POST", url, json=data, **kwargs)
|
231 |
+
|
232 |
+
def update_document_by_text(
|
233 |
+
self, document_id, name, text, extra_params: dict | None = None, **kwargs
|
234 |
+
):
|
235 |
+
"""
|
236 |
+
Update a document by text.
|
237 |
+
|
238 |
+
:param document_id: ID of the document
|
239 |
+
:param name: Name of the document
|
240 |
+
:param text: Text content of the document
|
241 |
+
:param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
|
242 |
+
e.g.
|
243 |
+
{
|
244 |
+
'indexing_technique': 'high_quality',
|
245 |
+
'process_rule': {
|
246 |
+
'rules': {
|
247 |
+
'pre_processing_rules': [
|
248 |
+
{'id': 'remove_extra_spaces', 'enabled': True},
|
249 |
+
{'id': 'remove_urls_emails', 'enabled': True}
|
250 |
+
],
|
251 |
+
'segmentation': {
|
252 |
+
'separator': '\n',
|
253 |
+
'max_tokens': 500
|
254 |
+
}
|
255 |
+
},
|
256 |
+
'mode': 'custom'
|
257 |
+
}
|
258 |
+
}
|
259 |
+
:return: Response from the API
|
260 |
+
"""
|
261 |
+
data = {"name": name, "text": text}
|
262 |
+
if extra_params is not None and isinstance(extra_params, dict):
|
263 |
+
data.update(extra_params)
|
264 |
+
url = (
|
265 |
+
f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_text"
|
266 |
+
)
|
267 |
+
return self._send_request("POST", url, json=data, **kwargs)
|
268 |
+
|
269 |
+
def create_document_by_file(
|
270 |
+
self, file_path, original_document_id=None, extra_params: dict | None = None
|
271 |
+
):
|
272 |
+
"""
|
273 |
+
Create a document by file.
|
274 |
+
|
275 |
+
:param file_path: Path to the file
|
276 |
+
:param original_document_id: pass this ID if you want to replace the original document (optional)
|
277 |
+
:param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
|
278 |
+
e.g.
|
279 |
+
{
|
280 |
+
'indexing_technique': 'high_quality',
|
281 |
+
'process_rule': {
|
282 |
+
'rules': {
|
283 |
+
'pre_processing_rules': [
|
284 |
+
{'id': 'remove_extra_spaces', 'enabled': True},
|
285 |
+
{'id': 'remove_urls_emails', 'enabled': True}
|
286 |
+
],
|
287 |
+
'segmentation': {
|
288 |
+
'separator': '\n',
|
289 |
+
'max_tokens': 500
|
290 |
+
}
|
291 |
+
},
|
292 |
+
'mode': 'custom'
|
293 |
+
}
|
294 |
+
}
|
295 |
+
:return: Response from the API
|
296 |
+
"""
|
297 |
+
files = {"file": open(file_path, "rb")}
|
298 |
+
data = {
|
299 |
+
"process_rule": {"mode": "automatic"},
|
300 |
+
"indexing_technique": "high_quality",
|
301 |
+
}
|
302 |
+
if extra_params is not None and isinstance(extra_params, dict):
|
303 |
+
data.update(extra_params)
|
304 |
+
if original_document_id is not None:
|
305 |
+
data["original_document_id"] = original_document_id
|
306 |
+
url = f"/datasets/{self._get_dataset_id()}/document/create_by_file"
|
307 |
+
return self._send_request_with_files(
|
308 |
+
"POST", url, {"data": json.dumps(data)}, files
|
309 |
+
)
|
310 |
+
|
311 |
+
def update_document_by_file(
|
312 |
+
self, document_id, file_path, extra_params: dict | None = None
|
313 |
+
):
|
314 |
+
"""
|
315 |
+
Update a document by file.
|
316 |
+
|
317 |
+
:param document_id: ID of the document
|
318 |
+
:param file_path: Path to the file
|
319 |
+
:param extra_params: extra parameters pass to the API, such as indexing_technique, process_rule. (optional)
|
320 |
+
e.g.
|
321 |
+
{
|
322 |
+
'indexing_technique': 'high_quality',
|
323 |
+
'process_rule': {
|
324 |
+
'rules': {
|
325 |
+
'pre_processing_rules': [
|
326 |
+
{'id': 'remove_extra_spaces', 'enabled': True},
|
327 |
+
{'id': 'remove_urls_emails', 'enabled': True}
|
328 |
+
],
|
329 |
+
'segmentation': {
|
330 |
+
'separator': '\n',
|
331 |
+
'max_tokens': 500
|
332 |
+
}
|
333 |
+
},
|
334 |
+
'mode': 'custom'
|
335 |
+
}
|
336 |
+
}
|
337 |
+
:return:
|
338 |
+
"""
|
339 |
+
files = {"file": open(file_path, "rb")}
|
340 |
+
data = {}
|
341 |
+
if extra_params is not None and isinstance(extra_params, dict):
|
342 |
+
data.update(extra_params)
|
343 |
+
url = (
|
344 |
+
f"/datasets/{self._get_dataset_id()}/documents/{document_id}/update_by_file"
|
345 |
+
)
|
346 |
+
return self._send_request_with_files(
|
347 |
+
"POST", url, {"data": json.dumps(data)}, files
|
348 |
+
)
|
349 |
+
|
350 |
+
def batch_indexing_status(self, batch_id: str, **kwargs):
|
351 |
+
"""
|
352 |
+
Get the status of the batch indexing.
|
353 |
+
|
354 |
+
:param batch_id: ID of the batch uploading
|
355 |
+
:return: Response from the API
|
356 |
+
"""
|
357 |
+
url = f"/datasets/{self._get_dataset_id()}/documents/{batch_id}/indexing-status"
|
358 |
+
return self._send_request("GET", url, **kwargs)
|
359 |
+
|
360 |
+
def delete_dataset(self):
|
361 |
+
"""
|
362 |
+
Delete this dataset.
|
363 |
+
|
364 |
+
:return: Response from the API
|
365 |
+
"""
|
366 |
+
url = f"/datasets/{self._get_dataset_id()}"
|
367 |
+
return self._send_request("DELETE", url)
|
368 |
+
|
369 |
+
def delete_document(self, document_id):
|
370 |
+
"""
|
371 |
+
Delete a document.
|
372 |
+
|
373 |
+
:param document_id: ID of the document
|
374 |
+
:return: Response from the API
|
375 |
+
"""
|
376 |
+
url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}"
|
377 |
+
return self._send_request("DELETE", url)
|
378 |
+
|
379 |
+
def list_documents(
|
380 |
+
self,
|
381 |
+
page: int | None = None,
|
382 |
+
page_size: int | None = None,
|
383 |
+
keyword: str | None = None,
|
384 |
+
**kwargs,
|
385 |
+
):
|
386 |
+
"""
|
387 |
+
Get a list of documents in this dataset.
|
388 |
+
|
389 |
+
:return: Response from the API
|
390 |
+
"""
|
391 |
+
params = {}
|
392 |
+
if page is not None:
|
393 |
+
params["page"] = page
|
394 |
+
if page_size is not None:
|
395 |
+
params["limit"] = page_size
|
396 |
+
if keyword is not None:
|
397 |
+
params["keyword"] = keyword
|
398 |
+
url = f"/datasets/{self._get_dataset_id()}/documents"
|
399 |
+
return self._send_request("GET", url, params=params, **kwargs)
|
400 |
+
|
401 |
+
def add_segments(self, document_id, segments, **kwargs):
|
402 |
+
"""
|
403 |
+
Add segments to a document.
|
404 |
+
|
405 |
+
:param document_id: ID of the document
|
406 |
+
:param segments: List of segments to add, example: [{"content": "1", "answer": "1", "keyword": ["a"]}]
|
407 |
+
:return: Response from the API
|
408 |
+
"""
|
409 |
+
data = {"segments": segments}
|
410 |
+
url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
|
411 |
+
return self._send_request("POST", url, json=data, **kwargs)
|
412 |
+
|
413 |
+
def query_segments(
|
414 |
+
self,
|
415 |
+
document_id,
|
416 |
+
keyword: str | None = None,
|
417 |
+
status: str | None = None,
|
418 |
+
**kwargs,
|
419 |
+
):
|
420 |
+
"""
|
421 |
+
Query segments in this document.
|
422 |
+
|
423 |
+
:param document_id: ID of the document
|
424 |
+
:param keyword: query keyword, optional
|
425 |
+
:param status: status of the segment, optional, e.g. completed
|
426 |
+
"""
|
427 |
+
url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments"
|
428 |
+
params = {}
|
429 |
+
if keyword is not None:
|
430 |
+
params["keyword"] = keyword
|
431 |
+
if status is not None:
|
432 |
+
params["status"] = status
|
433 |
+
if "params" in kwargs:
|
434 |
+
params.update(kwargs["params"])
|
435 |
+
return self._send_request("GET", url, params=params, **kwargs)
|
436 |
+
|
437 |
+
def delete_document_segment(self, document_id, segment_id):
|
438 |
+
"""
|
439 |
+
Delete a segment from a document.
|
440 |
+
|
441 |
+
:param document_id: ID of the document
|
442 |
+
:param segment_id: ID of the segment
|
443 |
+
:return: Response from the API
|
444 |
+
"""
|
445 |
+
url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
|
446 |
+
return self._send_request("DELETE", url)
|
447 |
+
|
448 |
+
def update_document_segment(self, document_id, segment_id, segment_data, **kwargs):
|
449 |
+
"""
|
450 |
+
Update a segment in a document.
|
451 |
+
|
452 |
+
:param document_id: ID of the document
|
453 |
+
:param segment_id: ID of the segment
|
454 |
+
:param segment_data: Data of the segment, example: {"content": "1", "answer": "1", "keyword": ["a"], "enabled": True}
|
455 |
+
:return: Response from the API
|
456 |
+
"""
|
457 |
+
data = {"segment": segment_data}
|
458 |
+
url = f"/datasets/{self._get_dataset_id()}/documents/{document_id}/segments/{segment_id}"
|
459 |
+
return self._send_request("POST", url, json=data, **kwargs)
|
sdks/python-client/setup.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from setuptools import setup
|
2 |
+
|
3 |
+
with open("README.md", "r", encoding="utf-8") as fh:
|
4 |
+
long_description = fh.read()
|
5 |
+
|
6 |
+
setup(
|
7 |
+
name="dify-client",
|
8 |
+
version="0.1.12",
|
9 |
+
author="Dify",
|
10 |
+
author_email="[email protected]",
|
11 |
+
description="A package for interacting with the Dify Service-API",
|
12 |
+
long_description=long_description,
|
13 |
+
long_description_content_type="text/markdown",
|
14 |
+
url="https://github.com/langgenius/dify",
|
15 |
+
license="MIT",
|
16 |
+
packages=["dify_client"],
|
17 |
+
classifiers=[
|
18 |
+
"Programming Language :: Python :: 3",
|
19 |
+
"License :: OSI Approved :: MIT License",
|
20 |
+
"Operating System :: OS Independent",
|
21 |
+
],
|
22 |
+
python_requires=">=3.6",
|
23 |
+
install_requires=["requests"],
|
24 |
+
keywords="dify nlp ai language-processing",
|
25 |
+
include_package_data=True,
|
26 |
+
)
|
sdks/python-client/tests/__init__.py
ADDED
File without changes
|
sdks/python-client/tests/test_client.py
ADDED
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import time
|
3 |
+
import unittest
|
4 |
+
|
5 |
+
from dify_client.client import (
|
6 |
+
ChatClient,
|
7 |
+
CompletionClient,
|
8 |
+
DifyClient,
|
9 |
+
KnowledgeBaseClient,
|
10 |
+
)
|
11 |
+
|
12 |
+
API_KEY = os.environ.get("API_KEY")
|
13 |
+
APP_ID = os.environ.get("APP_ID")
|
14 |
+
API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.dify.ai/v1")
|
15 |
+
FILE_PATH_BASE = os.path.dirname(__file__)
|
16 |
+
|
17 |
+
|
18 |
+
class TestKnowledgeBaseClient(unittest.TestCase):
|
19 |
+
def setUp(self):
|
20 |
+
self.knowledge_base_client = KnowledgeBaseClient(API_KEY, base_url=API_BASE_URL)
|
21 |
+
self.README_FILE_PATH = os.path.abspath(
|
22 |
+
os.path.join(FILE_PATH_BASE, "../README.md")
|
23 |
+
)
|
24 |
+
self.dataset_id = None
|
25 |
+
self.document_id = None
|
26 |
+
self.segment_id = None
|
27 |
+
self.batch_id = None
|
28 |
+
|
29 |
+
def _get_dataset_kb_client(self):
|
30 |
+
self.assertIsNotNone(self.dataset_id)
|
31 |
+
return KnowledgeBaseClient(
|
32 |
+
API_KEY, base_url=API_BASE_URL, dataset_id=self.dataset_id
|
33 |
+
)
|
34 |
+
|
35 |
+
def test_001_create_dataset(self):
|
36 |
+
response = self.knowledge_base_client.create_dataset(name="test_dataset")
|
37 |
+
data = response.json()
|
38 |
+
self.assertIn("id", data)
|
39 |
+
self.dataset_id = data["id"]
|
40 |
+
self.assertEqual("test_dataset", data["name"])
|
41 |
+
|
42 |
+
# the following tests require to be executed in order because they use
|
43 |
+
# the dataset/document/segment ids from the previous test
|
44 |
+
self._test_002_list_datasets()
|
45 |
+
self._test_003_create_document_by_text()
|
46 |
+
time.sleep(1)
|
47 |
+
self._test_004_update_document_by_text()
|
48 |
+
# self._test_005_batch_indexing_status()
|
49 |
+
time.sleep(1)
|
50 |
+
self._test_006_update_document_by_file()
|
51 |
+
time.sleep(1)
|
52 |
+
self._test_007_list_documents()
|
53 |
+
self._test_008_delete_document()
|
54 |
+
self._test_009_create_document_by_file()
|
55 |
+
time.sleep(1)
|
56 |
+
self._test_010_add_segments()
|
57 |
+
self._test_011_query_segments()
|
58 |
+
self._test_012_update_document_segment()
|
59 |
+
self._test_013_delete_document_segment()
|
60 |
+
self._test_014_delete_dataset()
|
61 |
+
|
62 |
+
def _test_002_list_datasets(self):
|
63 |
+
response = self.knowledge_base_client.list_datasets()
|
64 |
+
data = response.json()
|
65 |
+
self.assertIn("data", data)
|
66 |
+
self.assertIn("total", data)
|
67 |
+
|
68 |
+
def _test_003_create_document_by_text(self):
|
69 |
+
client = self._get_dataset_kb_client()
|
70 |
+
response = client.create_document_by_text("test_document", "test_text")
|
71 |
+
data = response.json()
|
72 |
+
self.assertIn("document", data)
|
73 |
+
self.document_id = data["document"]["id"]
|
74 |
+
self.batch_id = data["batch"]
|
75 |
+
|
76 |
+
def _test_004_update_document_by_text(self):
|
77 |
+
client = self._get_dataset_kb_client()
|
78 |
+
self.assertIsNotNone(self.document_id)
|
79 |
+
response = client.update_document_by_text(
|
80 |
+
self.document_id, "test_document_updated", "test_text_updated"
|
81 |
+
)
|
82 |
+
data = response.json()
|
83 |
+
self.assertIn("document", data)
|
84 |
+
self.assertIn("batch", data)
|
85 |
+
self.batch_id = data["batch"]
|
86 |
+
|
87 |
+
def _test_005_batch_indexing_status(self):
|
88 |
+
client = self._get_dataset_kb_client()
|
89 |
+
response = client.batch_indexing_status(self.batch_id)
|
90 |
+
response.json()
|
91 |
+
self.assertEqual(response.status_code, 200)
|
92 |
+
|
93 |
+
def _test_006_update_document_by_file(self):
|
94 |
+
client = self._get_dataset_kb_client()
|
95 |
+
self.assertIsNotNone(self.document_id)
|
96 |
+
response = client.update_document_by_file(
|
97 |
+
self.document_id, self.README_FILE_PATH
|
98 |
+
)
|
99 |
+
data = response.json()
|
100 |
+
self.assertIn("document", data)
|
101 |
+
self.assertIn("batch", data)
|
102 |
+
self.batch_id = data["batch"]
|
103 |
+
|
104 |
+
def _test_007_list_documents(self):
|
105 |
+
client = self._get_dataset_kb_client()
|
106 |
+
response = client.list_documents()
|
107 |
+
data = response.json()
|
108 |
+
self.assertIn("data", data)
|
109 |
+
|
110 |
+
def _test_008_delete_document(self):
|
111 |
+
client = self._get_dataset_kb_client()
|
112 |
+
self.assertIsNotNone(self.document_id)
|
113 |
+
response = client.delete_document(self.document_id)
|
114 |
+
data = response.json()
|
115 |
+
self.assertIn("result", data)
|
116 |
+
self.assertEqual("success", data["result"])
|
117 |
+
|
118 |
+
def _test_009_create_document_by_file(self):
|
119 |
+
client = self._get_dataset_kb_client()
|
120 |
+
response = client.create_document_by_file(self.README_FILE_PATH)
|
121 |
+
data = response.json()
|
122 |
+
self.assertIn("document", data)
|
123 |
+
self.document_id = data["document"]["id"]
|
124 |
+
self.batch_id = data["batch"]
|
125 |
+
|
126 |
+
def _test_010_add_segments(self):
|
127 |
+
client = self._get_dataset_kb_client()
|
128 |
+
response = client.add_segments(
|
129 |
+
self.document_id, [{"content": "test text segment 1"}]
|
130 |
+
)
|
131 |
+
data = response.json()
|
132 |
+
self.assertIn("data", data)
|
133 |
+
self.assertGreater(len(data["data"]), 0)
|
134 |
+
segment = data["data"][0]
|
135 |
+
self.segment_id = segment["id"]
|
136 |
+
|
137 |
+
def _test_011_query_segments(self):
|
138 |
+
client = self._get_dataset_kb_client()
|
139 |
+
response = client.query_segments(self.document_id)
|
140 |
+
data = response.json()
|
141 |
+
self.assertIn("data", data)
|
142 |
+
self.assertGreater(len(data["data"]), 0)
|
143 |
+
|
144 |
+
def _test_012_update_document_segment(self):
|
145 |
+
client = self._get_dataset_kb_client()
|
146 |
+
self.assertIsNotNone(self.segment_id)
|
147 |
+
response = client.update_document_segment(
|
148 |
+
self.document_id,
|
149 |
+
self.segment_id,
|
150 |
+
{"content": "test text segment 1 updated"},
|
151 |
+
)
|
152 |
+
data = response.json()
|
153 |
+
self.assertIn("data", data)
|
154 |
+
self.assertGreater(len(data["data"]), 0)
|
155 |
+
segment = data["data"]
|
156 |
+
self.assertEqual("test text segment 1 updated", segment["content"])
|
157 |
+
|
158 |
+
def _test_013_delete_document_segment(self):
|
159 |
+
client = self._get_dataset_kb_client()
|
160 |
+
self.assertIsNotNone(self.segment_id)
|
161 |
+
response = client.delete_document_segment(self.document_id, self.segment_id)
|
162 |
+
data = response.json()
|
163 |
+
self.assertIn("result", data)
|
164 |
+
self.assertEqual("success", data["result"])
|
165 |
+
|
166 |
+
def _test_014_delete_dataset(self):
|
167 |
+
client = self._get_dataset_kb_client()
|
168 |
+
response = client.delete_dataset()
|
169 |
+
self.assertEqual(204, response.status_code)
|
170 |
+
|
171 |
+
|
172 |
+
class TestChatClient(unittest.TestCase):
|
173 |
+
def setUp(self):
|
174 |
+
self.chat_client = ChatClient(API_KEY)
|
175 |
+
|
176 |
+
def test_create_chat_message(self):
|
177 |
+
response = self.chat_client.create_chat_message(
|
178 |
+
{}, "Hello, World!", "test_user"
|
179 |
+
)
|
180 |
+
self.assertIn("answer", response.text)
|
181 |
+
|
182 |
+
def test_create_chat_message_with_vision_model_by_remote_url(self):
|
183 |
+
files = [
|
184 |
+
{"type": "image", "transfer_method": "remote_url", "url": "your_image_url"}
|
185 |
+
]
|
186 |
+
response = self.chat_client.create_chat_message(
|
187 |
+
{}, "Describe the picture.", "test_user", files=files
|
188 |
+
)
|
189 |
+
self.assertIn("answer", response.text)
|
190 |
+
|
191 |
+
def test_create_chat_message_with_vision_model_by_local_file(self):
|
192 |
+
files = [
|
193 |
+
{
|
194 |
+
"type": "image",
|
195 |
+
"transfer_method": "local_file",
|
196 |
+
"upload_file_id": "your_file_id",
|
197 |
+
}
|
198 |
+
]
|
199 |
+
response = self.chat_client.create_chat_message(
|
200 |
+
{}, "Describe the picture.", "test_user", files=files
|
201 |
+
)
|
202 |
+
self.assertIn("answer", response.text)
|
203 |
+
|
204 |
+
def test_get_conversation_messages(self):
|
205 |
+
response = self.chat_client.get_conversation_messages(
|
206 |
+
"test_user", "your_conversation_id"
|
207 |
+
)
|
208 |
+
self.assertIn("answer", response.text)
|
209 |
+
|
210 |
+
def test_get_conversations(self):
|
211 |
+
response = self.chat_client.get_conversations("test_user")
|
212 |
+
self.assertIn("data", response.text)
|
213 |
+
|
214 |
+
|
215 |
+
class TestCompletionClient(unittest.TestCase):
|
216 |
+
def setUp(self):
|
217 |
+
self.completion_client = CompletionClient(API_KEY)
|
218 |
+
|
219 |
+
def test_create_completion_message(self):
|
220 |
+
response = self.completion_client.create_completion_message(
|
221 |
+
{"query": "What's the weather like today?"}, "blocking", "test_user"
|
222 |
+
)
|
223 |
+
self.assertIn("answer", response.text)
|
224 |
+
|
225 |
+
def test_create_completion_message_with_vision_model_by_remote_url(self):
|
226 |
+
files = [
|
227 |
+
{"type": "image", "transfer_method": "remote_url", "url": "your_image_url"}
|
228 |
+
]
|
229 |
+
response = self.completion_client.create_completion_message(
|
230 |
+
{"query": "Describe the picture."}, "blocking", "test_user", files
|
231 |
+
)
|
232 |
+
self.assertIn("answer", response.text)
|
233 |
+
|
234 |
+
def test_create_completion_message_with_vision_model_by_local_file(self):
|
235 |
+
files = [
|
236 |
+
{
|
237 |
+
"type": "image",
|
238 |
+
"transfer_method": "local_file",
|
239 |
+
"upload_file_id": "your_file_id",
|
240 |
+
}
|
241 |
+
]
|
242 |
+
response = self.completion_client.create_completion_message(
|
243 |
+
{"query": "Describe the picture."}, "blocking", "test_user", files
|
244 |
+
)
|
245 |
+
self.assertIn("answer", response.text)
|
246 |
+
|
247 |
+
|
248 |
+
class TestDifyClient(unittest.TestCase):
|
249 |
+
def setUp(self):
|
250 |
+
self.dify_client = DifyClient(API_KEY)
|
251 |
+
|
252 |
+
def test_message_feedback(self):
|
253 |
+
response = self.dify_client.message_feedback(
|
254 |
+
"your_message_id", "like", "test_user"
|
255 |
+
)
|
256 |
+
self.assertIn("success", response.text)
|
257 |
+
|
258 |
+
def test_get_application_parameters(self):
|
259 |
+
response = self.dify_client.get_application_parameters("test_user")
|
260 |
+
self.assertIn("user_input_form", response.text)
|
261 |
+
|
262 |
+
def test_file_upload(self):
|
263 |
+
file_path = "your_image_file_path"
|
264 |
+
file_name = "panda.jpeg"
|
265 |
+
mime_type = "image/jpeg"
|
266 |
+
|
267 |
+
with open(file_path, "rb") as file:
|
268 |
+
files = {"file": (file_name, file, mime_type)}
|
269 |
+
response = self.dify_client.file_upload("test_user", files)
|
270 |
+
self.assertIn("name", response.text)
|
271 |
+
|
272 |
+
|
273 |
+
if __name__ == "__main__":
|
274 |
+
unittest.main()
|