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.
- cwyodmodules-0.0.2/LICENSE +27 -0
- cwyodmodules-0.0.2/PKG-INFO +50 -0
- cwyodmodules-0.0.2/README.md +7 -0
- cwyodmodules-0.0.2/backend/api/__init__.py +0 -0
- cwyodmodules-0.0.2/backend/api/chat_history.py +510 -0
- cwyodmodules-0.0.2/backend/batch/__init__.py +0 -0
- cwyodmodules-0.0.2/backend/batch/utilities/__init__.py +0 -0
- cwyodmodules-0.0.2/backend/batch/utilities/chat_history/auth_utils.py +43 -0
- cwyodmodules-0.0.2/backend/batch/utilities/chat_history/cosmosdb.py +205 -0
- cwyodmodules-0.0.2/backend/batch/utilities/chat_history/database_client_base.py +82 -0
- cwyodmodules-0.0.2/backend/batch/utilities/chat_history/database_factory.py +59 -0
- cwyodmodules-0.0.2/backend/batch/utilities/chat_history/postgresdbservice.py +159 -0
- cwyodmodules-0.0.2/backend/batch/utilities/chat_history/sample_user.py +39 -0
- cwyodmodules-0.0.2/backend/batch/utilities/common/__init__.py +0 -0
- cwyodmodules-0.0.2/backend/batch/utilities/common/answer.py +65 -0
- cwyodmodules-0.0.2/backend/batch/utilities/common/source_document.py +143 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/__init__.py +12 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/chunking_strategy.py +56 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/document_chunking_base.py +16 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/fixed_size_overlap.py +42 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/layout.py +43 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/page.py +40 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/paragraph.py +14 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_chunking/strategies.py +35 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/__init__.py +24 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/document_loading_base.py +13 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/layout.py +25 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/read.py +25 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/strategies.py +25 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/web.py +30 -0
- cwyodmodules-0.0.2/backend/batch/utilities/document_loading/word_document.py +45 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/__init__.py +0 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_blob_storage_client.py +268 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_computer_vision_client.py +96 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_form_recognizer_helper.py +155 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_postgres_helper.py +275 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/azure_search_helper.py +279 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/assistant_strategy.py +7 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/config_helper.py +330 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/conversation_flow.py +6 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/database_type.py +6 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/config/embedding_config.py +27 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/document_chunking_helper.py +22 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/document_loading_helper.py +18 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/embedder_base.py +23 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/embedder_factory.py +20 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/integrated_vectorization_embedder.py +59 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/postgres_embedder.py +111 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/embedders/push_embedder.py +194 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/env_helper.py +359 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/llm_helper.py +179 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/orchestrator_helper.py +29 -0
- cwyodmodules-0.0.2/backend/batch/utilities/helpers/secret_helper.py +84 -0
- cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_datasource.py +47 -0
- cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_index.py +176 -0
- cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_indexer.py +70 -0
- cwyodmodules-0.0.2/backend/batch/utilities/integrated_vectorization/azure_search_skillset.py +141 -0
- cwyodmodules-0.0.2/backend/batch/utilities/loggers/conversation_logger.py +80 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/__init__.py +18 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/lang_chain_agent.py +162 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/open_ai_functions.py +190 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/orchestration_strategy.py +18 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/orchestrator_base.py +153 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/prompt_flow.py +188 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/semantic_kernel.py +178 -0
- cwyodmodules-0.0.2/backend/batch/utilities/orchestrator/strategies.py +29 -0
- cwyodmodules-0.0.2/backend/batch/utilities/parser/__init__.py +11 -0
- cwyodmodules-0.0.2/backend/batch/utilities/parser/output_parser_tool.py +140 -0
- cwyodmodules-0.0.2/backend/batch/utilities/parser/parser_base.py +19 -0
- cwyodmodules-0.0.2/backend/batch/utilities/plugins/chat_plugin.py +79 -0
- cwyodmodules-0.0.2/backend/batch/utilities/plugins/post_answering_plugin.py +32 -0
- cwyodmodules-0.0.2/backend/batch/utilities/search/azure_search_handler.py +189 -0
- cwyodmodules-0.0.2/backend/batch/utilities/search/integrated_vectorization_search_handler.py +181 -0
- cwyodmodules-0.0.2/backend/batch/utilities/search/postgres_search_handler.py +104 -0
- cwyodmodules-0.0.2/backend/batch/utilities/search/search.py +63 -0
- cwyodmodules-0.0.2/backend/batch/utilities/search/search_handler_base.py +67 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/__init__.py +0 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/answer_processing_base.py +12 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/answering_tool_base.py +15 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/content_safety_checker.py +80 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/post_prompt_tool.py +56 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/question_answer_tool.py +301 -0
- cwyodmodules-0.0.2/backend/batch/utilities/tools/text_processing_tool.py +37 -0
- cwyodmodules-0.0.2/cwyodmodules.egg-info/PKG-INFO +50 -0
- cwyodmodules-0.0.2/cwyodmodules.egg-info/SOURCES.txt +88 -0
- cwyodmodules-0.0.2/cwyodmodules.egg-info/dependency_links.txt +1 -0
- cwyodmodules-0.0.2/cwyodmodules.egg-info/requires.txt +31 -0
- cwyodmodules-0.0.2/cwyodmodules.egg-info/top_level.txt +1 -0
- cwyodmodules-0.0.2/pyproject.toml +46 -0
- 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)
|
|
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
|