lollms-client 0.29.0__py3-none-any.whl → 0.29.1__py3-none-any.whl
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.
Potentially problematic release.
This version of lollms-client might be problematic. Click here for more details.
- examples/text_gen.py +1 -1
- lollms_client/__init__.py +1 -1
- lollms_client/llm_bindings/llamacpp/__init__.py +1 -0
- lollms_client/llm_bindings/lollms/__init__.py +411 -267
- lollms_client/llm_bindings/lollms_webui/__init__.py +428 -0
- lollms_client/lollms_core.py +151 -124
- lollms_client/lollms_discussion.py +262 -38
- lollms_client/lollms_utilities.py +10 -2
- {lollms_client-0.29.0.dist-info → lollms_client-0.29.1.dist-info}/METADATA +248 -47
- {lollms_client-0.29.0.dist-info → lollms_client-0.29.1.dist-info}/RECORD +13 -13
- lollms_client/llm_bindings/lollms_chat/__init__.py +0 -571
- {lollms_client-0.29.0.dist-info → lollms_client-0.29.1.dist-info}/WHEEL +0 -0
- {lollms_client-0.29.0.dist-info → lollms_client-0.29.1.dist-info}/licenses/LICENSE +0 -0
- {lollms_client-0.29.0.dist-info → lollms_client-0.29.1.dist-info}/top_level.txt +0 -0
|
@@ -128,7 +128,11 @@ def create_dynamic_models(
|
|
|
128
128
|
__abstract__ = True
|
|
129
129
|
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
130
130
|
system_prompt = Column(EncryptedText, nullable=True)
|
|
131
|
-
|
|
131
|
+
user_data_zone = Column(EncryptedText, nullable=True) # Field for persistent user-specific data
|
|
132
|
+
discussion_data_zone = Column(EncryptedText, nullable=True) # Field for persistent discussion-specific data
|
|
133
|
+
personality_data_zone = Column(EncryptedText, nullable=True) # Field for persistent personality-specific data
|
|
134
|
+
memory = Column(EncryptedText, nullable=True) # New field for long-term memory across discussions
|
|
135
|
+
|
|
132
136
|
participants = Column(JSON, nullable=True, default=dict)
|
|
133
137
|
active_branch_id = Column(String, nullable=True)
|
|
134
138
|
discussion_metadata = Column(JSON, nullable=True, default=dict)
|
|
@@ -213,8 +217,6 @@ class LollmsDataManager:
|
|
|
213
217
|
with self.engine.connect() as connection:
|
|
214
218
|
print("Checking for database schema upgrades...")
|
|
215
219
|
|
|
216
|
-
# --- THIS IS THE FIX ---
|
|
217
|
-
# We must wrap raw SQL strings in the `text()` function for direct execution.
|
|
218
220
|
cursor = connection.execute(text("PRAGMA table_info(discussions)"))
|
|
219
221
|
columns = [row[1] for row in cursor.fetchall()]
|
|
220
222
|
|
|
@@ -226,12 +228,27 @@ class LollmsDataManager:
|
|
|
226
228
|
print(" -> Upgrading 'discussions' table: Adding 'pruning_point_id' column.")
|
|
227
229
|
connection.execute(text("ALTER TABLE discussions ADD COLUMN pruning_point_id VARCHAR"))
|
|
228
230
|
|
|
229
|
-
if 'data_zone'
|
|
230
|
-
print(" -> Upgrading 'discussions' table:
|
|
231
|
-
connection.execute(text("ALTER TABLE discussions
|
|
231
|
+
if 'data_zone' in columns:
|
|
232
|
+
print(" -> Upgrading 'discussions' table: Removing 'data_zone' column.")
|
|
233
|
+
connection.execute(text("ALTER TABLE discussions DROP COLUMN data_zone"))
|
|
234
|
+
|
|
235
|
+
if 'user_data_zone' not in columns:
|
|
236
|
+
print(" -> Upgrading 'discussions' table: Adding 'user_data_zone' column.")
|
|
237
|
+
connection.execute(text("ALTER TABLE discussions ADD COLUMN user_data_zone TEXT"))
|
|
238
|
+
|
|
239
|
+
if 'discussion_data_zone' not in columns:
|
|
240
|
+
print(" -> Upgrading 'discussions' table: Adding 'discussion_data_zone' column.")
|
|
241
|
+
connection.execute(text("ALTER TABLE discussions ADD COLUMN discussion_data_zone TEXT"))
|
|
242
|
+
|
|
243
|
+
if 'personality_data_zone' not in columns:
|
|
244
|
+
print(" -> Upgrading 'discussions' table: Adding 'personality_data_zone' column.")
|
|
245
|
+
connection.execute(text("ALTER TABLE discussions ADD COLUMN personality_data_zone TEXT"))
|
|
246
|
+
|
|
247
|
+
if 'memory' not in columns:
|
|
248
|
+
print(" -> Upgrading 'discussions' table: Adding 'memory' column.")
|
|
249
|
+
connection.execute(text("ALTER TABLE discussions ADD COLUMN memory TEXT"))
|
|
232
250
|
|
|
233
251
|
print("Database schema is up to date.")
|
|
234
|
-
# This is important to apply the ALTER TABLE statements
|
|
235
252
|
connection.commit()
|
|
236
253
|
|
|
237
254
|
except Exception as e:
|
|
@@ -388,7 +405,6 @@ class LollmsDiscussion:
|
|
|
388
405
|
object.__setattr__(self, '_is_db_backed', db_manager is not None)
|
|
389
406
|
|
|
390
407
|
object.__setattr__(self, '_system_prompt', None)
|
|
391
|
-
|
|
392
408
|
if self._is_db_backed:
|
|
393
409
|
if not db_discussion_obj and not discussion_id:
|
|
394
410
|
raise ValueError("Either discussion_id or db_discussion_obj must be provided for DB-backed discussions.")
|
|
@@ -490,7 +506,10 @@ class LollmsDiscussion:
|
|
|
490
506
|
proxy = SimpleNamespace()
|
|
491
507
|
proxy.id = id or str(uuid.uuid4())
|
|
492
508
|
proxy.system_prompt = None
|
|
493
|
-
proxy.
|
|
509
|
+
proxy.user_data_zone = None
|
|
510
|
+
proxy.discussion_data_zone = None
|
|
511
|
+
proxy.personality_data_zone = None
|
|
512
|
+
proxy.memory = None
|
|
494
513
|
proxy.participants = {}
|
|
495
514
|
proxy.active_branch_id = None
|
|
496
515
|
proxy.discussion_metadata = {}
|
|
@@ -601,7 +620,23 @@ class LollmsDiscussion:
|
|
|
601
620
|
|
|
602
621
|
return [LollmsMessage(self, orm) for orm in reversed(branch_orms)]
|
|
603
622
|
|
|
604
|
-
|
|
623
|
+
def get_full_data_zone(self):
|
|
624
|
+
"""Assembles all data zones into a single, formatted string for the prompt."""
|
|
625
|
+
parts = []
|
|
626
|
+
# If memory is not empty, add it to the list of zones.
|
|
627
|
+
if self.memory and self.memory.strip():
|
|
628
|
+
parts.append(f"-- Memory --\n{self.memory.strip()}")
|
|
629
|
+
if self.user_data_zone and self.user_data_zone.strip():
|
|
630
|
+
parts.append(f"-- User Data Zone --\n{self.user_data_zone.strip()}")
|
|
631
|
+
if self.discussion_data_zone and self.discussion_data_zone.strip():
|
|
632
|
+
parts.append(f"-- Discussion Data Zone --\n{self.discussion_data_zone.strip()}")
|
|
633
|
+
if self.personality_data_zone and self.personality_data_zone.strip():
|
|
634
|
+
parts.append(f"-- Personality Data Zone --\n{self.personality_data_zone.strip()}")
|
|
635
|
+
|
|
636
|
+
# Join the zones with double newlines for clear separation in the prompt.
|
|
637
|
+
return "\n\n".join(parts)
|
|
638
|
+
|
|
639
|
+
|
|
605
640
|
def chat(
|
|
606
641
|
self,
|
|
607
642
|
user_message: str,
|
|
@@ -650,7 +685,7 @@ class LollmsDiscussion:
|
|
|
650
685
|
where the 'ai_message' will contain rich metadata if an agentic turn was used.
|
|
651
686
|
"""
|
|
652
687
|
callback = kwargs.get("streaming_callback")
|
|
653
|
-
|
|
688
|
+
# extract personality data
|
|
654
689
|
if personality is not None:
|
|
655
690
|
object.__setattr__(self, '_system_prompt', personality.system_prompt)
|
|
656
691
|
|
|
@@ -660,8 +695,8 @@ class LollmsDiscussion:
|
|
|
660
695
|
# --- Static Data Source ---
|
|
661
696
|
if callback:
|
|
662
697
|
callback("Loading static personality data...", MSG_TYPE.MSG_TYPE_STEP, {"id": "static_data_loading"})
|
|
663
|
-
|
|
664
|
-
|
|
698
|
+
if personality.data_source:
|
|
699
|
+
self.personality_data_zone = personality.data_source.strip()
|
|
665
700
|
|
|
666
701
|
elif callable(personality.data_source):
|
|
667
702
|
# --- Dynamic Data Source ---
|
|
@@ -703,8 +738,9 @@ class LollmsDiscussion:
|
|
|
703
738
|
if callback:
|
|
704
739
|
callback(f"Retrieved data successfully.", MSG_TYPE.MSG_TYPE_STEP_END, {"id": dr_id, "data_snippet": retrieved_data[:200]})
|
|
705
740
|
|
|
706
|
-
|
|
707
|
-
|
|
741
|
+
|
|
742
|
+
if retrieved_data:
|
|
743
|
+
self.personality_data_zone = retrieved_data.strip()
|
|
708
744
|
|
|
709
745
|
except Exception as e:
|
|
710
746
|
trace_exception(e)
|
|
@@ -714,7 +750,7 @@ class LollmsDiscussion:
|
|
|
714
750
|
trace_exception(e)
|
|
715
751
|
if callback:
|
|
716
752
|
callback(f"An error occurred during query generation: {e}", MSG_TYPE.MSG_TYPE_EXCEPTION, {"id": qg_id})
|
|
717
|
-
|
|
753
|
+
|
|
718
754
|
# Determine effective MCPs by combining personality defaults and turn-specific overrides
|
|
719
755
|
effective_use_mcps = use_mcps
|
|
720
756
|
if personality and hasattr(personality, 'active_mcps') and personality.active_mcps:
|
|
@@ -945,11 +981,19 @@ class LollmsDiscussion:
|
|
|
945
981
|
|
|
946
982
|
branch = self.get_branch(branch_tip_id)
|
|
947
983
|
|
|
948
|
-
# Combine system prompt and
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
984
|
+
# Combine system prompt and data zones
|
|
985
|
+
system_prompt_part = (self._system_prompt or "").strip()
|
|
986
|
+
data_zone_part = self.get_full_data_zone() # This now returns a clean, multi-part block or an empty string
|
|
987
|
+
full_system_prompt = ""
|
|
988
|
+
|
|
989
|
+
# Combine them intelligently
|
|
990
|
+
if system_prompt_part and data_zone_part:
|
|
991
|
+
full_system_prompt = f"{system_prompt_part}\n\n{data_zone_part}"
|
|
992
|
+
elif system_prompt_part:
|
|
993
|
+
full_system_prompt = system_prompt_part
|
|
994
|
+
else:
|
|
995
|
+
full_system_prompt = data_zone_part
|
|
996
|
+
|
|
953
997
|
|
|
954
998
|
participants = self.participants or {}
|
|
955
999
|
|
|
@@ -1107,6 +1151,73 @@ class LollmsDiscussion:
|
|
|
1107
1151
|
self.touch()
|
|
1108
1152
|
print(f"[INFO] Discussion auto-pruned. {len(messages_to_prune)} messages summarized. History preserved.")
|
|
1109
1153
|
|
|
1154
|
+
def memorize(self, branch_tip_id: Optional[str] = None):
|
|
1155
|
+
"""
|
|
1156
|
+
Analyzes the current discussion, extracts key information suitable for long-term
|
|
1157
|
+
memory, and appends it to the discussion's 'memory' field.
|
|
1158
|
+
|
|
1159
|
+
This is intended to build a persistent knowledge base about user preferences,
|
|
1160
|
+
facts, and context that can be useful across different future discussions.
|
|
1161
|
+
|
|
1162
|
+
Args:
|
|
1163
|
+
branch_tip_id: The ID of the message to use as the end of the context
|
|
1164
|
+
for memory extraction. Defaults to the active branch.
|
|
1165
|
+
"""
|
|
1166
|
+
try:
|
|
1167
|
+
# 1. Get the current conversation context
|
|
1168
|
+
discussion_context = self.export("markdown", branch_tip_id=branch_tip_id)
|
|
1169
|
+
if not discussion_context.strip():
|
|
1170
|
+
print("[INFO] Memorize: Discussion is empty, nothing to memorize.")
|
|
1171
|
+
return
|
|
1172
|
+
|
|
1173
|
+
# 2. Formulate the prompt for the LLM
|
|
1174
|
+
system_prompt = (
|
|
1175
|
+
"You are a Memory Extractor AI. Your task is to analyze a conversation "
|
|
1176
|
+
"and extract only the most critical pieces of information that would be "
|
|
1177
|
+
"valuable for a future, unrelated conversation with the same user. "
|
|
1178
|
+
"Focus on: \n"
|
|
1179
|
+
"- Explicit user preferences, goals, or facts about themselves.\n"
|
|
1180
|
+
"- Key decisions or conclusions reached.\n"
|
|
1181
|
+
"- Important entities, projects, or topics mentioned that are likely to recur.\n"
|
|
1182
|
+
"Format the output as a concise list of bullet points. Be brief and factual. "
|
|
1183
|
+
"If no new, significant long-term information is present, output the single word: 'NOTHING'."
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
prompt = (
|
|
1187
|
+
"Analyze the following discussion and extract key information for long-term memory:\n\n"
|
|
1188
|
+
f"--- Conversation ---\n{discussion_context}\n\n"
|
|
1189
|
+
"--- Extracted Memory Points (as a bulleted list) ---"
|
|
1190
|
+
)
|
|
1191
|
+
|
|
1192
|
+
# 3. Call the LLM to extract information
|
|
1193
|
+
print("[INFO] Memorize: Extracting key information from discussion...")
|
|
1194
|
+
extracted_info = self.lollmsClient.generate_text(
|
|
1195
|
+
prompt,
|
|
1196
|
+
system_prompt=system_prompt,
|
|
1197
|
+
n_predict=512, # A reasonable length for a summary
|
|
1198
|
+
temperature=0.1, # Low temperature for factual extraction
|
|
1199
|
+
top_k=10,
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
# 4. Process and append the information
|
|
1203
|
+
if extracted_info and "NOTHING" not in extracted_info.upper():
|
|
1204
|
+
new_memory_entry = extracted_info.strip()
|
|
1205
|
+
|
|
1206
|
+
# Format with a timestamp for context
|
|
1207
|
+
timestamp = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
1208
|
+
formatted_entry = f"\n\n--- Memory entry from {timestamp} ---\n{new_memory_entry}"
|
|
1209
|
+
|
|
1210
|
+
current_memory = self.memory or ""
|
|
1211
|
+
self.memory = (current_memory + formatted_entry).strip()
|
|
1212
|
+
self.touch() # Mark as updated and save if autosave is on
|
|
1213
|
+
print(f"[INFO] Memorize: New information added to long-term memory.")
|
|
1214
|
+
else:
|
|
1215
|
+
print("[INFO] Memorize: No new significant information found to add to memory.")
|
|
1216
|
+
|
|
1217
|
+
except Exception as e:
|
|
1218
|
+
trace_exception(e)
|
|
1219
|
+
print(f"[ERROR] Memorize: Failed to extract memory. {e}")
|
|
1220
|
+
|
|
1110
1221
|
def count_discussion_tokens(self, format_type: str, branch_tip_id: Optional[str] = None) -> int:
|
|
1111
1222
|
"""Counts the number of tokens in the exported discussion content.
|
|
1112
1223
|
|
|
@@ -1141,27 +1252,134 @@ class LollmsDiscussion:
|
|
|
1141
1252
|
|
|
1142
1253
|
return self.lollmsClient.count_tokens(text_to_count)
|
|
1143
1254
|
|
|
1144
|
-
def get_context_status(self, branch_tip_id: Optional[str] = None) -> Dict[str,
|
|
1145
|
-
"""
|
|
1255
|
+
def get_context_status(self, branch_tip_id: Optional[str] = None) -> Dict[str, Any]:
|
|
1256
|
+
"""
|
|
1257
|
+
Returns a detailed breakdown of the context size and its components.
|
|
1146
1258
|
|
|
1147
|
-
This provides a snapshot of the context usage,
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
for pruning calculations.
|
|
1259
|
+
This provides a comprehensive snapshot of the context usage, including the
|
|
1260
|
+
content and token count for each part of the prompt (system prompt, data zones,
|
|
1261
|
+
pruning summary, and message history). The token counts are based on the
|
|
1262
|
+
"lollms_text" export format, which is the format used for pruning calculations.
|
|
1151
1263
|
|
|
1152
1264
|
Args:
|
|
1153
1265
|
branch_tip_id: The ID of the message branch to measure. Defaults
|
|
1154
1266
|
to the active branch.
|
|
1155
1267
|
|
|
1156
1268
|
Returns:
|
|
1157
|
-
A dictionary with
|
|
1269
|
+
A dictionary with a detailed breakdown:
|
|
1270
|
+
{
|
|
1271
|
+
"max_tokens": int | None,
|
|
1272
|
+
"current_tokens": int,
|
|
1273
|
+
"zones": {
|
|
1274
|
+
"system_prompt": {"content": str, "tokens": int},
|
|
1275
|
+
"memory": {"content": str, "tokens": int},
|
|
1276
|
+
"user_data_zone": {"content": str, "tokens": int},
|
|
1277
|
+
"discussion_data_zone": {"content": str, "tokens": int},
|
|
1278
|
+
"personality_data_zone": {"content": str, "tokens": int},
|
|
1279
|
+
"pruning_summary": {"content": str, "tokens": int},
|
|
1280
|
+
"message_history": {"content": str, "tokens": int, "message_count": int}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
Zones are only included if they contain content.
|
|
1158
1284
|
"""
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
"current_tokens":
|
|
1162
|
-
"
|
|
1285
|
+
result = {
|
|
1286
|
+
"max_tokens": self.max_context_size,
|
|
1287
|
+
"current_tokens": 0,
|
|
1288
|
+
"zones": {}
|
|
1289
|
+
}
|
|
1290
|
+
total_tokens = 0
|
|
1291
|
+
|
|
1292
|
+
# 1. System Prompt
|
|
1293
|
+
system_prompt_text = (self._system_prompt or "").strip()
|
|
1294
|
+
if system_prompt_text:
|
|
1295
|
+
# We count tokens for the full block as it would appear in the prompt
|
|
1296
|
+
full_block = f"!@>system:\n{system_prompt_text}\n"
|
|
1297
|
+
tokens = self.lollmsClient.count_tokens(full_block)
|
|
1298
|
+
result["zones"]["system_prompt"] = {
|
|
1299
|
+
"content": system_prompt_text,
|
|
1300
|
+
"tokens": tokens
|
|
1301
|
+
}
|
|
1302
|
+
total_tokens += tokens
|
|
1303
|
+
|
|
1304
|
+
# 2. All Data Zones
|
|
1305
|
+
zones_to_process = {
|
|
1306
|
+
"memory": self.memory,
|
|
1307
|
+
"user_data_zone": self.user_data_zone,
|
|
1308
|
+
"discussion_data_zone": self.discussion_data_zone,
|
|
1309
|
+
"personality_data_zone": self.personality_data_zone,
|
|
1163
1310
|
}
|
|
1164
1311
|
|
|
1312
|
+
for name, content in zones_to_process.items():
|
|
1313
|
+
content_text = (content or "").strip()
|
|
1314
|
+
if content_text:
|
|
1315
|
+
# Mimic the formatting from get_full_data_zone for accurate token counting
|
|
1316
|
+
header = f"-- {name.replace('_', ' ').title()} --\n"
|
|
1317
|
+
full_block = f"{header}{content_text}"
|
|
1318
|
+
# In lollms_text format, zones are part of the system message, so we add separators
|
|
1319
|
+
# This counts the standalone block.
|
|
1320
|
+
tokens = self.lollmsClient.count_tokens(full_block)
|
|
1321
|
+
result["zones"][name] = {
|
|
1322
|
+
"content": content_text,
|
|
1323
|
+
"tokens": tokens
|
|
1324
|
+
}
|
|
1325
|
+
# Note: The 'export' method combines these into one system prompt.
|
|
1326
|
+
# For this breakdown, we count them separately. The total will be a close approximation.
|
|
1327
|
+
|
|
1328
|
+
# 3. Pruning Summary
|
|
1329
|
+
pruning_summary_text = (self.pruning_summary or "").strip()
|
|
1330
|
+
if pruning_summary_text and self.pruning_point_id:
|
|
1331
|
+
full_block = f"!@>system:\n--- Conversation Summary ---\n{pruning_summary_text}\n"
|
|
1332
|
+
tokens = self.lollmsClient.count_tokens(full_block)
|
|
1333
|
+
result["zones"]["pruning_summary"] = {
|
|
1334
|
+
"content": pruning_summary_text,
|
|
1335
|
+
"tokens": tokens
|
|
1336
|
+
}
|
|
1337
|
+
total_tokens += tokens
|
|
1338
|
+
|
|
1339
|
+
# 4. Message History
|
|
1340
|
+
branch_tip_id = branch_tip_id or self.active_branch_id
|
|
1341
|
+
messages_text = ""
|
|
1342
|
+
message_count = 0
|
|
1343
|
+
if branch_tip_id:
|
|
1344
|
+
branch = self.get_branch(branch_tip_id)
|
|
1345
|
+
messages_to_render = branch
|
|
1346
|
+
|
|
1347
|
+
# Adjust for pruning to get the active set of messages
|
|
1348
|
+
if self.pruning_summary and self.pruning_point_id:
|
|
1349
|
+
pruning_index = -1
|
|
1350
|
+
for i, msg in enumerate(branch):
|
|
1351
|
+
if msg.id == self.pruning_point_id:
|
|
1352
|
+
pruning_index = i
|
|
1353
|
+
break
|
|
1354
|
+
if pruning_index != -1:
|
|
1355
|
+
messages_to_render = branch[pruning_index:]
|
|
1356
|
+
|
|
1357
|
+
message_parts = []
|
|
1358
|
+
for msg in messages_to_render:
|
|
1359
|
+
sender_str = msg.sender.replace(':', '').replace('!@>', '')
|
|
1360
|
+
content = msg.content.strip()
|
|
1361
|
+
if msg.images:
|
|
1362
|
+
content += f"\n({len(msg.images)} image(s) attached)"
|
|
1363
|
+
msg_text = f"!@>{sender_str}:\n{content}\n"
|
|
1364
|
+
message_parts.append(msg_text)
|
|
1365
|
+
|
|
1366
|
+
messages_text = "".join(message_parts)
|
|
1367
|
+
message_count = len(messages_to_render)
|
|
1368
|
+
|
|
1369
|
+
if messages_text:
|
|
1370
|
+
tokens = self.lollmsClient.count_tokens(messages_text)
|
|
1371
|
+
result["zones"]["message_history"] = {
|
|
1372
|
+
"content": messages_text,
|
|
1373
|
+
"tokens": tokens,
|
|
1374
|
+
"message_count": message_count
|
|
1375
|
+
}
|
|
1376
|
+
total_tokens += tokens
|
|
1377
|
+
|
|
1378
|
+
# Finalize the total count. This re-calculates based on the actual export format
|
|
1379
|
+
# for maximum accuracy, as combining zones can slightly change tokenization.
|
|
1380
|
+
result["current_tokens"] = self.count_discussion_tokens("lollms_text", branch_tip_id)
|
|
1381
|
+
|
|
1382
|
+
return result
|
|
1165
1383
|
def switch_to_branch(self, branch_id):
|
|
1166
1384
|
self.active_branch_id = branch_id
|
|
1167
1385
|
|
|
@@ -1170,15 +1388,21 @@ class LollmsDiscussion:
|
|
|
1170
1388
|
if self.metadata is None:
|
|
1171
1389
|
self.metadata = {}
|
|
1172
1390
|
discussion = self.export("markdown")[0:1000]
|
|
1173
|
-
|
|
1391
|
+
system_prompt="You are a title builder out of a discussion."
|
|
1392
|
+
prompt = f"""Build a title for the following discussion:
|
|
1174
1393
|
{discussion}
|
|
1175
1394
|
...
|
|
1176
1395
|
"""
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1396
|
+
title_generation_schema = {
|
|
1397
|
+
"type": "object",
|
|
1398
|
+
"properties": {
|
|
1399
|
+
"title": {"type": "string", "description": "Short, catchy title for the discussion."},
|
|
1400
|
+
},
|
|
1401
|
+
"required": ["title"],
|
|
1402
|
+
"description": "JSON object as title of the discussion."
|
|
1403
|
+
}
|
|
1404
|
+
infos = self.lollmsClient.generate_structured_content(prompt = prompt, system_prompt=system_prompt, schema = title_generation_schema)
|
|
1405
|
+
discussion_title = infos["title"]
|
|
1182
1406
|
new_metadata = (self.metadata or {}).copy()
|
|
1183
1407
|
new_metadata['title'] = discussion_title
|
|
1184
1408
|
|
|
@@ -93,10 +93,18 @@ def robust_json_parser(json_string: str) -> dict:
|
|
|
93
93
|
ValueError: If parsing fails after all correction attempts.
|
|
94
94
|
"""
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
|
|
97
|
+
# STEP 0: Attempt to parse directly
|
|
98
|
+
try:
|
|
99
|
+
return json.loads(json_string)
|
|
100
|
+
except json.JSONDecodeError as e:
|
|
101
|
+
trace_exception(e)
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# STEP 1: Remove code block wrappers if present (e.g., ```json ... ```)
|
|
97
105
|
json_string = re.sub(r"^```(?:json)?\s*|\s*```$", '', json_string.strip())
|
|
98
106
|
|
|
99
|
-
# STEP
|
|
107
|
+
# STEP 2: Attempt to parse directly
|
|
100
108
|
try:
|
|
101
109
|
return json.loads(json_string)
|
|
102
110
|
except json.JSONDecodeError:
|