Spaces:
Sleeping
Sleeping
Commit
Β·
f88de80
1
Parent(s):
6e5b27a
handling contexts
Browse files- __pycache__/config.cpython-312.pyc +0 -0
- app.backup +0 -126
- app.py +277 -82
- config.py +6 -2
- data/txt/Key statisitcs startups.txt +0 -2
- utilities/llm/LlmManager.py +274 -77
- utilities/llm/__pycache__/LlmManager.cpython-312.pyc +0 -0
- utilities/vectorstore/QdrantLangchainManager.py +49 -24
- utilities/vectorstore/SummaryManager.py +37 -19
- utilities/vectorstore/__pycache__/QdrantLangchainManager.cpython-312.pyc +0 -0
- utilities/vectorstore/__pycache__/SummaryManager.cpython-312.pyc +0 -0
- utils.py +15 -4
__pycache__/config.cpython-312.pyc
CHANGED
|
Binary files a/__pycache__/config.cpython-312.pyc and b/__pycache__/config.cpython-312.pyc differ
|
|
|
app.backup
DELETED
|
@@ -1,126 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import time
|
| 3 |
-
import gradio as gr
|
| 4 |
-
|
| 5 |
-
from config import initialize, check_user
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
llm_manager, qdrant_manager = initialize()
|
| 9 |
-
if llm_manager is None:
|
| 10 |
-
print("Error: Failed to initialize configuration: llm_manager. Exiting application.", flush=True)
|
| 11 |
-
sys.exit(1)
|
| 12 |
-
if qdrant_manager is None:
|
| 13 |
-
print("Error: Failed to initialize configuration: qdrant_manager. Exiting application.", flush=True)
|
| 14 |
-
sys.exit(1)
|
| 15 |
-
|
| 16 |
-
def reset_textbox():
|
| 17 |
-
"""Clears the textbox after sending a message."""
|
| 18 |
-
return gr.update(value="")
|
| 19 |
-
|
| 20 |
-
def slow_echo(message, history):
|
| 21 |
-
if history is None:
|
| 22 |
-
history = [] # Ensure history is initialized
|
| 23 |
-
|
| 24 |
-
# Append user message with role "user"
|
| 25 |
-
history.append({"role": "user", "content": message})
|
| 26 |
-
|
| 27 |
-
# Placeholder for assistant response
|
| 28 |
-
bot_entry = {"role": "assistant", "content": ""}
|
| 29 |
-
history.append(bot_entry)
|
| 30 |
-
|
| 31 |
-
response = "You typed: "
|
| 32 |
-
|
| 33 |
-
for i in range(len(message)):
|
| 34 |
-
time.sleep(0.05)
|
| 35 |
-
response += message[i]
|
| 36 |
-
bot_entry["content"] = response # Update assistant's response progressively
|
| 37 |
-
yield history # Yield updated history in the correct format
|
| 38 |
-
|
| 39 |
-
yield history # Final yield with full message
|
| 40 |
-
|
| 41 |
-
def llm_send_message(message, history):
|
| 42 |
-
if history is None:
|
| 43 |
-
history = []
|
| 44 |
-
|
| 45 |
-
# Append user message to history
|
| 46 |
-
history.append({"role": "user", "content": message})
|
| 47 |
-
|
| 48 |
-
yield history
|
| 49 |
-
|
| 50 |
-
# Placeholder for assistant response
|
| 51 |
-
bot_entry = {"role": "assistant", "content": ""}
|
| 52 |
-
history.append(bot_entry)
|
| 53 |
-
|
| 54 |
-
# Send message to LLM and stream response
|
| 55 |
-
response = ""
|
| 56 |
-
for chunk in llm_manager.send_message(message): # Streaming response
|
| 57 |
-
time.sleep(0.01) # Simulate gradual output
|
| 58 |
-
response += chunk
|
| 59 |
-
bot_entry["content"] = response # Update assistant response progressively
|
| 60 |
-
yield history # Yield updated history
|
| 61 |
-
|
| 62 |
-
yield history # Final yield
|
| 63 |
-
|
| 64 |
-
def authenticate(username, password):
|
| 65 |
-
if check_user(username, password):
|
| 66 |
-
print("π Login successful!")
|
| 67 |
-
return gr.update(visible=False), gr.update(visible=True), gr.update(value="", visible=False) # Hide login, show chatbot, clear error
|
| 68 |
-
else:
|
| 69 |
-
print("β Incorrect username or password")
|
| 70 |
-
return gr.update(visible=True), gr.update(visible=True), gr.update(value="β Incorrect username or password", visible=True) # Show error
|
| 71 |
-
|
| 72 |
-
with gr.Blocks(fill_height=True) as demo:
|
| 73 |
-
|
| 74 |
-
with gr.Column(visible=True) as login_section:
|
| 75 |
-
gr.Markdown("### π Login Required")
|
| 76 |
-
username_input = gr.Textbox(label="Username")
|
| 77 |
-
password_input = gr.Textbox(label="Password", type="password")
|
| 78 |
-
login_button = gr.Button("Login")
|
| 79 |
-
error_message = gr.Text("", visible=False)
|
| 80 |
-
|
| 81 |
-
with gr.Column(visible=False) as chat_section:
|
| 82 |
-
|
| 83 |
-
chat_configuration = gr.Markdown("")
|
| 84 |
-
|
| 85 |
-
chat = gr.Chatbot(
|
| 86 |
-
label="Video Helper",
|
| 87 |
-
type="messages"
|
| 88 |
-
)
|
| 89 |
-
|
| 90 |
-
input = gr.Textbox(
|
| 91 |
-
label="Input",
|
| 92 |
-
placeholder="Type something here..."
|
| 93 |
-
)
|
| 94 |
-
|
| 95 |
-
stored_message = gr.State()
|
| 96 |
-
|
| 97 |
-
input.submit(
|
| 98 |
-
fn=lambda text: (text, ""),
|
| 99 |
-
inputs=[input],
|
| 100 |
-
outputs=[stored_message, input]
|
| 101 |
-
).then(
|
| 102 |
-
#fn=llm_send_message,
|
| 103 |
-
fn=slow_echo,
|
| 104 |
-
inputs=[stored_message, chat],
|
| 105 |
-
outputs=chat
|
| 106 |
-
)
|
| 107 |
-
|
| 108 |
-
send_btn = gr.Button("Send")
|
| 109 |
-
send_btn.click(
|
| 110 |
-
fn=lambda text: (text, ""),
|
| 111 |
-
inputs=[input],
|
| 112 |
-
outputs=[stored_message, input]
|
| 113 |
-
).then(
|
| 114 |
-
fn=llm_send_message,
|
| 115 |
-
inputs=[stored_message, chat],
|
| 116 |
-
outputs=chat
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
login_button.click(
|
| 120 |
-
authenticate,
|
| 121 |
-
[username_input, password_input],
|
| 122 |
-
[login_section, chat_section, error_message]
|
| 123 |
-
)
|
| 124 |
-
|
| 125 |
-
if __name__ == "__main__":
|
| 126 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -33,27 +33,81 @@ def set_interactive_state(interactive: bool):
|
|
| 33 |
gr.update(interactive=interactive) # send_btn
|
| 34 |
)
|
| 35 |
|
| 36 |
-
def _add_trust_icon(text, level):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
icons = {
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
"
|
| 41 |
-
"
|
| 42 |
-
"no_context": "βͺοΈ"
|
| 43 |
}
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"
|
| 50 |
}
|
| 51 |
|
| 52 |
-
icon = icons.get(
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
# Prepend semaforo + etichetta (puoi anche usare HTML per Gradio futuro)
|
| 56 |
-
return f"{icon} *{tooltip}*\n\n{text}"
|
| 57 |
|
| 58 |
def get_summary(summary_type, history):
|
| 59 |
if history is None:
|
|
@@ -70,12 +124,40 @@ def get_summary(summary_type, history):
|
|
| 70 |
label = "β Unknown Summary"
|
| 71 |
|
| 72 |
if content:
|
|
|
|
| 73 |
history.append({"role": "assistant", "content": f"{label}\n\n{content}"})
|
| 74 |
else:
|
| 75 |
history.append({"role": "assistant", "content": f"β οΈ No {label.lower()} available."})
|
| 76 |
|
| 77 |
return history
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
def send_chat_message(message, history):
|
| 80 |
if history is None:
|
| 81 |
history = []
|
|
@@ -85,21 +167,60 @@ def send_chat_message(message, history):
|
|
| 85 |
history.append(bot_entry)
|
| 86 |
|
| 87 |
response = ""
|
| 88 |
-
context_level = "high" # default
|
|
|
|
| 89 |
|
| 90 |
-
#
|
| 91 |
-
stream = llm_manager.
|
| 92 |
|
| 93 |
for chunk in stream:
|
| 94 |
-
if isinstance(chunk, dict): # nuova
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
else: # retrocompatibilitΓ
|
| 98 |
response += chunk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
| 103 |
|
| 104 |
yield history
|
| 105 |
|
|
@@ -116,6 +237,7 @@ def authenticate(username, password):
|
|
| 116 |
return (
|
| 117 |
gr.update(visible=False), # Hide login
|
| 118 |
gr.update(visible=True), # Show chat
|
|
|
|
| 119 |
gr.update(value="", visible=False), # Clear error
|
| 120 |
assistant_msgs # Initial chat history
|
| 121 |
)
|
|
@@ -129,6 +251,7 @@ def authenticate(username, password):
|
|
| 129 |
|
| 130 |
with gr.Blocks(fill_height=True) as demo:
|
| 131 |
|
|
|
|
| 132 |
with gr.Column(visible=True) as login_section:
|
| 133 |
gr.Markdown("### π Login Required")
|
| 134 |
username_input = gr.Textbox(label="Username")
|
|
@@ -136,83 +259,155 @@ with gr.Blocks(fill_height=True) as demo:
|
|
| 136 |
login_button = gr.Button("Login")
|
| 137 |
error_message = gr.Text("", visible=False)
|
| 138 |
|
|
|
|
| 139 |
with gr.Column(visible=False) as chat_section:
|
| 140 |
-
|
| 141 |
chat_configuration = gr.Markdown("")
|
| 142 |
-
|
| 143 |
spinner = gr.Markdown("β³ Sto pensando...", visible=False)
|
| 144 |
|
| 145 |
-
|
| 146 |
-
map_btn = gr.Button("π§Ύ Map Summary")
|
| 147 |
-
stuff_btn = gr.Button("π Stuff Summary")
|
| 148 |
-
|
| 149 |
-
chat = gr.Chatbot(
|
| 150 |
-
label="Video Helper",
|
| 151 |
-
type="messages"
|
| 152 |
-
)
|
| 153 |
-
|
| 154 |
-
input = gr.Textbox(
|
| 155 |
-
label="Input",
|
| 156 |
-
placeholder="Type something here..."
|
| 157 |
-
)
|
| 158 |
send_btn = gr.Button("Send")
|
| 159 |
|
| 160 |
stored_message = gr.State()
|
| 161 |
chat_history = gr.State(value=[])
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
)
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
)
|
| 174 |
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
inputs=[stored_message, chat_history],
|
| 185 |
-
outputs=chat
|
| 186 |
-
).then(
|
| 187 |
-
fn=hide_spinner,
|
| 188 |
-
outputs=[spinner]
|
| 189 |
-
)
|
| 190 |
-
|
| 191 |
-
send_btn.click(
|
| 192 |
-
fn=lambda x: (x, ""),
|
| 193 |
-
inputs=input,
|
| 194 |
-
outputs=[stored_message, input]
|
| 195 |
-
).then(
|
| 196 |
-
fn=show_spinner,
|
| 197 |
-
outputs=[spinner]
|
| 198 |
-
).then(
|
| 199 |
-
fn=send_chat_message,
|
| 200 |
-
inputs=[stored_message, chat_history],
|
| 201 |
-
outputs=chat
|
| 202 |
-
).then(
|
| 203 |
-
fn=hide_spinner,
|
| 204 |
-
outputs=[spinner]
|
| 205 |
-
)
|
| 206 |
|
|
|
|
| 207 |
login_button.click(
|
| 208 |
authenticate,
|
| 209 |
[username_input, password_input],
|
| 210 |
-
[login_section, chat_section, error_message, chat_history]
|
| 211 |
).then(
|
| 212 |
fn=lambda history: history,
|
| 213 |
inputs=[chat_history],
|
| 214 |
outputs=[chat]
|
| 215 |
)
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
if __name__ == "__main__":
|
| 218 |
demo.launch()
|
|
|
|
| 33 |
gr.update(interactive=interactive) # send_btn
|
| 34 |
)
|
| 35 |
|
| 36 |
+
# def _add_trust_icon(text, level):
|
| 37 |
+
# icons = {
|
| 38 |
+
# "high": "π’", # alta affidabilitΓ
|
| 39 |
+
# "medium": "π‘", # simile ma non perfetto
|
| 40 |
+
# "summary": "π ", # riassunto usato
|
| 41 |
+
# "low": "π΄", # basato solo sulla chat
|
| 42 |
+
# "no_context": "βͺοΈ"
|
| 43 |
+
# }
|
| 44 |
+
# label = {
|
| 45 |
+
# "high": "Reliable",
|
| 46 |
+
# "medium": "Moderate Similarity",
|
| 47 |
+
# "summary": "Summary Used",
|
| 48 |
+
# "low": "No Source Match",
|
| 49 |
+
# "no_context": "No Context"
|
| 50 |
+
# }
|
| 51 |
+
|
| 52 |
+
# icon = icons.get(level, "βͺοΈ")
|
| 53 |
+
# tooltip = label.get(level, "Unknown")
|
| 54 |
+
|
| 55 |
+
# # Prepend semaforo + etichetta (puoi anche usare HTML per Gradio futuro)
|
| 56 |
+
# return f"{icon} *{tooltip}*\n\n{text}"
|
| 57 |
+
|
| 58 |
+
def _add_trust_icon(text, support_level="unknown"):
|
| 59 |
icons = {
|
| 60 |
+
"green": "π’",
|
| 61 |
+
"yellow": "π‘",
|
| 62 |
+
"red": "π΄",
|
| 63 |
+
"unknown": "βͺοΈ"
|
|
|
|
| 64 |
}
|
| 65 |
+
|
| 66 |
+
labels = {
|
| 67 |
+
"green": "Supported by context",
|
| 68 |
+
"yellow": "Partially supported",
|
| 69 |
+
"red": "Not supported",
|
| 70 |
+
"unknown": "Support level unknown"
|
| 71 |
}
|
| 72 |
|
| 73 |
+
icon = icons.get(support_level, "βͺοΈ")
|
| 74 |
+
label = labels.get(support_level, "Support level unknown")
|
| 75 |
+
|
| 76 |
+
return f"{icon} *{label}*\n\n{text}"
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def toggle_study_mode(is_study, index, history):
|
| 81 |
+
if is_study:
|
| 82 |
+
# π Uscita dalla modalitΓ studio β torna alla modalitΓ standard
|
| 83 |
+
return (
|
| 84 |
+
"π Study", # bottone torna Studio
|
| 85 |
+
gr.update(visible=True), # chat
|
| 86 |
+
gr.update(visible=True), # input
|
| 87 |
+
gr.update(visible=True), # send_btn
|
| 88 |
+
gr.update(visible=False), # study_chat
|
| 89 |
+
gr.update(visible=True), # summary_mode_btn
|
| 90 |
+
gr.update(visible=False), # chunk_nav
|
| 91 |
+
index,
|
| 92 |
+
history,
|
| 93 |
+
False
|
| 94 |
+
)
|
| 95 |
+
else:
|
| 96 |
+
# π Entrata in modalitΓ studio
|
| 97 |
+
return (
|
| 98 |
+
"β Studio off",
|
| 99 |
+
gr.update(visible=False), # chat
|
| 100 |
+
gr.update(visible=False), # input
|
| 101 |
+
gr.update(visible=False), # send_btn
|
| 102 |
+
gr.update(visible=True), # study_chat
|
| 103 |
+
gr.update(visible=False), # summary_mode_btn
|
| 104 |
+
gr.update(visible=True), # chunk_nav
|
| 105 |
+
0,
|
| 106 |
+
[], # nuova chat studio
|
| 107 |
+
True
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
|
|
|
|
|
|
|
| 111 |
|
| 112 |
def get_summary(summary_type, history):
|
| 113 |
if history is None:
|
|
|
|
| 124 |
label = "β Unknown Summary"
|
| 125 |
|
| 126 |
if content:
|
| 127 |
+
history.append({"role": "user", "content": "Summary requested."})
|
| 128 |
history.append({"role": "assistant", "content": f"{label}\n\n{content}"})
|
| 129 |
else:
|
| 130 |
history.append({"role": "assistant", "content": f"β οΈ No {label.lower()} available."})
|
| 131 |
|
| 132 |
return history
|
| 133 |
|
| 134 |
+
# def send_chat_message(message, history):
|
| 135 |
+
# if history is None:
|
| 136 |
+
# history = []
|
| 137 |
+
|
| 138 |
+
# history.append({"role": "user", "content": message})
|
| 139 |
+
# bot_entry = {"role": "assistant", "content": ""}
|
| 140 |
+
# history.append(bot_entry)
|
| 141 |
+
|
| 142 |
+
# response = ""
|
| 143 |
+
# context_level = "high" # default
|
| 144 |
+
|
| 145 |
+
# # β
Usa la nuova funzione standard che restituisce dict con context_level
|
| 146 |
+
# stream = llm_manager.stream_message_standard(message)
|
| 147 |
+
|
| 148 |
+
# for chunk in stream:
|
| 149 |
+
# if isinstance(chunk, dict): # nuova struttura
|
| 150 |
+
# response += chunk["content"]
|
| 151 |
+
# context_level = chunk.get("context_level", "high")
|
| 152 |
+
# else: # retrocompatibilitΓ , se mai usato
|
| 153 |
+
# response += chunk
|
| 154 |
+
|
| 155 |
+
# # aggiornamento in tempo reale
|
| 156 |
+
# bot_entry["content"] = _add_trust_icon(response, context_level)
|
| 157 |
+
# yield history
|
| 158 |
+
|
| 159 |
+
# yield history
|
| 160 |
+
|
| 161 |
def send_chat_message(message, history):
|
| 162 |
if history is None:
|
| 163 |
history = []
|
|
|
|
| 167 |
history.append(bot_entry)
|
| 168 |
|
| 169 |
response = ""
|
| 170 |
+
context_level = "high" # default
|
| 171 |
+
support_level = "green" # default (safe fallback)
|
| 172 |
|
| 173 |
+
# β
Usa la nuova funzione standard che restituisce dict con context_level e support_level
|
| 174 |
+
stream = llm_manager.stream_message_standard(message)
|
| 175 |
|
| 176 |
for chunk in stream:
|
| 177 |
+
if isinstance(chunk, dict): # nuova struttura
|
| 178 |
+
content = chunk.get("content")
|
| 179 |
+
if content:
|
| 180 |
+
response += content
|
| 181 |
+
|
| 182 |
+
support_level = chunk.get("support_level", support_level)
|
| 183 |
+
#print(f"π SUPPORT LEVEL RECEIVED: {support_level}")
|
| 184 |
+
bot_entry["content"] = _add_trust_icon(response, support_level)
|
| 185 |
+
yield history
|
| 186 |
+
|
| 187 |
else: # retrocompatibilitΓ
|
| 188 |
response += chunk
|
| 189 |
+
bot_entry["content"] = _add_trust_icon(response, support_level)
|
| 190 |
+
yield history
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def load_chunk(index, history):
|
| 194 |
+
chunk = qdrant_manager.get_chunk_by_index(index)
|
| 195 |
+
if chunk:
|
| 196 |
+
llm_manager.set_focus_on_chunk(chunk)
|
| 197 |
+
|
| 198 |
+
# Aggiungi messaggio assistant con il contenuto del chunk
|
| 199 |
+
history.append({"role": "assistant", "content": f"π **Chunk {index + 1}**\n\n{chunk}"})
|
| 200 |
+
return history, gr.update(visible=index > 0), gr.update(visible=True)
|
| 201 |
+
else:
|
| 202 |
+
history.append({"role": "assistant", "content": "β οΈ Chunk non trovato."})
|
| 203 |
+
return history, gr.update(visible=False), gr.update(visible=False)
|
| 204 |
+
|
| 205 |
+
def handle_study_message(msg, index, history):
|
| 206 |
+
if history is None:
|
| 207 |
+
history = []
|
| 208 |
+
|
| 209 |
+
history.append({"role": "user", "content": msg})
|
| 210 |
+
bot_entry = {"role": "assistant", "content": ""}
|
| 211 |
+
history.append(bot_entry)
|
| 212 |
+
|
| 213 |
+
response = ""
|
| 214 |
+
context_level = None
|
| 215 |
+
|
| 216 |
+
for partial in llm_manager.stream_message_study(msg):
|
| 217 |
+
content = partial.get("content")
|
| 218 |
+
support_level = partial.get("support_level", "unknown")
|
| 219 |
|
| 220 |
+
if content:
|
| 221 |
+
response += content
|
| 222 |
+
bot_entry["content"] = _add_trust_icon(response, support_level)
|
| 223 |
+
yield history
|
| 224 |
|
| 225 |
yield history
|
| 226 |
|
|
|
|
| 237 |
return (
|
| 238 |
gr.update(visible=False), # Hide login
|
| 239 |
gr.update(visible=True), # Show chat
|
| 240 |
+
gr.update(visible=True), # Show nav
|
| 241 |
gr.update(value="", visible=False), # Clear error
|
| 242 |
assistant_msgs # Initial chat history
|
| 243 |
)
|
|
|
|
| 251 |
|
| 252 |
with gr.Blocks(fill_height=True) as demo:
|
| 253 |
|
| 254 |
+
# === LOGIN ===
|
| 255 |
with gr.Column(visible=True) as login_section:
|
| 256 |
gr.Markdown("### π Login Required")
|
| 257 |
username_input = gr.Textbox(label="Username")
|
|
|
|
| 259 |
login_button = gr.Button("Login")
|
| 260 |
error_message = gr.Text("", visible=False)
|
| 261 |
|
| 262 |
+
# === SEZIONE CHAT (visibile per default) ===
|
| 263 |
with gr.Column(visible=False) as chat_section:
|
|
|
|
| 264 |
chat_configuration = gr.Markdown("")
|
|
|
|
| 265 |
spinner = gr.Markdown("β³ Sto pensando...", visible=False)
|
| 266 |
|
| 267 |
+
input = gr.Textbox(label="Input", placeholder="Type something here...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
send_btn = gr.Button("Send")
|
| 269 |
|
| 270 |
stored_message = gr.State()
|
| 271 |
chat_history = gr.State(value=[])
|
| 272 |
|
| 273 |
+
# === SEZIONE STUDIO (mostrata solo quando attiva) ===
|
| 274 |
+
with gr.Column(visible=False) as study_chat:
|
| 275 |
+
study_chatbox = gr.Chatbot(label="Study Moxde", type="messages")
|
| 276 |
+
study_input = gr.Textbox(placeholder="Ask something about the current chunk...")
|
| 277 |
+
study_send = gr.Button("Send")
|
| 278 |
|
| 279 |
+
# === NAVIGAZIONE MODALITΓ ===
|
| 280 |
+
with gr.Row(visible=False) as nav_section:
|
| 281 |
+
study_mode_btn = gr.Button("π Study")
|
| 282 |
+
summary_mode_btn = gr.Button("π§ Summary")
|
|
|
|
| 283 |
|
| 284 |
+
# === NAVIGAZIONE CHUNKS (mostrata solo in modalitΓ studio) ===
|
| 285 |
+
with gr.Row(visible=False) as chunk_nav:
|
| 286 |
+
prev_chunk_btn = gr.Button("β¬
οΈ Previous Chunk")
|
| 287 |
+
next_chunk_btn = gr.Button("β‘οΈ Next Chunk")
|
| 288 |
+
|
| 289 |
+
# === STATE ===
|
| 290 |
+
study_chunk_index = gr.State(value=0)
|
| 291 |
+
study_history = gr.State(value=[])
|
| 292 |
+
is_study_mode = gr.State(value=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
+
# === AUTENTICAZIONE ===
|
| 295 |
login_button.click(
|
| 296 |
authenticate,
|
| 297 |
[username_input, password_input],
|
| 298 |
+
[login_section, chat_section, nav_section, error_message, chat_history]
|
| 299 |
).then(
|
| 300 |
fn=lambda history: history,
|
| 301 |
inputs=[chat_history],
|
| 302 |
outputs=[chat]
|
| 303 |
)
|
| 304 |
|
| 305 |
+
# === CHAT STANDARD ===
|
| 306 |
+
input.submit(
|
| 307 |
+
fn=lambda x: (x, ""),
|
| 308 |
+
inputs=input,
|
| 309 |
+
outputs=[stored_message, input]
|
| 310 |
+
).then(
|
| 311 |
+
fn=show_spinner,
|
| 312 |
+
outputs=[spinner]
|
| 313 |
+
).then(
|
| 314 |
+
fn=send_chat_message,
|
| 315 |
+
inputs=[stored_message, chat_history],
|
| 316 |
+
outputs=chat
|
| 317 |
+
).then(
|
| 318 |
+
fn=hide_spinner,
|
| 319 |
+
outputs=[spinner]
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
send_btn.click(
|
| 323 |
+
fn=lambda x: (x, ""),
|
| 324 |
+
inputs=input,
|
| 325 |
+
outputs=[stored_message, input]
|
| 326 |
+
).then(
|
| 327 |
+
fn=show_spinner,
|
| 328 |
+
outputs=[spinner]
|
| 329 |
+
).then(
|
| 330 |
+
fn=send_chat_message,
|
| 331 |
+
inputs=[stored_message, chat_history],
|
| 332 |
+
outputs=chat
|
| 333 |
+
).then(
|
| 334 |
+
fn=hide_spinner,
|
| 335 |
+
outputs=[spinner]
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
# === CHAT STUDIO ===
|
| 339 |
+
study_input.submit(
|
| 340 |
+
fn=lambda x: (x, ""),
|
| 341 |
+
inputs=study_input,
|
| 342 |
+
outputs=[stored_message, study_input]
|
| 343 |
+
).then(
|
| 344 |
+
fn=handle_study_message,
|
| 345 |
+
inputs=[stored_message, study_chunk_index, study_history],
|
| 346 |
+
outputs=study_chatbox
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
study_send.click(
|
| 350 |
+
fn=lambda x: (x, ""),
|
| 351 |
+
inputs=study_input,
|
| 352 |
+
outputs=[stored_message, study_input]
|
| 353 |
+
).then(
|
| 354 |
+
fn=handle_study_message,
|
| 355 |
+
inputs=[stored_message, study_chunk_index, study_history],
|
| 356 |
+
outputs=study_chatbox
|
| 357 |
+
)
|
| 358 |
+
|
| 359 |
+
# === MODALITΓ STUDIO ON/OFF ===
|
| 360 |
+
study_mode_btn.click(
|
| 361 |
+
fn=toggle_study_mode,
|
| 362 |
+
inputs=[is_study_mode, study_chunk_index, study_history],
|
| 363 |
+
outputs=[
|
| 364 |
+
study_mode_btn,
|
| 365 |
+
chat,
|
| 366 |
+
input,
|
| 367 |
+
send_btn,
|
| 368 |
+
study_chat,
|
| 369 |
+
summary_mode_btn,
|
| 370 |
+
chunk_nav,
|
| 371 |
+
study_chunk_index,
|
| 372 |
+
study_history,
|
| 373 |
+
is_study_mode
|
| 374 |
+
]
|
| 375 |
+
).then(
|
| 376 |
+
fn=load_chunk,
|
| 377 |
+
inputs=[study_chunk_index, study_history],
|
| 378 |
+
outputs=[study_chatbox, prev_chunk_btn, next_chunk_btn]
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
# === MOSTRA SUMMARY (solo modalitΓ standard) ===
|
| 382 |
+
summary_mode_btn.click(
|
| 383 |
+
fn=get_summary,
|
| 384 |
+
inputs=[gr.State("map"), chat_history],
|
| 385 |
+
outputs=[chat]
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# === NAVIGAZIONE CHUNKS ===
|
| 389 |
+
next_chunk_btn.click(
|
| 390 |
+
fn=lambda idx: idx + 1,
|
| 391 |
+
inputs=study_chunk_index,
|
| 392 |
+
outputs=study_chunk_index
|
| 393 |
+
).then(
|
| 394 |
+
fn=load_chunk,
|
| 395 |
+
inputs=[study_chunk_index, study_history],
|
| 396 |
+
outputs=[study_chatbox, prev_chunk_btn, next_chunk_btn]
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
prev_chunk_btn.click(
|
| 400 |
+
fn=lambda idx: max(idx - 1, 0),
|
| 401 |
+
inputs=study_chunk_index,
|
| 402 |
+
outputs=study_chunk_index
|
| 403 |
+
).then(
|
| 404 |
+
fn=load_chunk,
|
| 405 |
+
inputs=[study_chunk_index, study_history],
|
| 406 |
+
outputs=[study_chatbox, prev_chunk_btn, next_chunk_btn]
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
|
| 412 |
if __name__ == "__main__":
|
| 413 |
demo.launch()
|
config.py
CHANGED
|
@@ -9,6 +9,10 @@ MODEL = "gpt-4o-mini"
|
|
| 9 |
LANGUAGE = "en"
|
| 10 |
|
| 11 |
crossencoder_model = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
credentials = {}
|
| 13 |
|
| 14 |
def check_user(username, password):
|
|
@@ -25,9 +29,9 @@ def initialize(app=None):
|
|
| 25 |
try:
|
| 26 |
load_dotenv()
|
| 27 |
|
| 28 |
-
if
|
| 29 |
print("Loading CrossEncoder model...")
|
| 30 |
-
crossencoder_model = CrossEncoder(
|
| 31 |
print("CrossEncoder model loaded!")
|
| 32 |
|
| 33 |
credentials[os.getenv("USERNAME")] = os.getenv("PASSWORD")
|
|
|
|
| 9 |
LANGUAGE = "en"
|
| 10 |
|
| 11 |
crossencoder_model = None
|
| 12 |
+
crossencoder_model_name = None
|
| 13 |
+
#crossencoder_model_name = "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
| 14 |
+
#crossencoder_model_name = "cross-encoder/ms-marco-MiniLM-L-12-v2"
|
| 15 |
+
|
| 16 |
credentials = {}
|
| 17 |
|
| 18 |
def check_user(username, password):
|
|
|
|
| 29 |
try:
|
| 30 |
load_dotenv()
|
| 31 |
|
| 32 |
+
if crossencoder_model_name:
|
| 33 |
print("Loading CrossEncoder model...")
|
| 34 |
+
crossencoder_model = CrossEncoder(crossencoder_model_name)
|
| 35 |
print("CrossEncoder model loaded!")
|
| 36 |
|
| 37 |
credentials[os.getenv("USERNAME")] = os.getenv("PASSWORD")
|
data/txt/Key statisitcs startups.txt
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
(Transcribed by TurboScribe.ai. Go Unlimited to remove this message.)
|
| 2 |
-
|
| 3 |
Hi everyone and welcome to this video lecture from the Entrepreneurial Literacy Initiative. I am Martti Wask and today we're going to present to you some brief key statistics about entrepreneurship. Before we go into the mud, let me give you some brief definition of what I understand about a startup.
|
| 4 |
|
| 5 |
So to some extent startups are just like any other business, right, or more specifically small businesses because they're small at the beginning. But there are three characteristics that make them unique. Startups are young, high growth orientated businesses.
|
|
|
|
|
|
|
|
|
|
| 1 |
Hi everyone and welcome to this video lecture from the Entrepreneurial Literacy Initiative. I am Martti Wask and today we're going to present to you some brief key statistics about entrepreneurship. Before we go into the mud, let me give you some brief definition of what I understand about a startup.
|
| 2 |
|
| 3 |
So to some extent startups are just like any other business, right, or more specifically small businesses because they're small at the beginning. But there are three characteristics that make them unique. Startups are young, high growth orientated businesses.
|
utilities/llm/LlmManager.py
CHANGED
|
@@ -1,76 +1,139 @@
|
|
|
|
|
| 1 |
from langchain_openai import ChatOpenAI
|
| 2 |
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
| 3 |
from utilities.vectorstore.SummaryManager import SummaryManager
|
| 4 |
|
| 5 |
MAX_MESSAGES = 50
|
| 6 |
|
| 7 |
-
system_message_it = """
|
| 8 |
-
Sei ELI, un assistente che aiuta gli studenti ad analizzare un video e rispondere a domande su di esso.
|
| 9 |
-
Basandoti sul contesto fornito, rispondi alla domanda dell'utente.
|
| 10 |
-
|
| 11 |
-
- Contesto: {context}
|
| 12 |
-
- Domanda: {question}
|
| 13 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
system_message_en = """
|
| 16 |
You are ELI, an assistant that helps students analyze a video and answer questions about it.
|
| 17 |
-
Based on the provided context, answer the user's question.
|
| 18 |
|
| 19 |
-
|
| 20 |
-
- Question: {question}
|
| 21 |
-
"""
|
| 22 |
|
| 23 |
-
|
| 24 |
-
if language == "it":
|
| 25 |
-
return system_message_it
|
| 26 |
-
else:
|
| 27 |
-
return system_message_en
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
|
|
|
| 37 |
"""
|
| 38 |
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
| 40 |
Sei ELI, un assistente che aiuta gli studenti ad analizzare un video e rispondere a domande su di esso.
|
| 41 |
-
Purtroppo, non Γ¨ stato trovato alcun contesto rilevante nei materiali forniti.
|
| 42 |
|
| 43 |
-
|
| 44 |
-
- Altrimenti, informa lβutente che non hai informazioni verificate per rispondere in modo affidabile.
|
| 45 |
|
| 46 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
"""
|
| 48 |
|
| 49 |
-
def get_fallback_prompt(language="en"):
|
| 50 |
-
fallback_prompts = {
|
| 51 |
-
"en": fallback_prompt_en,
|
| 52 |
-
"it": fallback_prompt_it
|
| 53 |
-
}
|
| 54 |
-
return fallback_prompts.get(language, fallback_prompts["en"])
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
def get_disclaimer(context_level, language="en"):
|
| 58 |
-
disclaimers = {
|
| 59 |
-
"en": {
|
| 60 |
-
"medium": "\n\nβ οΈ Note: the retrieved context has moderate similarity. The answer may not be fully reliable.",
|
| 61 |
-
"summary": "\n\nπ Note: the context is based on a general summary of the content. Please verify the information if needed.",
|
| 62 |
-
"low": "\n\nβ οΈ No reliable information was found in the source material. The answer may rely only on the conversation.",
|
| 63 |
-
"no_context": "\n\nβ οΈ No context available. The assistant will try to respond based on previous conversation, if possible."
|
| 64 |
-
},
|
| 65 |
-
"it": {
|
| 66 |
-
"medium": "\n\nβ οΈ Nota: il contesto recuperato ha una similaritΓ moderata. La risposta potrebbe non essere pienamente affidabile.",
|
| 67 |
-
"summary": "\n\nπ Nota: il contesto usato Γ¨ un riassunto generale del contenuto. Verifica le fonti se necessario.",
|
| 68 |
-
"low": "\n\nβ οΈ Nessuna informazione affidabile trovata nei materiali. La risposta potrebbe basarsi solo sulla conversazione.",
|
| 69 |
-
"no_context": "\n\nβ οΈ Nessun contesto disponibile. Lβassistente proverΓ a rispondere in base alla conversazione, se possibile."
|
| 70 |
-
}
|
| 71 |
-
}
|
| 72 |
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
class LlmManager():
|
| 76 |
|
|
@@ -88,48 +151,177 @@ class LlmManager():
|
|
| 88 |
def reset_messages(self, context, question):
|
| 89 |
self.messages = [SystemMessage(content=self.system_message.format(context=context, question=question))]
|
| 90 |
|
| 91 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
"""
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
"""
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
-
|
| 100 |
-
context, context_level = self.qdrant_manager.get_context_for_query(message)
|
| 101 |
-
print(f"π Contesto recuperato [{context_level}]: {context[:100]}")
|
| 102 |
|
| 103 |
-
|
| 104 |
-
base_prompt = get_system_message(self.language)
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
else:
|
| 116 |
-
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
else:
|
| 122 |
-
self.messages.append(HumanMessage(content=message))
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
self._roll_messages()
|
| 125 |
|
|
|
|
| 126 |
response = ""
|
|
|
|
|
|
|
|
|
|
| 127 |
for chunk in self.llm.stream(self.messages):
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
self.messages.append(AIMessage(content=response))
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
def send_message(self, message, contextualize=False):
|
| 135 |
if contextualize:
|
|
@@ -203,6 +395,11 @@ class LlmManager():
|
|
| 203 |
summary, _, _ = summary_manager.do_summary_stuff()
|
| 204 |
return summary
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
def _roll_messages(self):
|
| 207 |
"""
|
| 208 |
Keeps only the last `MAX_MESSAGES` from `messages`, excluding the first (SystemMessage).
|
|
|
|
| 1 |
+
import json, re
|
| 2 |
from langchain_openai import ChatOpenAI
|
| 3 |
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
| 4 |
from utilities.vectorstore.SummaryManager import SummaryManager
|
| 5 |
|
| 6 |
MAX_MESSAGES = 50
|
| 7 |
|
| 8 |
+
# system_message_it = """
|
| 9 |
+
# Sei ELI, un assistente che aiuta gli studenti ad analizzare un video e rispondere a domande su di esso.
|
| 10 |
+
# Basandoti sul contesto fornito, rispondi alla domanda dell'utente.
|
| 11 |
+
|
| 12 |
+
# - Contesto: {context}
|
| 13 |
+
# - Domanda: {question}
|
| 14 |
+
# """
|
| 15 |
+
|
| 16 |
+
# system_message_en = """
|
| 17 |
+
# You are ELI, an assistant that helps students analyze a video and answer questions about it.
|
| 18 |
+
# Based on the provided context, answer the user's question.
|
| 19 |
+
|
| 20 |
+
# - Context: {context}
|
| 21 |
+
# - Question: {question}
|
| 22 |
+
# """
|
| 23 |
+
|
| 24 |
+
# def get_system_message(language="en"):
|
| 25 |
+
# if language == "it":
|
| 26 |
+
# return system_message_it
|
| 27 |
+
# else:
|
| 28 |
+
# return system_message_en
|
| 29 |
+
|
| 30 |
+
# fallback_prompt_en = """
|
| 31 |
+
# You are ELI, an assistant that helps students analyze a video and answer questions about it.
|
| 32 |
+
# Unfortunately, no relevant context could be found in the provided material.
|
| 33 |
+
|
| 34 |
+
# - If possible, try to answer based on the previous conversation.
|
| 35 |
+
# - Otherwise, inform the user that no verified information is available to answer the question reliably.
|
| 36 |
+
|
| 37 |
+
# - Question: {question}
|
| 38 |
+
# """
|
| 39 |
+
|
| 40 |
+
# fallback_prompt_it = """
|
| 41 |
+
# Sei ELI, un assistente che aiuta gli studenti ad analizzare un video e rispondere a domande su di esso.
|
| 42 |
+
# Purtroppo, non Γ¨ stato trovato alcun contesto rilevante nei materiali forniti.
|
| 43 |
+
|
| 44 |
+
# - Se possibile, prova a rispondere basandoti sulla conversazione precedente.
|
| 45 |
+
# - Altrimenti, informa lβutente che non hai informazioni verificate per rispondere in modo affidabile.
|
| 46 |
+
|
| 47 |
+
# - Domanda: {question}
|
| 48 |
+
# """
|
| 49 |
+
|
| 50 |
+
# def get_fallback_prompt(language="en"):
|
| 51 |
+
# fallback_prompts = {
|
| 52 |
+
# "en": fallback_prompt_en,
|
| 53 |
+
# "it": fallback_prompt_it
|
| 54 |
+
# }
|
| 55 |
+
# return fallback_prompts.get(language, fallback_prompts["en"])
|
| 56 |
+
|
| 57 |
+
# def get_disclaimer(context_level, language="en"):
|
| 58 |
+
# disclaimers = {
|
| 59 |
+
# "en": {
|
| 60 |
+
# "medium": "\n\nβ οΈ Note: the retrieved context has moderate similarity. The answer may not be fully reliable.",
|
| 61 |
+
# "summary": "\n\nπ Note: the context is based on a general summary of the content. Please verify the information if needed.",
|
| 62 |
+
# "low": "\n\nβ οΈ No reliable information was found in the source material. The answer may rely only on the conversation.",
|
| 63 |
+
# "no_context": "\n\nβ οΈ No context available. The assistant will try to respond based on previous conversation, if possible."
|
| 64 |
+
# },
|
| 65 |
+
# "it": {
|
| 66 |
+
# "medium": "\n\nβ οΈ Nota: il contesto recuperato ha una similaritΓ moderata. La risposta potrebbe non essere pienamente affidabile.",
|
| 67 |
+
# "summary": "\n\nπ Nota: il contesto usato Γ¨ un riassunto generale del contenuto. Verifica le fonti se necessario.",
|
| 68 |
+
# "low": "\n\nβ οΈ Nessuna informazione affidabile trovata nei materiali. La risposta potrebbe basarsi solo sulla conversazione.",
|
| 69 |
+
# "no_context": "\n\nβ οΈ Nessun contesto disponibile. Lβassistente proverΓ a rispondere in base alla conversazione, se possibile."
|
| 70 |
+
# }
|
| 71 |
+
# }
|
| 72 |
+
|
| 73 |
+
# return disclaimers.get(language, {}).get(context_level, "")
|
| 74 |
|
| 75 |
system_message_en = """
|
| 76 |
You are ELI, an assistant that helps students analyze a video and answer questions about it.
|
|
|
|
| 77 |
|
| 78 |
+
Evaluate how well the provided context supports answering the user's question.
|
|
|
|
|
|
|
| 79 |
|
| 80 |
+
If the context is not sufficient, you may use the previous conversation to help answer, if possible.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
+
Use one of the following support levels:
|
| 83 |
+
- green: clearly supported by the context
|
| 84 |
+
- yellow: partially supported or inferred
|
| 85 |
+
- red: not supported by the context (but you may still answer based on the conversation)
|
| 86 |
+
|
| 87 |
+
At the beginning of your answer, write a line with the support level in the following format:
|
| 88 |
+
|
| 89 |
+
[SUPPORT: green]
|
| 90 |
+
|
| 91 |
+
Then go to the next line and provide the actual answer, with proper formatting.
|
| 92 |
|
| 93 |
+
Context:
|
| 94 |
+
{context}
|
| 95 |
|
| 96 |
+
Question:
|
| 97 |
+
{question}
|
| 98 |
"""
|
| 99 |
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
system_message_it = """
|
| 104 |
Sei ELI, un assistente che aiuta gli studenti ad analizzare un video e rispondere a domande su di esso.
|
|
|
|
| 105 |
|
| 106 |
+
Valuta quanto il contesto fornito ti permette di rispondere in modo affidabile alla domanda.
|
|
|
|
| 107 |
|
| 108 |
+
Se il contesto non Γ¨ sufficiente, puoi anche usare la conversazione precedente per rispondere, se disponibile.
|
| 109 |
+
|
| 110 |
+
Usa uno dei seguenti livelli di supporto:
|
| 111 |
+
- verde: supportata chiaramente dal contesto
|
| 112 |
+
- giallo: supportata parzialmente o inferita
|
| 113 |
+
- rosso: non supportata dal contesto (ma potresti rispondere comunque grazie alla conversazione)
|
| 114 |
+
|
| 115 |
+
All'inizio della tua risposta scrivi una riga con il livello di supporto nel formato:
|
| 116 |
+
|
| 117 |
+
[SUPPORT: verde]
|
| 118 |
+
|
| 119 |
+
Poi vai a capo e fornisci la risposta normalmente, con la formattazione corretta.
|
| 120 |
+
|
| 121 |
+
Contesto:
|
| 122 |
+
{context}
|
| 123 |
+
|
| 124 |
+
Domanda:
|
| 125 |
+
{question}
|
| 126 |
"""
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def get_system_message(language="en"):
|
| 132 |
+
if language == "it":
|
| 133 |
+
return system_message_it
|
| 134 |
+
else:
|
| 135 |
+
return system_message_en
|
| 136 |
+
|
| 137 |
|
| 138 |
class LlmManager():
|
| 139 |
|
|
|
|
| 151 |
def reset_messages(self, context, question):
|
| 152 |
self.messages = [SystemMessage(content=self.system_message.format(context=context, question=question))]
|
| 153 |
|
| 154 |
+
# def stream_message_standard(self, message):
|
| 155 |
+
# """
|
| 156 |
+
# ModalitΓ STANDARD: recupera il contesto semanticamente da Qdrant.
|
| 157 |
+
# """
|
| 158 |
+
# context = ""
|
| 159 |
+
# context_level = "no_context"
|
| 160 |
+
|
| 161 |
+
# if self.qdrant_manager and self.qdrant_manager.is_loaded():
|
| 162 |
+
# context, context_level = self.qdrant_manager.get_context_for_query(message)
|
| 163 |
+
# print(f"π Contesto recuperato [{context_level}]: {context[:100]}")
|
| 164 |
+
|
| 165 |
+
# disclaimer = get_disclaimer(context_level, self.language)
|
| 166 |
+
# base_prompt = get_system_message(self.language)
|
| 167 |
+
|
| 168 |
+
# # Fallback se il contesto Γ¨ debole o assente
|
| 169 |
+
# if not context.strip() and context_level in ["low", "no_context"]:
|
| 170 |
+
# formatted_message = get_fallback_prompt(self.language).format(question=message)
|
| 171 |
+
# else:
|
| 172 |
+
# formatted_message = base_prompt.format(context=context, question=message) + disclaimer
|
| 173 |
+
|
| 174 |
+
# # Aggiorna o imposta il messaggio di sistema
|
| 175 |
+
# if self.messages and isinstance(self.messages[0], SystemMessage):
|
| 176 |
+
# self.messages[0] = SystemMessage(content=formatted_message)
|
| 177 |
+
# else:
|
| 178 |
+
# self.messages.insert(0, SystemMessage(content=formatted_message))
|
| 179 |
+
|
| 180 |
+
# self.messages.append(HumanMessage(content=message))
|
| 181 |
+
# self._roll_messages()
|
| 182 |
+
|
| 183 |
+
# response = ""
|
| 184 |
+
# for chunk in self.llm.stream(self.messages):
|
| 185 |
+
# response += chunk.content
|
| 186 |
+
# yield {"content": chunk.content, "context_level": context_level} # β
stream + context_level
|
| 187 |
+
|
| 188 |
+
# self.messages.append(AIMessage(content=response))
|
| 189 |
+
|
| 190 |
+
def stream_message_standard(self, message):
|
| 191 |
"""
|
| 192 |
+
ModalitΓ STANDARD:
|
| 193 |
+
- Recupera contesto da Qdrant
|
| 194 |
+
- Costruisce prompt con system message
|
| 195 |
+
- Mantiene memoria conversazionale
|
| 196 |
+
- Streamma solo la parte di risposta dopo [SUPPORT: ...]
|
| 197 |
"""
|
| 198 |
+
context = ""
|
| 199 |
+
context_level = "no_context"
|
| 200 |
+
|
| 201 |
+
if self.qdrant_manager and self.qdrant_manager.is_loaded():
|
| 202 |
+
context, context_level = self.qdrant_manager.get_context_for_query(message)
|
| 203 |
+
print(f"π Contesto recuperato [{context_level}]: {context[:100]}")
|
| 204 |
|
| 205 |
+
base_prompt = get_system_message(self.language)
|
|
|
|
|
|
|
| 206 |
|
| 207 |
+
formatted_message = base_prompt.format(context=context, question=message)
|
|
|
|
| 208 |
|
| 209 |
+
# Mantieni la cronologia
|
| 210 |
+
if self.messages and isinstance(self.messages[0], SystemMessage):
|
| 211 |
+
self.messages[0] = SystemMessage(content=formatted_message)
|
| 212 |
+
else:
|
| 213 |
+
self.messages.insert(0, SystemMessage(content=formatted_message))
|
| 214 |
+
|
| 215 |
+
self.messages.append(HumanMessage(content=message))
|
| 216 |
+
self._roll_messages()
|
| 217 |
+
|
| 218 |
+
buffer = ""
|
| 219 |
+
response = ""
|
| 220 |
+
support_level = None
|
| 221 |
+
start_streaming = False
|
| 222 |
|
| 223 |
+
for chunk in self.llm.stream(self.messages):
|
| 224 |
+
chunk_text = chunk.content
|
| 225 |
+
if not start_streaming:
|
| 226 |
+
buffer += chunk_text
|
| 227 |
+
match = re.search(r"\[SUPPORT:\s*(green|yellow|red|verde|giallo|rosso)\]", buffer, re.IGNORECASE)
|
| 228 |
+
if match:
|
| 229 |
+
raw_level = match.group(1).lower().strip()
|
| 230 |
+
support_level = {
|
| 231 |
+
"verde": "green",
|
| 232 |
+
"giallo": "yellow",
|
| 233 |
+
"rosso": "red"
|
| 234 |
+
}.get(raw_level, raw_level)
|
| 235 |
+
|
| 236 |
+
print(f"β
SUPPORT LEVEL: {support_level}")
|
| 237 |
+
|
| 238 |
+
# Inizia lo streaming dal contenuto dopo il tag
|
| 239 |
+
after = buffer[match.end():].lstrip()
|
| 240 |
+
if after:
|
| 241 |
+
response += after
|
| 242 |
+
#yield {"content": after, "context_level": context_level}
|
| 243 |
+
yield {"content": after, "support_level": support_level or "unknown"}
|
| 244 |
+
buffer = ""
|
| 245 |
+
start_streaming = True
|
| 246 |
else:
|
| 247 |
+
response += chunk_text
|
| 248 |
+
#yield {"content": chunk_text, "context_level": context_level}
|
| 249 |
+
yield {"content": chunk_text, "support_level": support_level or "unknown"}
|
| 250 |
|
| 251 |
+
response = response.strip()
|
| 252 |
+
print(f"π€ ELI: {response}")
|
| 253 |
+
self.messages.append(AIMessage(content=response))
|
| 254 |
+
|
| 255 |
+
yield {
|
| 256 |
+
"content": None,
|
| 257 |
+
#"context_level": context_level,
|
| 258 |
+
"support_level": support_level or "unknown"
|
| 259 |
+
}
|
| 260 |
|
|
|
|
|
|
|
| 261 |
|
| 262 |
+
def stream_message_study(self, message):
|
| 263 |
+
"""
|
| 264 |
+
ModalitΓ STUDIO: il contesto Γ¨ giΓ stato fissato tramite set_focus_on_chunk().
|
| 265 |
+
La risposta include anche il livello di supporto ([SUPPORT: ...]).
|
| 266 |
+
"""
|
| 267 |
+
context_level = "fixed" # contesto chunk-based
|
| 268 |
+
self.messages.append(HumanMessage(content=message))
|
| 269 |
self._roll_messages()
|
| 270 |
|
| 271 |
+
buffer = ""
|
| 272 |
response = ""
|
| 273 |
+
support_level = None
|
| 274 |
+
start_streaming = False
|
| 275 |
+
|
| 276 |
for chunk in self.llm.stream(self.messages):
|
| 277 |
+
chunk_text = chunk.content
|
| 278 |
+
if not start_streaming:
|
| 279 |
+
buffer += chunk_text
|
| 280 |
+
match = re.search(r"\[SUPPORT:\s*(green|yellow|red|verde|giallo|rosso)\]", buffer, re.IGNORECASE)
|
| 281 |
+
if match:
|
| 282 |
+
raw_level = match.group(1).lower().strip()
|
| 283 |
+
support_level = {
|
| 284 |
+
"verde": "green",
|
| 285 |
+
"giallo": "yellow",
|
| 286 |
+
"rosso": "red"
|
| 287 |
+
}.get(raw_level, raw_level)
|
| 288 |
+
|
| 289 |
+
print(f"β
SUPPORT LEVEL (study): {support_level}")
|
| 290 |
+
|
| 291 |
+
after = buffer[match.end():].lstrip()
|
| 292 |
+
if after:
|
| 293 |
+
response += after
|
| 294 |
+
yield {"content": after, "support_level": support_level or "unknown"}
|
| 295 |
+
buffer = ""
|
| 296 |
+
start_streaming = True
|
| 297 |
+
else:
|
| 298 |
+
response += chunk_text
|
| 299 |
+
yield {"content": chunk_text, "support_level": support_level or "unknown"}
|
| 300 |
|
| 301 |
self.messages.append(AIMessage(content=response))
|
| 302 |
+
|
| 303 |
+
yield {
|
| 304 |
+
"content": None,
|
| 305 |
+
"support_level": support_level or "unknown"
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
# def stream_message_study(self, message):
|
| 310 |
+
# """
|
| 311 |
+
# ModalitΓ STUDIO: il contesto Γ¨ giΓ stato fissato tramite set_focus_on_chunk().
|
| 312 |
+
# """
|
| 313 |
+
# context_level = "fixed" # π contesto chunk-based
|
| 314 |
+
|
| 315 |
+
# self.messages.append(HumanMessage(content=message))
|
| 316 |
+
# self._roll_messages()
|
| 317 |
+
|
| 318 |
+
# response = ""
|
| 319 |
+
# for chunk in self.llm.stream(self.messages):
|
| 320 |
+
# response += chunk.content
|
| 321 |
+
# yield {"content": chunk.content, "context_level": context_level}
|
| 322 |
+
|
| 323 |
+
# self.messages.append(AIMessage(content=response))
|
| 324 |
+
|
| 325 |
|
| 326 |
def send_message(self, message, contextualize=False):
|
| 327 |
if contextualize:
|
|
|
|
| 395 |
summary, _, _ = summary_manager.do_summary_stuff()
|
| 396 |
return summary
|
| 397 |
|
| 398 |
+
def set_focus_on_chunk(self, chunk_text):
|
| 399 |
+
self.messages = [
|
| 400 |
+
SystemMessage(content=self.system_message.format(context=chunk_text, question=""))
|
| 401 |
+
]
|
| 402 |
+
|
| 403 |
def _roll_messages(self):
|
| 404 |
"""
|
| 405 |
Keeps only the last `MAX_MESSAGES` from `messages`, excluding the first (SystemMessage).
|
utilities/llm/__pycache__/LlmManager.cpython-312.pyc
CHANGED
|
Binary files a/utilities/llm/__pycache__/LlmManager.cpython-312.pyc and b/utilities/llm/__pycache__/LlmManager.cpython-312.pyc differ
|
|
|
utilities/vectorstore/QdrantLangchainManager.py
CHANGED
|
@@ -200,6 +200,34 @@ class QdrantLangchainManager:
|
|
| 200 |
print(f"Error fetching documents from Qdrant: {e}")
|
| 201 |
return []
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
def get_context_for_query(self, query, top_k=8):
|
| 204 |
print("π get_context_for_query:", query)
|
| 205 |
|
|
@@ -219,7 +247,14 @@ class QdrantLangchainManager:
|
|
| 219 |
print("β οΈ Nessun documento con type='content' trovato.")
|
| 220 |
return "", "no_context"
|
| 221 |
|
| 222 |
-
print(f"π¦ Trovati {len(docs_with_scores)} documenti candidati.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
# Se non c'Γ¨ reranker, usa soglia base
|
| 225 |
if not self.reranker:
|
|
@@ -241,9 +276,9 @@ class QdrantLangchainManager:
|
|
| 241 |
high_conf, medium_conf = [], []
|
| 242 |
|
| 243 |
for (doc, _), score in reranked:
|
| 244 |
-
if score >
|
| 245 |
high_conf.append(doc.page_content)
|
| 246 |
-
elif score >
|
| 247 |
medium_conf.append(doc.page_content)
|
| 248 |
|
| 249 |
if high_conf:
|
|
@@ -287,7 +322,6 @@ class QdrantLangchainManager:
|
|
| 287 |
print(f"β Errore nel recupero del contesto: {e}")
|
| 288 |
return "", "no_context"
|
| 289 |
|
| 290 |
-
|
| 291 |
def delete_collection(self, collection_name):
|
| 292 |
try:
|
| 293 |
self.client.delete_collection(collection_name)
|
|
@@ -331,34 +365,25 @@ class QdrantLangchainManager:
|
|
| 331 |
|
| 332 |
return merged_chunks, merge_performed
|
| 333 |
|
| 334 |
-
# def _reranking(self, query, docs_with_scores):
|
| 335 |
-
# if not self.reranker:
|
| 336 |
-
# print("β οΈ Reranker not initialized. Skipping reranking.")
|
| 337 |
-
# return docs_with_scores
|
| 338 |
-
|
| 339 |
-
# query_pairs = [(query, doc.page_content) for doc, _ in docs_with_scores]
|
| 340 |
-
# new_scores = self.reranker.predict(query_pairs)
|
| 341 |
-
# return sorted(zip(docs_with_scores, new_scores), key=lambda x: x[1], reverse=True)
|
| 342 |
-
|
| 343 |
def _reranking(self, query, docs_with_scores):
|
| 344 |
if not self.reranker:
|
| 345 |
print("β οΈ Reranker not initialized. Skipping reranking.")
|
| 346 |
return docs_with_scores
|
| 347 |
-
|
|
|
|
| 348 |
query_pairs = [(query, doc.page_content) for doc, _ in docs_with_scores]
|
| 349 |
-
|
| 350 |
-
#
|
| 351 |
raw_scores = self.reranker.predict(query_pairs)
|
| 352 |
-
|
| 353 |
-
# Applica sigmoid per ottenere probabilitΓ tra 0 e 1
|
| 354 |
-
prob_scores = sigmoid(torch.tensor(raw_scores)).tolist()
|
| 355 |
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
print(f"{i+1:02d}. chunk_index={
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
-
return sorted(zip(docs_with_scores, prob_scores), key=lambda x: x[1], reverse=True)
|
| 362 |
|
| 363 |
def _calculate_tokens(self, text):
|
| 364 |
"""Calculate the number of tokens in the given text."""
|
|
|
|
| 200 |
print(f"Error fetching documents from Qdrant: {e}")
|
| 201 |
return []
|
| 202 |
|
| 203 |
+
def get_chunk_by_index(self, index: int):
|
| 204 |
+
try:
|
| 205 |
+
results = self.client.scroll(
|
| 206 |
+
collection_name=self.collection_name,
|
| 207 |
+
scroll_filter=Filter(
|
| 208 |
+
must=[
|
| 209 |
+
FieldCondition(
|
| 210 |
+
key="metadata.chunk_index",
|
| 211 |
+
match=MatchValue(value=index)
|
| 212 |
+
),
|
| 213 |
+
FieldCondition(
|
| 214 |
+
key="metadata.type",
|
| 215 |
+
match=MatchValue(value="content")
|
| 216 |
+
)
|
| 217 |
+
]
|
| 218 |
+
),
|
| 219 |
+
limit=1,
|
| 220 |
+
with_payload=True
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
if results[0]:
|
| 224 |
+
return results[0][0].payload.get("page_content", "")
|
| 225 |
+
else:
|
| 226 |
+
return None
|
| 227 |
+
except Exception as e:
|
| 228 |
+
print(f"β Error retrieving chunk {index}: {e}")
|
| 229 |
+
return None
|
| 230 |
+
|
| 231 |
def get_context_for_query(self, query, top_k=8):
|
| 232 |
print("π get_context_for_query:", query)
|
| 233 |
|
|
|
|
| 247 |
print("β οΈ Nessun documento con type='content' trovato.")
|
| 248 |
return "", "no_context"
|
| 249 |
|
| 250 |
+
# print(f"π¦ Trovati {len(docs_with_scores)} documenti candidati.")
|
| 251 |
+
|
| 252 |
+
# Log original scores (solo per confronto visivo)
|
| 253 |
+
# print("\nπ Original similarity scores (with sigmoid just for print):")
|
| 254 |
+
# for i, (doc, original_score) in enumerate(docs_with_scores):
|
| 255 |
+
# chunk_index = doc.metadata.get("chunk_index", "-")
|
| 256 |
+
# sigmoid_score = sigmoid(torch.tensor(original_score)).item()
|
| 257 |
+
# print(f"{i+1:02d}. chunk_index={chunk_index}, original_score={original_score:.3f}, sigmoid(original)={sigmoid_score:.3f}")
|
| 258 |
|
| 259 |
# Se non c'Γ¨ reranker, usa soglia base
|
| 260 |
if not self.reranker:
|
|
|
|
| 276 |
high_conf, medium_conf = [], []
|
| 277 |
|
| 278 |
for (doc, _), score in reranked:
|
| 279 |
+
if score > 1:
|
| 280 |
high_conf.append(doc.page_content)
|
| 281 |
+
elif score > -5:
|
| 282 |
medium_conf.append(doc.page_content)
|
| 283 |
|
| 284 |
if high_conf:
|
|
|
|
| 322 |
print(f"β Errore nel recupero del contesto: {e}")
|
| 323 |
return "", "no_context"
|
| 324 |
|
|
|
|
| 325 |
def delete_collection(self, collection_name):
|
| 326 |
try:
|
| 327 |
self.client.delete_collection(collection_name)
|
|
|
|
| 365 |
|
| 366 |
return merged_chunks, merge_performed
|
| 367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
def _reranking(self, query, docs_with_scores):
|
| 369 |
if not self.reranker:
|
| 370 |
print("β οΈ Reranker not initialized. Skipping reranking.")
|
| 371 |
return docs_with_scores
|
| 372 |
+
|
| 373 |
+
# Prepara input per reranker
|
| 374 |
query_pairs = [(query, doc.page_content) for doc, _ in docs_with_scores]
|
| 375 |
+
|
| 376 |
+
# Ottieni i punteggi dal reranker (giΓ in scala 0β1, ma non da sigmoid)
|
| 377 |
raw_scores = self.reranker.predict(query_pairs)
|
|
|
|
|
|
|
|
|
|
| 378 |
|
| 379 |
+
print("\nπ Reranker scores (raw):")
|
| 380 |
+
for i, ((doc, _), score) in enumerate(zip(docs_with_scores, raw_scores)):
|
| 381 |
+
chunk_index = doc.metadata.get("chunk_index", "-")
|
| 382 |
+
print(f"{i+1:02d}. chunk_index={chunk_index}, reranked_score={score:.4f}")
|
| 383 |
+
|
| 384 |
+
# Ritorna risultati ordinati per score decrescente
|
| 385 |
+
return sorted(zip(docs_with_scores, raw_scores), key=lambda x: x[1], reverse=True)
|
| 386 |
|
|
|
|
| 387 |
|
| 388 |
def _calculate_tokens(self, text):
|
| 389 |
"""Calculate the number of tokens in the given text."""
|
utilities/vectorstore/SummaryManager.py
CHANGED
|
@@ -220,14 +220,24 @@ class SummaryManager:
|
|
| 220 |
print("β No documents found in collection.")
|
| 221 |
return None, 0, 0
|
| 222 |
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
-
# STEP
|
| 226 |
-
embeddings = [doc["vector"] for doc in
|
| 227 |
-
documents = [doc["payload"]["page_content"] for doc in
|
| 228 |
-
metadata = [doc["payload"] for doc in
|
| 229 |
|
| 230 |
-
# STEP
|
| 231 |
MAX_SELECTED_DOCS = 5
|
| 232 |
selected_docs = self._select_best_chunks(
|
| 233 |
documents=documents,
|
|
@@ -239,7 +249,7 @@ class SummaryManager:
|
|
| 239 |
total_tokens = sum(self.llm.get_num_tokens(doc.page_content) for doc in selected_docs)
|
| 240 |
print(f"β
Selected {len(selected_docs)} docs with total tokens: {total_tokens}")
|
| 241 |
|
| 242 |
-
# STEP
|
| 243 |
map_prompt_template = PromptTemplate(template=get_map_prompt(self.language), input_variables=["text"])
|
| 244 |
combine_prompt_template = PromptTemplate(template=get_combine_prompt(self.language), input_variables=["text"])
|
| 245 |
|
|
@@ -257,7 +267,7 @@ class SummaryManager:
|
|
| 257 |
token_count = self.llm.get_num_tokens(doc.page_content)
|
| 258 |
print(f"Chunk {i+1}: {token_count} tokens")
|
| 259 |
|
| 260 |
-
# STEP
|
| 261 |
with get_openai_callback() as cb:
|
| 262 |
result = summary_chain.invoke({"input_documents": selected_docs})
|
| 263 |
input_tokens_used = cb.prompt_tokens
|
|
@@ -268,7 +278,7 @@ class SummaryManager:
|
|
| 268 |
full_summary = result['output_text']
|
| 269 |
print("β
Map-reduce summary generated.")
|
| 270 |
|
| 271 |
-
# STEP
|
| 272 |
inserted = self.qdrant_manager.insert_text(
|
| 273 |
text=full_summary,
|
| 274 |
metadata={
|
|
@@ -301,14 +311,22 @@ class SummaryManager:
|
|
| 301 |
print("β No documents found in collection.")
|
| 302 |
return None, 0, 0
|
| 303 |
|
| 304 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
metadata = [doc["payload"] for doc in all_documents]
|
| 310 |
|
| 311 |
-
# STEP 3:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
selected_docs = self._get_chunks_for_stuff(
|
| 313 |
documents=documents,
|
| 314 |
metadata=metadata,
|
|
@@ -321,7 +339,7 @@ class SummaryManager:
|
|
| 321 |
total_tokens = sum(self.llm.get_num_tokens(doc.page_content) for doc in selected_docs)
|
| 322 |
print(f"β
Selected {len(selected_docs)} docs with total tokens: {total_tokens}")
|
| 323 |
|
| 324 |
-
# STEP
|
| 325 |
combine_prompt_template = PromptTemplate(
|
| 326 |
template=get_combine_prompt(self.language),
|
| 327 |
input_variables=["text"]
|
|
@@ -335,7 +353,7 @@ class SummaryManager:
|
|
| 335 |
verbose=False
|
| 336 |
)
|
| 337 |
|
| 338 |
-
# STEP
|
| 339 |
with get_openai_callback() as cb:
|
| 340 |
result = summary_chain.invoke({"input_documents": selected_docs})
|
| 341 |
input_tokens_used = cb.prompt_tokens
|
|
@@ -346,7 +364,7 @@ class SummaryManager:
|
|
| 346 |
full_summary = result['output_text']
|
| 347 |
print("β
Stuff summary generated.")
|
| 348 |
|
| 349 |
-
# STEP
|
| 350 |
inserted = self.qdrant_manager.insert_text(
|
| 351 |
text=full_summary,
|
| 352 |
metadata={
|
|
@@ -359,7 +377,7 @@ class SummaryManager:
|
|
| 359 |
print("π Final summary saved to vector store.")
|
| 360 |
|
| 361 |
return full_summary, input_tokens_used, output_tokens_used
|
| 362 |
-
|
| 363 |
def _find_closest_embeddings(self,vectors, num_clusters, kmeans):
|
| 364 |
closest_indices = []
|
| 365 |
for i in range(num_clusters):
|
|
|
|
| 220 |
print("β No documents found in collection.")
|
| 221 |
return None, 0, 0
|
| 222 |
|
| 223 |
+
# STEP 2: filtra fuori i documenti di tipo summary
|
| 224 |
+
filtered_docs = [
|
| 225 |
+
doc for doc in all_documents
|
| 226 |
+
if not str(doc["payload"].get("type", "")).endswith("summary")
|
| 227 |
+
]
|
| 228 |
+
|
| 229 |
+
if not filtered_docs:
|
| 230 |
+
print("β No non-summary documents available for summarization.")
|
| 231 |
+
return None, 0, 0
|
| 232 |
+
|
| 233 |
+
print(f"π {len(filtered_docs)} documents after filtering summaries.", flush=True)
|
| 234 |
|
| 235 |
+
# STEP 3: extract vectors & text
|
| 236 |
+
embeddings = [doc["vector"] for doc in filtered_docs]
|
| 237 |
+
documents = [doc["payload"]["page_content"] for doc in filtered_docs]
|
| 238 |
+
metadata = [doc["payload"] for doc in filtered_docs]
|
| 239 |
|
| 240 |
+
# STEP 4: select up to MAX_SELECTED_DOCS chunks via KMeans
|
| 241 |
MAX_SELECTED_DOCS = 5
|
| 242 |
selected_docs = self._select_best_chunks(
|
| 243 |
documents=documents,
|
|
|
|
| 249 |
total_tokens = sum(self.llm.get_num_tokens(doc.page_content) for doc in selected_docs)
|
| 250 |
print(f"β
Selected {len(selected_docs)} docs with total tokens: {total_tokens}")
|
| 251 |
|
| 252 |
+
# STEP 5: load LangChain prompts
|
| 253 |
map_prompt_template = PromptTemplate(template=get_map_prompt(self.language), input_variables=["text"])
|
| 254 |
combine_prompt_template = PromptTemplate(template=get_combine_prompt(self.language), input_variables=["text"])
|
| 255 |
|
|
|
|
| 267 |
token_count = self.llm.get_num_tokens(doc.page_content)
|
| 268 |
print(f"Chunk {i+1}: {token_count} tokens")
|
| 269 |
|
| 270 |
+
# STEP 6: run the chain with token tracking
|
| 271 |
with get_openai_callback() as cb:
|
| 272 |
result = summary_chain.invoke({"input_documents": selected_docs})
|
| 273 |
input_tokens_used = cb.prompt_tokens
|
|
|
|
| 278 |
full_summary = result['output_text']
|
| 279 |
print("β
Map-reduce summary generated.")
|
| 280 |
|
| 281 |
+
# STEP 7: store the final summary
|
| 282 |
inserted = self.qdrant_manager.insert_text(
|
| 283 |
text=full_summary,
|
| 284 |
metadata={
|
|
|
|
| 311 |
print("β No documents found in collection.")
|
| 312 |
return None, 0, 0
|
| 313 |
|
| 314 |
+
# STEP 2: filtra fuori i documenti di tipo summary
|
| 315 |
+
filtered_docs = [
|
| 316 |
+
doc for doc in all_documents
|
| 317 |
+
if not str(doc["payload"].get("type", "")).endswith("summary")
|
| 318 |
+
]
|
| 319 |
|
| 320 |
+
if not filtered_docs:
|
| 321 |
+
print("β No non-summary documents available for summarization.")
|
| 322 |
+
return None, 0, 0
|
|
|
|
| 323 |
|
| 324 |
+
# STEP 3: extract vectors & text
|
| 325 |
+
embeddings = [doc["vector"] for doc in filtered_docs]
|
| 326 |
+
documents = [doc["payload"]["page_content"] for doc in filtered_docs]
|
| 327 |
+
metadata = [doc["payload"] for doc in filtered_docs]
|
| 328 |
+
|
| 329 |
+
# STEP 4: selezione intelligente con fallback a clustering
|
| 330 |
selected_docs = self._get_chunks_for_stuff(
|
| 331 |
documents=documents,
|
| 332 |
metadata=metadata,
|
|
|
|
| 339 |
total_tokens = sum(self.llm.get_num_tokens(doc.page_content) for doc in selected_docs)
|
| 340 |
print(f"β
Selected {len(selected_docs)} docs with total tokens: {total_tokens}")
|
| 341 |
|
| 342 |
+
# STEP 5: load chain
|
| 343 |
combine_prompt_template = PromptTemplate(
|
| 344 |
template=get_combine_prompt(self.language),
|
| 345 |
input_variables=["text"]
|
|
|
|
| 353 |
verbose=False
|
| 354 |
)
|
| 355 |
|
| 356 |
+
# STEP 6: run the chain with token tracking
|
| 357 |
with get_openai_callback() as cb:
|
| 358 |
result = summary_chain.invoke({"input_documents": selected_docs})
|
| 359 |
input_tokens_used = cb.prompt_tokens
|
|
|
|
| 364 |
full_summary = result['output_text']
|
| 365 |
print("β
Stuff summary generated.")
|
| 366 |
|
| 367 |
+
# STEP 7: store the final summary
|
| 368 |
inserted = self.qdrant_manager.insert_text(
|
| 369 |
text=full_summary,
|
| 370 |
metadata={
|
|
|
|
| 377 |
print("π Final summary saved to vector store.")
|
| 378 |
|
| 379 |
return full_summary, input_tokens_used, output_tokens_used
|
| 380 |
+
|
| 381 |
def _find_closest_embeddings(self,vectors, num_clusters, kmeans):
|
| 382 |
closest_indices = []
|
| 383 |
for i in range(num_clusters):
|
utilities/vectorstore/__pycache__/QdrantLangchainManager.cpython-312.pyc
CHANGED
|
Binary files a/utilities/vectorstore/__pycache__/QdrantLangchainManager.cpython-312.pyc and b/utilities/vectorstore/__pycache__/QdrantLangchainManager.cpython-312.pyc differ
|
|
|
utilities/vectorstore/__pycache__/SummaryManager.cpython-312.pyc
CHANGED
|
Binary files a/utilities/vectorstore/__pycache__/SummaryManager.cpython-312.pyc and b/utilities/vectorstore/__pycache__/SummaryManager.cpython-312.pyc differ
|
|
|
utils.py
CHANGED
|
@@ -78,7 +78,15 @@ def chat_with_bot(llm_manager, contextualize=True):
|
|
| 78 |
except Exception as e:
|
| 79 |
print(f"β οΈ Error: {e}\n")
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
|
|
|
| 82 |
|
| 83 |
llm_manager, qdrant_manager = initialize()
|
| 84 |
|
|
@@ -86,10 +94,10 @@ llm_manager, qdrant_manager = initialize()
|
|
| 86 |
collection_name="key_statistics"
|
| 87 |
|
| 88 |
llm_manager, qdrant_manager = initialize()
|
| 89 |
-
if qdrant_manager.get_collection(collection_name):
|
| 90 |
-
|
| 91 |
|
| 92 |
-
chat_with_bot(llm_manager)
|
| 93 |
|
| 94 |
#manage_collection(file_name, collection_name)
|
| 95 |
#summary=get_initial_summary(collection_name)
|
|
@@ -99,4 +107,7 @@ chat_with_bot(llm_manager)
|
|
| 99 |
# if summary:
|
| 100 |
# print(f"β
Summary:\n{summary}")
|
| 101 |
# else:
|
| 102 |
-
# print("β οΈ Nessun riassunto generato.")
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
except Exception as e:
|
| 79 |
print(f"β οΈ Error: {e}\n")
|
| 80 |
|
| 81 |
+
def get_chunk(collection_name, chunk_id):
|
| 82 |
+
"""Retrieve a specific chunk from a Qdrant collection."""
|
| 83 |
+
|
| 84 |
+
# Carica la collection se esiste
|
| 85 |
+
if not qdrant_manager.get_collection(collection_name):
|
| 86 |
+
print(f"β Collection '{collection_name}' non trovata.")
|
| 87 |
+
return None
|
| 88 |
|
| 89 |
+
return qdrant_manager.get_chunk_by_index(chunk_id)
|
| 90 |
|
| 91 |
llm_manager, qdrant_manager = initialize()
|
| 92 |
|
|
|
|
| 94 |
collection_name="key_statistics"
|
| 95 |
|
| 96 |
llm_manager, qdrant_manager = initialize()
|
| 97 |
+
# if qdrant_manager.get_collection(collection_name):
|
| 98 |
+
# llm_manager.set_qdrant_manager(qdrant_manager)
|
| 99 |
|
| 100 |
+
#chat_with_bot(llm_manager)
|
| 101 |
|
| 102 |
#manage_collection(file_name, collection_name)
|
| 103 |
#summary=get_initial_summary(collection_name)
|
|
|
|
| 107 |
# if summary:
|
| 108 |
# print(f"β
Summary:\n{summary}")
|
| 109 |
# else:
|
| 110 |
+
# print("β οΈ Nessun riassunto generato.")
|
| 111 |
+
|
| 112 |
+
text=get_chunk(collection_name, 1)
|
| 113 |
+
print(text)
|