cwyodmodules 0.0.2__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.
Files changed (90) hide show
  1. cwyodmodules-0.0.2/LICENSE +27 -0
  2. cwyodmodules-0.0.2/PKG-INFO +50 -0
  3. cwyodmodules-0.0.2/README.md +7 -0
  4. cwyodmodules-0.0.2/backend/api/__init__.py +0 -0
  5. cwyodmodules-0.0.2/backend/api/chat_history.py +510 -0
  6. cwyodmodules-0.0.2/backend/batch/__init__.py +0 -0
  7. cwyodmodules-0.0.2/backend/batch/utilities/__init__.py +0 -0
  8. cwyodmodules-0.0.2/backend/batch/utilities/chat_history/auth_utils.py +43 -0
  9. cwyodmodules-0.0.2/backend/batch/utilities/chat_history/cosmosdb.py +205 -0
  10. cwyodmodules-0.0.2/backend/batch/utilities/chat_history/database_client_base.py +82 -0
  11. cwyodmodules-0.0.2/backend/batch/utilities/chat_history/database_factory.py +59 -0
  12. cwyodmodules-0.0.2/backend/batch/utilities/chat_history/postgresdbservice.py +159 -0
  13. cwyodmodules-0.0.2/backend/batch/utilities/chat_history/sample_user.py +39 -0
  14. cwyodmodules-0.0.2/backend/batch/utilities/common/__init__.py +0 -0
  15. cwyodmodules-0.0.2/backend/batch/utilities/common/answer.py +65 -0
  16. cwyodmodules-0.0.2/backend/batch/utilities/common/source_document.py +143 -0
  17. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/__init__.py +12 -0
  18. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/chunking_strategy.py +56 -0
  19. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/document_chunking_base.py +16 -0
  20. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/fixed_size_overlap.py +42 -0
  21. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/layout.py +43 -0
  22. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/page.py +40 -0
  23. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/paragraph.py +14 -0
  24. cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/strategies.py +35 -0
  25. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/__init__.py +24 -0
  26. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/document_loading_base.py +13 -0
  27. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/layout.py +25 -0
  28. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/read.py +25 -0
  29. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/strategies.py +25 -0
  30. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/web.py +30 -0
  31. cwyodmodules-0.0.2/backend/batch/utilities/document_loading/word_document.py +45 -0
  32. cwyodmodules-0.0.2/backend/batch/utilities/helpers/__init__.py +0 -0
  33. cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_blob_storage_client.py +268 -0
  34. cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_computer_vision_client.py +96 -0
  35. cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_form_recognizer_helper.py +155 -0
  36. cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_postgres_helper.py +275 -0
  37. cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_search_helper.py +279 -0
  38. cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/assistant_strategy.py +7 -0
  39. cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/config_helper.py +330 -0
  40. cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/conversation_flow.py +6 -0
  41. cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/database_type.py +6 -0
  42. cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/embedding_config.py +27 -0
  43. cwyodmodules-0.0.2/backend/batch/utilities/helpers/document_chunking_helper.py +22 -0
  44. cwyodmodules-0.0.2/backend/batch/utilities/helpers/document_loading_helper.py +18 -0
  45. cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/embedder_base.py +23 -0
  46. cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/embedder_factory.py +20 -0
  47. cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/integrated_vectorization_embedder.py +59 -0
  48. cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/postgres_embedder.py +111 -0
  49. cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/push_embedder.py +194 -0
  50. cwyodmodules-0.0.2/backend/batch/utilities/helpers/env_helper.py +359 -0
  51. cwyodmodules-0.0.2/backend/batch/utilities/helpers/llm_helper.py +179 -0
  52. cwyodmodules-0.0.2/backend/batch/utilities/helpers/orchestrator_helper.py +29 -0
  53. cwyodmodules-0.0.2/backend/batch/utilities/helpers/secret_helper.py +84 -0
  54. cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_datasource.py +47 -0
  55. cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_index.py +176 -0
  56. cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_indexer.py +70 -0
  57. cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_skillset.py +141 -0
  58. cwyodmodules-0.0.2/backend/batch/utilities/loggers/conversation_logger.py +80 -0
  59. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/__init__.py +18 -0
  60. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/lang_chain_agent.py +162 -0
  61. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/open_ai_functions.py +190 -0
  62. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/orchestration_strategy.py +18 -0
  63. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/orchestrator_base.py +153 -0
  64. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/prompt_flow.py +188 -0
  65. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/semantic_kernel.py +178 -0
  66. cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/strategies.py +29 -0
  67. cwyodmodules-0.0.2/backend/batch/utilities/parser/__init__.py +11 -0
  68. cwyodmodules-0.0.2/backend/batch/utilities/parser/output_parser_tool.py +140 -0
  69. cwyodmodules-0.0.2/backend/batch/utilities/parser/parser_base.py +19 -0
  70. cwyodmodules-0.0.2/backend/batch/utilities/plugins/chat_plugin.py +79 -0
  71. cwyodmodules-0.0.2/backend/batch/utilities/plugins/post_answering_plugin.py +32 -0
  72. cwyodmodules-0.0.2/backend/batch/utilities/search/azure_search_handler.py +189 -0
  73. cwyodmodules-0.0.2/backend/batch/utilities/search/integrated_vectorization_search_handler.py +181 -0
  74. cwyodmodules-0.0.2/backend/batch/utilities/search/postgres_search_handler.py +104 -0
  75. cwyodmodules-0.0.2/backend/batch/utilities/search/search.py +63 -0
  76. cwyodmodules-0.0.2/backend/batch/utilities/search/search_handler_base.py +67 -0
  77. cwyodmodules-0.0.2/backend/batch/utilities/tools/__init__.py +0 -0
  78. cwyodmodules-0.0.2/backend/batch/utilities/tools/answer_processing_base.py +12 -0
  79. cwyodmodules-0.0.2/backend/batch/utilities/tools/answering_tool_base.py +15 -0
  80. cwyodmodules-0.0.2/backend/batch/utilities/tools/content_safety_checker.py +80 -0
  81. cwyodmodules-0.0.2/backend/batch/utilities/tools/post_prompt_tool.py +56 -0
  82. cwyodmodules-0.0.2/backend/batch/utilities/tools/question_answer_tool.py +301 -0
  83. cwyodmodules-0.0.2/backend/batch/utilities/tools/text_processing_tool.py +37 -0
  84. cwyodmodules-0.0.2/cwyodmodules.egg-info/PKG-INFO +50 -0
  85. cwyodmodules-0.0.2/cwyodmodules.egg-info/SOURCES.txt +88 -0
  86. cwyodmodules-0.0.2/cwyodmodules.egg-info/dependency_links.txt +1 -0
  87. cwyodmodules-0.0.2/cwyodmodules.egg-info/requires.txt +31 -0
  88. cwyodmodules-0.0.2/cwyodmodules.egg-info/top_level.txt +1 -0
  89. cwyodmodules-0.0.2/pyproject.toml +46 -0
  90. cwyodmodules-0.0.2/setup.cfg +4 -0
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 NeuralNine
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.
22
+ © 2022 GitHub, Inc.
23
+ Terms
24
+ Privacy
25
+ Security
26
+ Status
27
+ Docs
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: cwyodmodules
3
+ Version: 0.0.2
4
+ Summary: Add your description here
5
+ Author-email: Patrik <patrikhartl@gmail.com>
6
+ Classifier: Programming Language :: Python :: 3.11
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: ==3.11.11
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: azure-monitor-opentelemetry<2.0.0,>=1.6.4
12
+ Requires-Dist: opentelemetry-instrumentation-httpx<0.52,>=0.51b0
13
+ Requires-Dist: openai<2.0.0,>=1.63.0
14
+ Requires-Dist: flask<4.0.0,>=3.1.0
15
+ Requires-Dist: azure-mgmt-cognitiveservices<14.0.0,>=13.6.0
16
+ Requires-Dist: azure-identity<2.0.0,>=1.20.0
17
+ Requires-Dist: azure-cosmos<5.0.0,>=4.9.0
18
+ Requires-Dist: asyncpg<0.31.0,>=0.30.0
19
+ Requires-Dist: langchain<0.4.0,>=0.3.18
20
+ Requires-Dist: azure-storage-queue<13.0.0,>=12.12.0
21
+ Requires-Dist: chardet<6.0.0,>=5.2.0
22
+ Requires-Dist: azure-ai-formrecognizer<4.0.0,>=3.3.3
23
+ Requires-Dist: langchain-chroma<0.3.0,>=0.2.2
24
+ Requires-Dist: langchain-openai<0.4.0,>=0.3.5
25
+ Requires-Dist: langchain-community<0.4.0,>=0.3.17
26
+ Requires-Dist: azure-search<2.0.0,>=1.0.0b2
27
+ Requires-Dist: azure-search-documents==11.6.0b4
28
+ Requires-Dist: azure-functions<2.0.0,>=1.21.3
29
+ Requires-Dist: azure-ai-ml<2.0.0,>=1.25.0
30
+ Requires-Dist: python-docx<2.0.0,>=1.1.2
31
+ Requires-Dist: azure-ai-contentsafety<2.0.0,>=1.0.0
32
+ Requires-Dist: httpx<0.29.0,>=0.28.1
33
+ Requires-Dist: semantic-kernel<2.0.0,>=1.21.2
34
+ Requires-Dist: requests<3.0.0,>=2.32.3
35
+ Requires-Dist: azure-cognitiveservices-speech<2.0.0,>=1.42.0
36
+ Requires-Dist: azure-keyvault-secrets<5.0.0,>=4.9.0
37
+ Requires-Dist: psycopg==3.2.4
38
+ Requires-Dist: psycopg2-binary==2.9.10
39
+ Requires-Dist: azure-storage-blob==12.20.0
40
+ Requires-Dist: beautifulsoup4==4.13.3
41
+ Requires-Dist: azure-ai-inference==1.0.0b9
42
+ Dynamic: license-file
43
+
44
+ # paddypy
45
+
46
+ Under construction! Not ready for use yet! Currently experimenting and planning!
47
+
48
+ Developed by Patrik (c) 2025
49
+
50
+ ## Examples of How To Use (Alpha Version)
@@ -0,0 +1,7 @@
1
+ # paddypy
2
+
3
+ Under construction! Not ready for use yet! Currently experimenting and planning!
4
+
5
+ Developed by Patrik (c) 2025
6
+
7
+ ## Examples of How To Use (Alpha Version)
File without changes
@@ -0,0 +1,510 @@
1
+ import os
2
+ import logging
3
+ from uuid import uuid4
4
+ #from dotenv import load_dotenv
5
+ from flask import request, jsonify, Blueprint
6
+ from openai import AsyncAzureOpenAI
7
+ from backend.batch.utilities.chat_history.auth_utils import (
8
+ get_authenticated_user_details,
9
+ )
10
+ from backend.batch.utilities.helpers.config.config_helper import ConfigHelper
11
+ from backend.batch.utilities.helpers.env_helper import EnvHelper
12
+ from backend.batch.utilities.chat_history.database_factory import DatabaseFactory
13
+
14
+ #load_dotenv()
15
+ bp_chat_history_response = Blueprint("chat_history", __name__)
16
+ logger = logging.getLogger(__name__)
17
+ logger.setLevel(level=os.environ.get("LOGLEVEL", "INFO").upper())
18
+
19
+ env_helper: EnvHelper = EnvHelper()
20
+
21
+
22
+ def init_database_client():
23
+ try:
24
+ conversation_client = DatabaseFactory.get_conversation_client()
25
+ return conversation_client
26
+ except Exception as e:
27
+ logger.exception("Exception in database initialization: %s", e)
28
+ raise e
29
+
30
+
31
+ def init_openai_client():
32
+ try:
33
+ if env_helper.is_auth_type_keys():
34
+ azure_openai_client = AsyncAzureOpenAI(
35
+ azure_endpoint=env_helper.AZURE_OPENAI_ENDPOINT,
36
+ api_version=env_helper.AZURE_OPENAI_API_VERSION,
37
+ api_key=env_helper.AZURE_OPENAI_API_KEY,
38
+ )
39
+ else:
40
+ azure_openai_client = AsyncAzureOpenAI(
41
+ azure_endpoint=env_helper.AZURE_OPENAI_ENDPOINT,
42
+ api_version=env_helper.AZURE_OPENAI_API_VERSION,
43
+ azure_ad_token_provider=env_helper.AZURE_TOKEN_PROVIDER,
44
+ )
45
+ return azure_openai_client
46
+ except Exception as e:
47
+ logging.exception("Exception in Azure OpenAI initialization: %s", e)
48
+ raise e
49
+
50
+
51
+ @bp_chat_history_response.route("/history/list", methods=["GET"])
52
+ async def list_conversations():
53
+ config = ConfigHelper.get_active_config_or_default()
54
+ if not config.enable_chat_history:
55
+ return jsonify({"error": "Chat history is not available"}), 400
56
+
57
+ try:
58
+ offset = request.args.get("offset", 0)
59
+ authenticated_user = get_authenticated_user_details(
60
+ request_headers=request.headers
61
+ )
62
+ user_id = authenticated_user["user_principal_id"]
63
+ conversation_client = init_database_client()
64
+ if not conversation_client:
65
+ return jsonify({"error": "Database not available"}), 500
66
+
67
+ await conversation_client.connect()
68
+ try:
69
+ conversations = await conversation_client.get_conversations(
70
+ user_id, offset=offset, limit=25
71
+ )
72
+ if not isinstance(conversations, list):
73
+ return (
74
+ jsonify({"error": f"No conversations for {user_id} were found"}),
75
+ 404,
76
+ )
77
+
78
+ return jsonify(conversations), 200
79
+ except Exception as e:
80
+ logger.exception(f"Error fetching conversations: {e}")
81
+ raise
82
+ finally:
83
+ await conversation_client.close()
84
+
85
+ except Exception as e:
86
+ logger.exception(f"Exception in /history/list: {e}")
87
+ return jsonify({"error": "Error while listing historical conversations"}), 500
88
+
89
+
90
+ @bp_chat_history_response.route("/history/rename", methods=["POST"])
91
+ async def rename_conversation():
92
+ config = ConfigHelper.get_active_config_or_default()
93
+ if not config.enable_chat_history:
94
+ return jsonify({"error": "Chat history is not available"}), 400
95
+
96
+ try:
97
+ authenticated_user = get_authenticated_user_details(
98
+ request_headers=request.headers
99
+ )
100
+ user_id = authenticated_user["user_principal_id"]
101
+
102
+ # check request for conversation_id
103
+ request_json = request.get_json()
104
+ conversation_id = request_json.get("conversation_id", None)
105
+
106
+ if not conversation_id:
107
+ return (jsonify({"error": "conversation_id is required"}), 400)
108
+
109
+ title = request_json.get("title", None)
110
+ if not title or title.strip() == "":
111
+ return jsonify({"error": "A non-empty title is required"}), 400
112
+
113
+ # Initialize and connect to the database client
114
+ conversation_client = init_database_client()
115
+ if not conversation_client:
116
+ return jsonify({"error": "Database not available"}), 500
117
+
118
+ await conversation_client.connect()
119
+ try:
120
+ # Retrieve conversation from database
121
+ conversation = await conversation_client.get_conversation(
122
+ user_id, conversation_id
123
+ )
124
+ if not conversation:
125
+ return (
126
+ jsonify(
127
+ {
128
+ "error": f"Conversation {conversation_id} was not found. It either does not exist or the logged in user does not have access to it."
129
+ }
130
+ ),
131
+ 400,
132
+ )
133
+
134
+ # Update the title and save changes
135
+ conversation["title"] = title
136
+ updated_conversation = await conversation_client.upsert_conversation(
137
+ conversation
138
+ )
139
+ return jsonify(updated_conversation), 200
140
+ except Exception as e:
141
+ logger.exception(
142
+ f"Error updating conversation: user_id={user_id}, conversation_id={conversation_id}, error={e}"
143
+ )
144
+ raise
145
+ finally:
146
+ await conversation_client.close()
147
+ except Exception as e:
148
+ logger.exception(f"Exception in /history/rename: {e}")
149
+ return jsonify({"error": "Error while renaming conversation"}), 500
150
+
151
+
152
+ @bp_chat_history_response.route("/history/read", methods=["POST"])
153
+ async def get_conversation():
154
+ config = ConfigHelper.get_active_config_or_default()
155
+ if not config.enable_chat_history:
156
+ return jsonify({"error": "Chat history is not available"}), 400
157
+
158
+ try:
159
+ authenticated_user = get_authenticated_user_details(
160
+ request_headers=request.headers
161
+ )
162
+ user_id = authenticated_user["user_principal_id"]
163
+
164
+ # check request for conversation_id
165
+ request_json = request.get_json()
166
+ conversation_id = request_json.get("conversation_id", None)
167
+ if not conversation_id:
168
+ return jsonify({"error": "conversation_id is required"}), 400
169
+
170
+ # Initialize and connect to the database client
171
+ conversation_client = init_database_client()
172
+ if not conversation_client:
173
+ return jsonify({"error": "Database not available"}), 500
174
+
175
+ await conversation_client.connect()
176
+ try:
177
+ # Retrieve conversation
178
+ conversation = await conversation_client.get_conversation(
179
+ user_id, conversation_id
180
+ )
181
+ if not conversation:
182
+ return (
183
+ jsonify(
184
+ {
185
+ "error": f"Conversation {conversation_id} was not found. It either does not exist or the logged in user does not have access to it."
186
+ }
187
+ ),
188
+ 400,
189
+ )
190
+
191
+ # Fetch conversation messages
192
+ conversation_messages = await conversation_client.get_messages(
193
+ user_id, conversation_id
194
+ )
195
+ messages = [
196
+ {
197
+ "id": msg["id"],
198
+ "role": msg["role"],
199
+ "content": msg["content"],
200
+ "createdAt": msg["createdAt"],
201
+ "feedback": msg.get("feedback"),
202
+ }
203
+ for msg in conversation_messages
204
+ ]
205
+
206
+ # Return formatted conversation and messages
207
+ return (
208
+ jsonify({"conversation_id": conversation_id, "messages": messages}),
209
+ 200,
210
+ )
211
+ except Exception as e:
212
+ logger.exception(
213
+ f"Error fetching conversation or messages: user_id={user_id}, conversation_id={conversation_id}, error={e}"
214
+ )
215
+ raise
216
+ finally:
217
+ await conversation_client.close()
218
+
219
+ except Exception as e:
220
+ logger.exception(f"Exception in /history/read: {e}")
221
+ return jsonify({"error": "Error while fetching conversation history"}), 500
222
+
223
+
224
+ @bp_chat_history_response.route("/history/delete", methods=["DELETE"])
225
+ async def delete_conversation():
226
+ config = ConfigHelper.get_active_config_or_default()
227
+ if not config.enable_chat_history:
228
+ return jsonify({"error": "Chat history is not available"}), 400
229
+
230
+ try:
231
+ # Get the user ID from the request headers
232
+ authenticated_user = get_authenticated_user_details(
233
+ request_headers=request.headers
234
+ )
235
+ user_id = authenticated_user["user_principal_id"]
236
+ # check request for conversation_id
237
+ request_json = request.get_json()
238
+ conversation_id = request_json.get("conversation_id", None)
239
+ if not conversation_id:
240
+ return (
241
+ jsonify(
242
+ {
243
+ "error": f"Conversation {conversation_id} was not found. It either does not exist or the logged in user does not have access to it."
244
+ }
245
+ ),
246
+ 400,
247
+ )
248
+
249
+ # Initialize and connect to the database client
250
+ conversation_client = init_database_client()
251
+ if not conversation_client:
252
+ return jsonify({"error": "Database not available"}), 500
253
+
254
+ await conversation_client.connect()
255
+ try:
256
+ # Delete conversation messages from database
257
+ await conversation_client.delete_messages(conversation_id, user_id)
258
+
259
+ # Delete the conversation itself
260
+ await conversation_client.delete_conversation(user_id, conversation_id)
261
+
262
+ return (
263
+ jsonify(
264
+ {
265
+ "message": "Successfully deleted conversation and messages",
266
+ "conversation_id": conversation_id,
267
+ }
268
+ ),
269
+ 200,
270
+ )
271
+ except Exception as e:
272
+ logger.exception(
273
+ f"Error deleting conversation: user_id={user_id}, conversation_id={conversation_id}, error={e}"
274
+ )
275
+ raise
276
+ finally:
277
+ await conversation_client.close()
278
+
279
+ except Exception as e:
280
+ logger.exception(f"Exception in /history/delete: {e}")
281
+ return jsonify({"error": "Error while deleting conversation history"}), 500
282
+
283
+
284
+ @bp_chat_history_response.route("/history/delete_all", methods=["DELETE"])
285
+ async def delete_all_conversations():
286
+ config = ConfigHelper.get_active_config_or_default()
287
+
288
+ # Check if chat history is available
289
+ if not config.enable_chat_history:
290
+ return jsonify({"error": "Chat history is not available"}), 400
291
+
292
+ try:
293
+ # Get the user ID from the request headers (ensure authentication is successful)
294
+ authenticated_user = get_authenticated_user_details(
295
+ request_headers=request.headers
296
+ )
297
+ user_id = authenticated_user["user_principal_id"]
298
+ # Initialize the database client
299
+ conversation_client = init_database_client()
300
+ if not conversation_client:
301
+ return jsonify({"error": "Database not available"}), 500
302
+
303
+ await conversation_client.connect()
304
+ try:
305
+ # Get all conversations for the user
306
+ conversations = await conversation_client.get_conversations(
307
+ user_id, offset=0, limit=None
308
+ )
309
+ if not conversations:
310
+ return (
311
+ jsonify({"error": f"No conversations found for user {user_id}"}),
312
+ 400,
313
+ )
314
+
315
+ # Delete each conversation and its associated messages
316
+ for conversation in conversations:
317
+ try:
318
+ # Delete messages associated with the conversation
319
+ await conversation_client.delete_messages(
320
+ conversation["id"], user_id
321
+ )
322
+
323
+ # Delete the conversation itself
324
+ await conversation_client.delete_conversation(
325
+ user_id, conversation["id"]
326
+ )
327
+
328
+ except Exception as e:
329
+ # Log and continue with the next conversation if one fails
330
+ logger.exception(
331
+ f"Error deleting conversation {conversation['id']} for user {user_id}: {e}"
332
+ )
333
+ continue
334
+ return (
335
+ jsonify(
336
+ {
337
+ "message": f"Successfully deleted all conversations and messages for user {user_id}"
338
+ }
339
+ ),
340
+ 200,
341
+ )
342
+ except Exception as e:
343
+ logger.exception(
344
+ f"Error deleting all conversations for user {user_id}: {e}"
345
+ )
346
+ raise
347
+ finally:
348
+ await conversation_client.close()
349
+
350
+ except Exception as e:
351
+ logger.exception(f"Exception in /history/delete_all: {e}")
352
+ return jsonify({"error": "Error while deleting all conversation history"}), 500
353
+
354
+
355
+ @bp_chat_history_response.route("/history/update", methods=["POST"])
356
+ async def update_conversation():
357
+ config = ConfigHelper.get_active_config_or_default()
358
+ if not config.enable_chat_history:
359
+ return jsonify({"error": "Chat history is not available"}), 400
360
+
361
+ try:
362
+ # Get user details from request headers
363
+ authenticated_user = get_authenticated_user_details(
364
+ request_headers=request.headers
365
+ )
366
+ user_id = authenticated_user["user_principal_id"]
367
+ request_json = request.get_json()
368
+ conversation_id = request_json.get("conversation_id", None)
369
+ if not conversation_id:
370
+ return jsonify({"error": "conversation_id is required"}), 400
371
+
372
+ messages = request_json["messages"]
373
+ if not messages or len(messages) == 0:
374
+ return jsonify({"error": "Messages are required"}), 400
375
+
376
+ # Initialize conversation client
377
+ conversation_client = init_database_client()
378
+ if not conversation_client:
379
+ return jsonify({"error": "Database not available"}), 500
380
+ await conversation_client.connect()
381
+ try:
382
+ # Get or create the conversation
383
+ conversation = await conversation_client.get_conversation(
384
+ user_id, conversation_id
385
+ )
386
+ if not conversation:
387
+ title = await generate_title(messages)
388
+ conversation = await conversation_client.create_conversation(
389
+ user_id=user_id, conversation_id=conversation_id, title=title
390
+ )
391
+
392
+ # Process and save user and assistant messages
393
+ # Process user message
394
+ if messages[0]["role"] == "user":
395
+ user_message = next(
396
+ (msg for msg in reversed(messages) if msg["role"] == "user"), None
397
+ )
398
+ if not user_message:
399
+ return jsonify({"error": "User message not found"}), 400
400
+
401
+ created_message = await conversation_client.create_message(
402
+ uuid=str(uuid4()),
403
+ conversation_id=conversation_id,
404
+ user_id=user_id,
405
+ input_message=user_message,
406
+ )
407
+ if created_message == "Conversation not found":
408
+ return jsonify({"error": "Conversation not found"}), 400
409
+
410
+ # Process assistant and tool messages if available
411
+ if messages[-1]["role"] == "assistant":
412
+ if len(messages) > 1 and messages[-2].get("role") == "tool":
413
+ # Write the tool message first if it exists
414
+ await conversation_client.create_message(
415
+ uuid=str(uuid4()),
416
+ conversation_id=conversation_id,
417
+ user_id=user_id,
418
+ input_message=messages[-2],
419
+ )
420
+ # Write the assistant message
421
+ await conversation_client.create_message(
422
+ uuid=str(uuid4()),
423
+ conversation_id=conversation_id,
424
+ user_id=user_id,
425
+ input_message=messages[-1],
426
+ )
427
+ else:
428
+ return jsonify({"error": "No assistant message found"}), 400
429
+
430
+ return (
431
+ jsonify(
432
+ {
433
+ "success": True,
434
+ "data": {
435
+ "title": conversation["title"],
436
+ "date": conversation["updatedAt"],
437
+ "conversation_id": conversation["id"],
438
+ },
439
+ }
440
+ ),
441
+ 200,
442
+ )
443
+ except Exception as e:
444
+ logger.exception(
445
+ f"Error updating conversation or messages: user_id={user_id}, conversation_id={conversation_id}, error={e}"
446
+ )
447
+ raise
448
+ finally:
449
+ await conversation_client.close()
450
+
451
+ except Exception as e:
452
+ logger.exception(f"Exception in /history/update: {e}")
453
+ return jsonify({"error": "Error while updating the conversation history"}), 500
454
+
455
+
456
+ @bp_chat_history_response.route("/history/frontend_settings", methods=["GET"])
457
+ def get_frontend_settings():
458
+ try:
459
+ # Clear the cache for the config helper method
460
+ ConfigHelper.get_active_config_or_default.cache_clear()
461
+
462
+ # Retrieve active config
463
+ config = ConfigHelper.get_active_config_or_default()
464
+
465
+ # Ensure `enable_chat_history` is processed correctly
466
+ if isinstance(config.enable_chat_history, str):
467
+ chat_history_enabled = config.enable_chat_history.strip().lower() == "true"
468
+ else:
469
+ chat_history_enabled = bool(config.enable_chat_history)
470
+
471
+ return jsonify({"CHAT_HISTORY_ENABLED": chat_history_enabled}), 200
472
+
473
+ except Exception as e:
474
+ logger.exception(f"Exception in /history/frontend_settings: {e}")
475
+ return jsonify({"error": "Error while getting frontend settings"}), 500
476
+
477
+
478
+ async def generate_title(conversation_messages):
479
+ title_prompt = "Summarize the conversation so far into a 4-word or less title. Do not use any quotation marks or punctuation. Do not include any other commentary or description."
480
+
481
+ # Filter only the user messages, but consider including system or assistant context if necessary
482
+ messages = [
483
+ {"role": msg["role"], "content": msg["content"]}
484
+ for msg in conversation_messages
485
+ if msg["role"] == "user"
486
+ ]
487
+ messages.append({"role": "user", "content": title_prompt})
488
+
489
+ try:
490
+ azure_openai_client = init_openai_client()
491
+
492
+ # Create a chat completion with the Azure OpenAI client
493
+ response = await azure_openai_client.chat.completions.create(
494
+ model=env_helper.AZURE_OPENAI_MODEL,
495
+ messages=messages,
496
+ temperature=1,
497
+ max_tokens=64,
498
+ )
499
+
500
+ # Ensure response contains valid choices and content
501
+ if response and response.choices and len(response.choices) > 0:
502
+ title = response.choices[0].message.content.strip()
503
+ return title
504
+ else:
505
+ raise ValueError("No valid choices in response")
506
+
507
+ except Exception as e:
508
+ logger.exception(f"Error generating title: {str(e)}")
509
+ # Fallback: return the content of the second to last message if something goes wrong
510
+ return messages[-2]["content"] if len(messages) > 1 else "Untitled"
File without changes
File without changes
@@ -0,0 +1,43 @@
1
+ import base64
2
+ import json
3
+ import logging
4
+
5
+
6
+ def get_authenticated_user_details(request_headers):
7
+ user_object = {}
8
+
9
+ # check the headers for the Principal-Id (the guid of the signed in user)
10
+ if "X-Ms-Client-Principal-Id" not in request_headers.keys():
11
+ # if it's not, assume we're in development mode and return a default user
12
+ from . import sample_user
13
+
14
+ raw_user_object = sample_user.sample_user
15
+ else:
16
+ # if it is, get the user details from the EasyAuth headers
17
+ raw_user_object = {k: v for k, v in request_headers.items()}
18
+
19
+ user_object["user_principal_id"] = raw_user_object.get("X-Ms-Client-Principal-Id")
20
+ user_object["user_name"] = raw_user_object.get("X-Ms-Client-Principal-Name")
21
+ user_object["auth_provider"] = raw_user_object.get("X-Ms-Client-Principal-Idp")
22
+ user_object["auth_token"] = raw_user_object.get("X-Ms-Token-Aad-Id-Token")
23
+ user_object["client_principal_b64"] = raw_user_object.get("X-Ms-Client-Principal")
24
+ user_object["aad_id_token"] = raw_user_object.get("X-Ms-Token-Aad-Id-Token")
25
+
26
+ return user_object
27
+
28
+
29
+ def get_tenantid(client_principal_b64):
30
+ logger = logging.getLogger(__name__)
31
+ tenant_id = ""
32
+ if client_principal_b64:
33
+ try:
34
+ # Decode the base64 header to get the JSON string
35
+ decoded_bytes = base64.b64decode(client_principal_b64)
36
+ decoded_string = decoded_bytes.decode("utf-8")
37
+ # Convert the JSON string1into a Python dictionary
38
+ user_info = json.loads(decoded_string)
39
+ # Extract the tenant ID
40
+ tenant_id = user_info.get("tid") # 'tid' typically holds the tenant ID
41
+ except Exception as ex:
42
+ logger.exception(ex)
43
+ return tenant_id