kbot-py-client 2.0.4__tar.gz → 2.0.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
- Copyright (c) 2024 Konverso SAS
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Konverso
2
4
 
3
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,19 +1,21 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kbot-py-client
3
- Version: 2.0.4
4
- Summary: Client for Konverso Kbot
5
- Home-page: https://www.konverso.ai
3
+ Version: 2.0.8
4
+ Summary: Client for Konverso Kbot.
5
+ License: MIT
6
6
  Author: Konverso
7
7
  Author-email: contact@konverso.ai
8
- Classifier: Programming Language :: Python :: 3
8
+ Requires-Python: >=3.12,<4.0
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.6
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: requests (>=2.27.1)
14
+ Project-URL: Homepage, https://www.konverso.ai/
15
+ Project-URL: Source, https://github.com/konverso-ai/kbot-py-client
12
16
  Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: requests>=2.27.1
15
17
 
16
- # README #
18
+ # kbot-py-client #
17
19
  This package contains utilities which you may use to easily interact with Konverso Kbot application.
18
20
 
19
21
  In particular, you may:
@@ -30,13 +32,19 @@ You may contact us:
30
32
  * For any commercial inquiry: contact@konverso.ai
31
33
  * For support: https://konverso.atlassian.net/servicedesk/customer/portals
32
34
 
33
- ### Installation ###
35
+ ## License
36
+ This client code is open source such that you can freely view it, modify it, create your own version.
37
+ We of course appreciate any contribution and sharing of enhancements made.
38
+
39
+ # Installation #
34
40
 
35
41
  You may use pip3 to install the software on your Kbot instance:
36
42
 
37
- First Navigate to your work-area and then invoke:
43
+ pip3 install kbot-py-client
44
+
45
+ Alternatively, you can invoke a more explicity pip installation request, with the URL path:
38
46
 
39
- pip3 install -e git+https://konverso@bitbucket.org/konversoai/kbot-py-client.git#egg=kbot-py-client
47
+ pip3 install -e git+https://github.com/konverso-ai/kbot-py-client.git#egg=kbot-py-client
40
48
 
41
49
  # Usage #
42
50
 
@@ -146,8 +154,30 @@ In this example, we create a conversation between the logged in user and the bot
146
154
  else:
147
155
  print("Could not create conversation due to: ", r.text)
148
156
 
149
- ## Uploading one file to kbot
150
157
 
158
+ ## Command line chatbot
159
+ The following sample is a complete example you may use to kick off a chat for a given user with the kbot chatbot.
160
+ Here there is no user authentication, but impersonation instead, and user/lookup. This example is a good starting point if you are looking at implementing your own chat interface on top of Kbot.
161
+ ```
162
+ # Create a session using an API key with strong privileges
163
+ cli = Client("mytenant.konverso.ai", api_key="34exxxxxxxxxxxxxa7bfb6aeb")
164
+
165
+ # Lookup create the user in kbot
166
+ response = cli.post("user/lookup_create", data={
167
+ "user_name": user_email,
168
+ "account_name": user_email,
169
+ "account_type": "local",
170
+ "external_auth": "nice-in-connect",
171
+ })
172
+ response.raise_for_status()
173
+
174
+ cli.impersonate(user_email, 'local', 'nice-in-connect') #, application_uuid="b0d91200-833f-466c-b5fd-1b0402cc3a36")
175
+
176
+ # Start the client (loop… ends with Ctrl D)
177
+ AsyncChatClient(cli)
178
+ ```
179
+
180
+ ## Uploading one file to kbot
151
181
 
152
182
  ### Prerequisites
153
183
  * A local file that you wish to send
@@ -186,7 +216,7 @@ In this example, we create a conversation between the logged in user and the bot
186
216
 
187
217
  ### Code sample
188
218
  In this example, we simply upload the content of a directory to a folder in the file manager.
189
-
219
+ ```
190
220
  from kbot_client import Client
191
221
  from kbot_client.folder_sync import FolderSync
192
222
 
@@ -195,3 +225,40 @@ In this example, we simply upload the content of a directory to a folder in the
195
225
  syncer = FolderSync(client)
196
226
  syncer.sync("/tmp/my_source_folder/", "1831f-XXXXXXXXXXXXXXXXXXXXXXX")
197
227
  print("Syncing is done :)")
228
+ ```
229
+
230
+ ## A command line chatbot
231
+ We provide a sample command line chatbot, available in Sync or Async mode
232
+
233
+ ### Code sample
234
+ In this example, we simply initialize a chatbot client after creating a relevant User and Impersonating to it.
235
+ This is a good example of implementation of a custom Client for the Kbot product.
236
+
237
+ ```
238
+ # Create your client
239
+ import json
240
+ from kbot_client import Client
241
+ from kbot_client.chat_client import AsyncChatClient, SyncChatClient
242
+
243
+ cli = Client("mytenant.konverso.ai", api_key="mykey")
244
+
245
+ external_auth = "cmd-line-chatbot"
246
+ user_email = "a-user-name"
247
+ assistant = "225d9647-0b41-473d-b097-ef4be363958a"
248
+
249
+ # Create your target user
250
+ response = cli.post("user/lookup_create", data={
251
+ "user_name": user_email,
252
+ "account_name": user_email,
253
+ "account_type": "local",
254
+ "external_auth": external_auth,
255
+ })
256
+ response.raise_for_status()
257
+
258
+ # Impersonate accordingly
259
+ cli.impersonate(user_email, 'local', external_auth)
260
+
261
+ # Start the chat
262
+ AsyncChatClient(cli, assistant=assistant, convert_html_to_text=True)
263
+
264
+ ```
@@ -1,19 +1,4 @@
1
- Metadata-Version: 2.1
2
- Name: kbot-py-client
3
- Version: 2.0.4
4
- Summary: Client for Konverso Kbot
5
- Home-page: https://www.konverso.ai
6
- Author: Konverso
7
- Author-email: contact@konverso.ai
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.6
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: requests>=2.27.1
15
-
16
- # README #
1
+ # kbot-py-client #
17
2
  This package contains utilities which you may use to easily interact with Konverso Kbot application.
18
3
 
19
4
  In particular, you may:
@@ -30,13 +15,19 @@ You may contact us:
30
15
  * For any commercial inquiry: contact@konverso.ai
31
16
  * For support: https://konverso.atlassian.net/servicedesk/customer/portals
32
17
 
33
- ### Installation ###
18
+ ## License
19
+ This client code is open source such that you can freely view it, modify it, create your own version.
20
+ We of course appreciate any contribution and sharing of enhancements made.
21
+
22
+ # Installation #
34
23
 
35
24
  You may use pip3 to install the software on your Kbot instance:
36
25
 
37
- First Navigate to your work-area and then invoke:
26
+ pip3 install kbot-py-client
27
+
28
+ Alternatively, you can invoke a more explicity pip installation request, with the URL path:
38
29
 
39
- pip3 install -e git+https://konverso@bitbucket.org/konversoai/kbot-py-client.git#egg=kbot-py-client
30
+ pip3 install -e git+https://github.com/konverso-ai/kbot-py-client.git#egg=kbot-py-client
40
31
 
41
32
  # Usage #
42
33
 
@@ -146,8 +137,30 @@ In this example, we create a conversation between the logged in user and the bot
146
137
  else:
147
138
  print("Could not create conversation due to: ", r.text)
148
139
 
149
- ## Uploading one file to kbot
150
140
 
141
+ ## Command line chatbot
142
+ The following sample is a complete example you may use to kick off a chat for a given user with the kbot chatbot.
143
+ Here there is no user authentication, but impersonation instead, and user/lookup. This example is a good starting point if you are looking at implementing your own chat interface on top of Kbot.
144
+ ```
145
+ # Create a session using an API key with strong privileges
146
+ cli = Client("mytenant.konverso.ai", api_key="34exxxxxxxxxxxxxa7bfb6aeb")
147
+
148
+ # Lookup create the user in kbot
149
+ response = cli.post("user/lookup_create", data={
150
+ "user_name": user_email,
151
+ "account_name": user_email,
152
+ "account_type": "local",
153
+ "external_auth": "nice-in-connect",
154
+ })
155
+ response.raise_for_status()
156
+
157
+ cli.impersonate(user_email, 'local', 'nice-in-connect') #, application_uuid="b0d91200-833f-466c-b5fd-1b0402cc3a36")
158
+
159
+ # Start the client (loop… ends with Ctrl D)
160
+ AsyncChatClient(cli)
161
+ ```
162
+
163
+ ## Uploading one file to kbot
151
164
 
152
165
  ### Prerequisites
153
166
  * A local file that you wish to send
@@ -186,7 +199,7 @@ In this example, we create a conversation between the logged in user and the bot
186
199
 
187
200
  ### Code sample
188
201
  In this example, we simply upload the content of a directory to a folder in the file manager.
189
-
202
+ ```
190
203
  from kbot_client import Client
191
204
  from kbot_client.folder_sync import FolderSync
192
205
 
@@ -195,3 +208,40 @@ In this example, we simply upload the content of a directory to a folder in the
195
208
  syncer = FolderSync(client)
196
209
  syncer.sync("/tmp/my_source_folder/", "1831f-XXXXXXXXXXXXXXXXXXXXXXX")
197
210
  print("Syncing is done :)")
211
+ ```
212
+
213
+ ## A command line chatbot
214
+ We provide a sample command line chatbot, available in Sync or Async mode
215
+
216
+ ### Code sample
217
+ In this example, we simply initialize a chatbot client after creating a relevant User and Impersonating to it.
218
+ This is a good example of implementation of a custom Client for the Kbot product.
219
+
220
+ ```
221
+ # Create your client
222
+ import json
223
+ from kbot_client import Client
224
+ from kbot_client.chat_client import AsyncChatClient, SyncChatClient
225
+
226
+ cli = Client("mytenant.konverso.ai", api_key="mykey")
227
+
228
+ external_auth = "cmd-line-chatbot"
229
+ user_email = "a-user-name"
230
+ assistant = "225d9647-0b41-473d-b097-ef4be363958a"
231
+
232
+ # Create your target user
233
+ response = cli.post("user/lookup_create", data={
234
+ "user_name": user_email,
235
+ "account_name": user_email,
236
+ "account_type": "local",
237
+ "external_auth": external_auth,
238
+ })
239
+ response.raise_for_status()
240
+
241
+ # Impersonate accordingly
242
+ cli.impersonate(user_email, 'local', external_auth)
243
+
244
+ # Start the chat
245
+ AsyncChatClient(cli, assistant=assistant, convert_html_to_text=True)
246
+
247
+ ```
@@ -1,13 +1,16 @@
1
+ import uuid
1
2
  import time
2
3
  from abc import ABC, abstractmethod
3
4
 
4
5
  class ChatClient(ABC):
5
6
  def __init__(self, client,
6
- render_types=("text", "html"),
7
+ render_types=("text", "html", "markdown"),
7
8
  prompt="{sender}> {message}",
8
9
  user_prompt = "User> ",
9
10
  bye = "Bye, hope to see you again soon",
10
- exit_commands = ("stop", "exit")):
11
+ assistant = None,
12
+ exit_commands = ("stop", "exit"),
13
+ convert_html_to_text=False):
11
14
 
12
15
  self._client = client
13
16
  self._render_types = render_types
@@ -15,10 +18,15 @@ class ChatClient(ABC):
15
18
  self._user_prompt = user_prompt
16
19
  self._bye = bye
17
20
  self._exit_commands = exit_commands
21
+ self._assistant = assistant
22
+ self._convert_html_to_text = convert_html_to_text
18
23
 
19
24
  # Create a new conversation
20
25
  #
21
- response = self._client.post("conversation", data={"username": "bot"})
26
+ response = self._client.post("conversation",
27
+ data={
28
+ "username": "bot",
29
+ "assistant": assistant})
22
30
  response.raise_for_status()
23
31
 
24
32
  # Print the first welcome message
@@ -74,14 +82,21 @@ class ChatClient(ABC):
74
82
  self._display_response(message_content)
75
83
 
76
84
  elif context == "response":
77
- #print(messages_json)
85
+
86
+ stop = not messages_json.get("dialog_in_progress")
78
87
 
79
88
  for one_message in messages_json.get("messages"):
80
- self._sender = one_message.get("sender").get("name")
89
+
81
90
  message_type = one_message.get("type")
82
91
 
83
- if message_type == "stop_topic":
84
- stop = True
92
+ if message_type == "typing":
93
+ continue
94
+
95
+ self._sender = one_message.get("sender").get("name")
96
+
97
+ # Deprecated. Applicable only for old kbot versions (before 2024.01)
98
+ #if message_type == "stop_topic":
99
+ # stop = True
85
100
 
86
101
  if one_message.get("type") != "message":
87
102
  continue
@@ -99,7 +114,33 @@ class ChatClient(ABC):
99
114
  "value": "Hello John",
100
115
  ...
101
116
  """
102
- print(self._prompt.format(sender=self._sender, message=message.get('value')))
117
+
118
+ # Potentially need to convert the message:
119
+ message_format = message.get('format')
120
+
121
+ if message_format == "text":
122
+ message_value = message.get('value')
123
+
124
+ elif message_format == "html":
125
+ if self._convert_html_to_text:
126
+ try:
127
+ from bs4 import BeautifulSoup
128
+ message_value = BeautifulSoup(message.get('value'), 'html.parser').get_text()
129
+ except:
130
+ print("ERROR: HTML to Text conversion requested, but beautifull soup is not installed. Use: ")
131
+ print("pip3 install BeautifulSoup4")
132
+ message_value = message.get('value')
133
+ else:
134
+ message_value = message.get('value')
135
+ elif message_format == "markdown":
136
+ message_value = message.get('value')
137
+
138
+ else:
139
+ print("Unsupported format:", message.get('format'))
140
+ message_value = message.get('value')
141
+
142
+ # finally display the result to the user
143
+ print(self._prompt.format(sender=self._sender, message=message_value))
103
144
 
104
145
  def _start_question_answer_loop(self, conversation_id):
105
146
 
@@ -130,10 +171,11 @@ class ChatClient(ABC):
130
171
  # Send the user question to Kbot.
131
172
  #
132
173
  data = {
133
- "conversation_id": conversation_id,
134
- "type":"message",
135
- "message": question,
136
- "fromUser": True,
174
+ 'conversation_id': conversation_id,
175
+ 'type': 'message',
176
+ 'message': question,
177
+ 'message_id': str(uuid.uuid4()),
178
+ 'assistant': self._assistant
137
179
  }
138
180
 
139
181
  response = self._client.post(f"conversation/{conversation_id}/message", data=data)
@@ -0,0 +1,119 @@
1
+ [tool.poetry]
2
+ name = "kbot-py-client"
3
+ version = "2.0.8"
4
+ description = "Client for Konverso Kbot."
5
+ authors = ["Konverso <contact@konverso.ai>"]
6
+ classifiers=[
7
+ "Programming Language :: Python :: 3",
8
+ "License :: OSI Approved :: MIT License",
9
+ "Operating System :: OS Independent",
10
+ ]
11
+ readme = "README.md"
12
+ license = "MIT"
13
+ packages = [
14
+ { include = "kbot_client" },
15
+ ]
16
+
17
+ [tool.poetry.urls]
18
+ Homepage = "https://www.konverso.ai/"
19
+ Source = "https://github.com/konverso-ai/kbot-py-client"
20
+
21
+ [tool.poetry.dependencies]
22
+ python = "^3.12"
23
+ requests = ">=2.27.1"
24
+
25
+ [tool.poetry.group.dev.dependencies]
26
+ ruff = "0.4.4"
27
+ pytest = "8.2.1"
28
+ pytest-env = "1.1.3"
29
+
30
+ [build-system]
31
+ requires = ["poetry-core"]
32
+ build-backend = "poetry.core.masonry.api"
33
+
34
+ [tool.ruff]
35
+ # Exclude a variety of commonly ignored directories.
36
+ exclude = [
37
+ ".bzr",
38
+ ".direnv",
39
+ ".eggs",
40
+ ".git",
41
+ ".git-rewrite",
42
+ ".hg",
43
+ ".ipynb_checkpoints",
44
+ ".mypy_cache",
45
+ ".nox",
46
+ ".pants.d",
47
+ ".pyenv",
48
+ ".pytest_cache",
49
+ ".pytype",
50
+ ".ruff_cache",
51
+ ".svn",
52
+ ".tox",
53
+ ".venv",
54
+ ".vscode",
55
+ "__pypackages__",
56
+ "_build",
57
+ "buck-out",
58
+ "build",
59
+ "dist",
60
+ "node_modules",
61
+ "site-packages",
62
+ "venv",
63
+ ]
64
+
65
+ # Same as Black.
66
+ line-length = 88
67
+ indent-width = 4
68
+
69
+ # Assume Python 3.10
70
+ target-version = "py310"
71
+
72
+ [tool.pytest]
73
+ testpaths = "tests"
74
+ addopts = "-vv"
75
+ python_files = "test_*.py"
76
+
77
+ # [tool.pytest.ini_options]
78
+ # asyncio_mode = "auto"
79
+
80
+ [tool.ruff.lint]
81
+ # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
82
+ # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
83
+ # McCabe complexity (`C901`) by default.
84
+ select = ["E4", "E7", "E9", "F"]
85
+ ignore = ["F401", "E722"]
86
+
87
+ # Allow fix for all enabled rules (when `--fix`) is provided.
88
+ fixable = ["ALL"]
89
+ unfixable = []
90
+
91
+ # Allow unused variables when underscore-prefixed.
92
+ # dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
93
+
94
+ [tool.ruff.format]
95
+ # Like Black, use double quotes for strings.
96
+ quote-style = "double"
97
+
98
+ # Like Black, indent with spaces, rather than tabs.
99
+ indent-style = "space"
100
+
101
+ # Like Black, respect magic trailing commas.
102
+ skip-magic-trailing-comma = false
103
+
104
+ # Like Black, automatically detect the appropriate line ending.
105
+ line-ending = "auto"
106
+
107
+ # Enable auto-formatting of code examples in docstrings. Markdown,
108
+ # reStructuredText code/literal blocks and doctests are all supported.
109
+ #
110
+ # This is currently disabled by default, but it is planned for this
111
+ # to be opt-out in the future.
112
+ docstring-code-format = false
113
+
114
+ # Set the line length limit used when formatting code snippets in
115
+ # docstrings.
116
+ #
117
+ # This only has an effect when the `docstring-code-format` setting is
118
+ # enabled.
119
+ docstring-code-line-length = "dynamic"
@@ -1,50 +0,0 @@
1
- # These are some examples of commonly ignored file patterns.
2
- # You should customize this list as applicable to your project.
3
- # Learn more about .gitignore:
4
- # https://www.atlassian.com/git/tutorials/saving-changes/gitignore
5
-
6
- # Node artifact files
7
- node_modules/
8
- dist/
9
-
10
- # Compiled Java class files
11
- *.class
12
-
13
- # Compiled Python bytecode
14
- *.py[cod]
15
-
16
- # Log files
17
- *.log
18
-
19
- # Package files
20
- *.jar
21
-
22
- # Maven
23
- target/
24
- dist/
25
-
26
- # JetBrains IDE
27
- .idea/
28
-
29
- # Unit test reports
30
- TEST*.xml
31
-
32
- # Generated by MacOS
33
- .DS_Store
34
-
35
- # Generated by Windows
36
- Thumbs.db
37
-
38
- # Applications
39
- *.app
40
- *.exe
41
- *.war
42
-
43
- # Large media files
44
- *.mp4
45
- *.tiff
46
- *.avi
47
- *.flv
48
- *.mov
49
- *.wmv
50
-
@@ -1,182 +0,0 @@
1
- # README #
2
- This package contains utilities which you may use to easily interact with Konverso Kbot application.
3
-
4
- In particular, you may:
5
-
6
- * Invoke some of the APIs to view / update / create Kbot configuration objects such as Intents, Message, etc.
7
- * Collect metrics showing how the bot is performing.
8
- * Create a Conversation and interact with it, sending message and getting responses
9
-
10
- # See also #
11
-
12
- ## Access Konverso Support
13
- You may contact us:
14
-
15
- * For any commercial inquiry: contact@konverso.ai
16
- * For support: https://konverso.atlassian.net/servicedesk/customer/portals
17
-
18
- ### Installation ###
19
-
20
- You may use pip3 to install the software on your Kbot instance:
21
-
22
- First Navigate to your work-area and then invoke:
23
-
24
- pip3 install -e git+https://konverso@bitbucket.org/konversoai/kbot-py-client.git#egg=kbot-py-client
25
-
26
- # Usage #
27
-
28
- You would typically first need to login and then invoke some of the API wrapped methods.
29
-
30
- ## Login ##
31
-
32
- import json
33
-
34
- from kbot_client import Client
35
-
36
- ### Using user / password ###
37
-
38
- cli = Client("mybot.konverso.ai")
39
- cli.login("myuser", "mysecretpassword")
40
-
41
- The API Key may be created for a given user, with relevant permissions, from the Configuration / Users & Roles / Users / Accounts panel. Create an account of type "Local"
42
-
43
- ### Using and api key ###
44
-
45
- cli = Client("mybot.konverso.ai", api_key="xxxxxxxxxxxxxxxxxxx")
46
-
47
- The API Key may be created for a given user, with relevant permissions, from the Configuration / Users & Roles / Users / Accounts panel. Create an account of type "API Key"
48
-
49
- ## Collect metrics
50
- Once authenticated, you can for example retrieve useful usage metrics, these can be used by a Monitoring application or for some business intelligence rendering:
51
-
52
- metrics = cli.metric().json()
53
- print("Collected metrics:")
54
- print(json.dumps(metrics, indent=4))
55
-
56
- ## Invoke a Workflow and retrieve result
57
- This is the most powerful mechanism, that let you invoke any business and processing logic in a workflow, leveraging all the capabilities of our platform, and retrieve the result
58
-
59
- r = client.request("post",
60
- "workflow/execute",
61
- {
62
- "name": "Test API Workflow",
63
- "keywords": {
64
- "var1": "abc"
65
- },
66
- "result": ["response"]
67
- }
68
- )
69
- print(r.json())
70
-
71
- ## Invoke a search
72
- You may invoke a search on any of the configured Search Contexts
73
- Note the search context UUID passed in the URL, which you may retrieve from our Search Context portal.
74
-
75
- r = client.request("post", "searchcontext/234923-235-sjdhfs-kdjf/search",
76
- {
77
- "sentence": "how to create a classifier",
78
- "num_results": 20,
79
- #"variables": {'kbs': {'value': ['Confluence_Security_KSEC']}}
80
- }
81
- )
82
-
83
-
84
- ## Retrieve object details
85
- You may retrieve list of defined objects. Note that only objects visibled to the logged in users will be returned.
86
-
87
- Here is a sample code that simply checks for a few objects existance:
88
-
89
- ## Get list of objects and check if object with name is present in response
90
- for unit, name in (('intention' ,'Create ticket'),
91
- ('knowledge_base', 'faq'),
92
- ('workflow', 'Transfer to Agent')):
93
- print(f"Get list of '{unit}'")
94
- objs = cli.unit(unit)
95
- if objs:
96
- # Create dict with
97
- # - key : object name
98
- # - value : object json data
99
- data = {obj['name']: obj for obj in objs}
100
- if name in data:
101
- print(f"'{name}' is present")
102
- else:
103
- print(f"'{name}' is not present")
104
- else:
105
- print("Get no data")
106
-
107
- ## Conversation test
108
- In this example, we create a conversation between the logged in user and the bot and then sends a sentence, and check if we get some expected text in the response. This could for example be the basis of automated testing of the bot
109
-
110
- r = cli.conversation(username='bot')
111
- if r.status_code == 201:
112
- cid = r.json().get('id')
113
- print("Created conversation with id '%s'" %(cid,))
114
-
115
- response = cli.message(cid, 'hello')
116
-
117
- # Process bot response
118
- if response:
119
- for resp in response:
120
- for message in resp.get('message', []):
121
- responses.append(message.get('value', '')) # dict {message type: message value}
122
- resp_message = '\n'.join(responses)
123
- print("Received response: ", resp_message)
124
- if 'I am kbot' in resp_message:
125
- print("Excepted response found")
126
- break
127
- else:
128
- print("Did not receive the expected response")
129
- else:
130
- print("Did not receive any response")
131
- else:
132
- print("Could not create conversation due to: ", r.text)
133
-
134
- ## Uploading one file to kbot
135
-
136
-
137
- ### Prerequisites
138
- * A local file that you wish to send
139
- * The UUID of the folder that will receive the files you want to upload
140
-
141
- ### Code sample
142
- ```
143
- current_top_folder = "uuid-of-a-folder"
144
- filepath = "/tmp/test.pdf"
145
- filename = os.path.basename(filepath)
146
-
147
- # Upload to remote filemanager
148
- with open(filepath, "rb") as fd:
149
- files = {
150
- "upload_files": fd
151
- }
152
- params= {
153
- "override": False
154
- }
155
- data = {
156
- "folder": current_top_folder,
157
- "name": file_name,
158
- }
159
- response = self.client.post_file("attachment",
160
- data=data,
161
- params=params,
162
- files=files)
163
- ```
164
-
165
- ## Uploading a batch of files to the file manager
166
-
167
- ### Prerequisites
168
-
169
- * An API key
170
- * The UUID of the folder that will receive the files you want to upload
171
-
172
- ### Code sample
173
- In this example, we simply upload the content of a directory to a folder in the file manager.
174
-
175
- from kbot_client import Client
176
- from kbot_client.folder_sync import FolderSync
177
-
178
- client = Client("mybot.konverso.ai", api_key="17ebXXXXXXXXXXXXXXXXXXXXX")
179
-
180
- syncer = FolderSync(client)
181
- syncer.sync("/tmp/my_source_folder/", "1831f-XXXXXXXXXXXXXXXXXXXXXXX")
182
- print("Syncing is done :)")
@@ -1,316 +0,0 @@
1
- """Dialog tester"""
2
- import json
3
- import time
4
-
5
- import requests
6
-
7
-
8
- class Client:
9
- """Base representation of Kbot instance"""
10
-
11
- def __init__(self, server, port=443, proto='https', api_key=None, verify=True):
12
- """
13
-
14
-
15
- Arguments:
16
- - server: an Host or Ip Address
17
- - port : default to 443
18
- - proto: HTTP or HTTPS
19
- - api_key: A Kbot valid API Key. If provided all requests will be sent with this API Key
20
- and it is not required to invoke the login method
21
- """
22
-
23
- self.host = server
24
- self.verify = verify
25
-
26
- if str(port).endswith('443'):
27
- proto = 'https'
28
- else:
29
- proto = 'http'
30
-
31
- if port in (80, 443):
32
- self.url = "%s://%s" % (proto, server)
33
- else:
34
- self.url = "%s://%s:%s" % (proto, server, port)
35
-
36
- self._login = False
37
- self._headers = {}
38
-
39
- self._recording_fd = None
40
-
41
- if api_key:
42
- self._headers = {
43
- 'Content-Type': 'application/json; charset=utf-8',
44
- 'X-API-KEY': api_key
45
- }
46
- self.schema()
47
-
48
- # Variable populated by the login:
49
- self._user_id = None
50
-
51
- # Variable populated by the conversation
52
- self._cid = None
53
-
54
- # The refresh token may be used in case
55
- # the access token as expired
56
- self.__refresh_token = None
57
-
58
- @property
59
- def admin_url(self):
60
- """Returns the URL of the Kbot Administration view"""
61
- return "%s/admin" % self.url
62
-
63
- @property
64
- def chat_url(self):
65
- """Returns the default URL of the Kbot chat view"""
66
- return "%s" % self.url
67
-
68
- @property
69
- def avatar_url(self):
70
- return "%s/images/kbot_avatar.png" % self.url
71
-
72
- def record_start(self, file_descriptor):
73
- self._recording_fd = file_descriptor
74
- self._recording_fd.write("import requests\n\n")
75
-
76
- def record_stop(self):
77
- self._recording_fd = None
78
-
79
- def login(self, username, password=None, timeout=5):
80
- """Login to local channel"""
81
- data = {}
82
- data['username'] = username
83
- data['usertype'] = 'local'
84
- if password is not None:
85
- data['password'] = password
86
- else:
87
- print("WARNING!!! Password is not set!!! Trying to use username...")
88
- data['password'] = username
89
-
90
- headers = {}
91
- headers['Content-Type'] = 'application/json; charset=utf-8'
92
-
93
- url = self.url + '/api/login'
94
- r = requests.post(url, headers=headers, data=json.dumps(data), timeout=timeout, verify=self.verify)
95
-
96
- r.raise_for_status()
97
-
98
- self.__reset_headers(r.json())
99
- self.schema()
100
-
101
- def impersonate(self, username, usertype='local', external_auth='', application_uuid='', timeout=5):
102
- """Impersonate the given user"""
103
-
104
- r = self.request("post", "user/impersonate", data={
105
- "username": username,
106
- "im_type": usertype,
107
- "external_auth": external_auth,
108
- "application_uuid": application_uuid})
109
-
110
- r.raise_for_status()
111
-
112
- self.__reset_headers(r.json())
113
- # After we impersonnate, we need to remove the original API KEY
114
- if "X-API-KEY" in self._headers:
115
- del self._headers["X-API-KEY"]
116
-
117
- self.schema()
118
-
119
- def schema(self):
120
- """Retrieve the public schema of kbot and builds python method for each of the defined end points"""
121
- r = self.get('schema')
122
- r.raise_for_status()
123
-
124
- j = r.json()
125
- for epoint in j.get('endpoints', []):
126
- if epoint['name'] != 'schema':
127
- self.__add_method(epoint['method'], epoint['name'], epoint['path'], epoint['params'], epoint['data'], epoint.get('description', ''))
128
-
129
- def __reset_headers(self, js):
130
- """
131
- Called when a login or impersonate end point is called. Then expecting to get a json with:
132
- - access_token
133
- - refresh_token
134
- Or in the case of a refresh call. Then we only get a json with:
135
- - access_token
136
-
137
- The header content is ajusted with the token information from the json
138
- """
139
- if not self._headers:
140
- self._headers = {}
141
- self._headers['Content-Type'] = 'application/json; charset=utf-8'
142
-
143
- if js.get('access_token'):
144
- self._headers['Authorization'] = js['access_token']
145
-
146
- # The refresh token will be used by the refresh_token method later if the access token expires
147
- if js.get('refresh_token'):
148
- self.__refresh_token = js['refresh_token']
149
-
150
- self._login = True
151
- if js.get("user_id"):
152
- self._user_id = js['user_id']
153
-
154
- def __add_method(self, method: str, name: str, path: str, params: list, data: list, description: str):
155
- """Method to add a new dynamic method, based on the schema. This provides "python method" wrapper
156
- on top of the kbot APIs
157
- """
158
- def endpoint(*args, **kwargs):
159
- """Invoke request to endpoint"""
160
- rargs = {}
161
- for aname, values in (('params', params), ('data', data)):
162
- rargs[aname] = {}
163
- for value in values:
164
- if value['mandatory'] and value['name'] not in kwargs:
165
- raise RuntimeError("Missed attribute '%s' in '%s'" % (value['name'], aname))
166
- # pylint: disable=eval-used
167
- if value['name'] in kwargs:
168
- if not isinstance(kwargs[value['name']], eval(value['type'])):
169
- raise RuntimeError("Invalid type of attribute '%s'" % (value['name']))
170
- v = kwargs[value['name']]
171
- elif value['name'] not in kwargs and value['default'] is not None:
172
- v = value['default']
173
- else:
174
- continue
175
- rargs[aname][value['name']] = v
176
- return self.__request(method, uri=path % args, **rargs)
177
- endpoint.__doc__ = description
178
- endpoint.__name__ = name
179
- setattr(self, name, endpoint)
180
-
181
- def __refresh(self):
182
- r = requests.post(self.url + "/api/refresh",
183
- data=json.dumps({"refresh_token": self.__refresh_token}),
184
- headers=self._headers)
185
- r.raise_for_status()
186
- self.__reset_headers(r.json())
187
-
188
- def __request(self, method: str, uri : str = None, data: dict = None, params: dict = None, files: dict = None, attempt=0):
189
- dump_data = json.dumps(data or {})
190
-
191
- if files:
192
- headers = self._headers.copy()
193
- del headers['Content-Type']
194
- headers['Accept'] = "*/*"
195
- else:
196
- headers = self._headers
197
-
198
- r = requests.request(method.upper(), self.url + '/api/%s/' % (uri), params=params, data=dump_data, headers=headers, verify=self.verify)
199
-
200
- # The recording if configured to do so
201
- if self._recording_fd:
202
- self._recording_fd.write("\n## {uri}".format(uri='/api/%s/' % (uri)))
203
- self._recording_fd.write("\nheaders = {headers}".format(headers=json.dumps(headers)))
204
- self._recording_fd.write("\nparams = {params}".format(params=json.dumps(params)))
205
- self._recording_fd.write("\ndata = {data}".format(data=json.dumps(data)))
206
- self._recording_fd.write("\nr = requests.request({method}, {url}, params=params, data=data)".format(
207
- method=method.upper(), url=self.url + '/api/%s/' % (uri)))
208
- self._recording_fd.write("\nr.raise_for_status()")
209
-
210
- if r.status_code == 401:
211
- # Refresh the token
212
- try:
213
- self.__refresh()
214
- except Exception as e:
215
-
216
- # We try 3 times at the most
217
- if attempt == 3:
218
- raise e
219
-
220
- time.sleep(3)
221
-
222
- # Re-invoke the request
223
- r = self.__request(method, uri=uri, data=data, params=params, attempt=attempt+1)
224
-
225
- return r
226
-
227
- def request(self, method: str, uri: str, data: dict = None, params: dict = None):
228
- return self.__request(method, uri=uri, data=data, params=params)
229
-
230
- def unit(self, name: str, params=None) -> dict:
231
- r = self.get(name, params)
232
- if r:
233
- return r.json()
234
- return None
235
-
236
- def message(self, cid: int, message: str, timeout: int=60) -> list:
237
- response = []
238
-
239
- data = {'type': 'message', 'message': message}
240
- r = self.request("post", f'conversation/{cid}/message', data)
241
-
242
- r.raise_for_status()
243
-
244
- curtime = time.time()
245
- while timeout > 0:
246
- r = self.get(f'conversation/{cid}')
247
-
248
- r.raise_for_status()
249
-
250
- j = r.json()
251
- for resp in j:
252
- if resp['type'] == 'message':
253
- # It's possible that bot will send several message to one input
254
- response.append(resp)
255
- elif resp['type'] in ('stop_topic', 'wait_user_input'):
256
- # Bot stop to process
257
- # - stop_topic : bot stop to process message and ready for new topic
258
- # - wait_user_input : bot asked the question and wait for user answer
259
- timeout = 0
260
- timeout = timeout + curtime - time.time()
261
- return response
262
-
263
- def logout(self):
264
- """Logout from local channel"""
265
- if self._login:
266
-
267
- # If we have an open conversation, close it.
268
- # if self._cid:
269
- # What for?
270
- # self._process('logout', 1)
271
-
272
- # Logout from the APIs
273
- requests.post(self.url + '/api/logout', headers=self._headers, verify=self.verify)
274
-
275
- #
276
- # In addition to the Generated and built in API methods, we have the classic base REST methods
277
- #
278
- def get(self, unit, params=None):
279
- return self.__request("get", unit, params=params)
280
-
281
- def put(self, unit, data=None):
282
- return self.__request("put", unit, data=data)
283
-
284
- def post(self, unit, data=None):
285
- return self.__request("post", unit, data=data)
286
-
287
- def delete(self, unit, params=None):
288
- return self.__request("delete", unit, params=params)
289
-
290
- def post_file(self, unit, data, params=None, files=None):
291
- """Attach the given files.
292
- Sample parameter values:
293
- unit = "attachment"
294
- files = {
295
- "upload_files": (f, fd, "application/pdf")
296
- }
297
- params= {
298
- "override": False
299
- }
300
- data = {
301
- "folder": current_top_folder,
302
- "name": f,
303
- }
304
- """
305
- return self.__request("post", unit, params=params, data=data, files=files)
306
-
307
- class UpKbotClient(Client):
308
- """Represents a currently reachable Kbot instance"""
309
-
310
-
311
- class DownKbotClient(Client):
312
- """Represents a currently not reachable Kbot instance"""
313
-
314
- def __init__(self, server, port=443, proto='http', error=''):
315
- super().__init__(server, port=port, proto=proto)
316
- self.error = error
@@ -1,16 +0,0 @@
1
- .gitignore
2
- LICENSE
3
- README.md
4
- setup.py
5
- kbot_client/__init__.py
6
- kbot_client/chat_client.py
7
- kbot_client/client.py
8
- kbot_client/client.recording.py
9
- kbot_client/client_group.py
10
- kbot_client/folder_sync.py
11
- kbot_client/test.py
12
- kbot_py_client.egg-info/PKG-INFO
13
- kbot_py_client.egg-info/SOURCES.txt
14
- kbot_py_client.egg-info/dependency_links.txt
15
- kbot_py_client.egg-info/requires.txt
16
- kbot_py_client.egg-info/top_level.txt
@@ -1 +0,0 @@
1
- requests>=2.27.1
@@ -1 +0,0 @@
1
- kbot_client
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -1,23 +0,0 @@
1
- import setuptools
2
-
3
- with open("README.md", mode="r", encoding="utf-8") as fh:
4
- long_description = fh.read()
5
-
6
- setuptools.setup(
7
- name="kbot-py-client",
8
- version="2.0.4",
9
- author="Konverso",
10
- author_email="contact@konverso.ai",
11
- description="Client for Konverso Kbot",
12
- long_description=long_description,
13
- long_description_content_type="text/markdown",
14
- url="https://www.konverso.ai",
15
- packages=setuptools.find_packages(),
16
- install_requires=["requests>=2.27.1"],
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
- )
@@ -36,6 +36,10 @@ class Client:
36
36
  self._login = False
37
37
  self._headers = {}
38
38
 
39
+ # The refresh token may be used in case
40
+ # the access token as expired
41
+ self.__refresh_token = None
42
+
39
43
  if api_key:
40
44
  self._headers = {
41
45
  'Content-Type': 'application/json; charset=utf-8',
@@ -49,10 +53,6 @@ class Client:
49
53
  # Variable populated by the conversation
50
54
  self._cid = None
51
55
 
52
- # The refresh token may be used in case
53
- # the access token as expired
54
- self.__refresh_token = None
55
-
56
56
  @property
57
57
  def admin_url(self):
58
58
  """Returns the URL of the Kbot Administration view"""